目录
1.可变参数模板
1.1基本语法及原理
- C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称为参数包(Args),存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函数参数。
// Args是参数的意思,也可以随便取名 // 参数包Args的个数:0~N template <class ...Args> void Func(Args... args) {}// 形参 template <class ...Args> void Func(Args&... args) {}// 左值引用 template <class ...Args> void Func(Args&&... args) {}// 万能模板
- 我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或 typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯普通模板 ⼀样,每个参数实例化时遵循引⽤折叠规则。
- 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
- 这⾥我们可以使⽤sizeof...运算符去计算参数包中参数的个数。sizeof...不同于sizeof,专门用于处理可变模板参数。
#include<iostream> using namespace std; template<class ...Args> void Print(Args&&... args) { // sizeof...不同于sizeof,是一个新的运算符 // 专门用于处理可变模板参数 cout << sizeof...(args) << endl; } int main() { double x = 2.2; Print();// 包里有0个参数 Print(1);// 包里有1个参数 Print(1, string("xxxxx"));// 包里有2个参数 Print(1, string("xxxxx"), x);// 包里有3个参数 return 0; }
总结:
- 模板:普通函数模板(非可变参数)的参数个数是固定的,编译器会实例化出参数个数相同、但参数类型不同的多个函数。
- 可变参数模板:可变参数模板函数通过参数包(Args...)支持任意数量的参数(包括 0 个)。编译器会根据传入的实参数量和类型,实例化出参数个数不同、且参数类型也可能不同的多个函数。
简言之:
- 普通模板函数的参数个数固定,类型可变,实例化出同参数个数、不同类型的函数;
- 可变参数模板函数的参数个数和类型均可变,实例化出不同参数个数、且类型可能不同的函数。
1.2包扩展
- 对于⼀个参数包,我们除了能计算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个 包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元 素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操作。底层的实现细节如下图所⽰。
- C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数去处理。
编译时递归解析参数包
void ShowList() { cout << endl; } template<class T,class... Args> void ShowList(T x, Args... args) { cout << x << " "; ShowList(args...); } template<class ...Args> void Print(Args&&... args) { // 编译时递归推导解析参数 ShowList(args...); } int main() { double x = 2.2; Print();// 包里有0个参数 Print(1);// 包里有1个参数 Print(1, string("xxxxx"));// 包里有2个参数 Print(1, string("xxxxx"), x);// 包里有3个参数 return 0; }//print(1, string("xxxxx"), 2.2);调⽤时 //本质编译器将可变参数模板通过模式的包扩展,编译器推导的以下几个重载函数函数 void showlist() { cout << endl; } void showlist(double x) { cout << x << " "; showlist(); } void showlist(string x, double z) { cout << x << " "; showlist(z); } void showlist(int x, string y, double z) { cout << x << " "; showlist(y, z); } void print(int x, string y, double z) { showlist(x, y, z); }
另一种解析方法
template<class T> const T& GetArg(const T& x) { cout << x << " "; return x; } template<class ...Args> void Arguments(Args... args) {} template<class ...Args> void Print(Args... args) { // 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments Arguments(GetArg(args)...); } int main() { double x = 2.2; Print();// 包里有0个参数 Print(1);// 包里有1个参数 Print(1, string("xxxxx"));// 包里有2个参数 Print(1, string("xxxxx"), x);// 包里有3个参数 return 0; }// 本质可以理解为编译器编译时,包的扩展模式 // 将上⾯的函数模板扩展实例化为下⾯的函数 void Print(int x, string y, double z) { Arguments(GetArg(x), GetArg(y), GetArg(z)); }
1.3empalce系列接⼝
template <class... Args> void emplace_back (Args&&... args); template <class... Args> iterator emplace (const_iterator position, Args&&... args);
- C++11以后STL容器新增了empalce系列的接⼝,empalce系列的接⼝均为模板可变参数,功能上兼容push和insert系列,但是empalce还⽀持新玩法,假设容器为container,empalce还⽀持直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
- emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列
int main() { list<wsj::string> lt; wsj::string s1("1111111111"); wsj::string s2("1111111111"); // 传左值,跟push_bakc效果一样,走拷贝构造 lt.emplace_back(s1); lt.push_back(s1); cout << "*********************************" << endl; // 传右值,跟push_bakc效果一样,走移动构造 lt.emplace_back(move(s1)); lt.push_back(move(s2)); cout << "*********************************" << endl; // 直接把构造string参数包往下传,直接用string参数包构造string // push_back则是先构造临时对象,再用临时对象移动构造所需对象(因为临时对象是右值) lt.emplace_back("111111111"); lt.push_back("111111111"); cout << "*********************************" << endl; list<pair<wsj::string, int>> lt1; pair<wsj::string, int> kv("苹果", 1); pair<wsj::string, int> kv2("苹果", 1); // 跟push_back一样 // 拷贝构造pair到list的节点中data上 lt1.emplace_back(kv); lt1.push_back(kv); cout << "*********************************" << endl; // 移动构造pair到list的节点中data上 lt1.emplace_back(move(kv)); lt1.push_back(move(kv2)); cout << "*********************************" << endl; // emplace_back支持可变模板参数,所以不用加{} // 直接把构造pair的参数包往下传,直接用pair参数包构造pair lt1.emplace_back("苹果", 1); lt1.push_back({ "苹果", 1 }); cout << "*********************************" << endl; return 0; }emplace_back和push_back在list<string>中的行为差异,核心区别在于对象构造的时机和方式:
- 传递左值时:两者均会调用string的拷贝构造函数(因为左值无法被移动),效果一致。
- 传递右值时:两者均会调用string的移动构造函数(右值可被移动),效果一致。
- 传递构造参数时(如const char*):
- emplace_back("111111111"):直接将参数传递给list内部的string构造函数,在容器内存中直接构造对象,仅 1 次构造。
- push_back("111111111"):
- 先将const char*隐式转换为临时string(第 1 次构造);
- 再通过移动构造将临时对象 “转移” 到容器中(第 2 次构造,利用右值特性)。
- 因此,emplace_back在此场景下少一次临时对象的构造 / 析构,效率更高。
- 总结:emplace_back的优势在于直接在容器内存中构造对象,避免了push_back可能产生的临时对象和移动 / 拷贝操作,尤其适合传递构造参数而非现成对象的场景。
- 第⼆个程序中我们模拟实现了list的emplace和emplace_back接⼝,这⾥把参数包不段往下传递, 最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前⾯说的empalce⽀持 直接插⼊构造T对象的参数,这样有些场景会更⾼效⼀些,可以直接在容器空间上构造T对象。
- 传递参数包过程中,如果是 Args&&... args 的参数包,要⽤完美转发参数包,⽅式如下 std::forward(args)... ,否则编译时包扩展后右值引⽤变量表达式就变成了左值。
// 节点类 template<class T> struct list_node { T _data; list_node<T>* _next; list_node<T>* _prev; template<class ...Args> list_node(Args... args)//t()内置类型的匿名对象 : _data(forward<Args>(args)...) , _next(nullptr) , _prev(nullptr) {} };// 链表类 template<class T> class list { typedef list_node<T> Node; public: typedef list_iterator<T, T&, T*> iterator; typedef list_iterator<T, const T&, const T*> const_iterator; //尾插 void push_back(T& x) { insert(end(), x); } void push_back(T&& x) { insert(end(), forward<T>(x)); } template<class ...Args> void emplace_back(Args&&... args) { insert(end(), forward<Args>(args)...); } //在pos位置之前插入数据 void insert(iterator pos, T& x) { //申请新节点 Node* newnode = new Node(x); Node* cur = pos._node; Node* prev = cur->_prev; newnode->_next = cur; newnode->_prev = prev; cur->_prev = newnode; prev->_next = newnode; ++_size; } void insert(iterator pos, T&& x) { //申请新节点 Node* newnode = new Node(forward<T>(x)); Node* cur = pos._node; Node* prev = cur->_prev; newnode->_next = cur; newnode->_prev = prev; cur->_prev = newnode; prev->_next = newnode; ++_size; } template<class ...Args> void insert(iterator pos,Args&&... args) { //申请新节点 Node* newnode = new Node(forward<Args>(args)...); Node* cur = pos._node; Node* prev = cur->_prev; newnode->_next = cur; newnode->_prev = prev; cur->_prev = newnode; prev->_next = newnode; ++_size; } private: //成员变量包括哨兵位和链表长度 Node* _head; size_t _size; };
2.lambda
2.1lambda表达式语法
- lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。 lambda 表达式语法使⽤层而言没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。
- lambda表达式的格式: [capture-list] (parameters)-> return type { function boby }
- lambda表达式两头(捕捉列表、函数体)不可省略,中间(参数列表、返回值类型)可以省略
- [capture-list] :捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来 判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使 ⽤,捕捉列表可以传值和传引⽤捕捉,具体细节7.2中我们再细讲。捕捉列表为空也不能省略。
- (parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连 同()⼀起省略
- ->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此 部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
- {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以 使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。
// lambda表达式 int main() { auto add1 = [](int x, int y)->int {return x + y; }; cout << add1(1, 2) << endl; // 1.捕捉为空也不能省略 // 2.参数为空可以省略 // 3.返回值可以省略,可以通过返回对象自动推导 // 4.函数体不能省略 auto func1 = [] { cout << "hello world!" << endl; return 0; }; func1(); int a = 0, b = 1; auto swap1 = [](int& x, int& y) { int tmp = x; x = y; y = tmp; }; swap1(a, b); cout << a << ":" << b << endl; return 0; }
2.2捕捉列表
- lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想⽤外层作⽤域中的变量就需要进⾏捕捉。
- 第⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割。[x, y,&z]表⽰x和y值捕捉,z引⽤捕捉。
- 第⼆种捕捉⽅式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表⽰隐式值捕捉,在捕捉列表 写⼀个&表⽰隐式引⽤捕捉,这样我们 lambda 表达式中⽤了那些变量,编译器就会⾃动捕捉那些 变量。
- 第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=,&x]表⽰其他变量隐式值捕捉, x引⽤捕捉;[&,x,y]表⽰其他变量引⽤捕捉,x和y值捕捉。当使⽤混合捕捉时,第⼀个元素必须是 &或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必 须是引⽤捕捉。
- lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态 局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使 ⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
- 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改, mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以 修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为 空)。
总结:
lambda的捕捉列表用于从外层作用域(通常是包含 lambda 的函数或代码块)中捕获变量,供 lambda 函数体内部使用。
- []:不捕获任何变量
- [var]:按值捕获变量var(拷贝一份到 lambda 内部,修改不影响外部),按值捕捉的变量,在lambda函数体内默认是const修饰的,无法直接修改其值。
- [&var]:按引用捕获变量var(引用外部变量,修改会影响外部)
- [=]:按值捕获所有外部变量(lambda 体内使用的变量),隐式值捕捉 ,⽤了哪些变量就捕捉哪些变量
- [&]:按引用捕获所有外部变量(lambda 体内使用的变量),隐式引⽤捕捉,⽤了哪些变量就捕捉哪些变量
- [=, &var]:默认按值捕获,仅var按引用捕获(需注意顺序,&不能在=后面单独出现)
- [&, var]:默认按引用捕获,仅var按值捕获
- static修饰的局部静态变量和全部的变量不能捕捉,也不需要捕捉
- mutable关键字的作用正是移除按值捕获变量的const限制,使其可以修改,但是修改了不会影响外面被捕捉的值,因为是一种拷贝
int x = 0;
// 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量
auto func = []()
{
x++;
};
int main()
{
// 只能⽤当前lambda局部域和捕捉的对象和全局对象
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b](int x,int y)
{
// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改
//a++;
b++;
int ret = a + b + x + y;
return ret;
};
cout << func1(1,2) << endl;
// 隐式值捕捉
// 用了哪些变量就捕捉哪些变量
auto func2 = [=]
{
int ret = a + b;
return ret;
};
cout << func2() << endl;
// 隐式引⽤捕捉
// 用了哪些变量就捕捉哪些变量
auto func3 = [&]
{
a++;
b++;
};
func3();
cout << a << " " << b << endl;
// 混合捕捉1
auto func4 = [&, a, b]
{
// a和b是按值捕捉,无法修改
//a++;
//b++;
c++;
d++;
};
func4();
cout << a << " " << b << " " << c << " " << d << endl;
// 混合捕捉2
auto func5 = [=, &a, &b]
{
a++;
b++;
//c++;
//d++;
};
func5();
cout << a << " " << b << " " << c << " " << d << endl;
// 局部的静态和全局的变量不能捕捉,也不需要捕捉
static int m = 0;
auto func6 = []
{
int ret = m + x;
return ret;
};
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
// mutable相当于去掉const属性,可以修改了
// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉
auto func7 = [=]()mutable
{
a++;
b++;
c++;
d++;
return a + b + c + d;
};
cout << func7() << endl;
cout << a << " " << b << " " << c << " " << d << endl;
return 0;
}
2.3lambda的应用
- 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
- lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定 制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不断接触到。
- 如下代码中,需要自定义类型排序需要传仿函数,仿函数命名不规范时,难以理解其行为,需要跳转至定义处才能明确比较逻辑。
- Lambda 表达式将比较逻辑直接嵌入调用处,gl._price > gr._price这样的代码一眼就能看出是 “价格降序”,无需额外跳转。
struct Goods { string _name;// 名字 double _price;// 价格 int _evaluate;// 评价 Goods(const char* str,double price, int evaluate) :_name(str) ,_price(price) ,_evaluate(evaluate) {} }; struct ComparePriceLess { // 价格降序仿函数 bool operator()(const Goods& gl,const Goods& gr) { return gl._price > gr._price; } }; struct ComparePriceGreater { // 价格升序仿函数 bool operator()(const Goods& gl, const Goods& gr) { return gl._price < gr._price; } }; int main() { vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 } , { "橙⼦", 2.2, 3}, { "菠萝", 1.5, 4 } }; // 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中 // 如果仿函数命名不规范,需要去看具体的仿函数才能知道是怎么比较的 sort(v.begin(), v.end(), ComparePriceLess()); sort(v.begin(), v.end(), ComparePriceGreater()); // 价格降序 sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr)->bool { return gl._price > gr._price; }); // 价格升序 sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr)->bool { return gl._price < gr._price; }); // 评价降序 sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr)->bool { return gl._evaluate > gr._evaluate; }); // 评价升序 sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr)->bool { return gl._evaluate > gr._evaluate; }); return 0; }
2.4lambda的原理
- lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for 这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个 lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
- 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕 捉,编译器要看使⽤哪些就传那些对象。
- 上⾯的原理,我们可以透过汇编层了解⼀下,下⾯第⼆段汇编层代码印证了上⾯的原理。
// lambda的原理
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 函数对象
Rate r1(rate);
r1(10000, 2);
r2(10000, 2);
auto func1 = [] {
cout << "hello world" << endl;
};
func1();
return 0;
}
汇编层可以看到r2 lambda对象调⽤本质还是调⽤operator(),类型是lambda_1,这个类型名 的规则是编译器⾃⼰定制的,保证不同的lambda不冲突


3.包装器
3.1function
template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
// function<返回类型(参数类型)>
- 以上是 function 的原型,他被定义头⽂件中。std::function-cppreference.com是function的官⽅⽂件链接。
- std::function 是⼀个类模板,也是⼀个包装器。 std::function 的实例对象可以包装存储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调⽤对 象被称为 std::function 的⽬标。若 std::function 不含⽬标,则称它为空。调⽤空 std::function 的⽬标导致抛出 std::bad_function_call 异常。
- 函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型,下⾯的第⼆个代 码样例展⽰了 std::function 作为map的参数,实现字符串和可调⽤对象的映射表功能。
1. 包装普通成员函数时,普通成员函数还有⼀个隐含的this指针参数,普通成员函数隐含this指针作为第一个参数,因此在通过std::function等方式包装时,必须显式提供一个对象(或对象指针)来初始化this指针:
- 传递对象指针(如Plus*):直接作为this指针使用(通过->*运算符调用)。
- 传递对象本身(如Plus):编译器会取该对象的地址作为this指针(通过.*运算符调用)
2. 包装静态成员函数时,静态成员函数属于类,不含this指针,因此绑定和调用与普通全局函数类似,无需传递对象或指针,直接通过类名即可访问:
// 包装器
//function
#include<functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{}
// 静态成员变量不包含this指针
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
// 包装各种可调用对象
// function<返回类型(参数类型, 参数类型)>
function<int(int, int)> f1 = f;// 函数
function<int(int, int)> f2 = Functor();// 仿函数
function<int(int, int)> f3 = [](int a, int b) {return a + b; };// lambda
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
// 包装静态成员函数
// 成员函数要指定类域并且前面加&才能获取地址
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
// 包装普通成员函数
// 普通成员函数还有一个隐含的this指针参数
// 所以绑定时传对象或者对象的指针过去都可以
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
return 0;
}
function应用场景
相比于第二种的switch,我们可以利用function存储lambda表达式
使⽤map映射string和function的⽅式实现,这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 遇到数字入栈
// 遇到符号,取出栈顶两元素left和right,配合运算符算出结果ret后再次入栈
// 最后栈中剩下的那个元素就是后缀表达式的结果
// 不使用switch,使用包装器function直接调用相关函数
map<string,function<int(int,int)>> mp={
{"+", [](int left,int right){return left+right;}},
{"-", [](int left,int right){return left-right;}},
{"*", [](int left,int right){return left*right;}},
{"/", [](int left,int right){return left/right;}}
};// lambda
stack<int> st;
for(auto& str:tokens)
{
// 是字符,调用fuction存储的可调用对象lambda
if(mp.count(str))
{
int right=st.top();
st.pop();
int left=st.top();
st.pop();
st.push(mp[str](left,right));
}
else
{
// 是数字,则入栈
st.push(stoi(str));
}
}
return st.top();
}
};
// class Solution {
// public:
// int evalRPN(vector<string>& tokens) {
// // 遇到数字入栈
// // 遇到符号,取出栈顶两元素left和right,配合运算符算出结果ret后再次入栈
// // 最后栈中剩下的那个元素就是后缀表达式的结果
// stack<int> st;
// for(auto& str:tokens)
// {
// // 是运算符,取出栈顶两元素,计算结果后入栈
// if(str=="+" ||str=="-" ||str=="*" ||str=="/")
// {
// int right=st.top();
// st.pop();
// int left=st.top();
// st.pop();
// switch(str[0])
// {
// case '+':
// st.push(left+right);
// break;
// case '-':
// st.push(left-right);
// break;
// case '*':
// st.push(left*right);
// break;
// case '/':
// st.push(left/right);
// break;
// }
// }
// else
// {
// // 数字则入栈
// //
// st.push(stoi(str));
// }
// }
// return st.top();
// }
// };
3.2bind(绑定)
simple(1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// bind用法
bind(函数名,参数);
- bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。 bind 也在这个头⽂件(functional)中。
- 调⽤bind的⼀般形式:
其中 newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的 参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。auto newCallable = bind(callable,arg_list);- arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰ newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象 中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占 位符放到 placeholders 的⼀个命名空间中。

// bind
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
// bind(函数名,参数)
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
// bind 本质返回的⼀个仿函数对象
// 调整参数顺序(不常用)
// _1代表第一个实参
// _2代表第二个实参
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
// 调整参数个数(常用)
// 固定Sub的第一个参数为100
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
// 固定Sub的第二个参数为100
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
// 分别绑定第123个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;
// 成员函数对象进行绑定,就不需要每次都传递类的对象或者指针了
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;
// bing一般用于绑定一些固定参数,例如
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
return 0;
}
bind应用场景
- 比如有一个计算存款复利的lambda,参数有:利率、本金、存款时间。
- 我们可以通过bind绑定一些参数,实现出支持不同年化利率,不同金额和不同年份计算出复利的结算利息
// bind应用场景
int main()
{
// 计算存款复利的lambda
// 利率、本金、存款时间
auto func1 = [](double rate, double money, int years){
double ret = money;
for (int i = 0; i < years; i++)
{
ret += ret * rate;
}
return ret - money;
};
// 通过bind绑定一些参数,实现出支持不同年化利率,不同金额和不同年份计算出复利的结算利息
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_1_5 = bind(func1, 0.015, _1, 10);
function<double(double)> func3_2_5 = bind(func1, 0.025, _1, 3);
function<double(double)> func5_2_5 = bind(func1, 0.025, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_1_5(1000000) << endl;
cout << func3_2_5(1000000) << endl;
cout << func5_2_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
return 0;
}
C++11核心特性详解





1368

被折叠的 条评论
为什么被折叠?



