目录
一、什么是仿函数?
为什么叫“仿函数”?
二、仿函数相比普通函数的优势
1. 可以保存状态
2. 可以内联,性能更好
3. 可以作为类型使用,方便模板传参
三、仿函数在STL中的典型应用
示例1:sort自定义排序
示例2:find_if查找满足条件的元素
示例3:for_each统计和收集
四、仿函数与lambda表达式的关系
一个lambda的例子
编译器把上面的lambda转换成类似这样的仿函数
带捕获的lambda
五、仿函数 vs 函数指针 vs lambda
对比示例
六、STL中预定义的仿函数
七、常见错误
2. 混淆 obj() 和 obj
3. 在需要可调用对象的地方传了对象而不是临时实例
4. 忘记处理const正确性
八、这一篇的收获
一、什么是仿函数?
先看一个最简单的例子:
cpp
class Adder { public: int operator()(int a, int b) const { return a + b; } }; int main() { Adder add; int result = add(3, 5); // 像函数一样调用 cout << result; // 输出 8 }add(3, 5)看起来像函数调用,实际上调用的是add.operator()(3, 5)。这就是仿函数——行为像函数的对象。
为什么叫“仿函数”?
“函数”是指普通的函数
“仿函数”是模仿函数行为的对象
更专业的名字:函数对象(Function Object)
二、仿函数相比普通函数的优势
1. 可以保存状态
普通函数是“无记忆”的。仿函数可以拥有成员变量,记录调用历史:
cpp
class Counter { private: int count; public: Counter() : count(0) {} void operator()() { count++; cout << "被调用了 " << count << " 次" << endl; } }; int main() { Counter c; c(); // 被调用了 1 次 c(); // 被调用了 2 次 c(); // 被调用了 3 次 }2. 可以内联,性能更好
编译器更容易对仿函数进行内联优化,而函数指针通常难以内联。
3. 可以作为类型使用,方便模板传参
仿函数的类型是唯一的,可以作为模板参数传递,而函数指针的类型只取决于签名。
三、仿函数在STL中的典型应用
STL算法大量使用仿函数来定制行为。
示例1:sort自定义排序
cpp
#include <iostream> #include <vector> #include <algorithm> using namespace std; // 仿函数:降序排序 class Descend { public: bool operator()(int a, int b) const { return a > b; // a大于b时返回true } }; // 仿函数:按绝对值排序 class AbsCompare { public: bool operator()(int a, int b) const { return abs(a) < abs(b); } }; int main() { vector<int> v = {3, -5, 1, -2, 4}; // 使用仿函数进行降序排序 sort(v.begin(), v.end(), Descend()); cout << "降序: "; for (int x : v) cout << x << " "; // 5 4 3 1 -2 cout << endl; // 使用仿函数按绝对值排序 sort(v.begin(), v.end(), AbsCompare()); cout << "按绝对值: "; for (int x : v) cout << x << " "; // 1 -2 3 4 -5 cout << endl; return 0; }示例2:find_if查找满足条件的元素
cpp
#include <iostream> #include <vector> #include <algorithm> using namespace std; class IsEven { public: bool operator()(int n) const { return n % 2 == 0; } }; class Between { private: int low, high; public: Between(int l, int h) : low(l), high(h) {} bool operator()(int n) const { return n >= low && n <= high; } }; int main() { vector<int> v = {1, 3, 5, 6, 7, 9, 10, 11}; // 查找第一个偶数 auto it1 = find_if(v.begin(), v.end(), IsEven()); if (it1 != v.end()) { cout << "第一个偶数: " << *it1 << endl; // 6 } // 查找第一个在[5,9]范围内的数 Between b(5, 9); auto it2 = find_if(v.begin(), v.end(), b); if (it2 != v.end()) { cout << "第一个在[5,9]的数: " << *it2 << endl; // 5 } // 查找第一个大于10的数(临时构造仿函数对象) struct { bool operator()(int n) const { return n > 10; } } greaterThan10; auto it3 = find_if(v.begin(), v.end(), greaterThan10); if (it3 != v.end()) { cout << "第一个大于10的数: " << *it3 << endl; // 11 } return 0; }示例3:for_each统计和收集
cpp
#include <iostream> #include <vector> #include <algorithm> using namespace std; class Accumulator { private: int sum; int count; public: Accumulator() : sum(0), count(0) {} void operator()(int x) { sum += x; count++; } int getSum() const { return sum; } double getAverage() const { return count > 0 ? (double)sum / count : 0; } }; int main() { vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Accumulator acc = for_each(v.begin(), v.end(), Accumulator()); cout << "总和: " << acc.getSum() << endl; // 55 cout << "平均值: " << acc.getAverage() << endl; // 5.5 return 0; }四、仿函数与lambda表达式的关系
C++11引入了lambda表达式,它本质上就是编译器自动生成一个仿函数类。
一个lambda的例子
cpp
auto add = [](int a, int b) { return a + b; }; int x = add(3, 5); // 8编译器把上面的lambda转换成类似这样的仿函数
cpp
class __Lambda_12345 { // 编译器生成一个唯一的名字 public: auto operator()(int a, int b) const { return a + b; } }; __Lambda_12345 add; int x = add(3, 5); // 8带捕获的lambda
cpp
int multiplier = 3; auto times = [multiplier](int x) { return x * multiplier; }; cout << times(5); // 15编译器的展开(简化版):
cpp
class __Lambda_67890 { private: int multiplier; // 捕获的变量成为成员 public: __Lambda_67890(int m) : multiplier(m) {} auto operator()(int x) const { return x * multiplier; } }; int multiplier = 3; __Lambda_67890 times(multiplier); cout << times(5); // 15结论:lambda是语法糖,底层就是仿函数。理解仿函数就能理解lambda的本质。
五、仿函数 vs 函数指针 vs lambda
| 特性 | 函数指针 | 仿函数 | lambda(C++11起) |
|---|---|---|---|
| 能否保存状态 | ❌ | ✅ | ✅(通过捕获) |
| 语法简洁度 | 一般 | 繁琐 | ✅ 最简洁 |
| 内联优化 | ❌ 难以内联 | ✅ 容易内联 | ✅ 容易内联 |
| 类型安全 | 签名级别 | ✅ 每个仿函数唯一类型 | ✅ 每个lambda唯一类型 |
| 使用场景 | C风格回调 | 复杂定制/需要状态 | 大多数现代C++场景 |
对比示例
cpp
#include <iostream> #include <vector> #include <algorithm> using namespace std; // 1. 普通函数 bool isOdd(int n) { return n % 2 == 1; } // 2. 仿函数类 class IsGreaterThan { int threshold; public: IsGreaterThan(int t) : threshold(t) {} bool operator()(int n) const { return n > threshold; } }; // 3. 泛型仿函数(模板) template<typename T> class IsInRange { T low, high; public: IsInRange(T l, T h) : low(l), high(h) {} bool operator()(T val) const { return val >= low && val <= high; } }; int main() { vector<int> v = {1, 5, 8, 12, 15, 20}; // 方式1:函数指针 auto it1 = find_if(v.begin(), v.end(), isOdd); cout << "第一个奇数: " << *it1 << endl; // 方式2:仿函数 IsGreaterThan gt(10); auto it2 = find_if(v.begin(), v.end(), gt); cout << "第一个大于10的数: " << *it2 << endl; // 方式3:泛型仿函数 IsInRange<int> range(5, 15); auto it3 = find_if(v.begin(), v.end(), range); cout << "第一个在[5,15]的数: " << *it3 << endl; // 方式4:lambda(现代C++首选) auto it4 = find_if(v.begin(), v.end(), [](int n) { return n > 5 && n < 15; }); cout << "第一个在(5,15)的数: " << *it4 << endl; // lambda也能保存状态 int target = 12; auto it5 = find_if(v.begin(), v.end(), [target](int n) { return n == target; }); if (it5 != v.end()) { cout << "找到目标值: " << *it5 << endl; } return 0; }六、STL中预定义的仿函数
C++标准库提供了常用的仿函数,在<functional>头文件中:
| 分类 | 仿函数 | 作用 |
|---|---|---|
| 算术 | plus<T>、minus<T>、multiplies<T>、divides<T>、modulus<T> | 加减乘除取模 |
| 比较 | equal_to<T>、not_equal_to<T>、greater<T>、less<T> | 各种比较 |
| 逻辑 | logical_and<T>、logical_or<T>、logical_not<T> | 与或非 |
cpp
#include <iostream> #include <functional> #include <algorithm> #include <vector> using namespace std; int main() { vector<int> v = {5, 2, 8, 1, 9, 3}; // 使用 greater<int>() 降序排序 sort(v.begin(), v.end(), greater<int>()); cout << "降序: "; for (int x : v) cout << x << " "; // 9 8 5 3 2 1 cout << endl; // 使用 less<int>() 升序排序(默认) sort(v.begin(), v.end(), less<int>()); cout << "升序: "; for (int x : v) cout << x << " "; // 1 2 3 5 8 9 cout << endl; // 使用 plus 做累加 vector<int> a = {1, 2, 3}; vector<int> b = {4, 5, 6}; vector<int> result(3); transform(a.begin(), a.end(), b.begin(), result.begin(), plus<int>()); cout << "逐元素相加: "; for (int x : result) cout << x << " "; // 5 7 9 cout << endl; return 0; }七、常见错误
1. 忘记包含<functional>
使用std::plus等预定义仿函数时需要包含<functional>。
2. 混淆obj()和obj
cpp
MyFunctor f; f(); // 调用operator() f; // 这是对象本身,不是调用
3. 在需要可调用对象的地方传了对象而不是临时实例
cpp
sort(v.begin(), v.end(), Descend); // ❌ 传了类型,不是对象 sort(v.begin(), v.end(), Descend()); // ✅ 传了临时对象
4. 忘记处理const正确性
如果operator()不修改成员,应该声明为const,否则不能用于const场景。
cpp
class Bad { public: void operator()() { } // 不是const }; class Good { public: void operator()() const { } // const };八、这一篇的收获
你现在应该理解:
仿函数:重载
operator()的类,对象可以像函数一样调用优势:可以保存状态、可内联性能好、类型唯一适合模板
STL应用:
sort、find_if、for_each等都广泛使用仿函数lambda本质:编译器生成的匿名仿函数,是语法糖
预定义仿函数:
<functional>中的greater、less、plus等
💡 小作业:写一个仿函数类
Filter,构造函数接收一个vector<int> threshold,operator()接收一个整数并判断它是否在threshold中(多次查找)。用find_if找到第一个在threshold中的元素。
下一篇预告:第26篇《对象的内存模型:成员变量与成员函数的存储分离》——对象的大小由什么决定?成员函数存在哪里?static成员和虚函数vptr放在哪?理解内存布局是写出高效C++的基础。