仿函数使用

大家好,今天我们来聊聊C++里一个看似"抽象",但实际非常常用的知识点------仿函数(Functor)。很多新手刚接触时会把它和函数指针、lambda混淆,其实仿函数的本质很简单,用好它能让代码更简洁、更灵活,尤其在STL中更是"常客"。

话不多说,我们从"是什么、为什么用、怎么用"三个维度,结合具体例子,把仿函数讲透。

一、什么是仿函数?

仿函数,顾名思义,就是"模仿函数的东西"。它的核心是:一个重载了"()"运算符的类或结构体。

我们知道,函数的使用方式是"函数名(参数)",而仿函数通过重载"()",让类的对象可以像函数一样被调用,格式也是"对象名(参数)"。所以仿函数也叫"函数对象(Function Object)"。

举个最基础的例子,快速感受一下:

cpp 复制代码
#include <iostream>
// 定义一个仿函数(结构体重载(),类也可以)
struct Add {
    // 重载()运算符,参数和返回值按需定义
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    // 1. 创建仿函数对象(和普通结构体对象一样)
    Add add_obj;
    // 2. 像调用函数一样使用对象
    int result = add_obj(3, 5); // 等价于 add_obj.operator()(3,5)
    std::cout << "3 + 5 = " << result << std::endl; // 输出:8
    return 0;
}

这个Add结构体就是一个仿函数,它的对象add_obj可以像函数一样接收两个int参数,返回它们的和。

这里有个小细节:仿函数的operator()通常会加const(除非需要修改对象内部状态),因为我们一般不需要通过仿函数调用改变对象本身,这样更安全、更规范。

二、为什么要用仿函数?(对比函数指针)

可能有人会问:"我用普通函数、函数指针,或者C++11后的lambda,不也能实现同样的功能吗?为什么非要用仿函数?"

仿函数的核心优势有3个,我们结合例子对比说明:

  1. 可以保存状态:仿函数是类/结构体的对象,能拥有成员变量,用来保存数据(函数指针和普通函数做不到这一点);
  2. 编译期确定,效率更高:仿函数的调用是静态绑定,编译器能优化,比函数指针的动态绑定更快;
  3. 可适配STL算法:STL中的sort、find_if等算法,默认支持仿函数作为参数(lambda本质也是一种匿名仿函数)。

比如"保存状态"这个优势,我们看下面的例子:

cpp 复制代码
#include <iostream>
// 仿函数:统计调用次数,并返回累计和
struct CounterAdd {
private:
    // 成员变量保存状态(调用次数、累计和)
    int count = 0;
    int sum = 0;
public:
    int operator()(int num) {
        count++; // 每次调用,次数+1
        sum += num; // 累计求和
        return sum;
    }
    // 提供接口,获取保存的状态
    int get_count() const { return count; }
    int get_sum() const { return sum; }
};

int main() {
    CounterAdd counter;
    // 多次调用仿函数
    counter(10);
    counter(20);
    counter(30);
    
    // 获取保存的状态
    std::cout << "调用次数:" << counter.get_count() << std::endl; // 3
    std::cout << "累计和:" << counter.get_sum() << std::endl; // 60
    return 0;
}

这个例子中,仿函数CounterAdd通过成员变量count和sum,保存了"调用次数"和"累计和"这两个状态。如果用普通函数,只能通过全局变量实现,而全局变量会破坏代码封装性,不如仿函数优雅。

三、仿函数的常见用法(附实战示例)

仿函数最常用的场景,是搭配STL算法使用。下面我们结合几个高频场景,给出完整示例,大家可以直接复制运行。

示例1:搭配STL sort排序(自定义排序规则)

STL的sort默认是升序排序,如果我们想实现降序、按结构体某个字段排序,就可以用仿函数。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm> // sort所在头文件

// 1. 仿函数:降序排序
struct DescendCompare {
    bool operator()(int a, int b) const {
        return a > b; // 降序:a比b大则返回true,a排在前面
    }
};

// 2. 仿函数:按结构体的age字段升序排序
struct Person {
    std::string name;
    int age;
};

struct SortByAgeAsc {
    bool operator()(const Person& p1, const Person& p2) const {
        return p1.age < p2.age; // 按age升序
    }
};

int main() {
    // 测试1:int数组降序排序
    std::vector<int> nums = {3, 1, 4, 1, 5, 9};
    sort(nums.begin(), nums.end(), DescendCompare()); // 传入仿函数对象
    std::cout << "降序排序结果:";
    for (int num : nums) std::cout << num << " "; // 输出:9 5 4 3 1 1
    std::cout << std::endl;

    // 测试2:结构体数组按age升序排序
    std::vector<Person> people = {{"Alice", 25}, {"Bob", 20}, {"Charlie", 30}};
    sort(people.begin(), people.end(), SortByAgeAsc());
    std::cout << "按age升序排序结果:" << std::endl;
    for (const auto& p : people) {
        std::cout << "姓名:" << p.name << ",年龄:" << p.age << std::endl;
    }
    return 0;
}

示例2:搭配STL find_if查找(自定义查找条件)

find_if用于查找满足"某个条件"的元素,这个条件就可以用仿函数定义。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm> // find_if所在头文件

// 仿函数:查找大于10的元素
struct GreaterThan10 {
    bool operator()(int num) const {
        return num > 10;
    }
};

