C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度。为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一下C++类型系统的冰山一角,如果加深了对C++类型系统的理解,想必大家也能够更好的应用由C++11带给我们的新"利器"。

1.左值与右值

左值(lvalue)和右值(rvalue)是C++类型系统之中的基础概念,我们不需要了解这些基础概念,同样也能写出代码。但是如果没有弄清左右值的概念,对于许多C++高级特性的探索会一叶障目,所以笔者尝试总结一下自己对于左值与右值的理解。

在C++11之前的版本,基本沿用了C语言之中对于左值与右值的定义,说起来也很简单:“在C++之中的变量只有左值与右值两种:其中凡是可以取地址的变量就是左值,而没有名字的临时变量,字面量就是右值”。 正是因为这两种变量分别位于=的左右两侧,所以被命名为左值与右值。下面,举个栗子:

int x;
int y; x = 1;
y = 2;
x = y;
y = x; // 以下代码有误
3 = x;
x + y = 4;

通过上述的代码我们可以快速的理解,显然x,y作为变量可以存在=的左侧,而称之为左值,而3,x + y作为字面量或中间结果,没有办法取得地址,则称之为右值。 这里笔者也给一个简单判定的左右值的方式:

判断能否取值的地址,能取地址的就是左值。

2.将亡值

其实上一节对于左值右值的描述,在我们编写绝大多数代码的场景下并没有什么影响。而在C++11扩展了右值的的概念,将右值分为了纯右值(pure rvalue)与将亡值(eXpiring Value)。纯右值的概念等同于我们之前所理解的右值,指的是临时变量或字面量值;而将亡值是C++11新引入的概念,它依托于右值。

在C++之中,使用左值去初始化对象或为对象赋值时,会调用拷贝构造函数或赋值构造函数。而使用一个右值来初始化或赋值时,会调用移动构造函数或移动赋值运算符来移动资源,从而避免拷贝,提高效率。 而将亡值可以理解为通过移动构造其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,来延长变量值的生命期。而实际上该右值会马上被销毁,所以称之为:将亡值。

上述概念阐述的略微抽象,我们来看下面这段代码:

这是我们简单定义的Time类,在类中我们定义了拷贝构造函数和移动构造函数:

class Time {

public:
int* hour;
int* minute;
int* second; Time(int h, int m, int s) {
hour = new int(h);
minute = new int(m);
second = new int(s);
} Time(const Time& t) {
cout << "copy" << endl;
hour = new int(*t.hour);
minute = new int(*t.minute);
second = new int(*t.second);
} Time(Time&& t) noexcept:hour(t.hour),minute(t.minute),second(t.second) {
t.hour = nullptr;
t.minute = nullptr;
t.second = nullptr;
cout << "move" << endl;
} ~Time() {
cout << "call ~Time()" << endl;
delete hour;
delete minute;
delete second;
} };

接下来我们执行下面的代码:

int main()
{
Time test(10,25,12);
Time test2(test);
return 0;
}

执行结果:

  copy
call ~Time()
call ~Time()

由上述代码我们看到test2对象调用了拷贝构造函数来构造了新的对象,这个过程显然是更占用内存的。而接下来,我们尝试利用move函数将test强行转化为将亡值,来避免内存重新分配的过程。但是之后我们也无法再访问test对象的内容了,因为都在移动构造函数之中置为了空指针。

int main()
{
Time test(10,25,12);
Time test2(move(test));
return 0;
}

执行结果:

     move
call ~Time()
call ~Time()

通过这样的方式来减少不必要的内存操作。但是之后我们也无法再访问test对象的内容了,因为都在移动构造函数之中置为了空指针。将亡值通过移动构造函数”借尸还魂“,通过test2变量延续了自己的生命周期。

3.左值的一些"坑"

虽然笔者给出了左右值分辨的一些基本标准,但是还是有下面一些很让人迷惑的场景:

  • 条件表达式返回左值
true ? i : i;
  • ++
i++ // 左值
++i // 右值
  • []数组取值返回左值
i[10]
  • 指针取值操作符返回左值
*i
  • 字符串字面量返回左值
“hello world”

这是一些表示左值的特殊情况,这里笔者也不展开一一赘述了,希望大家可以简单的进行记忆。当然,笔者从来不去记一些太琐碎的问题,因为太他喵难记了,所以在C++11之中,可以标准库中添加的模板类is_lvalue_reference来判断表达式是否为左值,is_rvalue_reference来判断是否为右值。

  cout << is_lvalue_reference<decltype(i[10])>::value << endl;
cout << is_rvalue_reference<decltype(i[10])>::value << endl;

返回1则为真,0为假。

4.小结

这只是我们对C++类型系统的第一篇探讨,后续笔者还会继续深入的探讨有关C++11新特性之中与类型系统相关的内容,欢迎大家多多讨论,指教。

