C++ 函数对象概述
什么是函数对象
在C++中,函数对象(Function Object)或者称为仿函数(Functor),是一个可以像函数一样被调用的对象。简单来说,函数对象是一个重载了函数调用运算符 operator()
的类或结构体的实例。
信息
函数对象结合了面向对象编程和函数式编程的特点,为C++程序提供了更灵活的功能实现方式。
函数对象与普通函数的区别
- 函数对象可以保存状态(成员变量)
- 函数对象是类型,可以作为模板参数
- 通常比普通函数执行效率高(编译器优化)
- 可以访问类的私有成员
如何创建函数对象
创建函数对象很简单,只需定义一个类并重载 operator()
运算符:
cpp
#include <iostream>
// 定义一个函数对象类
class Add {
public:
// 重载函数调用运算符
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
// 创建函数对象实例
Add add;
// 使用函数对象
int result = add(5, 3); // 调用 operator()
std::cout << "5 + 3 = " << result << std::endl;
return 0;
}
输出:
5 + 3 = 8
带状态的函数对象
函数对象的一个主要优势是可以保存状态:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
// 带状态的函数对象
class Counter {
private:
int count; // 状态变量
public:
// 构造函数
Counter() : count(0) {}
// 重载函数调用运算符
void operator()(int) {
++count;
}
// 获取计数值
int getCount() const {
return count;
}
};
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
Counter counter;
// 计算大于3的元素个数
counter = std::for_each(v.begin(), v.end(),
[&counter](int x) {
if (x > 3) counter(x);
}
);
std::cout << "大于3的元素个数: " << counter.getCount() << std::endl;
return 0;
}
输出:
大于3的元素个数: 2
STL中的预定义函数对象
C++ STL提供了一系列预定义的函数对象,定义在 <functional>
头文件中:
算术函数对象
cpp
#include <iostream>
#include <functional>
int main() {
// 加法函数对象
std::plus<int> add;
std::cout << "2 + 3 = " << add(2, 3) << std::endl;
// 减法函数对象
std::minus<int> subtract;
std::cout << "5 - 3 = " << subtract(5, 3) << std::endl;
// 乘法函数对象
std::multiplies<int> multiply;
std::cout << "4 * 2 = " << multiply(4, 2) << std::endl;
// 除法函数对象
std::divides<int> divide;
std::cout << "8 / 2 = " << divide(8, 2) << std::endl;
// 取模函数对象
std::modulus<int> mod;
std::cout << "7 % 3 = " << mod(7, 3) << std::endl;
// 取反函数对象
std::negate<int> neg;
std::cout << "-5 = " << neg(5) << std::endl;
return 0;
}
输出:
2 + 3 = 5
5 - 3 = 2
4 * 2 = 8
8 / 2 = 4
7 % 3 = 1
-5 = -5
比较函数对象
cpp
#include <iostream>
#include <functional>
int main() {
// 相等比较函数对象
std::equal_to<int> equal;
std::cout << "3 == 3: " << (equal(3, 3) ? "true" : "false") << std::endl;
// 不等比较函数对象
std::not_equal_to<int> not_equal;
std::cout << "3 != 4: " << (not_equal(3, 4) ? "true" : "false") << std::endl;
// 大于比较函数对象
std::greater<int> greater;
std::cout << "4 > 3: " << (greater(4, 3) ? "true" : "false") << std::endl;
// 小于比较函数对象
std::less<int> less;
std::cout << "2 < 3: " << (less(2, 3) ? "true" : "false") << std::endl;
// 大于等于比较函数对象
std::greater_equal<int> greater_equal;
std::cout << "3 >= 3: " << (greater_equal(3, 3) ? "true" : "false") << std::endl;
// 小于等于比较函数对象
std::less_equal<int> less_equal;
std::cout << "3 <= 3: " << (less_equal(3, 3) ? "true" : "false") << std::endl;
return 0;
}
输出:
3 == 3: true
3 != 4: true
4 > 3: true
2 < 3: true
3 >= 3: true
3 <= 3: true
逻辑函数对象
cpp
#include <iostream>
#include <functional>
int main() {
// 逻辑与函数对象
std::logical_and<bool> logical_and;
std::cout << "true && false: " << (logical_and(true, false) ? "true" : "false") << std::endl;
// 逻辑或函数对象
std::logical_or<bool> logical_or;
std::cout << "true || false: " << (logical_or(true, false) ? "true" : "false") << std::endl;
// 逻辑非函数对象
std::logical_not<bool> logical_not;
std::cout << "!true: " << (logical_not(true) ? "true" : "false") << std::endl;
return 0;
}
输出:
true && false: false
true || false: true
!true: false
实际应用场景
排序场景
函数对象常用于STL算法中,例如对集合排序:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
// 自定义函数对象,用于自定义排序规则
class CustomSort {
public:
bool operator()(int a, int b) const {
// 按照个位数字排序
return (a % 10) < (b % 10);
}
};
int main() {
std::vector<int> v = {23, 15, 32, 44, 19, 30};
// 使用自定义函数对象排序
std::sort(v.begin(), v.end(), CustomSort());
std::cout << "按个位数排序后: ";
for (int num : v) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用标准库函数对象实现降序排序
std::sort(v.begin(), v.end(), std::greater<int>());
std::cout << "降序排序后: ";
for (int num : v) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
按个位数排序后: 30 32 23 44 15 19
降序排序后: 44 32 30 23 19 15
累加器
函数对象作为累加器使用:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
// 自定义累加器
class Accumulator {
private:
int total;
public:
Accumulator() : total(0) {}
// 累加操作
int operator()(int current) {
total += current;
return total;
}
};
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> results(v.size());
// 使用自定义函数对象计算累积和
std::transform(v.begin(), v.end(), results.begin(), Accumulator());
std::cout << "原始数组: ";
for (int num : v) {
std::cout << num << " ";
}
std::cout << std::endl;
std::cout << "累积和: ";
for (int num : results) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
原始数组: 1 2 3 4 5
累积和: 1 3 6 10 15
条件过滤
使用函数对象进行数据过滤:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
// 过滤函数对象
class IsEven {
public:
bool operator()(int num) const {
return num % 2 == 0; // 检查是否为偶数
}
};
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> evenNumbers;
// 使用函数对象过滤偶数
std::copy_if(v.begin(), v.end(),
std::back_inserter(evenNumbers),
IsEven());
std::cout << "原始数组: ";
for (int num : v) {
std::cout << num << " ";
}
std::cout << std::endl;
std::cout << "偶数: ";
for (int num : evenNumbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
原始数组: 1 2 3 4 5 6 7 8 9 10
偶数: 2 4 6 8 10
函数对象与Lambda表达式对比
在C++11之后,Lambda表达式提供了一种更简洁的创建匿名函数对象的方式:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 使用函数对象
class IsGreaterThanThree {
public:
bool operator()(int value) const {
return value > 3;
}
};
int count1 = std::count_if(v.begin(), v.end(), IsGreaterThanThree());
// 使用Lambda表达式
int count2 = std::count_if(v.begin(), v.end(), [](int value) { return value > 3; });
std::cout << "大于3的元素个数(函数对象): " << count1 << std::endl;
std::cout << "大于3的元素个数(Lambda): " << count2 << std::endl;
return 0;
}
输出:
大于3的元素个数(函数对象): 2
大于3的元素个数(Lambda): 2
函数对象的优势总结
- 保存状态:可以包含内部状态,而普通函数不能
- 内联优化:编译器可以更容易地内联函数对象的调用,提高性能
- 类型多样性:作为模板参数使用时提供编译时类型信息
- 与STL无缝配合:专门设计用于STL算法
- 灵活组合:可以组合多个函数对象实现复杂功能
函数对象的局限性
- 语法冗长:相对于Lambda表达式,定义函数对象需要写更多代码
- 代码分离:函数对象的定义通常需要与使用点分离
- 理解难度:对初学者来说可能比普通函数难理解
总结
函数对象是C++中一个强大而灵活的功能,它结合了面向对象编程和函数式编程的优点。通过重载 operator()
运算符,类的实例可以像函数一样被调用,同时还能维护状态。STL提供了许多预定义的函数对象,使得常见操作变得简单高效。
在实际开发中,函数对象通常与STL算法一起使用,特别是在排序、过滤和转换数据时。尽管现代C++中Lambda表达式提供了一种更简洁的替代方法,但了解函数对象的概念和用法仍然对掌握C++编程非常重要。
练习
- 编写一个函数对象,计算一个整数序列中能被3整除的数字的个数。
- 创建一个函数对象,将字符串转换为大写。
- 实现一个函数对象,用于按照字符串长度对字符串vector进行排序。
- 编写一个可配置的过滤器函数对象,可以根据构造时提供的条件过滤整数集合。
- 使用STL提供的函数对象,对一个包含浮点数的vector进行各种运算。
进阶学习
要深入了解函数对象,建议研究STL源代码中的函数对象实现,以及了解C++20中引入的新概念和改进。