C++ STL Tutorial
  • 📗概述
    • 🥣关于容器
    • ⬇️关于迭代器
    • 📱关于算法
    • 💿一个串联前文的例子
    • 🎍关于如何使用库的几句话
  • 🍜容器(Container)
    • 🎶string - 字符串
    • 🚃vector - 向量
    • ➿deque - 双向队列
    • 🍡stack - 栈
    • 🏁queue - 队列
    • 📜list - 链表
    • 🏵️set / multiset - 集合
    • 🗺️map / multimap - 映射
    • 🗒️容器简单小结
  • 🏭仿函数(Functor)
  • 💻算法(Algorithm)
  • 🧪写在最后
由 GitBook 提供支持
在本页
  • 函数对象 / 仿函数
  • 谓词
  • 内建函数对象
  • 算术类函数对象
  • 关系运算类函数对象
  • 逻辑运算类运算函数
  • 适配器
  • 函数对象适配器
  • 取反适配器
  • 函数指针适配器
  • 成员函数适配器
  • 偏函数

这有帮助吗?

仿函数(Functor)

在详细讲常用的算法之前补充一下函数对象的相关内容,后面会用到。

上一页容器简单小结下一页算法(Algorithm)

最后更新于3年前

这有帮助吗?

函数对象 / 仿函数

  1. 重载函数调用操作符的类,其对象常称为函数对象(function object),也叫仿函数(functor),使得类对象可以像函数那样调用。

  2. STL提供的算法往往有两个版本,一种是按照我们常规默认的运算来执行,另一种允许用户自己定义一些运算或操作,通常通过回调函数或模版参数的方式来实现,此时functor便派上了用场,特别是作为模版参数的时候,只能传类型。

  3. 函数对象超出了普通函数的概念,其内部可以拥有自己的状态(其实也就相当于函数内的static变量),可以通过成员变量的方式被记录下来。

  4. 函数对象可以作为函数的参数传递。

  5. 函数对象通常不定义构造和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用时的运行时问题。

  6. 模版函数对象使函数对象具有通用性,这也是它的优势之一。

  7. STL需要我们提供的functor通常只有一元和二元两种。

  8. lambda 表达式的内部实现其实也是仿函数

谓词

返回值为bool的普通函数或者函数对象,也就是我们离散数学中学习的predictor,比较常用的是一元谓词和二元谓词。

在概述中有提到过,这里再重复一下。

内建函数对象

使用时需要包含头文件<functional>

STL 内建了一些函数对象,分为:

  • 算术类函数对象

  • 关系运算类函数对象

  • 逻辑运算类函数对象

算术类函数对象

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>; // 取反函数

negate 是一元运算,其他都是二元运算。

关系运算类函数对象

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>; // 小于等于

逻辑运算类运算函数

template<class T> bool logical_and<T>; // 逻辑与
template<class T> bool logical_or<T>; // 逻辑或
template<class T> bool logical_not<T>; // 逻辑非

适配器

为了方便理解,我们以例子的形式来讲述这个部分的内容。

函数对象适配器

原程序

#include <iostream>
#inlcude <algorithm>
#include <vector>
using namespace std;

class myPrint
{
public:
    void operator()(int val) { cout << val << endl; }
}

int main()
{
    vector<int> v;
    for (int i = 0; i < 10; i++) v.push_back(i);
    for_each(v.begin(), v.end(), myPrint());
    return 0;
}

新需求

我们希望在每个数据输出的时候加上一个基值,并且该基值由用户输入。

使用函数适配器做的改进

#include <iostream>
#inlcude <algorithm>
#include <vector>
#include <functional>
using namespace std;

class myPrint: public binary_function<int, int, void> 
// 2.做继承 参数1类型 + 参数2类型 + 返回值类型 binary_function
{
public:
    void operator()(int val, int base) const // 3. 加const, 和父类保持一致
    {
        cout << val + base << endl;
    }
}

int main()
{
    vector<int> v;
    for (int i = 0; i < 10; i++) v.push_back(i);
    int n;
    cin >> n;
    for_each(v.begin(), v.end(), bind2nd(myPrint(), n)); 
    // 1. 将参数进行绑定 bind2nd
    // bind1st 功能类似,不过n会被绑定到第一个参数中
    return 0;
};

取反适配器

原程序

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

class GreaterThanFive
{
public:
    bool operator()(int val) { return val > 5; }
}

int main()
{
    vector<int> v;
    for (int i = 0; i < 10; i++) v.push_back(i);
    
    vector<int>::iterator pos = find_if(v.begin(), v.end(), GreaterThanFive());
    
    if (pos != v.end()) cout << *pos << endl;
    return 0;
}

新需求

我们希望找第一个不大于5的数,但又不想再写一个LessEqualThanFive。

使用取反适配器做的改进

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

class GreaterThanFive: public unary_function<int, bool> 
// 2. 做继承 参数类型 + 返回值类型 unary_function
{
public:
    bool operator()(int val) const // 3.加 const
    {
        return val > 5;
    }
}

int main()
{
    vector<int> v;
    for (int i = 0; i < 10; i++) v.push_back(i);
    
    vector<int>::iterator pos = find_if(v.begin(), v.end(), not1(GreaterThanFive()));  //1. 一元取反 not1
    
    if (pos != v.end()) cout << *pos << endl;
    return 0;
}

其实综合前面的内容会有更简便的方法:

vector<int>::iterator pos = find_if(v.begin(), v.end(), not1(bind2nd(greater<int>(), 5)));

这一行代码如果还半知半解的话还请驻足思考一番,思考透彻了上述内容才算是真的懂了。

函数指针适配器

沿用函数对象适配器的例子,假设myPrint是一个全局函数

for_each(v.begin(), v.end(), bind2nd(ptr_fun(myPrint), n));
// 函数指针适配器 ptr_fun 将函数指针适配成仿函数

成员函数适配器

我们假设有一个Dog类,Dog类内部有一个bark()成员方法,有一个装满了Dog的vector叫做v。

for_each(v.begin(), v.end(), mem_fun_ref(&Dog::bark));
// 成员函数适配器 mem_fun_ref
// 如果容器中存放的不是对象实体,而是对象指针时,则需使用 ptr_fun

偏函数

对于一个多参数的函数,在某些应用场景下,它的一些参数往往取固定值,可以针对这样的函数,生成一个新函数,该新函数不包含原函数中已指定固定值的参数。(partial function application, 偏函数)

  • 偏函数可缩小一个函数的适用范围,提高函数的针对性

例如,对于下面的print函数:

void print(int n, int base); // 按base进制来输出n

由于它常常用来按十进制输出,因此,可以基于print生成一个新函数print10,只接受一个参数n,base固定为10:

#include <functional>
using namespace std;
using namespace std::placeholders;

function<void(int)> print10 = bind(print, _1, 10);
print10(23); //相当于 print(23, 10)

function类和bind的使用需要c++11标准

至此,读者应当对于仿函数/函数对象有了一个稍微深入一点的理解了。如果还觉得有些云里雾里的话也没关系,以后熟悉了就好了。

🏭
Page cover image