C++雾中风景10:聊聊左值,纯右值与将亡值的更多相关文章

  1. 话说C++中的左值、纯右值、将亡值

    写在前面 C++中有“左值”.“右值”的概念,C++11以后,又有了“左值”.“纯右值”.“将亡值”的概念.关于这些概念,许多资料上都有介绍,本文在拾人牙慧的基础上又加入了一些自己的一些理解,同时提出 ...

  2. c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用

    为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...

  3. 【原创】C++11:左值和右值(深度分析)

    ——原创,引用请附带博客地址 2019-12-06 23:42:18 这篇文章分析的还是不行,先暂时放在这以后再更新. 本篇比较长,需要耐心阅读 以一个实际问题开始分析 class Sub{} Sub ...

  4. C++中的左值和右值

    左值和右值的定义 在C++中,能够放到赋值操作符=左边的是左值,能够放到赋值操作符右边的是右值.有些变量既能够当左值又能够当右值.进一步来讲,左值为Lvalue,事实上L代表Location,表示在内 ...

  5. C++11 左值、右值、右值引用详解

    C++11 左值.右值.右值引用详解 左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值. 在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没 ...

  6. C++11 左值、右值、右值引用

    左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值.在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没有名字的就是右值(将亡值或纯右值).举个 ...

  7. [转]C++11 左值、右值、右值引用详解

    https://blog.csdn.net/hyman_yx/article/details/52044632 左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡 ...

  8. c++11の的左值、右值以及move,foward

    左值和右值的定义 在C++中,可以放到赋值操作符=左边的是左值,可以放到赋值操作符右边的是右值.有些变量既可以当左值又可以当右值.进一步来讲,左值为Lvalue,其实L代表Location,表示在内存 ...

  9. C++中 左值和右值的区别

    总结: C++11中所有的值属于左值,右值两者之一. 左值引用:指的是可以放在赋值表达式左边的事物——在堆上或者栈上分配的命名对象或者其他对象成员——有明确的内存地址. 对左值的const引用创建临时 ...

随机推荐

  1. bzoj1485: [HNOI2009]有趣的数列(Catalan数)

    一眼卡特兰数...写完才发现不对劲,样例怎么输出$0$...原来模数不一定是质数= =... 第一次见到模数不是质数的求组合数方法$(n,m\leq 10^7)$,记录一下... 先对于$1$~$n$ ...

  2. 解题:国家集训队 Middle

    题面 求中位数的套路:二分,大于等于的设为1,小于的设为-1 于是可以从小到大排序后依次加入可持久化线段树,这样每次只会变化一个位置 那左右端点是区间怎么办? 先把中间的算上,然后维护每个区间左右两侧 ...

  3. CF679E Bear and Bad Powers of 42

    一段时间不写线段树标记,有些生疏了 codeforces 679e Bear and Bad Powers of 42 - CHADLZX - 博客园 关键点是:42的次幂,在long long范围内 ...

  4. P3089 [USACO13NOV]POGO的牛Pogo-Cow

    P3089 [USACO13NOV]POGO的牛Pogo-Cow FJ给奶牛贝西的脚安装上了弹簧,使它可以在农场里快速地跳跃,但是它还没有学会如何降低速度. FJ觉得让贝西在一条直线的一维线路上进行练 ...

  5. Hadoop生态圈-Azkaban实现hive脚本执行

    Hadoop生态圈-Azkaban实现hive脚本执行 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客中在HDFS分布式系统取的数据,而这个数据的是有之前我通过MapRed ...

  6. Hadoop上传文件时报错: could only be replicated to 0 nodes instead of minReplication (=1)....

    问题 上传文件到Hadoop异常,报错信息如下: org.apache.hadoop.ipc.RemoteException(java.io.IOException): File /home/inpu ...

  7. 启动MyEclipse8.5时未响应

    错误原因: MyEclipse在进行编译时被强行关闭,就会编译内容出错. 解决方法: 1. 换个工作空间. 2.    寻找到工作空间那,访问到H:\javaWork5\.metadata\.plug ...

  8. Java 存储和读取 oracle CLOB 类型字段的实用方法

    import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.Str ...

  9. js 正则之 检测素数

    相信很多人应该看过这篇文章,我第一次看到的时候是11年的样子,那时候学vbs的时候看过这个问题.原文<检查素数的正则表达式>,在文章里已经解释了他是怎么判断的,我就不啰嗦了.我们来说说 j ...

  10. [转载]嵌入式C语言中的Doxygen注释模板

    http://blog.csdn.net/willerency/article/details/7083953 嵌入式C语言开发中通常使用Doxygen进行文档的生成.Doxygen支持多种格式,非常 ...