Algorithm-Pattern
  • Introduction
  • C & C++
    • C语言
      • C/C++编译器
      • 宏的使用
      • 编译过程
      • 指针 & 数组
      • 柔性数组
      • 函数指针 & 回调函数
      • C标准库之stdio
      • C标准库之string
      • C标准库之errno
      • C标准库之stdarg
      • C标准库之regex
    • C++基础语法
      • 自增(++) & 自减(--)
      • c语言到c++
      • 可变模板参数
      • 强制类型转换
      • C/C++类型转换的本质
      • 指针 & 引用
      • const的用法
      • static的用法
      • 重要的关键字(一)
      • 重要的关键字(二)
      • 内存申请和释放
      • 内联函数
      • 函数 & 运算符重载
      • 面向对象之封装
      • 构造函数 & 析构函数
      • 面向对象之继承
      • 面向对象之多态
      • 泛型编程
      • 异常
      • 再谈函数指针
    • C++并发编程
      • C++的锁
      • 并发与多线程
    • C++高级特性
      • 函数对象
      • 移动语义 & 完美转发
      • lambda表达式
      • RTTI技术
      • RAII技术
      • 智能指针
      • 模板的特化
      • C++静态库和动态库
      • 内存溢出和内存泄漏
    • STL基础
      • String
      • array/vector/list
      • deque/priority_queue
      • set/map
      • unordered_set/unordered_map
      • algorithm_1
      • functional
      • allocator
    • C++标准库
      • IO
      • Tuple
      • regex
      • bitset
      • numeric
    • STL深入源码
      • vector内部实现
      • deque内部实现
      • sort函数实现
    • 第三方库
      • JsonCpp
      • ProtoBuf
  • 数据结构
    • 线性表
    • 字符串
    • 栈和队列
    • 二叉树
    • 平衡二叉树
    • 平衡多路搜索树
    • 树结构的延申
    • 图
    • 二进制
    • 散列表
  • 算法基础
    • 排序算法
    • 查找算法
    • 数学问题
    • 并查集
    • 递归算法
    • 附加——主定理
    • Catalan数
  • 算法设计思想
    • 滑动窗口思想
    • BFS/DFS
    • 二分法
    • 回溯法
    • 贪心算法
    • 分治法
    • 动态规划
    • 分支限界算法
    • 有限状态机(FSM)
  • LeetCode系列
    • 死磕二叉树
    • 股票买卖问题
    • 打家劫舍问题
    • 跳跃游戏问题
    • 括号匹配问题
    • 石子游戏问题
    • 子序列问题
    • 数组 & 矩阵
    • 排列 & 组合
  • 经典算法问题
    • 几何问题
    • 区间问题
    • 背包问题
    • 石子堆问题
    • 表达式求值
  • 面试题
    • 数据结构和算法基础
    • 程序设计题
      • 实现双线程交替打印
      • C++实现读写锁
      • 实现阻塞队列
      • 实现环形队列
      • 实现线程池
      • 实现智能指针
      • 实现string类
      • 实现高性能local cache
      • 实现内存池
      • 生产者-消费者模型
      • 设计定时器
    • 经典的算法题
    • C++面试题总结
    • 面试算法题总结
由 GitBook 提供支持
在本页
  • 1、不同函数复用相同处理代码
  • 1.1、C语言的处理方式
  • 1.2、C++语言的处理方式
  • 2、优势
  • 2.1、函数对象和普通函数
  • 2.2、函数对象与函数指针
  • 2.3、函数对象与Lambda表达式
  • 3、使用场景
  • 3.1、自定义排序规则
  • 3.2、谓词函数

这有帮助吗?

  1. C & C++
  2. C++高级特性

函数对象

上一页C++高级特性下一页移动语义 & 完美转发

最后更新于4年前

这有帮助吗?

函数对象:重载了调用操作符()的类对象。当用该类对象调用此操作符时,其表现形式如同普通函数调用一般,因此取名叫函数对象。如:

class A
{
public:
    int operator() (int val)
    {
        return val > 0 ? val : -val;
    }
};

int main()
{
    A a;
    cout << a(-10);  // 10
    return 0;
}

1、不同函数复用相同处理代码

1.1、C语言的处理方式

使用函数指针和回调函数来实现代码复用。例如qsort()

#include <stdio.h>  
#include <stdlib.h>  
int arr_sort( const void *a, const void *b) {
    return *(int*)a - *(int*)b;  
}
  
