自增、自减的前缀即先增/减后取回,后缀则是取回然后增/减

运算符重载差异

为了区分前缀和后缀,后缀形式的重载函数有int型参数,在调用处编译器会传一个0作为函数参数。这个参数没有实际用途,为了避免编译器对未使用参数的警告,应避免在声明和实现给这个参数命名。

class UPInt{
public:
    UPInt & operator++();		//++前缀
    const UPInt operator++(int); //++后缀 省略参数名
    UPInt & operator--();		//--前缀
    const UPInt operator--(int); //--后缀
};

除了在参数,还需要注意返回类型和实现方式的不同。

UPInt & UPInt::operator++()//++前缀
{
	*this += 1;//自增
    return *this;
}
const UPInt UPInt::operator++(int)//++后缀
{
	UPInt old = *this;//拷贝原值
    ++(*this);//自增
    return old;//返回原值
}

前缀形式在处理自增后直接返回。而后缀形式首先拷贝原值,进行自增操作后返回原值的拷贝。返回的拷贝值是常量,目的是避免连续的后缀自增/自减运算。

UPInt++++相当于UPInt.opeator(0)++.operator(0)++,第二步自增运算符是对UPInt的拷贝做运算,实际UPInt只自增了一次,这和看起来有明显差异。同时,C++也不允许对内置类型做连续的后缀自增/自减操作,后缀运算符重载函数也应返回常量以和内置类型保持一致。

效率差异

后缀形式的重载函数有一次拷贝操作,需要临时对象保持原值。对于内置类型后缀自增运算符相比前缀有额外的一拷贝指令。

mov     DWORD PTR [rbp-4], 10
#前缀自增
add     DWORD PTR [rbp-4], 1  	#值+1
mov     eax, DWORD PTR [rbp-4]
mov     DWORD PTR [rbp-8], eax
#后缀自增
mov     eax, DWORD PTR [rbp-4]	 #拷贝到eax
lea     edx, [rax+1]		 #值+1
mov     DWORD PTR [rbp-4], edx	 #移动自增后的值到eax
mov     DWORD PTR [rbp-8], eax	 #移动原值到edx

可见,前缀形式的自增运算符效率更高。无论是内置类型还是用户定义类型,都应尽量使用前缀形式的自增/自减运算符。