当前位置:   article > 正文

c++11(三)

c++11(三)

一、可变参数

1、可变参数模板

c语言中的 scanf 和 printf 可以支持我们传入任意个数的参数,原理就是用了参数包。

  1. //可变参数包
  2. template<class ...Args>
  3. void Print(Args... args)
  4. {}

Args:模板参数

args:函数形参参数包

声明一个参数包:Args... args  

Args... 表示有0~n个类型,args 表示模板参数定义的形参参数包。

sizeof...(args):求参数包中参数个数

2、参数包在编译时的参数推导递归(方法一)

  1. //最后参数包个数是0时打印换行
  2. void _Printf()
  3. {
  4. cout << endl;
  5. }
  6. //子函数递归调用 把参数包中的值全部打印
  7. template<class T, class ...Args>
  8. void _Printf(T& t, Args... args)
  9. {
  10. cout << t << endl;
  11. _Printf(args...);
  12. }
  13. //主函数调用子函数传入参数包
  14. template<class ...Args>
  15. void Printf(Args... args)
  16. {
  17. _Printf(args...);
  18. }
  19. int main()
  20. {
  21. Printf(1, 2.2, "string");
  22. return 0;
  23. }

结果:

3、运用数组初始化展开参数包(方法二)

  1. template<class T>
  2. int Printf(T t)
  3. {
  4. cout << t << endl;
  5. return 0;
  6. }
  7. template<class ...Args>
  8. void showList(Args... args)
  9. {
  10. //定义数组是为了在构造时展开参数包
  11. int a[] = { Printf(args)... };//函数Printf推导参数包个数
  12. cout << endl;
  13. }
  14. int main()
  15. {
  16. showList(1, "hello", 'x');
  17. return 0;
  18. }

a 数组创建时,会根据 { } 中的参数进行初始化,可以在此直接将可变参数包展开,展开过程中就完成了参数的解析工作。

结果:

4、emplace系列函数

在stl容器中插入数据可以使用emplace系列函数,这样在多数情况下效率最高。

可以看到用的就是参数包形式的传参,在模板中 Args&&... args 是万能引用。

下面我们对比push_back和emplace_back来讨论emplace系列函数的优势

  1. int main()
  2. {
  3. std::list<bit::string> l;
  4. bit::string str1 = "Hello";
  5. bit::string str2 = "Hello";
  6. // 插入左值
  7. l.push_back(str1);
  8. l.emplace_back(str2);
  9. cout << endl;
  10. // 插入 move 出来的右值
  11. l.push_back(move(str1));
  12. l.emplace_back(move(str2));
  13. cout << endl;
  14. return 0;
  15. }

结论1:参数是左值和move得到的右值,两者并无区别。

  1. int main()
  2. {
  3. // 插入纯右值
  4. l.push_back("World");
  5. l.emplace_back("World");
  6. return 0;
  7. }

emplace_back函数直接都没有移动构造,emplace 系列函数可以直接将纯右值作为参数传递,传递途中不展开参数包,直到构造函数才把参数包展开,体现可变参数包 的优势(直接传递参数)

结论2:在插入纯右值,并且构造函数能正常接收时,emplace 系列函数可以直接构造,省去了调用移动构造函数时的开销。

总结

1、对于深拷贝的类型,push_back是构造 + 移动构造,emplace_back是直接构造

2、对于浅拷贝的类型,push_back是构造 + 拷贝构造,emplace_back是直接构造

3、由于push_back函数参数写死,所以只能先构造再拷贝构造,但是emplace_back函数参数是参数包,能够随函数一层一层传递下去,到构造函数时一起构造,省去中间所有构造。

4、在插入单参数,左值,move 之后的右值时两者没有区别,当插入多参数时 emplace 参数包就会随函数调用一层一层向下传,最后直接构造。

类别说明
emplace或push / insert 右值对象构造 + 移动构造
emplace参数包构造
emplace或push / insert 左值对象构造 + 拷贝构造
emplace参数包构造
  1. int main()
  2. {
  3. list<string> l;
  4. string s("111"); //构造
  5. l.emplace_back(s); //左值深拷贝
  6. l.emplace_back(move(s)); //右值移动构造
  7. l.emplace_back("111"); //单参数参数包最后直接构造
  8. l.emplace_back(string("111")); //构造 + 移动构造
  9. }

二、包装器

1、作用

首先我们先了解可调用对象

可调用对象缺点
函数指针类型定义复杂
仿函数对象要定义一个类
lambda没有类型概念

C++中的 function 本质是一个类模板,也是一个包装器。为了函数调用方便,统一可调用对象的调用方式,function包装器用来包装对象,由于底层使用的是operator()函数重载,所以调用方式就能得到统一。

