文章目录
一.仿函数介绍
1.什么是仿函数
仿函数(functor)在后面的正式命名为 函数对象(function objection),前者的意思是行为像函数的对象,后者更是直接指名其本质是一个对象,行为像是函数。
一般的函数操作即 函数名 + ( + 参数 + )
调用,为了实现相同的语法,仿函数重载了operator()
运算符,因此也可以使用实例 + ( + 参数 + )
的语法将其像是函数一样使用。
定义的例子
class functor
{
returnValue operator()( 参数 )
{
// code
}
}
仿函数实际上是对使用者关系比较大的一个类型,比如在使用unordered_map且对应数据是自定义类型(或者是pair)的时候,需要自己手写hash仿函数来作为参数传入。因此侯捷老师说的“对STL库作出扩充”,第一步往往是发生在添加仿函数上。
2.仿函数和函数的区别
既然已经有函数了,可以使用函数指针,那为什么要要有仿函数呢?
- 函数指针不能满足STL对于抽象性的要求,也不能与其他组间(比如适配器adapter搭配)----即不能提供接口(关于其不可适配的问题将在第二节介绍)
- 仿函数内部可以定义一些值作为隐性的参数,这是函数指针做不到的,通过搭配额外的数据有更灵活的效果
3.仿函数的用法
- 构建一个仿函数的实例,然后在实例后加括号来使用
- 或者直接使用对象名+() 构建临时对象,这个在进行传参的时候经常采用这个做法,比较方便。
4.仿函数的分类
- 以操作数划分
- 一元
- 二元
- 以功能划分
- 算数运算
- 关系运算
- 逻辑运算
5.仿函数定义的位置
- 常规调用是使用
<functional>
,(SGI还有一个<ext/functional>
,里面有一些非标准的仿函数) - 对应的定义位置在
<stl_function.h
处
二.可适配(adaptable)的关键
1.什么是可适配
就像iterator要提供对应的类型作为对外接口一样,仿函数因为使用类嵌套了一层,因此也需要提供类型的接口:参数的类型和返回值的类型。只有这样,在将仿函数传入仿函数适配器的时候,适配器才能正常运作。
2.作为通用接口的两个对象
就像iterator这个通用的类一样,仿函数也要两个提供模板接口的类,只需要对其进行继承即可,就可以定义标准的接口了。
unary_function
一元函数
- argument_type : 唯一一个参数的类型
- result_type : 返回值类型
binary_function
二元函数
-
first_argument_type : 第一个参数的类型
-
second_argument : 第二个参数的类型
-
result_type : 返回值类型
使用实例:
template <class T> struct plus : public binary_function <T,T,T>{ T operator()(const T&x,const T& y) const {return x + y;} }
public binary_function <T,T,T>
便是对应的接口
三.常用仿函数
1.关系运算
其实就是对其运算的调用(因此自定义类可以通过重载运算符)
- plus 加
- minus 减
- multiplies 乘
- divides 除
- modulus 取模(%)
- negate 取否定(加负号)
2.关系运算
同样是关系运算符的调用
仿函数 | 功能 |
---|---|
equai_to | 等于 |
not_equal_to | 不等于 |
greater | 大于 |
greater_equal | 大于等于 |
less | 小于 |
less_equal | 小于等于 |
3.逻辑运算
仿函数 | 功能 |
---|---|
logical_and | 逻辑与 && |
logical_or | 逻辑或 || |
logical_not | 逻辑非 ! |
4.证同,选择,投射
这三个不是C++标准库中的内容
类型 | 名称 | 作用 |
---|---|---|
证同 | identity | 返回自身 |
选择 | select1st | 取元素的first(常用于pair) |
selest2nd | 取元素的second(常用于pair) | |
投射 | project1st | 传回第一参数,忽略第二参数 |
project2nd | 传回第二参数,忽略第一参数 |