大家好,这里是彩妙呀~

今天我们来聊一下STL中一种特殊函数:仿函数

什么是仿函数 --- 剖析仿函数的本质

仿函数的定义

可以这么理解:仿函数就是一个“伪装”成函数的对象

仿函数,本质上就是一个重载了 operator() 的类或结构体,是类对象,所以他可以被实例化,进而被调用。

简单举例

假设,我们要使用STL库中的排序sort(),没接触过仿函数的人可能会这么写:

//逻辑:按数字的个位数排序
bool compareByLastDigit(int a, int b) {
    return (a % 10) < (b % 10);
}

vector<int> nums = {12, 5, 23, 8, 19};
sort(nums.begin(), nums.end(), compareByLastDigit);

那么,假如我们要重新换一个逻辑:按照数字的十位数来排序,我们又要写另一个函数来传递。

在这种场景下,我们就可以使用仿函数来方便代码的复用:

struct CompareBy {
    int digit;  // 0 表示个位,1 表示十位
    CompareBy(int d) : digit(d) {}

    bool operator()(int a, int b) const {
        if (digit == 0) return (a % 10) < (b % 10);
        else return (a / 10 % 10) < (b / 10 % 10);
    }
};

// 使用
sort(nums.begin(), nums.end(), CompareBy(0));  // 按个位
sort(nums.begin(), nums.end(), CompareBy(1));  // 按十位

虽然案例有点牵强,但是STL为了方便兼容C++的特性(例如模版,类等),所以才设计了这款仿函数。

本质上还是为了代码复用:仿函数可以使用类模版来实例化,从而满足各个类型的函数调用(最为明显的就是比大小)。

仿函数的核心原理:operator()重载的运用

仿函数能够实现函数般的调用,其核心原理在于编译器对operator()重载的特殊处理。当我们编写代码对象名(参数列表)来调用仿函数时,编译器会在背后进行一次巧妙的语法转换,将其自动转换为对象名.operator()(参数列表) 。这一转换过程是仿函数能够 “模拟” 函数行为的底层逻辑,也是它区别于普通类对象的关键所在。

class Adder {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};
int main() {
    Adder add;
    int result = add(3, 5); // 等价于 int result = add.operator()(3, 5);
    std::cout << "3 + 5 = " << result << std::endl;
    return 0;
}

进阶仿函数:

仿函数类的特性:对数据的“保存”

与普通函数不同的是:仿函数的本质是类对象。这一特点从而让仿函数有了独特的优势:

我们可以通过成员变量来保存仿函数的状态(前提是这个仿函数没有被销毁)。

class CallCounter {
private:
    int callCount;
public:
    CallCounter() : callCount(0) {}
    int operator()() {
        callCount++;
        return callCount;
    }
};

CallCounter counter;
std::cout << "Call 1: " << counter() << std::endl;
std::cout << "Call 2: " << counter() << std::endl;
std::cout << "Call 3: " << counter() << std::endl;

这一特性可以使得仿函数可以处理需要记录的中间数据以及进行决策。

上述的代码就是利用类对象的特点,实现了记录调用自身仿函数的次数,并返回。

仿函数类的特性:支持泛型编程

由于仿函数是一个类对象,那么我们去定义仿函数的时候,可以使用模版技术来实现跨类型的使用:

//使用类模版来实现多种类型的转化
template <typename T>
class ThresholdChecker {
private:
    T threshold;
public:
    ThresholdChecker(const T& th) : threshold(th) {}
    bool operator()(const T& value) const {
        return value > threshold;
    }
};

有了类模版这个特性,就可以实现代码复用了,非常方便。

仿函数在STL库中的运用

可以指定STL算法的行为以及存放顺序

在STL库中,有许多算法是支持传参仿函数的:例如for_each()sort()

for_each()函数的仿函数应用:

template <class InputIterator, class Function>  
Function for_each (InputIterator first, InputIterator last, Function fn);
//这里的Function就是仿函数
#include <vector>
#include <algorithm>
#include <iostream>
// 定义一个仿函数,为每个元素加10
struct AddTen {
    void operator()(int& num) {
        num += 10;
    }
};
int main() {
    std::vector<int> nums = {1, 2, 3, 4};
    // 使用for_each和AddTen仿函数对nums中的每个元素加10
    std::for_each(nums.begin(), nums.end(), AddTen());
    for (int num : nums) {
        std::cout << num << " "; // 输出: 11 12 13 14
    }
    return 0;
}

sort()函数的仿函数应用:

//default (1)	

template <class RandomAccessIterator>  
void sort (RandomAccessIterator first, RandomAccessIterator last);

//custom (2)	

template <class RandomAccessIterator, class Compare>  
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
#include <vector>
#include <algorithm>
#include <iostream>
// 定义一个仿函数,用于降序排序
struct Descending {
    bool operator()(int a, int b) {
        return a > b;
    }
};
int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    // 使用sort和Descending仿函数对nums进行降序排序
    
    std::sort(nums.begin(), nums.end(), Descending());
    for (int num : nums) {
        std::cout << num << " "; // 输出: 9 6 5 5 5 4 3 3 2 1 1
    }
    return 0;
}

STL内置的仿函数

<functional>头文件为我们提供了丰富的内置仿函数,这些仿函数可以分为算术类、比较类和逻辑类,它们是 STL 中非常实用的工具,无需我们手动定义,即可直接使用 。

#include <vector>
#include <numeric>
#include <functional>
#include <iostream>
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    // 使用std::multiplies仿函数结合std::accumulate实现累乘
    int product = std::accumulate(numbers.begin(), numbers.end(), 1, std::multiplies<int>());
    std::cout << "Product: " << product << std::endl; // 输出: Product: 120
    return 0;
}

常用的STL库中的仿函数:

仿函数名 作用 类型 典型用途
plus<T> 加法 (a + b) 二元函数对象 数值累加、变换
minus<T> 减法 (a - b) 二元函数对象 数值计算
multiplies<T> 乘法 (a * b) 二元函数对象 乘积、缩放
divides<T> 除法 (a / b) 二元函数对象 比率计算
modulus<T> 取模 (a % b) 二元函数对象 奇偶判断、分组
negate<T> 取负 (-a) 一元函数对象 符号翻转
equal_to<T> 相等判断 (a == b) 二元谓词 查找、去重
not_equal_to<T> 不等判断 (a != b) 二元谓词 过滤、条件移除
greater<T> 大于判断 (a > b) 二元谓词 降序排序、优先队列(最大堆)
less<T> 小于判断 (a < b) 二元谓词 升序排序(默认行为)、优先队列(最小堆)
greater_equal<T> 大于等于 (a >= b) 二元谓词 范围查询
less_equal<T> 小于等于 (a <= b) 二元谓词 范围查询
logical_and<T> 逻辑与 (a && b) 二元谓词 多重条件组合
logical_or<T> 逻辑或 (a || b) 二元谓词 多重条件组合
logical_not<T> 逻辑非 (!a) 一元谓词 条件取反

简单示例:

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>   // 仿函数所在地

int main() {
    std::vector<int> v{5, 2, 8, 1, 9};

    // 1. 降序排序:使用 greater
    std::sort(v.begin(), v.end(), std::greater<int>());
    // v 变为 {9, 8, 5, 2, 1}

    // 2. 计算累加:使用 plus
    int sum = std::accumulate(v.begin(), v.end(), 0, std::plus<int>());
    // sum = 25

    // 3. 找出第一个大于5的元素:使用 greater 配合 bind(或 lambda 更直观)
    auto it = std::find_if(v.begin(), v.end(), 
                           std::bind(std::greater<int>(), std::placeholders::_1, 5));
    // *it = 8
    return 0;
}

本篇到这里就结束了,喜欢文章的小伙伴可以关注一下彩妙,我们下一篇再见~

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