引言
在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之前,可以使用bind1st、bind2nd适配仿函数。
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的底层实现就是仿函数。
学习建议:
-
理解
operator()重载的基本语法 -
掌握使用仿函数作为STL算法参数
-
熟悉常用的STL内置仿函数
-
了解仿函数与Lambda的等价关系和使用场景