📖 名词溯源:program 为什么叫「程序」? 英文 program 源自古希腊语 programma,本义是「事先写出来的公告、节目单」。一场演出开始前,节目单会提前写好「第一首、第二首、第三首……」的顺序。程序也是如此:它是一份事先写好的、有固定顺序的行动清单,计算机照着清单一条条执行。「程序」这个中文译名,取的正是「事先排好的次序」这层意思。
🎯 本章学习目标
- 用一句话说清「编程」是什么,以及「程序」「指令」这两个核心词的含义。
- 理解程序为什么必须是一串精确、无歧义、有序的指令。
- 看懂计算机「执行」程序时,硬件层面真正发生了什么(取指→译码→执行)。
- 理解人机「对话」的本质:计算机零容错,人类则靠语境和常识。
- 学会用「伪代码」把一件日常任务拆成一条条可照做的指令清单。
- 会估算 CPU 执行指令的速度,体会「机械循环 × 极高频率」的威力。
1.1先给计算机「下命令」:指令与程序
想象你雇了一个从零开始、什么常识都没有的机器人厨师。你想让它做一份煎蛋。你会怎么跟它说?
如果你只说一句「给我做个煎蛋」,机器人会原地发呆——因为它不知道「煎蛋」意味着什么,更不知道该从哪一步做起。你必须一步一步拆开讲:
- 从冰箱拿出一个鸡蛋。
- 把平底锅放到灶台上。
- 打开火,调到中火。
- 往锅里倒一点油。
- 把鸡蛋在碗边磕开,倒进锅里。
- 等 2 分钟,翻面。
- 再等 1 分钟,盛出来。
这每一句「做某件具体的事」,在编程里就对应一条 指令(instruction)。而把这一串指令按顺序组织起来,就构成了一个 程序(program)。
把清单写下来:伪代码
上面这份煎蛋清单,已经非常接近编程里一个重要的过渡工具——伪代码(pseudocode)。伪代码就是「用人话写的程序」:它比日常语言精确(每一步都明确到可以照做),又比真正的代码宽松(不抠语法符号、不管标点缩进)。初学阶段,伪代码是组织思路最顺手的工具——先把步骤想清楚,再考虑怎么写代码。本教材从第 4 章开始写真正的代码,但从这一章起就会大量使用伪代码:它帮助你把注意力放在「做事的顺序」上,而不是被符号规则绊住脚。
写伪代码时有一个关键的判断动作:每一条指令,都必须落在执行者的基本动作清单之内。机器人厨师的基本动作,是工程师预先替它装配好的技能——取物、移动、倒、磕、翻、加热;计算机的基本动作则朴素得多,只有区区几类:存取数据、算术与逻辑运算、比较、跳转。任何复杂任务,最终都要拆解到这几类基本动作的组合上去。你以后写的每一行代码,无论看起来多「高级」,落到硬件上都是这四类基本动作的排列组合——这是全课程最重要的一条因果链,请从今天起就记住它。
核心思想 指令是「让计算机做一件具体小事」的命令;程序是「一组有顺序的指令」的集合。编程(programming),就是编写程序——把一件复杂的大事,拆成计算机能执行的一条条小指令。
这套「拆成清单」的思路,在工程界早就存在,而且用得极其严肃。飞机装配现场的工艺卡(装配指令单)会把一道工序拆成数十个步骤,每一步写明用哪种工具、拧到多少扭矩、检查结果如何留痕;机务维护使用的工卡(检查单)同样逐项列出动作与判据。为什么工程文件要写得这么「啰嗦」?因为执行的人和复核的人可能不是同一个人,执行的时间可能隔了几个星期甚至几年——只有精确到无歧义的指令,才能保证不同的人在不同时间执行出同一个结果。这与程序的要求一模一样:程序之所以可靠,正是因为它把「怎么做」写死到不留任何发挥空间。可以说,工程背景的学习者学习编程,不需要转换思维方式,只需要把「写工艺卡」的严谨程度,平移到「写代码」上来。
📐 例题 1 把「检查一批零件」写成指令清单
车间到货一批销钉零件,共 10 件,验收要求:直径在 9.95~10.05 mm 之间(含边界)为合格。质检员实测直径(mm)依次为:10.02、9.98、10.07、10.00、9.94、10.05、10.01、9.99、10.03、9.96。请把「检查这批零件并统计结果」写成一条条指令,并亲手按指令走一遍,数出合格与不合格件数。
思路 先设计「检查一件」的原子动作:读直径 → 与上下限比较 → 归类计数。再把「检查一件」扩展成「检查一批」:加一个「还有下一件吗」的判断,让流程回到开头;再准备两个计数器(合格数、不合格数),从 0 开始往上加。注意把边界写死——「9.95 ≤ d ≤ 10.05」是闭区间,正好压在边界上的算合格。
解 指令清单(伪代码): ① 设合格数 = 0,不合格数 = 0;② 取下一件零件,测得其直径 d;③ 若 9.95 ≤ d ≤ 10.05,合格数 = 合格数 + 1,否则不合格数 = 不合格数 + 1;④ 若还有未检零件,回到第 ② 步;⑤ 输出合格数与不合格数。逐件执行:10.02✓、9.98✓、10.07✗(超上限)、10.00✓、9.94✗(差 0.01,低于下限)、10.05✓(压边界、算合格)、10.01✓、9.99✓、10.03✓、9.96✓。数得合格 8 件、不合格 2 件。把同样的指令交给 Python 执行,结果应完全一致:
diameters = [10.02, 9.98, 10.07, 10.00, 9.94,
10.05, 10.01, 9.99, 10.03, 9.96]
ok = 0
for d in diameters: # 逐件检查(第 2~4 步的循环)
if 9.95 <= d <= 10.05: # 闭区间判定(第 3 步)
ok += 1 # 合格计数 +1
print(ok, len(diameters) - ok)
运行输出:8 2——合格 8 件,不合格 2 件,与手工执行一致。你现在不用读懂每一行语法,只需对照清单:第 2 行对应「取下一件」,第 3 行对应「比较」,第 4 行对应「计数」,最后一行对应「输出」。伪代码与真代码,骨架相同。
答:合格 8 件、不合格 2 件。 ⚠ 易错点:① 边界归属必须事先写死(含边界 or 不含边界),否则同一批数据、两个执行者会数出两种结果——这正是「无歧义」条款的意义;② 「10.05 恰好压线」这类临界值最容易判错,指令里若写「d < 10.05」,这件合格件就会被误判为不合格。
顺带辨析一个微妙的差别:指令与请求。你对同事说「把温度调高一点」,这是请求——同事会结合经验自行决定调多少;你对机器写「温度 = 85」,这是指令——机器不会变通,也不需要变通。日常语言里我们习惯了请求式表达,所以初学者常把「请求」直接写进程序,然后被机器拒绝。写程序请养成一个自查习惯:每写一行,问自己一句——「如果执行者完全没有常识,这句话还能被执行吗?」能,就留下;不能,就拆细。
伪代码还有一层常被低估的价值:它是算法的「通用语」。工程团队讨论方案时,用伪代码把思路钉在白板上,不同语言背景的工程师都能看懂、都能挑毛病;方案评审时先审伪代码(思路对不对),再审代码(写得规不规范)。本章例题 1 的清单就是活例子:哪怕完全不懂 Python 语法,你也能对照清单读懂那段程序在做什么——因为它的骨架是「清单」,不是「语法」。以后每学一个新概念,先问自己「用伪代码怎么写」,你会发现自己始终在学「思路」,而不是在背「符号」。
所以,编程的第一层真相其实非常简单:
🧠 一句话记忆 编程 = 给计算机写一份「一步一步照做」的行动清单。程序就是这份清单,指令就是清单里的一条。
1.2程序 = 一串精确的指令序列
上一节说程序是「指令的集合」,但光「集合」还不够。一个能跑的程序,它的指令必须满足三个硬条件。缺一个,计算机就会卡住或出错。
条件一:精确,不能含糊
跟人说话,我们可以含糊:「多放点盐」「差不多熟了就出锅」。人靠经验和舌头能猜出你的意思。但计算机不行——它不知道「多」是多少,「差不多」是什么标准。
在程序里,你得写:「加 3 克盐」「当锅里的蛋边缘变白且中心不再流动时翻面」。每个量、每个条件都要精确到计算机能直接判断。
「含糊」的根源,值得多想一层:自然语言的量词没有固定参照系。「多放点盐」里的「点」,取决于你上一次放的量、这锅菜的咸淡、说话人的口味——每听一次,参照系都会漂移。而程序语言里,一切量都有字面值(如 3)或变量(如 salt,指向一个确定的数值)作为唯一参照。写 salt = 3 的那一刻,「盐量」这个概念就被钉死成一个数,任何人、任何时刻读到这一行,理解都完全一致。所谓精确,就是给每个概念锁定一个不可漂移的参照。
还要注意:精确的粒度要恰到好处。「加 3 克盐」对煎蛋是精确的,写成「加 3.0027 克盐」就过了——精度只需满足任务需要。工程上这叫「与需求匹配的精度」:图纸标注公差 ±0.05 mm,就按 ±0.05 mm 检验,不必搬出纳米级仪器较真。精确 ≠ 冗长;把精确用在该用的地方,是写程序(也是写工程文件)的基本功。
条件二:无歧义,一句话只能有一个意思
「把那个箱子搬到那边」——「那个」是哪个?「那边」是哪边?这句话有无数种理解方式,就叫有歧义。程序语言被设计成每个符号、每个词都有且只有一个含义,就是为了彻底消灭歧义。你写 1 + 1,它只能是「一加一」,不可能是别的。
自然语言的歧义比比皆是。经典例子:「咬死了猎人的狗」——是狗被咬死了,还是狗把猎人咬死了?两种解读都成立,人靠语感选一种,机器却没有语感可用。自然语言是几千年自然演化的产物,歧义是它的固有属性,永远清不干净;而程序语言是一种人造的形式语言(formal language):先定一张有限的符号表,再定一套严格的文法(grammar),规定什么样的组合合法、每个合法组合是什么意思。规则有限、封闭,所以每个合法程序有且仅有一种解读。你在第 2 章会看到,这套文法是靠「翻译程序」强制执行的——不合法的句子根本进不了门。
无歧义的收益有代价:你必须迁就文法的死板——全角半角、缩进、括号配对,差一个字符都不行。初学者常抱怨这些规则烦人,但换个角度想:正因为规则死板,你写的程序在千里之外的另一台机器上,才会被一模一样地理解。死板不是缺陷,死板是承诺。
条件三:有序,顺序不能乱
煎蛋必须先「倒油」再「打蛋」,反了就会粘锅。程序也一样:指令是按先后顺序执行的,先写先执行,后写后执行。这个「从上到下」的顺序,是程序最基本、最核心的执行规则。
顺序的严格性,不是谁规定的「编程礼仪」,而是硬件执行方式决定的。预告 1.3 节的结论:CPU 里有一个专门的寄存器,时刻记着「下一条指令在哪」,它只会自动指向「再下一条」——「从上到下逐条执行」是 CPU 的物理本能。同一个指令集合,换一种排列顺序,执行结果可能完全不同:先「倒油」再「打蛋」是煎蛋,先「打蛋」再「倒油」,油还没热、蛋液已经流了一灶台。三个条件里,「有序」离硬件最近,也最没有商量余地。
📐 例题 2 模糊还是精确?逐条判定并改写
下面四条「指令」准备交给一台监控系统执行,请逐条判定哪些能直接执行,不能的请改写到能执行:
(a)把冷却液温度调高一点。(b)把液压泵出口压力设定为 21 MPa。(c)如果振动异常就报警。(d)若振动值超过 0.8 g 且持续 3 秒以上,触发一级报警。
思路 拿三条件当筛子,逐条过:有没有锁定的量(精确)?有没有唯一的解读(无歧义)?动作先后是否明确(有序)?「调高一点」「异常」这类词,人读得懂,机器读不懂。
解 (a)不精确:「一点」没有参照,改写为「把冷却液温度设定为 85 ℃」——给定一个可执行的确定值(若原是升温任务,还要写明变化速率)。(b)精确:对象(液压泵出口)、量(21 MPa)、动作(设定)都唯一,可直接执行。(c)歧义:「异常」没有定义——多大算异常?哪路振动传感器?改写为(d)的形式才可执行。(d)精确:阈值(0.8 g)、持续时间(3 秒)、触发动作(一级报警)全部写死,可直接执行。
答:可直接执行的是(b)(d);(a)缺参照量、(c)缺判断标准,改写后方可执行。 ⚠ 易错点:多数新手会把(c)误判为合格指令——因为「对人来说它完全说得通」。请记住判定的标准只有一个:执行者没有常识时,这句话还能不能落地。机器没有「异常」的概念,只有可以比较大小的数值。
底层原理 为什么计算机「笨」到不能容忍任何含糊?因为它执行指令时没有「理解」,只有机械的比对和运算。它不会像人一样停下来想一想「他是不是这个意思」。你给什么,它就照着字面做——精确输入,才有精确输出。这不是缺陷,恰恰是计算机可靠的原因:同样的程序,跑一万次,结果一定一样。
把这三条标准放到工程现场检验,你会发现它有个工程界的「孪生兄弟」。适航与工艺管理中有条通行原则:可复现——一份合格的工艺文件,任何一名合格工人、在任何一班、按文件执行都应得到同样结果。「按需拧紧」显然不满足可复现;「用定扭矩扳手拧至 25±2 N·m 并做标记留痕」才满足。「可复现」与程序三条件(精确、无歧义、有序)本质上是同一件事的两套说法——一套对人,一套对机器。这也解释了为什么工程师群体转型编程往往上手很快:严谨度是现成的,缺的只是一门「翻译」语言。
最后辨析一个容易走偏的误区:精确不等于事无巨细。有人为了「精确」,把每条指令塞满限定词,结果清单没人读得下去——对人如此,对机器同样糟糕:指令越多,出错的面就越大。正确的做法是精确到任务需要的粒度,然后停手。检验零件只要两位小数,就别写三位;控制目标到 1 ℃ 就够,就别控制到 0.1 ℃。程序与工艺文件的共同美学是:每句话都必要,且每句话都无歧义。
本节的三个条件还可以压成一句口诀:锁定量、锁解读、锁次序。锁定量,让每个概念有唯一参照(精确);锁解读,让每句话只有一个含义(无歧义);锁次序,让执行路径唯一(有序)。以后每写完一段程序,都可用这三把锁自查一遍——三把锁都锁上了,机器才肯接活;哪把锁没锁上,报错或者错跑就等在哪把锁后面。
1.3计算机如何「执行」程序
程序写好了,计算机到底是怎么「照着做」的?要回答这个,得回到「计算机软硬件」那门课讲过的 CPU。这里我们把关键机制快速串起来,建立「程序 ↔ 硬件」之间的因果链。
你写的程序(比如 3 + 5),最终会被翻译成一串机器指令——就是一堆 0 和 1 组成的编码。CPU 拿到这串指令后,会陷入一个永不停歇的循环:
- 取指(Fetch):从内存里「读」出下一条指令。
- 译码(Decode):看懂这条指令是什么意思——它让 CPU 做什么。
- 执行(Execute):真正去做这件事,比如把两个数加起来。
- 写回(Write-back):把结果存回内存或寄存器。
做完这四步,CPU 又回到第一步去取「下一条」指令……如此循环几十亿次,直到程序结束。这就是著名的 取指-译码-执行循环(fetch-decode-execute cycle),也叫指令周期。
幕后主角:程序计数器与时钟
这个循环为什么能「自己转起来」而不跑偏?靠一个幕后主角:程序计数器(Program Counter,PC)。它是 CPU 里一个专门的小寄存器,内容始终只有一句话:「下一条指令存放在内存的地址 X」。取指那一步,CPU 就是按 PC 里的地址去内存把指令搬回来;一条指令执行完,PC 自动加上一个固定长度,指向紧挨着的下一条——除非碰到跳转指令,把 PC 直接改写成一个全新地址。你以后写循环、写条件分支,落到硬件上全是「改写 PC」这一个动作。所以,「程序从上到下顺序执行」不是一条抽象规定,而是 PC 不断加一的物理过程的直接结果——1.2 节条件三的谜底在这里。
循环的「节奏」由谁打拍子?答案是时钟(clock)。CPU 内部有一个以固定频率振荡的电路,像节拍器一样发出一连串「嗒、嗒、嗒」,每一下节拍,电路各部分统一前进一步:该取指的取指、该运算的运算、该写回的写回。我们说某处理器「主频 3 GHz」,意思是它每秒打 30 亿拍。主频越高,四步循环转得越快。日常工作中你不需要关心节拍如何产生——抽象又一次把细节藏好了;但做时间关键的计算时(见下面的工程视角),「数拍子」就是硬功夫了。
📐 例题 3 CPU 一秒能执行多少条指令?
某嵌入式控制器的 CPU 主频为 3 GHz(每秒 30 亿个时钟节拍),执行一条指令平均占用 4 个节拍(取指、译码、执行、写回各 1 拍)。问:(1) 它每秒能执行多少条指令?(2) 执行一段共 100 条机器指令的小程序,大约要多久?(3) 人眨一次眼约 0.1 秒,这段时间里它能执行多少条指令?
思路 把「节拍」当作时间单位:先算每条指令花几个节拍、每个节拍多长,再用总量 ÷ 单位量。频率给出的是「每秒节拍数」,指令条数 = 节拍总数 ÷ 每条指令的节拍数。
解 (1) 每秒可执行指令数 = 3×10⁹ 拍/秒 ÷ 4 拍/条 = 7.5×10⁸ 条/秒,即 7.5 亿条/秒。(2) 100 条指令共需 100 × 4 = 400 拍,耗时 400 ÷ (3×10⁹) ≈ 1.33×10⁻⁷ 秒 ≈ 133 纳秒(1 纳秒 = 10⁻⁹ 秒)。(3) 0.1 秒 × 7.5×10⁸ 条/秒 = 7.5×10⁷ 条,即 7500 万条。用程序验算(三个数量都不允许心算,这正是本教材对数字的要求):
freq = 3e9 # 时钟:每秒 30 亿拍
cpi = 4 # 每条指令平均 4 拍
ips = freq / cpi # 每秒指令数
print(ips) # 750000000.0 -> 7.5 亿条/秒
print(100 * cpi / freq * 1e9) # 133.33... -> 100 条约需 133 纳秒
print(int(0.1 * ips)) # 75000000 -> 眨眼间 7500 万条
运行输出:750000000.0、133.33333333333334、75000000,与手工推导一致。顺带一提:若这台 CPU 只有 2.4 GHz,执行同样 100 条指令则需 100×4 ÷ 2.4×10⁹ ≈ 1.67×10⁻⁷ 秒(约 167 纳秒)——主频降低 20%,耗时相应拉长约 25%。
答:(1) 每秒约 7.5 亿条;(2) 约 133 纳秒;(3) 约 7500 万条。 ⚠ 易错点:纳秒级的数字反直觉,很多人算对了却不敢相信。请记住换算链:1 秒 = 10³ 毫秒 = 10⁶ 微秒 = 10⁹ 纳秒。另外「每条指令 4 拍」是本题主设的简化平均,真实 CPU 的不同指令拍数不同、还有流水线与缓存的加速,入门阶段用平均值估算即可。
核心思想 程序之所以「能运行」,是因为每一行代码最终都变成了一串机器指令,而 CPU 这台机器会机械地、循环地、一条接一条地执行它们。「编程」和「计算机硬件」在这一点上彻底打通:代码不是飘在空中的文字,它是驱动硬件一步步动作的开关序列。
「数指令周期」听起来像老古董,但在今天的安全关键工程里仍是硬技能。飞机上的舵面控制计算机、发动机控制器、液压系统的调节单元,这类设备必须在确定的时间内完成计算并输出结果——晚一拍,控制作用就失效。工程师要能回答「最坏情况下这段控制程序要跑多少拍」,这就要逐段分析指令数、逐拍核对。你现在算的「每秒 7.5 亿条」,就是这门功夫的第一步。同时你也该意识到一个朴素的事实:程序能跑起来,从来不是理所当然——它是 CPU 以纳秒级节拍、机械循环几十亿次的结果。这份「机械的可靠」,正是机载设备敢把生命攸关的任务托付给计算机的底气。
辨析两个容易混淆的「循环」:CPU 的指令周期循环是硬件固有的物理行为,通电就转、永不停止,不依赖任何程序;程序里的循环语句(第 8 章正式学)是写在代码里、靠跳转指令实现的「回到上面某一行再执行一次」。前者是转的机器,后者是机器转的内容。打个比方:唱片机一直在匀速旋转(硬件循环),而唱片上录的副歌重复了三遍(程序循环)——一个是播放器的本能,一个是音频内容的编排。两者层次不同,缺一不可。
最后补上一块关键拼图:指令本身也住在内存里。你大概默认「程序」是硬盘上的一个文件,跟「数据」是两类东西;但在内存的视角下,指令和数据毫无二致——都是一串 0/1,按地址排成一列长队。取指这一步,本质就是按程序计数器里的地址,去这排格子里「取货」。CPU 之所以不会把一张照片错当成指令去执行,是因为它只按 PC 指的地址依次取——程序与数据在存储上不分家,区分靠的是「谁在读、什么时候读、按什么方式解读」。这个「程序当数据存」的设计思想叫「存储程序」原理,是现代计算机的基石,软硬件课程会专门展开;这里你只需记住:所谓「执行程序」,就是 CPU 在内存这排格子里,按地址一格一格向前——取一件、读懂它、照做、再前进一格。你往手机里装一个 App,发生的事情不过是一段指令序列被拷贝进存储器,等着 CPU 按地址逐条取用;软件之所以能被复制、安装、分发,正因为它的本体就是一串可拷贝的数据。
1.4人机「对话」的本质:零容错与语境
很多人第一次写代码会崩溃:明明看起来「没问题」,机器却报错。要理解这种挫败感,得先理解人和计算机「对话」方式的根本不同。
人和人交流,靠的是三样东西:语境(我们之前在聊什么)、常识(世界是怎么运转的)、容错(你说错一个词我也能猜出意思)。这三样,计算机一样都没有。
计算机和你的「对话」,是一种极端不对等的关系:
| 维度 | 人与人的交流 | 人与计算机的交流 |
|---|---|---|
| 语境 | 靠上下文补全没说出口的话 | 没有语境,只认字面 |
| 常识 | 「煎蛋要用锅」是理所当然 | 没有常识,一切都要明说 |
| 容错 | 错一个字、漏一个词都能猜 | 错一个符号就报错,绝不猜 |
| 反馈 | 会反问「你指的是哪个?」 | 要么照做,要么报错停下 |
这就是为什么新手会觉得计算机「不讲理」:你不是在跟一个「聪明但偶尔装傻」的人对话,你是在跟一台极其诚实、极其死板、绝不脑补的机器对话。它不会猜你「本来的意思」,它只执行你写出来的意思。
报错是善意,照做才危险
「零容错」听起来冷酷,但请对它公平一点:报错其实是善意的拦截。计算机拒绝你的代码,通常分三类情形,严重程度依次递增——语法错误:你写的不是合法句子,在翻译阶段就被拦下,程序根本没开始跑(漏个引号、错个函数名都算这类);运行时错误:句子合法、跑起来了,但执行途中摔倒(比如除以零、找不到文件);逻辑错误:全程无报错、顺利跑完,但结果不符合你的意图。前两类机器会「叫」出来,第三类机器毫无察觉——它对你的意图一无所知,它只核对「写出来的意思」。
所以真正的危险从来不是「报错多」,而是「不报错但错了」。这也解释了工程软件为什么要在测试验证上投入巨大:机载软件的验证与确认体系,本质上就是系统性地围剿第三类错误——用成千上万条测试用例去逼近「程序做的,就是你想要它做的」。初学阶段请建立这样的心态:满屏报错不必沮丧,那是机器在告诉你「问题找到了,位置在这」;最难缠的敌人,是安安静静的错。
📐 例题 4 找茬:这份「煮面清单」哪里含糊?
下面是一份「煮一碗面」的操作清单:①水烧开后下面;②煮一会儿;③尝一下,差不多就关火;④盛进大碗。假设执行者是一个毫无生活常识的机器人,请逐条挑出它无法执行的含糊之处,并给出改写。
思路 站在零常识执行者的视角逐条追问:这个量有没有锁定?这个判断有没有可核验的标准?这个对象有没有指定到唯一?凡是需要「生活经验」补全的地方,全是坑。
解 ①「水烧开」有可观察的判据(沸腾),基本合格;但若在高原,水不到 100 ℃ 就沸腾,严谨写法应为「加热至沸腾」,把判据与具体温度脱钩。②「煮一会儿」没有时限——多久算「一会儿」?改写为「计时 3 分钟」。③「差不多」没有标准——以什么为准?改写为「取一根面条夹断观察,中心无白色硬芯即关火」,给出可核验的终止条件。④「大碗」没有指定唯一对象——改写为「盛入编号 A-3 的碗中」。另外整份清单还隐含一个缺失:水量、面条重量都没有给出,照单执行的机器人连下多少面都不知道,应补「取面条 100 g」之类的前置指令。
答:含糊点为②③④(外加①的环境前提与缺失的用量说明),改写后每条都满足「量已锁定、判据可核验、对象唯一」。 ⚠ 易错点:最常见的误判是把③当成合格的——「尝一下」对人确实是精准的判据(人有味觉经验),但机器人没有味觉经验可依赖;改写的方向永远是「把经验替换成可观察、可比较的标准」。
📖 一点背景:为什么「零容错」其实是件好事 早期程序员用打孔卡片写程序,一个孔打错,整张卡就废了。今天你敲错一个符号照样报错,看起来是折磨人,但换个角度想:正因为计算机不猜、不脑补,它才可靠。银行转账、飞机导航、医院设备,靠的正是这份「一丝不苟」。你要做的,不是跟它生气,而是学会用它的语言,把话说死、说准。
📖 一点历史:检查单,就诞生于一次「漏了一步」 20 世纪 30 年代,某新型轰炸机在竞标首飞中坠毁——事后调查发现,不是设计缺陷,而是机组起飞前忘记解除升降舵锁销。媒体嘲讽它「太复杂,一个人根本开不了」,制造方却没有放弃:他们组织精英试飞员写了一份逐项打勾的起飞前检查单。此后该机累计飞行近两百万千米,再未因操纵失误发生重大事故,航空业的「检查单文化」由此发端。你看:人执行「程序」时会忘步、会跳步,于是工程界干脆发明了一种防御手段——把指令清单印在卡片上、逐项确认、执行打勾。检查单,就是写给「会犯错的人」的「程序」。
🧠 一句话记忆 人会脑补,计算机不会。跟计算机对话,别指望它「懂你」,要让它「无从误解」。
有人会问:如今的人工智能助手不是能「猜」我的意思吗?这里要做一个重要的辨析:AI 的「猜」,是程序用概率模型算出来的推测,它的底层依然是精确的数学运算与一条条指令的执行;猜中了你觉得贴心,猜偏了你需要纠正——所谓「智能」,是建立在零容错的机械执行之上的高层能力。地基越机械,楼层才越敢往上盖。先学编程打地基,再谈各种智能应用盖楼,顺序不能反。
1.5编程、程序与算法的关系
还有两个词经常和「编程」混在一起:算法(algorithm)和 代码(code)。把它们的关系理顺,你对「编程」的理解就完整了。
- 算法:解决问题的思路和步骤,是「怎么做」的方法,跟语言无关。比如「从 1 加到 100」的算法可以是「一个个加」,也可以是「(1+100)×50」。算法是脑子里的方案。
- 代码:用某种编程语言把算法写出来的具体文字。同一套算法,可以用 Python 写,也可以用别的语言写,文字不同,思路一样。
- 程序:最终能交给计算机运行的那份完整代码。
算法的三个硬性质:无歧义、有限步、可行
并不是随便一份「步骤清单」都有资格叫算法。一个合格的算法必须同时满足三个硬性质:无歧义——每一步在任意执行者眼里含义唯一(这继承了 1.2 节的标准);有限步——整个流程必须在有限次操作后终止,「一直搅拌直到好喝」就不算算法,因为「好喝」可能永远等不来、流程可能永不收尾(那是「过程」,不是「算法」);可行——每一步都必须落在执行者的基本动作清单里、在有限时间内可完成,「等天气转晴再起飞」对计算机不可行,因为「改变天气」不在它的基本动作清单上。算法,就是步骤的精确化:把「大概这么做」推进到「严格这么做」。
这三个性质合起来划出一条清晰的边界:菜谱、工艺卡、检查单都是步骤清单,但只有同时满足三条的才配称为算法;而程序,是把算法用编程语言落实成机器可执行的形态。这也决定了做事的正确顺序——先有合格算法,再谈写程序。算法错了,代码写得再漂亮,也只是在精确地执行一个错误。
📐 例题 5 「1 加到 100」:两种算法的步数对决
要计算 1+2+3+…+100,有算法 A 与算法 B 两种思路。算法 A「逐个相加」:从 1 开始,每次把下一个数累加进总和,直到加完 100。算法 B「配对法」:1 与 100 配成一对、2 与 99 配成一对……每对的和都是 101,共 50 对。问:(1) 两种算法各需要多少次运算?(2) 写出两种算法的指令级描述,并验证两者答案一致。
思路 算法 A 的加法次数由「100 个数两两合并成一个总和」决定:每合并一次减少一个数,需要 99 次加法。算法 B 则是三步算术:先算一对的和 1+100=101,再算对数 100÷2=50,最后相乘 101×50。一个要 99 步、一个只要 3 步,而两者结果必须相同——这就是「算法的选择」在起作用。
解 算法 A:设 sum=1,依次执行 sum = sum + 2,sum = sum + 3……直到 sum = sum + 100,共 99 条加法指令。算法 B:第 1 步 101 = 1 + 100;第 2 步 50 = 100 ÷ 2;第 3 步 5050 = 101 × 50。用 Python 把两种算法都跑一遍核对:
total = 0
for i in range(1, 101): # 算法 A:从 1 逐个加到 100
total += i
print(total) # 5050
print((1 + 100) * 100 // 2) # 算法 B:首尾配对,3 步出答案
运行输出两行,都是 5050——两种思路殊途同归,但步数是 99 比 3。还可以再想一层:若题目改成「加到一百万」,算法 A 要做 999999 次加法,算法 B 依旧 3 步——数据规模越大,好算法的优势越悬殊。
答:算法 A 需 99 次加法;算法 B 只需 3 步(1 加、1 除、1 乘),两者结果同为 5050。 ⚠ 易错点:① 别把「步数少」误当成算法评判的全部标准——正确性永远是第一位的,效率是正确之后的事;② 配对法成立依赖「等差」这个前提,换成杂乱无序的一批数就不能套用,选算法要先看清前提条件。
一句话关系 算法是「方法」,代码是「把方法写下来」,程序是「能跑起来的那份代码」,编程是「从想到写、再到跑通」的整个过程。
📖 名词溯源:algorithm 从哪来? algorithm 一词来自波斯数学家 花剌子密(al-Khwārizmī) 的名字。公元 9 世纪,他写了一本讲「如何按步骤解方程」的书,后来这本书传到欧洲,书名里他的名字就慢慢演变成了「算法」这个词。所以「算法」从一开始,指的就是一套清清楚楚、可照着执行的步骤。
这道「1 加到 100」绝不只是数学趣题,它是工程计算的普遍缩影。处理一批试验数据要逐行累加求和?那就是算法 A 的翻版——数据一万行,就要做近万次加法。有没有算法 B 式的捷径?常常有:等差数列求和有闭式公式,常用统计量有现成公式,数据分析库甚至把整批运算一次性交给底层高度优化的机器码去完成(第 10 章数据结构还会再遇到这个思想)。拿到问题先问一句「有没有更聪明的步骤」,再动手写代码——这个习惯的价值,会随你处理的数据规模变大而指数级放大。
最后辨析「算法」与「程序」的关系边界。算法是思想:语言无关、平台无关,写在纸上、讲给别人听都成立;程序是实现:受具体语言语法、受机器资源的约束。同一个配对算法,用 Python 写是一个样子,用其他语言写是另一个样子,但两者承载的是同一套思路。工程审查里常说「审算法」与「审代码」是两道独立的关口,原因正在于此:思想错了,改实现救不回来;实现错了,再好的思想也落不了地。两道关都要过,但顺序永远不变——先算法,后代码。
这一整套关系,可以用一句话收尾:
🧠 一句话记忆 先有「思路」(算法),再「写下来」(代码),最后「跑起来」(程序)。编程,就是这条从想法到运行的路。
最后把本章开头的机器人厨师召回来说一句收尾的话:它按清单逐条执行,每一步都机械、每一步都可预测——而恰恰是这种可预测性,让工程师敢把关键工序交给它,也让你将来敢把自己写的小工具交给团队使用。编程建立的信任链条是:语句精确 → 指令可预测 → 结果可复现 → 责任可追溯。这条链条的第一环,就是本章反复念叨的「把话说死、说准」。下一章我们回答一个新的问题:既然机器只认 0 和 1,人类为它垫了哪些「台阶」,让我们不必直接跟 0/1 搏斗?
本章小结
- 指令是「做一件具体小事」的命令,程序是一组有顺序的指令集合,编程就是编写程序。
- 程序的指令必须满足三条件:精确、无歧义、有序。
- 计算机靠 取指→译码→执行→写回 的循环机械地执行程序;代码最终是驱动硬件的开关序列。
- 人机「对话」的本质是:计算机零容错、无语境、无常识,只会照字面执行。
- 算法是方法,代码是把方法写下来,程序是能跑起来的代码。
- 顺序执行的硬件根源是程序计数器不断加一;节拍由时钟供给,3 GHz 即每秒 30 亿拍。
- 错误分三类:语法错误(进不了门)、运行时错误(途中摔倒)、逻辑错误(跑完但错)——最危险的是不报错的第三类。
- 算法有三个硬性质:无歧义、有限步、可行;同样正确的前提下,步数的差距(99 对 3)就是算法选择的价值。
读完本章,你应该能……
- 用一句话向别人解释「编程」「程序」「指令」分别是什么。
- 举一个生活中的例子,说明「模糊指令」为什么计算机执行不了。
- 说出 CPU 执行程序的四个步骤,并说清它们在循环。
- 解释为什么跟计算机「对话」会让人感到它「不讲理」。
- 区分「算法」「代码」「程序」三个概念。
- 把一件日常任务(如烧水泡茶)拆成满足三条件的指令清单。
- 给一台 3 GHz、平均每条指令 4 拍的 CPU,估算它每秒执行的指令数与一段小程序的耗时。
- 说出算法的三个硬性质,并判断「一直搅拌直到好喝」缺了哪一条。
思考题
1. 「给我泡杯茶」为什么不能直接作为一条计算机指令?把它拆成精确指令,你能写出几步?
提示:因为「泡茶」是个模糊的大任务。拆解时要精确到对象和动作:如「从柜子取出杯子」「把水壶接满 300 毫升水」「加热到沸腾」……每一步都无歧义、可执行。
2. 为什么说「程序的指令必须有顺序」?如果顺序乱了会怎样?
提示:因为计算机默认从上到下逐条执行;顺序错乱会导致结果错误(比如先放蛋后倒油)。
3. 用自己的话解释:你写的 3 + 5 是怎么变成 CPU 里的动作的?
提示:先被翻译成机器指令(0/1 编码),然后 CPU 进入取指→译码→执行→写回的循环去执行它。
4. 一个「算法」能用两种不同的编程语言写出来吗?为什么?
提示:能。算法是语言无关的思路,代码才是具体语言的文字;思路相同,写法可以不同。
5. 工艺卡、检查单和程序有什么共同点?它们验证了本章的哪一条原则?
提示:都是「事先写好、按序执行、不留发挥空间」的行动清单,区别只是执行者是人还是机器;共同点是都要满足精确、无歧义、有序——三条件的另一套名字叫「可复现」。
6. 某监控程序写着「温度降到合适范围再继续」,机器启动后却停在那里不动。用三条件诊断问题,并给出改写。
提示:「合适范围」没有锁定具体数值(不精确),机器无法判断何时算达标;应改为「温度 ≤ 40 ℃ 时继续」,给出可比较的数值判据与明确的比较方向。
7. 算法 A 与算法 B 都能算出正确结果,凭什么说 B 更好?「步数少」是评价算法的唯一标准吗?
提示:正确只是底线,步数(资源消耗)是重要的第二标准,数据规模越大差距越悬殊;但不是唯一标准——还要看前提条件是否满足、实现是否可行。选算法永远是「在正确的前提下,为当前问题规模挑最省的」。