zoukankan      html  css  js  c++  java
  • 左值和右值(小小翻译)

    原文地址:http://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c

    左值和右值这两个术语在c/c++编程中经常出现,但每当你运行程序是,你对他们并不是很了解,只有在编译器报错的时候我们才回去深究这些东西。
    先看看两个例子:

    int foo() 
    {
        return 2;
    }
    
    int main()
    {
        foo() = 2;
        return 0;
    }

    编译器报错:
    In function 'int main()':
    [Error] lvalue required as left operand of assignment
    意思是表达式中的=号左边应该有个运算符(或者变量)

    int& foo()
    {
        return 2;
    }
    int main()
    {
        foo() = 2;
        return 0;
    }

    这次错误换了
    In function 'int& foo()':
    [Error] invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'
     
    是的,这个代码是有点反常,错误信息提到左值(lvalue)和右值(rvalue),那么,左值和右值在C和C++中到底指的是什么呢?这就是我们接下来该探讨的。

    简单的定义
         本节介绍了左值和右值的有意简化定义。本文的其余部分将详细阐述这一定义。
        一个左值(定位值)代表一个对象,它占用一个可识别的内存位置(即有一个地址)。
        右值没有定义,只是说每一个表达式是一个左值或右值。因此,从上述的左值定义来看,右值是一个表达式,这个表达式不能表示一个占用可识别内存位置的对象。
    基本的例子
        上面定义的条款,可能会出现模糊,所以现在利用一些简单的例子来解释这一点是很重要的。
        假设我们有一个整型变量定义和赋值:   

            int var;
            var = 4;

        赋值操作期望它的左操作数是一个左值,var是一个左值,因为它是一个对象,具有可识别的内存位置。
        我们在看一个无效的例子:

            4 = var;       // ERROR!
            (var + 1) = 4; // ERROR!

        无论是常数4还是表达式var+1都不是左值(它们其实是右值)。它们不是左神是因为两者都是表达式的临时结果,不具有可识别的内存位置(即在运行过程中它们只可以存在于某些临时寄存器中)。因此,对他们进行赋值没有任何意义,也无处可赋值。
        所以,现在应该对第一个代码段的错误提示很清楚了。 foo返回一个临时的值,它是一个右值。试图给它赋值是一个错误,因此当看到foo() = 2时;编译器就会提示,它希望看到的赋值语句的左手侧是一个左值。
        然而,并非所有函数调用的结果都是无效的。例如,C++的引用就可以实现这一点:

            int globalvar = 20;
    
            int& foo()
            {
                    return globalvar;
            }
    
            int main()
            {
                    foo() = 10;
                    return 0;
            }

        这里foo返回一个引用,这个引用是一个左值,因此它可以被赋值。其实,C++从函数返回左值的能力,对于实现一些重载运算符是重要的。一个常见的例子就是在重载括号运算符[]中实现某种形式的查询访问类。

        std ::map:
            std::map<int, float> mymap;
            mymap[10] = 5.6;

        对mymap[10]进行赋值是正确的,因为std ::map::operator[]的非const重载返回一个可以赋值的引用。

    可修改的左值:
        最初,在C中定义左值时,它的字面意思是“这个值适用于赋值操作的左手侧”。可是后来,当ISO C中加了const关键字,这个定义就必须加以完善。

            const int a = 10; // 'a' is an lvalue
            a = 10;           // but it can't be assigned!

    这样的改进使我们必须认识到,不是所有的左值可以进行赋值操作。那些可以被赋值的称为可修改的左值。从形式上看,C99标准定义修改的左值如下:
            [...] an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.
            也就是说:可修改的左值不能具有数组类型、不完整的类型或带 const 特性的类型。 对于要成为可修改的左值的结构和联合,它们不得具有带 const 特性的任何成员。 标识符的名称表示存储位置,而变量的值是存储在该位置的值。
    左值和右值之间的转换:

    一般来说,对象的值进行语言结构操作时需要右值作为参数。例如,二进制加法运算符“+”有两个右值作为参数,并返回一个右值:

            int a = 1;     // a is an lvalue
            int b = 2;     // b is an lvalue
            int c = a + b; // + needs rvalues, so a and b are converted to rvalues
                                  // and an rvalue is returned

      正如我们前面看到的,a与b都是左值。因此,在第三行它们经历一个隐含的左值到右值的转换。所有的左值除了数组,函数或不完整的类型都可以被转换为右值。
          既然左值可以转换成为右值,那么右值可以转换成为左值,Of course not!根据左值的定义这将违反左值的本质。(右值可以直接赋值给左值。缺乏隐式转换意味着右值不能在左值预计出现的地方使用。)
          这并不意味着左值不能由右值以直接的方式产生。例如,一元'*'(解引用)操作符接受一个右值参数,但会产生一个左值结果。考虑下面这个有效的代码:

            int arr[] = {1, 2};
            int* p = &arr[0];
            *(p + 1) = 10;   // OK: p + 1 is an rvalue, but *(p + 1) is an lvalue

      相反,一元取址操作符'&'需要一个左值参数,并产生一个右值:

            int var = 10;
            int* bad_addr = &(var + 1); // ERROR: lvalue required as unary '&' operand
            int* addr = &var;           // OK: var is an lvalue
            &var = 40;                  // ERROR: lvalue required as left operand
                                           // of assignment    

        该符号(&)在C++中还扮演着另一个角色 - 允许它定义引用类型,被称为“左值引用”。不能为非const左值引用赋一个右值,因为这将需要一个无效的右值到左值的转换:

            std::string& sref = std::string();      // ERROR: invalid initialization of
                                                    // non-const reference of type
                                                    // 'std::string&' from an rvalue of
                                                    // type 'std::string'

    Constant lvalue references can be assigned rvalues. Since they're constant, the value can't be modified through the reference and hence there's no problem of modifying an rvalue. This makes possible the very common C++ idiom of accepting values by constant references into functions, which avoids unnecessary copying and construction of temporary objects.
            注:不是很懂//可以为一个非const左值引用赋一个右值。因为他们的值是常量,该值不能通过引用来改变,而且改变一个右值是没有问题的。函数接收一个值的const引用为形参,这避免了当前对象一些不必要的拷贝和构造,这可能在C++中很常见。
    cv 限定符(const volatile)的右值:
        volatile:这个关键字提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。
        const:这个关键字是C++中常用的类型修饰符,常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。
        const和volatile放在一起的意义在于:
            (1)本程序段中不能对a作修改,任何修改都是非法的,或者至少是粗心,编译器应该报错,防止这种粗心;
            (2)另一个程序段则完全有可能修改,因此编译器最好不要做太激进的优化。
         如果我们仔细阅读C++标准,讨论左值到右值转换的部分,我们注意到它说:
         An lvalue (3.10) of a non-function, non-array type T can be converted to an rvalue. [...] If T is a non-class type, the type of the rvalue is the cv-unqualified version of T. Otherwise, the type of the rvalue is T.
        也就说:非函数,数组类型的左值T可以转换成为右值。如果T是非类类型的,那么这种类型就不能被 cv 限定符修饰,如果T是一个类类型的,那么他就可以被cv限定符修饰。
        那么什么“cv限定符”呢? cv限定符是用来描述const和volatile类型限定符的术语。
        From section 3.9.3:
            Each type which is a cv-unqualified complete or incomplete object type or is void (3.9) has three corresponding cv-qualified versions of its type: a const-qualified version, a volatile-qualified version, and a const-volatile-qualified version. [...] The cv-qualified or cv-unqualified versions of a type are distinct types; however, they shall have the same representation and alignment requirements (3.9)
            每一个没有被cv限定符修饰的完整或者是不完整的对象类型或者无类型的类型都有三种相cv限定符版本的类型:const修饰,volatile修饰和cv修饰的版本。cv限定符修饰的和没有cv限定符修饰的版本的类型是不同的类型。然而,他们应具有相同的表示和对齐的要求。
        但是这和右值有什么关系呢?在C中右值永远不会有cv限定符修饰的类型。只有左值有。从另一方面来看,在C ++中,,类类型右值可以有cv限定符修饰的类型,但内置类型(如int)不能。考虑下面这个例子:

           #include <iostream>
    
            class A {
                public:
                        void foo() const { std::cout << "A::foo() const
    "; }
                        void foo() { std::cout << "A::foo()
    "; }
                    };
    
                    A bar() { return A(); }
                    const A cbar() { return A(); }
    
    
            int main()
            {
                    bar().foo();  // calls foo
                   cbar().foo(); // calls foo const
            }

        在主函数中第二个函数调用是真正的调用了A类中的foo() const,这是因为cbar()函数返回的是一个const A的类型。这个类型和A是不同的,这正是我们前面所提到的一点,还需要注意的是,从cbar()返回值是一个右值。因此,这是cv限定符修饰的右值的一个例子。
    右值引用(C ++ 11):
        右值引用和移动语义的相关概念是最强大的新功能的C ++ 11标准引入到语言之一。该功能的全面讨论超出了该文章[的范围,但我还是想提供一个简单的例子,因为我认为这是来证明如何理解左值和右值能辅助我们思考不平凡的语言概念的能力的一个好地方。
        我刚刚解释的左值和右值之间的主要区别之一是:左值可以修改,而右值不能。那么,C ++ 11增加了一个重要的转折这种区别,在某些特殊情况下,允许我们通过对右值的引用,从而对其进行修改。

  • 相关阅读:
    Go 单元测试、基准测试、并发基准测试
    Go url编码和字符转码
    ssh 登录进入 docker container
    Python 开发
    Ethereum 源码分析之 accounts
    Ethereum 源码分析之框架
    数据库视图
    共识算法:PBFT、RAFT
    JQuery Mobile
    Android Studio
  • 原文地址:https://www.cnblogs.com/jlxuexijidi-2015/p/5068012.html
Copyright © 2011-2022 走看看