当前位置:   article > 正文

C++11 -- 可变参数模板 + 包装器_c++ 打包参数

c++ 打包参数

目录

可变参数模板

可变参数模板定义

 参数包的展开方式

递归展开参数包

逗号表达式展开参数包

容器的emplace方法

function包装器

对于是函数、函数对象、lambda用法如下:

如果是类成员函数,使用有点区别:

function的实际用途

bind包装器

bind使用

bind包装器的意义


可变参数模板


在C语言阶段,我们已经接触过可变参数,比如scanf、printf等等

这里的 ... 就是可变参数列表,这也是 scanf 和 printf 可以接受多个参数的原因:使用了可变参数列表,但是scanf 和 printf 的可变参数是函数参数的可变参数,并不是模板的可变参数

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是一个函数形参参数包

可以在函数模板中通过 sizeof 计算参数包中参数的个数,代码如下

注意:sizeof 后面也需要加上参数列表 ... ,不加直接报错 

  1. template <class ...Args>
  2. void ShowList1(Args... args) {
  3. //sizeof 后面也需要加上参数列表 ... ,不加直接报错
  4. cout << sizeof...(args) << endl; //获取参数包中参数的个数
  5. }
  6. int main() {
  7. //想传几个就传几个,想传什么类型就传什么类型
  8. ShowList1();
  9. ShowList1(1, 2);
  10. ShowList1(1, 2, '3');
  11. return 0;

但是我们无法使用 args[i] (语法不支持)这样方式获取可变参数,只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点,也是最大的难点 。

 参数包的展开方式

参数包的展开方式有:递归展开参数包、使用逗号表达式展开参数包

递归展开参数包

递归展开参数包的方式如下:

  1. void ShowList2() // 这里是当参数包中的个数为0时,调用函数就会去匹配我们重载的函数,结束递归
  2. {
  3. cout << endl;
  4. }
  5. //展开函数
  6. template <class T, class ...Args>
  7. void ShowList2(T value, Args... args)
  8. {
  9. cout << value << " ";
  10. ShowList2(args...);
  11. }
  12. int main()
  13. {
  14. ShowList2();
  15. ShowList2(1);
  16. ShowList2(1, 2);
  17. ShowList2(1, 'A');
  18. ShowList2(1, 'A', std::string("sort"));
  19. return 0;
  20. }

逗号表达式展开参数包

  1. // 逗号表达式
  2. // 从左到右逐个计算;
  3. // 它的值为最后一个表达式的值;
  4. // 逗号表达式的优先级别在所有运算符中最低。
  5. // int a, b, c;
  6. // cout << (a = 3, b = 5, b += a, c = b * 5, b = 2); // 2
  7. //数组可以通过列表进行初始化,比如:
  8. //int a[] = { 1,2,3,4,5 };
  1. //展开函数
  2. template<class ...Args>
  3. void ShowList3(Args... args)
  4. {
  5. //如果参数包中各个参数的类型都是整型,那么也可以把这个参数包放到列表当中初始化这个整型数组,
  6. // 此时参数包中参数就放到数组中了
  7. int arr[] = { args... }; //列表初始化
  8. //打印参数包中的各个参数
  9. for (auto e : arr)
  10. {
  11. cout << e << " ";
  12. }
  13. cout << endl;
  14. cout << "第一个元素:" << arr[0] << endl;
  15. }
  16. int main()
  17. {
  18. //C++规定一个容器中存储的数据类型必须是相同的,因此如果这样写的话,
  19. // 那么调用 ShowList函数时传入的参数只能是整型的,并且还不能传入0个参数,
  20. // 因为数组的大小不能为0,需要再重载一个无参的函数,在此基础上借助逗号表达式来展开参数包
  21. //ShowList3(); // 会报错
  22. ShowList3(1);
  23. ShowList3(1, 2);
  24. ShowList3(1, 2, 3);
  25. return 0;
  26. }

使用逗号表达式:

  1. //支持无参调用
  2. void ShowList()
  3. {
  4. cout << endl;
  5. }
  6. template <class T>
  7. void PrintArg(T t)
  8. {
  9. cout << t << " ";
  10. }
  11. //展开函数
  12. template <class ...Args>
  13. void ShowList(Args... args)
  14. {
  15. //逗号表达式:结果为后面的值,通过可变参数列表展开并推演个数,进行实例化调用上面的函数。
  16. int arr[] = { (PrintArg(args), 0)... };//列表初始化+逗号表达式
  17. cout << endl;
  18. }
  19. int main()
  20. {
  21. ShowList();
  22. ShowList(1);
  23. ShowList(1, 1.1);
  24. ShowList(1, 'A');
  25. ShowList(1, 'A', std::string("sort"));
  26. return 0;
  27. }

在优化一下:

  1. template <class T>
  2. int PrintArg(T t)
  3. {
  4. cout << t << " ";
  5. return 0;
  6. }
  7. int PrintArg() // 只用这个,不支持无参ShowList() 调用,需要重载一个无参的ShowList函数
  8. {
  9. return 0;
  10. }
  11. //展开函数
  12. template <class ...Args>
  13. void ShowList(Args... args)
  14. {
  15. //实际上我们也可以不用逗号表达式,因为这里的问题就是初始化整型数组时必须用整数,
  16. // 那我们可以将处理函数的返回值设置为整型,然后用这个返回值去初始化整型数组也是可以的
  17. int arr[] = { PrintArg(args)... };
  18. cout << endl;
  19. }
  20. int main()
  21. {
  22. // ShowList(); // 重载一个无参的ShowList函数,才可调用
  23. ShowList(1);
  24. ShowList(1, 1.1);
  25. ShowList(1, 'A');
  26. ShowList(1, 'A', std::string("sort"));
  27. return 0;
  28. }

将逗号表达式换成PrintArg带有返回值。

注意必须重载一个无参的ShowList函数,才可调用 ShowList();

容器的emplace方法


对于各种容器的emplace、emplace_back方法,由于是c++11新出的方法,参数无论是右值还是左值,都存在一个可变参数列表为函数的重载函数,其功能与push、push_back是一样的。

  1. template <class... Args>
  2. void emplace_back (Args&&... args);
  1. #include<list>
  2. int main() {
  3. list<int> list1;
  4. list1.push_back(1);
  5. list1.emplace_back(2);
  6. list1.emplace_back();
  7. //参数过多就识别不了了,会报错
  8. //list1.emplace_back(3, 4);
  9. //list1.emplace_back(3, 4, 5);
  10. for (auto& e : list1)
  11. {
  12. cout << e << endl;
  13. }
  14. list< std::pair<int, char> > mylist;
  15. mylist.push_back(make_pair(1, 'a'));//构造+拷贝构造
  16. //mylist.push_back(1, 'a'); //push_back不支持
  17. mylist.emplace_back(10, 'a');//直接构造
  18. mylist.emplace_back(20, 'b');
  19. mylist.emplace_back(30, 'c');
  20. cout << "mylist contains:";
  21. for (auto& x : mylist)
  22. cout << " (" << x.first << "," << x.second << ")";
  23. cout << endl;
  24. return 0;
  25. }

  1. //emplace_back有时比push_back更快,就是因为其底层存在着参数包,不用进行拷贝构造。
  2. // 当然,emplace_back也可以直接传对象。
  3. //emlplace就是少拷贝一次,直接构造,没有参数上的拷贝过程,
  4. // 因此如果对于没有实现移动构造的深拷贝的类,减少的就是一次深拷贝,性能就会提升很多。

function包装器


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

  1. // 类模板原型如下
  2. template <class T> function; // undefined
  3. template <class Ret, class... Args> class function<Ret(Args...)>;
  4. std::function在头文件<functional>
  5. 模板参数说明:
  6. Ret: 被调用函数的返回类型
  7. Args…:被调用函数的形参
  1. #include <functional>
  2. template<class F, class T>
  3. T useF(F f, T x)
  4. {
  5. static int count = 0;
  6. cout << "count:" << ++count << endl;
  7. cout << "count:" << &count << endl;
  8. return f(x);
  9. }
  10. double f(double i)
  11. {
  12. return i / 2;
  13. }
  14. struct Functor
  15. {
  16. double operator()(double d)
  17. {
  18. return d / 3;
  19. }
  20. };
  21. int main()
  22. {
  23. // 函数名
  24. cout << useF(f, 11.11) << endl;
  25. cout << endl;
  26. // 函数对象
  27. cout << useF(Functor(), 11.11) << endl;
  28. cout << endl;
  29. // lamber表达式
  30. cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
  31. cout << endl;
  32. //由于函数指针、仿函数、lambda表达式是不同的类型,因此useF函数会被实例化出三份,三次调用useF函数所打印 count的地址也是不同的。
  33. // 但实际这里根本没有必要实例化出三份useF函数,因为三次调用 useF函数时传入的可调用对象虽然是不同类型的,
  34. // 但这三个可调用对象的返回值和形参类型都是相同的
  35. //
  36. //这时就可以用包装器分别对着三个可调用对象进行包装,
  37. // 然后再用这三个包装后的可调用对象来调用useF函数,这时就只会实例化出一份useF函数。
  38. //根本原因就是因为包装后,这三个可调用对象都是相同的function类型,
  39. // 因此最终只会实例化出一份useF函数,该函数的第一个模板参数的类型就是function类型的
  40. //函数名
  41. function<double(double)> func1 = f;
  42. cout << useF(func1, 11.11) << endl;
  43. //函数对象
  44. function<double(double)> func2 = Functor();
  45. cout << useF(func2, 11.11) << endl;
  46. //lambda表达式
  47. function<double(double)> func3 = [](double d)->double {return d / 4; };
  48. cout << useF(func3, 11.11) << endl;
  49. return 0;
  50. }

function包装器的作用: 对各种可调用对象进行类型统一

对于是函数、函数对象、lambda用法如下:

  1. int f2(int a, int b)
  2. {
  3. return a + b;
  4. }
  5. struct Functor2
  6. {
  7. public:
  8. int operator() (int a, int b)
  9. {
  10. return a + b;
  11. }
  12. };
  13. int main()
  14. {
  15. // 函数名(函数指针)
  16. //int(int, int) 第一个int返回类型,(int, int)是参数列表
  17. function<int(int, int)> func1 = f2;
  18. cout << func1(1, 2) << endl;
  19. //也可以这样
  20. function<int(int, int)> func2(f2);
  21. cout << func2(1, 2) << endl;
  22. cout << endl;
  23. // 匿名函数对象
  24. function<int(int, int)> func3 = Functor2();
  25. cout << func3(1, 2) << endl;
  26. //这种匿名对象的写法,VS系列不支持,可能是编译器识别的问题
  27. //function<int(int, int)> func4(Functor2());
  28. //cout << func4(1, 2) << endl;
  29. // 仿函数对象
  30. Functor2 ft;
  31. function<int(int, int)> func5 = ft;
  32. cout << func5(1, 2) << endl;
  33. cout << endl;
  34. // lambda表达式
  35. function<int(int, int)> func6 = [](const int a, const int b) {return a + b; };
  36. cout << func6(1, 2) << endl;
  37. return 0;
  38. }

如果是类成员函数,使用有点区别:

  1. //如果是类成员函数,使用有点区别
  2. class Plus
  3. {
  4. public:
  5. static int plusi(int a, int b)
  6. {
  7. return a + b;
  8. }
  9. double plusd(double a, double b)
  10. {
  11. return a + b;
  12. }
  13. };
  14. int main()
  15. {
  16. // 类的成员函数
  17. //类静态成员函数
  18. function<int(int, int)> func1 = &Plus::plusi;//类静态成员函数指针
  19. cout << func1(1, 2) << endl;
  20. //也可以不取地址
  21. function<int(int, int)> func2 = Plus::plusi;//类静态成员函数指针
  22. cout << func2(1, 3) << endl;
  23. //类普通成员函数
  24. //对于类普通成员函数,必须进行&,没有直接报错
  25. //参数列表还需要加上一个参数:类名,这个参数代表的是 this指针
  26. //this指针不能显示去传,所以需要用Plus替代
  27. function<double(Plus, double, double)> func8 = &Plus::plusd;//类普通成员函数指针
  28. //调用的时候必须要传多一个匿名对象,不传直接报错
  29. cout << func8(Plus(), 1.1, 2.2) << endl;
  30. return 0;
  31. }

function的实际用途

150. 逆波兰表达式求值

  • 定义一个栈,依次遍历所给字符串。
  • 如果遍历到的字符串是数字则直接入栈。
  • 如果遍历到的字符串是加减乘除运算符,则从栈定抛出两个数字进行对应的运算,并将运算后得到的结果压入栈中。
  • 所给字符串遍历完毕后,栈顶的数字就是逆波兰表达式的计算结果

暴力判断

  1. int evalRPN(vector<string>& tokens) {
  2. stack<int> st;
  3. int right = 0;
  4. int left = 0;
  5. int ret = 0;
  6. for ( auto& str : tokens)
  7. {
  8. if ( str == "+" || str == "-" || str == "*" || str == "/")
  9. {
  10. right = st.top();
  11. st.pop();
  12. left = st.top();
  13. st.pop();
  14. switch(str[0]) // 不支持自定义类型,所以这样写
  15. {
  16. case '+':
  17. ret = left + right;
  18. st.push(ret);
  19. break;
  20. case '-':
  21. ret = left - right;
  22. st.push(ret);
  23. break;
  24. case '*':
  25. ret = left * right;
  26. st.push(ret);
  27. break;
  28. case '/':
  29. ret = left / right;
  30. st.push(ret);
  31. break;
  32. }
  33. }else
  34. {
  35. // st.push(atoi(str));
  36. st.push(stoi(str));
  37. }
  38. }
  39. return st.top();
  40. }

用包装器来简化代码

  1. int evalRPN(vector<string>& tokens) {
  2. stack<int> st;
  3. // function包装器
  4. map<string, function<int(int, int)>> opFuncMap =
  5. {
  6. {"+", [](int x, int y)->int{return x + y;}},
  7. {"-", [](int x, int y)->int{return x - y;}},
  8. {"*", [](int x, int y)->int{return x * y;}},
  9. {"/", [](int x, int y)->int{return x / y;}}
  10. };
  11. for(auto& str : tokens)
  12. {
  13. if(opFuncMap.count(str) == 0) // 容器中的所有元素都是唯一的,因此该函数只能返回1(如果找到该元素)或零(否则)。
  14. {
  15. st.push(stoi(str));
  16. }
  17. else
  18. {
  19. int right = st.top();
  20. st.pop();
  21. int left = st.top();
  22. st.pop();
  23. st.push(opFuncMap[str](left, right));
  24. }
  25. }
  26. return st.top();
  27. }

bind包装器


模板参数说明:

  • fn:可调用对象
  • Args:参数列表
  • Ret:返回值类型
  • args...:要绑定的参数列表:值或占位符

调用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为第二个参数,以此类推

占位符说明:

 placeholders其实是一个命名空间

placeholders_1 是一个占位符

bind使用

  1. int Plus(int a, int b)
  2. {
  3. return a + b;
  4. }
  5. int Sub(int a, int b)
  6. {
  7. return a - b;
  8. }
  9. class SubC
  10. {
  11. public:
  12. int subC(int a, int b)
  13. {
  14. return a - b * x;
  15. }
  16. private:
  17. int x = 20;
  18. };
  19. int main()
  20. {
  21. //表示绑定函数Plus 参数分别由调用 f1 的第一,二个参数为指定
  22. auto f1 = bind(Plus, placeholders::_1, placeholders::_2);
  23. cout << f1(1, 2) << endl;
  24. cout << f1(2, 2) << endl;
  25. //表示绑定函数 Plus 的第一个参数,第二个参数为: 10, 20
  26. auto f2 = bind(Plus, 10, 20);
  27. cout << f2() << endl;
  28. //表示绑定函数 Plus 的第一个参数为指定,第二个参数固定为:20
  29. auto f3 = bind(Plus, placeholders::_1, 20);
  30. cout << f3(1) << endl;
  31. //可以用 function类型指明返回值和形参类型后 接收 bind包装后的可调用对象
  32. //f4 的类型为 function<void(int, int, int)> 与 f1类型一样
  33. function<int(int, int)> f4 = bind(Plus, placeholders::_1, placeholders::_2);
  34. cout << f4(1, 2) << endl;
  35. cout << f4(2, 2) << endl;
  36. //无意义的绑定
  37. function<int(int, int)> func = bind(Plus, placeholders::_1, placeholders::_2);
  38. cout << func(1, 2) << endl; //3
  39. //此时绑定后生成的新的可调用对象的传参方式,和原来没有绑定的可调用对象是一样的,所以说这是一个无意义的绑定
  40. //调整传参顺序,只需更改占位符的位置即可
  41. function<int(int, int)> f5 = bind(Sub, placeholders::_2, placeholders::_1);
  42. cout << f5(2, 1) << endl;
  43. //固定绑定参数:减少参数的传递
  44. function <int(SubC, int, int)> func4 = &SubC::subC;
  45. cout << func4(SubC(), 10, 20) << endl;//-390
  46. //绑定之后相当于减少了参数的个数,_1和_2代表参数传递的顺序
  47. function <int(int, int)> func5 = bind(&SubC::subC, SubC(), placeholders::_1, placeholders::_2);
  48. cout << func5(10, 20) << endl;//-390
  49. return 0;
  50. }

bind包装器的意义

  • 将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。
  • 可以对函数参数的顺序进行灵活调整

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

闽ICP备14008679号