什么是C++中的函数对象?
概念
C++ 中的 函数对象(Function Object),也称为 仿函数(Functor),是指一个可以被当作函数来使用的对象。具体而言,它是一个重载了 operator() 的类或结构体实例。通过这种方式,函数对象可以像普通函数那样被调用,但它们同时也能拥有状态(成员变量)和其他操作。
特点
- 可调用性:函数对象通过重载 operator() 方法,使得一个类的实例可以被调用,就像函数一样。
#include <iostream>
class Adder {
public:
// 成员变量,用于存储一个加数
int num;
// 构造函数
Adder(int n) : num(n) {}
// 重载 operator()
int operator()(int x) {
return x + num; // 返回参数与 num 的和
}
};
int main() {
Adder add5(5); // 创建一个 Adder 对象,初始化 num 为 5
// 使用函数对象调用
std::cout << "5 + 3 = " << add5(3) << std::endl; // 输出:5 + 3 = 8
std::cout << "5 + 10 = " << add5(10) << std::endl; // 输出:5 + 10 = 15
return 0;
}
//结果:
5 + 3 = 8
5 + 10 = 15
- 状态:函数对象可以持有状态(数据成员),可以在调用时根据其内部状态执行不同的操作。
- 灵活性:函数对象可以拥有人类自定义的逻辑,可以内置复杂的行为。
class less
{
public :
less(int num) : n(num) {}
bool operator()(int value)
{
return value < n;
}
private :
int n;
};
调用
less isLess(10);
cout << isLess(9) << " " << isLess(12); // 输出 1 0
//9比10小返回true(1),12比10大返回false(0),
//isless()是一个标准库函数在<cmath>中定义,是用来比较数的大小的,标准用法应该是less(x,y),如果x<y返回true,否则返回false。(上述代码用了函数对象的写法,所以是这样写的),单独用isless函数,还是要用isless(x,y)格式当然还有一种存在有NaN的情况,
//less(x,NaN),无论NaN在那个参数上,都返回false,因为NaN 与任何值的比较结果都是未定义的。
定义
class Adder {
public:
// 构造函数,用于初始化加数
Adder(int value) : value(value) {}
// 重载 operator(),使对象可以被调用
int operator()(int x) const {
return x + value;
}
private:
int value; // 存储用于加法的值
};
使用
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
// 创建函数对象
Adder adder(10);
// 使用函数对象
std::cout << "Adding 5 and 10: " << adder(5) << std::endl; // 输出 15
// 在 STL 算法中使用函数对象
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::transform(numbers.begin(), numbers.end(), numbers.begin(), adder);
std::cout << "Transformed numbers: ";
for (const auto& n : numbers) {
std::cout << n << " "; // 输出 11 12 13 14 15
}
std::cout << std::endl;
return 0;
}
当然函数对象也有弱势的地方,涉及到指针的情况就无能为力了。不过使用模板就可以解决,即接受函数指针,也能接受函数对象。
template<typename FUNC> //许函数接受任意类型的回调函数 FUNC,使得该函数可以在不同的条件下使用。
int count_n(int* array, int size, FUNC func) //第一个参数一个指向整数数组的指针。第二个数组的大小。第三个用于判断条件的函数。
{
int count = 0;
for(int i = 0; i < size; ++i)
if(func(array[i])) //检查当前数组元素是否满足条件。
count ++; //如果满足条件,数器 count 增加 1。
return count;
}
使用时
const int SIZE = 5;
int array[SIZE] = { 50, 30, 9, 7, 20};
cout << count_n(array, SIZE, less(10)); // 2 条件时小于10的元素
bool less10(int v)
{
return v < 10;
}
cout << count_n(array, SIZE, less10); // 2
这是统计数组中符合条件的元素个数,
适用场景
- STL 算法:函数对象常被用于算法库(如 std::sort, std::transform 等)中,因为它们可以携带状态和逻辑。
- 函数对象能够持有状态,这意味着它们可以存储成员变量并在调用时使用这些变量。这对于需要在多个函数调用间保持信息的情况非常有用
- 数对象可以将复杂的逻辑封装在其 operator() 方法中。这意味着你可以将多个操作结合成一个对象,从而达到更好的可读性和复用性。
#include <vector>
#include <algorithm>
#include <iostream>
class MultiplyBy {
public:
MultiplyBy(int factor) : factor(factor) {}
int operator()(int x) const {
return x * factor; // 将输入的值乘以因子
}
private:
int factor; // 存储乘法因子
};
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::vector<int> results;
// 使用函数对象进行 transform 操作
std::transform(numbers.begin(), numbers.end(), std::back_inserter(results), MultiplyBy(2));
// 输出结果
for (int result : results) {
std::cout << result << " "; // 输出:2 4 6 8 10
}
std::cout << std::endl;
return 0;
}
- 代替普通函数:当需要传递额外状态或参数时,函数对象比普通函数更为灵活。
- 需要共享状态:如果多个函数需要共享一些状态,比如计数器、阈值等,使用普通函数可能要求每次调用都手动传递这些额外的信息。
- 复杂的参数管理:当需要根据不同条件动态修改参数时,普通函数的设计将变得复杂且不方便。
使用普通函数
#include <iostream>
#include <vector>
#include <algorithm>
void increment(int& value, int incrementBy) {
value += incrementBy; // 增加值
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int incrementValue = 10;
// 使用普通函数进行增加操作
for (int& num : numbers) {
increment(num, incrementValue); // 每次都需要传递 incrementValue
}
// 输出结果
for (const int& num : numbers) {
std::cout << num << " "; // 输出:11 12 13 14 15
}
std::cout << std::endl;
return 0;
}
在上面的代码中,我们需要在每次调用 increment 函数时都传递 incrementValue,这将代码变得冗长且不灵活。
使用函数对象
#include <iostream>
#include <vector>
#include <algorithm>
class Incrementer {
public:
Incrementer(int incrementBy) : increment(incrementBy) {}
void operator()(int& value) const {
value += increment; // 增加值
}
private:
int increment; // 存储增量值
};
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
Incrementer incrementValue(10); // 创建函数对象并携带状态
// 使用函数对象进行增加操作
std::for_each(numbers.begin(), numbers.end(), incrementValue); // 不需要显式传递
// 输出结果
for (const int& num : numbers) {
std::cout << num << " "; // 输出:11 12 13 14 15
}
std::cout << std::endl;
return 0;
}
在这个例子中,我们定义了一个 Incrementer 类作为函数对象,该类持有一个增量值,而不是每次调用时都需要传递它。这样,代码变得更加简洁明了,且能够很好地封装状态。
- 自定义排序:可以用函数对象实现自定义的比较规则,以便在 STL 容器中进行排序或查找操作。
- 定义自定义结构体
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Person {
std::string name;
int age;
Person(const std::string& name, int age) : name(name), age(age) {}
};
- 定义比较函数对象
- 接下来,我们定义一个函数对象 CompareByAge,用于根据年龄对 Person 对象进行排序。
class CompareByAge {
public:
bool operator()(const Person& a, const Person& b) const {
return a.age < b.age; // 按年龄升序排序
}
};
- 使用函数对象进行排序
- 现在我们可以创建一个 std::vector 对象,并使用 std::sort 结合我们的比较函数对象来进行排序。
int main() {
std::vector<Person> people = {
Person("Alice", 30),
Person("Bob", 25),
Person("Charlie", 35)
};
// 使用自定义的比较对象进行排序
std::sort(people.begin(), people.end(), CompareByAge());
// 输出排序后的结果
std::cout << "Sorted by age:\n";
for (const auto& person : people) {
std::cout << person.name << ", Age: " << person.age << std::endl;
}
return 0;
}
- 输出结果
Sorted by age:
Bob, Age: 25
Alice, Age: 30
Charlie, Age: 35
扩展:自定义多重排序
以下是一个示例,展示如何按年龄和姓名进行排序
class CompareByAgeAndName {
public:
bool operator()(const Person& a, const Person& b) const {
if (a.age == b.age) {
return a.name < b.name; // 如果年龄相同,则按姓名排序
}
return a.age < b.age; // 按年龄排序
}
};
// 在 main 函数中使用这个比较对象:
int main() {
std::vector<Person> people = {
Person("Alice", 30),
Person("Bob", 25),
Person("Charlie", 25),
Person("David", 30)
};
std::sort(people.begin(), people.end(), CompareByAgeAndName());
std::cout << "Sorted by age and name:\n";
for (const auto& person : people) {
std::cout << person.name << ", Age: " << person.age << std::endl;
}
return 0;
}