C++ 仿函数(Functor)深度解析:从基础到应用

引言

在C++编程中,我们经常需要将"行为"作为参数传递给函数或算法。C语言中,我们使用函数指针来实现这一需求。但函数指针有局限性:不能携带状态、类型安全性较差。

C++提供了更优雅的解决方案------仿函数

仿函数 (Functor)是重载了operator()的类对象,它可以像普通函数一样被调用,但可以携带状态,并且支持泛型编程。

cpp 复制代码
// 函数指针 vs 仿函数
int add1(int a, int b) { return a + b; }           // 普通函数

struct Add2 {
    int operator()(int a, int b) const {            // 仿函数
        return a + b;
    }
};

int main() {
    // 函数指针调用
    int r1 = add1(10, 20);
    
    // 仿函数调用(看起来和函数一样!)
    Add2 add;
    int r2 = add(10, 20);      // add.operator()(10, 20)
    
    // 匿名对象调用
    int r3 = Add2()(10, 20);
    
    return 0;
}

今天,我将从基础到高级,全面讲解C++仿函数的概念、使用方法、与Lambda表达式的关系,以及在实际开发中的应用。


第一部分:仿函数的基本概念

一、什么是仿函数?

仿函数是一个行为类似函数的对象 。它的本质是重载了operator()运算符的类或结构体。

cpp 复制代码
#include <iostream>
using namespace std;

// 定义仿函数
struct MyFunctor {
    // 重载operator()
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    MyFunctor func;              // 创建对象
    int result = func(10, 20);   // 像函数一样调用
    
    cout << "result = " << result << endl;
    
    // 匿名对象调用
    int result2 = MyFunctor()(5, 3);
    cout << "result2 = " << result2 << endl;
    
    return 0;
}

关键理解:

  • func(10, 20) 本质上是 func.operator()(10, 20) 的语法糖

  • 编译器会将对象加括号的调用转换为成员函数调用

二、仿函数与普通函数的对比

特性 普通函数 仿函数
调用方式 func() obj()
状态保持 ❌ 不支持(需静态变量) ✅ 支持(成员变量)
类型 函数类型 类类型
泛型支持 有限 ✅ 模板化
内联优化 可能 更容易(编译器可见定义)
作为模板参数 需要指针类型 可直接使用类型

三、仿函数的状态保持能力

这是仿函数相对于普通函数的最大优势。

cpp 复制代码
#include <iostream>
using namespace std;

// 普通函数:无法保持状态(只能用静态变量,且全局唯一)
int counter_func() {
    static int count = 0;
    return ++count;
}

// 仿函数:每个对象独立保持状态
struct Counter {
private:
    int count = 0;
public:
    int operator()() {
        return ++count;
    }
    
    int getCount() const { return count; }
};

int main() {
    // 普通函数:不同调用之间共享同一个静态变量
    cout << counter_func() << endl;  // 1
    cout << counter_func() << endl;  // 2
    
    // 仿函数:每个对象有独立的计数器
    Counter c1, c2;
    cout << c1() << endl;   // 1
    cout << c1() << endl;   // 2
    cout << c2() << endl;   // 1(独立计数)
    
    return 0;
}

应用场景: 统计函数被调用的次数、累加器、生成唯一ID等。


第二部分:仿函数的实现方式

一、基本实现

cpp 复制代码
#include <iostream>
using namespace std;

// 无参数仿函数
struct Hello {
    void operator()() const {
        cout << "Hello, World!" << endl;
    }
};

// 单参数仿函数
struct Square {
    int operator()(int x) const {
        return x * x;
    }
};

// 多参数仿函数
struct Add {
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    Hello()();           // 输出:Hello, World!
    
    Square sq;
    cout << sq(5) << endl;      // 25
    
    Add add;
    cout << add(10, 20) << endl; // 30
    
    return 0;
}

二、带状态的仿函数

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 带状态的仿函数:记录调用信息
struct Logger {
private:
    string name;
    int call_count = 0;
    
public:
    Logger(const string& n) : name(n) {}
    
    void operator()(const string& msg) {
        call_count++;
        cout << "[" << name << "] 第" << call_count << "次调用: " << msg << endl;
    }
    
    int getCount() const { return call_count; }
};

int main() {
    Logger log1("模块A");
    Logger log2("模块B");
    
    log1("初始化完成");
    log1("处理数据");
    log2("启动服务");
    log1("保存结果");
    
    cout << "log1调用次数: " << log1.getCount() << endl;  // 3
    cout << "log2调用次数: " << log2.getCount() << endl;  // 1
    
    return 0;
}

