C++自增运算符的探索
今天在阅读C++ primer时遇到如下一段代码:
istream_iterator<int> in_iter(cin);//read ints from cin istream_iterator<int> eof;//istream "end" iterator //read until end of file,storing what read in vec while(in_iter != eof) { //increament advances the stream to the nest value //dereferent reads next value from the istream vec.push_back(*in_iter++); }
对于*in_iter++,我们知道++的优先级高于*,所以相当于写成*(in_iter++),意思是对istream_iterator对象做自增运算使该迭代器在流中向前移动。然而,使用后自增运算的表达式,其结果是迭代器原来的值。自增的效果是使迭代器在流中移动到下一个值,但返回值指向前一个值的迭代器。对该迭代器进行解引用获取该值。
为了完全理解这段话,需要深刻了解自增运算符。
自增运算符分为前增运算符++a与后增运算符a++,先看下面代码:
#include <iostream> using namespace std; int main() { int a=5; int b=++++a; cout<<"b= "<<b<<"\t"<<"a= "<<a<<endl; return 0; }
运行结果:
b= 7 a= 7
#include <iostream> using namespace std; int main() { int a=5; int b=(a++)++; cout<<"b= "<<b<<"\t"<<"a= "<<a<<endl; return 0; }
运行结果:
发生编译错误:error C2105: '++' needs l-value
我们知道:自增运算符++是结合方向是自右自左(VC++6.0),所以++++a也在写成++(++)a。根据++结合性,a++++肯定是错误的。至于(a++)++和++a++是否会发生错误,分析a++与++a后自会知道。
a++与++a的差别:
(1)在运算过程中,先将对象进行递增修改,而后返回该对象(其实就是对象的引用)的叫前递增运算++a。在运算符重载函数中采用返回对象引用的方式编写。
(2)在运算过程中,先返回原有对象的值,而后进行对象递增运算的叫后递增运算a++。在运算符重载函数中采用值返回的方式编写,重载函数的内部实现必须创建一个用于临时存储原有对象值的对象,函数返回的时候就是返回该临时对象。
现在来分析下上面提出的问题:对于 (a++)++,先运算(a++),但(a++)返回的不是引用,而是原有a值的一个拷贝,而此时的拷贝不再是一个变量,还是一个常量,故不能当作左值继续参加括号外部的++运算。至于++a++,即++(a++),同样不能编译通过,原因是同样的道理。
自增运算符++的重载:
在编写运算符重载函数的时候该如何区分前递增运算符重载函数与后递增运算符重载函数呢?方法就是:在后递增运算符重载函数的参数中多加一个int标识,标记为后递增运算符重载函数。
#include <iostream> using namespace std; class Test { public: Test(int a=0) { Test::a=a; } friend Test& operator++(Test&); friend const Test operator++(Test&,int); public: int a; }; Test& operator++(Test& val)//前递增 { val.a++; return val; } const Test operator++(Test& val,int)//后递增,int在这里只起到区分作用,没有实际意义 { Test temp(val);//这里会调用拷贝构造函数进行对象的复制工作 val.a++; return temp; } int main() { Test test(5); ++++test; cout<<"test.a= "<<test.a<<endl; cout<<"后递增情况下临时存储对象的值状态:"<<(test++).a<<endl; cout<<"test.a= "<<test.a<<endl; Test test1(5); (test1++)++; cout<<"test1.a= "<<test1.a<<endl; cout<<"前递增情况下临时存储对象的值状态:"<<(++test1).a<<endl;
cout<<"test1.a= "<<test1.a<<endl; return 0; }
运行结果:
test.a= 7
后递增情况下临时存储对象的值状态:7
test.a= 8
test1.a= 6
前递增情况下临时存储对象的值状态:7
test1.a= 7
上面代码是非成员格式,在《C++编程惯用法—高级程序员常用方法和技巧》中讲到对所有的一元操作符建议重载操作符函数为成员函数。
#include <iostream> using namespace std; class Test { public: Test(int a=0) { Test::a=a; } Test& operator++(); const Test operator++(int); public: int a; }; Test& Test::operator++()//前递增 { this->a++; return *this; } const Test Test::operator++(int)//后递增,int在这里只起到区分作用,没有实际意义 { Test temp(*this);//这里会调用拷贝构造函数进行对象的复制工作 this->a++; return temp; } int main() { Test test(5); ++++test; cout<<"test.a= "<<test.a<<endl; cout<<"后递增情况下临时存储对象的值状态:"<<(test++).a<<endl; cout<<"test.a= "<<test.a<<endl; Test test1(5); (test1++)++; cout<<"test1.a= "<<test1.a<<endl; cout<<"前递增情况下临时存储对象的值状态:"<<(++test1).a<<endl; cout<<"test1.a= "<<test1.a<<endl; return 0; }
运行结果:
test.a= 7
后递增情况下临时存储对象的值状态:7
test.a= 8
test1.a= 6
前递增情况下临时存储对象的值状态:7
test1.a= 7
在这里注意一点:为什么(test1++)++能编译通过,而上面提到的(a++)++却不能编译通过,这是因为进行(test1++)后,假设临时变量为temp,则temp=test1(原来的test1值),然后进行temp++,其实是进行的是temp.a++,而temp成员a是一个变量,所以可以进行自增运算。
这里注意,红色标志的const是我后面加上去的,但加上去了,我上面的运行结果就会出错了,因为temp为const对象了,后面自然不能自增++了,而且我认为应该加const,避免(test1++)++这样出现。
最后再想下,
Test& operator++();
const Test operator++(int);
为什么一个返回引用,一个要返回const。
参考:
(1)《30天掌握C++精髓》
(2)http://www.cnblogs.com/hazir/archive/2012/04/16/2451933.html#2356352
(3)《more effective c++》 4.2 Item M6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别