跳到主要内容

C++ 函数适配器

在 C++ STL(标准模板库)中,函数适配器是一种强大的工具,它能够将现有的函数对象转换为具有不同特性的新函数对象。函数适配器通过包装或修改现有函数对象,使其满足特定的需求,从而增加了函数对象使用的灵活性。

函数适配器的基本概念

函数适配器主要用于以下场景:

  • 改变函数对象的参数数量
  • 改变函数对象的运算参数顺序
  • 绑定特定值到函数对象的某些参数
  • 对函数对象的结果进行逻辑操作

在 C++11 之前,函数适配器主要通过 <functional> 头文件中定义的一系列类模板实现。C++11 后,许多传统函数适配器被新的更灵活的绑定器所取代,如 std::bind 和 lambda 表达式。

常见函数适配器类型

1. 绑定器(Binders)

绑定器可以把二元函数对象的一个参数绑定为特定值,从而将二元函数对象转换为一元函数对象。

C++ 98/03 中的绑定器

  • bind1st:绑定第一个参数
  • bind2nd:绑定第二个参数
cpp
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 查找大于 5 的第一个元素
auto it = std::find_if(numbers.begin(), numbers.end(),
std::bind2nd(std::greater<int>(), 5));

if (it != numbers.end()) {
std::cout << "第一个大于 5 的数是: " << *it << std::endl;
}

return 0;
}

输出:

第一个大于 5 的数是: 6

C++ 11 及以后的 std::bind

std::bind 是一个更强大的绑定器,它可以绑定任意参数位置,并支持占位符(placeholders)。

cpp
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 使用 std::bind 查找大于 5 的元素
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;
}

输出:

第一个大于 5 的数是: 6

2. 否定器(Negators)

否定器用于对谓词函数对象的结果取反。

  • not1:对一元谓词取反
  • not2:对二元谓词取反
cpp
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 查找不小于 5 的第一个元素(等同于大于等于 5)
auto it = std::find_if(numbers.begin(), numbers.end(),
std::not1(std::bind2nd(std::less<int>(), 5)));

if (it != numbers.end()) {
std::cout << "第一个不小于 5 的数是: " << *it << std::endl;
}

return 0;
}

输出:

第一个不小于 5 的数是: 5

在 C++11 之后,我们可以使用 lambda 表达式来替代这种复杂的否定器写法:

cpp
auto it = std::find_if(numbers.begin(), numbers.end(), 
[](int n) { return n >= 5; });

3. 函数组合器(Function Composers)

C++11 引入了 std::function 和 lambda 表达式,使函数组合变得更加灵活。我们可以将多个函数组合在一起:

cpp
#include <iostream>
#include <functional>

// 一个自定义的函数组合器
template <typename F, typename G>
auto compose(F f, G g) {
return [=](auto x) { return f(g(x)); };
}

int main() {
auto square = [](int x) { return x * x; };
auto increment = [](int x) { return x + 1; };

// 组合 square 和 increment:先平方,再加1
auto squareAndIncrement = compose(increment, square);

std::cout << "f(5) = " << squareAndIncrement(5) << std::endl; // (5*5) + 1 = 26

return 0;
}

输出:

f(5) = 26

实际应用案例

案例1:使用函数适配器实现自定义数据筛选

假设我们有一个学生列表,需要根据不同条件筛选学生:

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>

struct Student {
std::string name;
int age;
float gpa;

Student(const std::string& n, int a, float g)
: name(n), age(a), gpa(g) {}
};

int main() {
std::vector<Student> students = {
{"Alice", 20, 3.8},
{"Bob", 22, 3.2},
{"Charlie", 19, 3.9},
{"David", 21, 3.5},
{"Eve", 20, 3.7}
};

// 找出所有 GPA 高于 3.5 的学生
std::cout << "GPA > 3.5 的学生:" << std::endl;
std::for_each(students.begin(), students.end(),
[](const Student& s) {
if (s.gpa > 3.5) {
std::cout << s.name << " (GPA: " << s.gpa << ")" << std::endl;
}
});

// 使用 std::bind 结合 std::greater 找出年龄大于 20 的学生
std::cout << "\n年龄 > 20 的学生:" << std::endl;
auto isOlderThan20 = std::bind(std::greater<int>(),
std::bind(&Student::age, std::placeholders::_1),
20);

std::for_each(students.begin(), students.end(),
[&](const Student& s) {
if (isOlderThan20(s)) {
std::cout << s.name << " (年龄: " << s.age << ")" << std::endl;
}
});

return 0;
}