// 仿函数:查找姓名长度大于5的Person
struct Person {
    std::string name;
    int age;
};

struct NameLongerThan5 {
    bool operator()(const Person& p) const {
        return p.name.size() > 5;
    }
};

int main() {
    // 测试1:查找大于10的元素
    std::vector<int> nums = {5, 12, 8, 15, 3};
    auto it1 = find_if(nums.begin(), nums.end(), GreaterThan10());
    if (it1 != nums.end()) {
        std::cout << "第一个大于10的元素:" << *it1 << std::endl; // 12
    }

    // 测试2:查找姓名长度大于5的Person
    std::vector<Person> people = {{"Alice", 25}, {"Bob", 20}, {"Charlie", 30}, {"David", 28}};
    auto it2 = find_if(people.begin(), people.end(), NameLongerThan5());
    if (it2 != people.end()) {
        std::cout << "姓名长度大于5的人:" << it2->name << std::endl; // Charlie
    }
    return 0;
}

示例3:带参数的仿函数(灵活定制逻辑)

前面的仿函数,逻辑都是固定的(比如"大于10""降序")。如果我们想让逻辑更灵活,可以给仿函数的构造函数传参数,实现"自定义条件"。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

// 带参数的仿函数:查找大于n的元素(n由构造函数传入)
struct GreaterThanN {
private:
    int n; // 保存传入的参数(自定义的阈值)
public:
    // 构造函数:接收参数n,初始化成员变量
    GreaterThanN(int num) : n(num) {}

    bool operator()(int num) const {
        return num > n; // 逻辑:判断num是否大于n(n是自定义的)
    }
};

int main() {
    std::vector<int> nums = {5, 12, 8, 15, 3, 20};

    // 查找大于8的元素(传入参数8)
    auto it1 = find_if(nums.begin(), nums.end(), GreaterThanN(8));
    if (it1 != nums.end()) {
        std::cout << "第一个大于8的元素:" << *it1 << std::endl; // 12
    }

    // 查找大于15的元素(传入参数15)
    auto it2 = find_if(nums.begin(), nums.end(), GreaterThanN(15));
    if (it2 != nums.end()) {
        std::cout << "第一个大于15的元素:" << *it2 << std::endl; // 20
    }
    return 0;
}

这个例子中,仿函数GreaterThanN通过构造函数接收参数n,实现了"可自定义阈值"的查找逻辑,比固定逻辑的仿函数更灵活。

示例4:STL自带的仿函数

C++ STL已经为我们提供了很多常用的仿函数,放在头文件中,我们可以直接使用,不用自己定义。

常见的STL仿函数:

  • 算术类:plus(加法)、minus(减法)、multiplies(乘法)等;

  • 比较类:equal_to(等于)、not_equal_to(不等于)、greater(大于)等;

  • 逻辑类:logical_and(逻辑与)、logical_or(逻辑或)等。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional> // STL仿函数所在头文件

int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5, 9};

    // 1. 使用STL的greater仿函数,实现降序排序(和示例1的自定义仿函数效果一样)
    sort(nums.begin(), nums.end(), std::greater<int>());
    std::cout << "STL greater降序排序:";
    for (int num : nums) std::cout << num << " "; // 9 5 4 3 1 1
    std::cout << std::endl;

    // 2. 使用STL的plus仿函数,计算两个数的和
    std::plus<int> add;
    std::cout << "5 + 3 = " << add(5, 3) << std::endl; // 8

    // 3. 使用STL的equal_to仿函数,判断两个数是否相等
    std::equal_to<int> eq;
    std::cout << "3 == 5? " << (eq(3,5) ? "是" : "否") << std::endl; // 否
    return 0;
}

四、总结

仿函数的核心就是"重载()运算符的类/结构体",它能像函数一样被调用,又能像类一样保存状态,是C++中连接类和函数的重要工具,尤其在STL中不可或缺。

记住3个关键点:

  1. 仿函数是"对象",但能像函数一样调用;

  2. 能保存状态,比函数指针更灵活;

  3. 适配STL算法,是STL的核心组件之一。

上面的示例都可以直接复制运行,建议大家动手敲一遍,感受仿函数的用法。如果有疑问,欢迎在评论区交流~

相关推荐
Z1Jxxx2 小时前
C++ P1150 Peter 的烟
数据结构·c++·算法
是娇娇公主~2 小时前
线程池:工作窃取线程池WorkingStealingPool
c++·线程池
CheerWWW2 小时前
C++学习笔记——函数指针、Lambda表达式、谨慎使用using namespace std、命名空间
c++·笔记·学习
夜猫子ing2 小时前
如何编写一个CMakelists文件
开发语言·c++
踮起脚看烟花2 小时前
chapter10_泛型算法
c++·算法
山栀shanzhi2 小时前
C++四大常见排序对比
c++·算法·排序算法
云栖梦泽2 小时前
Linux内核与驱动:8.ioctl驱动基础
linux·c++
云栖梦泽2 小时前
Linux内核与驱动:7.从应用层 lseek() 到驱动层 .llseek,Linux 字符设备偏移控制详解
linux·c++
steins_甲乙2 小时前
从0做一个小型内存泄露检测器(2): elf文件的动态链接
c++