【c++面向对象编程】第25篇:仿函数(函数对象):重载operator()

目录

一、什么是仿函数?

为什么叫"仿函数"?

二、仿函数相比普通函数的优势

[1. 可以保存状态](#1. 可以保存状态)

[2. 可以内联,性能更好](#2. 可以内联,性能更好)

[3. 可以作为类型使用,方便模板传参](#3. 可以作为类型使用,方便模板传参)

三、仿函数在STL中的典型应用

示例1:sort自定义排序

示例2:find_if查找满足条件的元素

示例3:for_each统计和收集

四、仿函数与lambda表达式的关系

一个lambda的例子

编译器把上面的lambda转换成类似这样的仿函数

带捕获的lambda

[五、仿函数 vs 函数指针 vs lambda](#五、仿函数 vs 函数指针 vs lambda)

对比示例

六、STL中预定义的仿函数

七、常见错误

[1. 忘记包含](#1. 忘记包含)

[2. 混淆 obj() 和 obj](#2. 混淆 obj() 和 obj)

[3. 在需要可调用对象的地方传了对象而不是临时实例](#3. 在需要可调用对象的地方传了对象而不是临时实例)

[4. 忘记处理const正确性](#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应用sortfind_iffor_each等都广泛使用仿函数

  • lambda本质:编译器生成的匿名仿函数,是语法糖

  • 预定义仿函数<functional>中的greaterlessplus

💡 小作业:写一个仿函数类Filter,构造函数接收一个vector<int> thresholdoperator()接收一个整数并判断它是否在threshold中(多次查找)。用find_if找到第一个在threshold中的元素。


下一篇预告 :第26篇《对象的内存模型:成员变量与成员函数的存储分离》------对象的大小由什么决定?成员函数存在哪里?static成员和虚函数vptr放在哪?理解内存布局是写出高效C++的基础。

相关推荐
Rust语言中文社区1 小时前
【Rust日报】2026-05-14 Pyrefly v1.0 正式发布:快速的 Python 类型检查器和语言服务器
开发语言·后端·python·rust
kkeeper~2 小时前
0基础C语言积跬步之深入理解指针(4)
c语言·开发语言
周末也要写八哥2 小时前
在C++中使用预定义宏
开发语言·c++·算法
Data_Journal2 小时前
使用Python lxml轻松进行网络爬取
开发语言·php
xcLeigh2 小时前
IoTDB JDBC 完整使用教程:连接、查询、批处理与字符集配置
开发语言·数据库·qt·iotdb·查询·批处理·连接
学会870上岸华师2 小时前
C 语言程序设计——第一章课后编程题
c语言·开发语言·学习·算法
小小编程路2 小时前
新手快速学 Python 极简速成指南
开发语言·c++·python
小马过河R2 小时前
RAG检索优化策略:系统性四层框架解析
人工智能·python·算法·ai·llm·rag·问答
AI技术控3 小时前
论文解读:AE-TCN-SA——基于自编码器、TCN 与自注意力机制的锂电池内短路诊断方法
人工智能·python·深度学习·算法·机器学习·自然语言处理