C语言++和--的用法(自增自减运算符)
自增运算符 ++ 和自减运算符 -- 是 C 语言中的两个重要运算符。它们可以被作为前缀或后缀运算符,仅需要运算符左边或右边有一个运算对象即可。
当运算符置于运算对象左边时,被称为前缀模式,例如 ++i 和 --i。相反,当运算符置于运算对象右边时,被称为后缀模式,例如 i++ 和 i--。
与赋值表达式类似,自增和自减表达式会对运算对象本身产生操作。自增表达式会将运算对象加 1,自减表达式会将运算对象减 1。
因此,printf() 函数输出的值为 11 和 9(自增和自减运算符的结果),随后输出 a 和 b 的值,可以发现它们分别被加 1 和减 1 了(自增和自减运算符产生的操作):
程序的运行结果为:
和前缀模式一样,后缀模式也会对运算对象本身产生操作。自增表达式会将运算对象加 1,自减表达式会将运算对象减 1。
因此,printf() 函数输出的值为 10 和 10(自增和自减运算符的结果),随后输出 a 和 b 的值,可以发现它们分别被加 1 和减 1 了(自增和自减运算符产生的操作)。
程序的输出结果为:
在 Visual Studio 编译器中执行编译的结果为 4 3:

图 1 Visual Studio中的编译结果
在 GCC 编译器中执行编译的结果为 4 6:

图 2 GCC中的编译结果
在这个代码片段中,“a++ + a++ + a++” 表示变量 a 被后缀自增了三次。当我们在下一行输出变量 a 的值时,结果是 4,这个结果在 Visual Studio 编译器和 GCC 编译器中是一致的。但是,这两种编译器对于何时产生额外操作有着不同的理解。
在 Visual Studio 编译器中,表达式的求值过程如下:
此时,“a++ + a++ + a++”变成了“1 + 1 + 1”,因此变量 b 的值为 3。执行三次额外操作,即让变量 a 自增三次,因此变量 a 的值变为 4。
在 GCC 编译器中,表达式的求值过程如下:
此时,“a++ + a++ + a++”变成了“1 + 2 + 3”,因此变量 b 的值为 6。
为什么不同的编译器在同一段代码中产生不同的结果?
这是因为某些表达式在求表达式结果的同时,还会对运算对象进行操作。例如,赋值运算符和自增、自减运算符。但是在不同的编译器中,这些操作的时机是不同的。
在 GCC 编译器中,每完成一个子表达式的求值,都会对运算对象进行操作。在 Visual Studio 编译器中,所有对运算对象的操作会累积到所有子表达式求值完成后再进行。
由于对运算对象的操作没有固定的时机,因此只有一个最晚时机:在完整表达式求值结束后,在进入下一步操作之前。完整表达式是指它不是任何一个表达式的子表达式。例如,表达式 b = a++ + a++ + a++ 是一个完整表达式,而 a++ 是它的一个子表达式。
因此,编译器只需要保证在这个表达式结束并进入下一步操作之前,对运算对象进行操作即可。
在这种情况下,前缀模式和后缀模式都会受到影响。例如,表达式 b = ++a + ++a + ++a 在不同的编译器中也可能产生不同的结果。
为了避免编写这种可能在不同编译器下产生不同结果的代码,不要在一个表达式中重复对一个变量进行自增或自减操作。
当运算符置于运算对象左边时,被称为前缀模式,例如 ++i 和 --i。相反,当运算符置于运算对象右边时,被称为后缀模式,例如 i++ 和 i--。
自增自减的前缀模式
下面是前缀模式的一个例子:#include <stdio.h> int main() { int a, b; a = 10; b = 10; printf("%d %d\n", ++a, --b); printf("%d %d\n", a, b); return 0; }在前缀模式下,++ 和 -- 与右运算对象构成自增和自减表达式。前缀自增表达式的结果为运算对象值加 1,前缀自减表达式的结果为运算对象值减 1。
与赋值表达式类似,自增和自减表达式会对运算对象本身产生操作。自增表达式会将运算对象加 1,自减表达式会将运算对象减 1。
因此,printf() 函数输出的值为 11 和 9(自增和自减运算符的结果),随后输出 a 和 b 的值,可以发现它们分别被加 1 和减 1 了(自增和自减运算符产生的操作):
程序的运行结果为:
11 9
11 9
要清晰地区分“表达式结果”和运算符产生的“额外操作”的概念,以便更好地理解自增和自减运算符的使用。
自增自减的后缀模式
下面是后缀模式的一个例子:#include <stdio.h> int main() { int a, b; a = 10; b = 10; printf("%d %d\n", a++, b--); printf("%d %d\n", a, b); return 0; }在后缀模式下,++ 和 -- 与左运算对象构成自增和自减表达式。后缀自增表达式的结果为运算对象本身的值,后缀自减表达式的结果也是如此。
和前缀模式一样,后缀模式也会对运算对象本身产生操作。自增表达式会将运算对象加 1,自减表达式会将运算对象减 1。
因此,printf() 函数输出的值为 10 和 10(自增和自减运算符的结果),随后输出 a 和 b 的值,可以发现它们分别被加 1 和减 1 了(自增和自减运算符产生的操作)。
程序的输出结果为:
10 10
11 9
自增和自减表达式的操作时机
在前面的代码示例中,我们都是在 Visual Studio 中运行程序的。接下来,我们尝试在不同的编译器中运行,观察其运行结果:#include <stdio.h> int main() { int a, b; a = 1; b = a++ + a++ + a++; printf("%d %d", a, b); return 0; }我们通过下面两张图可以看到,同一段代码在不同的编译器中出现了不同的结果:
在 Visual Studio 编译器中执行编译的结果为 4 3:

