C++中的左值和右值

C++编程语言与C语言相比有很多不同之处,而且这些不同的地方有都体现着非常重要的作用。现在我们将会为大家详细介绍一下有关C++左值与右值之间的一些联系,希望能帮助大家对这一语言有一个深刻的认识。

1. C++左值与右值概念

变量和文字常量都有存储区,并且有相关的类型,区别在于变量是可寻址的;

对于每个变量,都有2个值与其相关联:

1>数据值,存储在某个内存地址中,也称右值(rvalue),右值是被读取的值(read value),文字常量和变量都可被用于右值。

2>地址值,即存储数据值的那块内存地址,也称左值(lvalue),文字常量不能被用作左值。

2 . C++左值与右值的相关问题

给表达式加上括号: ++a--

结果 ++(a--)

这个表达式是非法的,因为前增量操作要求一个可修改的左值,而 "a--" 不是左值(即右值)

3 . C++左值与右值中前增量和后增量的区别

早期的c语言教材,for循环语句通常写成:

for(int i=0;i<10;i++)

而现在多为:

for(int i=0;i<10;++i)

两者有区别吗?

a++ 即是返回 a的值,然后变量 a 加 1,返回需要产生一个临时变量类似于

  1. {   
  1. int temp = a;   
  1. aa=a+1;   
  1. return temp; //返回右值   
  1. }  
  1. {   
  1. aa=a+1;   
  1. return &a; //返回左值   
  1. }  

++a 则为:

显然,前增量不需要中间变量,效率更高。

C++左值与右值的含义与误区

术语 “L-Values” 和 “R-Values” 是很容易被搞混的,因为它们的历史渊源也是混淆。他们最初起源是编译器的设计者,从字面上来理解就是表达式左边的值和表达式右边的值。它们的含义一直在演化而名字却没变,现在已经“名”不副“实”了。虽然还是称为left-value 和right-value,但是他们的含义已经大大不同了。

C++ 03 标准上是这样写的: “每一个表达式要么是一个 lvalue,要么就是一个 rvalue。”

记住,lvalue和rvalue是针对表达式而言的。

lvalue 是指那些单一表达式结束之后依然存在的持久对象。例如: obj,*ptr, prt[index], ++x 都是 lvalue。

rvalue 是指那些表达式结束时(在分号处)就不复存在了的临时对象。例如:1729 , x + y , std::string("meow") , 和 x++ 都是 rvalue。

++x 和 x++ 的区别的语义上的区别: 当写 int i = 10 ; 时, i 是一个 lvalue,它实际代表一个内存里的地址,是持久的。 表达式 ++x 也是一个 lvalue,它修改了 x 的值,但还是代表原来那个持久对象。但是,表达式 i++ 却是一个 rvalue,它只是拷贝一份i的初值,再修改i的值,最后返回那份临时的拷贝,那份拷贝是临时对象。 ++i 和 i++ 都递增i,但 ++i 返回i本身,而 i++ 返回临时拷贝。这就是为什么 ++i 之所以是一个 lvalue,而 i++ 是一个 rvalue。

lvalue 与 rvalue 之分不在于表达式做了什么,而在于表达式代表了什么(持久对象或临时产物)。 判断一个表达式是不是 lvalue 的直接方法就是“能不能对表达式取址?”,如果能够,那就是一个 lvalue;如果不能,那就是一个 rvalue。

以下内容转载自http://www.cnblogs.com/catch/p/3500678.html

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

左值右值的定义

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

复制代码
int a;
int b;

a = 3;
b = 4;
a = b;
b = a;

//不合法。

3 = a;
a+b = 4;
复制代码

在 c 语言中,通常来说有名字的变量就是左值(如上面例子中的a, b),而由运算操作(加减乘除,函数调用返回值等)产生的中间结果(没有名字)就是右值,如上的3+4, a + b等。

我们暂且可以认为:左值就是在程序中能够寻值的东西,右值就是没法取到它的地址的东西(不完全准确),但如上概念到了c++中,就变得稍有不同。

在c++中,每一个表达式都会产生一个左值,或者右值,相应的,该表达式也就被称作“左值表达式”,“右值表达式”。对于内置的基本数据类型来说(build-in 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 = 0;
    return cs(i++);
}


int main()
{
    // 合法
    (get_cs() = cs(2)).change(323);
    get_cs() = cs(2);// operator=()
    get_cs().change(32);

    return 0;
}
复制代码

这个特性多少有些奇怪,通常来说,c++ 中的自定义类型是应该设计地尽量和内置类型一样才对的,但这个特性却偏偏违背了这个原则。

对于这个特性,我们其实可以这样想,也许会好理解点:自定义类型允许有成员函数,而通过右值调用成员函数是被允许的,但成员函数有可能不是 const 类型,因此通过调用右值的成员函数,也就可能会修改了该右值,done!

左值引用,右值引用

关于右值,有一个十分值得关注的语言的特性:右值能被 const 类型的引用所指向

const cs& ref = get_cs();

而且只能被const 类型的 reference 所指向:

//error 

cs& ref = get_cs();

当一个右值被 const reference 指向时,它的生命周期就被延长了,这个用法我在前面一篇博客里讲到过它的相关应用,点这

这里暗藏逻辑其实就是:右值不能直接转化成左值(但左值可以转化为右值).

 

关于前面提到的右值的两个特性:

 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 的引用就能明确区分开来了,而在之前,它们是一样的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值