C++中的左值,右值,左值引用,右值引用

左值与右值(lvalue&rvalue

  左右值来源于C语言,原意是为了方便记忆:左值可以位于赋值语句的左侧,右值则不能。但是在C++中,左右值的区分没有那么简单,还存在 许多例外的情况,如:

  • 一个左值表达式的求值结果是一个对象或一个函数,然而以常量对象为代表的某些左值实际上是不能作为赋值语句的左侧运算对象的。如:
const int a =1;
//a是左值但不可位于赋值语句左侧
  • 某些表达式的求值结果是对象,但它们是右值而非左值。

  那么,到底如何区分左右值,我的理解是,一个左值表达式首先必须要能使用其地址,所以不能是常量表达式,计算式,字面常量。可以简单归纳为:

  • 左值:占用了一定内存,且拥有可辨认的地址的对象。包括所有变量。
  • 右值:左值以外的所有对象。如,字面量、临时对象、临时表达式等。

  此外,左右值的使用可以总结出几点规律:

  • 一般而言,一个左值表达式表示的是一个对象的身份(在内存中的位置),而一个右值表达式表示的是一个对象的值(内容)
  • 在需要右值表达式的地方可以用左值表达式来代替(此时使用的是左值的值),但不能用右值表达式代替左值表达式。
  • 一个表达式一定是左值表达式和右值表达式中的一种。
  • 返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减运算符,都生成左值。
  • 返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值。

左值引用与右值引用(lvalue reference&rvalue reference)

 左值引用,就是正常的引用,需要注意的是const引用,

int i=1;
int &a = i;        //正确,i的左值引用
int &b = 2;        //错误,左值引用不能绑定右值
const int &c = 3;    
// 正确,特殊情况,可以理解为临时创建了一个左值,然后引用

  右值引用,是C++11引入的新类型,

  用来表示一个本没有名称的临时对象,给这个临时内存位置分配了一个名称,使得程序的其他部分能够访问这个临时内存位置,并且可以把这个临时位置变成了一个左值。所谓右值引用就是必须绑定到右值的引用。右值引用关联到右值时,右值被存储到特定位置,右值引用指向该特定位置,也就是说,右值虽然无法获取地址,但是右值引用是可以获取地址的,该地址表示临时对象的存储位置。通过&&而不是&来获得右值引用。右值引用有一个重要的性质----只能绑定到一个将要销毁的对象。因此,可以自由地将一个右值引用的资源”移动”到另一个对象中。
int &&i  = 1;

  右值引用有两个主要用途:

  • 移动语意(Move Semantics)

  Rvalue引用支持移动语义的实现,这可以显着提高应用程序的性能。移动语义使您可以将资源(如动态分配的内存或者数据)从一个对象传输到另一个对象,减少内存分配和复制操作。移动运算符只是简单的切换右值的内部缓冲区为自己的,所以右值析构器将会释放我们对象自己不再使用的缓冲区。移动语义之所以起作用,是因为它使资源能够从程序中其他地方无法引用的临时对象中转移。要实现移动语义,通常需要为类提供一个移动构造函数以及一个可选的移动赋值运算符(operator =)。源为右值的复制和赋值操作将自动利用移动语义。与默认的复制构造函数不同,编译器不提供默认的move构造函数。

  std::move就是一个将左值转化为右值的函数,例如:

 template<typename T>
void swap(T& a, T& b)
{
     T t(std::move(a));  // a为空,t占有a的初始数据
     a = std::move(b); //  b为空, a占有b的初始数据
     b = std::move(t); // t为空,b占有a的初始数据
} 
  • 完美转发(Perfect Forwarding)

  完美转发减少了重载函数,避免了转发的问题。转发的问题出现在你写通用函数将引用作为参数,将这些参数由函数调用的时候。举个例子,如果通用函数将 type const T&作为参数,那么调用函数不能修改参数的值。如果通用函数将 type T&作为参数,那么当参数是右值的时候,函数不能调用。通常来说,为了解决上述的问题,你需要提供重载函数,既要有type const T&参数的函数,也要有type T&参数的函数。结果呢,重载函数的数量随着参数数量呈指数递增。而右值引用能够使你只用一个函数就能适用于任意数量的参数。

以下是不使用完美转发的代码:

#include<iostream>
#include<string>
using namespace std;

template<typename T> struct S;
template<typename T>
struct S<T&> {
    static void print(T& t) {
        cout << "print<T&>: " << t << endl;
    }
};

template<typename T>
struct S<const T&> {
    static void print(const T& t) {
        cout << "print<const T&>: " << t << endl;
    }
};

template<typename T>
struct S<T&&> {
    static void print(T&& t) {
        cout << "print<T&&>: " << t << endl;
    }
};

template <typename T>
struct S<const T&&> {
    static void print(const T&& t) {
        cout << "print<const T&&>: " << t << endl;
    }
};

template<typename T>
void print_type_and_value(T&& t) {
    S<T&&>::print(std::forward<T>(t));
}
const string fourth() {
    return string("fourth");
}

int main()
{
    string s1("first");
    print_type_and_value(s1);//左值

    const string s2("second");
    print_type_and_value(s2);//const 左值

    print_type_and_value(string("third"));//右值

    print_type_and_value(fourth());//const 右值
    return 0;
}

然后是使用了完美转发后的代码:

#include<iostream>
#include<string>
using namespace std;

template <class T>
void print(T&& t) {
    cout << "success! " <<t<< endl;
}
string fourth() {
    return string("fourth");
}

int main()
{
    string s1("first");
    print(s1);//左值

    const string s2("second");
    print(s2);//const 左值

    print(string("third"));//右值

    print(fourth());//const 右值
    return 0;
}

 

  • 同时接受左值和右值作为参数
函数参数不变:C++对const &添加了特殊技能:既能接受左值又能接受右值。举例代码如下:
#include<iostream>
using namespace std;
void Print(const string &s){
    cout<<s;
}
int main(){
    string s="abc";
    Print(s);//s为左值
    Print("abc");//"abc"为右值
}
函数参数可变:右值引用
#include<iostream>
using namespace std;
class Demo{};
void Func(const Deno &){
    cout<<"Func(const Demo &)"<<endl;
}
void Func(Demo &){
    cout<<"Func(Demo &)"<<endl;
}
void Func(Demo &&){
    cout<<"Func(Demo &&)"<<endl;
}
int main(){
    Demo d;
    Func(d);//左值
    Func(Demo());//右值
    const Demo cd;
    Func(cd);//左值
}

 

posted @ 2020-04-09 08:47  浩楠honer  阅读(620)  评论(0编辑  收藏  举报