图 1 Visual Studio中的编译结果
在 GCC 编译器中执行编译的结果为 4 6:

图 2 GCC中的编译结果
在这个代码片段中,“a++ + a++ + a++” 表示变量 a 被后缀自增了三次。当我们在下一行输出变量 a 的值时,结果是 4,这个结果在 Visual Studio 编译器和 GCC 编译器中是一致的。但是,这两种编译器对于何时产生额外操作有着不同的理解。
在 Visual Studio 编译器中,表达式的求值过程如下:
- 第一个 a++:变量 a 的值为 1,后缀自增表达式的结果为 1,此时不做额外操作;
- 第二个 a++:变量 a 的值为 1,后缀自增表达式的结果为 1,此时不做额外操作;
- 第三个 a++:变量 a 的值为 1,后缀自增表达式的结果为 1,此时不做额外操作。
此时,“a++ + a++ + a++”变成了“1 + 1 + 1”,因此变量 b 的值为 3。执行三次额外操作,即让变量 a 自增三次,因此变量 a 的值变为 4。
在 GCC 编译器中,表达式的求值过程如下:
- 第一个 a++:变量 a 的值为 1,后缀自增表达式的结果为 1,求值完成后立即操作运算对象,因此变量 a 的值变为 2;
- 第二个 a++:变量 a 的值为 2,后缀自增表达式的结果为 2,求值完成后立即操作运算对象,因此变量 a 的值变为 3;
- 第三个 a++:变量 a 的值为 3,后缀自增表达式的结果为 3,求值完成后立即操作运算对象,因此变量 a 的值变为 4。
此时,“a++ + a++ + a++”变成了“1 + 2 + 3”,因此变量 b 的值为 6。
为什么不同的编译器在同一段代码中产生不同的结果?
这是因为某些表达式在求表达式结果的同时,还会对运算对象进行操作。例如,赋值运算符和自增、自减运算符。但是在不同的编译器中,这些操作的时机是不同的。
在 GCC 编译器中,每完成一个子表达式的求值,都会对运算对象进行操作。在 Visual Studio 编译器中,所有对运算对象的操作会累积到所有子表达式求值完成后再进行。
由于对运算对象的操作没有固定的时机,因此只有一个最晚时机:在完整表达式求值结束后,在进入下一步操作之前。完整表达式是指它不是任何一个表达式的子表达式。例如,表达式 b = a++ + a++ + a++ 是一个完整表达式,而 a++ 是它的一个子表达式。
因此,编译器只需要保证在这个表达式结束并进入下一步操作之前,对运算对象进行操作即可。
在这种情况下,前缀模式和后缀模式都会受到影响。例如,表达式 b = ++a + ++a + ++a 在不同的编译器中也可能产生不同的结果。
为了避免编写这种可能在不同编译器下产生不同结果的代码,不要在一个表达式中重复对一个变量进行自增或自减操作。