Ret:返回值类型

Args:参数包

2、使用包装器

(1)定义包装器

  1. #include<functional>
  2. function<返回类型(参数包类型)> fc;

(2)举例

  1. int f(int a, int b)
  2. {
  3. return a + b;
  4. }
  5. struct add
  6. {
  7. public:
  8. int operator()(int a, int b)
  9. {
  10. return a + b;
  11. }
  12. };
  13. int main()
  14. {
  15. //函数指针包装
  16. function<int(int, int)> fc1 = f;
  17. //仿函数对象包装
  18. function<int(int, int)> fc2 = add();
  19. //lambda表达式包装
  20. function<int(int, int)> fc3 = [](int a, int b) {return a + b; };
  21. return 0;
  22. }

注意:包装器只是包装可调用对象,不是定义可调用对象。

3、理解代码

  1. map<string, functional<int(int, int)> func = opFuncMap = {
  2. {" + ", [](int a, int b){return a + b;}},
  3. {" - ", [](int a, int b){return a - b;}},
  4. {" * ", [](int a, int b){return a * b;}},
  5. {" / ", [](int a, int b){return a / b;}},
  6. };

这是命令与动作的对应实现代码,用到了 initializer_list 初始化4个命令与动作的对应操作。

通过包装器封装的函数最后调用函数的代码就会统一,简单。

  1. function<int(int, int)> func = opFuncMap["+"];
  2. func(1, 2);

4、如何包装静态 / 普通成员函数

  1. class Func
  2. {
  3. public:
  4. static int plusi(int a, int b) {return a + b; };
  5. int plusd(double a, double b) {return a + b; };
  6. }
  7. int main()
  8. {
  9. //包装静态成员函数
  10. function<int(int, int)> f1 = &Func::plusi;
  11. //包装普通成员函数(用对象指针)
  12. function<double(Func*, double, double)> f2 = &Func::plusd;
  13. Func f;
  14. f2(&f, 3.1, 2.1);
  15. //包装普通成员函数(用对象)
  16. function<double(Func, double, double)> f3 = &Func::plusd;
  17. f3(Func(), 3.1, 2.1);
  18. }

(1)获取成员函数函数指针方法:&类型 : : 函数名

(2)普通成员函数参数要包含 this 指针,所以对于普通成员函数的 function 定义就一定要带上 this 指针的参数个数(静态函数因为没有 this 指针不用考虑),这时就有两种方法,一种是定义对象指针,一种是定义对象。

问题:要传的参数是对象的指针,为什么第二种用对象传也可以?

首先 this 指针不能显示传递,所以其实 function 底层就是operator() 用传入的对象调用函数,所以在底层就不关心是不是对象的指针,只关心是不是传对象。

三、绑定

1、作用

调整可调用对象的参数个数或参数顺序。

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

第二个模板中的 Ret 如果需要修改原来函数对象的返回值,就可以显示实例化来确定特殊的返回类型。

万能引用里面的参数包包括了绑定值和参数(placeholders::_1, placeholders::_2,....)

其中 placeholders::_1 表示传进函数的第一个实参,以此类推。

2、作用一:调整参数顺序

  1. int Sub(int a, int b)
  2. {
  3. return a - b;
  4. }
  5. int main()
  6. {
  7. auto f1 = Sub;
  8. cout << f1(10, 5) << endl;
  9. //调整顺序
  10. auto f2 = bind(Sub, placeholders::_2, placeholders::_1);
  11. cout << f2(10, 5) << endl;
  12. return 0;
  13. }

结果:

原理图:

绑定函数时交换实参的位置就能交换参数。

3、作用二:调整参数个数

原理:绑死几个参数到bind函数里面,调用时参数减少。

  1. class Sub
  2. {
  3. public:
  4. Sub(int x)
  5. :_x(x)
  6. {}
  7. int sub(int a, int b)
  8. {
  9. return a - b;
  10. }
  11. private:
  12. int _x;
  13. };
  14. int main()
  15. {
  16. auto f3 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3);
  17. cout << f3(Sub(1), 10, 5) << endl;
  18. Sub sub(1);
  19. cout << f3(&sub, 10, 5) << endl;
  20. // 绑定,调整参数个数
  21. auto f4 = bind(&Sub::sub, Sub(1), placeholders::_1, placeholders::_2);
  22. cout << f4(10, 5) << endl;
  23. auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);
  24. cout << f5(10, 5) << endl;
  25. return 0;
  26. }

上面的代码绑定了调用成员函数所用到的对象,这样每次调用函数就不用传对象。

结果:

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

闽ICP备14008679号