自增/自减 运算符
C 程序中最常用的两种运算就是自增 (加 1) 和自减 (减 1)。当然,我们可以通过赋值运算符来完成这类操作:
cpp
i = i + 1;
i = i - 1;
i += 1;
i -= 1;
但这样肯定是比较麻烦的,C 语言提供了一种更简洁的方式:
- ++:自增运算符
- --:自减运算符
大多数C语言学习者都会对前缀和后缀的不同运算方式头疼,这里我们将彻底给大家讲明白这个语法。
前缀自增自减具有右结合性
对于一个简单的前缀运算表达式:--a
- 对于表达式
--a
我们先分析它的主要作用和副作用。- 主要作用是:返回变量
a
自减1后的结果,这就是此表达式计算出来的一个值。 - 副作用是:变量
a
自减1。
- 主要作用是:返回变量
- 前缀运算符的优先级非常高,如果
--a
处于一个复杂的表达式当中,那么它的主要作用就发挥作用。在整个表达式中它代表a
自减1后的值。 - 比如表达式是
--a * 10 + 10
这样,--a
会优先计算,并且它在整个表达式中代表a
自减1后的值。
所以我们可以对前缀自增自减运算符做一个总结:
小练习
cpp
int a = 1;
int b = ++a;
printf("%d\n", --b);
printf("a is %d now.\n", a);
printf("b is %d now.\n", b);
输出的结果分别是什么呢?
shell
后缀自增自减,具有左结合性
对于一个简单的后缀运算表达式:a++
- 表达式
a++
同样具有主要作用和副作用:- 主要作用是:直接返回变量
a
的值,这就是此表达式计算出来的一个值。 - 副作用是:变量
a
自加1。
- 主要作用是:直接返回变量
- 后缀运算符的优先级同样很高,如果
a++
处于一个复杂的表达式当中,那么它的主要作用就发挥作用。在整个表达式中它代表a
变量原本的值。 - 比如表达式是
a++ * 10 + 10
这样,a++
会优先计算,并且它在整个表达式中就代表a
的值。
所以我们可以对后缀自增自减运算符做一个总结:
练习
cpp
int i = 1;
int j = 2;
int k = ++i + j++;
int x = 4;
int y = 5;
int z = x++ + ++y * 10;
请指出上述代码中,各变量的最终结果分别是什么。
首先:i 和 j 的结果肯定是2和3,因为它们各自累加了1。
那么k等于多少呢?
++i
和j++
都处在一个大的表达式中,所以只需要看它的主要作用即可。前置自增自减的主要作用是返回自增自减后的结果,后缀自增自减的主要作用是返回自增自减前的结果。
所以:
k = 2 + 2 = 4
同样的,基于以上原理:
z = 4 + 6 * 10 = 64
注意事项(重要)
首先我们来看一段代码:
cpp
int a = 1;
// a = a++;
// int b = a++ + --a;
分别释放两段注释处的代码,结果是什么呢?符合上述我们对自增自减符号的理解吗?
实际上,上述代码在不同平台编译器运行的结果,可能是不一样的。这又涉及到C语言的一个坑爹的语法陷阱。
在C语言中,类似表达式i = i++
这样,,会引发未定义行为。这意味着不同平台编译器可以选择任意方式来处理这种情况,包括但不限于返回任何值、程序错误崩溃或其余难以预测的结果。
那么为什么这种代码会导致未定义的行为呢?
这是因为同一个变量 i
在同一条语句中被修改了多次。
而
于是不同的编译器平台,就可以自由地决定,同一变量多次修改的执行顺序(执行顺序不同结果必然不同),甚至可以返回任意值或者程序崩溃。
总之,基于程序员日常开发的经验以及出于规避C语言语法陷阱的考量。对于自增自减符号的使用,我们给出以下建议:
- 在for循环中使用自增自减符号是最常见的、且最清晰、稳定准确不出错的用法。
- 自增自减运算符尽量不要用于连接表达式,尽量单独成行,这样就不会因副作用产生歧义。
- 如果一定要将自增自减符号写在表达式中,那么。