左值和左值引用左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址,一般可以对它赋值,左值可以出现在赋值符号的左边,右值不能出现在赋值符号的左边.定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址,因此还是左值.左值引用就是给左值的引用,给左值取别名.如:代码语言javascriptAI代码解释//左值引用 int a 0; int r1 a; //给a取别名为r1右值和右值引用右值是一个表示数据的表达式,如:字面常量,表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址.右值引用就是对右值的引用,给右值取别名.如:代码语言javascriptAI代码解释//右值引用 int r5 10; //给10取别名为r5 double x 1.1, y 2.2; double r6 x y; //给表达式xy取别名为r6左值引用和右值引用比较左值引用左值引用只能引用左值,不能引用右值但是const左值引用既可以引用左值,也可以引用右值代码语言javascriptAI代码解释/左值引用引用右值 double x 2.2; double y 3.3; const int r2 10; const double r3 x y;//这里x和y都是左值,但是xy表达式返回的结果5是一个临时变量是右值右值引用右值引用只能引用右值,不能引用左值.但是特殊情况下右值引用可以引用move以后的左值.代码语言javascriptAI代码解释//右值引用引用左值 int a 10; int r7 move(a);也就是说,正常情况下左值只能引用左值, 右值只能引用右值, 但是const左值可以引用右值,右值可以引用move后的左值。左值引用和右值引用使用场景和意义左值引用使用场景和意义左值引用使用场景:做参数代码语言javascriptAI代码解释void swap(int a,intb) //左值引用可以直接修改原对象,减少参数传递时的拷贝 { int tmp a; a b; b tmp; } int main() { int x 2; int y 3; swap(x,y); return 0; }做返回值代码语言javascriptAI代码解释//左值引用可以直接修改返回值,同时减少了函数传值返回的拷贝 int get(size_t pos) { return data[pos]; }左值引用意义:减少拷贝,并可以直接修改原对象左值引用的缺点:但是当函数返回对象是一个局部变量出了函数作用域就不存在了就不能使用左值引用返回只能传值返回。例如函数中可以看到这里只能使用传值返回传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。右值引用使用场景和意义通过上面我们对左值引用使用场景和意义的分析,我们得知了左值引用的短板。因此C的大佬们就引入了右值引用和移动语义来解决这个问题:移动语义包括移动构造和移动赋值,我们先来看移动构造:移动构造本质是将参数右值的资源窃取过来占为已有那么就不用做深拷贝了所以它叫做移动构造就是窃取别人的资源来构造自己:而移动赋值也是将赋值运算符右边的右值资源窃取过来,占为己有,也就不用再做深拷贝了:基于上面的概念,实现的string类移动构造和移动赋值函数如下:代码语言javascriptAI代码解释//移动构造 string(string s) :_str(nullptr) , _size(0) , _capacity(0) { swap(s); } //移动赋值 string operator(string s) { swap(s); return *this; }右值引用引用左值及其使用场景有些场景下我们可能需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时可以通过move函数将左值转化为右值。代码语言javascriptAI代码解释int main() { string s1(hello world); // 这里s1是左值调用的是拷贝构造 string s2(s1); // 这里我们把s1 move处理以后, 会被当成右值调用移动构造 // 但是这里要注意一般是不要这样用的因为我们会发现s1的 // 资源被转移给了s3s1被置空了。 string s3(std::move(s1)); return 0; }完美转发完美转发Perfect Forwarding是 C11 引入的核心特性之一用于在泛型编程中精确传递参数的左值/右值属性避免不必要的拷贝或类型损失。它结合了右值引用、万能引用Universal Reference和std::forward实现。为什么需要完美转发?假设有一个泛型函数wrapper需要将参数转发给另一个函数target代码语言javascriptAI代码解释templatetypename T void wrapper(T arg) { target(arg); // 直接传递参数 }问题值类别丢失无论arg是左值还是右值target(arg)接收的始终是左值因为右值引用本身是左值, 如果右值引用本身是右值那么就没法移动语义了所以左值引用和右值引用传递到下层都变成了左值引用。拷贝开销若arg是临时对象右值无法触发移动语义可能导致深拷贝。右值引用默认是左值,我们才能基于此实现移动语义:但是如果不支持完美转发的话,右值引用无法保持右值属性,那么我们遇到嵌套容器深拷贝的情况就没法用移动语义:如何实现完美转发?1. 万能引用Universal Reference语法模板参数中使用T且T需要被推导如函数模板或auto。特性可以绑定到左值或右值保留参数的原始类型信息。如果实参是左值,他就是左值引用(引用折叠)如果实参是右值,他就是右值引用代码语言javascriptAI代码解释templatetypename T void wrapper(T arg) { // arg 是万能引用 // 模板中的不代表右值引用而是万能引用其既能接收左值又能接收右值。 // 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力 // 但是引用类型的唯一作用就是限制了接收的类型后续使用中都退化成了左值 // 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发 }2.std::forwardT作用根据T的原始类型左值或右值将参数有条件地转换回原始类型。本质若T是左值引用返回左值否则返回右值引用触发移动语义。完整示例代码语言javascriptAI代码解释#include iostream #include utility // std::forward // 目标函数 void target(int x) { std::cout 左值: x std::endl; } void target(int x) { std::cout 右值: x std::endl; } // 完美转发的包装函数 templatetypename T void wrapper(T arg) { target(std::forwardT(arg)); // 关键保留参数的原始类型 } int main() { int a 10; wrapper(a); // 传递左值 → 调用 target(int) wrapper(20); // 传递右值 → 调用 target(int) wrapper(std::move(a)); // 显式转为右值 → 调用 target(int) return 0; }结语希望这篇关于 C11之左值引用,右值引用和移动语义 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!