程序设计基本概念,说穿了就是回答三个问题:程序是什么、程序怎么运行、写程序的人该用什么方式思考。我在带新人的时候发现,90%的人卡住不是因为智商,而是因为一开始就在错误的方向上使劲——背语法、刷题库,却从来没弄明白那一行行代码落到计算机里到底发生了什么。这篇文章想做的事情很简单:把“程序设计”这四个字拆开揉碎了讲清楚。如果你是想入门的零基础,或者是自学了几个月还觉得一头雾水的朋友,可以把它当成一张地图,用来建立主干,再往里填细节。
1. 程序执行前的那几步:源码、编译器与运行时各司其职
1.1 机器只认指令,代码是写给人的说明书
很多新手把“写代码”理解成“给计算机下命令”,这个理解不算错,但容易让人产生一种错觉:好像我写一句,计算机就听懂一句。实际上计算机本身非常“笨”,它只认由0和1组成的机器指令,不认任何人类语言。你写的 Python、Java、C++,本质上都是“写给人看的翻译稿”——为了让你能看懂、能维护、能和别人协作,人类才发明了这些高级语言。
所以程序设计的第一课,其实是建立“中间层”的直觉。你写print("你好"),不是说屏幕上就该出“你好”,而是说:有个翻译工具把你的意图翻译成一串机器指令,操作系统加载这些指令,CPU 再一条一条执行,最终驱动显示器输出文字。这套链路里,你写的代码只是起点。
我记得有次带个新人,他一直不理解为什么同一个程序换个机器会报错,后来我让他手动执行一遍“编译-运行-报错”的完整流程,他才意识到:代码是需要经过“加工处理”才能运行的原料,不是成品。这个认知比会写几十行代码重要得多。
1.2 编译与解释:两条不同的“翻译路线”
翻译代码有两种主流方式,对应两类语言,理解这个区分能解释很多“玄学报错”。
编译型语言(比如 C、C++、Go)像“先翻译整本书再阅读”。你写完代码后,编译器把整份源码一次性翻译成机器码,生成一个可执行文件。这个文件可以直接运行,运行的时候不再需要源码和编译器。优点是执行快、运行时不依赖环境;缺点是改一行代码往往要重新编译整个项目,起步慢。
解释型语言(比如 Python、JavaScript)像“请个翻译坐在旁边,边读边翻”。解释器读取一行源码,翻译一行,执行一行。好处是开发反馈极快,写了就能跑,调试方便;坏处是运行效率通常比编译型低一些,而且跑起来的那一刻必须有解释器在场。
对初学者来说,我不建议把“选哪条路线”看得太重。Python 解释型适合快速验证想法,C 编译型适合理解底层机制。两种都学一遍,对程序的敬畏感会更强。下面这张表可以帮你直观对照:
| 维度 | 编译型(以 C 为代表) | 解释型(以 Python 为代表) |
|---|---|---|
| 翻译时机 | 运行前一次性编译 | 运行时逐行翻译 |
| 运行前提 | 需要先生成可执行文件 | 需要安装对应解释器 |
| 修改后能否直接跑 | 一般要重新编译 | 直接改,直接跑 |
| 运行速度 | 快 | 相对较慢 |
| 学习价值 | 能看清内存与指令的细节 | 能快速建立业务逻辑 |
1.3 变量名的背后是一块真实内存
再深入一层:代码里的变量名,其实不是给你用的“装饰品”,而是程序员为了方便才创造出来的抽象。真实的程序运行过程中,数据都存在内存(RAM)里,内存像一排排编好号码的小房间。你声明一个变量age = 25时,解释器或编译器会做两件事:找一间空闲的小房间来存放 25 这个值,然后在内部记下一张映射表——“age 对应地址 XXXX”。你写代码时可以只管age,但机器执行时真正访问的是那个编号地址。
这里有个非常重要的推论:变量名只是门的标签,标签可以换,房间里的值才是关键。很多新手困惑“我把 a 赋值给 b,后来 a 变了,为什么 b 也跟着变”,这通常涉及“引用”而不是“值”的概念,本质是两个标签贴到了同一个小房间的门口。这类问题在讲数据类型和对象时会反复出现,但根子就在这里。
还有一层要理解:栈和堆。函数里的局部变量一般放在栈上,空间自动分配、自动回收,速度快但容量有限;而程序运行过程中动态创建的大块数据,往往放在堆上,需要手动管理或靠垃圾回收。新手不必一上来就啃内存细节,但至少要明白一个心法:程序里没有鬼,所有诡异现象都能顺着“数据在哪、标签指向谁、谁分配谁回收”这三条线查出来。
提示:如果你刚开始学,不要急着把《深入理解计算机系统》啃下来。先记住“源码要经过翻译才能运行”“变量名背后是内存地址”“数据有存放的位置和生命周期”这三件事,够用很久。
2. 变量与数据类型:内存里的储物格,别乱装东西
2.1 声明与赋值:先做盒子,再往里面放东西
变量这个概念,每个初学程序设计的人都与之打过照面,但很少有人细想过“声明”和“赋值”的区别。用储物格来类比:声明变量相当于找一只盒子,并贴上标签;赋值相当于往盒子里放入物品。在 Python 这种动态类型语言里,声明和赋值常常一步完成,x = 10同时完成了“创建盒子”“贴标签”“放东西”三件事。在 C 这类静态类型语言里,你得先说“我要一只装整数的盒子”,再决定要不要立刻放东西。
我建议新手做一个小练习:尝试在你的语言里写出“声明一个变量但不赋值”,然后看看会发生什么。Python 里直接使用未赋值的变量会报NameError;C 语言里未初始化的局部变量会有一个不确定的“随机垃圾值”。这个差异非常有意思,它反映了两种设计哲学:Python 宁可报错也不让你用脏数据,C 信任你把内存管理好。理解了这种哲学差异,你就不会觉得“为什么这门语言这么烦”了。
2.2 数据类型决定了盒子的形状
数据类型说白了就是盒子的形状规则。整数盒子里不能放一串文字,浮点盒子用来放带小数点的数,布尔盒子只能装真或假。让类型体系存在,是为了让计算机知道“该为这块数据分配多少内存”“如何解释这块内存里的二进制”。
下面是一张入门阶段最实用的常见类型对照表,无论你学哪门语言,都能对号入座:
| 类型 | 通俗解释 | 典型例子 | 常见陷阱 |
|---|---|---|---|
| 整数(int) | 装整数的盒子 | 42、-17 | 处理除法时结果可能截断 |
| 浮点数(float) | 装小数,但有精度问题 | 3.14、0.1 | 0.1+0.2 可能不等于 0.3 |
| 字符串(string) | 装一串字符 | "hello"、"123" | 与数字拼接时可能隐式转换 |
| 布尔(bool) | 装真/假 | true、false | 把“相等”写成“赋值” |
| 数组/列表(list) | 装一堆同类型的盒子 | [1,2,3] | 索引从 0 开始,越界报错 |
| 字典/哈希表(dict/hash) | 装“键值对”的盒子群 | {"name":"张三"} | 键必须可哈希,顺序不定 |
为什么要花时间理解类型?“类型就是一种约束”这句话可以回答几乎所有问题。约束虽然麻烦,但它能在你犯错的第一时间提醒你:你这里想放字符串还是数字,是不是搞混了?很多隐蔽 bug 就来自类型混用,比如把用户输入的“年龄”字符串直接拿去算年份,结果拼接出了一个奇怪的字符串。
2.3 作用域:盒子的可见范围
在你学会函数之后,“作用域”会成为天天打照面的事情。作用域指的是一个变量“从哪个位置开始能访问,到哪个位置失效”。你可以把它理解成:每个储物格都有所属的房间。在if或循环内部创建的变量,出了那个房间可能就找不到了;在文件顶部创建的全局变量,各房间都能访问,但谁都能改,容易出事。
我见过太多新手因全局变量吃了暗亏:几个函数共享一个全局状态,改着改着就乱了,最后查 bug 查到怀疑人生。我的建议很朴素:刚学编程时尽量不要用全局变量,所有数据都通过函数参数传进去,通过返回值带出来。这样做一开始会觉得麻烦,但能帮你省掉后面无数个小时的排查时间。
2.4 新手最容易踩的四个类型坑
第一坑是整数除法。C 语言里5 / 2的结果是 2 而不是 2.5,因为两个整数相除,整数盒子装不下小数,结果就被截断。Python 3 里用/会得到 2.5,但如果你写5 // 2,又变成 2。语言不同,规则不同,一定先查文档再下手。
第二坑是浮点精度。0.1 + 0.2在很多语言里会得到一个近似值,比如 0.30000000000000004。这不是程序 bug,而是二进制无法精确表示某些十进制小数的固有限制。凡涉及金额计算,务必使用专门处理十进制的类型,而不是普通浮点。
第三坑是隐式类型转换。某些语言允许字符串和数字相加时“自动转换”,比如 JavaScript 里"1" + 2得到字符串"12"。这种便利在大型项目里会变成排查困难的头号嫌疑犯。我的习惯是:任何跨类型的操作都显式转换,宁可多写两行,不做隐式转换的“乘客”。
第四坑是可变与不可变。字符串和数字这种简单类型往往不可变,意思是“修改”实际上创建了一个新值;而列表、字典这种容器类型可变,多个变量可能同时指向同一个容器。你要时刻问自己:我是在修改原盒子,还是在做一个新盒子?搞不清这一点,后面学到“引用传参”时会再次撞墙。
3. 控制结构:顺序、分支与循环,程序逻辑的三条主干道
3.1 顺序执行是默认的“走路姿势”
很多人刚开始写程序时,会下意识地认为“代码是一瞬间全部跑完的”,于是对调试时一行行执行的过程感到无比困惑。其实程序最基本的执行方式就是顺序执行:从文件开头,一行一行地往下跑,像沿着一条走廊往前走,直到遇见岔路或终点。这条“默认走廊”是所有控制结构的地基。
理解顺序执行的实操意义在于:当你排查 bug 时,可以顺着代码的物理顺序去推理状态变化。比如:
x = 1 x = x + 2 print(x)这段代码执行到第二行时,右侧先取出x当前的值 1,加上 2 得到 3,再放回盒子,最后输出 3。如果新手能徒手算出每一步之后每个变量的值,写起代码来会踏实得多。我甚至会让新人用一张纸,手动模拟“变量状态表”,一列是行号,一行是每个变量的当前值。这个过程枯燥,但极其有效,它能治好“看不懂运行顺序”的毛病。
3.2 分支:判断条件,决定往哪走
程序真正“有智能”的感觉,来自分支结构。核心就一句话:根据某个布尔条件的真假,选择走哪条路。最常见的写法是if / else if / else,像路口立了一块指示牌:“条件为真往左,否则往右,其他情况走第三个门”。
这里我想强调“条件”的精确定义。一个条件的真假,本质上是对“程序当前状态”做一次提问。比如“如果年龄大于等于 18,就可以进入”,程序会先去看age这个盒子里的值,再比较大小,得出一个布尔结果,最后决定进入哪个分支。新手常见的错误是在应该比较的地方写成赋值:if (age = 18)在部分语言里不是“判断是否等于 18”,而是“把 18 放进 age,然后永远为真”。这类 bug 极其隐蔽,好在现在的编译器多数会给出警告。
分支嵌套也要克制。三层以上的if嵌套,读起来像迷宫。我的建议是:能用逻辑运算符(与、或、非)合并条件就合并;合并不了就优先用早退出(先处理不满足条件的情况,后面代码自然变平)。比如猜数字游戏里:
secret = 7 guess = int(input("猜一个数字:")) # 不推荐的深层嵌套 if guess < 1 or guess > 100: print("输入不合法") else: if guess < secret: print("太小了") elif guess > secret: print("太大了") else: print("中了")把它改成“先处理非法输入,再比较大小”的结构,代码就直白很多。这种改写的思路,等你学到“函数”时会更有体会。
3.3 循环:把重复劳动交给机器
计算机最擅长的事就是重复。循环结构的价值在于:让程序用一段代码反复处理大量数据。while适合“不知道要重复多少次,只知道继续的条件”;for适合“明确知道要遍历多少个元素”。两者都能实现同样的功能,但选对的心法不同。
写循环最怕三件事:死循环、跑多一次、跑少一次。死循环是条件永远为真,程序像进入鬼打墙;跑多一次或跑少一次通常是边界条件没处理好。以“从 1 加到 100”为例:
total = 0 for i in range(1, 101): # 注意 range 是左闭右开区间 total = total + i print(total) # 5050如果你写range(1, 100),就会漏掉 100;写range(1, 101)才是正确的。很多语言里的循环区间都遵循“左闭右开”规则,也就是“包含起始值,不包含结束值”。这个规则刚接触时不适应,一旦形成肌肉记忆,反而能避免大量的边界 bug。
调试循环时,有个操作我特别推荐:在循环体内打印当前的下标和变量值,跑两三轮就停(比如循环 10 次时提前 break)。看着输出规律,你就能立刻判断是初始值错了、边界错了还是更新步长错了。等你有经验之后,这种打印日志的方式依然是最可靠的定位手段。
3.4 小例子:把“煮蛋流程”翻译成伪代码
控制结构光讲理论,容易形成“会看不会用”的错觉。我习惯让新人做一个“把日常流程翻译成逻辑”的练习,比如煮鸡蛋:
1. 准备鸡蛋(顺序) 2. 烧水(顺序) 3. 判断水是否开了(分支) 如果没开,等待(循环) 4. 判断想吃溏心还是全熟(分支) 溏心:煮 4 分钟 全熟:煮 8 分钟 5. 关火、捞蛋(顺序)这个流程里出现了顺序、分支、循环三种基本结构,但没有任何一行真正的代码。做这种翻译练习的价值在于:它逼着你把一个模糊的“我想煮鸡蛋”拆解成计算机能执行的最小步骤。如果你能把这种“翻译日常”的功力用熟,写正式代码时思路会异常清晰,因为程序的本质就是把现实里的流程精确地表达出来。
4. 函数与抽象:把大问题切碎的第一把刀
4.1 函数的核心:输入、处理、输出
写过几十行代码之后你会发现,如果所有逻辑全塞在 main 里,代码会越来越长,看后面忘前面,改一个地方牵一发而动全身。这时候就该使用函数了。函数的核心模型特别简单:接收输入,做处理,返回输出。就像一台榨汁机,你把苹果放进去,它给你苹果汁;你把橙子放进去,它给你橙汁。你根本不需要关心榨汁机内部怎么运作,只需要知道“放什么、得到什么”。
函数带来的最重要的能力叫“抽象”:把“怎么做”的细节藏起来,只暴露“做什么”的接口。这一点在团队协作里极其重要。比如一个函数叫calculate_tax(income),调用者只需要传收入,拿回税额,至于税额是怎么算的,是调用者不关心也不需要关心的。我自己写代码时有一条红线:一个函数只做一件事。如果这个函数里既有读取文件、又有清洗数据、还有保存结果,我会毫不犹豫地拆成三个函数。短期看代码变多了,长期看每个函数都可以单独测试、单独修改,这才是真正的省钱。
4.2 参数与返回值:数据进出的两条通道
函数与外界交换数据,主要通过参数和返回值。这里的核心难点是“传值还是传引用”。如果你传进去的是一个简单值(数字、字符串),函数内部怎么改都不影响外面的原变量;但如果你传进去的是一个列表或字典,函数内部修改它,外面的数据也会跟着变。这就像你交给别人一份文件的复印件(值),他随便涂改都不影响原件;但你把原件借给他(引用),他在上面写字,原件就留下痕迹了。
怎么判断?最简单的办法是查语言文档,确认“哪种类型传值、哪种类型传引用”。如果暂时不想被细节困扰,可以在函数开头先把传入的容器对象复制一份,费点内存但图个安心。等经验丰富了,你自然能熟练地在共享状态和防御式复制之间做取舍。
关于返回值,我的建议是“尽量保证任何情况都有明确返回”。有些语言允许函数没有返回值,隐含返回 null,这种写法在分支较多时容易漏掉某条路径,最后造成“数据莫名丢失”的现象。写函数时先把所有分支的返回路径列清楚,比写完再调试快得多。
4.3 递归:函数调用自己,戏法与本钱
递归是新手在函数阶段最容易兴奋也最容易懵的知识点。递归的本质非常简单:函数在运行时调用自己。它用“把大问题缩成同一结构的子问题”的方式解决问题,比如求阶乘:
def factorial(n): if n <= 1: return 1 return n * factorial(n - 1) print(factorial(5)) # 120这段代码里,factorial(5)会先算5 * factorial(4),而factorial(4)又需要factorial(3),一直往下拆到factorial(1),再一层层把结果传回来。这个“先往下拆、再往上回”的过程,就是递归的真相。
写递归有两条铁律:一是必须有终止条件(基线条件),否则会无限递归到程序崩溃;二是每次递归调用都必须让问题规模变小,否则也是白费。实战中,递归代码写起来很优雅,但它有成本:每一次调用都要占用一段栈内存,递归太深会触发“栈溢出”错误。所以我建议新手先学会用普通循环改写简单递归问题,理解两种方案各自的代价,再决定哪种更合适。像“遍历文件夹所有文件”这种问题,递归写法比循环优雅得多,但前提是你明确知道目录深度不会大到爆栈。
4.4 从函数到模块:抽象思维的实际落地
单个函数解决一个小问题,模块则把一组相关函数打包成一个工具箱。你不需要反复发明轮子,只需要声明“我要用哪个工具箱”。这种“搭积木”的思维,是程序设计从“会写代码”迈向“会设计代码”的分水岭。
我见过很多自学者的通病:拥有几十个函数但没有任何组织,文件名也随意,三个月后自己都找不到。组织代码没有灵丹妙药,但有几条朴实的习惯可以少走弯路:按功能域分模块,比如“用户相关的功能放一个文件,订单相关的放另一个”;模块内部的函数尽量不直接操作别的模块的内部数据;给每个函数写一句话注释,说清楚“输入是什么、输出是什么”。做到这三条,你的代码就已经超过了 60% 的业余项目。
5. 算法与数据结构:入门期就应该建立的两块基石
5.1 算法就是做事的步骤,复杂度是“步骤的量级”
很多人把“算法”和“数学题”划等号,一听到就发怵。实际上算法不过是一套有穷的操作步骤。菜谱就是算法:“打两个鸡蛋、加油、翻炒”。程序里的算法也一样,只是步骤要精确到计算机能听懂。你从第一天写程序起,其实已经在用算法,只是没有命名而已。
真正值得花时间理解的,是“复杂度”这个工具。它不关心你的代码实际跑了多少毫秒,而关心“当数据规模变大时,运行时间/内存消耗会以什么方式增长”。比如在 100 个数字里找一个目标,顺序查找最多需要比 100 次;如果在 1000 个数字里找,最多比 1000 次,这叫线性增长,记作 O(n)。而如果你面对的是已经排好序的数据,每次对半查找,100 个数字最多只需要比 7 次,1000 个也只需要比 10 次,增长速度极慢,这叫对数增长,记作 O(log n)。
新手理解复杂度有什么实际好处?它帮你提前做选择。我见过有人用 O(n²) 的两层循环处理几十万条记录,跑了几小时没出结果,换成 O(n log n) 的排序算法后几秒就出来了。不求你一上来就能手写快排,但至少应该能判断:我的代码里有没有“循环套循环”?数据量大了会不会爆炸?这种估算意识才是复杂度学习的核心产出。
5.2 数组、链表、哈希表:先建立存储直觉
数据结构听上去高深,其实是在回答一个问题:数据在内存里怎么摆,才能读得快、写得也快?
数组(列表)是“连续排列的一排储物格”。知道第一个格子的地址,就能通过偏移计算出任意下标的位置,所以按下标随机访问极快。缺点是在中间插入或删除元素时,往往需要把后面的元素整体挪动。
链表是“散落的小盒子,用线串起来”。每个盒子除了装数据,还记录下一个盒子的地址。插入、删除只需要“改一下线头”就行,但想访问第 k 个盒子时,必须从头开始一个线头一个线头地摸过去,不能随机跳。
哈希表(字典)则像“带索引的仓库”:根据一个键,通过哈希函数快速算出一个编号位置,大部分情况下一次访问就能找到值。它用空间换时间,是最常用的数据结构之一。入门阶段不需要掌握哈希表的内部实现,但要建立“用键取值的存取模型”,并理解为什么它通常比在大列表里挨个找要快得多。
我的建议是:先用数组解决所有问题,等它成了性能瓶颈,再去考虑链表、哈希表甚至更复杂的数据结构。过早优化是新手的大忌,但“不知道有更合适的结构”同样是坑。建立直觉的办法很简单:把常见数据结构都画成内存示意小图,旁边标注每种操作的快慢,贴在你写代码时能看到的地方。
5.3 查找与排序:最早遇到也最常用的算法
你在任何项目里几乎都会遇到两件事:找一个东西、排一个顺序。入门期至少要掌握三种查找和三种排序的粗略思想。
查找方面:线性查找适合无序数据,简单但慢;二分查找适合有序数据,每次砍一半,快得惊人,但前提是数据必须已经排序。哈希查找是用空间换时间,读多写少、键值固定的场景尤其好用。排序方面:冒泡排序最容易理解,适合教学;选择排序比冒泡稍好一点;快速排序是实践中常见的高效方案。你不用急着背快排的每一行代码,只需要做一件事:手写一遍冒泡排序,然后反复运行,观察每一轮之后数组的变化。
为什么排序这么基础?因为很多问题一旦数据有序,解法就豁然开朗。比如查重、合并、找中位数,都以有序为前提。你如果能在入门阶段亲手实现一个冒泡排序和二分查找,理解“有序带来的搜索加速”,后面再看任何复杂算法都会从容很多。
6. 编程思维与新手路线图:从看懂代码到自己写出来
6.1 拆解问题的能力比语法熟练更重要
程序设计的基本概念学了一遍之后,真正的考验才刚开始:拿到一个没人教过的问题,你怎么用程序解决它?答案的第一步永远是拆解,而不是写代码。
举个例子,老板给你一个任务:“统计一篇英文文章里出现频率最高的 10 个单词。”如果你直接想代码,大脑会充血。但如果你先拆解:
- 怎么读入整篇文章?
- 怎么把句子切分成一个个单词?
- 怎么统一大小写、去掉标点?
- 怎么统计每个单词出现的次数?
- 怎么排序并取前 10 个?
拆完这五步,每一小步都是你已经学过的基础概念拼接。读文件是 I/O,切分是字符串处理,统计是字典加循环,排序是算法。程序设计高手和初学者的差距,往往不在于谁记得 API 多,而在于谁能把模糊的大问题切成一堆清晰的小问题。
我建议你从今天开始,每次拿到练习题目,先在纸上写步骤,不要打开编辑器。等你写出的步骤里每一条都能对应到“变量、分支、循环、函数”时,再动手写代码。坚持一个月,你会发现写代码变成了一件“翻译”工作,而不是“创造”工作。
6.2 调试不是乱试,是定位与验证
不会调试,就等于不会编程。很多新人遇到程序报错,第一反应是反复修改代码,碰运气式地再跑一次。这种“乱试”最大的问题是:你并不确定问题出在哪,改对了也不知道为什么对,改错了更不知道往哪走。
我总结了一套适合新手的调试流程,简单但极其管用:
- 先读错误信息的最下面一行,那里往往有错误类型和文件行号。
- 在那个位置的上下 10 行内,找出“嫌疑变量”。
- 在关键节点打印这些变量的值,观察哪个和预期不符。
- 把代码二分:在中间位置加一个打印,判断问题在前半段还是后半段。
- 连续缩小范围,直到定位到某一行,再针对它思考修复方案。
这套流程的逻辑是“用排除法缩小问题空间”,而不依赖灵光一现。对报错信息也要辩证看:编译器的报错提示绝大多数时候是准确的,但有时候报错的位置只是“症状爆发点”,真正的问题在更早的地方。比如一个变量在某处是 null,直到被使用时才报错。这时候你有两张牌可打:回溯追踪“这个变量从哪里来”,以及用日志记录它每一步的值。
提示:千万不要在“困得不行”“心情烦躁”的时候调试。写代码的人都知道,高质量调试需要清醒的头脑。如果找了两小时还没头绪,起来倒杯水、走一圈、把代码结构从头到尾给别人讲一遍,答案往往自己就跳出来了。
6.3 给新手的三个月路线图与每日习惯
学习路线不需要太长,但要有明确的“产出”意识。我给零基础朋友的路线是这样的:
第一个月:建立主干。只学变量、类型、分支、循环、函数这五件事。每天写至少 3 个小练习,比如“输出九九乘法表”“猜数字游戏”“统计一句话里的元音字母”。这个阶段不碰框架、不碰复杂项目,越枯燥的基础越要练扎实。
第二个月:进入数据结构。数组、字符串、字典、简单排序与查找。开始尝试写“通讯录管理”“记账本”“待办清单”这类需要存储数据的小程序。这些项目不惊吓,但能逼你把“控制结构+函数+数据结构”组合起来用。
第三个月:做综合小项目并学习调试。比如写一个“随机出题考试系统”、做一个小型命令行工具。这个阶段的重点不是功能多炫,而是你能否独立完成“拆解—编码—调试—修改”的完整闭环。
每日习惯方面,我特别推荐“复述代码法”:拿到一段别人写的代码,先不看答案,自己从头写一遍,然后对照差异。不要抄,而是像复述故事一样用自己的话和风格实现同样的功能。三个月后你会发现,你对语法和 API 的记忆远超天天看教程但不动手的同辈。
6.4 我带新人时反复强调的三句话
第一句是“代码是写给人看的,顺便让机器执行”。所以命名要清晰、结构要整洁、注释要说明“为什么”而不是“是什么”。变量叫a、b、c的项目,三个月后连作者本人都不想维护。
第二句是“先让它跑起来,再让它正确,最后让它优雅”。很多新手想一步到位写出完美代码,结果卡在第一步。你先用最笨的办法实现功能,哪怕嵌套很深、变量很丑,只要跑通了,你就有了可验证的基准;然后再逐步重构,每次改动小一点,验证一次。这个习惯能让你的编程质量稳步提升。
第三句是“报错不可怕,可怕的是不看报错”。我见过新人把一整屏的错误日志截图发过来,却说“我都没看,不知道什么意思”。其实耐心的第一课,就是逼自己读完那条红色的报错。大多数问题,在你按下回车之前已经有无数前辈踩过坑,把答案留在了报错信息里。
程序设计的基本概念掰开来看,就是这一套东西:程序怎么运行、数据怎么存放、逻辑怎么流转、大问题怎么拆、小函数怎么写、数据规模怎么预估。把这些主干想清楚,具体语言的语法细节不过是换了一副手套。我带人的经验里,凡是先把这些概念嚼烂的人,换语言、学新框架都很快;凡是只想“学会某语言 100 个技巧”的人,过几个月多半又回到了原点。所以我的最终建议只有一条:慢即是快。把基本概念反复吃透,直到你能用人话向一个外行解释清楚什么是变量、什么是循环、什么是函数,那个时候,你已经是一名真正的程序员了。