三、模板化仿函数(泛型仿函数)

cpp 复制代码
#include <iostream>
using namespace std;

// 泛型仿函数:支持多种类型
template<typename T>
struct Greater {
    bool operator()(const T& a, const T& b) const {
        return a > b;
    }
};

template<typename T>
struct Less {
    bool operator()(const T& a, const T& b) const {
        return a < b;
    }
};

int main() {
    Greater<int> greater_int;
    cout << greater_int(10, 5) << endl;   // 1 (true)
    cout << greater_int(3, 7) << endl;    // 0 (false)
    
    Greater<string> greater_str;
    cout << greater_str("banana", "apple") << endl;  // 1 (按字典序)
    
    Less<double> less_double;
    cout << less_double(3.14, 2.71) << endl;  // 0
    cout << less_double(2.71, 3.14) << endl;  // 1
    
    return 0;
}

四、继承标准库仿函数

cpp 复制代码
#include <iostream>
#include <functional>
#include <string>
using namespace std;

// 继承 std::binary_function(C++17前)
// C++17后,可以直接继承,但更推荐组合或使用lambda

struct CaseInsensitiveLess {
    bool operator()(const string& a, const string& b) const {
        // 不区分大小写的比较
        for (size_t i = 0; i < min(a.size(), b.size()); i++) {
            char ca = tolower(a[i]);
            char cb = tolower(b[i]);
            if (ca != cb) return ca < cb;
        }
        return a.size() < b.size();
    }
};

int main() {
    CaseInsensitiveLess cmp;
    
    cout << cmp("Apple", "apple") << endl;   // 0(相等)
    cout << cmp("apple", "Banana") << endl;  // 1(a < b)
    
    return 0;
}

第三部分:仿函数在STL中的应用

一、排序中的仿函数

STL算法大量使用仿函数作为比较参数。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 升序仿函数
struct Ascending {
    bool operator()(int a, int b) const {
        return a < b;
    }
};

// 降序仿函数
struct Descending {
    bool operator()(int a, int b) const {
        return a > b;
    }
};

// 按绝对值排序
struct ByAbs {
    bool operator()(int a, int b) const {
        return abs(a) < abs(b);
    }
};

int main() {
    vector<int> arr = {5, -2, 8, -9, 1, 3, -4};
    
    // 使用自定义仿函数排序
    cout << "升序: ";
    sort(arr.begin(), arr.end(), Ascending());
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    cout << "降序: ";
    sort(arr.begin(), arr.end(), Descending());
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    cout << "按绝对值: ";
    sort(arr.begin(), arr.end(), ByAbs());
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    return 0;
}

二、STL内置仿函数

C++标准库提供了常用的仿函数,位于<functional>头文件中。

