自增(++) & 自减(--)

✏️ 1、内置类型

++a    a++   a=a+1    a+=1

对于内置类型,对于现代编译器而言,这四个的效率都是没有区别的,在VS2019下查看汇编代码如下:

注意:不要在一条语句中使用多个++,因为在不同系统中对这样的情况处理可能不一样,比如y = (4 + x++) + (6 + x++)。这条语句只能保证程序执行到下一条语句之前,x被递增两次,并不能保证4 + x++后立即自增。

✏️ 2、自定义类型

🖋️ 2.1、a++与++a区别

  1. a++是先赋值再自增,++a是先自增再赋值。

  2. a++是先用临时对象保存原来的对象,然后对原对象自增,再返回临时对象,不能作为左值;++a是直接对于原对象进行自增,然后返回原对象的引用,可以作为左值。

  3. 由于要生成临时对象,a++需要调用两次拷贝构造函数与析构函数(将原对象赋给临时对象一次,临时对象以值传递方式返回一次);++a由于不用生成临时变量,且以引用方式返回,故没有构造与析构的开销,效率更高。

左值一般是可以放在赋值符号左边的值,其在内存中有实体;右值一般只能放在赋值符号右边,不具有内存实体,无法通过取地址获得相应对象。如:

💎 2.1.1、效率检测

a++将会有两次的拷贝构造与析构的调用,效率非常低。

💎 2.1.2、左右值检测

可以看到++b返回的对象指针跟b原来的地址是一样的,而b++返回的对象地址跟原来的b地址不一样(应该是临时对象的地址),虽然可以取到地址,但当成左值将有可能导致错误。比如b++ = c就不会将cb,达不到原来的目的。为此,我们应该将后置的++函数返回值定义为const类型:const Point Point::operator++(int),就可以避免这种当成左值情况出现。另外发现返回temp的引用可以减少一次拷贝和析构,但是不建议返回局部变量的引用!因为函数退出,局部变量将析构,引用就会指向不确定内存。

🖋️ 2.2、a+=b与a=a+b的区别

a+=b返回的是a的引用,中间不涉及构造与析构,效率与++a一样。而a=a+b则会生成临时变量,而且以值传递方式返回,会有两次的构造与析构,与a++一样。

✏️ 3、求值过程中的副作用与顺序点

对于该代码,gcc和clang给出了不同的结果。clang给出了5,这很好理解;gcc却给出了6。为什么是6?

如果把i换成不同的变量,我们能写出这样的代码:k = (++i) + (++j)。尽管ij自增先后不确定,但对结果是没有影响的。编译器可以这样拆分它:

那么,我们把三个i代入,就得到了:

因此结果是6。

幕后的“顺序点”

之所以(++i) + (++i)未定义,这涉及到“顺序点”的概念。

“副作用”,即代码中对数据的改变,++ii = ...都会产生副作用。C/C++为了让编译器能更好地优化代码,定义了一些位置,编译器能保证这些位置之后的副作用开始前,之前的副作用完成。这些位置,也就是顺序点。

顺序点包括:

  • 两个语句之间,for循环的括号中包含了三个语句;

  • 函数调用的参数与函数返回之间(但是参数自身没有顺序保证);

  • 短路求值操作符前后;

  • 问号表达式的条件与结果之间;

  • 变量初始化之间、之后。

最后更新于

这有帮助吗?