当前位置:   article > 正文

【C++进阶学习】第十一弹——C++11(上)——右值引用和移动语义

【C++进阶学习】第十一弹——C++11(上)——右值引用和移动语义

前言:

前面我们已经将C++的重点语法讲的大差不差了,但是在C++11版本之后,又出来了很多新的语法,其中有一些作用还是非常大的,今天我们就先来学习其中一个很重要的点——右值引用以及它所扩展的移动定义

目录

一、左值引用和右值引用

左值引用 

右值引用

二、左值引用与右值引用的比较

三、右值引用的使用

移动构造

移动赋值

四、总结


一、左值引用和右值引用

左值引用 

左值引用是最常见的引用类型,通常用于绑定到一个左值。左值是一个具有名称的对象,可以取地址,通常出现在赋值操作符的左边。(简单的说,能取地址的就是左值)

语法:

类型 &引用名 = 左值;

示例:

  1. int a = 10;
  2. int &refA = a; // refA是一个左值引用,绑定到左值a

特点:

  • 左值引用必须初始化,并且只能绑定到左值。
  • 左值引用可以修改绑定的对象。

右值引用

右值引用是C++11引入的新特性,用于绑定到一个右值。右值是一个临时对象,通常没有名称,不能取地址,通常出现在赋值操作符的右边。(右值不能取地址,比如常量)

语法:

类型 &&引用名 = 右值;

示例:

int &&refB = 20;  // refB是一个右值引用,绑定到右值20

特点:

  • 右值引用必须初始化,并且只能绑定到右值。
  • 右值引用主要用于实现移动语义和完美转发。

有一个需要强调的是,常变量虽然也属于常量,但是它可以取地址,所以它属于左值

二、左值引用与右值引用的比较

左值引用:

1. 左值引用只能引用左值,不能引用右值。
2. 但是const左值引用既可引用左值,也可引用右值
  1. int main()
  2. {
  3.    // 左值引用只能引用左值,不能引用右值。
  4.    int a = 10;
  5.    int& ra1 = a;   // ra为a的别名
  6.    //int& ra2 = 10;   // 编译失败,因为10是右值
  7.    // const左值引用既可引用左值,也可引用右值。
  8.    const int& ra3 = 10;
  9.    const int& ra4 = a;
  10.    return 0
  11. }

右值引用:

1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以move以后的左值。
  1. int main()
  2. {
  3. // 右值引用只能右值,不能引用左值。
  4. int&& r1 = 10;
  5. // error C2440: “初始化”: 无法从“int”转换为“int &&”
  6. // message : 无法将左值绑定到右值引用
  7. int a = 10;
  8. int&& r2 = a;
  9. // 右值引用可以引用move以后的左值
  10. int&& r3 = std::move(a);
  11. return 0;
  12. }

三、右值引用的使用

在上面我们也已经讲到了,左值引用及可以引用左值,又可以引用右值,那么C++11为什么还要设计右值引用呢?下面我们来看一下原因。

我们借助string类来讲解

