C++ 函数适配器
在 C++ STL(标准模板库)中,函数适配器是一种强大的工具,它能够将现有的函数对象转换为具有不同特性的新函数对象。函数适配器通过包装或修改现有函数对象,使其满足特定的需求,从而增加了函数对象使用的灵活性。
函数适配器的基本概念
函数适配器主要用于以下场景:
- 改变函数对象的参数数量
- 改变函数对象的运算参数顺序
- 绑定特定值到函数对象的某些参数
- 对函数对象的结果进行逻辑操作
在 C++11 之前,函数适配器主要通过 <functional>
头文件中定义的一系列类模板实现。C++11 后,许多传统函数适配器被新的更灵活的绑定器所取代,如 std::bind
和 lambda 表达式。
常见函数适配器类型
1. 绑定器(Binders)
绑定器可以把二元函数对象的一个参数绑定为特定值,从而将二元函数对象转换为一元函数对象。
C++ 98/03 中的绑定器
bind1st
:绑定第一个参数bind2nd
:绑定第二个参数
#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)。
#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
:对二元谓词取反
#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 表达式来替代这种复杂的否定器写法:
auto it = std::find_if(numbers.begin(), numbers.end(),
[](int n) { return n >= 5; });
3. 函数组合器(Function Composers)
C++11 引入了 std::function
和 lambda 表达式,使函数组合变得更加灵活。我们可以将多个函数组合在一起:
#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:使用函数适配器实现自定义数据筛选
假设我们有一个学生列表,需要根据不同条件筛选学生:
#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:使用函数适配器进行复杂排序
我们可以使用函数适配器来创建复杂的排序规则:
#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 表达式替代,这通常能提供更清晰、更简洁的代码:
#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 中的强大工具,它们允许我们调整和转换现有的函数对象以适应特定需求。主要类型包括:
- 绑定器:如
bind1st
、bind2nd
和现代 C++ 中的std::bind
- 否定器:如
not1
和not2
- 函数组合器:让我们能够组合多个函数
随着 C++11 的发展,lambda 表达式和新的绑定器(如 std::bind
)提供了更灵活和直观的方式来实现函数适配器的功能。
练习
- 使用
std::bind
创建一个函数,检查一个数是否在特定范围内(例如 5 到 10 之间)。 - 编写一个程序,使用函数适配器或 lambda 表达式从字符串向量中筛选出长度大于 5 的字符串。
- 实现一个函数组合器,将三个函数 f、g、h 组合为 f(g(h(x)))。
- 使用函数适配器或 lambda 表达式创建一个排序规则,先按字符串长度升序排序,长度相同时按字母顺序排序。
进一步阅读
- 《C++ 标准库》(第2版)- Nicolai M. Josuttis
- 《Effective STL》- Scott Meyers
- C++ 参考文档中关于
<functional>
的内容
通过掌握函数适配器,你将能够更灵活地使用 STL 算法,写出更简洁高效的代码。