左值和左值引用、右值和右值引用
左值和右值
左值(L-value):能用“取地址&”运算符获得对象的内存地址,表达式结束后依然存在的持久化对象。左值可以出现在等号左边也能够出现在等号右边。
右值(R-value):不能用“取地址&”运算符获得对象的内存地址,表达式结束后就不再存在的临时对象。只能出现在等号右边。
当一个对象被用作右值的时候,用的是对象的值(内容);而被用作左值的时候,用的是对象的身份(在内存中的位置)。总之:左值看地址,右值看内容。所有的具名变量或者对象都是左值,而右值不具名,如常见的右值有非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等。右值要么是字面常量,要么是在表达式求值过程中创建的对象。特例:因为可以用&取得字符串字面值常量的地址,虽然它不能被赋值,但它是一个左值。
为什么右值不能用&取地址呢?
对于临时对象,它可以存储于寄存器中,所以没办法用“取地址&”运算符;
对于(非字符串)常量,它可能被编码到机器指令的“立即数”中,所以没办法用“取地址&”运算符。
左值引用和右值引用
使用引用的目的就在于减少不必要的拷贝。
对左值的引用,就是给左值取别名。其基本语法如下:
变量名实质上是一段连续存储空间的别名,是一个标号(门牌号),通过变量的名字可以使用存储空间。可以通过引用为一个内存空间取多个别名。
普通引用在声明时必须用其它的变量进行初始化,引用作为函数参数声明时不进行初始化。
引用在C++中的内部实现是一个常指针。Type& name <=> Type* const name。所以一旦一个引用被初始化之后,无法再更改它所指向的对象。C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用名所占用的空间大小与指针相同。对指针的引用:
const引用:const Type& name <=>const Type* const name,当使用常量(字面量)这类右值对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名。初始化后,将生成一个只读变量。只有常引用才可以用右值表达式初始化,这一点很重要,因为如果不加const,那么这个临时的对象是无法进行传递给左值引用的,比如
因为MyString("hello")是一个临时对象,即右值,所以MyString实现的拷贝构造函数参数不加const就会报错。
对右值的引用,就是给右值取别名。其基本语法如下:
在C++中创建对象是一个费时、废空间的一个操作,有些固然必不可少,但还有一些对象却在我们不知道的情况下创建了。
以值的方式给函数传参:给函数传参有两种方式----按值传递和按引用传递。按值传递时,首先将需要传给函数的参数,调用拷贝构造函数创建一个副本,所有在函数里的操作都是针对这个副本的,也正是因为这个原因,在函数体里对该副本进行任何操作,都不会影响原参数。
类型转换生成的临时对象。
函数返回一个对象:当函数需要返回一个对象,他会在栈中创建一个临时对象或也叫匿名对象(如果是类对象,则会调用拷贝构造函数),存储函数的返回值。这个临时对象在表达式 sum = Double(tm) 结束后就自动销毁了,这个临时对象就是右值。
引入右值引用的目的:右值引用是C++11中新增加的一个很重要的特性,它主要用来解决以下问题。
1. 函数返回临时对象造成不必要的拷贝操作:通过使用右值引用,右值不会在表达式结束之后就销毁了,而是会被“续命”,的生命周期将会通过右值引用得以延续,和变量的声明周期一样长。
通过右值引用,比之前少了一次拷贝构造和一次析构,原因在于右值引用绑定了右值,让临时右值的生命周期延长了。我们可以利用这个特点做一些性能优化,即避免临时对象的拷贝构造和析构。
2. 通过右值引用传递临时参数:使用字面值(如1、3.15f、true),或者表达式等临时变量作为函数实参传递时,按左值引用传递参数会被编译器阻止。而进行值传递时,将产生一个和参数同等大小的副本。C++11提供了右值引用传递参数,不申请局部变量,也不会产生参数副本。