您的位置:首页 > 新闻 > 资讯 > 东莞做网站设计制作_网络营销是什么大类_营销培训课程内容_宁波网络推广优化公司

东莞做网站设计制作_网络营销是什么大类_营销培训课程内容_宁波网络推广优化公司

2025/10/16 21:42:43 来源:https://blog.csdn.net/m0_74626010/article/details/143372347  浏览:    关键词:东莞做网站设计制作_网络营销是什么大类_营销培训课程内容_宁波网络推广优化公司
东莞做网站设计制作_网络营销是什么大类_营销培训课程内容_宁波网络推广优化公司

一、右值引用的定义

   1、什么是左值和左值引用?

        左值指的是可以出现在等号左边可以被赋值(非const),可以取地址的值

        左值引用就是左值的引用,给左值取别名。(int& lr = a)

   2、什么是右值和右值引用?

        右值指的是不能出现在等号左边不能被赋值不能取地址的值

        右值引用就是右值的引用,给右值取别名。(int&& rr = 10)

   3、区别

        ①、不能以 可不可以修改,判断左值和右值。

                因为左值 const 也不能修改。

        ②、不能以 在等号左右,判断左值和右值。

                因为左值可以出现在等号右边。(但右值不能出现在等号左边)

        ③、可以用 是否能取地址,判断左值和右值。

                因为左值可以取地址,但右值不行。

        ④、引用的本质是为了减少拷贝!

   4、示例 

// 返回左值引用
int& func1()
{int* a = new int;return *a;
}// 返回右值,因为有临时拷贝
int func2()
{int a = 10;return a;
}int main()
{int a = 0; // 左值int b = 2; // 左值int& ra = a; // 左值引用/*常见的右值*/// 10// a + b// func2()/*右值引用*/int&& rra = 10;int&& rrs = a + b;return 0;
}

   5、总结

        1、语法上,引用都是取别名,不开空间,左值引用给左值取别名。右值引用就是给右值取别名。

        2、底层,引用都是用指针实现的。左值引用存的是当前左值的地址。右值引用是把右值拷贝到栈上的一个临时空间,再存放这个临时空间的地址。

        3、非const 左值引用不能给右值取别名,但const 左值引用可以。

        4、 右值引用不能给左值取别名,但是左值 move 后,可以。

二、右值引用的作用

   1、左值引用解决的问题

        前提环境:VS2019。(VS2022编译器优化太强了,无法观察)

        ①、左值引用解决了传参效率的问题,减少了拷贝。

// 非引用传参,要进行拷贝
void func(string str)
{}// 引用传参,减少了拷贝
void func(const string& str)
{}

        ②、左值引用解决了全局对象返回的拷贝问题 (未解决局部对象返回的问题)

// 返回左值引用
int& func1()
{int* a = new int;return *a;
}

   2、右值引用解决的问题

        ①、解决局部对象返回的问题 

// Test 类就是包含了一个 char* 变量做的测试
class Test
{
public:Test(const char* str = nullptr){cout << "Test(const char* str = nullptr) -- 构造\n";// 将 str 拷贝进 _strif (str){int size = strlen(str);_str = new char[size + 1];int i = 0;for (i = 0; i < size; ++i){_str[i] = str[i];}_str[i] = '\0';}}void Swap(Test& t){swap(t._str, _str);}Test(const Test& t){cout << "左值 深拷贝 -- Test(const Test& t) -- 拷贝构造\n";// 将 str 拷贝进 _strif (t._str){cout << "深拷贝\n";int size = strlen(t._str);_str = new char[size + 1];int i = 0;for (i = 0; i < size; ++i){_str[i] = t._str[i];}_str[i] = '\0';}}Test& operator=(const Test& t){cout << "左值 深拷贝 -- Test& operator=(const Test& t)\n";// 将 str 拷贝进 _strif (t._str){cout << "深拷贝\n";int size = strlen(t._str);delete _str;_str = new char[size + 1];int i = 0;for (i = 0; i < size; ++i){_str[i] = t._str[i];}_str[i] = '\0';}return *this;}/*Test(Test&& t){cout << "右值 移动拷贝 -- Test(Test&& t) -- 拷贝构造\n";Swap(t);}*/// t1 = t2;/*Test& operator=(Test&& t){cout << "右值 移动赋值 -- Test& operator=(Test&& t)\n";Swap(t);return *this;}*/~Test(){}char* _str = nullptr;
};Test func1()
{const char* str = "abc";Test t(str);return t;
}int main()
{Test t = func1();return 0;
}

