for_each和transform是C++ STL算法库中使用频率最高的两个遍历工具。前者偏向于“对每个元素执行某个动作”,后者偏向于“把每个元素经过运算后存到另一个地方”。虽然看起来相似,但两者在返回值设计、适用场景上差别很大。本文将分别介绍这两个算法的语法、用法和注意事项,最后给出对比总结。

for_each的基本用法与返回值特性
for_each定义在<algorithm>头文件中,其函数原型为for_each(InputIt first, InputIt last, UnaryFunction f)。它的作用是对区间[first, last)中的每个元素调用一次f(elem),并最终把这个函数对象按值返回。这一点经常被忽视,却非常有用:如果传入的是一个有状态的仿函数,可以通过返回值拿到遍历过程中累积的状态。
来看一个最简单的例子,遍历vector并打印每个元素:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
// 使用lambda对每个元素执行打印动作
std::for_each(v.begin(), v.end(), [](int x) {
std::cout << x << " ";
});
std::cout << std::endl;
return 0;
}for_each的一个重要特性是它可以返回传入的函数对象。下面的例子利用这一点统计元素总和,同时遍历过程中不需要额外的全局变量:
#include <algorithm>
#include <vector>
#include <iostream>
struct Sum {
int total = 0;
void operator()(int x) { total += x; }
};
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
// for_each会返回传入的函数对象副本,从中取出累积结果
Sum s = std::for_each(v.begin(), v.end(), Sum{});
std::cout << "sum = " << s.total << std::endl; // 输出 15
return 0;
}需要注意的是,for_each返回的是函数对象的拷贝,因此要求仿函数的状态在拷贝时能正确保留。另外,从C++11开始,range-based for循环在大多数“只读遍历”场景下比for_each更直观,但for_each在需要传递函数对象、配合STL管道风格代码时依然有自己的优势。
transform的三种典型用法
transform同样位于<algorithm>头文件,它有两种重载形式。第一种是单输入区间版本:transform(InputIt first, InputIt last, OutputIt d_first, UnaryOp op),把每个元素经过op运算后写入目标区间;第二种是双输入区间版本:transform(InputIt first1, InputIt last1, InputIt first2, OutputIt d_first, BinaryOp op),把两个区间中对应位置的元素进行二元运算后写入目标区间。它返回目标区间末尾的迭代器。
第一种典型用法是“转换并复制到另一个容器”,原容器保持不变:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> src{1, 2, 3, 4};
std::vector<int> dst(src.size());
// 每个元素平方后写入dst,src不变
std::transform(src.begin(), src.end(), dst.begin(),
[](int x) { return x * x; });
for (int x : dst) std::cout << x << " "; // 输出 1 4 9 16
return 0;
}第二种是原地修改,让目标迭代器和源迭代器指向同一容器即可,这在批量修改元素时非常方便:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
// 原地将每个元素取负
std::transform(v.begin(), v.end(), v.begin(),
[](int x) { return -x; });
for (int x : v) std::cout << x << " "; // 输出 -1 -2 -3 -4 -5
return 0;
}第三种是双区间合并,比如把两个vector对应位置相加:
#include <algorithm>
#include <vector>
#include <iostream>
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; });
for (int x : c) std::cout << x << " "; // 输出 11 22 33
return 0;
}使用双区间版本时要特别注意:第二个输入区间只提供了起始迭代器,算法默认它至少和第一个区间一样长,如果实际长度不足会引发未定义行为,编译器和运行时都不会帮你检查。如果目标容器为空,记得先调用resize或back_inserter来保证有足够空间,否则写入也会越界。
两者的核心区别与选型建议
从设计意图上看,for_each改变的是“状态”,它对元素做什么完全由你决定,可以是打印、累计、修改元素本身;transform强调的是“计算并产出新值”,它的函数对象必须有返回值,运算结果会被写入目标区间。因此transform的lambda里一定要写return语句,而for_each的lambda通常返回void。
从返回值上看也有明显差异:for_each返回传入的函数对象本身(按值拷贝),transform返回输出区间的尾后迭代器。利用transform的返回值配合back_inserter,可以优雅地向空容器追加结果:
#include <algorithm>
#include <iterator>
#include <vector>
#include <iostream>
int main() {
std::vector<int> src{1, 2, 3};
std::vector<int> dst; // 空容器,无需预先分配空间
std::transform(src.begin(), src.end(),
std::back_inserter(dst),
[](int x) { return x * 10; });
for (int x : dst) std::cout << x << " "; // 输出 10 20 30
return 0;
}还有一个容易被忽略的细节:如果想在for_each中通过仿函数修改元素,参数必须传引用(如void operator()(int& x)),传值只会修改副本。而transform原地修改版本中,lambda的参数按值传递即可,因为修改是通过返回值写回完成的。理解了这一点,两者在修改语义上的区别就非常清晰了。
总结一下选型经验:只需要遍历执行副作用(打印、统计、通知)时用for_each或range-based for;需要把元素经过某种运算变成新值时用transform;需要两个序列按位置做运算时,transform的双区间版本几乎是唯一简洁的选择。另外,如果项目中可以使用C++20,不妨了解一下std::ranges::transform和std::ranges::for_each,它们支持传入整个容器而不用手写begin和end,代码更简洁安全。
C++ STLfor_eachtransform算法修改时间:2026-09-12 00:38:36