跳到主要内容

C++ 函数对象概述

什么是函数对象

在C++中,函数对象(Function Object)或者称为仿函数(Functor),是一个可以像函数一样被调用的对象。简单来说,函数对象是一个重载了函数调用运算符 operator() 的类或结构体的实例。

信息

函数对象结合了面向对象编程和函数式编程的特点,为C++程序提供了更灵活的功能实现方式。

函数对象与普通函数的区别

  1. 函数对象可以保存状态(成员变量)
  2. 函数对象是类型,可以作为模板参数
  3. 通常比普通函数执行效率高(编译器优化)
  4. 可以访问类的私有成员

如何创建函数对象

创建函数对象很简单,只需定义一个类并重载 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

函数对象的优势总结

  1. 保存状态:可以包含内部状态,而普通函数不能
  2. 内联优化:编译器可以更容易地内联函数对象的调用,提高性能
  3. 类型多样性:作为模板参数使用时提供编译时类型信息
  4. 与STL无缝配合:专门设计用于STL算法
  5. 灵活组合:可以组合多个函数对象实现复杂功能

函数对象的局限性

  1. 语法冗长:相对于Lambda表达式,定义函数对象需要写更多代码
  2. 代码分离:函数对象的定义通常需要与使用点分离
  3. 理解难度:对初学者来说可能比普通函数难理解

总结

函数对象是C++中一个强大而灵活的功能,它结合了面向对象编程和函数式编程的优点。通过重载 operator() 运算符,类的实例可以像函数一样被调用,同时还能维护状态。STL提供了许多预定义的函数对象,使得常见操作变得简单高效。

在实际开发中,函数对象通常与STL算法一起使用,特别是在排序、过滤和转换数据时。尽管现代C++中Lambda表达式提供了一种更简洁的替代方法,但了解函数对象的概念和用法仍然对掌握C++编程非常重要。

练习

  1. 编写一个函数对象,计算一个整数序列中能被3整除的数字的个数。
  2. 创建一个函数对象,将字符串转换为大写。
  3. 实现一个函数对象,用于按照字符串长度对字符串vector进行排序。
  4. 编写一个可配置的过滤器函数对象,可以根据构造时提供的条件过滤整数集合。
  5. 使用STL提供的函数对象,对一个包含浮点数的vector进行各种运算。
进阶学习

要深入了解函数对象,建议研究STL源代码中的函数对象实现,以及了解C++20中引入的新概念和改进。