C 赋值与自增自减
更新时间:2026-08-26。本文是
languages/c/主题入门层第 11 篇。=、+=、++这些看着简单的运算符,其实是新手 bug 的重灾区——不是因为不会用,而是因为它们牵扯 C 特有的"求值顺序"和"序列点"概念。
本文要回答的问题
a += 3和a = a + 3完全等价吗?x = y = 5这种连等是什么意思?- 为什么说"一条语句里多次修改同一个变量"是未定义行为?
一、赋值是表达式,不是语句
C 里 = 是运算符,赋值表达式本身有个值(就是赋进去的值),所以可以"连等":
int a, b, c;
c = b = a = 5; // 从右往左:a=5,表达式值是5;b=5;c=5还可以把赋值嵌进条件(不推荐,但要知道):
int x;
if ((x = read_byte()) != -1) { // 先赋值,再比较
// 处理 x
}这也正是 if (x = 5) 是 bug 的原因:它不是"比较 x 和 5",而是"把 5 赋给 x",条件恒真。想比较用 ==,这是 C 新手第一大坑。-Wall 会对 if (x = 5) 给出警告(提示加括号表示"我故意的")。
二、复合赋值:+= 家族
| 复合赋值 | 等价于 |
|---|---|
a += b | a = a + b |
a -= b | a = a - b |
a *= b | a = a * b |
a /= b | a = a / b |
a %= b | a = a % b |
a <<= k | a = a << k |
a &= mask | a = a & mask |
| `a | = mask` |
几乎等价,但有个细节:a += b 中 a 只求值一次。a[f(x)] += 1 里 f(x) 只调用一次;展开成 a[f(x)] = a[f(x)] + 1 则调用两次(若 f 有副作用就有区别)。日常写代码两者没差,复合赋值更简洁、意图更明确,多用。
三、自增自减:前置与后置
int i = 1;
int a = i++; // a = 1(旧值),i = 2
int b = ++i; // i 已是 3,b = 3(新值)| 写法 | 返回 | i 的变化 | 用途 |
|---|---|---|---|
i++ | 旧值 | +1 | 循环里遍历 |
++i | 新值 | +1 | 计数后立即用新值 |
独立成句时(i++;)无差别,写惯哪种都行。C++ 里 ++i 略高效,C 里现代编译器对两者生成同样代码。
四、序列点:为什么"一条语句改两次"是 UB
i = i++ + ++i; // 未定义行为!编译器可以按任何顺序算
a[i] = i++; // 未定义行为!i 被读两次还写一次C 标准规定:在一个序列点(通常是分号)之间,同一个变量不能被修改超过一次。违反就是未定义行为——程序不是"报错",而是行为不可预测,不同编译器、不同优化级别结果都可能不同。
实际代码里,a[i] = i++; 这种写法可能有人觉得"挺自然",但它在不同编译器下确实会给出不同结果(先取 i 还是先写 i)。铁律:一条语句里,同一个变量最多修改一次。
五、逗号运算符(顺便一提)
int x = (1, 2, 3); // x = 3:逗号表达式从左到右算,值是最后一个逗号运算符的"值是最后一个表达式",在 for 循环里偶尔用到(for (i = 0, j = 10; ...))。但注意声明和逗号表达式的区别:int a, b; 里的逗号是分隔符,不是运算符。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
= 当 == | 条件恒真 | 比较用 ==,开 -Wall |
| 一条语句多次自增 | 结果因编译器而异 | 一语句只改一个变量 |
a[i] = i++ | 未定义行为 | 分开两行写 |
| 连等忘初始化 | 用到未初始化变量 | 声明即初始化 |
| 混淆逗号运算符与分隔符 | 声明解析错误 | 只在 for 里用逗号运算符 |
七、与本站主线衔接
- 赋值和自增在汇编里长什么样(
inc、add指令),见与汇编、性能剖析衔接; - 未定义行为的完整原理,见专家层未定义行为与优化;
- 序列点与求值顺序在编译器优化中的角色,见编译优化行为。
一句话总结
= 是赋值、== 是相等,这是 C 第一大坑;+= 简洁且只求值一次;i++ 返回旧值、++i 返回新值,独立成句无差别;一条语句内同一个变量最多改一次——四条记住了,赋值和自增这关稳稳过。