C++: STL 函数对象

第37课我们学了迭代器。

现在,我们要学STL算法的"灵魂"——函数对象

算法是骨架,函数对象是血肉。两者结合,才能发挥STL的真正威力。


1. 函数对象概述

(1) 1.1 什么是函数对象?

函数对象(Functor)是可以像函数一样调用的对象

三种函数对象:

  1. 函数指针
  2. 函数对象类(重载 operator()
  3. Lambda表达式(C++11)

(2) 1.2 为什么需要函数对象?

STL算法是通用的,但具体操作因需求而异。函数对象让你可以自定义操作

生活类比:



2. 函数指针

(1) 2.1 基本用法

示例:用函数指针自定义排序(难度⭐⭐)

▶ 示例 2:STL容器使用(难度⭐)

CPP
#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;
}
▶ 试一试

输出:

TEXT 📖 仅展示
3 1 4 1 5 9 2 6

运行结果:

TEXT 📖 仅展示
9 6 5 4 3 2 2 1 1 

💡 提示:



3. 函数对象类

(1) 3.1 什么是函数对象类?

函数对象类重载了 operator() 的类,其实例可以像函数一样调用。

示例:自定义比较器(难度⭐⭐)

CPP
#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;
}

运行结果:

TEXT 📖 仅展示
cat dog apple banana 

(2) 3.2 函数对象的优势

对比 函数指针 函数对象类
状态 无状态 可以有状态(成员变量)
性能 可能无法内联 可以内联,更快
灵活性 (可以模板化)

(3) 3.3 带状态的函数对象

示例:计数器函数对象(难度⭐⭐⭐)

CPP
#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引入的匿名函数,可以在需要函数的地方直接定义。

基本语法:

CPP
[capture](parameters) -> return_type { body }
部分 说明
capture 捕获列表(捕获外部变量)
parameters 参数列表
return_type 返回类型(可省略)
body 函数体

(2) 4.2 基本示例

示例:Lambda排序(难度⭐)

CPP
#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;
}

输出:

TEXT 📖 仅展示
3 1 4 1 5 9 2 6

(3) 4.3 捕获列表

捕获列表决定Lambda能访问哪些外部变量。

捕获方式 说明
`` 不捕获任何变量
[x] 按值捕获x
[&x] 按引用捕获x
[=] 按值捕获所有变量
[&] 按引用捕获所有变量
[this] 捕获this指针(在类中使用)

示例:带状态的Lambda(难度⭐⭐)

CPP
#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翻倍(难度⭐)

CPP
#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:成绩处理器(难度⭐⭐⭐)

CPP
#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;
}
▶ 试一试

输出:

TEXT 📖 仅展示
最高分: 
及格人数:

❓ 常见问题

Q:Lambda和函数对象类哪个好? A:- 简单操作 → Lambda(代码简洁) - 复杂操作/需要复用 → 函数对象类(可维护性好)


Q:auto 能推导Lambda类型吗? A:Lambda类型是唯一的匿名类型,只能用 auto 推导,不能写具体类型。

CPP
auto func = (int x) { return x * 2; };
// std::function<int(int)> func = ... // 也可以,但有性能开销

Q:什么时候用 std::function A:当需要存储函数对象(作为成员变量、返回值等)时,用 std::function


▶ 示例 3:Lambda表达式(难度⭐)

CPP
#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;
}
▶ 试一试

输出:

TEXT 📖 仅展示
1 2 3 4 5
💡 提示:Lambda语法:[捕获](参数) { 函数体 }[&sum] 捕获引用,可以修改外部变量。


知识点 要点
函数指针 简单但功能有限
函数对象类 可自定义操作,可带状态
Lambda 匿名函数,简洁强大
预定义函数对象 std::plus等,在functional
捕获列表 Lambda访问外部变量的方式

📖 小节

📝 作业

  1. **基础题 (Difficulty ⭐):创建一个仿函数(重载 operator() 的类),实现"比较两个整数的大小",用 std::sort 测试。

  2. **进阶题 (Difficulty ⭐⭐):用 std::function 存储不同类型的可调用对象(普通函数、lambda、仿函数),统一调用。

  3. **挑战题 (Difficulty ⭐⭐⭐):用 std::bind 绑定部分参数,生成新的可调用对象。实现一个"参数预填充"的函数适配器。



下一课:STL适配器(#39)

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