CC++ & Algorithm

为什么你的程序跑得比同学快?可能只是因为你更懂这三块“积木”

先说一个我经常在课堂上做的实验。

同一个计算任务,两个学生写出几乎相同的代码,一个跑完用了 0.8 秒,另一个用了 12 秒。代码逻辑看起来没差,变量名都差不多,循环层数也一致。问题出在哪?出在他们对计算机底层三块“积木”的理解上——CPU、内存、输入输出设备。

这不是玄学。当你真正理解这三者各自干什么、怎么配合,你写代码时脑子里会多出一张“执行地图”,知道每一行语句到底动了哪块积木。今天我们就用几道竞赛题当切口,把这件事聊透。

先看一道题:你的变量到底住在哪里

有一道省赛选择题是这么问的:

表达式 6-1 的值是( )。 A. 整数5 B. 字符5 C. 表达式不合法 D. 字符6

很多同学第一反应:这有什么好问的,不就是 5 吗?但题目问的是“值”的类型,不是算术结果。6-1 是两个整数字面量做减法,CPU 执行的是整数运算,结果就是整数 5。答案是 A。

这道题表面在考表达式,实际上在考一个更根本的问题:谁在执行这个减法?执行完结果放在哪?

答案是:CPU 执行减法,结果临时放在寄存器里,然后根据上下文写入内存中的某个变量。如果你写的是 int x = 6 - 1;,那么内存里会开辟一个 4 字节的空间,名字叫 x,里面存着二进制的 00000000 00000000 00000000 00000101。

这就是 CPU 和内存的分工:CPU 负责算,内存负责存。

CPU:不是“大脑”,是“流水线上的工人”

把 CPU 比作大脑,其实不太准确。大脑会走神、会累、会同时想好几件事。CPU 更像一个极端专注的流水线工人——它只会做两件事:

  1. 算术运算:加减乘除、取余
  2. 逻辑判断:比大小、判断真假

而且它做得极快。一颗普通 CPU 每秒能执行几十亿次操作。你眨一下眼,它已经算完了几十亿个加法。

但 CPU 有一个致命短板:它自己不存东西。 算完的结果如果不马上写回内存,下一纳秒就没了。这就是为什么 CPU 需要和内存紧密配合。

看这道题:

若定义 int a=2, b=2,下列表达式中值不为 4 的是( )。 A. a*(++b) B. a*(b++) C. a+b D. a*b

这题考的不是算术,是副作用的时机。++b 是先把 b 加 1 再参与运算,所以 A 是 2*3=6;b++ 是先用 b 的原值参与运算再加 1,所以 B 是 2*2=4。C 和 D 都是 4。答案是 A。

这里的关键在于:CPU 执行 ++b 和 b++ 时,对内存的写入时机不同。++b 先写内存再读,b++ 先读内存再写。内存的读写顺序,决定了表达式的值。

内存:你的“工作台”决定了你能同时想多少事

内存是临时存放数据的地方。程序运行时,所有正在使用的数据——变量、数组、对象——都放在内存里。

内存有两个特点值得反复强调:

  • 快:比硬盘快几百倍
  • 断电就丢:关机后数据全部消失

有一个判断题经常出现在题库里:

内存(如RAM)中存储的数据在计算机关闭电源后会永久保存。

这是错的。RAM 是易失性存储器,靠电信号维持数据。断电,数据就没了。硬盘、U盘才是非易失的。

为什么这个点重要?因为很多初学者分不清“内存”和“硬盘”的角色。我常用一个比喻:

内存是你的课桌桌面,硬盘是你的书包。 桌面越大,你能同时摊开的书本越多,做作业越快。 但放学铃一响(断电),桌面必须清空,所有东西收回书包(硬盘)。

写代码时,你定义的每一个变量,都是在桌面上占一块地方。桌面就那么大,占满了程序就崩。

输入输出:计算机和世界的接口

