zoukankan      html  css  js  c++  java
  • 探究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;
    }

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

        

      

    ————全心全意投入,拒绝画地为牢
  • 相关阅读:
    CSS之旅——第二站 如何更深入的理解各种选择器
    CSS之旅——第一站 为什么要用CSS
    记录一些在用wcf的过程中走过的泥巴路 【第一篇】
    asp.net mvc 之旅—— 第二站 窥探Controller下的各种Result
    asp.net mvc 之旅—— 第一站 从简单的razor入手
    Sql Server之旅——终点站 nolock引发的三级事件的一些思考
    Sql Server之旅——第十四站 深入的探讨锁机制
    Sql Server之旅——第十三站 对锁的初步认识
    Sql Server之旅——第十二站 sqltext的参数化处理
    Sql Server之旅——第十一站 简单说说sqlserver的执行计划
  • 原文地址:https://www.cnblogs.com/Bw98blogs/p/8168088.html
Copyright © 2011-2022 走看看