左值、右值、左值引用、右值引用、引用折叠、完美转发

C++11引入了右值引用,用于优化对象的复制和移动。左值有明确地址,右值则不提供地址,如临时对象。右值引用可以延长右值的生命周期,用于移动语义,减少拷贝开销。std::move用于将左值转换为右值引用,std::forward实现完美转发,保持参数原本的左值或右值性质。

右值&&左值

c++11增加了一个新的类型,右值引用,记作:&&

左值是指在内存中有明确的地址,我们可以找到这块地址的数据(可取地址)

右值是只提供数据,无法找到地址(不可取地址)

所有有名字的变量都是左值,而右值 是匿名的。

一般情况下位于等号左边的是左值,位于等号右边的是右值,但是也可以出现左值给左值赋值的情况

c++11中右值分为两种情况:一个是将亡值,一个是纯右值.

将亡值:与右值引用相关的表达式,比如:T&&类型函数的返回值,std::move()的返回值等

纯右值:非引用返回的临时变量,运算表达式产生的临时变量,原始字变量,lambda表达式等

左值引用:

左值引用就是我们平常使用的“引用”。引用是为对象起的别名,必须被初始化,与变量绑定到一起,且将一直绑定在一起。

我们通过 & 来获得左值引用,可以把引用绑定到一个左值上,而不能绑定到要求转换的表达式、字面常量或是返回右值的表达式。这个没啥说的,像拷贝构造啥的平时都能用到。

右值引用:

右值引用就是对右值引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到它。无论是左值引用还是右值引用都必须被初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用,该右值所占的内存又可以被使用。

int&& vaule = 520;    //右值引用,520是字面量,是右值

class Test
{
public:
    Test()
    {
        cout<<"构造函数"<<endl;
    }
    Test(const Test&a )
    {
        cout<<"拷贝构造函数"<<endl;
    }
};

Test getObj()
{
    return Test();
}

int main()
{
    int a1;
    //int && a2 = a1;  报错 右值引用不能被左值初始化
    //Test & t1 = getObj();     右值不能初始化左值引用
    Test && t2 = getObj();    //函数返回的临时对象是右值,可以被调用
    const Test & t3 = getObj();    //常量左值引用是万能可以接左值
    coonst int & t3 = a1;     //被左值初始化
    return 0;
}

右值引用的用处

在c++用对象初始化对象时会调用拷贝构造,如果这个对象占用堆内存很大,那么这个拷贝的代价就算是非常大的,在某些情况,如果想避免对象的深拷贝,就可以使用右值引用进行性能的优化。

  右值引用具有移动语义,移动语义可以将堆区资源,通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象创建,拷贝以及销毁,大幅度提高性能

结论:需要动态申请大量的资源的类,应该设计移动构造,提高程序的效率,需要注意的是在提供移动构造的同时,一般也会提供左值引用拷贝构造函数,左值初始化新对象时会走拷贝构造

右值引用的特点:

c++中,并不是所有情况下&&都代表右值引用,在模板和自动类型推导(auto)中,如果是模板参数需要指定为T&&,如果是自动类型推导需要指定为auto&&,这两种情况下&&被称作未定的引用类型。另外constT&&表示一个右值引用,不是未定引用类型。因为T&&或者auto&&这种未定引用类型作为参数时,有可能被推导成右值引用,也有能被推导为左值引用,在进行类型推导时右值引用会发生变化,这种变化被称为引用折叠,折叠规则如下:

 通过右值 推导T&&或者auto&&得到的是一个右值引用类型,constT&&表示一个右值引用

 通过非右值(右值引用,左值,左值引用,常量右值引用,常量左值引用)推导T&&或者auto&&得到的是一个左值引用类型.

int main()
{
    int && a1 = 1;    //右值推导为    右值引用
    auto && bb = a1;    //右值引用推导为    左值引用
    auto && bb1 = 2;    //右值推导为    右值引用

    int a2 = 1;
    int &a3 = a2;    //左值推导为    左值引用
    auto && cc = a3;    //左值引用推导为    左值引用
    auto && cc1 = a2;    //左值推导为    左值引用

    const int & s1 = 1;    //常量左值引用
    cosnt int && s2 = 1;    //常量右值引用
    auto && dd = s1;    //常量左值引用推导为    左值引用
    auto && ee = s2;    //常量右值引用推导为    左值引用

    return 0;
}

 move