先来看一下下面所出现的所有代码,可以先思考看看思考思考

  1. namespace zda
  2. {
  3. class string
  4. {
  5. public:
  6. typedef char* iterator;
  7. iterator begin()
  8. {
  9. return _str;
  10. }
  11. iterator end()
  12. {
  13. return _str + _size;
  14. }
  15. string(const char* str = "")
  16. :_size(strlen(str))
  17. , _capacity(_size)
  18. {
  19. //cout << "string(char* str)" << endl;
  20. _str = new char[_capacity + 1];
  21. strcpy(_str, str);
  22. }
  23. // s1.swap(s2)
  24. void swap(string& s)
  25. {
  26. ::swap(_str, s._str);
  27. ::swap(_size, s._size);
  28. ::swap(_capacity, s._capacity);
  29. }
  30. // 拷贝构造
  31. string(const string& s)
  32. :_str(nullptr)
  33. {
  34. cout << "string(const string& s) -- 深拷贝" << endl;
  35. string tmp(s._str);
  36. swap(tmp);
  37. }
  38. // 赋值重载
  39. string& operator=(const string& s)
  40. {
  41. cout << "string& operator=(string s) -- 深拷贝" << endl;
  42. string tmp(s);
  43. swap(tmp);
  44. return *this;
  45. }
  46. // 移动构造
  47. string(string&& s) //右值引用
  48. :_str(nullptr)
  49. , _size(0)
  50. , _capacity(0)
  51. {
  52. cout << "string(string&& s) -- 移动语义" << endl;
  53. swap(s);
  54. }
  55. // 移动赋值
  56. string& operator=(string&& s) //右值引用
  57. {
  58. cout << "string& operator=(string&& s) -- 移动语义" << endl;
  59. swap(s);
  60. return *this;
  61. }
  62. ~string()
  63. {
  64. delete[] _str;
  65. _str = nullptr;
  66. }
  67. char& operator[](size_t pos)
  68. {
  69. assert(pos < _size);
  70. return _str[pos];
  71. }
  72. void reserve(size_t n)
  73. {
  74. if (n > _capacity)
  75. {
  76. char* tmp = new char[n + 1];
  77. strcpy(tmp, _str);
  78. delete[] _str;
  79. _str = tmp;
  80. _capacity = n;
  81. }
  82. }
  83. void push_back(char ch)
  84. {
  85. if (_size >= _capacity)
  86. {
  87. size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
  88. reserve(newcapacity);
  89. }
  90. _str[_size] = ch;
  91. ++_size;
  92. _str[_size] = '\0';
  93. }
  94. //string operator+=(char ch)
  95. string& operator+=(char ch)
  96. {
  97. push_back(ch);
  98. return *this;
  99. }
  100. const char* c_str() const
  101. {
  102. return _str;
  103. }
  104. private:
  105. char* _str;
  106. size_t _size;
  107. size_t _capacity; // 不包含最后做标识的\0
  108. };
  109. string to_string(int value)
  110. {
  111. bool flag = true;
  112. if (value < 0)
  113. {
  114. flag = false;
  115. value = 0 - value;
  116. }
  117. string str;
  118. while (value > 0)
  119. {
  120. int x = value % 10;
  121. value /= 10;
  122. str += ('0' + x);
  123. }
  124. if (flag == false)
  125. {
  126. str += '-';
  127. }
  128. std::reverse(str.begin(), str.end());
  129. return str;
  130. }
  131. }

左值引用使用场景:

  1. void func1(bit::string s)
  2. {}
  3. void func2(const bit::string& s)
  4. {}
  5. int main()
  6. {
  7. bit::string s1("hello world");
  8. // func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
  9. func1(s1);
  10. func2(s1);
  11. // string operator+=(char ch) 传值返回存在深拷贝
  12. // string& operator+=(char ch) 传左值引用没有拷贝提高了效率
  13. s1 += '!';
  14. return 0;
  15. }

左值引用短板:

当函数返回对象为临时变量的时候,左值引用就派不上用场了,就只能传值返回,就需要拷贝至少一次(老一点的编译器为两次)

右值引用和移动语义:
对于上面这种问题,我们就可以通过右值引用和移动语义来实现

移动构造

移动构造的本质就是将参数的右值窃取过来,占为己有,这样它就不用再深度拷贝了,所以叫做移动构造
  1. // 移动构造
  2. string(string&& s)
  3. :_str(nullptr)
  4. ,_size(0)
  5. ,_capacity(0)
  6. {
  7. cout << "string(string&& s) -- 移动语义" << endl;
  8. swap(s);
  9. }
  10. int main()
  11. {
  12. zda::string ret2 = bit::to_string(-1234);
  13. return 0;
  14. }

当返回值是右值时,因为移动构造并没有开辟空间进行深拷贝,所以效率就会更高

需要注意的是,当拷贝构造和移动构造同时存在时,编译器默认的也会调用移动构造,因为编译器会默认调用效率更高的函数

移动赋值

  1. // 移动赋值
  2. string& operator=(string&& s)
  3. {
  4. cout << "string& operator=(string&& s) -- 移动语义" << endl;
  5. swap(s);
  6. return *this;
  7. }
  8. int main()
  9. {
  10. zda::string ret1;
  11. ret1 = zda::to_string(1234);
  12. return 0;
  13. }
  14. // 运行结果:
  15. // string(string&& s) -- 移动语义
  16. // string& operator=(string&& s) -- 移动语义

这里运行后发现,调用了一次移动构造和一次移动赋值,因为这里的ret1是一个已经存在的对象,用它来接受函数返回值的时候编译器就无法再优化了,所以会在移动构造后创建一个临时变量,且这个临时变量会被编译器识别为右值,从而调用移动赋值

四、总结

上面我们就简单的先提了一下右值引用的应用:移动语义,下一篇我们再重点讲解一下右值引用的另一个重点语法:完美挥发

感谢各位大佬观看,创作不易,还请各位大佬点赞支持一下!!!
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/1013136
推荐阅读
相关标签
  

闽ICP备14008679号