当前位置:   article > 正文

【C/C++ 13】C++11高效特性

【C/C++ 13】C++11高效特性

目录

一、初始化列表

二、auto

三、decltype

四、可变参数列表

五、lambda表达式


C++11在C++98的基础上增添了许多特性,但是同时也使得C++程序的开发变得复杂繁琐,让众多开发者苦不堪言,于是我们需要从C++11新增舔的特性中选择一些能够提高开发效率的东西进行学习和应用。

一、初始化列表

在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扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型,使用初始化列表时,可添加等号(=),也可不添加,但是这样一来,C++代码就有些显得花里胡哨了,为了和C++98更兼容,建议在对列表进行初始化时都加上等号。

但是C++11的初始化列表在new表达式中非常方便,我们只在new表达式中使用C++11初始化列表即可。

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int* arr1 = new int[3]{ 0 };
  6. int* arr2 = new int[3]{ 1, 2, 3 };
  7. for (int i = 0; i < 3; ++i)
  8. cout << arr1[i] << ' ';
  9. // 0 0 0
  10. cout << endl;
  11. for (int i = 0; i < 3; ++i)
  12. cout << arr2[i] << ' ';
  13. // 1 2 3
  14. return 0;
  15. }
  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. auto il = { 10, 20, 30 };
  6. cout << typeid(il).name() << endl;
  7. // class std::initializer_list<int>
  8. return 0;
  9. }
  10. // std::initializer_list
  11. // This type is used to access the values in a C++ initialization list,
  12. // which is a list of elements of type const T.
  13. // std::initializer_list一般是作为构造函数的参数。
  14. // C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。
  15. // 也可以作为operator = 的参数,这样就可以用大括号赋值。
  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. int main()
  5. {
  6. // 初始化列表对STL容器初始化构造
  7. vector<int> arr = { 1, 2, 3 };
  8. for (int i = 0; i < arr.size(); ++i)
  9. cout << arr[i] << ' ';
  10. // 1 2 3
  11. return 0;
  12. }

二、auto

C++11中 auto 用于实现自动类型推断,常应用于迭代器范围for循环中。

  1. #include <iostream>
  2. #include <map>
  3. using namespace std;
  4. int main()
  5. {
  6. int i = 10;
  7. auto p = &i;
  8. cout << typeid(p).name() << endl; // int *
  9. map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"}, {"delete", "删除"} };
  10. //map<string, string>::iterator it = dict.begin();
  11. auto it = dict.begin(); // 迭代器类型
  12. while (it != dict.end())
  13. {
  14. cout << "<" << it->first << ", " << it->second << ">" << ' '; // delete, 删除
  15. ++it;
  16. }
  17. cout << endl;
  18. // 范围for循环
  19. for (const auto& e : dict)
  20. cout << "<" << e.first << ", " << e.second << ">" << ' '; // delete, 删除
  21. return 0;
  22. }

三、decltype

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

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. template<class T1, class T2>
  5. void F(T1 x, T2 y)
  6. {
  7. decltype(x * y) ret;
  8. cout << typeid(ret).name() << endl;
  9. }
  10. int main()
  11. {
  12. const int x = 1;
  13. double y = 2.5;
  14. decltype(x * y) ret;
  15. decltype(&x) p;
  16. cout << typeid(ret).name() << endl; // double
  17. cout << typeid(p).name() << endl; // int const *
  18. F(1, 'a'); // int
  19. return 0;
  20. }

四、可变参数列表