int main() {  
   int arr[5] = { 4, 1, 2, 5, 6 };  
   qsort(arr, 5, sizeof(arr[0]), arr_sort);
   // qsort()函数的第四个参数为 函数指针:
   // typedef int (__cdecl* _CoreCrtNonSecureSearchSortCompareFunction)(void const*, void const*);
   for (int i = 0; i < 5; i++){  
      printf("%i\n", arr[i]);  
   }
   return 0;  
}
#include <iostream>  
#include <algorithm>  
using namespace std;  
inline bool Sort(int a, int b){
    return a > b;
}
inline void Display(int a){
    cout << a << endl;
}

int main() {  
    int arr[5] = { 4, 1, 2, 5, 6 }; 
    sort(arr, arr + 5, Sort);  
    for_each(arr, arr + 5, Display);
    return 0;   
}
#include <iostream>  
#include <algorithm>  
using namespace std;  
template<class T>
inline bool Sort(T const& a, T const& b){
    return a > b;
}

template<class T>
inline void Display(T a){
    cout << a << endl;
}

int main() {  
    int arr[5] = { 4, 1, 2, 5, 6 }; 
    sort(arr, arr + 5, Sort<int>);  
    for_each(arr, arr + 5, Display<int>);
    return 0;   
}
class Sort{  
public:  
    bool operator()(int a, int b) const {
        return a > b;
    }   
};

class Display{
public:
    void operator()(int a) const{
        cout << a << endl;
    } 
};

int main()  
{  
    int arr[5] = { 4, 1, 2, 5, 6 }; 
    sort(arr, arr + 5, Sort());  
    for_each(arr, arr + 5, Display());
    return 0;   
}
template<class T>
class Sort{  
public:  
    inline bool operator()(T const& a, T const& b) const {
        return a > b;
    }   
};

template<class T>
class Display{
public:
    inline void operator()(T const& a) const{
        cout << a << endl;
    } 
};

int main()  
{  
    int arr[5] = { 4, 1, 2, 5, 6 }; 
    sort(arr, arr + 5, Sort<int>());  
    for_each(arr, arr + 5, Display<int>());
    return 0;   
}

与普通函数相比,函数对象比函数更加灵活,函数对象有以下的优势:

  1. 函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。

  2. 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则。比如自定义容器的排序规则。

  3. 函数对象一般快于普通函数:因为函数对象一般用于模板参数,模板一般会在编译时会做一些优化。

尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。函数对象(也称“函数符”)是重载了“()”操作符的普通类的对象。

用函数对象代替函数指针有几个优点:

  • 首先,因为对象可以在内部修改而不用改动外部接口,因此设计更灵活,更富有弹性。函数对象也具备有存储先前调用结果的数据成员。在使用普通函数时需要将先前调用的结果存储在全程或者本地静态变量中,但是全程或者本地静态变量有某些我们不愿意看到的缺陷。

  • 其次,在函数对象中编译器能实现内联调用,从而更进一步增强了性能。这在函数指针中几乎是不可能实现的。

  • C++11还提供了lambda表达式来实现函数的灵活调用。

定义一个可以拥有状态的函数对象,其用于生成连续序列:

class IntSequence
{
public:
    IntSequence(int initVal) : value{ initVal } {}

    int operator()() { return ++value; }
private:
    int value;
};

// T需要支持输出流运算符
template <typename T>
class Print
{
public:
    void operator()(T elem) const
    {
        cout << elem << ' ' ;
    }
};


int main()
{
    vector<int> v(10);
    std::generate(v.begin(), v.end(), IntSequence{ 0 });
    /*  lambda实现同样效果
        int init = 0;
        std::generate(v.begin(), v.end(), [&init] { return ++init; });
    */
    std::for_each(v.begin(), v.end(), [](int x) { cout << x << ' '; });
    // std::for_each(v.begin(), v.end(), Print<int>{});
    //输出:1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    return 0;
}

可以看到,函数对象可以拥有一个私有数据成员,每次调用时递增,从而产生连续序列。同样地,你可以用lambda表达式实现类似的效果,但是必须采用引用捕捉方式。但是,函数对象可以实现更复杂的功能,而用lambda表达式则需要复杂的引用捕捉。当需要捕捉的外部变量很多时,函数对象就比较有优势。

可以看到Print<int>的实例可以传入std::for_each,其表现可以像函数一样,因此我们称这个实例为函数对象。大家可能会想,for_each为什么可以既接收lambda表达式,也可以接收函数对象,其实STL算法是泛型实现的,其不关心接收的对象到底是什么类型,但是必须要支持函数调用运算:

// for_each的类似实现
namespace std
{
    template <typename Iterator, typename Operation>
    Operation for_each(Iterator act, Iterator end, Operation op)
    {
        while (act != end)
        {
            op(*act);
            ++act;
        }
        return op;
    }
}

泛型提供了高级抽象,不论是lambda表达式、函数对象,还是函数指针,都可以传入for_each算法中。

//以普通函数的方式实现自定义排序规则
bool greater_comp(int i, int j) {
    return (i < j);
}
//以函数对象的方式实现自定义排序规则
class Less {
public:
    bool operator() (int i, int j) {
        return (i > j);
    }
};

int main() {
    std::vector<int> data{ 32, 71, 12, 45, 26, 80, 53, 33 };
    //对 32、71、12、45 进行排序
    std::sort(data.begin(), data.begin() + 4); //(12 32 45 71) 26 80 53 33
    for (std::vector<int>::iterator it = data.begin(); it != data.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << std::endl;

    //利用STL标准库提供的其它比较规则(比如 greater<T>)进行排序
    std::sort(data.begin(), data.begin() + 4, std::greater<int>()); //(71 45 32 12) 26 80 53 33
    for (std::vector<int>::iterator it = data.begin(); it != data.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << std::endl;

    //通过自定义比较规则进行排序
    std::sort(data.begin(), data.end(), greater_comp);//12 26 32 33 45 53 71 80
    for (std::vector<int>::iterator it = data.begin(); it != data.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << std::endl;

    //通过自定义比较规则进行排序
    Less less;
    std::sort(data.begin(), data.end(), less);
    for (std::vector<int>::iterator it = data.begin(); it != data.end(); ++it) {
        std::cout << *it << ' ';
    }

    return 0;
}
// 输出
12 32 45 71 26 80 53 33
71 45 32 12 26 80 53 33
12 26 32 33 45 53 71 80
80 71 53 45 33 32 26 12

谓词函数是一个返回布尔值的函数。

一元谓词函数就是只釆用一个实参的函数。使用一元谓词函数可以确定一个给定对象是否具有某些特征。

bool isEven(int i) {
    return ((i % 2) == 1);
}

class IsEven
{
public:
    bool operator()(int x)
    {
        return x % 2 == 0;
    }
}

二元谓词函数就是釆用两个形参的谓词函数。使用二元谓词函数可以确定两个对象是否以某种方式相关联。

bool lessThan(int a, int b)
{
    return a < b;
}

class LessThan
{
public:
    bool operator()(int a, int b)
    {
        return a < b;
    }
}

在调用用到函数对象的标准库算法时,除非显式地指定模板类型为传引用,否则默认情况下函数对象是按值传递的,因此,如果传递一个具有内部状态的函数对象,则被改变状态的是函数内部被复制的临时对象,函数结束后随之消失。真正传进来的函数对象状态并为改变。

class B
{
public:
    B(int n = 0) : th(n), count(1) {}
    bool operator() (int)
    {
        return count++ == th;
    }

    int getCount() const
    {
        return count;
    }
private:
    int th;
    int count;
};

int main() {
    vector<int> vec({2, 4, 5, 7, 8, 6, 9});
    B b(3);
    vector<int>::iterator iter = find_if(vec.begin(), vec.end(), b); //调用函数对象,查找第三个数字  
    cout << "3rd:" << *iter << endl;
    cout << "State:" << b.getCount() << endl;  //指向函数对象内容,但内部值却为改变
    return 0;
}

原则:

不是所有的返回布尔值的函数对象都适合作为谓词函数,因此用作谓词函数的函数对象,最好不要依赖其内部状态的改变。

1.2、C++语言的处理方式

1.2.1、函数指针方式

1.2.2、函数模板方式

1.2.3、仿函数方式

1.2.4、仿函数模板方式

2、优势

2.1、函数对象和普通函数

2.2、函数对象与函数指针

2.3、函数对象与Lambda表达式

3、使用场景

3.1、自定义排序规则

std::sort()函数的排序规则和都是可以自定义的,一种方式就是使用函数对象。如:

3.2、谓词函数

✏️
🖋️
🖋️
🐹
🐹
🐹
🐹
✏️
🖋️
🖋️
🖋️
✏️
🖋️
🖋️
关联式容器的排序规则