C++ 左值引用和右值引用

左值引用

引用是什么

引用为对象起了另外一个名字, 引用类型引用另外一种类型,通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名;
例子:

int ival = 1024;
int &refval = ival; //定义了ival的引用,refval指向ival,是ival的另一个名字,所以说,引用即是别名
int &refval2; // 报错;

定义引用类型要注意的点

引用类型必须被初始化
一般在初始化变量时,初始值会被拷贝到新建的对象中去,然而定义引用的时候,程序会把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用,一旦初始化完成,引用将和它的初始值对象一直绑定在一起,所以无法令引用重新绑定到另外一个对象,所以引用必须初始化;这也就是为什么上面示例代码中第三行会报错的原因,正是因为其没有进行初始化;

不能定义引用的引用
由上面的示例代码的第二行我们可以明白,引用即是别名,引用并不是对象,它只是为一个已经存在的对象所起的另一个名字,也就是说应用类型并不开辟任何新的内存空间。
当你定义了一个引用之后,你对该引用的所有操作,其实都是在与之绑定的对象上进行的,正因为引用本身不是一个对象,所以不能定义引用的引用。

所有的引用类型都要和与之绑定的对象严格匹配
在这里有两种例外情况,我们将在下文进行阐述。

  • 第一种例外情况
    在初始常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可,尤其,允许为一个常量引用绑定非常量的对象,字面值,甚至是个表达式。

    const的引用

    可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用,很明显,与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象,因为其所绑定的对象是const的(如果对const限定符不了解,可以观看我的博客c++中的const限定符详解
    例子:
int i = 42;
const int &r1 = i; // 定义一个常量引用r1,使用常量引用r1绑定非常量对象i,在这里常量引用r1不能被用作修改其所绑定对象i的值
const int &r2 = 42; // 正确,因为r2是一个常量引用,所以其可以绑定字面值
const int &r3 = r1 * 2; // 正确,因为r3是常量引用,所以其可以用来绑定一个表达式的结果;
int &r4 = r1 * 2;// 报错,错误1 :不允许让一个非常量引用r4指向一个常量引用,假如这种行为是成立的,那么也就意味着可以通过引用r4来修改r1的值,这显然是矛盾的。 错误2 :我们上文提到,不能定义引用的引用,这里的r1和r4均是引用。

总结来说就是***不能让非常量引用指向一个常量对象,但允许常量引用绑定非常量的对象,字面值,表达式***
值得注意的是:常量引用仅对引用可参与的操作做出了限制,对于引用的对象本身是不是一个常量未做限制,其引用的对象也可能是个非常量值,允许通过其他的途径来改变它的值;
允许在一条语句中定义多个引用,其中每个引用标识符都必须用&开头

非常量引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起

我们通过一个简单的例子来理解为什么

例子:

double dval = 3.14;
const int &ri = dval;

此处的ri被定义为引用int类型的对象,所以对ri的操作应该是整数运算,但是dval却是一个双精度浮点数,因此为了确保让ri绑定一个整数,编译器将上述代码变成了如下形式:

const int temp = dval;// 由双精度浮点数生成一个临时的整形常量
const int &ri = temp;// 让ri绑定这个常量

在这种情况下,ri绑定了一个临时量对象(当编译器需要一个空间来暂存表达式的求值结果时临时创建的未命名对象,c++程序员们常常把临时量对象简称为临时量)

接下来我们进入这个例子真正的主题,假如当ri不是常量的时候,也就是说我们违反了引用类型和与之绑定的类型对象必须严格匹配的原则,此时会产生什么样的后果呢?
如果ri不是常量,那么就允许对ri赋值,这样就会改变ri所引用对象的值,注意:此时绑定的对象是一个临时量而非dval,程序员既然想让ri引用dval,就肯定是想通过ri改变dval的值,否则干什么要给ri赋值呢?如此看来,既然大家基本上不会想着把引用绑定到临时量上,那么c++语言也就把这种行为归位非法了即除了特殊情况所有的引用类型都要和与之绑定的对象严格匹配,而且常量引用只能绑定在对象上。

int &refval4 = 10;// 报错,引用类型的初始值必须是一个对象而不能是字面值
double i = 3.14;
int &refval5 = i; //报错,引用类型必须和与之绑定的类型严格匹配,这里引用的类型是int,而与之绑定的对象类型是double;

右值引用

在阐明右值引用之前我们需要先明确一下什么是左值,什么是右值:
左值和右值是表达式的属性,一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。
所谓右值引用就是必须绑定到右值的引用,我们通过&&而不是&来获得右值引用,右值引用有一个重要的性质:只能绑定到一个将要销毁的对象,因此,我们可以自由地将一个右值引用的资源移动到另一个对象中。
我们不能将左值引用绑定到要求转换的表达式,字面常量或是返回右值的表达式上,右值引用有着完全相反的绑定特性:我们可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上,值得特别注意的是,因为变量可以看作只有一个运算对象而没有运算符的表达式,类似其他的任何表达式,变量表达式也有左值右值属性,变量表达式都是左值,,带来的结果就是我们不能把一个右值引用绑定到一个右值引用类型的变量上
例子:

int &&rr1 = 42; // 正确
int &&rr2 = rr1; // 错误;rr1是一个右值引用类型的变量

返回左值引用的函数,连同赋值,下标,解引用和前置递增前置递减运算符都是返回左值的表达式的例子,我们可以把一个左值引用绑定到这类表达式的结果上。
返回非引用类型的函数,连同算数,关系,位运算以及后置递增后置递减运算符都生成右值。我们可以把一个const的左值引用或者右值引用绑定到这类表达式上。

左值和右值的区别

左值有持久的状态,而右值要么是字面值常量,要么是在表达式求值过程中创建的临时对象。
由于右值引用只能绑定到临时对象,我们得知:

  • 所引用的对象将要被销毁
  • 该对象没有其他的用户
    这两个特性意味着:使用右值引用的代码可以自由的接管所引用对象的资源;

将左值转换为对应的右值引用类型

通过调用名为move(头文件utility)的新标准库函数来获得绑定到左值上的右值引用。
move告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。调用move就意味着承诺:除了对该左值赋值或销毁它外,我们将不在使用它。我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值,我们不能对移后源对象的值做任何假设。
我们直接调用std::move而不是提供using声明后使用move;

int &&rr3 = std::move(rr1);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值