走进C++11(二十) -- 左值、右值、左值引用、右值引用

图片

 

有段时间没有更新了,最近进了新部门,玩我的那个12.48寸的电子纸给耽误了。还好的是把电子纸的驱动给搞定了,对外开放REST的API。。。扯远了,今天聊一聊四个概念,文章总共分为四部分,分别对应一个概念。后边的文章会聊一聊移动语义与完美转发。

 

1. 左值

 

左值(lvalue,left value),顾名思义就是赋值符号左边的值。准确来说,左值是表达式(不一定是赋值表达式)后依然存在的持久对象。

 

可以将左值看作是一个关联了名称的内存位置,允许程序的其他部分来访问它。在这里,我们将 "名称" 解释为任何可用于访问内存位置的表达式。所以,如果 arr 是一个数组,那么 arr[1] 和 *(arr+1) 都将被视为相同内存位置的“名称”。

 

2. 右值

 

右值(rvalue,right value),右边的值,是指表达式结束后就不再存在的临时对象。

 

而C++11中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值、将亡值。这里说的右值指代纯右值。


纯右值(prvalue,pure rvalue),纯粹的右值,要么是纯粹的字面量,例如10, true;要么是求值结果相当于字面量或匿名临时对象,例如1+2。非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、Lambda表达式都属于纯右值。

 

小结:相对而言,右值则是一个临时值,它不能被程序的其他部分访问。为了说明这些概念,请看以下程序段:

 

int square(int a){        return a * a;}int main(){        int x = 0; // 1        x = 12; // 2        cout << x << endl; // 3        x = square(5); // 4        cout << x << endl; // 5        return 0;}

 

在该程序中,x 是一个左值,这是因为 x 代表一个内存位置,它可以被程序的其他部分访问,例如上面注释的第 2、3、4 和 5 行。

而表达式 square(5) 却是一个右值,因为它代表了一个由编译器创建的临时内存位置,以保存由函数返回的值。该内存位置仅被访问一次,也就是在第 4 行赋值语句的右侧。在此之后,它就会立即被删除,再也不能被访问了。

对于包含右值的内存位置来说,其本质就是:它虽然没有名称,但是可以从程序的其他部分访问到它。

 

3. 左值引用

 

左值引用的声明是通过在某个类型后放置一个符号&来进行的。前文代码中的int & y = x;便是一个左值引用。


需要注意的是,在定义左值引用时,=右边的要求是一个可修改的左值。因此下面几种左值引用都是错误的:

 

#include <stdio.h>
int main(){    const int x = 5;    int y = 1;    int z = 1;    int & tmp1 = x;     // ERROR:x不是一个可修改的左值    int & tmp2 = 5;     // ERROR:5是一个右值    int & tmp3 = y + z; // ERROR:y+z是一个右值    return 0;}

 

左值引用的用途: 

 

(1) 作为复杂名称变量的别名


我们可以写出类似如下的语句:

 
auto & whichList = theList[myHash(x, theList.size())];

 

可以看到,我们用简短的whichList代替了其原本复杂的名称,这能够简化我们的代码书写。


(2) 用于rangeFor循环


设想我们希望通过rangeFor循环使一个vector对象所有值都增加1,下面的rangeFor循环是做不到的、

for (auto x : arr)   // x仅相当于每个元素的拷贝    ++x;

 

但我们可以通过使用引用达到这一目的

for (auto & x : arr)    ++x;

 

(3) 避免复制大的对象


假定有一个findMax函数,它返回一个vector中最大的元素。若给定vector存储的是某些大的对象时,下述代码中的x拷贝返回的最大值到x的内存中:

auto x = finaMax(vector);

 

在大型的项目中这显然会增大程序的开销,这时我们可以通过引用来减小这类开销

auto & x = findMax(vector);

 

类似的,我们在处理函数返回值的时候也可以使用传引用返回。但是要注意,当返回的是类中私有属性时,传回的引用会导致外界能够对其修改。


(4) 参与函数中的参数传递


在C和C++的函数中,addSelf(int x)这类函数对直接传入的参数进行修改并不会改变原有参数的值。而有时我们希望能够实现类似swap(int a, int b)这类能够修改原参数的函数时,我们可以通过1.传入指针和2.传入引用实现。
swap函数的实现是一个很好的例子

#include <stdio.h>
void swap_non(int, int);    // 直接传入参数 void swap_p(int *, int *);  // 传入指针void swap_r(int &, int &);  // 传入引用
int main(){    int a = 1;    int b = 2;    printf("直接传入参数时:\n");    swap_non(a, b);    printf("a = %d, b = %d\n", a, b);    printf("传入指针时:\n");    swap_p(&a, &b);    printf("a = %d, b = %d\n", a, b);    printf("传入引用时:\n");    swap_r(a, b);    printf("a = %d, b = %d\n", a, b);    return 0;}
void swap_non(int a, int b){    int temp = a;    a = b;    b = temp;}
void swap_p(int * a, int * b){    int temp = *a;    *a = *b;    *b = temp;}
void swap_r(int & a, int & b){    int temp = a;    a = b;    b = temp;}

 

4. 右值引用

 

首先要提出一个新的概念: 将亡值(xvalue,expiring value),是C++11为了引入右值引用而提出的概念(因此在传统 C++中,纯右值和右值是同一个概念),也就是即将被销毁、却能够被移动的值。

 

C++11 引入了右值引用的概念,以表示一个本应没有名称的临时对象。右值引用的声明与左值引用类似,但是它使用的是 2 个 & 符号(&&),以下代码使用了右值引用打印了两次 5 的平方:

 

int && rRef = square(5);cout << rRef << endl;cout << rRef << endl;

 

有意思的是,声明一个右值引用,给一个临时内存位置分配一个名称,这使得程序的其他部分访问该内存位置成为了可能,并且可以将这个临时位置变成一个左值。

右值引用不能约束到左值上,所以,以下代码将无法编译:

 

int x = 0;int && rRefX = x;

 

再来看以下初始化语句:

 

int && rRef1 = square(5);

 

在初始化完成之后,这个包含值 square(5) 的内存位置有了一个名称,即 rRef1,所以 rRef1 本身变成了一个左值。这意味着后面的这个初始化语句将不会编译:

 

int && rRef2 = rRef1;

 

究其原因,就是右侧的 rRef1 不再是一个右值。综上所述,临时对象最多可以有一个左值引用指向它。如果函数有一个临时对象的左值引用,则可以确认,程序的其他部分都不能访问相同的对象。

 

右值引用会涉及到很多概念比如移动语义,完美转发,会在今后的文章中聊

 

引用和指针的区别

 

我们知道,指针是在内存中存放地址的一种变量,cpu能够直接通过而变量名访问唯一对应的内存单元,且每个内存单元的地址都是唯一的。


而变量名和引用,都可以看做内存的一个标签或是标识符,计算机通过是否符合标识符判断是否为目标内存,而一个内存可以有多个标识符

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值