左值、右值、左值引用和右值引用

左值和右值

什么是左值和右值?

按照我们常规理解左值就是在等号左边的值,右值是等号右边的值。如果你要这么理解,你就会发现遇到++ii++这一类的语句你就开始混乱了!

其实左值和右值是我们习惯性的叫法,他的全拼应该是:
左值是:locator value(可寻址的数据)
右值是:read value(不可寻址的数据或用来读的数据)

我们常规去理解,应该理解为:
在该程序语句之后能再找到该值,就是左值;否则就是右值。就是不可寻址!

举例说明

左值:

++x;
y*=33;
//这里面++x是直接对x自增,我们得到的值就是x的值,
//是可以后来直接使用x这个变量去使用这个值的。

右值:

x++;
y+3;
123//y+3这个值会被计算,但是没有被承接到,
//后来即便我们再用y+3去获得这个临时的值,大小是一样的,
//但不是我上次计算的那个值,我又经过了以此计算的到的。

//x++;这个从底层去分析:
//x++会产生一个临时变量,用来保存x+1的值,
//等到语句结束,将x+1赋值给x.
//但是语句没结束时,这个临时变量时在寄存器中保存的,一个计算结果的临时变量,
//此时是不可寻址的!!即右值。

程序分析
#include<iostream>
#include<string>

#define func(x)  _func(x,"func("#x")") 
using namespace std;

void _func(int &x,string str){ //左值值重载_func()
    cout << str << "left value !"<<endl;
    return ;
}

void _func(int &&x,string str){ //右值重载_func()
    cout << str << "right value !"<<endl;
    return ;
}
int main(){
    int x = 3;
    int y = 4;
    func(123);
    func(x++);
    func(++x);
    func(x*3);
    func(x*=3);
    func(y/4);
    func(y/=4);
    return 0;
}

输出结果为:

func(123)right value !
func(x++)right value !
func(++x)left value !
func(x*3)right value !
func(x*=3)left value !
func(y/4)right value !
func(y/=4)left value !

左值引用和右值引用

什么是左值引用和右值引用?
  • 什么是引用?

引用表示为符号“&”;
引用就是用另外的名称来索引到该变量。

左值引用

左值引用得到的就是还是一个左值

#include<iostream>
using namespace std;
int main(){
    int a = 10;
    int &b = a;
    cout << b << endl; //输出为10
    b = 5;
    cout << a << endl; //输出为5
    cout << &a << " " << &b << endl; //a和b的地址相同。
    return 0;
}

分析:

其中a和b都是左值,他们都是一个变量,且都是表示相同地址上存放的某个int类型的数。
且此时输出a和b的地址是一样的。

右值引用 (important!!!)

右值引用的使用常常在自定义类中,可以查看该博文去理解

右值引用操作符为 “&&”;
右值引用得到的是一个左值。
右值引用通常将一个临时变量拿过来用。
右值引用最主要的功能是解决的是自定义类重复构造冗余的问题。

下面三种情况就告诉你什么时右值引用?为什么要有右值引用?

  • 未使用右值引用时且不去承接返回值
//"test2.cpp"
#include<iostream>
using namespace std;

class A{
    int x;
    public:
    A(int x = 0):x(x){
        cout << this << ":default constructor"<<endl;
    }
    A(const A&a):x(a.x){
        cout << this << ":copy constructor"<<endl;

    }
    A operator+(const A& a){
        return A(x+a.x);
    }
    ~A(){
        cout << this << ":destructor"<<endl;
    }
};

int main(){
    A a(1),b(3);
    a+b;

    cout << "====================="<<endl;
    return 0;
}

通过关闭返回值优化g++ test2.cpp -fno-elide-constructor ,可以看到结果:

0x7fffbe1e127c:default constructor   //A a(1)
0x7fffbe1e1280:default constructor   //A b(3)
0x7fffbe1e1244:default constructor   //A(a.x+b.x)
0x7fffbe1e1284:copy constructor      //return A(a.x+b.x)->返回值会调用拷贝构造给承接a+b的变量A。代码中没有承接到a+b,会立马构析。
0x7fffbe1e1244:destructor
0x7fffbe1e1284:destructor
=====================   //程序结束后构析
0x7fffbe1e1280:destructor
0x7fffbe1e127c:destructor
  • 使用对象A直接去承接返回值
int main(){
    A a(1),b(3);
    A c = a+b;   //此时用A类对象C承接返回值。

    cout << "====================="<<endl;
    return 0;
}

关闭返回值优化,得到结果:

0x7ffd08697558:default constructor
0x7ffd0869755c:default constructor
0x7ffd08697524:default constructor  //A(a.x+b.x)
0x7ffd08697564:copy constructor  //return A(a.x+b.x)的临时对象
0x7ffd08697524:destructor  ~A(a.x+b.x)
0x7ffd08697560:copy constructor  //A C = (return A(a.x+b.x)) 拷贝构造
0x7ffd08697564:destructor
=====================
0x7ffd08697560:destructor
0x7ffd0869755c:destructor
0x7ffd08697558:destructor
//可以看到产生大量的拷贝构造,临时变量我没有救火,卫视拷贝了一个拿来用。
  • 使用右值引用且承接返回值
int main(){
    A a(1),b(3);
    A &&c = a+b;  //右值引用

    cout << "====================="<<endl;
    return 0;
}

关闭返回值优化,得到结果:

0x7ffc22247b74:default constructor
0x7ffc22247b78:default constructor
0x7ffc22247b44:default constructor
0x7ffc22247b7c:copy constructor
0x7ffc22247b44:destructor
=====================
0x7ffc22247b7c:destructor
0x7ffc22247b78:destructor
0x7ffc22247b74:destructor
//可以发现return A(a.x+b.x)产生的临时对象直接被C用了,没产生多余的拷贝构造。

右值引用就是把右值变成左值,通常实在C++返回值上,对于自定子类的重复拷贝做了重要改善,大大提高了C++的效率。
右值引用的概念是C++中的重要概念!!!!。

左值和右值的转换

左值变右值
  • 通过move(class value)函数

move()可以通过man手册查看。

  • 通过通用转换 forward<B&&>
    • 通过引用const &

变成只读,也是不能放在等号左边

右值变左值
  • 通过右值引用&&

  • 通过通用转换 forward<B&>

<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; // ❌错误:常量引用不可修改 ``` --- #### 七、总结对比表 | **特征** | **左值** | **** | **左值引用** | **引用** | |------------------|------------------------|------------------------|-----------------------|-----------------------| | **地址可获取性** | ✅ | ❌ | 继承绑定对象属性 | 继承绑定对象属性 | | **主要用途** | 存储持久数据 | 临时计算/资源转移 | 别名/避免拷贝 | 移动语义/完美转发 | | **典型绑定对象** | 变量、数组元素 | 字面量、返回临时对象 | 左值 | | ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四库全书的酷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值