c++11之前,STL中提供了bind1st以及bind2nd绑定器
首先来看一下他们如何使用:
如果我们要对vector中的元素排序,首先会想到sort,比如:
void output(const vector<int> &vec) { for (auto v : vec) { cout << v << " "; } cout << endl; } int main() { vector<int> vec; srand(time(nullptr)); for (int i = 0; i < 20; i++) { vec.push_back(rand() % 100 + 1); } output(vec); sort(vec.begin(), vec.end()); output(vec); //greater 从大到小排序 sort(vec.begin(), vec.end(), greater<int>()); output(vec); //less 从小到大排序 sort(vec.begin(), vec.end(), less<int>()); output(vec); return; }
sort最后一个参数传入的greater或less都被称为函数对象,顾名思义,表现像函数的对象,因为他们的调用都是在后面加上"()"。
其实这是因为他们都重载了operator()。
来看下greater的定义:
template<class _Ty = void> struct greater { // functor for operator> _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty first_argument_type; _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty second_argument_type; _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool result_type; constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const { // apply operator> to operands return (_Left > _Right); } };
可以看到确实重载了operator(),需要传入两个参数,所以它还是一个二元函数对象。函数对象的概念至关重要!
那什么时候会用到bind1st、bind2nd呢?
比如我们现在要找到第一个小于70的位置,插入70。
可以使用find_if:
auto it1 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70)); if (it1 != vec.end()) { vec.insert(it1, 70); }
这里使用bind1st(greater<int>(), 70)
作为find_if的第三个参数,bind1st的作用,首先,将70绑定到二元函数对象(greater)的第一个参数上,其次,将二元函数对象(greater)转为一元函数对象(因为70已知了),传入到find_if的第三个参数中。这便是他的应用场景,或者还可以使用bind2nd和less的搭配:
auto it1 = find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70)); if (it1 != vec.end()) { vec.insert(it1, 70); }
关于bind1st(greater(), 70)和bind2nd(less(), 70)的理解
因为我们要找小于70的位置,所以,
对于greater来说,left > right,所以绑定到第一个位置
对于less来说,left < right,所以绑定到第二个位置
理解了绑定器后,再来看看function:
function需要一个函数类型进行实例化:
void hello1() { cout << "hello world!" << endl; } void hello2(string str) { cout << str << endl; } class Test { public: void hello(string str) { cout << str << endl; } }; int main() { function<void()> func1 = hello1; //function<void()> func1(hello1); func1();//func1.operator() => hello1(); function<void(string)> func2 = hello2; func2("gao");//func2.operator()(string str) => hello2(str); function<int(int, int)> func3 = [](int a, int b) -> int { return a + b; }; cout << func3(100, 200) << endl; //通过function调用类的成员方法 function<void(Test*, string)> func5 = &Test::hello; func5(&Test(), "call Test::hello!"); return 0; }
对function的调用,实际上是调用了function的()重载,从而调用原函数。上面的例子中可以看到lambda表达式也可以通过function调用。这其实就说明了function的真正用途:保存函数对象的类型,也是对函数对象的封装。这也是它和c语言的函数指针的区别(lambda无法通过函数指针调用)。
现在有这样一个场景:
两(多)个函数,有大部分的代码都是一样的,其中只有一两行代码有不一样的地方,我们可以对这个不一样的地方,使用function做一个抽象,比如:
有两个vector打印函数,一个打印模5=0的元素,一个打印大于10的元素:
void print(vector<int> &number, function<bool(int)> filter) { for (const int &i : number) { if (filter(i)) { cout << i << endl; } } } print(numbers, [](int i){ return i % 5 == 0; }); print(numbers, [](int i){ return i > 10; });
这样就不用定义两个不同的打印函数了。
关于闭包的概念:
下面是维基百度对于闭包的定义:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
简单来说:闭包可以记忆住创建它时候的那些变量。
下面,我们再通过一个例子来说明。
现在,假设我们的需求是:获取一个集合中最小和最大值,并在稍后的时候(可能是另外一个函数中)打印它们。 这里,我们常规的做法通常是:通过一个函数获取集合的最大,最小值,然后保存住,最后在需要的时候访问这两个值,然后打印它们。
这样做就会需要解决:如何保存和传递最大,最小这两个值。
但实际上,这里我们可以考虑用闭包来实现这个功能,让闭包把最大,最小两个值捕获下来,然后在需要的地方调用就可以了。
请看一下下面这段代码:
void getMinMax(vector<int>& number, function<void ()>& printer) { int min = number.front(); int max = number.front(); for (int i : number) { if (i < min) { min = i; } if (i > max) { max = i; } } printer = [=] () { cout << "min:" <<min<< endl; cout << "max:" << max << endl; }; }
这里,我们通过function<void ()>& printer
传递出这个闭包。 然后,在需要的地方,这样即可:
function<void()> printer; getMinMax(numbers, printer); ...... printer();
这里的printer其实是我们前面从getMinMax
函数出传出的闭包,这个闭包捕获了min和max。我们直接传递这个闭包给需要的地方使用,而不用传递裸的两个数值,是不是优雅的不少?
bind
/* c++11 bind绑定器 -> 返回的结果还是一个函数对象 */ void hello(string str) { cout << str << endl; } int sum(int a, int b) { return a + b; } class Test { public: int sum(int a, int b) { return a + b; } }; int main() { bind(hello, "hello, bind!")(); cout << bind(sum, 10, 20)() << endl; cout << bind(&Test::sum, Test(), 20, 30)() << endl; //参数占位符 绑定器出了语句,无法继续使用 bind(hello, placeholders::_1)("hello bind 2!"); cout << bind(sum, placeholders::_1, placeholders::_2)(200, 300) << endl; //此处把bind返回的绑定器binder就复用起来了 function<void(string)> func1 = bind(hello, placeholders::_1); func1("hello gao"); return 0; }
使用bind和function的线程池例子:
class Thread { public: //接收一个函数对象,参数都绑定了,所以不需要参数 Thread(function<void()> func) : _func(func) {} thread start() { thread t(_func); return t; } private: function<void()> _func; }; class ThreadPool { public: ThreadPool() {} ~ThreadPool() { for (int i = 0; i < _pool.size(); i++) { delete _pool[i]; } } void startPool(int size) { for (int i = 0; i < size; i++) { //成员方法充当线程函数,绑定this指针 _pool.push_back(new Thread(bind(&ThreadPool::runInThread, this, i))); } for (int i = 0; i < size; i++) { _handler.push_back(_pool[i]->start()); } for (thread &t : _handler) { t.join(); } } private: vector<Thread*> _pool; vector<thread> _handler; void runInThread(int id) { cout << "call runInThread! id:" << id << endl; } }; int main() { ThreadPool pool; pool.startPool(10); return 0; }
lambda(匿名函数对象)
lambda表达式的语法
[捕获外部变量](形参列表)->返回值{操作代码}; []:表示不捕获任何外部变量 [=]:表示以传值的方式捕获外部的所有变量 [&]:表示以传引用的方式捕获外部的所有变量 [this]:捕获外部的this指针 [=,&a]: 表示以传值的方式捕获外部的所有变量,但是a变量以传引用的方式捕获 [a,b]:表示以值传递的方式捕获外部变量a和b [a,&b]:a以值传递捕获,b以引用捕获
使用举例:
int main() { auto func1 = []()->void {cout << "hello world!" << endl; }; func1(); //[]:表示不捕获任何外部变量 //编译报错 /*int a = 10; int b = 20; auto func3 = []() { int tmp = a; a = b; b = tmp; }; */ //以值传递a,b,lambda实现的重载函数operator()中,是const方法,不能修改成员变量 //如果一定要修改,将lambda修饰成mutable,但是这并不会改变a的值,因为这是值传递 //int a = 10; //int b = 20; //auto func3 = [a, b]() /*mutable*/ //{ // int tmp = a; // a = b; // b = tmp; //}; vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(3); for_each(vec.begin(), vec.end(), [](int a) { cout << a << endl; }); return 0; }
class Data { public: Data(int a, int b) : ma(a), mb(b) {} int ma; int mb; }; int main() { map<int, function<int(int, int)>> caculateMap; caculateMap[1] = [](int a, int b)->int {return a + b; }; caculateMap[2] = [](int a, int b)->int {return a - b; }; cout << caculateMap[1](1, 2) << endl; //智能指针自定义删除器 unique_ptr<FILE, function<void(FILE*)>> ptr1(fopen("data.txt", "w"), [](FILE *pf) { fclose(pf); }); //优先队列 using FUNC = function<bool(Data&, Data&)>; priority_queue<Data, vector<Data>, FUNC> maxHeap([](Data &d1, Data &d2)->bool { return d1.mb > d2.mb; }); maxHeap.push(Data(10, 20)); return 0; }
lambda表达式是如何实现的?
其实是编译器为我们了创建了一个类,这个类重载了(),让我们可以像调用函数一样使用。所以,你写的lambda表达式和真正的实现,是这个样子的:
而对于捕获变量的lambda表达式来说,编译器在创建类的时候,通过成员函数的形式保存了需要捕获的变量,所以看起来是这个样子:
似乎也没有什么神奇的地方。但正是由于编译器帮我们实现了细节,使我们的代码变得优雅和简洁了许多。