CPU 和内存再强,如果没法接收外界信息、没法把结果送出去,就是一台自嗨的机器。

输入设备把外界信息送进来:键盘、鼠标、麦克风、摄像头。 输出设备把结果送出去:显示器、打印机、音箱。

在 C++ 里,这两个动作被抽象成两个箭头:

cin >> score;    // 从键盘读入,送到内存中的 score
cout << total;   // 从内存中的 total 送到显示器

箭头的方向就是数据的流向。>> 是从输入流到变量,<< 是从变量到输出流。写反了编译器直接报错。

有一个容易踩的坑:忘记定义变量就直接 cin >> age;。这就像往一个还没放好的盒子里塞东西——程序不知道往哪塞,直接崩溃。必须先 int age; 在内存里开好格子,再往里送数据。

一道综合题:把三块积木串起来

来看这道题:

小蓝打羽毛球实行积分赛制,获胜积5分,打平积2分,失败扣1分。已知小蓝在20场积分赛后积61分且有3场比赛打平,那么小蓝的胜率为:( ) A. 48% B. 55% C. 60% D. 75%

这题看起来是数学题,但解题过程完美对应了 CPU、内存、输入输出的协作。

第一步:从题目读取信息(输入)

  • 总场次:20
  • 总积分:61
  • 平局场次:3

第二步:在内存中设未知数(变量定义)

  • 设胜场为 w,负场为 l
  • 平局已知是 3

第三步:CPU 建立方程并求解(运算)

  • 场次关系:w + l + 3 = 20 → w + l = 17
  • 积分关系:5w + 2*3 - l = 61 → 5w - l = 55
  • 两式相加:6w = 72 → w = 12
  • 胜率:12 / 20 = 60%

答案是 C。

如果你用代码来解,大概是这样:

int total_games = 20;
int total_points = 61;
int draws = 3;

int wins = (total_points - 2 * draws + total_games - draws) / 6;
// 这里 CPU 做了加法、减法、乘法、除法
// 所有中间结果都存在内存的临时变量里

double rate = wins * 100.0 / total_games;
cout << rate << "%" << endl;  // 输出设备显示结果

每一步都在重复同一个模式:输入 → 内存存储 → CPU 运算 → 输出。

那些年我们踩过的“方向”坑

有一道题问:

以下叙述中正确的是( )。 A. break 语句只能用于 switch 语句体中。 B. continue 语句的作用是:使程序的执行流程跳出包含它的所有循环。 C. break 语句只能用在循环体内和 switch 语句体内。 D. 在循环体内使用 break 语句和 continue 语句的作用相同。

答案是 C。

这题考的是流程控制,但底层逻辑仍然是 CPU 如何跳转执行。break 让 CPU 跳出当前循环或 switch,continue 让 CPU 跳过本次循环剩余部分、直接进入下一次迭代。两者都在改变 CPU 的执行路径,但跳转的目标不同。

你把这些控制流语句想象成给 CPU 的“指令便签”:到这儿停,或者到这儿跳。CPU 本身不会思考,它只是忠实地执行你写的每一条指令。

进阶建议:从“会写”到“懂为什么”

如果你已经能熟练写出 for 循环、if 判断、数组操作,下一步该做什么?

我的建议是:去理解每一行代码在硬件层面发生了什么。

  • 定义一个 int 时,内存里发生了什么?
  • 调用一个函数时,CPU 的栈指针怎么变化?
  • 数组越界为什么有时不报错、有时直接崩?

这些问题看起来底层,但它们决定了你能否写出高效、稳定的代码。竞赛中那些“超时”的案例,十有八九是内存访问模式出了问题,而不是算法本身错了。

下次写代码时,试着在脑子里画一张图:数据从哪来(输入)、存在哪(内存)、怎么算(CPU)、到哪去(输出)。这张图清晰了,代码自然就稳了。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

评论0

还没有评论,来抢沙发~

评论加载中...

想系统学习这个知识点?查看完整知识点 →