在C++标准模板库里,遍历容器元素是最基础也最高频的操作。除了手写循环,算法库提供了多种通用遍历工具,其中for_each与transform最为常用。二者接口相似,却承担不同的设计意图:一个关注过程动作,一个关注数据变换。

for_each的典型应用
for_each接受一对迭代器和一个可调用对象,依次把每个元素传给该对象。它的返回值是传入的函数对象,因此适合执行打印、修改原元素、累加等带副作用的操作。由于直接操作原容器,不需要额外空间,在仅需处理而不产生新序列时非常直观。
下面示例用for_each将vector中所有整数加倍,并打印结果:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {1, 2, 3, 4};
// 使用lambda修改原容器元素
std::for_each(nums.begin(), nums.end(), [](int& n) {
n *= 2;
});
// 再次遍历打印
std::for_each(nums.begin(), nums.end(), [](int n) {
std::cout << n << " ";
});
return 0;
}
上述代码第一个lambda通过引用捕获参数,直接改写原vector;第二个lambda仅做输出。可以看到,for_each并不关心返回值,重点在于“对每个元素做什么”。如果遍历目的是触发行为而非生成新数据,它比手写for更简洁且语义清晰。
需要注意的是,若lambda内部有复杂状态,可将其定义为有成员变量的函数对象,利用for_each的返回值取回状态,例如统计满足条件的个数。但总体上,它不适合用来构建新容器。
transform的典型应用
transform用于将一个序列映射为另一个序列。它有两种重载:单序列变换与双序列二元运算。结果写入由输出迭代器指定的目标位置,因此可以原地处理,也可以输出到新容器。它的核心是“输入到输出的映射关系”,强调不可变转换。
以下代码把整数向量平方后存入新向量:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> src = {1, 2, 3, 4};
std::vector<int> dst(src.size());
std::transform(src.begin(), src.end(), dst.begin(), [](int n) {
return n * n;
});
for (int v : dst) {
std::cout << v << " ";
}
return 0;
}
这里transform读取src每个元素,经lambda计算后写入dst。若想原地修改,只需把输出迭代器设为src.begin()。与for_each不同,它明确要求返回新值,编译器会强制你表达映射逻辑,从而减少隐式修改带来的bug。
双序列版本可合并两个向量,例如对应元素相加:
#include <vector>
#include <algorithm>
int main() {
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {10, 20, 30};
std::vector<int> c(a.size());
std::transform(a.begin(), a.end(), b.begin(), c.begin(),
[](int x, int y) { return x + y; });
return 0;
}
二者差异与选型建议
从语义看,for_each是“做动作”,transform是“算新值”。若操作会改变原对象状态或无返回值,选for_each;若要把A序列变成B序列,选transform。误用会导致代码意图模糊,比如用for_each内部push_back到另一个容器,虽能跑却违背其设计。
性能上二者都属线性复杂度,差别主要在表达力。下表简要对比:
| 算法 | 输入 | 输出 | 典型用途 |
|---|---|---|---|
| for_each | 单序列 | 无(副作用) | 打印、修改原值、触发事件 |
| transform | 单或双序列 | 新序列 | 类型转换、数值映射、合并 |
实际工程中,配合lambda和范围库(C++20 ranges)能进一步精简遍历代码。但理解这两个算法的根本区别,是写出可读且健壮STL代码的前提。
自定义类型中的实践
对于自定义结构体,同样适用。假设有表示学生的结构,需要用for_each输出姓名,用transform提取成绩列表:
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Student {
std::string name;
int score;
};
int main() {
std::vector<Student> list = {
{"Alice", 90}, {"Bob", 85}
};
std::for_each(list.begin(), list.end(), [](const Student& s) {
std::cout << s.name << "n";
});
std::vector<int> scores(list.size());
std::transform(list.begin(), list.end(), scores.begin(),
[](const Student& s) { return s.score; });
return 0;
}
这段代码清晰展示了二者分工:遍历动作与数据投影解耦。当容器元素变复杂时,这种显式区分能降低维护成本。建议在团队代码中明确约定,避免混用造成逻辑隐蔽。