码迷,mamicode.com
首页 > 编程语言 > 详细

c++中的左值与右值

时间:2014-09-14 23:35:27      阅读:227      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   color   io   os   使用   ar   

++(a++)
a++相当于
int a;
{
int temp=a;
a++;
teturn temp;
}
所以我们可以将++(a++)看成++temp;而temp
显然是一个右值,所以不能用啊~~

L-value中的L指的是Location,表示可寻址。The "l" in lvalue can be though of as location
R-value中的R指的是Read,表示可读。The "r" in rvalue can be thought of as "read" value. 

 

 

左值右值

左值(lvalue)和右值(rvalue) 是编程中两个非常基本的概念,但是也非常容易让人误解,看了很多文章,自我感觉真正将这个问题讲的很透彻的文章还没有看见,所以自告奋勇来尝试一下。如果 左值右值的概念不是非常清楚的话,它们迟早会像拦路虎一样跳出来,让你烦心不已,就像玩电脑游戏的时候每隔一段时间总有那么几个地雷考验你的耐性,如果一 次把所有地雷扫尽就好了。:)

左值(lvalue)和右值(rvalue)最先来源于编译理论(感谢南大小百合的programs)。在C语言中表示位于赋值运算符两侧的两个值,左边的就叫左值,右边的就叫右值。比如:

int ii = 5;//ii是左值,5是右值

int jj = ii;//jj是左值,ii是右值

上面表明,左值肯定可以作为右值使用,但反之则不然。左值和右值的最早区别就在于能否改变。左值是可以变的,右值不能变。【注1】

注1:这一点在C++中已经猪羊变色,不再成立。拱猪游戏还是挺好玩的,我还真抓过好几次全红心,不过真的好险。:)

在很多文章中提到,在C++中,左值更多的指的是可以定位,即有地址的值,而右值没有地址。【注2】

注2:这一点仍然不准确,我在程序中生成一个临时右值std::vector(),你能够说它没有地址吗?难道它是没有肉体的鬼魂或幽灵?它是有地址的,而且它也是绝对的右值。

在现代C++中,现在左值和右值基本上已经失去它们原本所具有的意义,对于左值表达式,通过具体名字和引用(pointer or reference)来指定一个对象。非左值就是右值。我来下一个定义:

左值表示程序中必须有一个特定的名字引用到这个值。

右值表示程序中没有一个特定的名字引用到这个值。

跟它们是否可以改变,是否在栈或堆(stack or heap)中有地址毫无关系。

1.左值

在下面的代码中:

int ii = 5;

int const jj = ii;

int a[5];

a[0] = 100;

*(a+3) = 200;

int const& max( int const& a, int const& b ) //call by reference

{

      return a > b ? a : b;

}

int& fun(int& a) //call by reference

{

      a += 5;

   return a;

}

ii,jj,a[0],*(a+3),还有函数max的返回值比如max(ii, jj),【注3】函数fun的返回值fun(ii)都是左值。,它们都是有特定的引用名字的值。ii,jj,a[0],*(a+3),max(ii, jj),fun(ii)分别就是它们的名字。

注3:在这里有一个不太容易分清楚的盲点。那就是有人会问max(8, 9)到达是左值还是右值,C++标准规定常量引用(reference to const)可以引用到右值,所以max(8, 9)似乎应该是右值,不过不管它是左值,还是右值,我们都不能试图去改变它。为了与前面的概念一致,我认为它是左值,不可改变的常量左值。

左值有不能改变的,即被const所修饰的左值,比如上面的jj,max(ii, jj)都是被常量(const)魔咒所困住的左值。

没有被const困住的左值当然是可以改变的,比如下面的代码都是成立的:

ii = 600;

a[0] = 700;

fun(ii) = 800; //OK!

我们的眼睛没有问题,fun(ii) = 800;完全正确,因为它是可以改变的左值。所以我们看STL的源码,就会理解std::vector中的重载operator[]运算符的返回值为什么要写成引用,因为operator[]必须返回左值。

 

2.右值

没有特定名字的值是右值。先看下面的代码:

std::list();

std::string(“It is a rvalue!”);

int fun1() //call by value

{

      …

}

int* fun2() //call by reference

{

      …

}

其中std::list(),std::string(“It is a rvalue!”),函数fun1的返回值fun1(),函数fun2的返回值fun2()都是右值,它们的值都没有特定的名字去引用。也许有人会奇怪,fun2()也是右值?最前面的max(a,b)不是左值吗?