c++11添加了右值引用,却不能左值初始化右值引用,在一特定的情况下免不了需要左值初始化右值引用(用左值调用移动构造),如果想要用左值初始化一个右值引用需要借助std::move()函数。move()函数可以将左值比换为右值

 forward

右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该产生给内部其他函数时,他就变成了一个左值(当右值被命名时编译器会认为他是个左值),并不是原来的类型了。如果按照参数原来的类型转发到另一个函数,可以使用c++11的 std::forward()函数,该函数实现的功能称之为完美转发

//函数原型
template<class T>T&&forward(typename remove_reference<T>::type & t)noexcept;
template<class T>T&&forward(typename remove_reference<T>::type && t)noexcept;

//精简之后的样子
std::forward<T>(t);
//当T为左值引用类型时,t将会被转换为左值
//当T不是左值引用类型时,t将会被转换为T类型的右值

#include<iostream>
using namespace std;

template<typename T>
void printValue(T& t)
{
    cout<<"左值引用:"<<t<<endl;
}

template<typename T>
void  printValue(T&& t)
{
    couot<<"右值引用:"<<t<<endl;
}

template<typename T>
boid testForward(T && v)
{
    printValue(v);
    
    printValue(move(v));
    printValue(forward<T>(v));
    cout<<endl;
}

int main()
{
    testForward(520);
    int num = 1314;
    testForward(num);
    testForward(forward<int>(num));
    testForward(forward<int&>(num));
    testForward(forward<int&&>(num));
    return 0;
}

