当前位置:   article > 正文

【C++航海王:追寻罗杰的编程之路】C++11(三)

【C++航海王:追寻罗杰的编程之路】C++11(三)

目录

1 -> 相关文章

【C++航海王:追寻罗杰的编程之路】C++11(一)

【C++航海王:追寻罗杰的编程之路】C++11(二)

2 -> 新的类功能

3 -> 可变参数模版


1 -> 相关文章

【C++航海王:追寻罗杰的编程之路】C++11(一)

【C++航海王:追寻罗杰的编程之路】C++11(二)

2 -> 新的类功能

默认成员函数

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. // 以下代码在vs2019下才能演示体现上面的特性。
  5. class Person
  6. {
  7. public:
  8. Person(const char* name = "", int age = 0)
  9. :_name(name)
  10. , _age(age)
  11. {}
  12. /*Person(const Person& p)
  13. *: _name(p._name)
  14. , _age(p._age)
  15. {}
  16. Person& operator=(const Person& p)
  17. {
  18. if (this != &p)
  19. {
  20. _name = p._name;
  21. _age = p._age;
  22. }
  23. return *this;
  24. }
  25. ~Person()
  26. {}*/
  27. private:
  28. fyd::string _name;
  29. int _age;
  30. };
  31. int main()
  32. {
  33. Person s1;
  34. Person s2 = s1;
  35. Person s3 = std::move(s1);
  36. Person s4;
  37. s4 = std::move(s2);
  38. return 0;
  39. }

类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。

强制生成默认函数的关键字default

C++11可以更好的控制要使用的默认函数。假设要使用某个函数,但因为一些原因这个函数没有默认生成。比如:提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

  1. class Person
  2. {
  3. public:
  4. Person(const char* name = "", int age = 0)
  5. :_name(name)
  6. , _age(age)
  7. {}
  8. Person(const Person& p)
  9. :_name(p._name)
  10. , _age(p._age)
  11. {}
  12. Person(Person&& p) = default;
  13. private:
  14. fyd::string _name;
  15. int _age;
  16. };
  17. int main()
  18. {
  19. Person s1;
  20. Person s2 = s1;
  21. Person s3 = std::move(s1);
  22. return 0;
  23. }

禁止生成默认函数的关键字delete

如果想要限制某些默认函数的生成,在C++98中,是将该函数设置成private,并且只声明,这样只要其他人想要调用就会报错。在C++11中更加简单,只需在该函数声明加上 = delete即可,该语法指示编译器不生成对应函数的默认版本,称 = delete修饰的函数为删除函数。

  1. class Person
  2. {
  3. public:
  4. Person(const char* name = "", int age = 0)
  5. :_name(name)
  6. , _age(age)
  7. {}
  8. Person(const Person& p) = delete;
  9. private:
  10. fyd::string _name;
  11. int _age;
  12. };
  13. int main()
  14. {
  15. Person s1;
  16. Person s2 = s1;
  17. Person s3 = std::move(s1);
  18. return 0;
  19. }

继承和多态中的final与override关键字 

3 -> 可变参数模版

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。

下面就是一个基本可变参数的函数模板

  1. // Args是一个模板参数包,args是一个函数形参参数包
  2. // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
  3. template <class ...Args>
  4. void ShowList(Args... args)
  5. {}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数
包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,
只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特
点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变
参数,所以我们的用一些奇招来一一获取参数包的值。

递归函数方式展开参数包

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. // 递归终止函数
  5. template <class T>
  6. void ShowList(const T& t)
  7. {
  8. cout << t << endl;
  9. }
  10. // 展开函数
  11. template <class T, class ...Args>
  12. void ShowList(T value, Args... args)
  13. {
  14. cout << value << " ";
  15. ShowList(args...);
  16. }
  17. int main()
  18. {
  19. ShowList(1);
  20. ShowList(1, 'A');
  21. ShowList(1, 'A', std::string("sort"));
  22. return 0;
  23. }

逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式
实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列
表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0), 
(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在
数组构造的过程展开参数包。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. template <class T>
  5. void PrintArg(T t)
  6. {
  7. cout << t << " ";
  8. }
  9. //展开函数
  10. template <class ...Args>
  11. void ShowList(Args... args)
  12. {
  13. int arr[] = { (PrintArg(args), 0)... };
  14. cout << endl;
  15. }
  16. int main()
  17. {
  18. ShowList(1);
  19. ShowList(1, 'A');
  20. ShowList(1, 'A', std::string("sort"));
  21. return 0;
  22. }

STL容器中的emplace相关接口函数:

std::vector::emplace_back

std::list::emplace_back

  1. template <class... Args>
  2. void emplace_back(Args&&... args);

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和
emplace系列接口的优势到底在哪里呢?

  1. int main()
  2. {
  3. std::list< std::pair<int, char> > mylist;
  4. // emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
  5. // 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
  6. mylist.emplace_back(10, 'a');
  7. mylist.emplace_back(20, 'b');
  8. mylist.emplace_back(make_pair(30, 'c'));
  9. mylist.push_back(make_pair(40, 'd'));
  10. mylist.push_back({ 50, 'e' });
  11. for (auto& e : mylist)
  12. cout << e.first << ":" << e.second << endl;
  13. return 0;
  14. }
  1. int main()
  2. {
  3. // 下面我们试一下带有拷贝构造和移动构造的fyd::string,再试试呢
  4. // 我们会发现其实差别也不到,emplace_back是直接构造了,push_back
  5. std::list< std::pair<int, fyd::string> > mylist;
  6. mylist.emplace_back(10, "sort");
  7. mylist.emplace_back(make_pair(20, "sort"));
  8. mylist.push_back(make_pair(30, "sort"));
  9. mylist.push_back({ 40, "sort" });
  10. return 0;
  11. }

感谢各位大佬支持!!!

互三啦!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/503749
推荐阅读
相关标签
  

闽ICP备14008679号