请看清楚,函数fun2的返回值是pointer,pointer也是call by value,而函数max的返回值是reference,reference是call by reference。所以说C++中引入reference不仅仅是为了方便,它也是一种必须。【注4】

注4:Scott Meyer写的《More Effective C++》的条款1专门讲了pointer和reference的区别,写的很好,辨别的非常清楚。

fun2()是右值,但 *fun2()却是左值,就跟经常看到的*p一样,所以看C++库代码的时候,会发现重载operator*的函数返回值是reference。

当然我还遗漏了一种右值,那就是字面上的(literal)值,比如5,8.23,’a’等等理所当然的都是右值。

右值最初出现的时候,一个最大的特征就是不可改变。但就跟我们的道德标准一样,时代不同了,标准也变化了,以前的三纲五常早已经被扔到历史的垃圾堆里面了。

C++中有可以改变的右值,而且这个特性还非常有用。那就是用户自定义的类(class)的构造函数生成的临时对象。比如:

std::vector(9),std::deque(),……都是可以改变的右值。在Herb Sutter的《More Exceptional C++》中的条款7的page51页有这样几行代码:

// Example 7-2(b): The right way to shrink-to-fit a vector.

vector<Customer> c( 10000 );

// ...now c.capacity() >= 10000...

// erase all but the first 10 elements

c.erase( c.begin()+10, c.end() );

// the following line does shrink c‘s

// internal buffer to fit (or close)

vector<Customer>( c ).swap( c );

// ...now c.capacity() == c.size(), or

// perhaps a little more than c.size()

认真看几遍,你会发现但vector的大小增大到一定程度,你又用不着这么多空间的时候,你会想办法把它收缩到最合适的大小,但利用别的办法比如调用成员函数reserve()都无法办到,这个时候就必须利用右值可以改变这个性质了。

vector<Customer>( c ).swap( c );这行代码就是点睛之处。

首先使用复制构造函数生成临时右值vector<Customer>( c ),这个右值正好是合适大小,然后和c交换【注5】,c就变成合适大小了,最后在整个表达式结束的时候,这个临时右值析构归还内存空间。真是绅士一般的优雅!

注5:这个时候这个临时右值就发生了改变。

如果还不理解,可以看看书,或者直接看库的源代码。

至于为什么会这样?我思考了一下,我想是这样的,我们看类(class)的数据布置结构,会发现它的每一个数据成员都是有名字的,我想编译器在编译的过程中,都会生成一个外部不所知的对这个临时对象右值的名字引用,但需要改变这个临时对象的时候,这个名字就用上了。比如:

class Point

{

public: //纯粹为了方便,我把数据成员公开,现实中尽量不要这样用

      int x, y ,z;

      ……//其他各种成员函数

};

我们现在就可以改变右值,用到了匿名的引用名字。

Point().x = 6;//改变了右值

Point().y = 6;//同意改变了右值,不过注意,这个右值跟上面的不是同一个。

总结

左值和右值的真正区别我想就是这些了,左值表示有特定的名字引用,而右值没有特定的名字引用。当然我仍然会有疏忽,希望大家能够提醒我,指正我的不足。

前两天看Herb Sutter从邮件中寄来的新文章(我订阅他的新文章邮件通知),一篇是讲Tuple数据结构的,没有什么新意,以前好像看过,还有一篇名字是:(Mostly)Private,地址为http://www.cuj.com/documents/s=8273/cujcexp2107sutter/ 内容本身并不深,但看完文章,发现随处可见C++的波诡云谲,又会对什么叫袖里乾坤,滴水藏海多一份感性认识。

在下一篇文章我想从不同于一般的角度,从自己的经历谈谈在校毕业生在IT行业怎样找工作,我想会让所有读者都有一些思考,不仅仅是求职者。题目我已经想好了,就叫《扮虎吃猪》,不过现在我有一些别的事情要忙,所以可能会让大家等几天。

转载请注明来源,谢谢!

吴桐写于2003.6.20

最近修改2003.6.21

 

 

 

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

什么是左值右值?

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

int a;
int b;

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

//不合法。

3 = a;
a+b = 4;

通常来说,有名字的变量就是左值(如上面例子中的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的,有了这种新类型,在c++11中,lvalue 和rvalue的引用类型从此区分了开来,而在之前,它们是一样的。

因为有了这种新的类型,接着就引出了c++11中新的语义,move(), forward()等,这儿先卖个关子,我们下次再讲。

 

[参考]

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

c++中的左值与右值

标签:des   style   blog   http   color   io   os   使用   ar   

原文地址:http://www.cnblogs.com/plzdaye/p/3971851.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!