• C++11的学习


    目录

    1. 列表初始化

     1.1  { } 初始化

     1.2 initilizer_list

    2. 声明

     2.1 auto

     2.2 decltype 

     2.3 nullptr

    3. 范围for循环

    4. STL容器的改变

    4.1 新增容器

    4.2 接口的变量

    5. 右值引用

    5.1 左值引用和右值引用

    5.2  右值引用的使用场景和意义

    5.3 右值引用和左值引用的转换

    6. lambda表达式

    6.1 lambda表达式


    1. 列表初始化

    1.1  { } 初始化

    在C++98中 { } 的作用是用来对数组或者结构体的初始化而设定的

    比如:

    1. struct Point
    2. {
    3. int _x;
    4. int _y;
    5. };
    6. int main()
    7. {
    8. int array1[] = { 1, 2, 3, 4, 5 };
    9. int array2[5] = { 0 };
    10. Point p = { 1, 2 };
    11. return 0;
    12. }

    在C++11中扩大了用大括号括起的列表使用的范围,不再仅仅是用来初始化数组和结构体,使其可以使用在所有的内置类型和自定义类型中,而实现这个功能就离不开一个容器 --- std:: initializer_list

    1.2 initilizer_list

     一般{ }内的元素,会被编译器识别成为initializer_list的参数,而initialize_list在C++11中一般是作为构造函数的新参数:

     我们可以发现在C++11中就多了initializer_list作为新参数来构造函数

    2. 声明

    2.1 auto

    在C++98中 auto只是作为一个存储类型的说明符,表示变量是局部自动存储类型,而在C++11中将auto 之前的用法舍弃,将其用于实现自动类型的推导。

    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. int a = 10;
    6. auto b = 20;
    7. auto* p = &a;
    8. cout << typeid(b).name() << endl;
    9. cout << typeid(p).name() << endl;
    10. return 0;
    11. }

    2.2 decltype

    我们可以使用typeid.name()来帮助我们推出变量的类型,那么我们可以使用这个来实现变量的声明吗?

    发现是不可以的,因为typeid.name()的返回值是一个string类型,那么我们就需要使用decltyp来帮助我们

    decltype是一个关键字,作用是将变量的类型声明作为指定的类型

     2.3 nullptr

    在C++11之前NULL被定义成字面值0,这样可能会带来一些问题,因为0既可以指为指针,又可以指为常量,出于安全的角度,C++11出台了nullptr,表示空指针。

     3. 范围for循环

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. vector<int> array = {1,2,3,4,5};
    7. for(auto &e : array)
    8. {
    9. cout<' ';
    10. }
    11. return 0;
    12. }

     范围for循环的底层结构其实也是迭代器来进行的实现。

    4. STL容器的改变

    4.1 新增容器

    在C++11中引入了两个新的容器分别是:1. unordered_map,2. unordered_set

    那么他们和map和set又有什么不同呢?

    1. 通过名称我们可以发现这两个容器都是无序的,也就是在遍历容器时,他们是不会进行排序2

    2. 实现方式不同,map、set的实现底层都是红黑树,而unordered_map、unordered_set的底层都是哈希函数

    4.2 接口的变量

     我们可以发现每个容器的构造函数都新增加了几个API,其中就包括我们之前所说的initializer_list和接下来我们要提到的右值引用

    5. 右值引用

    5.1 左值引用和右值引用

    传统的C++写法中就包括引用这个语法,在C++11中又新增加了右值引用这种写法,那么从C++11开始我们之前学过的引用都被称为左值引用,但是无论是之前学过的左值引用,还是新添加了右值引用都是对变量起别名。

    1. 什么是左值?什么是左值引用?

    左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋
    ,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左
    值,不能给他赋值,但是可以取它的地址。
    左值引用就是给左值的引用,给左值取别名。
     

    我们可以发现左值的最大特点就是: 可以取地址

    1. int main()
    2. {
    3. // 以下的p、b、c、*p都是左值
    4. int* p = new int(0);
    5. int b = 1;
    6. const int c = 2;
    7. // 以下几个是对上面左值的左值引用
    8. int*& rp = p;
    9. int& rb = b;
    10. const int& rc = c;
    11. int& pvalue = *p;
    12. return 0;
    13. }

    2. 什么是右值?什么是右值引用?

     右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引
    用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
    取地址。
    右值引用就是对右值的引用,给右值取别名。

    1. int main()
    2. {
    3. double x = 1.1, y = 2.2;
    4. // 以下几个都是常见的右值
    5. 10;
    6. x + y;
    7. fmin(x, y);
    8. // 以下几个都是对右值的右值引用
    9. int&& rr1 = 10;
    10. double&& rr2 = x + y;
    11. double&& rr3 = fmin(x, y);
    12. // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
    13. 10 = 1;
    14. x + y = 1;
    15. fmin(x, y) = 1;
    16. return 0;
    17. }

    由此我们可以发现左右引用的最大区别就是:是否能取地址

    5.2  右值引用的使用场景和意义

    那么我们既然已经有了左值引用,为什么还要设置一个右值引用?

    首先我们来思考一下,使用左值引用作为参数的特点:提高效率,不用进行临时拷贝

    那么左值引用有什么缺点呢?

    但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
    只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,
    传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)

    如果返回值类型是内置类型,那么消耗还小,那如果是自定义类型呢?如果是vector>类型呢,如果我们传值返回那么它的消耗是巨大的,并且我们浪费了局部对象中的资源,那么为了避免资源消耗过大,C++11中引入了右值引用和移动构造解决问题。

    什么是移动构造?

    移动构造的本质就是将参数右值的资源窃取过来,占位己有,以完成资源转移,那么如果之前进行的是深拷贝,则在移动构造下只需要将传入的参数资源占位己有即可,无序进行拷贝,移动构造就是窃取别人的资源。

    那么不仅仅有移动构造,还有移动赋值,其原理也和移动构造一样,将他人的资源窃取。

    5.3 右值引用和左值引用的转换

    那么右值引用只能引用右值,但右值引用不能引用左值吗?在某些特点的场景下我们需要右值引用左值,那么应该怎么做? 如果需要用右值引用一个左值,可以通过move来将一个左值转换称为右值,在C++11中实现了std::move这个函数,其作用仅仅是将左值转换成右值

    6. lambda表达式

    在C++98中如果想对一个数组集合进行排序的话,可以使用std::sort的方法

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. int main()
    7. {
    8. vector<int>a{ 1,2,3,7,6,4,3,2 };
    9. sort(a.begin(), a.end());
    10. //sort默认升序
    11. for (auto e : a) cout << e << ' ';
    12. cout<
    13. //如果要实现降序,则需要改变比较规则
    14. sort(a.begin(), a.end(),greater<int>());
    15. for (auto e : a) cout << e << ' ';
    16. return 0;
    17. }

    如果待排列的是自定义类型,那么我们如果需要排序的话,需要自己制定排序规则:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. struct Goods
    6. {
    7. string _name;
    8. double _price;
    9. int _evaluate;
    10. };
    11. struct lessname
    12. {
    13. bool operator()(const Goods a,const Goods b)
    14. {
    15. return a._name < b._name;
    16. }
    17. };
    18. struct lessprice
    19. {
    20. bool operator()(const Goods a,const Goods b)
    21. {
    22. return a._price < b._price;
    23. }
    24. };
    25. int main()
    26. {
    27. vector arr = {{"苹果",5,4},{"香蕉",6,3},{"桃子",3,2}};
    28. //如果我们按照名称来排序
    29. sort(arr.begin(),arr.end(),lessname());
    30. for(auto e:arr)
    31. {
    32. cout<<"名称"<' ';
    33. cout<<"价钱"<' ';
    34. cout<<"评价"<
    35. }
    36. cout<
    37. //按照价钱来排序
    38. sort(arr.begin(),arr.end(),lessprice());
    39. for(auto e:arr)
    40. {
    41. cout<<"名称"<' ';
    42. cout<<"价钱"<' ';
    43. cout<<"评价"<
    44. }
    45. return 0;
    46. }

     那么当我们在实现自定义类型,并需要对它进行各方面的排序时,我们都需要自己去实现一个仿函数,并且这种写法是复杂的且多余的。

    那么在C++11中,出现了lambda表达式来解决了这类问题。

    6.1 lambda表达式

    lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement
    }

    1. capture-list:也称作捕捉列表,该列表总是出现在lamdba表达式的起始位置,编译器会根据[ ]来判断该表达式是否是lamdba表达式,捕捉列表可以捕捉上下变量以供lamdba表达式的使用
    2. (parameters):也称作参数列表,与普通函数的参数一致,也可以不用传参。
    3. mutable :在默认情况下,传入lamdba表达式的参数都是const属性,那么mutable的作用就是将参数的const属性取消
    4. -> return-type:返回值类型,一般情况下,我们可以省略
    5. {statement}:函数体,在函数体内部可以使用捕捉列表中的所有变量

    那么对于数组集合的升序排序使用lamdba是怎么完成的呢?

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. int main()
    6. {
    7. vector<int> arr{1,4,2,5,3,4,1};
    8. //升序排列
    9. sort(arr.begin(),arr.end(),[](int x,int y){return x
    10. for(auto e:arr) cout<" ";
    11. return 0;
    12. }

     

  • 相关阅读:
    Docker镜像的制作(容器转镜像和DockerFile)
    rk3568 buildroot
    MyBatis-Plus快速入门
    Android音视频开发:AudioRecord录制音频
    内存函数(memcpy、memmove、memset、memcmp)你真的懂了吗?
    关于Git的入门教程(附GitHub和Gitee的使用方法)
    keras归一化与反归一化
    项目管理之财务基础
    《从0开始写一个微内核操作系统》4-关于mmu
    这五个bug,论文绘图时千万别碰!
  • 原文地址:https://blog.csdn.net/Rinki123456/article/details/127058819