C++ 函数对象
在C++编程中,除了我们熟悉的常规函数,还有一个强大而灵活的工具——函数对象(Function Objects),也被称为仿函数(Functors)。本文将帮助你理解什么是函数对象,它们如何工作,以及为什么它们在现代C++编程中如此重要。
什么是函数对象?
函数对象是一个行为类似函数的对象。从技术上讲,它是一个重载了函数调用运算符 operator()
的类的实例。这个特性使得该类的对象可以像函数一样被调用。
函数对象 = 对象 + 行为像函数
创建函数对象的基础语法
要创建一个函数对象,我们需要定义一个类并重载函数调用运算符 ()
:
class MyFunctor {
public:
// 重载函数调用运算符
return_type operator()(parameter_list) {
// 函数体
}
};
基础示例
让我们创建一个简单的函数对象,它将两个数相加:
#include <iostream>
// 定义一个加法函数对象
class Adder {
public:
// 重载函数调用运算符
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
// 创建函数对象实例
Adder add;
// 像调用函数一样使用函数对象
int result = add(5, 3);
std::cout << "5 + 3 = " << result << std::endl;
// 也可以创建临时对象并直接调用
std::cout << "10 + 20 = " << Adder()(10, 20) << std::endl;
return 0;
}
输出:
5 + 3 = 8
10 + 20 = 30
在这个例子中,Adder
是一个函数对象类,它重载了 operator()
,使其实例可以像函数一样被调用。
函数对象的优势
1. 可以保持状态
与普通函数不同,函数对象可以在多次调用之间保持状态(数据):
#include <iostream>
class Counter {
private:
int count; // 状态
public:
// 构造函数初始化状态
Counter() : count(0) {}
// 函数调用重载
int operator()() {
return ++count; // 每次调用时递增计数
}
// 重置计数器
void reset() {
count = 0;
}
};
int main() {
Counter counter;
std::cout << "First call: " << counter() << std::endl;
std::cout << "Second call: " << counter() << std::endl;
std::cout << "Third call: " << counter() << std::endl;
counter.reset();
std::cout << "After reset: " << counter() << std::endl;
return 0;
}
输出:
First call: 1
Second call: 2
Third call: 3
After reset: 1
2. 可以作为模板参数
函数对象可以很容易地作为模板参数传递,这在标准模板库(STL)算法中特别有用:
#include <iostream>
#include <vector>
#include <algorithm>
// 自定义比较器
class GreaterThan {
public:
bool operator()(int a, int b) const {
return a > b;
}
};
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
// 使用自定义函数对象排序(降序)
std::sort(numbers.begin(), numbers.end(), GreaterThan());
std::cout << "排序后的数组(降序): ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
排序后的数组(降序): 9 8 5 3 2 1
3. 内联和优化
编译器通常可以更轻松地内联函数对象的代码,从而提高性能。
带参数的函数对象
函数对象可以通过构造函数接受参数,使其更加灵活:
#include <iostream>
#include <vector>
#include <algorithm>
// 可配置的比较器
class Comparator {
private:
bool ascending;
public:
// 构造函数接受排序方向作为参数
Comparator(bool asc) : ascending(asc) {}
bool operator()(int a, int b) const {
return ascending ? (a < b) : (a > b);
}
};
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
// 创建两个不同配置的比较器
Comparator ascendingOrder(true);
Comparator descendingOrder(false);
// 升序排序
std::sort(numbers.begin(), numbers.end(), ascendingOrder);
std::cout << "升序排序: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 降序排序
std::sort(numbers.begin(), numbers.end(), descendingOrder);
std::cout << "降序排序: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
升序排序: 1 2 3 5 8 9
降序排序: 9 8 5 3 2 1
在STL中的应用
标准模板库(STL)大量使用函数对象。例如,以下是一些预定义的函数对象:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional> // 包含预定义的函数对象
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
// 使用STL预定义的函数对象greater<int>
std::sort(numbers.begin(), numbers.end(), std::greater<int>());
std::cout << "使用std::greater排序: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用预定义函数对象查找满足条件的元素
auto it = std::find_if(numbers.begin(), numbers.end(),
std::bind(std::greater<int>(), std::placeholders::_1, 5));
if (it != numbers.end()) {
std::cout << "找到第一个大于5的数: " << *it << std::endl;
}
return 0;
}
输出:
使用std::greater排序: 9 8 5 3 2 1
找到第一个大于5的数: 9
STL中常用的预定义函数对象
C++ STL在<functional>
头文件中提供了多种预定义的函数对象:
- 算术运算:
plus<T>
,minus<T>
,multiplies<T>
,divides<T>
,modulus<T>
,negate<T>
- 比较运算:
equal_to<T>
,not_equal_to<T>
,greater<T>
,less<T>
,greater_equal<T>
,less_equal<T>
- 逻辑运算:
logical_and<T>
,logical_or<T>
,logical_not<T>
实际应用案例
案例1:自定义复杂排序
假设我们有一个学生类,我们希望能够根据不同的标准灵活地对学生进行排序:
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
class Student {
public:
std::string name;
int age;
double gpa;
Student(const std::string& n, int a, double g)
: name(n), age(a), gpa(g) {}
void print() const {
std::cout << name << ", 年龄: " << age << ", GPA: " << gpa << std::endl;
}
};
// 按照不同属性排序的函数对象
class StudentSorter {
private:
enum SortCriterion { BY_NAME, BY_AGE, BY_GPA };
SortCriterion criterion;
bool ascending;
public:
StudentSorter(SortCriterion c, bool asc = true)
: criterion(c), ascending(asc) {}
bool operator()(const Student& a, const Student& b) const {
bool result;
switch (criterion) {
case BY_NAME:
result = a.name < b.name;
break;
case BY_AGE:
result = a.age < b.age;
break;
case BY_GPA:
result = a.gpa < b.gpa;
break;
}
return ascending ? result : !result;
}
// 静态方法,便于创建特定的排序器
static StudentSorter byName(bool asc = true) {
return StudentSorter(BY_NAME, asc);
}
static StudentSorter byAge(bool asc = true) {
return StudentSorter(BY_AGE, asc);
}
static StudentSorter byGPA(bool asc = true) {
return StudentSorter(BY_GPA, asc);
}
};
int main() {
std::vector<Student> students = {
Student("Alice", 20, 3.8),
Student("Bob", 22, 3.5),
Student("Charlie", 19, 4.0),
Student("Diana", 21, 3.9)
};
// 按姓名排序(升序)
std::sort(students.begin(), students.end(), StudentSorter::byName());
std::cout << "按姓名排序(升序):" << std::endl;
for (const auto& student : students) {
student.print();
}
std::cout << std::endl;
// 按年龄排序(降序)
std::sort(students.begin(), students.end(), StudentSorter::byAge(false));
std::cout << "按年龄排序(降序):" << std::endl;
for (const auto& student : students) {
student.print();
}
std::cout << std::endl;
// 按GPA排序(降序)
std::sort(students.begin(), students.end(), StudentSorter::byGPA(false));
std::cout << "按GPA排序(降序):" << std::endl;
for (const auto& student : students) {
student.print();
}
return 0;
}
输出:
按姓名排序(升序):
Alice, 年龄: 20, GPA: 3.8
Bob, 年龄: 22, GPA: 3.5
Charlie, 年龄: 19, GPA: 4
Diana, 年龄: 21, GPA: 3.9
按年龄排序(降序):
Bob, 年龄: 22, GPA: 3.5
Diana, 年龄: 21, GPA: 3.9
Alice, 年龄: 20, GPA: 3.8
Charlie, 年龄: 19, GPA: 4
按GPA排序(降序):
Charlie, 年龄: 19, GPA: 4
Diana, 年龄: 21, GPA: 3.9
Alice, 年龄: 20, GPA: 3.8
Bob, 年龄: 22, GPA: 3.5
案例2:自定义转换规则
假设我们需要对字符串进行各种转换:
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <cctype> // 用于toupper和tolower
// 字符串转换器函数对象
class StringTransformer {
public:
virtual ~StringTransformer() {}
virtual std::string operator()(const std::string& input) const = 0;
};
// 转换为大写
class ToUpperTransformer : public StringTransformer {
public:
std::string operator()(const std::string& input) const override {
std::string result = input;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::toupper(c); });
return result;
}
};
// 转换为小写
class ToLowerTransformer : public StringTransformer {
public:
std::string operator()(const std::string& input) const override {
std::string result = input;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
};
// 反转字符串
class ReverseTransformer : public StringTransformer {
public:
std::string operator()(const std::string& input) const override {
std::string result = input;
std::reverse(result.begin(), result.end());
return result;
}
};
// 打印字符串转换结果
void printTransformed(const std::string& input,
const StringTransformer& transformer,
const std::string& transformerName) {
std::cout << transformerName << ": " << transformer(input) << std::endl;
}
int main() {
std::string text = "Hello, Function Objects!";
ToUpperTransformer toUpper;
ToLowerTransformer toLower;
ReverseTransformer reverse;
std::cout << "原文: " << text << std::endl;
printTransformed(text, toUpper, "转为大写");
printTransformed(text, toLower, "转为小写");
printTransformed(text, reverse, "反转字符串");
// 组合转换
std::cout << "组合转换 (大写后反转): "
<< reverse(toUpper(text)) << std::endl;
return 0;
}
输出:
原文: Hello, Function Objects!
转为大写: HELLO, FUNCTION OBJECTS!
转为小写: hello, function objects!
反转字符串: !stcejbO noitcnuF ,olleH
组合转换 (大写后反转): !STCEJBO NOITCNUF ,OLLEH
Lambda 表达式与函数对象
在C++11及以后的版本中,你可以使用Lambda表达式作为函数对象的简洁替代。实际上,Lambda表达式在编译时会被转换为匿名函数对象:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
// 使用函数对象排序
class Comparator {
public:
bool operator()(int a, int b) const {
return a > b;
}
};
std::sort(numbers.begin(), numbers.end(), Comparator());
std::cout << "使用函数对象排序: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 重置数组
numbers = {5, 2, 8, 1, 9, 3};
// 使用Lambda表达式排序(更简洁)
std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
return a > b;
});
std::cout << "使用Lambda排序: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
使用函数对象排序: 9 8 5 3 2 1
使用Lambda排序: 9 8 5 3 2 1
尽管Lambda表达式在许多场景下更为简洁,函数对象在需要维护状态、复用或提供多个功能时仍然很有价值。
总结
函数对象是C++中一个强大的编程工具,它结合了函数的灵活性和类的特性,具有以下优点:
- 可以保持状态:函数对象可以在多次调用之间保持状态
- 可以作为模板参数:它们很容易作为STL算法的参数
- 内联优化:编译器通常可以更好地优化函数对象
- 灵活配置:可以通过构造函数参数化函数对象
- 可扩展:可以通过继承和组合扩展功能
在现代C++中,函数对象与Lambda表达式一起,构成了函数式编程风格的重要组成部分,特别是在使用STL算法时。
练习
- 创建一个函数对象,计算任何数的平方。
- 创建一个函数对象,用于检查一个数是否是另一个数的倍数。
- 实现一个计数器函数对象,它可以被调用多次,并在每次调用时返回已调用的总次数。
- 使用函数对象实现一个文本过滤器,它可以从文本中移除特定的单词。
- 创建一个函数对象,用于按指定的方向(升序或降序)对数组进行排序,并在排序后打印数组的最大值和最小值。
进一步学习
如果你想更深入地了解函数对象,可以探索以下内容:
- STL中的预定义函数对象
- 使用
std::bind
和std::function
进行函数组合 - 函数对象与设计模式的结合,如策略模式
- C++20中的新功能,如概念(Concepts)和范围(Ranges)与函数对象的结合
函数对象是迈向高级C++编程和函数式编程风格的重要一步。掌握它们将使你能够编写更灵活、更可维护的代码!