std::for_each 无法用 const_iterator 遍历并修改元素,因 const_iterator 解引用得 const t&,而 lambda 若声明 int& 参数则绑定失败;只读遍历时用普通迭代器配值参或 const 引用参数即可。

std::for_each 为什么遍历不了 vector 的 const_iterator?
因为 std::for_each 默认要求迭代器可解引用并赋值(即非 const),而你传了 cbegin()/cend() 或手动写了 const_iterator,却在 lambda 里试图修改元素——编译器会直接报错:assignment of read-only location。
实际场景中,多数人想「只读遍历」,但误以为必须用 const 迭代器才能安全;其实只要 lambda 参数是 const T& 或值类型,用普通 begin()/end() 完全没问题,且更兼容。
- 正确写法:
std::for_each(v.begin(), v.end(), [](int x) { /* 只读用 x */ }); - 错误写法:
std::for_each(v.cbegin(), v.cend(), [](int& x) { x = 0; });——x是const int&,不能绑定到int& - 若真要修改,别用
cbegin(),也别给 lambda 写int&参数,改用int&捕获或直接用普通迭代器
lambda 捕获方式影响 for_each 的副作用可见性
std::for_each 本身不保证 lambda 被调用几次(虽然实际是 N 次),但它不“返回”任何东西,所有状态变更必须靠捕获实现。很多人以为捕获 [&] 就万事大吉,结果发现变量没变——其实是 lambda 被复制了,而副本修改了局部副本。
典型表现:循环结束后计数器还是 0,或者容器没被修改。根本原因是 std::for_each 内部可能拷贝 lambda 对象(尤其在某些 libstdc++ 实现中)。
立即学习“C++免费学习笔记(深入)”;
- 安全做法:用
[&counter, &vec]() mutable { counter++; vec.push_back(...); }——mutable允许修改捕获的副本,但注意vec是引用,所以仍生效 - 更稳方案:避免依赖 lambda 副作用,改用传统 for 循环,或把逻辑封装进函数对象(
struct+operator()),显式控制拷贝语义 - 别用
[=]捕获需要修改的变量,除非你明确知道它会被拷贝且你希望修改副本
for_each 和 range-based for 性能差多少?
几乎没差别。现代编译器(GCC 9+、Clang 10+、MSVC 2019)对两者生成的汇编基本一致,都是简单指针/迭代器递增 + 解引用。所谓“for_each 更快”或“更慢”都是过时经验。
真正影响性能的是 lambda 是否内联、是否触发别名分析失败、以及容器类型(vector 快,list 慢)。但这些和遍历语法无关。
- 优先选 range-based for:写起来直觉,调试时断点位置清晰,IDE 支持好
- 只在需要复用算法逻辑(比如和
std::transform统一接口)、或配合其他 STL 算法组合使用时,才用std::for_each - 别为了“函数式风格”硬套
for_each,C++ 不是 Haskell,可读性比范式更重要
std::for_each 在 std::map 遍历时容易漏掉 key/value 类型
std::map<k>::iterator</k> 解引用得到的是 std::pair<const k v></const>,不是 std::pair<k></k>。很多代码直接写 [](auto p) { p.first = ...; },结果编译失败:assignment of read-only member 'std::pair<const int>::first'</const>。
这不是 bug,是 map 设计使然:key 必须不可变,否则破坏红黑树结构。但新手常忽略这个 const,尤其从 vector 切换过来时。
- 正确访问 key:
[](const auto& p) { std::cout —— <code>p.first是const K&,只读安全 - 修改 value:
[](auto& p) { p.second = 42; }——p.second是V&,可写 - 别用
auto p(值拷贝),尤其是 value 类型大时;用const auto& p或auto& p明确意图
最常被忽略的点是:for_each 不检查迭代器有效性,也不做空范围保护。传入 v.end() 和 v.begin() 顺序颠倒、或容器在 lambda 中被意外清空,都会导致未定义行为——它不会像 range-for 那样天然规避这类问题。











