在学习运算符重载时,写了一个类外定义的加法重载,但在对多个对象相加时会报错,看了一些解释,最终发现是左值与右值的问题,解决方法是对两个参数加上const修饰
总体关于两条引用规则:
左值引用 只能引用左值
对左值引用加上const后,其也可以引用右值
下面进行详细解读,若有错误欢迎指正
代码如下:
//mian.cpp文件内容
#include<iostream>
#include"student.h"
//student.h文件内容
#ifndef __HEAD_H__
#define __HEAD_H__
#include<iostream>
class Student
{
private:
int id;
public:
Student(int x):id(x){}
Student(){}
int get_id();
friend Student operator+(const Student &j,const Student &t);
//此处若将两个传参前的const去掉(包括cpp文件中的const),则在多个对象相加时报错:
//[错误] 无法绑定非常量左值引用(类型为 'Student&' )到右值,类型为 'Student'
//但在仅两个对象相加时正常编译
//又发现,仅将左边参数的const保留,编译也可以通过
Student& operator=(Student &j)
{
Student x;
this->id = j.id;
return *this;
}
};
#endif
//student.cpp文件内容
#include"student.h"
int Student::get_id()
{
return this->id;
}
Student operator+(const Student &j,const Student &t)
{
Student x;
x.id = j.id + t.id;
return x;
}
int main()
{
Student a(20),b(30),c(10);
Student d = a+b+c;
std::cout << d.get_id() << std::endl;
return 0;
}
最后发现,这是一个关于左值、右值的问题。
首先介绍什么是左值与右值
在下面代码中我定义了一个整型变量,右边的10被赋值给左边的a
在绝大多数情况中,左值位于左边,右值位于右边,对于此例,是正确的。
而更深入的,我们可以先为左值右值下一个定义:左值是一个在内存中有位置的实际变量;而右值是一个字面量,没有储存空间,则也没有地址。
int a = 10;
左值可以被赋值,但是右值不能被赋值(变量a能赋值而10不能,其没有自己的位置)
而左值可以被赋值给左值,则此时就是前面所说右边的不全是右值,也可以为左值。
int i = a;
函数的返回值可以为左值或右值
为右值情况:
此时函数的返回值为一个临时值,可以被赋给一个左值
int get_x(int x)
{
return x;
}
int a = get_x(10);
为左值情况:
此时函数的返回值为一个引用,且返回的对象需为静态局部变量,否则对x的修改不会产生任何影响但也不会报错(笔者理解为,为返回值声明为引用后编译器会将该返回值当作可修改的左值?),若返回值不为引用则报错
#include<iostream>
using namespace std;
int& get_x()
{
static int x = 10;
cout << "x = " << x << endl;
return x;
}
int main()
{
get_x() = 20; //输出:x = 10
get_x(); //输出:x = 20
return 0;
}
而对于函数调用,也有两种形式,一种是左值调用,一种是右值调用,见下面代码:
#include<iostream>
using namespace std;
void print(int value)
{
cout << value << endl;
}
int main()
{
int x = 10;
print(x); //左值调用:参数为左值,实际是将x的值赋给value
print(10); //右值调用:参数为右值,则是用该右值初始化了一个左值(value)
return 0;
}
讲了这么多,终于要引入正题了,也就是我所遇到的问题:在重载加号运算符时,多个对象相加会报错。
首先有一个引用的规则:左值引用 只能引用左值
将刚刚的函数参数改为引用后,则右值调用会报错,因为在函数调用时,对value进行了初始化,将10赋给value,但因为value为一个左值引用,违反了上述的左值引用只能引用左值,则报错
#include<iostream>
using namespace std;
void print(int& value)
{
cout << value << endl;
}
int main()
{
print(10); //此时使用右值调用会报错
int x = 10;
print(x); //左值调用不报错
return 0;
}
对于我所写的重载函数:
Student operator+(Student& j,Student& t)
{
Student x;
x.id = j.id + t.id;
return x;
}
//重载函数 a + b 相当于operator+(a,b)
他的返回值是一个Student类的对象,但在执行完这个函数体后,这个对象的地址已经被释放,则实际上他的返回值为一个右值(我理解为任何函数体内初始化的非静态局部变量,作返回值时都为右值)而进行一次加法操作a = b + c时没有问题,因为此时加法返回一个右值,赋给左值a;而在进行多次加法b + c + d时,先执行b + c,返回一个右值,再执行这个右值与d的加法,此时出错,因为试图将一个右值赋给引用 j
解决这个问题也很简单,就是在对参数加上const:对左值引用加上const后,其也可以引用右值
加上const后,在编译器内有如下过程:
const int& x = 10;
//在编译器内实际进行如下过程
//编译器会用所给右值创建一个临时变量,将其赋值给左值
int temp = 10;
const int& x = temp;
则重载运算符也同理,只需对参数加上const便能完成多个对象相加
Student operator+(const Student& j,const Student& t)
{
Student x;
x.id = j.id + t.id;
return x;
}