        我们可以将要释放的右值称作将亡值,反正它都要被释放了,我们就可以通过右值引用(移动拷贝)将它的资源拿出来,就可以通过较低的代价,完成拷贝。 

  有人可能会有疑问:右值不能修改,为什么移动构造 Test(Test&& t) 中的 t 可以修改?

       这是因为 (四.2) 右值被右值引用后,右值引用变量的属性是左值,因此,t 可以被修改.

        上述代码,在类 Test 中,有需要深拷贝的成员 char* _str;

        如果没有移动构造,就需要先构造,再拷贝构造

        而有了移动构造,就可以先构造,再移动构造

        拷贝构造需要深拷贝,而移动构造只需要交换资源即可! 

        因为左值不能掠夺资源,而右值可以。

        注释掉移动构造:

        有移动构造:


        拷贝赋值:

Test& operator=(const Test& t){cout << "左值 深拷贝 -- Test& operator=(const Test& t)\n";if (t._str){cout << "深拷贝\n";int size = strlen(t._str);delete _str;_str = new char[size + 1];int i = 0;for (i = 0; i < size; ++i){_str[i] = t._str[i];}_str[i] = '\0';}return *this;}
Test func1()
{const char* str = "abc";Test t(str);return t;
}int main()
{Test t;t = func1();return 0;
}

         在没有移动拷贝和移动赋值的情况下:

                要进行两次深拷贝。因为分两行写,编译器无法识别,优化。

         移动赋值: 

Test& operator=(Test&& t)
{cout << "右值 移动赋值 -- Test& operator=(Test&& t)\n";Swap(t);return *this;
}

         在有移动拷贝和移动赋值的情况下:

                不用进行深拷贝。效率大大提高!

 三、右值概念的细分

        1、纯右值(内置类型的右值)

                如:a + b、10

        2、将亡值(自定义类型的右值)

                如:匿名对象、传值返回函数 (如上例)

        C++ 提供右值引用,就是为了能够区分左值和右值,左值的资源不能乱动,但将亡值我们可以将它的资源进行利用。

        3、总结

        浅拷贝的类不需要移动构造(右值引用)

        只有要深拷贝的类才需要移动构造(右值引用)。 

四、有关右值引用的相关知识

        1、move 函数 

       move(leftval)函数的作用是将左值参数变为右值返回,move 以后左值还是左值,只是 move 的返回值是右值。注意:如果 move 后的左值的资源被掠夺,那么该左值的资源也会消失。  

int main()
{// Test 类就是包含了一个 char* 变量做的测试// 有关 Test 类的具体定义在上面哦Test t1("aaaa");Test t2(move(t1));return 0;
}


         2、右值被右值引用以后,右值引用变量的属性是左值。 

Test func1()
{const char* str = "abc";Test t(str);return t;
}int main()
{Test&& r = func1(); // r 是左值Test t(r);return 0;
}

        我们可以看到,上述代码中,如果 r 是右值,那么 main() 函数内的 t 应该调移动构造,但它实际上调的是拷贝构造,因此得出结论,r 是左值。


        3、万能引用&完美转发

        在模版中,为了简化工作,让我们可以不写两份函数区分左值和右值,参数部分直接写为 T&&,模版会自动推演这是左值还是右值 (左值、右值、const 左值、const 右值 都可以匹配),这被称为万能引用。

        它也被称作引用折叠,左值就推演为 (T&& -> T&),右值就不变 (T&& -> T&&)。

template<class T>
void PerfectForward(T&& t)
{// 传左值,t就是左值引用// 传右值,t就是右值引用
}

        我们知道,t 是左值,如果在 PerfectForward 函数内部还想区分左值和右值怎么办呢?

        那就需要完美转发。 

        完美转发:forward<T>(t) 在传参过程中保持了 t 的原生类型属性。 

void func(int& x)
{cout << "func(int& x)" << endl;
}void func(const int& x)
{cout << "func(const int& x)" << endl;
}void func(int&& x)
{cout << "func(int&& x)" << endl;
}void func(const int&& x)
{cout << "func(const int&& x)" << endl;
}template<class T>
void PerfectForward(T&& t)
{// 传左值,t就是左值引用// 传右值,t就是右值引用// func(t);  --> 未使用完美转发func(forward<T>(t)); // ---> 完美转发
}int main()
{PerfectForward(10); // 右值int a = 10;int& ra = a;PerfectForward(ra); // 左值引用const int& cra = a;PerfectForward(cra);// const 左值引用return 0;
}

        未使用完美转发,全是左值引用。

        使用完美转发:保持了原生类型属性

         感谢观看♪(・ω・)ノ

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com