C++ 引用

C++11 之后,C++的引用扩充为 非常量左值引用,常量左值引用,非常量右值引用,常量右值引用。

什么是左值?什么是右值?左值、右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不存在的临时对象。一个区分左值、右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。

什么是引用?引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的。

引用又分为常量引用、非常量引用,它们的区别是:通过非常量引用可以对与之绑定的对象做所有操作,而对通过常量引用,则不可以对与之绑定的对象做任何修改操作。

引用在定义时需要同时初始化,即完成与一个对象的绑定。以上四类引用对可以与之绑定的对象是有要求的。

非常量左值引用(T & = x) :  x只能是一个T类型的非常量左值。

常量左值引用 (const T & = x) : x可以是左值,也可以是右值。x的类型可以是T类型以及所有可以隐式转换为T类型的其它类型。x既可以是常量也可是非常量。

非常量右值引用(T && = x) : 如果X是T类型的,则必须是非常量右值。如果X是可以隐式转换为T类型的其它类型,则左值、右值都可以。

常量右值引用(const T && = x) :可以指向T类型的常量、非常量右值。如果X是可以隐式转换为T类型的其它类型,则左值、右值都可以。

当引用绑定的对象被销毁后,这个引用就失效了,使用失效的引用是未定义的行为,一般就是程序崩溃。所以引用绑定的对象啥时候被销毁是需要关注的。引用绑定左值没啥问题,但指向右值,不是说右值在表达式结束后就销毁了吗?那定义的这个引用还有啥用?引发未定义的行为?引用定义有两个场景:函数参数传递、其它。

先说函数参数传递。比如函数声明为 void fun(const string & param),调用时使用右值初始化param: fun( string()); 。可以看到,string()虽然是个右值,fun(string())这个表达式结束后被销毁了,但这时fun函数已经运行完了,所以在fun运行期间,引用param一直有效。所以,这个场景引用绑定右值完全没有问题。

再说其它(函数作用域、全局作用域、类作用域内定义引用),以函数作用域为例:

class test1
{
public:
    test1(int ){
        cout<<"cc"<<endl;
    }
    test1(const test1 &)
    {
        cout<<"copy"<<endl;
    }
    ~test1(){cout<<"destroy"<<endl;}
};
void fun()
{
   const test1 &x = test1(2); 
  test1 y(x); }

  以上代码中,x绑定了一个右值,而这个右值按照常规,在const test1 &x = test1(2); 这个表达式结束后就被销毁了,但实际上却没有。在这种情况下,这个右值的生命期被延长了,test1(2)等同于一个匿名的左值变量,在退出它的作用域时才被销毁。其行为等同于以下代码:

void fun()
{
    test1 tmp(2);
    const test1 &x = tmp;
    test1 y(x);
}

  以上讨论的右值不包括字面常量,如果右值是字面常量,编译器会为其定义一个临时变量来存储它的值,然后引用绑定的就是这个临时变量。当然,这个临时变量也是个右值,所以其生命期的规律就是普通右值的规律。

  常量左值引用 const T &  = x ,x还可以指向不是T类型的左值、右值,只要x的类型可以隐式转换为T类型。其原理是这样的:编译器将x隐式转换为T类型的一个临时变量,然后 常量左值引用绑定的就是这个临时变量。这个临时变量当然是个右值。右值的生命期的规律上面已经讲过。

class test1
{
public:
    test1(int ){
        cout<<"cc"<<endl;
    }
    test1(const test1 &)
    {
        cout<<"copy"<<endl;
    }
    ~test1(){cout<<"destroy"<<endl;}
};int main()
{
    test1 x(3);
    //test1 && y = x;   //error,右值引用只能指向右值
    int i = 3;
    test1 && y = i; //right,y指向的是用i生成的一个临时变量:右值
    system("pause");
    return 0;
}

   需要注意的一点是,左值引用都是左值,但右值引用有可能是左值,也可能是右值。是左值还是右值要看这个引用“是否是是右值”。

int main()
{
    int && x = 3;
    const int && y = x;//error,x是左值
    system("pause");
    return 0;
}

  以上的例子中,const int && y = std::move(x);才是正确的写法。如果把右值引用看做一个对象,这个对象本身是右值,那么这个右值引用才会被视为右值。好绕啊。

 不过,从本质上,引用是左值还是右值,还是从其指向的对象是否是长久存在的来判断的。因为右值引用指向的对象的生命期会被延长到至少与右值引用一样长,所以,只有当这个右值引用也只是个临时的,它所指向的对象才会被视为临时的,所以才会被视为右值。

原文地址:https://www.cnblogs.com/vsuu/p/4092452.html