c++如何使用std::for_each_c++容器算法遍历详解【总结】

2次阅读

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

c++如何使用std::for_each_c++容器算法遍历详解【总结】

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; }); —— xconst 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_eachc++ 不是 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.firstconst K&,只读安全
  • 修改 value:[](auto& p) { p.second = 42; } —— p.secondV&,可写
  • 别用 auto p(值拷贝),尤其是 value 类型大时;用 const auto& pauto& p 明确意图

最常被忽略的点是:for_each 不检查迭代器有效性,也不做空范围保护。传入 v.end()v.begin() 顺序颠倒、或容器在 lambda 中被意外清空,都会导致未定义行为——它不会像 range-for 那样天然规避这类问题。

text=ZqhQzanResources