c++中的左值与右值
左值(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++中的左值与右值的更多相关文章
- C++中的左值与右值(二)
以前以为自己把左值和右值已经弄清楚了,果然发现自己还是太年轻了,下面的这些东西是自己通过在网上拾人牙慧,加上自己的理解写的. 1. 2. 怎么区分左值和右值:知乎大神@顾露的回答. 3. 我们不能直接 ...
- C++中的左值和右值
左值和右值的定义 在C++中,能够放到赋值操作符=左边的是左值,能够放到赋值操作符右边的是右值.有些变量既能够当左值又能够当右值.进一步来讲,左值为Lvalue,事实上L代表Location,表示在内 ...
- C++中 左值和右值的区别
总结: C++11中所有的值属于左值,右值两者之一. 左值引用:指的是可以放在赋值表达式左边的事物——在堆上或者栈上分配的命名对象或者其他对象成员——有明确的内存地址. 对左值的const引用创建临时 ...
- C++中让人忽视的左值和右值
前言 为了了解C++11的新特性右值引用,不得不重新认识一下左右值.学习之初,最快的理解,莫过于望文生义了,右值那就是赋值号右边的值,左值就是赋值号左边的值.在中学的数学的学习中,我们理解的是,左值等 ...
- c++中的左值和右值的理解
1.左值和右值的概念 C++中左值(lvalue)和右值(rvalue)是比较基础的概念,虽然平常几乎用不到,但C++11之后变得十分重要,它是理解 move/forward 等新语义的基础. 左值与 ...
- c++左值和右值
c++编程中如果出现把一个函数的返回值.强行转化后的对象 作为函数的参数传进去时,编译器会报错的情况.这时候就该注意了,你需要把该函数的参数类型前加上const修饰. 原因在于c++的左值和右值有所区 ...
- c++ 左值 和 右值
什么是lvalue, 什么是rvalue? lvalue: 具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables).函数.函数指针等 ...
- c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用
为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...
- C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward
这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...
随机推荐
- 转载 linux基本操作
转载地址 https://segmentfault.com/a/1190000014840829 前言 只有光头才能变强 这个学期开了Linux的课程了,授课的老师也是比较负责任的一位.总的来说也算是 ...
- concurrent.futures性能阐述
python因为其全局解释器锁GIL而无法通过线程实现真正的平行计算.这个论断我们不展开,但是有个概念我们要说明,IO密集型 vs. 计算密集型. IO密集型:读取文件,读取网络套接字频繁. 计算密集 ...
- EmWin 字体相关函数
学习笔记:----------------------------------------------------------- 1:const GUI_FONT * GUI_GetFont(void ...
- Jenkins入门,介绍、安装
一.介绍 Jenkins: 三.安装 Jenkins: 第一种方式: 下载 windows 版本:进入 Jenkins 官网:https://jenkins.io/ 进入下 ...
- MySQL 连接出现 Authentication plugin 'caching_sha2_password' cannot be loaded
参考帖子: https://www.cnblogs.com/zhurong/p/9898675.html cmd 需要使用管理员权限打开
- 异常:Caused by: java.sql.SQLException: Field 'cust_id' doesn't have a default value
异常: 由Java.q.L.SqLExpExt引起:字段“CuSTyID”没有默认值 Caused by: java.sql.SQLException: Field 'cust_id' doesn't ...
- 关于python-flask中规范创建项目的几个关键py项目文件
1.config.py——配置文件 DEBUG = True DIALECT = 'mysql' DRIVER = 'mysqldb' USERNAME = 'root' PASSWORD = '' ...
- mysql ERROR 1451 (23000)
问题描述:报错如下:ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint提示有外键约束, ...
- 关于Socket.IO的知识点记录
最近因为项目的需要,开始学习nodejs,本着js的那点儿功底,nodejs学习起来还是挺快能上手的.随着深入学习,知道了express框架并那它写了一个小功能,作为一个php程序员哈,在expres ...
- index.php入口文件至根目录
登录|注册 咖啡如同生活的专栏 从不羡慕别人的优秀,因为相信自己也可以优秀. 闲下来时看看书,书本里的故事,总有你学到的人生. 目录视图 摘要视图 订阅 异步赠书:9月重磅 ...