左值(lvalue)和右值(rvalue)是 c/c++ 中一个比较晦涩基础的概念,不少写了很久c/c++的人甚至没有听过这个名字,但这个概念到了 c++11 后却变得十分重要,它们是理解 move/forward 等新语义的基础。

左值右值的定义

左值与右值这两概念是从 c 中传承而来的,在 c 中,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式).

int a;
int b; a = ;
b = ;
a = b;
b = a; // 以下写法不合法。
= a;
a+b = ;

在 c 语言中,通常来说有名字的变量就是左值(如上面例子中的 a, b),而由运算操作(加减乘除,函数调用返回值等)所产生的中间结果(没有名字)就是右值,如上的 3 + 4, a + b 等。我们暂且可以认为:左值就是在程序中能够寻值的东西,右值就是没法取到它的地址的东西(不完全准确),但如上概念到了 c++ 中,就变得稍有不同。具体来说,在 c++ 中,每一个表达式都会产生一个左值,或者右值,相应的,该表达式也就被称作“左值表达式", "右值表达式"。对于基本数据类型来说(primitive types),左值右值的概念和 c 没有太多不同,不同的地方在于自定义的类型,而且这种不同比较容易让人混淆:

1) 对于基础类型,右值是不可被修改的(non-modifiable),也不可被 const, volatile 所修饰(cv-qualitification ignored)

2) 对于自定义的类型(user-defined types),右值却允许通过它的成员函数进行修改。

对于 1),这和 c 是一致的,2) 却是 C++ 中所独有, 因此,如果你看到 C++ 中如下的写法,千万不要惊讶:

class cs
{
public:
cs(int i): i_(i) { cout << "cs(" << i <<") constructor!" << endl; }
~cs() { cout << "cs destructor,i(" << i_ << ")" << endl; } cs& operator=(const cs& other)
{
i_ = other.i_;
cout << "cs operator=()" << endl;
return *this;
} int get_i() const { return i_; }
void change(int i) { i_ = i; } private:
int i_;
}; cs get_cs()
{
static int i = ;
return cs(i++);
} int main()
{
// 合法
(get_cs() = cs()).change();
get_cs() = cs();// operator=()
get_cs().change(); return ;
}

这个特性看起来多少有些奇怪,因为通常来说,自定义类型应该设计得和内置类型尽量一样(所谓 value type,value semantic),但允许成员函数改变右值这个特性却有意无意使得自定义类型特殊化了。对此,我们其实可以这样想,也许会好理解点:自定义类型允许有成员函数,而通过右值调用成员函数是被允许的,但成员函数有可能不是 const 类型,因此通过调用右值的成员函数,也就可能会修改了该右值,done!

左值引用,右值引用

关于右值,在 c++11 以前有一个十分值得关注的语言的特性:右值能被 const 类型的引用所指向,所以如下代码是合法的。

const cs& ref = get_cs();

而且准确地说,右值只能被 const 类型的 reference 所指向,非 const 的引用则是非法的:

// error
cs& ref = get_cs();

当一个右值被 const 引用指向时,它的生命周期就被延长了,这个用法我在前面一篇博客里讲到过它的相关应用。其中暗藏的逻辑其实就是:右值不能当成左值使用(但左值可以当成右值使用)。另外值得注意的是,对于前面提到的右值的两个特性:

1) 允许调用成员函数。

2) 只能被 const reference 指向。

它们导致了一些比较有意思的结果,比如:

void func(cs& c)
{
cout << "c:" << c.get_i() << endl;
} //error
func(get_cs()); //正确
func(get_cs() = get_cs());

其中: func(get_cs() = get_cs()); 能够被正常编译执行的原因就在于,cs 的成员函数 operator=() 返回的是 cs&!不允许非 const reference 引用 rvalue 并不是完美的,它事实上也引起了一些问题,比如说拷贝构造函数的接口不一致了,这是什么意思呢?

class cs
{
public:
cs& operator=(const cs& c);
}; // 另一种写法
class cs2
{
public:
cs2& operator=(cs2& c);
};

上面两种写法的不同之处就在于参数,一个是 const reference,一个是非 const。对于自定义类型的参数,通常来说,如果函数不需要修改传进来的参数,我们往往就按 const reference 的写法,但对于 copy constructor 来说,它经常是需要修改参数的值,比如 auto_ptr。

// 类似auto_ptr
class auto_ptr
{
public:
auto_ptr(auto_tr& p)
{
ptr_ = p.ptr_;
p.ptr_ = NULL;
} private:
void* ptr_;
};

