前两天一个刚学 C 语言的朋友发来一段代码,问了一个几乎每个初学者都会问的问题:为什么我算 1 加到 100,sum 输出怎么都是 100?我一看代码就明白了——这是 C 语言累加求和里最典型的经典错误:把循环变量 i 和固定值 n 搞混了。这个错误在初学者里面出现频率极高,因为你可能觉得自己已经理解了 for 循环,可真坐下来写累加求和,手却不自觉地就把 n 写进了循环体。这篇文章既是错误剖析,也是一份调试手记:我会用一段错误代码、一张变量追踪表和一个完整的调试过程,把 for 循环的执行顺序彻底拆开,让你以后看到 sum = n 这类代码就知道哪里出了问题。适合刚学完 for 语法、上机实验总卡 bug 的初学者,也适合想再捋一遍思路的老读者。1. 错误现场:一段“经典”的累加代码1.1 先看这段代码,你中招了吗?假设题目要求:输入一个整数 n,使用循环计算 1+2+...+n,输出总和 sum。很多初学者会写下这样的代码:#include <stdio.h> int main() { int n, sum = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) { sum = n; // 意图是累加,可这里写成了赋值 } printf("%d\n", sum); return 0; }这段代码编译不会报错,运行也不会崩溃,但输入任何 n,输出的结果都等于 n。这一点特别迷惑人:程序看起来完全正常,语法也对,就是结果不对。这类“能跑但结果错”的 bug,恰恰是初学者调试能力成长的起点。下面先看一下“症状表”,再往里挖根因。1.2 症状对比:预期输出与实际输出输入 n预期 printf 输出实际输出核心症状363输出等于 n5155输出等于 n1005050100输出等于 n为什么?因为 for 循环每执行一次循环体,就执行一句 sum = n。这句话的意思是:把变量 n 当前的值复制到 sum,然后覆盖 sum 原来的值。n 从用户输入以后一直没有变,所以循环体执行 100 次,实际上是把同一个数 100 往 sum 里写了 100 遍。循环结束时,变量 i 变成了 101,循环退出;sum 保留的是最后一次写入的值,也就是 100。于是“预期累加和”变成了“边界值”。如果把它改成一个更隐蔽的版本 sum += n,循环体执行 n 次,每次都能在 sum 上加一个 n,所以输入 100 会输出 10000。这个结果看起来有点“累加的意思”,但本质还是错的,因为循环变量 i 全程没有参与运算,你加的永远是固定值 n。1.3 另一个变体:把固定值当成更新对象把混淆方向反过来也常见,另一位初学者改成了这样:int n, i, sum = 0; scanf("%d", &n); for (i = 1; i <= n; n++) { // 更新语句写成了 n++ sum += i; } printf("%d\n", sum);这段代码直接跑的话,窗口大概率卡死,因为 n 在每次循环后都自增,而 i 一直停在 1,条件 i <= n 永远满足。n 会无限制地往上涨,直到 int 溢出变成负数,循环才可能退出。这正是“把循环变量与固定值搞混”的另一种表现:循环变量是负责驱动的“引擎”,固定值只是“终点线”,你把终点线往前挪,比赛就永远结束不了。这个例子提醒我们,错误不一定只在循环体里出现,for 的三个组成部分都有放错变量的可能。正因为如此,我们得回到 for 循环本身的语义去拆解。2. 根因剖析:为什么我会把循环变量和固定值搞混2.1 for 循环的三个部分到底是谁在干活先看 for 的标准结构:for (初始化表达式; 条件表达式; 更新表达式) 循环体;初始化表达式:设置循环变量初值,比如 int i = 1,相当于给计数器归位。条件表达式:每次进入循环体之前都要判断,成立则继续,不成立则退出。更新表达式:每次执行完循环体之后自动执行,通常用于改变循环变量的值。循环体:真正干活的代码。可以这样类比:假设你要求自己在跑步机上跑 n 分钟。i 相当于秒针,每跑一分钟会跳一格;而 n 是你提前设定好的目标分钟数,它只是静静躺在那里,不会自动增加。循环变量 i 是“过程”,固定值 n 是“终点”。累加求和需要关心的是过程里每一分每一秒:第 1 分钟、第 2 分钟、……第 n 分钟,分别把对应的数字加进总时间。如果你在循环体里只执行 sum = n,就等于把“还有 n 分钟”当成“已经跑完 n 分钟”,当然会出错。2.2 “循环多少次”和“加到哪个数”是两码事累加 1+2+...+n 的核心要求是“把从 1 到 n 的每一个整数都累加一次”。这里有两个信息:一个是范围边界 n,另一个是当前遍历到哪个数。使用者输入的是 n,但循环体内处理对象应该是不断更新的 i。你可以想象成顾客到店买货,n 是“一共要买 n 件”,i 是“正在拿第 i 件”。收款员的计算机只认当前拿过来的这一件,不能每一件都按总数算。sum = sum + i 的含义是:把当前这一项的数值 i 加进 sum,然后继续下一步。sum = n 呢?它只把边界值复制进来,前面 1、2、…、n-1 全都丢了。这就是“把固定值误当成当前项”。2.3 三个最容易踩中的思维误区误区一:看到 n 就以为它是“当前的数”。很多初学者觉得,用户输入了 n,那么“这一趟加 n 不就行了”。但这里的 n 在循环开始前就固定了,它不是和 i 一起变化的量。真正每轮变化的只有 i。把 n 写进 sum = n,程序不会报错,所以人很容易被“假正确”骗过。误区二:以为 sum 会有记忆。sum = n 是赋值,不是追加;sum += n 是追加,但有追加也不够,因为 n 不变。时常见到初学者把 sum 理解成“一个自己会累积的盒子”,但其实累积是我们通过代码写出来的动作:sum = sum + i。赋值运算符的优先级是右侧先算,结果再覆盖左侧,执行完 sum 变成一个新值。误区三:把 C 语言的等号当成数学等号。数学里 x = x + 1 是荒谬的,但 C 语言里 sum = sum + i 非常自然。初学者一旦被数学惯性影响,就不太敢写带自身的赋值,于是更容易写出 sum = n 这种“看起来干净、实际上错误”的表达。这个误区需要用“赋值方向:右边给左边”来纠正。3. 从一行错误代码读懂 for 执行逻辑(调试手记)3.1 那一行代码到底按什么顺序执行再回到这段错误代码:for (int i = 1; i <= n; i++) { sum = n; }编译器眼中它的执行顺序是固定的:先执行 int i = 1,这个步骤在整个 for 生命周期里只执行一次。判断 i <= n 是否成立。第一次 i=1,若 n>=1,则成立。进入循环体,执行 sum = n。此刻 sum 被覆盖成 n。执行更新表达式 i++,i 变成 2。回到第 2 步继续判断。直到 i 变成 n+1,i <= n 不成立,循环结束。关键点在于:步骤 3 每一次都把 sum 覆盖成同一个 n,步骤 4 只改变 i,不改 n。循环次数由 i <= n 控制,但 sum 的结果和 i 没有任何关系,因为 sum 的表达式里根本没有 i。这就是“为什么结果永远等于 n”的根本解释。3.2 手动模拟追踪表:把变量变化画出来为了让自己真正看清,我建议初学者在纸上做一次“变量追踪”。以 n=3 为例,错误代码 sum = n 的执行过程是:轮次进入循环体前 i条件判断循环体内 sum 变成更新后 i11真3222真3333真34退出4假最终 sum=3-同样的 n=3,正确代码 sum += i 的执行过程则是:轮次进入循环体前 isum 原值新 sum(sum+=i)更新后 i110122213333364退出46最终 sum=6-两行表放在一起,再笨的 bug 也能现形:错误版本每一轮的 sum 都跳回 3,正确版本每一轮的 sum 都在原基础上增加。手动模拟的价值不是让你成为计算器,而是让你亲眼看见“覆盖”和“累加”的区别。我带过的初学者里,能坚持画三张这种表的人,基本不会再犯同类错误。3.3 用调试器一步步看变量变化除了手动模拟,还可以用调试器实时观察。以 Linux 下常用调试器 gdb 为例,把程序保存为 sum.c,编译时加 -g 生成调试信息:gcc -g sum.c -o sum gdb ./sum进入调试会话后,先用 break 9 在循环体那一行打断点(行号以实际代码为准),再用 run 输入 3 启动程序。程序停在循环体后,分别执行:print i print sum print n接着用 next 单步执行一次循环体,再用 print 看 sum 的变化。如果看到 i 从 1 变到 2,而 sum 依旧等于 3,就说明 sum 没有被累加,而是被赋值了。也可以用 display sum 让每次下一步都自动显示 sum。如果在图形化 IDE 里,步骤更简单:在 for 循环体那行点断点,运行,然后盯着“监视窗口”里的 i 和 sum 变化即可。我现在依然推荐初学者至少手动把 gdb 的这几个命令跑熟:break、run、next、print、display。原因很简单:C 语言逻辑 bug 用眼睛看往往看不出来,但变量值一摊开,真相立刻浮出水面。所谓的调试手记,核心就是这一套“看变量”的功夫。4. 修正方案与常见变体4.1 标准修正:把固定值改成循环变量正确写法:#include <stdio.h> int main() { int n, sum = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) { sum += i; } printf("%d\n", sum); return 0; }sum += i 等价于 sum = sum + i,它把当前项 i 追加到总和里。i 会随着循环推进依次变成 1、2、3……n,所以 sum 最终等于 1+2+...+n。这个版本不需要数学公式,通用于各种“按顺序迭代累加”的场景。如果你想写 while,核心逻辑完全一致:int i = 1; while (i <= n) { sum += i; i++; }两种写法都要记住三点:sum 必须初始化为 0,否则累加结果会建立在随机垃圾值上;循环变量 i 必须能走向 n+1,否则死循环;printf 必须在循环结束后执行,否则你看到的只是中间结果。4.2 几个变体练手:从 1 到 n、阶乘、奇数累加掌握了 i、n、sum 的分工,可以举一反三。下面几个变体都围绕同一个模式:循环体里出现“当前项”,而这个当前项通常由循环变量 i 表达。变体一:求 1 到 n 之间所有奇数之和。可以 for (int i = 1; i <= n; i += 2) 直接让 i 按奇数步进,也可以 for 每次 i++,循环体内 if (i % 2 == 1) sum += i。后一种写法更通用,适合判断条件复杂的场景。变体二:求 n 的阶乘。注意初始值从 0 改成 1,因为乘法从 0 开始会永远得 0:int result = 1; for (int i = 1; i <= n; i++) { result *= i; }这里的循环变量 i 依然是“当前乘到第几个数”,固定值 n 依然只出现在条件里。变体三:求 1/1 + 1/2 + ... + 1/n。由于 i 是整数,1/i 会得到整数 0,所以要写成 1.0 / i,让浮点除法生效。这个例子再次强调“当前项”的形态可以很丰富,但来源永远是循环变量 i,而不是固定值 n。还可以顺手算一下连续自然数的平方和:sum += i * i。这些都是同一个模式:for 负责迭代,循环体内用 i 构造当前项,固定值只当边界。把这些变体都写一遍,比背十遍语法都有用。4.3 避免再次混淆的三条经验法则法则一:只要循环体里表示“当前项”,就从循环变量里取。累加求和的当前项是 i,不是 n;高级点的情况可能是 i*i、1.0/i、arr[i] 之类,但绝不会是 n 本身。法则二:固定值在 for 循环里最多出现在条件判断和边界相关的位置。如果它出现在循环体的赋值右半边,或者出现在更新表达式里,你就要停下来怀疑自己是不是角色搞混了。法则三:写代码前先用中文说清楚要做什么。比如“从 i=1 到 n,每次把 i 加到 sum 上”,然后逐字翻译成 C 代码。翻译时发现“每次”对应的变量是 i,而不是 n,错误就很难发生。这三条法则看起来简单,但在实际调试场景里非常管用。我有一次检查某位同学的作业,他写了 sum = n; 我问他 sum 应该等于什么,他说“等于 n 啊,因为要加到 n”。我再问“那 1 到 n-1 呢”,他才突然意识到自己漏了整个过程。很多 bug 不是语法问题,是脑子里没有把“循环过程”建立起来。5. 常见问题速查与自查清单5.1 初学者的典型困惑问答问:为什么 sum 不初始化就乱?答:局部变量 int sum; 不会自动变成 0,它的初始值是栈上一块随机旧数据。sum += i 在这个随机值上累加,结果必然乱七八糟。正确做法是 int sum = 0;。如果写全局变量,不初始化也会自动是 0,但我不建议为了让代码能跑而把变量全塞成全局。问:我写 sum += n,为什么输出了 nn?答:因为循环体执行了 n 次,每次都在 sum 上加同一个 n,等效于 n 加 n 次,所以结果是 nn。这说明你“把 n 当成了当前项”,但没有意识到当前项必须和循环次数绑定,只有 i 才能做到这一点。问:i <= n 和 i < n 到底差多少?答:前者包含 n,循环执行 n 次;后者不包含 n,循环执行 n-1 次。在累加求和里,如果条件写成 i < n,你就会漏加最后一个值 n,结果变成 1+2+...+(n-1)。边界判断在找 bug 时可以逐值代入,比如 n=1 时 i < n 直接不执行,sum 还是 0。问:我的程序一运行就“卡住”了,怎么办?答:先看 for 的更新表达式更新的是不是循环变量。常见写法 n++ 会让 n 一直变大,条件 i <= n 永远成立,循环退不出去。用 Ctrl+C 终止程序,然后检查更新表达式是 i++ 还是 n++。这类问题用调试器或者快速用 printf 看变量值都能很快定位。5.2 自查清单:提交前逐项打勾检查点正确描述变量初始化sum 初始化为 0,阶乘初始化为 1循环变量初始化i 有明确初值,通常为 1条件边界i <= n 或 i < n 与题意一致,注意是否漏最后一项循环体角色累加表达式右半边使用 i,而不是固定值 n更新表达式更新的是循环变量 i,不是边界 n输出位置printf 在循环结束后,输出最终 sum类型范围n 较大时 int 可能溢出,必要时用 long long这张表是我自己 debug 时用的“扫雷表”。每次写完一个循环,从头到尾扫一遍这七项,能挡掉九成低级错误。尤其是“循环体角色”那一行,正是本文标题里的“循环变量与固定值混淆”,值得你在每次提交前多盯几秒。5.3 调试手记最后想分享的一个习惯我当年学 C 语言时也写过 sum = n。那会儿我盯着输出结果看了十分钟,始终觉得“语法没问题啊,为什么结果是 100”。后来我在循环体里临时加了一行 printf("i=%d sum=%d\n", i, sum); 当程序跑完,我清清楚楚看见 sum 每次都被重置成 n,才真正理解了“覆盖”和“累加”的区别。这个习惯我保留到了今天:凡是写循环求和,先用中文伪代码把角色分工写一行,比如“对每个 i,把 i 加到 sum”;代码写完立刻扫一眼循环体,看里面有没有不该出现的固定值。如果有,马上改。这样做能节省大量时间。如果你现在也被这个错误折磨,我的建议是别急着到处问人。先手动画一遍变量追踪表,再用调试器看一遍变量变化。等到你亲眼看到 i 一个个往前走、sum 在后面一点点累积,for 循环就不再是三个空泛的语法部分,而是一台你会操作、也会修理的机器。这就是这篇调试手记最想让你带走的东西:变量角色清楚,程序才能清楚。
前两天一个刚学 C 语言的朋友发来一段代码,问了一个几乎每个初学者都会问的问题:为什么我算 1 加到 100,sum 输出怎么都是 100?我一看代码就明白了——这是 C 语言累加求和里最典型的经典错误:把循环变量 i 和固定值 n 搞混了。这个错误在初学者里面出现频率极高,因为你可能觉得自己已经理解了 for 循环,可真坐下来写累加求和,手却不自觉地就把 n 写进了循环体。这篇文章既是错误剖析,也是一份调试手记:我会用一段错误代码、一张变量追踪表和一个完整的调试过程,把 for 循环的执行顺序彻底拆开,让你以后看到 sum = n 这类代码就知道哪里出了问题。适合刚学完 for 语法、上机实验总卡 bug 的初学者,也适合想再捋一遍思路的老读者。1. 错误现场:一段“经典”的累加代码1.1 先看这段代码,你中招了吗?假设题目要求:输入一个整数 n,使用循环计算 1+2+...+n,输出总和 sum。很多初学者会写下这样的代码:#include <stdio.h> int main() { int n, sum = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) { sum = n; // 意图是累加,可这里写成了赋值 } printf("%d\n", sum); return 0; }这段代码编译不会报错,运行也不会崩溃,但输入任何 n,输出的结果都等于 n。这一点特别迷惑人:程序看起来完全正常,语法也对,就是结果不对。这类“能跑但结果错”的 bug,恰恰是初学者调试能力成长的起点。下面先看一下“症状表”,再往里挖根因。1.2 症状对比:预期输出与实际输出输入 n预期 printf 输出实际输出核心症状363输出等于 n5155输出等于 n1005050100输出等于 n为什么?因为 for 循环每执行一次循环体,就执行一句 sum = n。这句话的意思是:把变量 n 当前的值复制到 sum,然后覆盖 sum 原来的值。n 从用户输入以后一直没有变,所以循环体执行 100 次,实际上是把同一个数 100 往 sum 里写了 100 遍。循环结束时,变量 i 变成了 101,循环退出;sum 保留的是最后一次写入的值,也就是 100。于是“预期累加和”变成了“边界值”。如果把它改成一个更隐蔽的版本 sum += n,循环体执行 n 次,每次都能在 sum 上加一个 n,所以输入 100 会输出 10000。这个结果看起来有点“累加的意思”,但本质还是错的,因为循环变量 i 全程没有参与运算,你加的永远是固定值 n。1.3 另一个变体:把固定值当成更新对象把混淆方向反过来也常见,另一位初学者改成了这样:int n, i, sum = 0; scanf("%d", &n); for (i = 1; i <= n; n++) { // 更新语句写成了 n++ sum += i; } printf("%d\n", sum);这段代码直接跑的话,窗口大概率卡死,因为 n 在每次循环后都自增,而 i 一直停在 1,条件 i <= n 永远满足。n 会无限制地往上涨,直到 int 溢出变成负数,循环才可能退出。这正是“把循环变量与固定值搞混”的另一种表现:循环变量是负责驱动的“引擎”,固定值只是“终点线”,你把终点线往前挪,比赛就永远结束不了。这个例子提醒我们,错误不一定只在循环体里出现,for 的三个组成部分都有放错变量的可能。正因为如此,我们得回到 for 循环本身的语义去拆解。2. 根因剖析:为什么我会把循环变量和固定值搞混2.1 for 循环的三个部分到底是谁在干活先看 for 的标准结构:for (初始化表达式; 条件表达式; 更新表达式) 循环体;初始化表达式:设置循环变量初值,比如 int i = 1,相当于给计数器归位。条件表达式:每次进入循环体之前都要判断,成立则继续,不成立则退出。更新表达式:每次执行完循环体之后自动执行,通常用于改变循环变量的值。循环体:真正干活的代码。可以这样类比:假设你要求自己在跑步机上跑 n 分钟。i 相当于秒针,每跑一分钟会跳一格;而 n 是你提前设定好的目标分钟数,它只是静静躺在那里,不会自动增加。循环变量 i 是“过程”,固定值 n 是“终点”。累加求和需要关心的是过程里每一分每一秒:第 1 分钟、第 2 分钟、……第 n 分钟,分别把对应的数字加进总时间。如果你在循环体里只执行 sum = n,就等于把“还有 n 分钟”当成“已经跑完 n 分钟”,当然会出错。2.2 “循环多少次”和“加到哪个数”是两码事累加 1+2+...+n 的核心要求是“把从 1 到 n 的每一个整数都累加一次”。这里有两个信息:一个是范围边界 n,另一个是当前遍历到哪个数。使用者输入的是 n,但循环体内处理对象应该是不断更新的 i。你可以想象成顾客到店买货,n 是“一共要买 n 件”,i 是“正在拿第 i 件”。收款员的计算机只认当前拿过来的这一件,不能每一件都按总数算。sum = sum + i 的含义是:把当前这一项的数值 i 加进 sum,然后继续下一步。sum = n 呢?它只把边界值复制进来,前面 1、2、…、n-1 全都丢了。这就是“把固定值误当成当前项”。2.3 三个最容易踩中的思维误区误区一:看到 n 就以为它是“当前的数”。很多初学者觉得,用户输入了 n,那么“这一趟加 n 不就行了”。但这里的 n 在循环开始前就固定了,它不是和 i 一起变化的量。真正每轮变化的只有 i。把 n 写进 sum = n,程序不会报错,所以人很容易被“假正确”骗过。误区二:以为 sum 会有记忆。sum = n 是赋值,不是追加;sum += n 是追加,但有追加也不够,因为 n 不变。时常见到初学者把 sum 理解成“一个自己会累积的盒子”,但其实累积是我们通过代码写出来的动作:sum = sum + i。赋值运算符的优先级是右侧先算,结果再覆盖左侧,执行完 sum 变成一个新值。误区三:把 C 语言的等号当成数学等号。数学里 x = x + 1 是荒谬的,但 C 语言里 sum = sum + i 非常自然。初学者一旦被数学惯性影响,就不太敢写带自身的赋值,于是更容易写出 sum = n 这种“看起来干净、实际上错误”的表达。这个误区需要用“赋值方向:右边给左边”来纠正。3. 从一行错误代码读懂 for 执行逻辑(调试手记)3.1 那一行代码到底按什么顺序执行再回到这段错误代码:for (int i = 1; i <= n; i++) { sum = n; }编译器眼中它的执行顺序是固定的:先执行 int i = 1,这个步骤在整个 for 生命周期里只执行一次。判断 i <= n 是否成立。第一次 i=1,若 n>=1,则成立。进入循环体,执行 sum = n。此刻 sum 被覆盖成 n。执行更新表达式 i++,i 变成 2。回到第 2 步继续判断。直到 i 变成 n+1,i <= n 不成立,循环结束。关键点在于:步骤 3 每一次都把 sum 覆盖成同一个 n,步骤 4 只改变 i,不改 n。循环次数由 i <= n 控制,但 sum 的结果和 i 没有任何关系,因为 sum 的表达式里根本没有 i。这就是“为什么结果永远等于 n”的根本解释。3.2 手动模拟追踪表:把变量变化画出来为了让自己真正看清,我建议初学者在纸上做一次“变量追踪”。以 n=3 为例,错误代码 sum = n 的执行过程是:轮次进入循环体前 i条件判断循环体内 sum 变成更新后 i11真3222真3333真34退出4假最终 sum=3-同样的 n=3,正确代码 sum += i 的执行过程则是:轮次进入循环体前 isum 原值新 sum(sum+=i)更新后 i110122213333364退出46最终 sum=6-两行表放在一起,再笨的 bug 也能现形:错误版本每一轮的 sum 都跳回 3,正确版本每一轮的 sum 都在原基础上增加。手动模拟的价值不是让你成为计算器,而是让你亲眼看见“覆盖”和“累加”的区别。我带过的初学者里,能坚持画三张这种表的人,基本不会再犯同类错误。3.3 用调试器一步步看变量变化除了手动模拟,还可以用调试器实时观察。以 Linux 下常用调试器 gdb 为例,把程序保存为 sum.c,编译时加 -g 生成调试信息:gcc -g sum.c -o sum gdb ./sum进入调试会话后,先用 break 9 在循环体那一行打断点(行号以实际代码为准),再用 run 输入 3 启动程序。程序停在循环体后,分别执行:print i print sum print n接着用 next 单步执行一次循环体,再用 print 看 sum 的变化。如果看到 i 从 1 变到 2,而 sum 依旧等于 3,就说明 sum 没有被累加,而是被赋值了。也可以用 display sum 让每次下一步都自动显示 sum。如果在图形化 IDE 里,步骤更简单:在 for 循环体那行点断点,运行,然后盯着“监视窗口”里的 i 和 sum 变化即可。我现在依然推荐初学者至少手动把 gdb 的这几个命令跑熟:break、run、next、print、display。原因很简单:C 语言逻辑 bug 用眼睛看往往看不出来,但变量值一摊开,真相立刻浮出水面。所谓的调试手记,核心就是这一套“看变量”的功夫。4. 修正方案与常见变体4.1 标准修正:把固定值改成循环变量正确写法:#include <stdio.h> int main() { int n, sum = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) { sum += i; } printf("%d\n", sum); return 0; }sum += i 等价于 sum = sum + i,它把当前项 i 追加到总和里。i 会随着循环推进依次变成 1、2、3……n,所以 sum 最终等于 1+2+...+n。这个版本不需要数学公式,通用于各种“按顺序迭代累加”的场景。如果你想写 while,核心逻辑完全一致:int i = 1; while (i <= n) { sum += i; i++; }两种写法都要记住三点:sum 必须初始化为 0,否则累加结果会建立在随机垃圾值上;循环变量 i 必须能走向 n+1,否则死循环;printf 必须在循环结束后执行,否则你看到的只是中间结果。4.2 几个变体练手:从 1 到 n、阶乘、奇数累加掌握了 i、n、sum 的分工,可以举一反三。下面几个变体都围绕同一个模式:循环体里出现“当前项”,而这个当前项通常由循环变量 i 表达。变体一:求 1 到 n 之间所有奇数之和。可以 for (int i = 1; i <= n; i += 2) 直接让 i 按奇数步进,也可以 for 每次 i++,循环体内 if (i % 2 == 1) sum += i。后一种写法更通用,适合判断条件复杂的场景。变体二:求 n 的阶乘。注意初始值从 0 改成 1,因为乘法从 0 开始会永远得 0:int result = 1; for (int i = 1; i <= n; i++) { result *= i; }这里的循环变量 i 依然是“当前乘到第几个数”,固定值 n 依然只出现在条件里。变体三:求 1/1 + 1/2 + ... + 1/n。由于 i 是整数,1/i 会得到整数 0,所以要写成 1.0 / i,让浮点除法生效。这个例子再次强调“当前项”的形态可以很丰富,但来源永远是循环变量 i,而不是固定值 n。还可以顺手算一下连续自然数的平方和:sum += i * i。这些都是同一个模式:for 负责迭代,循环体内用 i 构造当前项,固定值只当边界。把这些变体都写一遍,比背十遍语法都有用。4.3 避免再次混淆的三条经验法则法则一:只要循环体里表示“当前项”,就从循环变量里取。累加求和的当前项是 i,不是 n;高级点的情况可能是 i*i、1.0/i、arr[i] 之类,但绝不会是 n 本身。法则二:固定值在 for 循环里最多出现在条件判断和边界相关的位置。如果它出现在循环体的赋值右半边,或者出现在更新表达式里,你就要停下来怀疑自己是不是角色搞混了。法则三:写代码前先用中文说清楚要做什么。比如“从 i=1 到 n,每次把 i 加到 sum 上”,然后逐字翻译成 C 代码。翻译时发现“每次”对应的变量是 i,而不是 n,错误就很难发生。这三条法则看起来简单,但在实际调试场景里非常管用。我有一次检查某位同学的作业,他写了 sum = n; 我问他 sum 应该等于什么,他说“等于 n 啊,因为要加到 n”。我再问“那 1 到 n-1 呢”,他才突然意识到自己漏了整个过程。很多 bug 不是语法问题,是脑子里没有把“循环过程”建立起来。5. 常见问题速查与自查清单5.1 初学者的典型困惑问答问:为什么 sum 不初始化就乱?答:局部变量 int sum; 不会自动变成 0,它的初始值是栈上一块随机旧数据。sum += i 在这个随机值上累加,结果必然乱七八糟。正确做法是 int sum = 0;。如果写全局变量,不初始化也会自动是 0,但我不建议为了让代码能跑而把变量全塞成全局。问:我写 sum += n,为什么输出了 nn?答:因为循环体执行了 n 次,每次都在 sum 上加同一个 n,等效于 n 加 n 次,所以结果是 nn。这说明你“把 n 当成了当前项”,但没有意识到当前项必须和循环次数绑定,只有 i 才能做到这一点。问:i <= n 和 i < n 到底差多少?答:前者包含 n,循环执行 n 次;后者不包含 n,循环执行 n-1 次。在累加求和里,如果条件写成 i < n,你就会漏加最后一个值 n,结果变成 1+2+...+(n-1)。边界判断在找 bug 时可以逐值代入,比如 n=1 时 i < n 直接不执行,sum 还是 0。问:我的程序一运行就“卡住”了,怎么办?答:先看 for 的更新表达式更新的是不是循环变量。常见写法 n++ 会让 n 一直变大,条件 i <= n 永远成立,循环退不出去。用 Ctrl+C 终止程序,然后检查更新表达式是 i++ 还是 n++。这类问题用调试器或者快速用 printf 看变量值都能很快定位。5.2 自查清单:提交前逐项打勾检查点正确描述变量初始化sum 初始化为 0,阶乘初始化为 1循环变量初始化i 有明确初值,通常为 1条件边界i <= n 或 i < n 与题意一致,注意是否漏最后一项循环体角色累加表达式右半边使用 i,而不是固定值 n更新表达式更新的是循环变量 i,不是边界 n输出位置printf 在循环结束后,输出最终 sum类型范围n 较大时 int 可能溢出,必要时用 long long这张表是我自己 debug 时用的“扫雷表”。每次写完一个循环,从头到尾扫一遍这七项,能挡掉九成低级错误。尤其是“循环体角色”那一行,正是本文标题里的“循环变量与固定值混淆”,值得你在每次提交前多盯几秒。5.3 调试手记最后想分享的一个习惯我当年学 C 语言时也写过 sum = n。那会儿我盯着输出结果看了十分钟,始终觉得“语法没问题啊,为什么结果是 100”。后来我在循环体里临时加了一行 printf("i=%d sum=%d\n", i, sum); 当程序跑完,我清清楚楚看见 sum 每次都被重置成 n,才真正理解了“覆盖”和“累加”的区别。这个习惯我保留到了今天:凡是写循环求和,先用中文伪代码把角色分工写一行,比如“对每个 i,把 i 加到 sum”;代码写完立刻扫一眼循环体,看里面有没有不该出现的固定值。如果有,马上改。这样做能节省大量时间。如果你现在也被这个错误折磨,我的建议是别急着到处问人。先手动画一遍变量追踪表,再用调试器看一遍变量变化。等到你亲眼看到 i 一个个往前走、sum 在后面一点点累积,for 循环就不再是三个空泛的语法部分,而是一台你会操作、也会修理的机器。这就是这篇调试手记最想让你带走的东西:变量角色清楚,程序才能清楚。