C语言for循环累加求和经典错误剖析:sum=n为何总输出n? 前两天一个刚学 C 语言的朋友发来一段代码问了一个几乎每个初学者都会问的问题为什么我算 1 加到 100sum 输出怎么都是 100我一看代码就明白了——这是 C 语言累加求和里最典型的经典错误把循环变量 i 和固定值 n 搞混了。这个错误在初学者里面出现频率极高因为你可能觉得自己已经理解了 for 循环可真坐下来写累加求和手却不自觉地就把 n 写进了循环体。这篇文章既是错误剖析也是一份调试手记我会用一段错误代码、一张变量追踪表和一个完整的调试过程把 for 循环的执行顺序彻底拆开让你以后看到 sum n 这类代码就知道哪里出了问题。适合刚学完 for 语法、上机实验总卡 bug 的初学者也适合想再捋一遍思路的老读者。1. 错误现场一段“经典”的累加代码1.1 先看这段代码你中招了吗假设题目要求输入一个整数 n使用循环计算 12...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 “循环多少次”和“加到哪个数”是两码事累加 12...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 是否成立。第一次 i1若 n1则成立。进入循环体执行 sum n。此刻 sum 被覆盖成 n。执行更新表达式 ii 变成 2。回到第 2 步继续判断。直到 i 变成 n1i n 不成立循环结束。关键点在于步骤 3 每一次都把 sum 覆盖成同一个 n步骤 4 只改变 i不改 n。循环次数由 i n 控制但 sum 的结果和 i 没有任何关系因为 sum 的表达式里根本没有 i。这就是“为什么结果永远等于 n”的根本解释。3.2 手动模拟追踪表把变量变化画出来为了让自己真正看清我建议初学者在纸上做一次“变量追踪”。以 n3 为例错误代码 sum n 的执行过程是轮次进入循环体前 i条件判断循环体内 sum 变成更新后 i11真3222真3333真34退出4假最终 sum3-同样的 n3正确代码 sum i 的执行过程则是轮次进入循环体前 isum 原值新 sumsumi更新后 i110122213333364退出46最终 sum6-两行表放在一起再笨的 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 最终等于 12...n。这个版本不需要数学公式通用于各种“按顺序迭代累加”的场景。如果你想写 while核心逻辑完全一致int i 1; while (i n) { sum i; i; }两种写法都要记住三点sum 必须初始化为 0否则累加结果会建立在随机垃圾值上循环变量 i 必须能走向 n1否则死循环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 开始会永远得 0int 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 循环里最多出现在条件判断和边界相关的位置。如果它出现在循环体的赋值右半边或者出现在更新表达式里你就要停下来怀疑自己是不是角色搞混了。法则三写代码前先用中文说清楚要做什么。比如“从 i1 到 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结果变成 12...(n-1)。边界判断在找 bug 时可以逐值代入比如 n1 时 i n 直接不执行sum 还是 0。问我的程序一运行就“卡住”了怎么办答先看 for 的更新表达式更新的是不是循环变量。常见写法 n 会让 n 一直变大条件 i n 永远成立循环退不出去。用 CtrlC 终止程序然后检查更新表达式是 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 循环就不再是三个空泛的语法部分而是一台你会操作、也会修理的机器。这就是这篇调试手记最想让你带走的东西变量角色清楚程序才能清楚。