输出:

GPA > 3.5 的学生:
Alice (GPA: 3.8)
Charlie (GPA: 3.9)
David (GPA: 3.5)
Eve (GPA: 3.7)

年龄 > 20 的学生:
Bob (年龄: 22)
David (年龄: 21)

案例2:使用函数适配器进行复杂排序

我们可以使用函数适配器来创建复杂的排序规则:

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>

struct Product {
std::string name;
double price;
int stock;

Product(const std::string& n, double p, int s)
: name(n), price(p), stock(s) {}
};

int main() {
std::vector<Product> products = {
{"Laptop", 999.99, 10},
{"Mouse", 19.99, 100},
{"Keyboard", 49.99, 50},
{"Monitor", 299.99, 15},
{"Headphones", 89.99, 30}
};

// 先按库存降序,库存相同时按价格升序排序
std::sort(products.begin(), products.end(),
[](const Product& a, const Product& b) {
if (a.stock == b.stock) {
return a.price < b.price;
}
return a.stock > b.stock;
});

std::cout << "产品列表 (按库存降序,价格升序):" << std::endl;
for (const auto& product : products) {
std::cout << product.name
<< " - 价格: $" << product.price
<< ", 库存: " << product.stock << std::endl;
}

return 0;
}

输出:

产品列表 (按库存降序,价格升序):
Mouse - 价格: $19.99, 库存: 100
Keyboard - 价格: $49.99, 库存: 50
Headphones - 价格: $89.99, 库存: 30
Monitor - 价格: $299.99, 库存: 15
Laptop - 价格: $999.99, 库存: 10

C++ 11 及以后的替代方案

随着 C++11 的发布,许多传统函数适配器的使用场景可以被 lambda 表达式替代,这通常能提供更清晰、更简洁的代码:

cpp
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 使用传统函数适配器
auto it1 = std::find_if(nums.begin(), nums.end(),
std::bind2nd(std::greater<int>(), 5));

// 使用 lambda 表达式
auto it2 = std::find_if(nums.begin(), nums.end(),
[](int n) { return n > 5; });

std::cout << "第一个大于 5 的数是: " << *it1 << std::endl;
std::cout << "使用 lambda: " << *it2 << std::endl;

return 0;
}

输出:

第一个大于 5 的数是: 6
使用 lambda: 6
提示

在现代 C++ 编程中,尽管传统函数适配器仍然有效,但鼓励使用 lambda 表达式和 std::bind 替代旧的函数适配器,因为它们提供了更大的灵活性和可读性。

总结

函数适配器是 C++ STL 中的强大工具,它们允许我们调整和转换现有的函数对象以适应特定需求。主要类型包括:

  1. 绑定器:如 bind1stbind2nd 和现代 C++ 中的 std::bind
  2. 否定器:如 not1not2
  3. 函数组合器:让我们能够组合多个函数

随着 C++11 的发展,lambda 表达式和新的绑定器(如 std::bind)提供了更灵活和直观的方式来实现函数适配器的功能。

练习

  1. 使用 std::bind 创建一个函数,检查一个数是否在特定范围内(例如 5 到 10 之间)。
  2. 编写一个程序,使用函数适配器或 lambda 表达式从字符串向量中筛选出长度大于 5 的字符串。
  3. 实现一个函数组合器,将三个函数 f、g、h 组合为 f(g(h(x)))。
  4. 使用函数适配器或 lambda 表达式创建一个排序规则,先按字符串长度升序排序,长度相同时按字母顺序排序。

进一步阅读

  • 《C++ 标准库》(第2版)- Nicolai M. Josuttis
  • 《Effective STL》- Scott Meyers
  • C++ 参考文档中关于 <functional> 的内容

通过掌握函数适配器,你将能够更灵活地使用 STL 算法,写出更简洁高效的代码。