C++11的可变参数模板能够创建可以接受可变参数的函数模板和类模板,相比于C++98类模板只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。

  1. // Args是一个模板参数包,args是一个函数形参参数包
  2. // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
  3. template <class ...Args>
  4. void ShowList(Args... args)
  5. {}
  6. /*
  7. 上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,
  8. 它里面包含了0到N(N >= 0)个模版参数。我们无法直接获取参数包args中的每个参数的,
  9. 只能通过展开参数包的方式来获取参数包中的每个参数,
  10. 这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
  11. 由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。
  12. */
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. // 递归方式展开参数包
  5. template<class T>
  6. void ShowList(const T& value)
  7. {
  8. cout << value << endl;
  9. }
  10. template <class T, class ...Args>
  11. void ShowList(T value, Args... args)
  12. {
  13. cout << value << " ";
  14. ShowList(args...);
  15. }
  16. int main()
  17. {
  18. ShowList(1);
  19. ShowList(1, 'a');
  20. ShowList(1, 'a', string("test"));
  21. return 0;
  22. }
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. // 逗号表达式展开参数包
  5. template<class T>
  6. void PrintArgs(T t)
  7. {
  8. cout << t << ' ';
  9. }
  10. template <class ...Args>
  11. void ShowList(Args... args)
  12. {
  13. int arr[] = { (PrintArgs(args), 0)... };
  14. cout << endl;
  15. }
  16. int main()
  17. {
  18. ShowList(1);
  19. ShowList(1, 'a');
  20. ShowList(1, 'a', string("test"));
  21. return 0;
  22. }
  23. /*
  24. 这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的,
  25. PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。
  26. 这种就地展开参数包的方式实现的关键是逗号表达式,我们知道逗号表达式会按顺序执行逗号前面的表达式。
  27. expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,
  28. 先执行PrintArg(args),再得到逗号表达式的结果0。
  29. 同时还通过初始化列表来初始化一个变长数组,
  30. {(PrintArg(args), 0)...}将会展开成((PrintArg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... )
  31. 最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。
  32. 于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分PrintArg(args)打印出参数,
  33. 也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
  34. */

五、lambda

C++11的 lambda 表达式,实际上是一个匿名函数,熟练使用 lambda 表达式,可以极大地提升编程效率。

  1. // lambda表达式语法
  2. [capture-list](parameters)mutable->return-type{statement}
  3. // [capture-list]:捕捉列表,捕捉上下文中的变量供lambda使用
  4. // (parameters):参数列表
  5. // mutable:取消lambda函数的默认常性,使用该修饰符时参数列表不可省略
  6. // ->return-type:返回值类型,可省略由编译器推导
  7. // {statement}:函数体,除了可以使用参数列表的变量,还可以使用捕捉到的变量

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. int main()
  5. {
  6. // 最简单的lambda表达式
  7. auto func1 = [] {};
  8. func1();
  9. // 省略参数列表,返回值由编译器推导
  10. int a = 3, b = 4;
  11. auto func2 = [=] {cout << a + b << endl; };
  12. func2();
  13. auto func3 = [&](int c) {b = c + a; };
  14. func3(10);
  15. cout << "b = " << b << endl;
  16. cout << [=, &b](int c)->int {return b += a + c; }(10) << endl;
  17. auto func4 = [b](int a)mutable {b %= 10; return b + a; };
  18. cout << func4(5) << endl;
  19. return 0;
  20. }
  21. /*
  22. 捕获列表说明:
  23. 1. [var]:表示值传递方式捕捉变量var
  24. 2. [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  25. 3. [&var]:表示引用传递捕捉变量var
  26. 4. [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  27. 5. [this]:表示值传递方式捕捉当前的this指针
  28. 注意:
  29. 1. 父作用域指包含lambda函数的语句块
  30. 2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
  31. 3. 捕捉列表不允许变量重复传递,否则就会导致编译错误
  32. 4. lambda表达式之间不能相互赋值
  33. */

从原理上看,编译器对于lambda表示式的实现方式,就是将其作为仿函数实现的,而仿函数的实现原理就是对 operator() 进行重载。也就是说,如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。 

从代码编写角度来说,使用lambda表达式效率更高。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. class Salary
  5. {
  6. public:
  7. Salary(int money)
  8. : _money(money)
  9. {}
  10. int operator()(int month)
  11. {
  12. return _money * month;
  13. }
  14. private:
  15. int _money;
  16. };
  17. int main()
  18. {
  19. int money = 3000;
  20. int month = 5;
  21. Salary a(money);
  22. cout << a(month) << endl;
  23. auto func = [=] {return money * month; };
  24. cout << func() << endl;
  25. }

