C98的C语法中就有引⽤的语法⽽C11中新增了的右值引⽤语法特性C11之后我们之前学习的引⽤就叫做左值引⽤。⽆论左值引⽤还是右值引⽤都是给对象取别名。 在C11之前C中只有左值引用。左值引用主要用于绑定到已命名的变量用于函数参数传递、返回值等场景但无法直接绑定到临时对象。临时对象通常是匿名的它们在表达式求值过程中产生并在表达式结束后销毁。例如std::string(“temporary”)就是一个临时对象。 然而临时对象在很多场景下都存在资源浪费的问题。以std::vector为例当我们对一个std::vector对象进行拷贝赋值时即使源对象是一个临时对象目标对象也会先分配一块新的内存然后将源对象中的元素逐个拷贝过去最后销毁临时对象。这个过程不仅涉及额外的内存分配和拷贝操作还可能导致不必要的性能开销。 为了解决这一问题C11引入了右值引用。右值引用可以绑定到临时对象从而允许我们直接操作临时对象的资源避免不必要的资源浪费。一.左值和右值左值的定义• 左值是⼀个表⽰数据的表达式(如变量名或解引⽤的指针)⼀般是有持久状态存储在内存中我们可以获取它的地址左值可以出现赋值符号的左边也可以出现在赋值符号右边。定义时const修饰符后的左值不能给他赋值但是可以取它的地址。右值的定义• 右值也是⼀个表⽰数据的表达式要么是字⾯值常量、要么是表达式求值过程中创建的临时对象等右值可以出现在赋值符号的右边但是不能出现出现在赋值符号的左边右值不能取地址。• 值得⼀提的是左值的英⽂简写为lvalue右值的英⽂简写为rvalue。传统认为它们分别是left value、right value 的缩写。现代C中lvalue 被解释为loacto rvalue的缩写可意为存储在内存中、有明确存储地址可以取地址的对象⽽ rvalue 被解释为 read value指的是那些可以提供数据值但是不可以寻址例如临时变量字⾯量常量存储于寄存器中的变量等也就是说左值和右值的核⼼区别就是能否取地址。二.左值引⽤和右值引⽤左值引用只能绑定到已命名的变量不能绑定到临时对象。例如std::string ref “hello”;是非法的因为hello是一个临时对象。右值引用可以绑定到临时对象也可以绑定到已命名的变量。例如std::string rref std::string(“temporary”);是合法的std::string(“temporary”)是一个临时对象。代码语言javascriptAI代码解释• 左值引⽤不能直接引⽤右值但是const左值引⽤可以引⽤右值 • 右值引⽤不能直接引⽤左值但是右值引⽤可以引⽤move(左值) • 需要注意的是变量表达式都是左值属性也就意味着⼀个右值被右值引⽤绑定后右值引⽤变量变量表达式的属性是左值三.左值和右值的参数匹配C98中我们实现⼀个const左值引⽤作为参数的函数那么实参传递左值和右值都可以匹配。C11以后分别重载左值引⽤、const左值引⽤、右值引⽤作为形参的f函数那么实参是左值会匹配f(左值引⽤)实参是const左值会匹配f(const 左值引⽤)实参是右值会匹配f(右值引⽤)。在C中函数参数的匹配规则会根据参数类型左值引用、右值引用、普通参数等和传递的实参类型左值或右值有所不同。1. 普通参数对于普通参数非引用类型无论是左值还是右值都会通过拷贝构造函数或拷贝赋值运算符进行传递。这意味着传递的实参会被复制到函数的形参中。例如代码语言javascriptAI代码解释void func(int x) { // x 是普通参数实参会被复制到这里 } int main() { int a 10; func(a); // a 是左值会被复制到 x func(20); // 20 是右值也会被复制到 x }2. 左值引用参数左值引用参数只能绑定到左值。如果尝试将右值绑定到左值引用参数会导致编译错误。例如代码语言javascriptAI代码解释void func(int x) { // x 是左值引用参数 } int main() { int a 10; func(a); // 正确a 是左值 func(20); // 错误20 是右值 }3. 右值引用参数右值引用参数只能绑定到右值。如果尝试将左值绑定到右值引用参数会导致编译错误。例如代码语言javascriptAI代码解释void func(int x) { // x 是右值引用参数 } int main() { int a 10; func(a); // 错误a 是左值 func(20); // 正确20 是右值 }4. 万能引用C11引入了万能引用的概念它允许一个引用既可以绑定到左值也可以绑定到右值。通用引用通过模板参数和实现。例如代码语言javascriptAI代码解释template typename T void func(T x) { // x 是通用引用 } int main() { int a 10; func(a); // 正确a 是左值x 会退化为左值引用 func(20); // 正确20 是右值x 会保持为右值引用 }在通用引用中T的行为取决于模板参数T的具体类型。如果T是一个具体的类型如intT就是右值引用如果T是一个模板参数T就是通用引用。5. 参数匹配的优先级当函数重载时参数匹配的优先级规则如下完美转发如果函数模板使用了std::forward则会根据模板参数的实际类型进行完美转发。左值引用优先如果一个函数接受左值引用参数而另一个函数接受右值引用参数那么左值引用函数会优先匹配左值。右值引用优先如果一个函数接受右值引用参数而另一个函数接受左值引用参数那么右值引用函数会优先匹配右值。例如代码语言javascriptAI代码解释void func(int x) { std::cout lvalue reference std::endl; } void func(int x) { std::cout rvalue reference std::endl; } int main() { int a 10; func(a); // 输出 lvalue reference func(20); // 输出 rvalue reference }下面的代码能够总的说明参数匹配这一规则代码语言javascriptAI代码解释#includeiostream using namespace std; void f(int x) { std::cout 左值引⽤重载 f( x )\n; } void f(const int x) { std::cout 到 const 的左值引⽤重载 f( x )\n; } void f(int x) { std::cout 右值引⽤重载 f( x )\n; } int main() { int i 1; const int ci 2; f(i); // 调⽤ f(int) f(ci); // 调⽤ f(const int) f(3); // 调⽤ f(int)如果没有 f(int) 重载则会调⽤ f(const int) f(std::move(i)); // 调⽤ f(int) // 右值引⽤变量在⽤于表达式时是左值 int x 1; f(x); // 调⽤ f(int x) f(std::move(x)); // 调⽤ f(int x) return 0; }四.右值引⽤和移动语义的使⽤4.1回顾左值引用出现的意义左值引⽤主要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉同时还可以修改实参和修改返回对象的价值。左值引⽤已经解决⼤多数场景的拷⻉效率问题但是有些场景不能使⽤传左值引⽤返回C98中的解决⽅案只能是被迫使⽤输出型参数解决。那么C11以后这⾥可以使⽤右值引⽤做返回值解决吗显然是不可能的因为这⾥的本质是返回对象是⼀个局部对象函数结束这个对象就析构销毁了右值引⽤返回也⽆法概念对象已经析构销毁的事实。在下面代码中若是没有优化的情况vv在这里返回时要重新申请空间进行构造所花费的代价实际是特别的大的。在之前我们要解决这个问题就会多一个参数传过去一个vv。