【C++ 系列笔记】07 C++ STL - 算法

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 这个命题中,小于就是一个谓词,且是一个二元谓词,因为它连接了两个客体 12

类似的,我们称 返回值为 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));

算法

算法主要涉及三个头文件:

algorithmfunctionalnumeric

遍历

遍历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) 

猜你喜欢

转载自blog.csdn.net/qq_16181837/article/details/112294498