在C++中,自增与自减均有前缀形式与后缀形式。
int i = 0;
++i; // 自增前缀形式
i++; // 自增后缀形式
--i; // 自减前缀形式
i--; // 自减后缀形式
总所周知,前缀与后缀的区别就是前缀是直接操作后再返回,而后缀则是先用临时对象保存旧值,增加旧值后再返回之前的临时对象。即有:
i = 0;
a = ++i; // ===> i=i+1; a=i;
a = i++; // ===> b=i; i=i+1; a=b;
可见从实现上来讲,前缀形式比后缀形式效率要高,特别是对于自定义类型,因为少了一次临时对象的创建与释放。自增与自减的重载定义只是符号不同,而前缀形式与后缀形式则是参数不同。
#include <iostream>
using namespace std;
class MInt {
public:
MInt& operator++(); // 后缀++
const MInt operator++(int); // 前缀++
MInt& operator--(); // 后缀--
const MInt operator--(int); // 前缀--
MInt& operator+=(int);
MInt() {
_value = 0;
}
~MInt() {
cout << "in Mint dtor" << endl;
}
MInt& operator=(const MInt &mInt);
int getValue() { return _value; }
private:
int _value;
};
MInt& MInt::operator++() {
*this += 1;
return *this;
}
const MInt MInt::operator++(int) {
MInt oldValue = *this;
++(*this);
return oldValue;
}
MInt& MInt::operator--() {
*this += -1;
return *this;
}
const MInt MInt::operator--(int) {
MInt oldValue = *this;
--(*this);
return oldValue;
}
MInt& MInt::operator+=(int value) {
_value += value;
return *this;
}
MInt& MInt::operator=(const MInt &mInt) {
_value = mInt._value;
return *this;
}
int main() {
MInt i;
cout << "before postfix ++" << endl;
i++;
cout << i.getValue() << endl;
cout << "before prefix ++" << endl;
++i;
cout << i.getValue() << endl;
cout << "after all test" << endl;
return 0;
}
上面是完整的重载例子,由于自增与自减是差不多的,所以只测试了自增的前缀形式与后缀形式,输出如下:
before postfix ++
in Mint dtor
1
before prefix ++
2
after all test
in Mint dtor
从上面的输出可以看到,后缀形式比前缀形式多了一个析构函数的输出,也说明了在后缀形式中,产生了临时对象。对于自定义类型来说,频繁的调用后缀形式可能会带来很大的开销,所以一般是使用前缀形式。
重载的实现
让我们再来看看上面重载的实现,对于前缀形式与后缀形式主要的区别就是返回值类型以及参数类型。
这里后缀形式又一个int类型参数是C++规定的,编译器可以通过该参数来区分是前缀还是后缀。当后缀形式的函数被调用的时候,编译器会传递一个0作为int参数的值给该函数,而在该函数中,并没有用到这个值。不嫌麻烦的话,也可以通过“i.operator++(0)”和“i.operator++()”来分别调用 i 的后缀形式和前缀形式的自增。
对于后缀形式,返回值的类型是const变量,而前缀形式则是引用。对于前缀形式,这是很显然的因为要返回自增后的自己,那为什么后缀形式要返回const变量呢?如果不是const变量,则下面的代码是可以通过编译的:
MInt i;
i++++;
就是第一个调用的++函数的返回值调用了第二个++函数,但是我们知道后缀形式返回的并不是自身,而是一个临时变量,所以第二个++函数是在一个临时变量上操作的,很显然这个语句实际上 i 只自增了一次,与我们所期望的是不一样的。还有就是内置类型是禁止连续两次后缀操作的,当我们自定义自己的类型时,一个好的准则就是使该类的行为与int类型一致。
通过上面的重载代码,我们可以看到在后缀形式的实现中调用了前缀形式的实现,这样子我们就只需要维护前后最形式的实现就可以了。