<think>嗯,用户现在想了解C++中的左值左值引用引用的概念及区别。我需要先回顾一下这些基本概念,确保自己理解正确,然后再组织语言用中文清晰解释。 首先,左值的定义。左值通常指的是有明确内存地址的对象,可以取地址,比如变量、类对象等。而则是临时的,无法取地址,比如字面量、临时对象或表达式结果。比如,int a = 5中的a是左值,5是。 接下来是左值引用引用左值引用用&符号,只能绑定左值,比如int& b = a;而引用用&&,只能绑定,比如int&& c = 5。不过,用户提供的引用内容中提到,当使用auto&&或模板参数推导时,可能会有引用折叠的情况,这时候引用可能推导为左值引用,比如当传递一个引用变量时,因为它有名字,所以被当作左值处理,这可能让用户容易混淆。 然后需要解释移动语义和完美转发,但用户的问题主要集中在概念和区别,可能需要简要提及这些高级特性,但重点还是放在基础区别上。根据引用中的例子,比如引用在传递时会被推导为左值,导致调用左值引用的函数,这说明理解引用折叠规则和类型推导很重要。 另外,用户提供的引用内容中提到了引用折叠的规则,比如T&&在类型推导时,如果传递的是左值,会被折叠左值引用。例如,auto&& bb = a1,其中a1是引用变量,但因为它本身是左值,所以推导为左值引用。这点需要强调,因为引用变量本身是左值,只有表达式才是。 用户可能混淆的地方在于引用变量虽然是引用类型,但其本身作为具名变量是左值,所以传递给函数时会被当作左值处理。例如,在引用1中的例子,传递引用变量t时,t是左值,所以调用左值引用的函数。这可能涉及到移动语义中的std::move,将左值转换为引用。 总结起来,回答的结构应该包括定义、区别、示例代码以及常见误区。需要确保每个概念都清晰,并且对比左值引用引用的使用场景和语法差异。同时,引用折叠和类型推导的规则需要简明解释,但不过于深入以免让初学者困惑。 最后,检查是否有遗漏点,比如const对引用的影响,或者移动构造函数的作用,但根据用户的问题,可能不需要深入这些高级主题,除非必要。确保回答符合用户的需求,帮助他们建立基础概念,并能够区分左值及其引用的不同。</think>--- ### C++左值引用类型全解析 --- #### 一、核心定义与关系图谱 ```mermaid graph TD A[表达式] --> B[左值] A --> C[] B --> D[可获取地址] C --> E[临时/字面量] B --> F[可修改] C --> G[不可直接修改] H[引用类型] --> I[左值引用 &] H --> J[引用 &&] ``` --- #### 二、基础概念对比 | **类型** | **特征** | **生命周期** | **典型示例** | |----------------|---------------------------------------------|--------------------------|-------------------------| | **左值** | 具名对象,可获取地址 | 持续到作用域结束 | `int a = 10;` 中的`a` | | **** | 匿名临时,不可取地址 | 立即销毁(可能延长) | `5`, `a+3`的结果 | | **左值引用** | 绑定左值引用(`&`) | 与绑定对象同步 | `int &b = a;` | | **引用** | 绑定引用(`&&`) | 接管资源所有权 | `int &&c = 10;` | --- #### 三、关键区别详解 ##### 1. **左值 vs ** - **地址访问性** ```cpp int x = 5; int* p1 = &x; // ✅ 左值可获取地址 int* p2 = &5; // ❌ 编译错误:无法取地址 ``` - **生命周期控制** ```cpp std::string s1 = "hello"; // 左值s1持续存在 std::string s2 = s1 + " world"; // 临时对象立即销毁 ``` ##### 2. **左值引用 vs 引用** - **绑定规则** ```cpp int a = 10; int &lr = a; // ✅ 左值引用绑定左值 int &&rr1 = 10; // ✅ 引用绑定 int &&rr2 = a; // ❌ 编译错误:引用不能绑定左值 ``` - **移动语义实现** ```cpp class MyString { public: // 移动构造函数(接管资源) MyString(MyString&& other) noexcept { data = other.data; other.data = nullptr; // 原对象置空[^1] } }; ``` --- #### 四、引用折叠规则(重点) 当出现**未定引用类型**时(如模板参数推导、`auto&&`),通过引用折叠确定最终类型: | **原始类型** | **推导类型** | **折叠规则** | **示例** | |--------------|-------------|--------------------------|----------------------------| | `T& &` | `T&` | 双左值引用折叠左值引用 | `auto&& a = x;`(x为左值) | | `T& &&` | `T&` | 混合引用折叠左值引用 | `auto&& b = get_lref();` | | `T&& &` | `T&` | 混合引用折叠左值引用 | `auto&& c = std::move(y);` | | `T&& &&` | `T&&` | 双引用折叠引用 | `auto&& d = 10;` | **示例分析**(来自引用[2]): ```cpp int&& a1 = 1; // a1是引用(绑定) auto&& bb = a1; // bb推导为int&(a1是具名引用→视为左值) auto&& bb1 = 2; // bb1推导为int&&(绑定) int a2 = 1; auto&& cc = a2; // cc推导为int&(a2是左值) ``` --- #### 五、实战场景与技巧 ##### 1. **完美转发(Perfect Forwarding)** ```cpp template<typename T> void wrapper(T&& arg) { // 万能引用(可绑定/) process(std::forward<T>(arg)); // 保留原始类型 } // 调用示例 int x = 5; wrapper(x); // 传递左值→调用process(int&) wrapper(10); // 传递→调用process(int&&) ``` ##### 2. **移动语义优化** ```cpp std::vector<int> createBigData() { std::vector<int> tmp(1000000); return tmp; // 触发移动构造(避免深拷贝)[^1] } auto v = createBigData(); // 高效资源转移 ``` --- #### 六、常见误区与调试 - **引用变量是左值** ```cpp int&& rref = 10; int& lr = rref; // ✅合法:rref是具名变量→视为左值 ``` - **`const`修饰符的影响** ```cpp const int&& crr = 10; crr = 20; // ❌错误:常量引用不可修改 ``` --- #### 七、总结对比表 | **特征** | **左值** | **** | **左值引用** | **引用** | |------------------|------------------------|------------------------|-----------------------|-----------------------| | **地址可获取性** | ✅ | ❌ | 继承绑定对象属性 | 继承绑定对象属性 | | **主要用途** | 存储持久数据 | 临时计算/资源转移 | 别名/避免拷贝 | 移动语义/完美转发 | | **典型绑定对象** | 变量、数组元素 | 字面量、返回临时对象 | 左值 | | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值