目录一、什么是仿函数为什么叫“仿函数”二、仿函数相比普通函数的优势1. 可以保存状态2. 可以内联性能更好3. 可以作为类型使用方便模板传参三、仿函数在STL中的典型应用示例1sort自定义排序示例2find_if查找满足条件的元素示例3for_each统计和收集四、仿函数与lambda表达式的关系一个lambda的例子编译器把上面的lambda转换成类似这样的仿函数带捕获的lambda五、仿函数 vs 函数指针 vs lambda对比示例六、STL中预定义的仿函数七、常见错误1. 忘记包含2. 混淆 obj() 和 obj3. 在需要可调用对象的地方传了对象而不是临时实例4. 忘记处理const正确性八、这一篇的收获一、什么是仿函数先看一个最简单的例子cppclass 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. 可以保存状态普通函数是“无记忆”的。仿函数可以拥有成员变量记录调用历史cppclass 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算法大量使用仿函数来定制行为。示例1sort自定义排序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() { vectorint 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; }示例2find_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() { vectorint 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; }示例3for_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() { vectorint 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表达式的关系C11引入了lambda表达式它本质上就是编译器自动生成一个仿函数类。一个lambda的例子cppauto add [](int a, int b) { return a b; }; int x add(3, 5); // 8编译器把上面的lambda转换成类似这样的仿函数cppclass __Lambda_12345 { // 编译器生成一个唯一的名字 public: auto operator()(int a, int b) const { return a b; } }; __Lambda_12345 add; int x add(3, 5); // 8带捕获的lambdacppint multiplier 3; auto times [multiplier](int x) { return x * multiplier; }; cout times(5); // 15编译器的展开简化版cppclass __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特性函数指针仿函数lambdaC11起能否保存状态❌✅✅通过捕获语法简洁度一般繁琐✅ 最简洁内联优化❌ 难以内联✅ 容易内联✅ 容易内联类型安全签名级别✅ 每个仿函数唯一类型✅ 每个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. 泛型仿函数模板 templatetypename 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() { vectorint 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泛型仿函数 IsInRangeint range(5, 15); auto it3 find_if(v.begin(), v.end(), range); cout 第一个在[5,15]的数: *it3 endl; // 方式4lambda现代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头文件中分类仿函数作用算术plusT、minusT、multipliesT、dividesT、modulusT加减乘除取模比较equal_toT、not_equal_toT、greaterT、lessT各种比较逻辑logical_andT、logical_orT、logical_notT与或非cpp#include iostream #include functional #include algorithm #include vector using namespace std; int main() { vectorint v {5, 2, 8, 1, 9, 3}; // 使用 greaterint() 降序排序 sort(v.begin(), v.end(), greaterint()); cout 降序: ; for (int x : v) cout x ; // 9 8 5 3 2 1 cout endl; // 使用 lessint() 升序排序默认 sort(v.begin(), v.end(), lessint()); cout 升序: ; for (int x : v) cout x ; // 1 2 3 5 8 9 cout endl; // 使用 plus 做累加 vectorint a {1, 2, 3}; vectorint b {4, 5, 6}; vectorint result(3); transform(a.begin(), a.end(), b.begin(), result.begin(), plusint()); cout 逐元素相加: ; for (int x : result) cout x ; // 5 7 9 cout endl; return 0; }七、常见错误1. 忘记包含functional使用std::plus等预定义仿函数时需要包含functional。2. 混淆obj()和objcppMyFunctor f; f(); // 调用operator() f; // 这是对象本身不是调用3. 在需要可调用对象的地方传了对象而不是临时实例cppsort(v.begin(), v.end(), Descend); // ❌ 传了类型不是对象 sort(v.begin(), v.end(), Descend()); // ✅ 传了临时对象4. 忘记处理const正确性如果operator()不修改成员应该声明为const否则不能用于const场景。cppclass 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构造函数接收一个vectorint thresholdoperator()接收一个整数并判断它是否在threshold中多次查找。用find_if找到第一个在threshold中的元素。下一篇预告第26篇《对象的内存模型成员变量与成员函数的存储分离》——对象的大小由什么决定成员函数存在哪里static成员和虚函数vptr放在哪理解内存布局是写出高效C的基础。