改善C++ 程序的150个建议学习之建议12:优先使用前缀操作符

建议12:优先使用前缀操作符
也许从开始接触C/C++程序的那天起,就记住了前缀和后缀运算,知道了++的前缀形式是“先加再用”,后缀形式是“先用再加”。前缀和后缀运算是C和C++语言中的基本运算,它们具有类似的功能,区别也很细微,主要体现在运行效率上。分析下面的代码片段:
int n=0, m=0;
n = ++m; /*m先加1, 之后赋给n*/
cout << n << m; /*结果:1 1*/
在这个例子中,赋值之后,n等于1,因为它是在将m赋予n之前完成的自增操作。再看下面的代码:
int n=0, m=0;
n = m++; /*先将m赋于n, 之后m加1*/
cout << n << m; /*结果:0 1*/
这个例子中,赋值之后,n等于0,因为它是先将m赋予n,之后m再加1的。为了更好地理解前缀操作符和后缀操作符之间的区别,可以查看这些操作的反汇编代
码。即使不了解汇编语言,也可以很清楚地看到二者之间的区别,注意inc指令出现的位置:
/* m=n++;的反汇编代码*/
mov ecx, [ebp-0x04] /*store n's value in ecx register*/
mov [ebp-0x08], ecx /*assign value in ecx to m*/
inc dword ptr [ebp-0x04] /*increment n*/
/*m=++n;的反汇编代码*/
inc dword ptr [ebp-0x04] /*increment n;*/
mov eax, [ebp-0x04] /*store n's value in eax register*/
mov [ebp-0x08], eax /*assign value in eax to m*/
从汇编代码可以看出,两者采取了相同的操作,只是顺序稍有不同而已。但是,前缀操作符的效率要优于后缀操作符,这是因为在运行操作符之前编译器需要建立一个临时的对象,而这还要从函数重载说起。重载函数间的区别取决于它们在参数类型上的差异,但不论是自增的前缀还是后缀,都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型的参数,当函数被调用时,编译器传递一个0作为int类型参数的值给该函数:
//成员函数形式的重载
< Type > ClassName :: operator ++ ( ); // 前缀
< Type > ClassName :: operator ++ ( int ); // 后缀
// 非成员函数形式的重载
< Type > operator ++ (ClassName & ); // 前缀
< Type > operator ++(ClassName &,int); // 后缀
在实现中,后缀操作会先构造一个临时对象,并将原对象保存,然后完成自增操作,最后将保存对象原值的临时对象返回。代码如下所示:
ClassName & ClassName::operator++() 
{
ClassAdd (1); //increment current object
return *this; //return by reference the current object

ClassName ClassName::operator++(int unused)

ClassName temp(*this); //copy of the current object
ClassAdd (1); //increment current object
return temp; //return copy

由于前缀操作省去了临时对象的构造,因此它在效率上优于后缀操作。不过,在应用到整型和长整型的操作时,前缀和后缀操作在性能上的区别通常是可以忽略的。但对于用户自定义类型,这还是非常值得注意的。当然就像80-20规则告诉我们的那样,如果在80-20规则:一个典型的程序将花去 80% 的时间仅仅运行 20% 的代码。一个很大的程序里,程序数据结构和算法不够优秀,它所能带来的效率提升也是微不足道的,不能使大局有所改变。但是既然它们有差异,我们为什么不在必要的时候采用更有效率的呢?
请记住:
对于整型和长整型的操作,前缀操作和后缀操作的性能区别通常是可以忽略的。对于用户自定义类型,优先使用前缀操作符。因为与后缀操作符相比,前缀操作符因为无须构造临时对象而更具性能优势。

你可能感兴趣的:(C++,++操作符)