背景
我写了一个矩阵加减的程序,其中main.cpp
如下:
// main.cpp |
他创建了两个矩阵对象m1
和m2
,分别打印m1
,m2
以及m1 + m1
。如果注释掉std::cout << "m1 + m2 = \n" << m1 + m2 << std::endl;
,可以正常执行,输出结果如下:
m1 = |
但是一旦加上这句,就会报如下错误:
➜ matrix-rref g++ main.cpp matrix.cpp -o main |
意思是找不到参数列表的为‘std::basic_ostream<char>
和matrix
的输出流重载。起初,我以为是运算符优先级的问题,但发现cout << 1 + 3 << endl
这种是能正常输出结果的。这次错误的原因编译器已经告诉我们了:cannot bind non-const lvalue reference of type ‘matrix&’ to an rvalue of type ‘matrix’
,即:无法将的左值matrix
引用引用绑定到matrix
右值。
我检查了我重载的输出流函数原型:friend std::ostream& operator << (std::ostream &out, matrix &m);
发现matrix &m
前没有加上const
修饰,记得课上看到的输出输出流重载的参数写法都是const T& v
,或许和这有关?把函数修改为:friend std::ostream& operator << (std::ostream &out, const matrix &m);
再次编译,果然编译通过了,成功运行。以前的写法都是照着博客上的写法依葫芦画瓢,没有深究这里const
修饰符的意义,但实际上这里涉及到一个重要的规则:非常值左值引用不能绑定右值。接下来,解释一下什么是左值与右值。
左值与右值
例:int a = 114 + 514;
这条语句在内存的栈上开辟了一个4字节的空间,a
有一个属于他的地址&a
。而114 + 514
是一个表达式字面量,他也有值,但是我们无法用一个地址获得它(它可能位于内存,也可能位于寄存器)。a
位于等号左侧,属于左值;114 + 514
位于等号右侧,属于右值。
百科中的定义:
- L-value中的L指的是Location,表示可寻址。a value (computer science)that has an address.
- R-value中的R指的是Read,表示可读。in computer science, a value that does not have an address in a computer language.
也就是说,区分左值还是右值的依据是:是否可以根据某个显示声明的变量获取到他的内存地址。有名字,可以取地址的就是左值,反之是右值(将亡值或纯右值)。
左值有地址,所以一定在内存中;右值则可能在寄存器中。
左值引用和右值引用
C++的引用就是一个值别名,他用于绑定一个值且不占空间。
- 对一个左值进行引用的类型叫左值引用
- 对一个右值进行引用的类型叫右值引用
左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
引用类型还可以被const
修饰,被const
修饰的引用对绑定的值是只读的。根据引用类型左右值,和是否被const
修饰,将引用类型分为四种(ra
):
- 常左值引用:
const T& ra = a
- 非常左值引用:
T& ra = a
- 常右值引用:
const T&a = 1
- 非常右值引用:
T&& a = 1
其中非常左值引用和非常右值引用可以对绑定的对象修改,而常左值引用和常右值引用对绑定的对象只读。
常左值引用是万能引用,除了可以绑定左值和右值,可以和任意上述四种引用类型绑定,提供只读操作:
- 绑定左值
常量左值绑定:const int a = 1;
const int& ra = a;
非常量左值绑定:int a = 1;
const int& ra = a;
绑定右值
const int& ra = 1 + 1;
绑定常左值引用
int b = 1;
const int& rb = b;
const int& ra = rb;绑定非常左值引用
int b = 1;
int& rb = b;
const int& ra = rb;绑定常右值引用
const int& a = 1 + 1;
const int& ra = a;绑定非常右值引用
int&& a = 1 + 1;
const int& ra = a;
非常左值引用只能绑定非常左值引用,绑定右值则会失败:int& a = 1 + 1; //编译错误
回到之前的问题:friend std::ostream& operator << (std::ostream &out, matrix &m)
其中matrix& m
参数是非常左值引用,当执行cout << m1 << endl
时,m1
是一个变量,属于左值,能够被非常左值引用绑定。而执行cout << m1 + m2 << endl
的时候,m1 + m2
是一个表达式,属于右值,而非常左值引用无法绑定右值,所以编译器会找不到参数列表匹配的函数,编译出错。
用关键字T&& a
表示右值引用,右值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()
将左值强制转换为右值,例如:int a;
int &&r1 = a; # 右值引用r1直接绑定左值a,编译失败
int &&r2 = std::move(a); # 强行把左值a转化成右值,再绑定到右值引用r2,编译通过
下面来说一下为什么要右值引用,右值引用在你需要使用寄存器中的值的时候可以进行右值引用。寄存器的刷新速度很快,没有右值引用的话就需要将寄存器中的值拷贝到内存中,在进行使用,这是很浪费时间的。
int getdata(int &&num) |
如上int getdata(int &&num)
就是对右值进行引用。 getdata(a + 1)
中a + 1
是右值在寄存器中,我们是不可以直接对他进行操作的,如果要操作得将其拷贝到内存中,如果是一个非常大的数据这种拷贝就会很占用内存,如果直接用右值引用就可以直接对其进行操作。从而节约内存。将右值转化为左值 直接新建变量然后赋值就可以了
右值和左值可以相互转化:int b = a + 1; //将 a + 1这个右值转变为左值了
move(a) //将a这个左值转变为了右值