C++进阶(7)——包装器
目录
包装器
function
💡基本介绍
💡function的统一性
💡function包装器的应用
💡总结一下:
bind
💡基本介绍
💡bing包装器的使用
💡bind包装器调整传入参数的顺序
💡总结一下
包装器
function
💡基本介绍
function是一个类模板,也是一个包装器。function的实例对象可以包装存储其他的可以调用对象,包括了函数指针、仿函数、lambda和bind表达式等,存储的可调用对象被称之为function的目标。
类模板的原型如下:
template <class T>
class function; // undefined;template <class Ret, class ...Args>
class function<Ret(Args...)>;
参数说明:
Ret:被包装存储的可调用对象的返回值类型。
Args...:被包装存储的调用对象的形参类型。
我们这里还是来举出一些栗子:
#include <iostream>
#include <functional>
using namespace std;
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) {}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<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {return a + b;};cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;// 包装静态成员函数// 这里需要指定类域并且在前面加上&才能获取地址function<int(int, int)> f4 = &Plus::plusi;// 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;
}
测试效果:
敲黑板:
我们这里取静态成员函数的地址可以不用取地址运算符“&”,但是我们取非静态成员函数的地址就必须要使用取地址运算符了。
我们这里包装非静态成员函数的时候,要注意还有一个隐含的this指针参数,这里我们要指明第一个参数的类型,可以值对象指针,也可以是对象。
💡function的统一性
我们这里可以将我们的function包装器和我们的函数模板来进行一个对比,首先我们要有一个函数模板,这个函数模板的要求如下:
1、传入这个函数的第一个参数可以是任意的可以调用的对象,就是我们上面所提到的函数指针、仿函数、lambda表达式等。
2、这个函数模板中需要定义一个静态变量,然后每次打印这个静态变量的地址,看看是不是调用的同一个函数。
函数模板:
template<class F, class T>
T func(F f, T t) {static int temp = 1;cout << "&temp: " << &temp << endl;return f(t);
}
测试代码:
int f(int x) {return x * 2;
}struct Functor {int operator()(int x) {return x * 3;}
};int main() {// 函数指针cout << func(f, 2) << endl;cout << "****************" << endl;// 仿函数cout << func(Functor(), 2) << endl;cout << "****************" << endl;// lambda表达式cout << func([](int x){return x * 4;}, 2) << endl;return 0;
}
测试效果:
我们这里可以看到我们的三种方式调用的都不是统一个函数,也是就说我们的func函数实际上是被实例化出来的三份,但是我们直到这里根本没有必要实例化出来三份,因为我们这里的三个函数的返回值和参数类型都是相同的,这里我们就可以使用包装器对着三个可调用对象进行包装了。
示例代码:
int main() {// 函数指针function<int(int)> f1 = f;cout << func(f1, 2) << endl;cout << "****************" << endl;// 函数对象function<int(int)> f2 = Functor();cout << func(f2, 2) << endl;cout << "****************" << endl;// lambda表达式function<int(int)> f3 = [](int x) {return x * 4;};cout << func(f3, 2) << endl;cout << "****************" << endl;return 0;
}
测试效果:
我们这里可以看到我们调用的是同一个函数。
💡function包装器的应用
我们这里给出的示例是一道力扣上面的题——逆波兰表达式求值
这个题目主要考察的是对于栈的运用,我们这里直接给出实现思路:
1、定义栈,然后遍历字符床
2、遍历的过程中遇到了数字字符直接入栈,遍历到了运算符,就从栈中取出两个数字进行运算,然后将我们计算的结果返回栈中。
3、循环上面的步骤直到我们遍历结束,栈顶就是结果。
代码如下:
class Solution {
public:int evalRPN(vector<string>& tokens) {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();}
};
我们这里的实现比较的臃肿,使用的是switch_case语句来进行的判断和计算,我们这一节学习了包装器,这样的情况是可以用我们的包装器来简化代码的,我们可以将运算符和我们的计算函数之间建立映射关系,然后直接需要的时候调用即可。
示例代码:
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;// function作为map的映射可调用对象的类型map<string, function<int(int, int)>> opFuncMap = {{"+", [](int x, int y) { return x + y; }},{"-", [](int x, int y) { return x - y; }},{"*", [](int x, int y) { return x * y; }},{"/", [](int x, int y) { return x / y; }}};for (auto& str : tokens) {if (opFuncMap.count(str)) { // 操作符int right = st.top();st.pop();int left = st.top();st.pop();int ret = opFuncMap[str](left, right);st.push(ret);} else {st.push(stoi(str));}}return st.top();}
};
我们这里使用哈希表映射了对应的函数,着不仅仅可以使代码更加简洁,还可以使代码具有更高的灵活性。
💡总结一下:
1、function包装器可以统一管理我们的可调用对象。
2、functoin包装器提高了代码的灵活性。
bind
💡基本介绍
bind是一个函数模板,同时也是一个可调用对象的包装器,可以把它看作是一个函数的适配器,对接受的可调用对象处理后返回一个可调用对象,主要是用来调整参数个数和参数顺序的。
函数模板如下:
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);
参数说明:
fn:可调用对象名。
args...:要绑定的参数列表。
调用bind的一般形式
auto newCallable = bind(callable, arg_list);
参数说明:
newCallable:生成的可调用对象。
callable:传入的需要包装的可调用对象。
arg_list:逗号分隔的参数列表,对应给定的callable的参数。
这里需要说明的是:我们的arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数实际上是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示的是生成的可调用对象中参数的位置:_1表示的就是我们newCallable的第一个参数,_2就是第二个参数,以此类推即可,这些占位符放在了placeholders的一个命名空间中。
💡bing包装器的使用
普通绑定
代码示例:
#include <functional>
#include <iostream>
using namespace std;
int Sub(int a, int b) {return (a - b) * 10;
}
int main() {auto f1 = bind(Sub, placeholders::_1, placeholders::_2);cout << f1(10, 5) << endl;return 0;
}
测试效果:
这里实现的也是第一个参数传入给placeholders::_1,第二个参数传入给placeholders::_2,并生成了新的可调用对象,所以我们这里的绑定和我们的直接调用可调用对象没什么区别。
绑定固定参数
示例代码:
#include <functional>
#include <iostream>
using namespace std;
int Sub(int a, int b) {return (a - b) * 10;
}
int main() {auto f2 = bind(Sub, placeholders::_1, 2);cout << f2(10) << endl;return 0;
}
测试效果:
我们这里讲我们的第二个参数固定为了10,所以此时我们传入的时候就只需要传入一个参数即可。
绑定成员函数
这个是我们实现一些项目的时候比较常用的,我们这里的第二个参数需要传入对象的示例(或是对象的指针,这里和上面的function相似)。
示例代码:
#include <iostream>
#include <functional>
using namespace std;
class Foo {
public:void print(int val) const { std::cout << "Foo value: " << val << std::endl; }
};int main() {Foo obj;function<void(int)> func_binder = bind(&Foo::print, &obj, placeholders::_1);// function<void(int)> func_binder = bind(&Foo::print, obj, placeholders::_1); // 也可以传入对象func_binder(42); return 0;
}
测试效果:
绑定lambda表达式
这里其实和上面的原理一样,就不再赘述了。
示例代码:
#include <iostream>
#include <functional>int main() {using namespace std::placeholders;auto lambda_add = [](int a, int b) { return a + b; };auto lambda_reorder = std::bind(lambda_add, _2, _1);std::cout << "Reordered sum: " << lambda_reorder(10, 20) << std::endl; return 0;
}
测试效果:
💡bind包装器调整传入参数的顺序
我们这里还是给出一个栗子来理解这个过程,就比如下面的代码:
示例代码:
#include <iostream>
#include <functional>
using namespace std;
class Add {public:int add(int x, int y) {return x * 10 + y;}
};int main() {function<int(int, int)> f = bind(&Add::add, Add(), placeholders::_1, placeholders::_2);cout << f(1, 2) << endl;return 0;
}
测试效果:
这个时候我们就可以来对这里的的传参顺序进行一个调整了。
示例代码:
#include <iostream>
#include <functional>
using namespace std;
class Add {public:int add(int x, int y) {return x * 10 + y;}
};int main() {function<int(int, int)> f = bind(&Add::add, Add(), placeholders::_2, placeholders::_1);cout << f(1, 2) << endl;return 0;
}
测试效果:
我们这里可以看到我们的参数确实是变换了顺序,但是这里要说明的是这个功能其实是不常用的,了解即可。
💡总结一下
1、可以将函数的一些参数固定下来,那样我们就不用传递那些参数了。
2、可以调整我们传入参数的顺序。