分类 仿函数 功能
算术运算 plus<T> x + y
minus<T> x - y
multiplies<T> x * y
divides<T> x / y
modulus<T> x % y
negate<T> -x
比较运算 equal_to<T> x == y
not_equal_to<T> x != y
greater<T> x > y
less<T> x < y
greater_equal<T> x >= y
less_equal<T> x <= y
逻辑运算 logical_and<T> x && y
logical_or<T> `x
logical_not<T> !x
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

int main() {
    vector<int> arr = {5, 2, 8, 1, 9, 3};
    
    // 降序排序(使用greater仿函数)
    sort(arr.begin(), arr.end(), greater<int>());
    cout << "降序: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    // 升序排序(使用less仿函数)
    sort(arr.begin(), arr.end(), less<int>());
    cout << "升序: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    // 计算累加(使用plus仿函数)
    int sum = accumulate(arr.begin(), arr.end(), 0, plus<int>());
    cout << "总和: " << sum << endl;
    
    // 计算乘积(使用multiplies仿函数)
    int product = accumulate(arr.begin(), arr.end(), 1, multiplies<int>());
    cout << "乘积: " << product << endl;
    
    return 0;
}

三、在set/map中使用自定义仿函数

cpp 复制代码
#include <iostream>
#include <set>
#include <string>
using namespace std;

// 不区分大小写的比较仿函数
struct CaseInsensitiveCompare {
    bool operator()(const string& a, const string& b) const {
        size_t i = 0;
        while (i < a.size() && i < b.size()) {
            char ca = tolower(a[i]);
            char cb = tolower(b[i]);
            if (ca != cb) return ca < cb;
            i++;
        }
        return a.size() < b.size();
    }
};

int main() {
    // 使用自定义仿函数作为set的比较器
    set<string, CaseInsensitiveCompare> names;
    
    names.insert("Apple");
    names.insert("apple");   // 被认为是重复的
    names.insert("BANANA");
    names.insert("Banana");  // 被认为是重复的
    
    cout << "不区分大小写的set: ";
    for (const auto& name : names) {
        cout << name << " ";
    }
    cout << endl;
    // 输出:Apple BANANA(只保留第一个)
    
    return 0;
}

第四部分:仿函数 vs Lambda 表达式

C++11引入了Lambda表达式,可以非常简洁地创建匿名函数对象。Lambda的底层实现就是一个仿函数。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 仿函数版本
struct GreaterThan {
    int threshold;
    GreaterThan(int t) : threshold(t) {}
    bool operator()(int x) const {
        return x > threshold;
    }
};

int main() {
    vector<int> arr = {1, 5, 2, 8, 3, 9, 4};
    int threshold = 5;
    
    // 仿函数版本
    int count1 = count_if(arr.begin(), arr.end(), GreaterThan(threshold));
    
    // Lambda表达式版本(完全等价)
    int count2 = count_if(arr.begin(), arr.end(), [threshold](int x) {
        return x > threshold;
    });
    
    cout << "大于" << threshold << "的元素个数: " << count1 << endl;
    
    return 0;
}

仿函数 vs Lambda 对比

特性 仿函数 Lambda
代码量 较多 简洁
可读性 较好(有名字) 简单逻辑好,复杂逻辑差
复用性 可多处重用 通常单次使用
性能 相同(Lambda是语法糖) 相同
捕获状态 成员变量 捕获列表
类型 有具体名称 匿名类型
适用场景 复杂逻辑、需要重用 简单逻辑、一次性使用

选择建议:

  • 简单逻辑(一行代码)→ Lambda

  • 复杂逻辑(多行)→ 仿函数

  • 需要在多处使用 → 仿函数

  • 需要携带大量状态 → 仿函数

  • 只在当前函数内使用一次 → Lambda


第五部分:高级应用

一、累加器仿函数

cpp 复制代码
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;

// 动态累加器仿函数
template<typename T>
class Accumulator {
private:
    T sum = 0;
    int count = 0;
    
public:
    T operator()(T value) {
        sum += value;
        count++;
        return sum;
    }
    
    T getSum() const { return sum; }
    int getCount() const { return count; }
    T getAverage() const { return count ? sum / count : 0; }
    void reset() { sum = 0; count = 0; }
};

int main() {
    Accumulator<int> acc;
    vector<int> nums = {10, 20, 30, 40, 50};
    
    for (int x : nums) {
        cout << "累加和: " << acc(x) << endl;
    }
    
    cout << "总次数: " << acc.getCount() << endl;
    cout << "平均值: " << acc.getAverage() << endl;
    
    return 0;
}

二、函数适配器(预C++11)

在C++11之前,可以使用bind1stbind2nd适配仿函数。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

int main() {
    vector<int> arr = {10, 20, 30, 40, 50, 60};
    
    // C++98/03 风格:将less<int>适配为"大于30"的比较
    // 找出第一个大于30的元素
    // vector<int>::iterator it = find_if(arr.begin(), arr.end(), 
    //     bind2nd(greater<int>(), 30));
    
    // C++11后,直接使用lambda更简洁
    auto it = find_if(arr.begin(), arr.end(), [](int x) {
        return x > 30;
    });
    
    if (it != arr.end()) {
        cout << "第一个大于30的元素: " << *it << endl;
    }
    
    return 0;
}

第六部分:完整示例------排序与统计程序

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <iomanip>
using namespace std;

// 学生结构体
struct Student {
    string name;
    int score;
    int id;
};

// 按成绩降序排序
struct ByScoreDesc {
    bool operator()(const Student& a, const Student& b) const {
        return a.score > b.score;
    }
};

// 按ID升序排序
struct ByIDAsc {
    bool operator()(const Student& a, const Student& b) const {
        return a.id < b.id;
    }
};

// 按姓名排序
struct ByName {
    bool operator()(const Student& a, const Student& b) const {
        return a.name < b.name;
    }
};

// 统计成绩分区间的学生数量
struct ScoreStat {
    int excellent = 0;  // >= 90
    int good = 0;       // 80-89
    int pass = 0;       // 60-79
    int fail = 0;       // < 60
    
    void operator()(const Student& s) {
        if (s.score >= 90) excellent++;
        else if (s.score >= 80) good++;
        else if (s.score >= 60) pass++;
        else fail++;
    }
    
    void print() const {
        cout << "优秀(>=90): " << excellent << "人" << endl;
        cout << "良好(80-89): " << good << "人" << endl;
        cout << "及格(60-79): " << pass << "人" << endl;
        cout << "不及格(<60): " << fail << "人" << endl;
    }
};

// 打印学生列表
void printStudents(const vector<Student>& students, const string& title) {
    cout << "\n==== " << title << " ====" << endl;
    cout << left << setw(10) << "ID" << setw(10) << "姓名" << "成绩" << endl;
    cout << "-------------------" << endl;
    for (const auto& s : students) {
        cout << left << setw(10) << s.id << setw(10) << s.name << s.score << endl;
    }
}

int main() {
    vector<Student> students = {
        {1004, "张三", 85},
        {1002, "李四", 92},
        {1005, "王五", 67},
        {1001, "赵六", 78},
        {1003, "钱七", 55},
        {1006, "孙八", 95},
        {1007, "周九", 88}
    };
    
    // 按成绩降序
    printStudents(students, "原始数据");
    
    sort(students.begin(), students.end(), ByScoreDesc());
    printStudents(students, "按成绩降序");
    
    sort(students.begin(), students.end(), ByIDAsc());
    printStudents(students, "按ID升序");
    
    // 统计成绩分布
    ScoreStat stat = for_each(students.begin(), students.end(), ScoreStat());
    cout << "\n==== 成绩统计 ====" << endl;
    stat.print();
    
    return 0;
}

总结

一、仿函数核心要点

概念 说明
本质 重载operator()的类对象
调用方式 obj(args) 等价于 obj.operator()(args)
优势 可携带状态、性能好、类型安全
适用 STL算法参数、复杂逻辑封装

二、常用STL仿函数

分类 仿函数 功能
比较 greater<T>less<T> 大于、小于
算术 plus<T>minus<T> 加、减
逻辑 logical_and<T>logical_or<T> 与、或

三、使用建议

cpp 复制代码
// 简单逻辑 → Lambda
sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

// 复杂逻辑 → 仿函数
struct ComplexCompare {
    bool operator()(const Data& a, const Data& b) const {
        // 复杂比较逻辑...
    }
};

// 需要携带状态 → 仿函数
struct Counter {
    int count = 0;
    void operator()() { count++; }
};

// 需要复用 → 仿函数
MyFunctor func;  // 多处使用

仿函数是C++中一个重要的设计模式,它在STL中扮演着关键角色。虽然C++11的Lambda表达式在很多场景下可以替代仿函数,但理解仿函数的原理仍然重要------因为Lambda的底层实现就是仿函数。

学习建议:

  1. 理解operator()重载的基本语法

  2. 掌握使用仿函数作为STL算法参数

  3. 熟悉常用的STL内置仿函数

  4. 了解仿函数与Lambda的等价关系和使用场景

相关推荐
郝学胜-神的一滴1 小时前
跨平台动态库与头文件:从原理到命名的深度解析
linux·c++·程序人生·unix·cmake
王老师青少年编程1 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串基础】:[NOIP 2018 普及组] 标题统计
c++·字符串·csp·高频考点·信奥赛·专项训练·标题统计
小杍随笔1 小时前
Rust桌面GUI框架:性能优化与实战避坑指南
开发语言·性能优化·rust
二哈赛车手1 小时前
新人笔记---项目中简易版的RAG检索后评测指标(@Recall ,Mrr..)实现
java·开发语言·笔记·spring·ai
格林威1 小时前
3D相机视觉检测:环境光太强,结构光点云全是噪点怎么办?
开发语言·人工智能·数码相机·计算机视觉·3d·视觉检测·工业相机
Rust语言中文社区2 小时前
【Rust日报】2026-05-02 Temper - 用 Rust 编写的 Minecraft 服务器项目发布 0.1.0 版
运维·服务器·开发语言·后端·rust
冯诺依曼的锦鲤2 小时前
从零实现高并发内存池:TCMalloc 核心架构拆解
c++·学习·算法·架构
爱滑雪的码农2 小时前
Java基础十一 流(Stream)、文件(File)和IO
java·开发语言·python
叶小鸡2 小时前
Java 篇-项目实战-天机学堂(从0到1)-day11
java·开发语言