1. 两种形式:前置 vs. 后置
-
前置形式:
++x
或--x
- 先将
x
加(减)1 - 再返回 修改后 的
x
(左值)
- 先将
-
后置形式:
x++
或x--
- 先保存
x
的当前值(临时副本) - 将
x
加(减)1 - 返回 修改前 的临时副本(右值)
- 先保存
int i = 0, j;
// 前置 ++:i 先自增,返回增后的值
j = ++i; // i == 1, j == 1
i = 0;
// 后置 ++:保存 i 的旧值,i 自增,然后返回旧值
j = i++; // i == 1, j == 0
注意:
- 前置版本返回的是一个 左值(仍指向原对象),可继续被赋值;
- 后置版本返回的是一个 右值(原值的副本),不可做左值。
2. 优先级与结合律
-
优先级:
- 后置
x++
/x--
优先级高于一元解引用*x
- 其余与乘除算术运算同级,均高于加减
- 后置
-
结合律:
所有增减运算符均为 右结合(与赋值类似),不过通常不需要链写。
3. 与解引用结合:常见范例
最常见的“看似魔法”写法,用于迭代器或指针遍历:
auto it = v.begin();
while (it != v.end() && *it >= 0) {
// 输出当前元素,再将 it 后移一位
std::cout << *it++ << " ";
}
等同于:
while (it != v.end() && *it >= 0) {
std::cout << *it << " ";
++it;
}
*it++
先取it
的旧值(指向当前元素),解引用输出;- 再对
it
执行后置自增,指向下一个元素。
警告:
不要写成*it = toupper(*it++)
那样同时对同一表达式左、右两侧使用后置增减,会造成未定义行为——因为求值顺序不确定,修改与访问同一对象冲突。
4. 求值顺序与未定义行为
C++(直到 C++17)并未保证二元操作中左右操作数的求值顺序,混合修改和读取同一对象会导致未定义行为:
int beg = 0;
char s[] = "Hello";
while (beg < strlen(s) && !isspace(s[beg])) {
s[beg] = toupper(s[beg++]); // ❌ 未定义行为!
}
s[beg++]
会修改beg
;- 同一个表达式中 又 用
s[beg]
访问它。 - 编译器可先后任意求值,结果不可预测。
避免办法:
- 将“修改”与“访问”拆成两条语句;
- 或明确序列点/C++17 强化的顺序保证。
5. 最佳实践
-
前置首选
- 对原始数值型做增减,使用
++x
/--x
; - 唯有在需要“使用旧值,之后再增减”的场景(如迭代器输出)才用后置。
- 对原始数值型做增减,使用
-
避免副作用冲突
- 不要在同一表达式中既修改又访问同一对象;
- 拆分或明确顺序:
char c = s[beg]; s[beg] = toupper(c); ++beg;
-
清晰胜于省略
- 虽然
*it++
多见,但初学者建议写成两步:cout<<*it; ++it;
,更易读、少出错。
- 虽然
-
遵循现代 C++
- C++17 后标准库算法(
std::transform
、std::for_each
)和范围for
(for (auto &ch : s) ch = toupper(ch);
)常能替代手写指针/迭代器。
- C++17 后标准库算法(
小结
- 前置 (
++x
):先增减,返回新值(左值); - 后置 (
x++
):先返回旧值(右值),再增减; - 注意优先级:后置增减高于解引用;
- 绝不在单一表达式内既修改又访问同一对象;
- 尽量用现代算法与范围
for
,让代码更简洁安全。
熟练掌握增减运算符,将让你在指针运算、迭代器遍历与性能关键代码中游刃有余!