STL
函数对象(仿函数)
函数对象是重载函数调用操作符的类实例。
函数对象根据参数数量可被称为 N 元仿函数,N 为函数对象调用所需参数个数。
重载 () 符号:
class Type {
public:
void operator()(/* args */);
};
调用:
Type objFun();
objFun();
// 匿名对象
Type()();
函数对象可以实现闭包:
class Counter {
private:
long count;
public:
Counter():a(0){}
void operator()(){
return count++;
}
};
int main(){
Counter add();
add();
}
谓词
在离散数学中,谓词指命题中表示客体关系的词。如 1 小于 2
这个命题中,小于
就是一个谓词,且是一个二元谓词,因为它连接了两个客体 1
和 2
。
类似的,我们称 返回值为 bool
的函数对象 为谓词,且 N 元谓词指 接收 N 个参数的谓词。
在 STL 中,谓词用以改变算法的策略。
例如从大到小排序:
class increase{
public:
bool operator()(int a, int b){
return a > b;
}
}
// ...
sort(v.begin(), v.end(), increase());
内建函数对象
即 STL 预先提供的谓词,这些谓词包含在一个头文件中functional
。
#include<functional>
常用谓词列举:
-
算数类
// 除 negate 为一元谓词外,其余全为二元谓词 template<class T> T plus<T> // 加法仿函数 template<class T> T minus<T> // 减法仿函数 template<class T> T multiplies<T> // 乘法仿函数 template<class T> T divides<T> // 除法仿函数 template<class T> T modulus<T> // 取模仿函数 template<class T> T negate<T> // 取反仿函数
-
关系类
// 均为二元谓词 template<class T> bool equal_to<T> // 等于 template<class T> bool not_equal_to<T> // 不等于 template<class T> bool greater<T> // 大于 template<class T> bool greater_equal<T> // 大于等于 template<class T> bool less<T> // 小于 template<class T> bool less_equal<T> // 小于等于
-
逻辑类
// 除 not 为一元谓词外,其余均为二元谓词 template<class T> bool logical_and<T> // 逻辑与 template<class T> bool logical_or<T> // 逻辑或 template<class T> bool logical_not<T> // 逻辑非
使用示例:
以sort
从大到小排序为例:
sort(v.begin(), v.end(), greater<int>());
适配器
感觉适配器更像是一种包装器,用于包装函数对象,完成不同的扩展功能。
函数对象适配器
bind1st
bind2nd
函数对象适配器用于扩展函数对象的功能,提供额外传入参数的方法。
例如,我现在有一个一元谓词:
class Pred {
public:
bool operator()(int a) {
// ...
}
};
现在调用某算法,该算法会使用这个一元谓词。
algorithm(..., ..., Pred());
但与此同时,我们希望这个谓词可以额外实现一个功能,而这个功能依赖一个临时传入的参数。
于是改了谓词的实现:
bool operator()(int a, string b){
// ...
}
但这个新参数b
是无法在调用algorithm(..., ..., Pred());
时传进来的。
此时就要用到适配器了。
基本步骤:
让这个谓词的类继承一个类模板binary_function<{参数1类型, 参数2类型, 返回值类型}>
,同时还要用const
修饰该谓词为一个常函数(实际上是重写了父类的一个方法)。
注意,一元谓词继承unary_function
二元谓词继承binary_function
class Pred: public binary_function<int, string, bool>{
public:
bool operator()(int a, string b) const {
// ...
}
};
然后,在调用算法时,
通过一个函数bind2nd
,将第二个参数绑定给适配器,同时将这个参数传入。
algorithm(..., ..., bind2nd(Pred(), "param");
另外,我们也可以使用函数bind1st
,将第一个参数绑定给适配器,此时额外的参数将传入第一个参数的位置,而算法则会使用第二个参数。
bind1st 不仅可以扩展一元谓词,也可以绑定二元谓词,将其变为一元谓词
例如下面的代码意为 x > 5 的谓词:
bind2nd(greater<int>(), 1);
底层实现
就函数对象适配器来说,如果不了解其实现原理,往往很难一下子记住使用步骤。
比如,为什么要继承,为什么模板类型列表是<{参数1类型, 参数2类型, 返回值类型}>
,
又比如,bind1st
做了什么工作?
由于这学期一直在搞 web 方向的东西,免不了要写 js,写 js 就免不了接触链式作用域。
这个东西虽然强大灵活,但也很难真正理解和应用。虽然四处踩坑,但也训练了我相应的能力。
不说废话了,进入正题。
当我看到bind2nd(Pred(), "param")
,就立刻想到了包装,写 js 总免不了要把一些东西包装起来,扩展功能。
读了一下库里的实现代码,发现它大概是做了这些事情,与我想的差不多:
返回一个闭包,将函数对象和额外的参数放在外部作用域,当算法调用时,将额外参数传入。
用 js 描述如下:
function bind2nd(fun, extraParam){
return function(param){
return fun(param, extraParam);
}
}
而在 C++ 里,它是返回了一个类的实例,这个实例通过函数对象及额外参数进行构造,随后在里面进行 () 的重写,包装我们的函数对象。
实际上到这里就已经很清楚了,不过之后我却一直在思考,为什么还有一个继承的步骤,为什么要继承。
实际上,js 写到这里也就结束了,但 C++ 是静态类型的语言,在 js 中习以为常的随便传参随便返回,什么 undefined、null 等等,在 C++ 中必须要有明确地定义。
那么实际上,这个继承就是在处理参数类型及返回值类型。
再次分析了一下实现代码,发现他做了这些工作:
我们要继承的类binary_function
是一个类模板,继承后进行实例化bind2nd(Pred(), "param")
。
此时父类通过using
别名指定,将参数及返回值类型记录下来,这些被记录下来的值在bind2nd
包装时被使用,用作 () 的重写。
两个类,一个作类型记录,一个作调用包装,就这样配合着完成适配器的工作。
大概手写一下:
这是大概是包装类的实现:
tmelate<class funType>
class bind2nd {
public:
using p1Type = funType::p1Type;
using p2Type = funType::p2Type;
using resultType = funType::resultType;
binary_function(funType& fun, p2Type extraParam): fun(fun), extraParam(extraParam) {};
resultType operator()(const p1Type& arg) const {
return fun(arg, value);
}
protected:
funType fun;
p2Type extraParam; // the right operand
};
这个大概是我们要继承的类的实现:
template<class arg1, class arg2, class result>
class binary_function {
public:
using p1Type = typename arg1;
using p2Type = typename arg2;
using resultType = typename result;
};
取反适配器
not1
not2
一元谓词的取反及二元谓词的取反。
取反就是表面意思,将返回值取反。
例如,目前有一个 x > 5 的谓词:
class Pred {
public:
bool operator()(int a) const {
return a > 5;
}
};
当该谓词经取反适配器包装后,它就变成了非 x > 5 即 x <= 5 的谓词:
not1(Pred());
同时需要继承类unary_function
class Pred: public unary_function<int, bool>;
函数指针适配器
ptr_fun
针对普通函数的适配器,将函数指针适配(包装)为一个函数对象,以使用其他针对函数对象的适配器。
ptr_fun(callback);
可以直接用,也不用做继承,ptr_fun 在底层已经做好了继承。
成员函数适配器
mem_fun_ref
mem_fun
用函数对象将成员函数进行包装。
遍历值容器使用前者,遍历指针容器使用后者,其在包装的时候稍微有一些不同。
mem_fun_ref(&Type::method);
可以用于批量调用成员方法:
for_each(v.begin(), v.end(), mem_fun_ref(&Type::method));
算法
算法主要涉及三个头文件:
algorithm
、functional
、numeric
遍历
遍历for_each
/*
遍历算法 遍历容器元素
@param _First 开始迭代器
@param _Last 结束迭代器
@param _Func 函数回调或者函数对象
@return 函数对象
*/
template <class _InIt, class _Fn>
_Fn for_each(_InIt _First, _InIt _Last, _Fn _Func)
计算accumulate
/*
accumulate 计算容器元素累计总和
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Val 累加值
*/
template <class _InIt, class _Ty, class _Fn>
_Ty accumulate(const _InIt _First, const _InIt _Last, _Ty _Val)
/*
accumulate 计算容器元素累计总和
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Val 累加值
@param _Reduce_op 回调函数或者谓词(@param sum, @param elem, @return sum)传入当前累加值和当前元素,返回累加值
*/
template <class _InIt, class _Ty, class _Fn>
_Ty accumulate(const _InIt _First, const _InIt _Last, _Ty _Val, _Fn _Reduce_op)
增删改、替换、拷贝
拷贝copy
/*
copy算法 将容器内指定范围的元素拷贝到另一容器中
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Dest 目标起始迭代器
*/
template <class _InIt, class _OutIt>
_OutIt copy(_InIt _First, _InIt _Last, _OutIt _Dest)
有趣的用法:
#include <iterator>
// 遍历并输出容器的内容(将容器内容 copy 到流中)
copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
替换replace
/*
replace 将容器内指定范围的旧元素修改为新元素
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Oldval 旧元素
@param _Newval 新元素
*/
template <class _FwdIt, class _Ty>
void replace(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Oldval, const _Ty& _Newval)
替换replace_if
/*
replace_if算法 将容器内指定范围满足条件的元素替换为新元素
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Pred 函数回调或者谓词(返回Bool类型的函数对象)
@param _Val 新元素
*/
template <class _FwdIt, class _Pr, class _Ty>
void replace_if(const _FwdIt _First, const _FwdIt _Last, _Pr _Pred, const _Ty& _Val)
交换swap
/*
swap 互换两个容器的元素
@param _Left 容器1
@param _Right 容器2
*/
template <class _Fty>
void swap(function<_Fty>& _Left, function<_Fty>& _Right)
拷贝transform
一个容器拷贝给另一个容器:
/*
transform 将指定容器区间元素搬运到另一容器中
注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
@param _First 源容器开始迭代器
@param _Last 源容器结束迭代器
@param _Dest 目标容器开始迭代器
@param _Func 回调函数或者函数对象
@return 返回目标容器迭代器
*/
template <class _InIt, class _OutIt, class _Fn>
_OutIt transform(const _InIt _First, const _InIt _Last, _OutIt _Dest, _Fn _Func)
两容器交互拷贝给第三个容器:
/*
transform 将指定容器区间元素搬运到另一容器中
注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
@param _First1 源容器1开始迭代器
@param _Last1 源容器1结束迭代器
@param _First2 源容器2开始迭代器
@param _Dest 目标容器开始迭代器
@param _Func 回调函数或者函数对象
@return 返回目标容器迭代器
*/
template <class _InIt1, class _InIt2, class _OutIt, class _Fn>
_OutIt transform(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _First2, _OutIt _Dest, _Fn _Func)
拼接merge
/*
merge 容器元素合并,并存储到另一容器中
@param _First1 容器1开始迭代器
@param _Last1 容器1结束迭代器
@param _First2 容器2开始迭代器
@param _Last2 容器2结束迭代器
@param _Dest 目标容器开始迭代器
@param _Pred 回调函数或者函数对象
*/
template <class _InIt1, class _InIt2, class _OutIt, class _Pr>
_OutIt merge(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred)
填充fill
/*
fill 向容器中添加元素
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Val 填充元素
*/
template <class _FwdIt, class _Ty>
void fill(const _FwdIt _First, const _FwdIt _Last, const _Ty& _Val)
查找
查找find
/*
find 查找元素
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Val 查找的元素
@return 返回查找元素的位置
*/
template <class _InIt, class _Ty>
_InIt find(_InIt _First, const _InIt _Last, const _Ty& _Val)
查找find_if
/*
find_if 条件查找
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Pred 回调函数或者谓词(@return bool 找到返回 true)
@return 返回查找元素的位置
*/
template <class _InIt, class _Pr>
_InIt find_if(_InIt _First, const _InIt _Last, _Pr _Pred)
查找adjacent_find
/*
adjacent_find 查找相邻重复元素
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Pred 回调函数或者谓词 (@return bool 找到返回 true)
@return 返回相邻元素的第一个位置的迭代器
*/
template <class _FwdIt, class _Pr>
_FwdIt adjacent_find(const _FwdIt _First, _FwdIt _Last, _Pr _Pred)
template <class _FwdIt, class _Pr>
_FwdIt adjacent_find(const _FwdIt _First, _FwdIt _Last)
查找binary_search
二分查找,要求容器必须有序。
/*
binary_search算法 二分查找法
注意: 在无序序列中不可用
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Val 查找的元素
@param _Pred 回调函数或者谓词 (@param value 查找的元素 @param preVal 当前元素 @return bool 找到返回 true)
@return bool 是否找到
*/
template <class _FwdIt, class _Ty, class _Pr>
bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val, _Pr _Pred);
template <class _FwdIt, class _Ty, class _Pr>
bool binary_search(_FwdIt _First, _FwdIt _Last, const _Ty& _Val);
数量count
/*
count 统计元素出现次数
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Val 查找的值
@return 返回元素个数
*/
template <class _InIt, class _Ty>
size_t count(const _InIt _First, const _InIt _Last, const _Ty& _Val)
数量count_if
/*
count_if 统计元素出现次数
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
@param _Pred 回调函数或者谓词(@return bool 找到返回 true)
@return 返回元素个数
*/
template <class _InIt, class _Pr>
size_t count_if(_InIt _First, _InIt _Last, _Pr _Pred)
下标distance
/*
distance 通过迭代器获取下标位置
@param _First 头迭代器
@param _Last 尾迭代器
@return 返回尾迭代器的下标
*/
template <class _InIt>
size_t distance(_InIt _First, _InIt _Last)
顺序操作
排序sort
/*
排序算法 默认从小到大
@param _First 源容器开始迭代器
@param _Last 源容器结束迭代器
@param _Pred 回调函数或者函数对象
@return void
*/
template <class _RanIt, class _Pr>
void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred)
洗牌random_shuffle
/*
random_shuffle 对指定范围内的元素随机调整次序
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
*/
template <class _RanIt>
void random_shuffle(_RanIt _First, _RanIt _Last)
翻转reverse
/*
reverse 反转指定范围的元素
@param _First 容器开始迭代器
@param _Last 容器结束迭代器
*/
template <class _BidIt>
void reverse(const _BidIt _First, const _BidIt _Last)
集合操作
交集运算set_intersection
/*
set_intersection 求两个 set 集合的交集
注意:两个集合必须是有序序列
@param _First1 容器1开始迭代器
@param _Last1 容器1结束迭代器
@param _First2 容器2开始迭代器
@param _Last2 容器2结束迭代器
@param _Dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
template <class _InIt1, class _InIt2, class _OutIt>
_OutIt set_intersection(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest)
/*
set_intersection 求两个 set 集合的交集
注意:两个集合必须是有序序列
@param _First1 容器1开始迭代器
@param _Last1 容器1结束迭代器
@param _First2 容器2开始迭代器
@param _Last2 容器2结束迭代器
@param _Dest 目标容器开始迭代器
@param _Pred 回调函数或者谓词(@param elem2, @param elem1, @return bool)传入两个元素,相等返回 true
@return 目标容器的最后一个元素的迭代器地址
*/
template <class _InIt1, class _InIt2, class _OutIt, class _Pr>
_OutIt set_intersection(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred)
并集运算set_union
/*
set_union 求两个 set 集合的并集
注意:两个集合必须是有序序列
@param _First1 容器1开始迭代器
@param _Last1 容器1结束迭代器
@param _First2 容器2开始迭代器
@param _Last2 容器2结束迭代器
@param _Dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
template <class _InIt1, class _InIt2, class _OutIt, class _Pr>
_OutIt set_union(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred)
/*
set_union 求两个 set 集合的并集
注意:两个集合必须是有序序列
@param _First1 容器1开始迭代器
@param _Last1 容器1结束迭代器
@param _First2 容器2开始迭代器
@param _Last2 容器2结束迭代器
@param _Dest 目标容器开始迭代器
@param _Pred 回调函数或者谓词(@param elem2, @param elem1, @return bool)传入两个元素,相等返回 true
@return 目标容器的最后一个元素的迭代器地址
*/
template <class _InIt1, class _InIt2, class _OutIt, class _Pr>
_OutIt set_union(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred)
注意,返回的迭代器不一定是 v.end(),它是集合运算完毕后有效数据的最大上限
差集运算set_difference
/*
set_difference 求两个 set 集合的差集
注意:两个集合必须是有序序列
@param _First1 容器1开始迭代器
@param _Last1 容器1结束迭代器
@param _First2 容器2开始迭代器
@param _Last2 容器2结束迭代器
@param _Dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
template <class _InIt1, class _InIt2, class _OutIt>
_OutIt set_difference(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest)
/*
set_difference 求两个 set 集合的差集
注意:两个集合必须是有序序列
@param _First1 容器1开始迭代器
@param _Last1 容器1结束迭代器
@param _First2 容器2开始迭代器
@param _Last2 容器2结束迭代器
@param _Dest 目标容器开始迭代器
@param _Pred 回调函数或者谓词(@param elem2, @param elem1, @return bool)传入两个元素,相等返回 true
@return 目标容器的最后一个元素的迭代器地址
*/
template <class _InIt1, class _InIt2, class _OutIt, class _Pr>
_OutIt set_difference(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _OutIt _Dest, _Pr _Pred)