C++: STL 函数对象
第37课我们学了迭代器。
现在,我们要学STL算法的"灵魂"——函数对象。
算法是骨架,函数对象是血肉。两者结合,才能发挥STL的真正威力。
1. 函数对象概述
(1) 1.1 什么是函数对象?
函数对象(Functor)是可以像函数一样调用的对象。
三种函数对象:
- 函数指针
- 函数对象类(重载
operator()) - Lambda表达式(C++11)
(2) 1.2 为什么需要函数对象?
STL算法是通用的,但具体操作因需求而异。函数对象让你可以自定义操作。
生活类比:
- 算法 = 洗衣机(通用)
- 函数对象 = 洗衣液(自定义:清香型/强力型/柔顺型)
2. 函数指针
(1) 2.1 基本用法
示例:用函数指针自定义排序(难度⭐⭐)
▶ 示例 2:STL容器使用(难度⭐)
#include <iostream>
#include <vector>
#include <algorithm>
// 自定义比较函数
bool compareDesc(int a, int b) {
return a > b; // 降序
}
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// 使用函数指针
std::sort(v.begin(), v.end(), compareDesc);
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
输出:
3 1 4 1 5 9 2 6
运行结果:
9 6 5 4 3 2 2 1 1
💡 提示:
- 函数指针是C语言遗留,C++更推荐用函数对象或Lambda
3. 函数对象类
(1) 3.1 什么是函数对象类?
函数对象类是重载了 operator() 的类,其实例可以像函数一样调用。
示例:自定义比较器(难度⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
// 函数对象类:按字符串长度排序
struct CompareByLength {
bool operator()(const std::string& a, const std::string& b) const {
return a.length() < b.length();
}
};
int main() {
std::vectorstd::string words = {"apple", "banana", "cat", "dog"};
// 使用函数对象
std::sort(words.begin(), words.end(), CompareByLength());
for (const auto& w : words) {
std::cout << w << " ";
}
std::cout << std::endl;
return 0;
}
运行结果:
cat dog apple banana
(2) 3.2 函数对象的优势
| 对比 | 函数指针 | 函数对象类 |
|---|---|---|
| 状态 | 无状态 | 可以有状态(成员变量) |
| 性能 | 可能无法内联 | 可以内联,更快 |
| 灵活性 | 低 | 高(可以模板化) |
(3) 3.3 带状态的函数对象
示例:计数器函数对象(难度⭐⭐⭐)
#include <iostream>
#include <algorithm>
#include <vector>
// 函数对象:统计满足条件的元素个数
struct Counter {
int threshold; // 阈值(状态)
Counter(int t) : threshold(t) {}
bool operator()(int x) const {
return x > threshold; // 统计大于threshold的元素
}
};
int main() {
std::vector<int> v = {1, 5, 10, 15, 20};
// 创建函数对象,设置阈值为10
Counter counter(10);
// 统计大于10的元素个数
int count = std::count_if(v.begin(), v.end(), counter);
std::cout << "大于10的元素个数:" << count << std::endl; // 输出:2
return 0;
}
4. Lambda表达式
(1) 4.1 什么是Lambda?
Lambda表达式是C++11引入的匿名函数,可以在需要函数的地方直接定义。
基本语法:
[capture](parameters) -> return_type { body }
| 部分 | 说明 |
|---|---|
capture |
捕获列表(捕获外部变量) |
parameters |
参数列表 |
return_type |
返回类型(可省略) |
body |
函数体 |
(2) 4.2 基本示例
示例:Lambda排序(难度⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// 用Lambda表达式排序(降序)
std::sort(v.begin(), v.end(), (int a, int b) {
return a > b;
});
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
输出:
3 1 4 1 5 9 2 6
(3) 4.3 捕获列表
捕获列表决定Lambda能访问哪些外部变量。
| 捕获方式 | 说明 |
|---|---|
| `` | 不捕获任何变量 |
[x] |
按值捕获x |
[&x] |
按引用捕获x |
[=] |
按值捕获所有变量 |
[&] |
按引用捕获所有变量 |
[this] |
捕获this指针(在类中使用) |
示例:带状态的Lambda(难度⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 5, 10, 15, 20};
int threshold = 10;
// 按值捕获threshold
int count = std::count_if(v.begin(), v.end(),
[threshold](int x) {
return x > threshold;
});
std::cout << "大于" << threshold << "的元素个数:" << count << std::endl;
return 0;
}
5. STL预定义函数对象
(1) 5.1 算术函数对象
functional 头文件提供了常用的函数对象:
| 函数对象 | 功能 | 示例 |
|---|---|---|
std::plusT |
加法 | std::plusint() |
std::minusT |
减法 | std::minusint() |
std::multipliesT |
乘法 | std::multipliesint() |
std::dividesT |
除法 | std::dividesint() |
std::negateT |
取负 | std::negateint() |
示例:用multiplies翻倍(难度⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 所有元素翻倍
std::transform(v.begin(), v.end(), v.begin(),
std::bind(std::multipliesint(), std::placeholders::_1, 2));
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 5.2 比较函数对象
| 函数对象 | 功能 |
|---|---|
std::equal_toT |
等于 |
std::not_equal_toT |
不等于 |
std::greaterT |
大于 |
std::lessT |
小于 |
std::greater_equalT |
大于等于 |
std::less_equalT |
小于等于 |
(3) 5.3 逻辑函数对象
| 函数对象 | 功能 |
|---|---|
std::logical_andT |
逻辑与 |
std::logical_orT |
逻辑或 |
std::logical_notT |
逻辑非 |
6. 综合示例
▶ 示例 1:成绩处理器(难度⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
struct Student {
std::string name;
int score;
};
int main() {
std::vectorStudent students = {
{"张三", 85},
{"李四", 92},
{"王五", 78}
};
// 1. 按成绩降序排序
std::sort(students.begin(), students.end(),
(const Student& a, const Student& b) {
return a.score > b.score;
});
// 2. 找出最高分
auto max_it = std::max_element(students.begin(), students.end(),
(const Student& a, const Student& b) {
return a.score < b.score;
});
std::cout << "最高分:" << max_it->name << " " << max_it->score << std::endl;
// 3. 统计及格人数
int passed = std::count_if(students.begin(), students.end(),
(const Student& s) {
return s.score >= 60;
});
std::cout << "及格人数:" << passed << std::endl;
return 0;
}
输出:
最高分:
及格人数:
❓ 常见问题
Q:Lambda和函数对象类哪个好? A:- 简单操作 → Lambda(代码简洁) - 复杂操作/需要复用 → 函数对象类(可维护性好)
Q:
auto能推导Lambda类型吗? A:Lambda类型是唯一的匿名类型,只能用auto推导,不能写具体类型。
auto func = (int x) { return x * 2; };
// std::function<int(int)> func = ... // 也可以,但有性能开销
Q:什么时候用
std::function? A:当需要存储函数对象(作为成员变量、返回值等)时,用std::function。
▶ 示例 3:Lambda表达式(难度⭐)
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
int sum = 0;
std::for_each(v.begin(), v.end(), [&sum](int x) {
sum += x;
});
std::cout << "总和:" << sum << std::endl;
return 0;
}
输出:
1 2 3 4 5
[捕获](参数) { 函数体 }。[&sum] 捕获引用,可以修改外部变量。
| 知识点 | 要点 |
|---|---|
| 函数指针 | 简单但功能有限 |
| 函数对象类 | 可自定义操作,可带状态 |
| Lambda | 匿名函数,简洁强大 |
| 预定义函数对象 | std::plus等,在functional中 |
| 捕获列表 | Lambda访问外部变量的方式 |
📖 小节
- 函数对象:重载
operator()的类,可像函数调用 - Lambda:匿名函数,语法
[捕获](参数) { 函数体 } - std::function:通用函数包装器,可存储任意可调用对象
- std::bind:绑定函数参数,生成新的可调用对象
📝 作业
-
**基础题 (Difficulty ⭐):创建一个仿函数(重载 operator() 的类),实现"比较两个整数的大小",用 std::sort 测试。
-
**进阶题 (Difficulty ⭐⭐):用 std::function 存储不同类型的可调用对象(普通函数、lambda、仿函数),统一调用。
-
**挑战题 (Difficulty ⭐⭐⭐):用 std::bind 绑定部分参数,生成新的可调用对象。实现一个"参数预填充"的函数适配器。
- 函数对象(仿函数):重载 operator() 的类
- 函数对象可保存状态,普通函数不行
- std::function:类型擦除的可调用包装器
- bind 绑定部分参数生成新可调用对象
- lambda 是函数对象的语法糖
下一课:STL适配器(#39)