六、包装器

function 包装器也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. using namespace std;
  4. template<class F, class T>
  5. T UseF(F f, T t)
  6. {
  7. static int count = 0;
  8. cout << "count: " << ++count << endl;
  9. cout << "count: " << &count << endl;
  10. return f(t);
  11. }
  12. double func(double x)
  13. {
  14. return x / 2;
  15. }
  16. struct Func
  17. {
  18. double operator()(double x)
  19. {
  20. return x / 3;
  21. }
  22. };
  23. int main()
  24. {
  25. // 函数名
  26. cout << UseF(func, 11.11) << endl;
  27. // 函数对象
  28. cout << UseF(Func(), 11.11) << endl;
  29. // lambda表达式
  30. cout << UseF([](double x) {return x / 4; }, 11.11) << endl;
  31. return 0;
  32. }
  33. // 由运行结果可见,UseF函数模板实例化了三份。
  34. // 每出现一种新的函数定义,通过UseF调用就会使UseF实例化一个新的对象,这会导致效率低下。
  35. // 包装器的出现就是为了解决这个问题。
  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include <iostream>
  3. #include <functional>
  4. using namespace std;
  5. // 通过包装器提高模板函数的效率
  6. template<class F, class T>
  7. T UseF(F f, T x)
  8. {
  9. static int count = 0;
  10. cout << "count:" << ++count << endl;
  11. cout << "count:" << &count << endl;
  12. return f(x);
  13. }
  14. double func(double x)
  15. {
  16. return x / 2;
  17. }
  18. struct Func
  19. {
  20. double operator()(double x)
  21. {
  22. return x / 3;
  23. }
  24. };
  25. class Plus
  26. {
  27. public:
  28. static int plusi(int a)
  29. {
  30. return a + 1;
  31. }
  32. double plusd(double a)
  33. {
  34. return a + 1;
  35. }
  36. };
  37. int main()
  38. {
  39. // 函数名
  40. std::function<double(double)> f1 = func;
  41. cout << UseF(f1, 11.11) << endl;
  42. // 函数对象
  43. std::function<double(double)> f2 = Func();
  44. cout << UseF(f2, 11.11) << endl;
  45. // lambda表达式
  46. std::function<double(double)> f3 = [](double x) {return x / 4; };
  47. cout << UseF(f3, 11.11) << endl;
  48. // 类的成员函数
  49. std::function<double(Plus, double)> f4 = &Plus::plusd;
  50. cout << f4(Plus(), 11.11) << endl;
  51. return 0;
  52. }

std::bind 是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. // 使用举例
  3. #include <iostream>
  4. #include <functional>
  5. int Plus(int a, int b)
  6. {
  7. return a + b;
  8. }
  9. class Sub
  10. {
  11. public:
  12. int sub(int a, int b)
  13. {
  14. return a - b;
  15. }
  16. };
  17. int main()
  18. {
  19. //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
  20. std::function<int(int, int)> func1 = std::bind(Plus, std::placeholders::_1,
  21. std::placeholders::_2);
  22. //auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
  23. //func2的类型为 function<void(int, int, int)> 与func1类型一样
  24. //表示绑定函数 plus 的第一,二为: 1, 2
  25. auto func2 = std::bind(Plus, 1, 2);
  26. std::cout << func1(1, 2) << std::endl;
  27. std::cout << func2() << std::endl;
  28. Sub s;
  29. // 绑定成员函数
  30. std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,
  31. std::placeholders::_1, std::placeholders::_2);
  32. // 参数调换顺序
  33. std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,
  34. std::placeholders::_2, std::placeholders::_1);
  35. std::cout << func3(1, 2) << std::endl;
  36. std::cout << func4(1, 2) << std::endl;
  37. return 0;
  38. }

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

闽ICP备14008679号