最近在看视频学习C++运算符重载部分时,遇到了一个问题,其表现是前置递增( cout << ++myint <<endl;//myint是一个对象)可以正常输出,但是后置递增(cout << myint++ << end)一直报错。网上找的博文解释也相对晦涩。后面自己对此问题着重思考,结合了前面的知识和网上的一些解释,也算是对自己所学知识的巩固,在这里做一个记录。
本文所测试的内容是重载左移运算符(<<)和递增运算符(++)组合运用。采用全局函数重载左移运算符,成员函数重载递增运算符。
1. 写左移运算符重载函数
首先回顾一下左移运算符的重载函数的构造(其中MyInteger是一个类,内的成员属性只含私有的m_num),当时本着不浪费内存的原则,想统统都用引用传递(此函数的第二个形参的引用是问题的伏笔):
//全局函数重载左移运算符
ostream& operator<<(ostream& cout, MyInteger &myint)
{
cout << myint.m_num;
return cout;
}
注意最后需要让此全局函数作为测试类MyInteger的一个友元,才能访问对象myint中的私有属性m_num。
此时能够正常使用对象的输出功能:cout << myint << endl;
2. 写递增运算符重载函数(包括前置和后置)
利用成员函重载前置递增运算符和后置递增运算符:
MyInteger &operator++()//前置递增重载函数
{
this->m_num ++;
return *this;
}
MyInteger operator++(int)//后置递增重载函数,用int进行重载
{
MyInteger temp = *this;
m_num++;
return temp;
}
此处解释为什么前置递增重载函数返回的是一个引用(*this此时返回的是对象本身),而后置递增重载函数反回的是一个值。
前置:为什么返回引用,不返回值??
如果返回值,cout << ++(++myint) << endl;
此代码在执行一次递增之后再执行递增会拷贝复制出新对象,后续便是对一个新的对象进行操作,无法链式操作。返回引用是为了一直对一个数据进行递增操作。
后置:为什么返回值,不返回引用??
temp是局部变量,在当前函数执行完之后,局部变量就会被释放,返回引用就是非法操作。(此时编译器比较人性会做一次值保留,但是后续使用便不会保留,因此禁止如此非法操作)
3. 通过测试函数测试结果
现在有了左移运算符的重载全局函数,以及递增运算符的重载成员函数。下面进行测试输出;
//测试函数
void test1()
{
MyInteger myint;//创建一个测试对象
cout << ++myint <<endl;//实现前置递增并输出
cout << myint++ << endl;//实现后置递增并输出
cout << myint << endl;
}
此处,问题便出现了,代码:
cout << ++myint <<endl;
cout << myint << endl;
都能正常输出,但是加上:cout << myint++ << endl;
程序便报错。
4. 解决方法
后面照着其他人的说法以及自己的测试,有两种修改方法:
1、在左移运算符的重载函数ostream& operator<<(ostream& cout, MyInteger & myint)的第二个形参的前面加一个const变成:
ostream& operator<<(ostream& cout,const MyInteger & myint)
2、去掉ostream& operator<<(ostream& cout, MyInteger & myint)的第二个形参前面的引用(&)符号。
5. 本质解释
自己对此两种方法进行了思考,发现就是一个简单的常量引用的问题。
首先解释一下代码的执行顺序:不论<<++myint还是<<myint++,虽然其显示结果也和默认的递增符一样(前置先递增再输出,后置先输出再递增),但其本质上却都是先调用++重载,再调用<<重载(这一点可能与默认数据类型的前置后置执行顺序不一样)。
接下来解释左移重载写ostream &operator<<(ostream &cout , MyInteger &myint)时,为什么前置递增(cout<<++myint)可以,而后置递增(cout<<myint++)会报错:
1、前置递增(++myint)时,重载返回的是对象myint,因此可以采用引用的方式传左移重载函数(<<)中进行重载;
2、但后置递增(myint++)时,对象myint虽然加了1,但是返回的却是局部变量temp记录的值(常量)而不是对象myint,此时需要拿个变量去接收,但是左移重载函数并没有实现此功能,而是直接传进去引用了,相当于MyInteger &myint = 10(直接给常量起别名myint,但这是非法的)。
因此问题就变成了解决常量引用的问题。有两种修改方法来修改左移重载函数的函数头(回到了前面所学知识):
1、……,MyInteger myint) 将常数0传入第二个形参,相当于MyInteger myint = 0(隐式转换法),即MyInteger myint=MyInteger(0)(定义一个新形参对象myint,然后将0传进去) ;
2、……,const MyInteger &myint) 将常数0传入第二个形参,相当于const MyInteger &myint = 0(开辟一片新内存,并用新的引用名myint操作这块内存;此时即使不知道此内存变量的具体名字,但是可以用此引用名对内存进行操作)
递减同理。
整个测试源码如下:
#include<iostream>
using namespace std;
//定义一个测试类
class MyInteger
{
friend ostream& operator<<(ostream& cout, const MyInteger &myint); //左移运算符重载函数作友元
public:
MyInteger() //无参构造函数初始化对象
{
m_num = 0;
}
MyInteger &operator++()//前置递增重载函数
{
this->m_num ++;
return *this;
}
MyInteger operator++(int)//后置递增重载函数
{
MyInteger temp = *this;
m_num++;
return temp;
}
private:
int m_num;
};
//全局函数重载左移运算符
ostream& operator<<(ostream& cout, const MyInteger & myint)
{
cout << myint.m_num;
return cout;
}
//测试函数
void test1()
{
MyInteger myint;//创建一个测试对象
cout << ++myint <<endl;//实现前置递增并输出
cout << myint++ << endl;//实现后置递增并输出
cout << myint << endl;
}
int main()
{
test1(); //调用测试函数
system("pause");
return 0;
}
如有错误,敬请指正。