C++ 仿函数详解:让对象像函数一样调用

前言

在 C++ 中,仿函数(Functor) 是指重载了 operator() 的类或结构体的对象,它们的行为类似于普通函数,因此可以像函数一样被调用。仿函数在 STL 算法、回调机制、函数适配器等场景中有着广泛的应用。本文将深入探讨仿函数的概念、优点、使用方式,并结合具体示例进行详细解析。


1. 为什么需要仿函数?

在 C++ 中,我们可以用普通函数或 std::function(C++11 引入)来定义可调用对象,但仿函数相比之下有以下优势:

  • 状态存储:普通函数无法存储状态,而仿函数可以在对象内部维护状态,例如计数器、阈值等。
  • 性能优化 :由于仿函数是类的实例,可以通过内联优化减少函数调用的开销。
  • 与 STL 兼容 :STL 容器和算法广泛使用仿函数,如 std::sort() 可接受仿函数作为自定义排序规则。

2. 仿函数的基本用法

要定义一个仿函数,需要在类或结构体中重载 operator(),示例如下:

cpp 复制代码
#include <iostream>

// 定义仿函数类
struct Add {
    int operator()(int a, int b) {
        return a + b;
    }
};

int main() {
    Add add; // 创建仿函数对象
    std::cout << "3 + 5 = " << add(3, 5) << std::endl; // 像函数一样调用
    return 0;
}

解析

  • operator() 使 Add 对象 add 变成可调用对象 ,类似于普通函数 add(3, 5)
  • operator() 可以接受参数,并返回计算结果。

3. 具有状态的仿函数

仿函数可以存储状态,使其在多个调用间保持数据。例如,创建一个计算调用次数的仿函数:

cpp 复制代码
#include <iostream>

class Counter {
private:
    int count;
public:
    Counter() : count(0) {}  // 初始化计数器为 0

    int operator()(int value) {
        count++;
        return count * value; // 使用 count 影响计算结果
    }

    int getCount() const { return count; }
};

int main() {
    Counter counter;
    std::cout << counter(10) << std::endl; // 第 1 次调用
    std::cout << counter(10) << std::endl; // 第 2 次调用
    std::cout << "调用次数:" << counter.getCount() << std::endl;
    return 0;
}

解析

  • count 作为成员变量存储状态,每次调用 operator() 都会递增 count
  • 这在 STL 算法、回调机制等场景非常有用。

4. STL 算法中的仿函数

STL 算法通常需要比较、变换、筛选等规则,这时候自定义仿函数特别有用。例如,自定义排序规则:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

// 自定义比较规则(降序)
struct Compare {
    bool operator()(int a, int b) {
        return a > b; // 降序排序
    }
};

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 3};
    
    std::sort(vec.begin(), vec.end(), Compare()); // 传递仿函数对象

    for (int num : vec) {
        std::cout << num << " ";
    }
    return 0;
}

解析

  • std::sort() 默认是升序排序,我们自定义 Compare 作为降序比较规则
  • std::sort(vec.begin(), vec.end(), Compare()); 传递了 Compare 类型的临时对象作为排序准则

5. STL 提供的标准仿函数

C++ STL 提供了一些标准仿函数 ,主要在 <functional> 头文件中,例如:

  • 算术运算仿函数std::plus<T>std::minus<T>std::multiplies<T>std::divides<T> 等。
  • 关系运算仿函数std::greater<T>std::less<T>std::equal_to<T> 等。
  • 逻辑运算仿函数std::logical_and<T>std::logical_or<T> 等。

示例:使用 std::greater<> 进行降序排序:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional> // 包含标准仿函数

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 3};
    
    std::sort(vec.begin(), vec.end(), std::greater<int>()); // 使用标准仿函数降序排序

    for (int num : vec) {
        std::cout << num << " ";
    }
    return 0;
}

解析

  • std::greater<int>() 作为 std::sort 的比较函数,与我们自己写的 Compare 作用类似。

6. Lambda 取代仿函数(C++11)

C++11 引入了 Lambda 表达式,使得代码更加简洁,许多仿函数的使用场景可以用 Lambda 代替。例如:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 3};

    // 使用 Lambda 进行降序排序
    std::sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; });

    for (int num : vec) {
        std::cout << num << " ";
    }
    return 0;
}

为什么使用 Lambda?

  • 减少代码量 :无需单独定义 struct 作为仿函数类。
  • 提高可读性 :Lambda 直接在 std::sort() 处定义逻辑,代码更直观。

尽管 Lambda 更简洁,但仿函数在需要存储状态复用代码跨多个地方使用时仍然是很好的选择。


7. 总结

特性 普通函数 Lambda 仿函数
是否可存储状态 ❌ 否 ⚠️ 仅限闭包捕获 ✅ 是
是否可复用 ✅ 是 ❌ 否(仅局部作用域) ✅ 是
性能优化 ⚠️ 可能无法内联 ✅ 内联优化 ✅ 内联优化
适用场景 一般计算 简单的一次性逻辑 STL、回调、复杂逻辑

什么时候选择仿函数?

  • 需要存储状态(例如计数器)。
  • 需要复用(多个地方使用相同逻辑)。
  • 需要STL 兼容性 (如 std::sort())。
  • 需要高效优化(内联)。

仿函数是 C++ 语言中的重要概念,它使得对象可以像函数一样调用,并在 STL 算法、回调、状态存储等场景中发挥重要作用。虽然 C++11 引入的 Lambda 使代码更加简洁,但仿函数在某些特定场景(如 STL 和状态保持)下仍然不可替代。

如果你对 C++ STL、Lambda 或智能指针等话题感兴趣,可以查看相关的深入文章! 🚀

相关推荐
猷咪7 小时前
C++基础
开发语言·c++
IT·小灰灰7 小时前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧7 小时前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q7 小时前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳07 小时前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾7 小时前
php 对接deepseek
android·开发语言·php
CSDN_RTKLIB7 小时前
WideCharToMultiByte与T2A
c++
2601_949868367 小时前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
星火开发设计7 小时前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
蒹葭玉树7 小时前
【C++上岸】C++常见面试题目--操作系统篇(第二十八期)
linux·c++·面试