C语言中递增、递减运算符前缀后缀模式深入理解


前言

在上学的时候,总是搞不清楚 i++ 与 ++i 之间的区别,最近翻看C Primer Plus,总算深入理解了一下这两种模式之间的区别和具体工作流程。

一、递增运算符

递增运算符执行简单的任务,将其运算对象递增1。该运算符以两种方式出现。第一种,++出现在作用的变量前面,称为前缀模式;第二张,++出现在作用的变量后面,称为后缀模式。

这两种模式的主要区别在于递增行为发生的时间。

下面的程序示例说明了递增运算符两种模式是怎么工作的。

#include<stdio.h>
int main(void){
	int a = 1, b = 1;
	int a_post, pre_b;
	
	a_post = a++; // 后缀递增
	pre_b = ++b; // 前缀递增
	printf("a   a_post   b   pre_b \n");
	printf("%1d %5d %5d %5d\n", a, a_post, b, pre_b);
	
	return 0;`
}

运行该程序后,其输出如下:

a a_post b pre_b
2 1 2 2

a和b都递增了1,但是,a_post是a递增之前的值,而pre_b是b递增之后的值,这就是++的前缀形式和后缀形式之间的区别。

前缀形式
q = 2*++a; 首先,a递增1;
然后,2乘以a,并将结果赋值给q

后缀形式
q = 2*a++; 首先,2乘以a,并将结果赋值给q;
然后,a递增1

当单独使用递增运算符时(如i++;),使用哪种形式都没关系,但当运算符和运算对象时更复杂表达式的一部分时,两种形式的效果将会不同。

例如
while(++shoe < 18.5)

该条件相当于提供了一个鞋子尺码到18的表,如果使用shoe++而不是++shoe,尺码表会增至19,因为shoe会与18.5比较后才递增,而不是先递增在比较。

当然,使用下面这种形式也没错:

shoe = shoe + 1;

只不过,有人会怀疑你是不是真正的C程序员。哈哈

如果前缀和后缀形式在代码中会产生不同的影响,那么最好不要这样使用。

例如,不要使用这样的语句:
b = ++i; // 如果使用i++,将会得到不同的结果
应该使用这样的语句:
++i;
b = i; // 无论上面使用的前缀形式还是后缀形式,都不会影响b的值

二、递减运算符

参考上面递增运算符

三、两条规则

不要一次使用太多递增运算符,例如:

While (num < 21)
	{
		printf("%10d %10d\n", num, num*num++);
	}

这个想法看起来不错,写点代码很简略,打印num,然后计算numnum得到平方值,最后把num递增1。但事实上,却存在很大的问题,当printf()获取待打印的值时,可能先对最后一个参数(numnum++)求值,这样在获取其他参数的值之前就递增了num。

本应打印:
5 25
却打印成了:
6 25

甚至它可能会从右向左执行,对最右边的num使用5,对第2个num和最左边的num使用6,

打印出:
6 30

导致这种现象的原因是,C语言中,编译器可以自行选择先对函数中的哪个参数求值。这样提高了编译器的效率,但是如果在函数的参数中使用了递增运算符,那就回产生一些问题。

类似这样的代码,也会带来麻烦:

ans = num/2 + 5*(1 + num++);

同样的问题,编译器可能不会按预期的顺序来执行,你可能认为,先计算第1项(num/2),然后计算第2项5*(1 + num++)。但是,编译器可能先计算第2项,递增num,然后在num/2中使用num递增后的新值。因此,无法保证编译器到底先计算哪一项。

还有一种情况:

n = 3;
y = n++ + n++;

虽然可以肯定,执行完这两天语句之后,n的值会增大2。但是y的值不确定。在对y求值时,编译器可以使用n的旧值(3)两次,然后把n递增1两次,使得y的值为6,n的值为5。或者,编译器使用n的旧值(3)一次,立即递增n,再对表达式中的第2个n使用递增后的新值,然后再递增n,这使得y的值为7,n的值为5。两种情况都可行,准确的说,对于这种情况,结果是未定义的。

应当尽量避免以这样的方式使用递增运算符。

遵循以下两条规则:
1、如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符;
2、如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。

四、执行时间

关于在代码中何时执行递增,C还是做了一些保证。

要了解递增、递减运算符何时执行,首先要了解C语言的另一个术语:副作用

副作用是对数据对象或文件的修改。

例如,语句:
states = 50;
它的副作用是将变量的值设置为50。

???看起来这更像是主要目的,但是从C语言的角度来看,主要目的是对表达式求值。给出表达式4 + 6,C会对其求值得10;给出表达式states = 50,C会对其求值得50。对该表达式求值的副作用是把变量states的值改为50。

同赋值运算符一样,递增和递减运算符也有副作用,使用它们的主要目的就是使用其副作用。

类似地,调用printf()函数时,它显示的信息其实是副作用(printf()的返回值是待显示字符的个数)。

序列点 是程序执行的点。在该点,所有的副作用都在进入下一步之前发生。在C语言中,语句的分号标记了一个序列点。意思是,在一个语句中,赋值、递增、递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。

另外,任何一个完整的表达式的结果也是一个序列点。

什么是完整的表达式?意思就是这个表达式不是另一个更大表达式的子表达式。例如表达式语句中的表达式和while循环中作为测试条件的表达式,都是完整表达式。

概念说的太多太枯燥,还是上个代码实例

while (guests++ < 10)
	printf("%d \n", guests);

初学者可能会以为,这是先使用值,再递增它的意思,先printf()语句中使用guests,再递增它。但是表达式guests++ < 10 是个完整的表达式,因为它是while循环的测试条件,所以该表达式的结束就是一个序列点。

因此,C保证了再程序转至执行printf()之前发生副作用(即,递增guests)。同时,后缀形式保证了guests在完成与10的比较后才进行递增。

理解了这一点,再来看下面这条语句:

y= (4 + x++) + (6 + x++);

表达式4 + x++ 不是一个完整的表达式 ,所以C无法保证x在子表达式4 + x++ 求值后立即递增x。这里完整的表达式是整个赋值语句,分号是序列点,所以C保证程序在执行下一条语句之前递增x两次。但是C没有保证x的两次递增执行的时间,因此要尽量避免与这类似的代码。

另外:逗号运算符也是一个序列点,在使用的时候,同样要注意。

五、总结

使用递增与递减运算符,尽量在一个单独的语句中,不要在一个序列点之前使用多次递增运算符,否则可能会导致无法预期的一些结果。

写了这篇文章,更加巩固了一下C语言的基础语法与知识,希望以后继续坚持写博客的习惯。

前路漫漫,加油共勉。

  • 50
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值