• C++11的更新内容--左值引用--1114


    1 初始化相关

    1.1 {}初始化

    C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型。

    1. struct Point
    2. {
    3. int _x;
    4. int _y;
    5. };
    6. class Date
    7. {
    8. public:
    9. Date(int year, int month, int day)
    10. :_year(year)
    11. ,_month(month)
    12. ,_day(day)
    13. {
    14. cout << "Date(int year, int month, int day)" << endl;
    15. }
    16. private:
    17. int _year;
    18. int _month;
    19. int _day;
    20. };
    21. int main()
    22. {
    23. //内置类型比如int
    24. int x1 = 1;
    25. int x2={ 2 };
    26. //数组
    27. int array1[]{ 1, 2, 3, 4, 5 };
    28. int array2[5]{ 0 };
    29. //自定义类型
    30. Point p{ 1, 2 };
    31. Date d1(2022,11,14);//用构造函数初始化
    32. Date d2{2022,11,14};//列表初始化
    33. // C++11中列表初始化也可以适用于new表达式中
    34. int* pa = new int[4]{ 0 };
    35. return 0;
    36. }

    1.2 std::initializer_list

    C++11中对{1,3,4}这样的列表定义了一个新的类型  --  initializer_list

    auto il = { 10, 20, 30 };

    cout << typeid(il).name() << endl;

     并在库中的容器的里支持了用initializer_list支持的构造函数。vector、list、map等都可以用它进行初始化。

    vector v = { 1,2,3,4 };

    list lt = { 1,2 };

    map dict = { {"sort", "排序"}, {"insert", "插入"} };

    自定义类型可以支持多个对象初始化,只需要增加initializer_list类型的构造函数即可

    2 声明

    2.1 auto

    自动推断类型。

    auto不能推导函数参数的类型,因为在函数编译阶段,还没有传递参数,就无法推演出形参的实际类型。

    C+11中已经去除了auto声明自动类型变量的功能,只可以用来进行变量类型推导。

    2.2 decltype

    关键字decltype将变量的类型声明为表达式指定的类型。

    1. int main()
    2. {
    3. const int x = 1;
    4. double y = 2.2;
    5. decltype(x * y) ret; // ret的类型是double
    6. decltype(&x) p; // p的类型是int*
    7. cout << typeid(ret).name() << endl;
    8. cout << typeid(p).name() << endl;
    9. return 0;
    10. }

    2.3 nullptr

    3 新增容器

    array --->静态数组

    forward_list --->单链表

    unordered_map --->哈希表

    unordered_set --->哈希表

    4 右值引用和移动语义

    4.1 左值和右值

    左值:可以取它的地址的就是左值。(左值除了被const修饰的 其余都可以修改)

    右值:不能出现在赋值运算符左边的,不能被取地址的。

    例如:字面常量、表达式返回值、函数返回值等。        

                    10          x+y        func(x,y)

     4.2 右值引用

    1. int main()
    2. {
    3. 10;
    4. x + y;
    5. Func(x, y);
    6. int&& rr1 = 10;
    7. double&& rr2 = x + y;
    8. double&& rr3 = Func(x, y);
    9. }

     tip:右值不能被获取地址,但是一旦被引用之后,就可以通过对引用的取地址/修改来影响右值。但这不是重点。

    无论左值引用还是右值引用,都是给对象取别名。

    4.3 左值引用和右值引用的比较

    左值引用只能引用左值,不能引用右值。但左值引用加了const后就可以引用右值了。

    const int& ra3 = 10;

    右值引用只能引用右值,不能引用左值。但是可以引用move后的左值。

    int&& r3 = std::move(a);

     4.4右值引用的使用场景

    之所以使用左值引用,目的是:

    1、减少函数参数调用以及做返回值时的拷贝构造,以提高效率。

    2、做输出型参数,修改返回对象。

    短板:但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回

    在我们以前的实现中,当我的返回值类型非常复杂时,比如vector> 或者是 红黑树、哈希表时,消耗仍然大。既然我们这个临时变量马上就要析构,C++11对于此方面进行了右值引用的优化方案。

    5.移动构造

    拿一个string类进行说明。主要关注对象是移动构造、和移动拷贝。

    1. // 拷贝构造
    2. string(const string& s)
    3. :_str(nullptr)
    4. {
    5. cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;
    6. string tmp(s._str);
    7. swap(s);
    8. }
    9. // 移动构造
    10. string(string&& s)
    11. :_str(nullptr)
    12. , _size(0)
    13. , _capacity(0)
    14. {
    15. cout << "string(string&& s) -- 资源转移" << endl;
    16. swap(s);
    17. }

    拷贝构造传入的参数是引用类型,以后还需要继续使用。所以不能直接交换,需要创建一个对象,用这个对象进行交换。

    既然局部对象拷贝后,本来就需要销毁。那还为什么要创建一个新的临时变量以完成交换呢?我们直接使用这个局部对象进行交换不就好了吗?

    如果实现了移动构造,在局部对象即将出作用域的时候,就会被识别称为将亡值,从而调用移动构造,减少了一次创建对象并拷贝的过程。

    1. namespace chy
    2. {
    3. class string
    4. {
    5. public:
    6. // 拷贝赋值
    7. string& operator=(const string& s)
    8. {
    9. cout << "string& operator=(string s) -- 拷贝赋值(深拷贝)" << endl;
    10. string tmp(s);
    11. swap(tmp);
    12. return *this;
    13. }
    14. // 移动赋值
    15. string& operator=(string&& s)
    16. {
    17. cout << "string& operator=(string s) -- 移动赋值(资源移动)" << endl;
    18. swap(s);
    19. return *this;
    20. }
    21. void swap(string& s)
    22. {
    23. ::swap(_str, s._str);
    24. ::swap(_size, s._size);
    25. ::swap(_capacity, s._capacity);
    26. }
    27. string to_string(int value)
    28. {
    29. bool flag = true;
    30. if (value < 0)
    31. {
    32. flag = false;
    33. value = 0 - value;
    34. }
    35. string str;
    36. while (value > 0)
    37. {
    38. int x = value % 10;
    39. value /= 10;
    40. str += ('0' + x);
    41. }
    42. if (flag == false)
    43. {
    44. str += '-';
    45. }
    46. std::reverse(str.begin(), str.end());
    47. return str;
    48. }
    49. private:
    50. char* _str;
    51. size_t _size;
    52. size_t _capacity;
    53. };
    54. }

    移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

    当我们调用to_string()函数时,编译器会将其的返回值识别成右值,从而调用类型最匹配的移动拷贝。如果没实现移动构造和移动赋值,那依然走的是深拷贝。

    1. int main()
    2. {
    3. chy::string ret;
    4. ret=to_string(-3456); //移动赋值
    5. chy::string ret2=chy::to_string(-1234);//移动拷贝
    6. return 0;
    7. }

     一些例子

    1. int main()
    2. {
    3. chy::string str1("hello");//拷贝构造
    4. chy::string str2(str1); // 拷贝构造
    5. chy::string str3(move(str1)); // 移动构造
    6. std::string s1("hello world");//拷贝构造
    7. std::string s2(s1); // 拷贝构造
    8. // std::string s3(s1+s2);
    9. std::string s3 = s1 + s2; // 移动构造
    10. std::string s4 = move(s1);//移动构造
    11. return 0;
    12. }
  • 相关阅读:
    17. 机器学习 - 随机森林
    C++前缀和算法:生成数组原理、源码及测试用例
    Java的stream流多个字段排序
    Java 判断两个Long类型是否相等
    c 查询v4l2 各种参数
    盈科视控荣获创响中国大赛第四名
    【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏1(附项目源码)
    竞赛 深度学习YOLOv5车辆颜色识别检测 - python opencv
    机器学习【KNN案例、API、总结】
    java锁升级
  • 原文地址:https://blog.csdn.net/qq_68741368/article/details/127866018