所以,对于 auto_ptr 来说,它的 copy constructor 的参数类型是 non const reference。有些情况下,这种写法应该被鼓励,毕竟 non const reference 比 const reference 更能灵活应对各种情况,从而保持一致的接口类型,当然也有代价,参数的语义表达不准确了。除此更大的问题是如果拷贝构造函数写成这样子,却又对 rvalue 的使用带来了极大的不变,如前面所讲的例子,rvalue 不能被 non const reference 所引用,所以像 auto_ptr 的这样的类的 copy constructor 就不能接受 rvalue.

// 错误
auto_ptr p(get_ptr()); // operator=() 同理,错误。
auto_ptr p = get_ptr();

这也是 auto_ptr 很不好用的原因之一,为了解决这个问题,c++11 中引入了一种新的引用类型,该种引用是专门用来指向 rvalue 的,有了这种新类型,对 lvalue 和 rvalue 的引用就能明确区分开来了。因为有了这种新的类型,接着就引出了 c++11 中新的语义,move(), forward() 等,这儿先卖个关子,我们下次再讲。

[参考]

http://accu.org/index.php/journals/227

c++中的左值与右值的更多相关文章

  1. C++中的左值与右值(二)

    以前以为自己把左值和右值已经弄清楚了,果然发现自己还是太年轻了,下面的这些东西是自己通过在网上拾人牙慧,加上自己的理解写的. 1. 2. 怎么区分左值和右值:知乎大神@顾露的回答. 3. 我们不能直接 ...

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

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

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

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

  4. C++中让人忽视的左值和右值

    前言 为了了解C++11的新特性右值引用,不得不重新认识一下左右值.学习之初,最快的理解,莫过于望文生义了,右值那就是赋值号右边的值,左值就是赋值号左边的值.在中学的数学的学习中,我们理解的是,左值等 ...

  5. c++中的左值和右值的理解

    1.左值和右值的概念 C++中左值(lvalue)和右值(rvalue)是比较基础的概念,虽然平常几乎用不到,但C++11之后变得十分重要,它是理解 move/forward 等新语义的基础. 左值与 ...

  6. c++左值和右值

    c++编程中如果出现把一个函数的返回值.强行转化后的对象 作为函数的参数传进去时,编译器会报错的情况.这时候就该注意了,你需要把该函数的参数类型前加上const修饰. 原因在于c++的左值和右值有所区 ...

  7. c++ 左值 和 右值

    什么是lvalue, 什么是rvalue? lvalue: 具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables).函数.函数指针等 ...

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

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

  9. C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward

    这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...

随机推荐

  1. [记录]MySQL 查询无法导出到文件

    很多时候我们需要将数据导出到 xls文件, 然后交给数据分析师分析. 而这个查询数据+导出的动作,理应使用一个有只读权限的用户使用. 但查询某表时: select * from table ,此用户可 ...

  2. java pdf转word 高效不失真

    将java工程导成jar包 使用 bat 执行 jar 包. --------------------------------------------------------------------- ...

  3. 关于numpy.maximum函数的测试

    atr.py import numpy as np a = np.arange(9)print("a:",a)print(a[0:3])print(a[3:6])print(a[6 ...

  4. NOIP考点

    NOIP考点 基础算法 图 树 数论 数据结构 动态规划 搜索 其他算法 省选知识点汇总 图论 数据结构 字符串相关算法及数据结构 数学 计算几何 搜索 动态规划 其他算法 转自:巨佬的博客 加*号是 ...

  5. Desktop Central 的移动设备管理功能

    Desktop Central 的移动设备管理功能1.移动应用程序管理设备管理不会仅仅只是配置策略.检索资产信息和保护设备.应用程序管理与设置员工的移动设备一样重要.使用 Desktop Centre ...

  6. MySQL/Oracle索引的创建与使用

    MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度. 索引分单列索引和组合索引. 单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引 ...

  7. maven 不能使用 snapshot 的解决方式

    最近项目需要用到snapshot的包来进行构建过程,但是怎么都下不了构建的snapshot包.查询了相关资料,发现网上的资料不全,特总结下: 我使用的是nexus来作为代理中央库proxy. 检查步骤 ...

  8. java模板设计模式

    1.概述 模板设计模式定义:定义一个操作中的算法骨架,将步骤延迟到子类中. 模板设计模式是一种行为设计模式,一般是准备一个抽象类,将部分逻辑以具体方法或者具体的构造函数实现,然后声明一些抽象方法,这样 ...

  9. 环境搭建文档——Windows下的Git搭建详解

    Git是一个开源的分布式版本控制系统,可以有效.高速的处理从很小到非常大的项目版本管理.具体安装步骤如下: 第一步:先从官网下载最新版本的Git 官网地址:https://git-scm.com/do ...

  10. Core Expression

    https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm A Cron Expressions Cro ...