• C++ 左值引用


    1.左右值定义

    c++11引入了右值引用。左值与右值是C语言中的概念,但C标准并没有给出严格的区分方式,一般认为:可以放在=左边的,或者能 够取地址的称为左值,只能放在=右边的,或者不能取地址的称为右值,但是也不一定完全正确。

    关于左值与右值的区分不是很好区分,一般认为:

    1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。

    2. const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是 const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间), C++11认为其是左值。

    3. 如果表达式的运行结果是一个临时变量或者对象,认为是右值。(字面常量,返回值(传值返回))

    4. 如果表达式运行结果或单个变量是一个引用则认为是左值。 (表达式,变量名等)

    总结:

    1. 不能简单地通过能否放在=左侧右侧或者取地址来判断左值或者右值,要根据表达式结果或变量的性质判断

    2. 能得到引用的表达式一定能够作为引用,否则就用常引用。 C++11对右值进行了严格的区分:

    C语言中的纯右值,比如:a+b, 100

    将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。

    2.左右值引用比较

    一般来说左值引用不能引用右值,右值引用也不能引用左值。

    1. const int& a = 10;//加const左值引用可以引用右值
    2. int b = 5;
    3. int&& c = move(b);//用move函数可以将左值转换成右值引用

    3. 移动拷贝

    C++98中,拷贝构造都是深拷贝,在某些情况下就会使效率变得特别低,甚至崩溃的情况发生。在这种情形下,右值引用的作用就体现了出来。接下来模拟实现下string容器

    1. namespace wzz
    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. , _size(0)
    34. , _capacity(0)
    35. {
    36. cout << "string(const string& s) -- 深拷贝" << endl;
    37. string tmp(s._str);
    38. swap(tmp);
    39. }
    40. // 赋值重载
    41. string& operator=(const string& s)
    42. {
    43. cout << "string& operator=(string s) -- 深拷贝" << endl;
    44. string tmp(s);
    45. swap(tmp);
    46. return *this;
    47. }
    48. ~string()
    49. {
    50. delete[] _str;
    51. _str = nullptr;
    52. }
    53. char& operator[](size_t pos)
    54. {
    55. assert(pos < _size);
    56. return _str[pos];
    57. }
    58. void reserve(size_t n)
    59. {
    60. if (n > _capacity)
    61. {
    62. char* tmp = new char[n + 1];
    63. strcpy(tmp, _str);
    64. delete[] _str;
    65. _str = tmp;
    66. _capacity = n;
    67. }
    68. }
    69. void push_back(char ch)
    70. {
    71. if (_size >= _capacity)
    72. {
    73. size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
    74. reserve(newcapacity);
    75. }
    76. _str[_size] = ch;
    77. ++_size;
    78. _str[_size] = '\0';
    79. }
    80. //string operator+=(char ch)
    81. string& operator+=(char ch)
    82. {
    83. push_back(ch);
    84. return *this;
    85. }
    86. const char* c_str() const
    87. {
    88. return _str;
    89. }
    90. private:
    91. char* _str;
    92. size_t _size;
    93. size_t _capacity; // 不包含最后做标识的\0
    94. };
    95. }
    96. wzz::string operator+(const bit::string& s, char ch)
    97. {
    98. bit::string ret(s);
    99. ret += ch;
    100. return ret;
    101. }
    102. wzz::string to_string(int value)
    103. {
    104. bool flag = true;
    105. if (value < 0)
    106. {
    107. flag = false;
    108. value = 0 - value;
    109. }
    110. bit::string str;
    111. while (value > 0)
    112. {
    113. int x = value % 10;
    114. value /= 10;
    115. str += ('0' + x);
    116. }
    117. if (flag == false)
    118. {
    119. str += '-';
    120. }
    121. std::reverse(str.begin(), str.end());
    122. return str;
    123. }

    在operator+中:ret在按照值返回时,必须创建一个临时对象,临时对象创建好之后,ret就被销毁了,最后使用返回的临时对象构造s,s构造好之后,临时对象就被销毁了。仔细观察会发现:ret、临时对象、s每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大,那能否对该种情况进行优化呢?

    C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。(就是将ret中的资源转移到临时对象中,再将临时对象中的资源转移到s对象中。不过在这里涉及编译器优化的问题,稍后看一下。)

    在C++11中如果需要实现移动语义,必须使用右值引用。对上述string类增加移动构造

    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. // 移动赋值
    11. string& operator=(string&& s)
    12. {
    13. cout << "string& operator=(string&& s) -- 资源转移" << endl;
    14. swap(s);
    15. return *this;
    16. }

    1. int main()
    2. {
    3. wzz::string s1("123456");
    4. wzz::string s2(s1);
    5. wzz::string s3(wzz::to_string(123);//这个调用的就是移动构造,因为to_string函数返回的是一个
    6. //临时对象,难么他就是右值。
    7. }

    4.编译器优化对比

    同一串代码,看看c++98和c++11编译器是如何优化的

    wzz::string s1 = wzz::to_string(123);

    C++98下的编译器优化

     C++11下的编译器优化

    在c++98下,优化后的效率也是会增加一半;c++11下优化后效率直线上升,没有了深拷贝的构造函数,移动构造的出现真的提升了效率。 

    1. wzz::string s1("123");
    2. //情况1
    3. wzz::string s2;
    4. s2 = bit::to_string(123);
    5. //情况2
    6. wzz::string s3 = bit::to_string(123);
    7. //这两种初始化是不一样的,情况1是先调用构造函数,再调用移动赋值
    8. //而情况2则是直接调用移动构造!
    9. //所以写出移动赋值函数也是很有必要的
    10. //因为赋值重载也是深拷贝

  • 相关阅读:
    使用PLM系统对电池企业有哪些好处
    【电商数仓】数仓搭建之DIM维度层(商品、优惠券、活动、地区、时间维度表)
    【博学谷学习记录】超强总结,用心分享|Shell运算符
    大数据时代下统计数据质量的影响因素
    小程序中如何查看会员的余额和变更记录
    【从零开始的Java开发】2-8-1 HTML入门:标签、表格、表单
    python爬虫—requests
    10.10作业
    CS61B sp21fall Project02 Gitlet
    Nuxt.js 生成sitemap站点地图文件
  • 原文地址:https://blog.csdn.net/weixin_60720508/article/details/126922373