C++STL中提供了bind1绑定器,通常与函数对象一起使用。
函数对象是重载了operator()函数的对象。
将二元函数对象operator()的第一个参数绑定为固定的x来构造一元函数对象。返回绑定了第一个参数的函数对象。
将二元函数对象operator()的第二个参数绑定为固定的x来构造一元函数对象。返回绑定了第二个参数的函数对象。
我们看以下代码:
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
#include<ctime>using namespace std;template<typename Container>void showContainer(Container &con)
{typename Container::iterator it = con.begin();for (; it != con.end(); it++){cout << *it << " ";}cout << endl;
}int main()
{vector<int> vec;srand(time(nullptr));for (int i = 0; i < 10; i++){vec.push_back(rand() % 100 + 1);}showContainer(vec);sort(vec.begin(), vec.end()); // 默认从小到大排序showContainer(vec);// greater 二元函数对象sort(vec.begin(), vec.end(), greater<int>()); // 从大到小排序,传入函数对象showContainer(vec);return 0;
}
如果现在要将60按顺序插入到vector,那么就要在vector中找第一个小于60的数字。可以使用泛型算法中的find_if在指定范围中查找满足特定条件的元素。需要接收的是一个一元函数对象。
template <class InputIterator, class UnaryPredicate>InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred);pred:一个一元谓词,是一个可调用对象,接收一个参数,返回布尔值,返回true表示条件满足。
此时,需要从vector中取一个对象跟60比,那么不能使用库中的greater或者less函数对象,因为它们是一个二元函数对象,接收两个值。
那我们应该怎样处理呢?就引入了绑定器bind1st、bind2nd。
bind1st和bind2nd
通过绑定器绑定一个二元函数对象,就可以得到一个一元函数对象。
bind1st:要在vector中找小于60的元素,可以将greater的第一个参数绑定为60,就将greater间接的变为了一元函数对象了,传参的时候只需要传给greater的第二个参数即可。
bind2nd:要在vector中小于60的元素,可以将less的第二个参数绑定为60,就将less间接的变为了一元函数对象了,传参的时候只需要传给less的第一个参数即可。
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
#include<ctime>using namespace std;template<typename Container>void showContainer(Container &con)
{typename Container::iterator it = con.begin();for (; it != con.end(); it++){cout << *it << " ";}cout << endl;
}int main()
{vector<int> vec;srand(time(nullptr));for (int i = 0; i < 10; i++){vec.push_back(rand() % 100 + 1);}showContainer(vec);sort(vec.begin(), vec.end()); // 默认从小到大排序showContainer(vec);// greater 二元函数对象sort(vec.begin(), vec.end(), greater<int>()); // 从大到小排序,传入函数对象showContainer(vec);// 将60按顺序插入到vector中 在vector中找第一个小于60的元素auto it1 = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 60));if (it1 != vec.end()){vec.insert(it1, 60);}showContainer(vec);auto it2 = find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 60));if (it2 != vec.end()){vec.insert(it2, 60);}showContainer(vec);return 0;
}
bind1st和bind2nd的底层实现原理
下面我们来模拟实现bind1st和bind2nd来探究以下它们的底层实现原理。
using namespace std;template<typename Container>void showContainer(Container &con)
{typename Container::iterator it = con.begin();for (; it != con.end(); it++){cout << *it << " ";}cout << endl;
}// 模拟实现find_if
template<typename Iterator,typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare com)
{for (; first != last; first++){if (com(*first)) // com.operator()(*first)return first;}return last;
}// 实现_mybind1st
template<typename Compare,typename T>
class _mybind1st
{
public:_mybind1st(Compare comp, T val):_comp(comp),_val(val){}// bind1st(greater<int>(), 60)bool operator()(const T& second){return _comp(_val, second); // 底层还是二元函数对象来作用的 greater}
private:Compare _comp;T _val;
};// 模拟实现bind1st
template<typename Compare,typename T>
_mybind1st<Compare,T> mybind1st(Compare com, const T& val)
{// 返回的是一元函数对象return _mybind1st<Compare,T>(com, val);
}// 实现_mybind2nd
template<typename Compare, typename T>
class _mybind2nd
{
public:_mybind2nd(Compare comp, T val):_comp(comp), _val(val){}// bind1st(greater<int>(), 60)bool operator()(const T& first){return _comp(first, _val); // 底层还是二元函数对象来作用的 greater}
private:Compare _comp;T _val;
};// 模拟实现bind2nd
template<typename Compare, typename T>
_mybind2nd<Compare, T> mybind2nd(Compare com, const T& val)
{// 返回的是一元函数对象return _mybind2nd<Compare, T>(com, val);
}int main()
{vector<int> vec;srand(time(nullptr));for (int i = 0; i < 10; i++){vec.push_back(rand() % 100 + 1);}showContainer(vec);sort(vec.begin(), vec.end()); // 默认从小到大排序showContainer(vec);// greater 二元函数对象sort(vec.begin(), vec.end(), greater<int>()); // 从大到小排序,传入函数对象showContainer(vec);// 将60按顺序插入到vector中 在vector中找第一个小于60的元素auto it1 = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), 60));if (it1 != vec.end()){vec.insert(it1, 60);}showContainer(vec);auto it2 = my_find_if(vec.begin(), vec.end(), mybind2nd(less<int>(), 60));if (it2 != vec.end()){vec.insert(it2, 60);}showContainer(vec);return 0;
}
bind1st或bind2nd绑定二元函数对象,可以生成一个一元函数对象,一元函数对象底层还是二元函数对象来实现的。
bind1st和bind2nd应用在在编程中如果想使用一个一元函数对象,但是库中没有,只有二元函数对象,我们就可以通过绑定器绑定二元函数对象的某一个参数,进而将它变成一个一元函数对象进而来使用。
bind1st和bind2nd有一个非常大的局限性,只能绑定二元函数对象,但是实际中,使用到的参数可能是三个或者以上,所以C++11就从Boost库中引入了更强大的bind绑定器。
C++11bind
std::bind是一个函数模板,可以看作是一个函数绑定器(适配器),它允许将一个可调用对象和其部分或全部参数进行绑定,生成一个新的可调用对象。这个新的可调用对象可以在后续的代码中被调用,并且可以省略掉已经绑定的参数。
// 原型如下:
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);
简单演示一下std::bind的使用方法:
#include <iostream>
#include <functional>
using namespace std;int add(int a, int b)
{return a + b;
}
struct Adder
{int operator()(int a, int b){return a + b;}
};
class Calculator
{
public:int add(int a, int b){return a + b;}
};
int main() {// 1、绑定普通函数// 使用 std::bind 绑定 add 函数和它的参数auto addFunc1 = std::bind(add, 3, 5);// 调用绑定后的可调用对象cout << addFunc1() << endl; //就相当于调用 add(3,5)// 使用占位符预留参数位置auto addFunc2 = std::bind(add, std::placeholders::_1, std::placeholders::_2);cout << addFunc2(3, 5) << endl;// 混合使用占位符和参数auto addFunc3 = std::bind(add, std::placeholders::_1, 5);cout << addFunc3(3) << endl;// 参数调换顺序auto addFunc4 = std::bind(add, std::placeholders::_2, std::placeholders::_1);cout << addFunc4(3, 5) << endl;// 2、绑定函数对象Adder adder;auto addFunc5 = std::bind(Adder(), std::placeholders::_1, std::placeholders::_2);cout << addFunc5(2, 2) << endl;auto addFunc6 = std::bind(adder, std::placeholders::_1, std::placeholders::_2);cout << addFunc6(2, 2) << endl;//相当于调用adder(1,2)// 3、绑定lambda表达式auto addFunc7 = std::bind([](int a, int b) {return a + b; }, std::placeholders::_1, std::placeholders::_2);cout << addFunc7(3, 3) << endl;// 2、绑定成员函数Calculator calc;auto addFunc8 = std::bind(&Calculator::add, &calc, std::placeholders::_1, std::placeholders::_2);cout << addFunc8(4, 4) << endl;//相当于cal.add(1,2)return 0;
}
function
bind绑定器,函数对象,Lambda表达式都只能在一条语句中使用,那么如果想在其他地方使用,该如何使用呢?可不可以留下函数对象的类型呢?C++11就引入了function。
function是一个强大的包装器,可以存储、赋值任何可调用函数,包括普通函数、函数指针、成员函数指针、函数对象(仿函数)以及 lambda 表达式。它就像一个容器,能将不同类型的可调用对象统一封装起来,以统一的方式进行调用。
我们先来看下function的简单应用:
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<functional>
#include<ctime>using namespace std;void hello1()
{cout << "hello1 world!" << endl;
}
void hello2(string str)
{cout << str << endl;
}
int sum(int a, int b)
{return a + b;
}
class Test
{
public:void hello(string str){cout << str << endl;}
};
int main()
{// 使用函数类型实例化function// 包装普通函数function<void()> func1 = hello1;func1(); // func1.operator()() => hello1()function<void(string)> func2 = hello2;func2("hello2 world!"); // func2.operator()() => hello2()function<int(int, int)> func3 = sum;cout << func3(10, 20) << endl; // func3.operator()() => sum()// 包装成员函数Test test;function<void(Test*, string)> func4 = &Test::hello;func4(&test, "Test::hello world!");// 包装函数对象function<int(int, int)> func5 = [](int a, int b)->int {return a + b; };cout << func5(100, 200) << endl; // operator()return 0;
}
- 使用函数类型实例化function
- 通过function调用operator()函数的时候,需要根据函数类型传入相应参数
但是上面的例子,好像并没有看出function的好处,还不如直接调用函数呢,下面我们实现段代码看一下使用function的好处。
#include<iostream>
#include<vector>
#include<string>
#include<map>
#include<algorithm>
#include<functional>
#include<ctime>using namespace std;void doShowAllBooks() { cout << "查看所有书籍" << endl; }
void doBack() { cout << "还书" << endl; }
void borrow() { cout << "借书" << endl; }
void doQueryBooks() { cout << "查询书籍信息" << endl; }
void dologinOut() { cout << "注销" << endl; }int main()
{int choice;map<int, function<void()>> actionMap;actionMap.insert({ 1, doShowAllBooks });actionMap.insert({ 2, doBack });actionMap.insert({ 3, borrow });actionMap.insert({ 4, doQueryBooks });actionMap.insert({ 5, dologinOut });for (;;){cout << "-----------------" << endl;cout << "查看所有书籍" << endl;cout << "还书" << endl;cout << "借书" << endl;cout << "查询书籍" << endl;cout << "注销" << endl;cout << "-----------------" << endl;cin >> choice;auto it = actionMap.find(choice);if (it == actionMap.end()){cout << "输入数字无效,请重新选择!" << endl;}else{it->second();}}// 不实用,这段代码无法闭合,无法做到“开-闭”原则/*switch (choice){case 1:cout << "查看书籍" << endl;break;case 2:cout << "还书" << endl;break;case 3:cout << "借书" << endl;break;case 4:cout << "查询" << endl;break;case 5:cout << "注销" << endl;break;default:break;}*/return 0;
}
function函数对象类型的实现原理
#include<iostream>
#include<string>
#include<functional>
using namespace std;void hello(string str)
{cout << str << endl;
}
int sum(int a, int b)
{return a + b;
}// 通用模板,作为偏特化的基础
template<typename Fty>
class myfunction {};/*
// 模板偏特化允许针对特定的模板参数类型或者参数组合,提供专门的实现
template<typename R, typename A1>
class myfunction<R(A1)>
{
public:using PFUNC = R(*)(A1);myfunction(PFUNC pfunc):_pfunc(pfunc){}R operator()(A1 arg){return _pfunc(arg); // hello("hello world!")}
private:PFUNC _pfunc;
};
template<typename R, typename A1, typename A2>
class myfunction<R(A1, A2)>
{
public:using PFUNC = R(*)(A1, A2);myfunction(PFUNC pfunc):_pfunc(pfunc){}R operator()(A1 arg1, A2 arg2){return _pfunc(arg1, arg2); // hello("hello world!")}
private:PFUNC _pfunc;
};
*/// 函数参数不确定,难道要特化很多模板出来吗?不需要,C++11提供了可变参数模板
template<typename R, typename... A>
class myfunction<R(A...)>
{
public:using PFUNC = R(*)(A...);myfunction(PFUNC pfunc):_pfunc(pfunc){}R operator()(A... arg){return _pfunc(arg...); // hello("hello world!") sum(10,20)}
private:PFUNC _pfunc;
};int main()
{myfunction<void(string)> func1(hello);func1("hello world!"); // func1.operator()("hello world!")myfunction<int(int, int)> func2(sum);cout << func2(10, 20) << endl; //func2.operator()(10,20)return 0;
}
function其实就是一个函数对象(重载了operator()函数),底层封装了一个函数指针,指向了要包装的函数,通过可变参数模板,operator()()可以接收类型及任意数量的参数,将参数传递给内部,内部使用函数指针进行函数的调用。
总结:
std::function和std::bind虽然都与可调用对象相关,但它们的功能和用途各有侧重。std::function主要用于存储和调用可调用对象,实现回调和多态;std::bind主要用于参数绑定和调整参数顺序,简化函数调用。