C++ | 重载运算符实现后置++的时候 关于函数形参不可使用引用的问题

问题描述:

首先我们使用重载运算符来实现对一个对象的前置++运算

class MyInteger {
    
    
public:
	MyInteger() {
    
     //初始化
		m_Num = 0;
	}
	//前置++
	MyInteger& operator++() {
    
    
		m_Num++; //先++
		return *this; //再返回  这里必须要返回引用 才可以实现链式操作
	}
	int m_Num;
};
ostream& operator<<(ostream& cout, MyInteger& myint) {
    
    
	out << myint.m_Num;
	return cout;
}

//前置++ 先++ 再返回
void test01() {
    
    
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
	cout << ++(++myInt) << endl;
}
int main() {
    
    
	test01();
	system("pause");
	return 0;
}

再使用重载运算符来实现对一个对象的后置++运算

class MyInteger {
    
    
public:
	MyInteger() {
    
    
		m_Num = 0;
	}
	//后置++
	MyInteger operator++(int) {
    
     //占位符
		//先返回 这里返回的是一个局部变量 所以返回值不能用引用
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;
	}
	int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
    
     //注意这里第二个参数,不能用引用
	out << myint.m_Num;
	return out;
}
//后置++ 先返回 再++
void test02() {
    
    
	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}
int main() {
    
    
	test02();
	system("pause");
	return 0;
}

问题:
后置++运算符的实现里面,重载<<运算符的时候,函数的参数ostream& operator<<(ostream& out, MyInteger myint)第二个不能使用引用类型MyInteger& myint,会报错。

问题原因:

错误解释
有一个常见的解释(包括b站的视频里很多弹幕也是这么说的)是说:
cout << myInt++ << endl; 这句话是先执行myInt++,也就是说先执行++运算符的重载,然后再执行<<运算符的重载。
而由于后置++运算符重载的特点(需要先返回未加的对象,所以需要一个临时变量),导致其返回值是一个局部变量,所以这个局部变量传入<<运算符重载函数的时候,就报错了,因为参数是一个引用变量,类型不一致,所以报错
这个说法是错的。
正确解释:
上面的说法错在什么地方呢?
确实是先执行的++运算符的重载,返回了一个局部变量,但是,这个变量在++重载函数里面是局部变量
返回之后就不是局部变量了,返回是用的浅拷贝,所以在主函数里面会复制一份,但是由于没有用变量来承接这个返回值,所以编译器会用一个匿名变量来承接这个变量。所以这里是一个匿名变量
cout << myInt++ << endl;
而之所以报错的原因是:匿名变量不能初始化引用。

因为匿名变量的声明周期仅存在于当前行,执行完当前行便被释放。
所以C++里面不允许用匿名对象来初始化引用(马上就释放了,引用了做的修改也都没用)。
(可以先这么理解,其实这句话严格来说应该是:匿名变量不能初始化非常量引用。至于为什么,请看下面拓展一)

拓展一

有些同学发现,可以使用引用的,只要加个const就行了:

ostream& operator<<(ostream& out, const MyInteger& myint) {
    
     
	out << myint.m_Num;
	return out;
}

这样做确实是可以的,原因是什么呢?
这就涉及到一些关于引用知识点:
首先引用有两个特性:

  • 引用必须初始化
  • 引用初始化后不可以改变

而之所以有这两个特性,则是因为引用的本质
引用的本质:在c++内部实现是一个指针常量
(指针常量:指针的指向不可更改,指针的值可以更改)
int &ref = a其实就是一个int* const ref = &a
那么MyInteger& myint其实就是MyInteger* const myInt
const MyInteger& myint就是const MyInteger* const myInt
这个东西叫常引用。
为什么写了个常引用就可以了呢?
要说清楚这个就比较复杂了:
首先我们来看三种函数传参方式

int test(a){
    
    
	a =10;
}
int main(){
    
    
	int a =1;
	cout<<a<<endl;
}

这个一看就懂,输出的a是1,因为函数传参是复制一份(新开辟了一片空间),所以在函数里面修改不会改变主函数的值。
而下面这个用了引用,就可以改了。

int test(int &a){
    
    
	a =10;
	cout<<&a<<endl;
}
int main(){
    
    
	int a =1;
	cout<<&a<<endl;
}

用了引用,在函数里面不会新开辟一片空间,而是直接引用原来的值,也就是说和主函数中的a指向同一片空间。所以两个输出的地址是一致的。
这些大家有点基础的都知道。接下来看常引用:

int test(const int& a){
    
    
	cout<<&a<<endl;
}
int main(){
    
    
	int a =1;
	cout<<&a<<endl;
}

可以试一下,这里输出的地址仍然是一样的。说明常引用也是和主函数指向同一片空间,不会开辟新的空间。
那么常引用和引用的区别在哪呢?
首先,我前面说了,常引用其本质就是const int * const a,所以既不可以修改指针的指向,也不可以修改指针指向的值。
那么看起来常引用和引用区别仅仅是不让修改指针的值罢了。
其实还有一个区别,当作为参数的时候,常引用和引用并不一样。

区别:一般来讲,常引用和引用都不会开辟新的空间,与主函数指向同一片空间。但是,当被引用的变量是临时变量的时候,常引用会开辟一片临时空间,来创建一个临时常量。

这句话换一个更加精确的说法是:

区别:左值引用只能接收左值,而常引用可以接收右值。使用常引用作为参数的函数会在必要的时候自动创建一个临时常量。

解释一下这句话:
来看一个简单的程序
我们将re()的值传给一个函数,用引用做参数来接收一下。

int test(int& a) {
    
    
    cout << &a << endl;
    return a;
}
int re() {
    
     //返回一个局部变量a 
    int a = 5;
    return a;
}
int main() {
    
    
    cout << test(re()) << endl;
    return 0;
}

程序会报错。但是报错内容并不是:匿名对象不能初始化引用。
而是:非常量引用的初始值必须是左值。
在这里插入图片描述

这里我们要说一下C++中左值、右值的区别了

参考链接:https://blog.csdn.net/ai_faker/article/details/118488011

在c++中,左值是一个指向内存的东西,换句话来讲,左值有地址,保存在内存中,右值则为不指向任何地方东西,即不在内存中占有确定位置。一般来说,右值是暂时和短暂的,而左值则存活的很久。
右值又可以分为纯右值,和将亡值。
在c++98中,右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。临时变量指的是非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b;不跟对象关联的字面量值,例如true,2,”C”等。

知道了左值、右值之后。
在C++中左值引用只能接受左值!

int a =10; //a是左值  10是右值
int &b = a;//&b是左值引用  只能接受左值  这么写是可以的
int &c = 10;// 这么写会报错  左值引用不能接受右值
int &d = test(); // 报错  因为test的返回值是一个临时变量 是右值  

可以去编译器试试。
在这里插入图片描述
绕了一大圈,终于讲到重点了。
所以函数的返回值是一个右值。(临时变量是右值),而<<重载函数中的参数MyInteger &myint是一个左值引用。
前面我们说匿名变量不能初始化引用。,为什么呢?其实这里才是本质,因为左值引用只能接受左值。

参考链接:https://blog.csdn.net/weixin_40378209/article/details/124198280

所以这就是前面出错的原因。
那么为什么加上const就可以了呢?const MyInteger &myint
报错内容也写的很清楚了:非常量引用的初始值必须是左值
那么常量引用就可以了呗
常量引用是一个例外,常量引用可以接收右值。
有些文章会表述为:左值引用加上关键字 const,也可以接收右值。
这就是常引用和引用的一个区别。

以下四句话描述的都是一个意思:常引用可以接受右值!

  • 左值引用不能接受右值,加上const就可以接受右值
  • 临时变量只能被常引用
  • 匿名对象只能被常引用
  • 匿名对象/临时变量不能初始化非常量引用

绕了一大圈,终于解释完了。
常引用可以接收右值。
因为:使用常引用作为参数的函数会在必要的时候自动创建一个临时常量。
在这里插入图片描述

参考链接:
https://blog.csdn.net/ai_faker/article/details/118488011
https://blog.csdn.net/weixin_40378209/article/details/124198280
https://zhuanlan.zhihu.com/p/532164085

拓展二:

还有一个小问题,那么由于后置++运算符重载时需要返回临时变量,导致其没有办法实现链式操作了
这个怎么解决?
其实很简单,应该可以想到,既然只能返回局部变量,那么就搞一个全局变量来记录,就可以返回引用了。
实现过程可以看看这个博客:
https://blog.csdn.net/qq_64793063/article/details/128136195

猜你喜欢

转载自blog.csdn.net/holly_Z_P_F/article/details/129848335
今日推荐