1. C++标准库中的仿函数例子
比如下面的plus
类, 重载了()
运算符, 可以这样子用plus p; p(1, 2);
, 类可以像调用函数一样的行为, 被称为仿函数.
再看看多几个例子:
然后我们自己写一个仿函数, 然后用sort调用试试:
struct myclass
{
bool operator()(int i, int j) { return i < j; }
} myobj;//声明一个变量
void test01()
{
vector<int> v{1,3,4,2,5,2};
sort(v.begin(), v.end(), myobj);
for(auto e: v){
cout << e << " ";
}
cout << endl;
}
输出是
1 2 2 3 4 5
但是这里有一个问题,
有没有发现标准库的仿函数都需要继承某个类, 这某个类是下面其中一种:
unary_function
类或者binary_function
类.
而我们的做法没有继承, 这样的不好之处是没有融入到标准库STL中.
那为什么会是不好的呢?
2. 仿函数的可适配(adaptable)条件
如下图, 要知道STL中是存在多种适配器的, 下图中包含
- 迭代器适配器
- 容器适配器
- 仿函数适配器
我们这里说仿函数适配器. 那仿函数适配器是干嘛的呢?
仿函数适配器向仿函数提问, 然后仿函数进行回答.
这个功能主要是这样实现的:
template<typename _Arg, typename _Result>
struct unary_function
{
typedef _Arg argument_type;
typedef _Result result_type;
};
template<typename _Arg1, typename _Arg2, typename _Result>
struct binary_function
{
typedef _Arg1 first_argument_type;
typedef _Arg2 second_argument_type;
typedef _Result result_type;
};
也就是把仿函数的第一个参数, (第二个参数)和返回值类型额外命了名, 方便统一调用.
下面是less的例子:
因为less仿函数有两个参数, 因此继承时需要继承binary_function
.
前面的博客里写了容器适配器, 容器适配器一共额外命名了5个类型, 如下图:
举例stack来说, 它内含deque作为底层容器, 同时需要额外命名5个类型, 这样我们在其他地方用到的时候, 就会很方便(具体例子后面会说)
让我们继续回到函数适配器中,分析一下函数适配器的具体例子:
count_if(v1.begin(), v1.end(),
not1(bind2nd(less<int>(), 40)));
- 首先我们知道
less<int>()
这样是一个对象, 当要调用它时, 其参数是需要两个的; - 后面是一个参数
40
, - 这两个参数分别作为
bind2nd
的两个参数, 看下图左上角可知, 我们传入的两个参数会被模板函数自动推断出来(其实bind2nd
在这里只是一个辅助函数, 功能就是把类型自动推断出来), 那我们就知道其中op = less<int>(), x = 40
了, 然后返回的话就创建一个binder2nd
对象. 需要注意的是: 因为x
要作为op
的第二个参数, 因此返回的时候用arg2_type(x)
将x转为arg2_type
这种类型了. - 现在我们就到了
binder2nd
类中了, 先走到构造函数中, 类把x和y分别保存为op
和value
, op为操作, value为第二个参数, 然后我们就等待 - (先忽略not1)我们现在调用
count_if
, 传入的参数分别是v1.begin(), v1.end(), 和bind2nd()
(这时候bind2nd
是一个对象, 但是同时它可以像函数一样被调用), 然后执行到pred(*first)
这里, 其实就是执行binder2nd
中的()
重载函数, 很显然我们看到, 传入的参数只有一个, 就是*first
, 然后执行的是less<int>()(x, value)
进行比较, 返回结果
以上这种利用辅助函数来推导类型, 然后函数接着调用另一个类, 类中保存操作方式和一个操作数, 此时还没有调用, 也就还没有绑定, 然后执行算法的时候, 再调用()
重载进行调用 的行为, 就被称为函数适配器. (个人理解)
接下来我们说一下not1
其实not1
和上面的差不多, 我们接着上面的来: bind2nd
之后,
not1(bind2nd...);
- 我们首先要知道, bind2nd是一个对象, 因此相当于
not1(对象)
, 这个时候我们就进入not1
辅助函数(下图左上角), 返回调用unary_negate
类创建对象, 要知道传入的参数是pred
, 也就是对象 - 现在来到
unary_negate
类, 继承的是unary_function
, 因为需要一个参数, 所以不用binary_function
. 接着构造函数将这个对象保存下来 - 执行
count_if
函数, 要知道我们传入的也是一个对象, 一个类名为unar_negate
的对象. 在pred(*first)
中, 调用对象的()
重载函数操作, 函数内部调用类中保存的pred
对象, 然后递归调用binder2nd
对象, 最后取反.
3. C++11之后出现的新型适配器:bind
以下个人觉得用起来比较麻烦, 仅列举:
用法例子: