• 探究C++中左值、右值与引用问题


    我的理解


      首先得知道,引用是一个左值,而常量引用是一个右值。两者最关键的地方在于,左值可以被取到地址,而右值取不到地址,这个性质就决定了右值不能在 “=” 的左侧。

      对于 lValue rValue,从语言角度,我的理解是:如果是左值,那么它在当前作用域内都可以被取得,而如果是右值,那么它只能在当前一条语句中被取得。

      从汇编角度类比,左值是一个内存单元里的数据,我们可以通过相对应的地址找到它,而右值是立即数或一个暂时存储在寄存器里的值。

      

    C与C++中的左值、右值


      C 中可以通过赋值号 “=” 简单地判断,比如

     int a = 42; // 表达式a是左值,字面值常量42是右值
        int b = 43; // 表达式b是左值,字面值常量43是右值
    
        a = b; // 表达式a和表达式b都是左值
        b = a; // 表达式a和表达式b都是左值
        a = a * b; // 表达式a是左值, 表达式a*b是右值
    
        int c = a * b; // ok,表达式c是左值,表达式a*b是右值
        a * b = 42; // error,表达式a*b是右值,右值不能出现在赋值操作符的左边
                    //lvalue required as left operand of assignment

      但是在 C++ 中,由于自定义类引入的一些新的特性,这些就没那么简单了。

      当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。

      C++中常见的左右值如下:

      • 赋值运算符(=)的左侧运算对象必须是一个非常量的左值,其结果也仍然是一个左值。
      • 递增和递减运算符(++和–)必须作用于左值运算对象,前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。
      • 箭头运算符(->)作用于一个指针类型的运算对象,结果是一个左值。
      • 点运算符(.)分成两种情况: 
        • 如果成员所属的对象是左值,那么结果是左值。
        • 如果成员所属的对象是右值,那么结果是右值。
      • 条件运算符(? :)的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值。
      • 取地址运算符(&)作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。
      • 内置解引用运算符*、迭代器解引用运算符*、内置下标运算符[]、容器下标运算符[]的求值结果都是左值。
      • 算术运算符的运算对象和求值结果都是右值。
      • 逻辑运算符(!,&&,||)的运算对象和求值结果都是右值。
      • 关系运算符(<,<=,>,>=,==,!=)的运算对象和求值结果都是右值。
      • 函数的返回类型决定函数调用是否是左值。 
        • 调用一个返回引用类型的函数得到左值。
        • 调用一个返回其他类型的函数得到右值。

    记忆方法


      

      我的记忆规则有两条:

        I)左值持久,右值短暂

           左值是持久的,而右值要么是常量,要么是表达式求值所产生的临时对象。

        II)一切变量都是左值,但const量是例外

    两个错误例子


     

      例一:在递归时,用由构造函数刚构造出来的对象作为引用绑定的对象    

        

    //错误样例
    int func(Object &a) { //假设 Object 有构造方法;引用需要绑定左值
        if (...) return ...;         
         
        return func(a(30)); //错误,a(30)是一个临时对象,是右值,而引用需要的是左值 
      //我觉得这儿可以沿用 C 中判断左值、右值的方法:a(30) 不能放在等号左侧,所以是右值 
    }
    
    //改进方法1
    int func(const Object &a) { //常量引用绑定右值
        if (...) return ...;          
        func(a(30)); 
    }
    
    //改进方法2
    int func( Object &a) { 
        if (...) return ...;
        Object b = a(30); // b是左值       
        func(b); 
    }

      例二:参数左右值匹配问题与函数类型问题

    #include<iostream>
    using namespace std;
    
    string func1 (string &s) {
        string s2;
        return s2;        
    }
    
    string& func2 (string &s) {
        string s2; 
        return s2;  //注意,其实s2不应返回,因为它是个局部变量
        //这里主要是强调函数类型对左值、右值的影响
    }
    
    const string func3 (string &s) {
        return "1234";        
    }
    
    int func4 (const string &s)  {
        return 0;
    }
    
    int main (void) 
    {
        string s1 = "123456";  // s1 是左值
        string &s2 = func1(s1);  //引用需要左值,而 func1() 非引用类型,它返回的是右值,所以错误
        string &s3 = func2(s2);  //引用需要左值,由于 func2() 为引用类型,它返回的是左值,所以编译器通过
        string &s4 = func3(s1);  // func3()返回的是右值,而 s3需要的是左值,所以出错    
        func4(s1);  // 标准规定不论是左值还是右值都可以转化成常量引用,所以正确
        return 0;
    }

        因为常量引用不仅可以接收普通引用和常量引用,而且还表明了函数不能修改它实参的值,利于访问控制,所以把函数不会改变的形参定义为常量引用是一个很好的习惯。

        

      

    ————全心全意投入,拒绝画地为牢
  • 相关阅读:
    数据库
    数据库
    数据库
    数据库
    数据库
    数据库
    windows
    LeetCode : Word Pattern
    LeetCode : Perfect Number
    LeetCode : Minimum Depth of Binary Tree
  • 原文地址:https://www.cnblogs.com/Bw98blogs/p/8168088.html
Copyright © 2020-2023  润新知