仿函数(Functor)是什么?
仿函数(Functor)
仿函数是通过重载()
运算符的类或结构体的对象。这样一个对象可以像普通函数一样被调用。
仿函数通常用于需要在对象内部保留状态或多次调用时有特定行为的情况。
特点:
- 仿函数是一个类对象。
- 通过重载
()
运算符使得类对象像函数一样可以被调用。 - 可以在类中保留状态或成员变量,提供比普通函数更复杂的逻辑和功能。
- 适合在需要复用逻辑和状态的场景中使用。
示例:
#include <iostream>
using namespace std;
class Adder {
public:
Adder(int x) : value(x) {}
// 重载 () 操作符,使对象能像函数一样调用
int operator()(int y) {
return value + y;
}
private:
int value;
};
int main() {
Adder add5(5); // 创建一个仿函数对象,初始值为5
cout << add5(10) << endl; // 输出 15
return 0;
}
Lambda 表达式
Lambda 表达式是一种匿名函数,可以在函数内定义一个临时的、无名的函数。
Lambda 表达式非常适合在简单的函数逻辑下使用,不需要事先定义一个函数名称。
它主要用于简化代码、捕获上下文环境中的变量、以及函数式编程等场景。
特点:
- Lambda 表达式是一个匿名函数,直接在使用的地方定义。
- 语法简洁,适合用于临时、一次性的小函数。
- 可以捕获外部作用域中的变量,提供便捷的上下文共享能力。
- 一般用于简单的逻辑处理,不需要复杂的状态保存。
Lambda 表达式的语法:
[capture](parameters) -> return_type {
// function body
};
示例:
#include <iostream>
using namespace std;
int main() {
int x = 10;
auto add = [x](int y) {
return x + y; // 捕获外部变量x
};
cout << add(5) << endl; // 输出 15
return 0;
}
仿函数与 Lambda 的区别
-
定义形式:
- 仿函数是通过类重载
()
运算符实现的。 - Lambda 表达式是匿名函数,直接在使用时定义。
- 仿函数是通过类重载
-
状态管理:
- 仿函数类可以通过成员变量保存状态,适合需要在多次调用间保持状态的场景。
- Lambda 表达式可以通过捕获外部变量来实现类似的效果,但一般不会像仿函数那样专门用于状态管理。
-
灵活性:
- 仿函数可以通过类的复杂逻辑实现更灵活的功能。
- Lambda 表达式适合实现简单的逻辑,更加简洁明了。
-
适用场景:
- 仿函数更适合用于复杂的、需要状态维护的场景。
- Lambda 表达式适用于临时、轻量级的函数逻辑。
仿函数作为模板的默认形参,带来了以下几个好处:
1. 提高灵活性和可扩展性
仿函数相比普通函数更灵活,因为它们是类的对象,可以保存状态信息。作为模板的默认形参,仿函数允许在调用模板时传入不同的策略,而不需要更改代码逻辑。
#include <iostream>
#include <vector>
#include <algorithm>
// 定义一个仿函数
struct MultiplyByTwo {
int operator()(int x) const {
return x * 2;
}
};
// 模板函数,接受一个可调用对象
template<typename T, typename Func = MultiplyByTwo>
void transformVector(std::vector<T>& vec, Func func = Func()) {
std::transform(vec.begin(), vec.end(), vec.begin(), func);
}
int main() {
std::vector<int> v = {1, 2, 3, 4};
// 使用默认的仿函数 MultiplyByTwo
transformVector(v);
for (int i : v) {
std::cout << i << " "; // 输出: 2 4 6 8
}
std::cout << std::endl;
return 0;
}
好处:
- 默认使用
MultiplyByTwo
仿函数来实现功能,避免了重复编写相同的逻辑。
-** 如果需要不同的行为,可以传递自定义的仿函数或普通函数**。例如,传入[](int x){ return x + 1; }
这样的 lambda 表达式。
2. 支持状态的维护
仿函数可以存储状态信息,这意味着它们可以在多个调用之间共享数据。通
过仿函数,模板的默认形参不仅可以是单纯的行为,还可以携带状态信息,提供更高级的功能。
#include <iostream>
// 仿函数,带有状态信息
struct Adder {
int offset;
Adder(int o) : offset(o) {}
int operator()(int x) const {
return x + offset;
}
};
// 使用 Adder 仿函数作为默认参数
template<typename Func = Adder>
int addNumber(int x, Func func = Func(5)) { // 默认偏移量为5
return func(x);
}
int main() {
std::cout << addNumber(10) << std::endl; // 输出: 15
std::cout << addNumber(10, Adder(3)) << std::endl; // 输出: 13
return 0;
}
好处:
- 仿函数可以持有状态信息(如
offset
),这在普通函数中是无法做到的。 - 默认情况下,可以使用带有特定状态的仿函数,如果需要自定义的行为,可以传递新的仿函数实例。
3. 使用 STL 容器和算法时的便利
在标准模板库(STL)中,许多算法接受仿函数作为参数,例如 std::sort
、std::for_each
等。
使用仿函数作为模板的默认形参可以使得这些算法更加灵活和高效。
#include <iostream>
#include <vector>
#include <algorithm>
// 自定义仿函数,判断是否是偶数
struct IsEven {
bool operator()(int x) const {
return x % 2 == 0;
}
};
// 过滤偶数的函数模板,使用仿函数作为默认参数
template<typename Func = IsEven>
std::vector<int> filterEven(const std::vector<int>& vec, Func func = Func()) {
std::vector<int> result;
std::copy_if(vec.begin(), vec.end(), std::back_inserter(result), func);
return result;
}
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
// 使用默认的 IsEven 仿函数
std::vector<int> evens = filterEven(nums);
for (int n : evens) {
std::cout << n << " "; // 输出: 2 4 6
}
std::cout << std::endl;
return 0;
}
好处:
- STL 算法和容器结合仿函数使用非常方便,使代码简洁并保持高可读性。
将仿函数作为模板的默认形参的主要好处包括:
- 提高灵活性和可扩展性:可以根据需要传递不同的行为(仿函数、函数或 lambda 表达式)。
- 支持状态维护:仿函数可以携带状态信息,实现比普通函数更复杂的逻辑。
- 与 STL 算法结合:方便和高效的标准算法使用方式。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!