C++ 仿函数(Functor)深度解析:从基础到应用
引言
在C++编程中,我们经常需要将“行为”作为参数传递给函数或算法。C语言中,我们使用函数指针来实现这一需求。但函数指针有局限性:不能携带状态、类型安全性较差。
C++提供了更优雅的解决方案——仿函数。
仿函数(Functor)是重载了operator()的类对象,它可以像普通函数一样被调用,但可以携带状态,并且支持泛型编程。
// 函数指针 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()运算符的类或结构体。
#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() |
| 状态保持 | ❌ 不支持(需静态变量) | ✅ 支持(成员变量) |
| 类型 | 函数类型 | 类类型 |
| 泛型支持 | 有限 | ✅ 模板化 |
| 内联优化 | 可能 | 更容易(编译器可见定义) |
| 作为模板参数 | 需要指针类型 | 可直接使用类型 |
三、仿函数的状态保持能力
这是仿函数相对于普通函数的最大优势。
#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等。
第二部分:仿函数的实现方式
一、基本实现
#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; }二、带状态的仿函数
#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; }三、模板化仿函数(泛型仿函数)
#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; }四、继承标准库仿函数
#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算法大量使用仿函数作为比较参数。
#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 || y | |
logical_not<T> | !x |
#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中使用自定义仿函数
#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的底层实现就是一个仿函数。
#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
第五部分:高级应用
一、累加器仿函数
#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之前,可以使用bind1st、bind2nd适配仿函数。
#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; }第六部分:完整示例——排序与统计程序
#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> | 与、或 |
三、使用建议
// 简单逻辑 → 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的底层实现就是仿函数。
学习建议:
理解
operator()重载的基本语法掌握使用仿函数作为STL算法参数
熟悉常用的STL内置仿函数
了解仿函数与Lambda的等价关系和使用场景
