最直接可控的索引遍历方式是普通for循环,适用于需同时访问元素值和索引的场景;原生数组无size()成员函数,须用sizeof或std::size,std::array才有.size()。

用普通 for 循环直接控制索引
这是最直接、最可控的方式,尤其适合需要同时访问元素值和索引的场景。C++ 数组(包括原生数组和 std::array)不自带索引迭代器,所以显式维护索引变量是最稳妥的做法。
常见错误是混淆 size() 和 length() —— 原生数组没有成员函数,必须用 sizeof(arr)/sizeof(arr[0]) 或 C++17 的 std::size(arr);std::array 才有 .size()。
- 对原生数组:
int arr[] = {10, 20, 30}; for (size_t i = 0; i - 对
std::array:std::array<int> a = {10, 20, 30}; for (size_t i = 0; i </int> - 注意:用
size_t而非int避免无符号比较警告;循环条件用而非 <code>,防止越界
用基于范围的 for 循环 + 索引变量(手动计数)
当代码主体更关注元素值,但偶尔需要当前索引时,可以在范围 for 外声明一个索引变量并递增。这种方式语义清晰,且避免了手动计算下标。
容易踩的坑是忘记初始化或在循环内重置索引,或者误把索引定义在循环体内导致每次都是 0。
- 正确写法:
std::vector<int> v = {100, 200, 300}; size_t idx = 0; for (const auto& x : v) { std::cout </int> - 如果需要只读访问且不修改
idx,也可用size_t idx = 0;放在循环前,然后在循环体中用idx++或++idx(效果一致) - 不推荐在循环内写
for (size_t idx = 0; auto& x : v)—— 语法非法
用 std::enumerate?C++23 还没它
别被 Python 或某些提案误导:std::enumerate 并未进入 C++23 标准。目前标准库中没有任何容器或算法能像 Python 那样返回 (index, value) 对。
有人尝试用 std::views::iota 配合 std::views::zip 模拟,但这要求 C++23 + `
- 示例(仅限 C++23,且仅适用于容器):
std::vector<int> v = {4, 5, 6}; auto indices = std::views::iota(0u); auto zipped = std::views::zip(indices, v); for (auto [i, x] : zipped) { std::cout </int> - 原生数组不能直接用于
std::views::zip,会编译失败;必须包装成std::span,多一层间接 - 除非你明确锁定 C++23 工具链且团队熟悉 ranges,否则不建议在生产环境用这套方案
为什么不要用指针减法“算”索引
有人会想:既然遍历用的是指针(比如 for (auto p = begin(arr); p != end(arr); ++p)),那 p - begin(arr) 就是索引 —— 理论上没错,但隐患明显。
问题在于可读性差、易出错,且对非随机访问迭代器(如 std::list::iterator)完全失效。即使对数组有效,也增加了认知负担。
- 不推荐写法:
int arr[] = {7, 8, 9}; for (auto p = std::begin(arr); p != std::end(arr); ++p) { size_t i = p - std::begin(arr); // 可行但绕远 std::cout - 这种写法在调试时难以一眼看出意图;一旦换成
std::list,p - begin()直接编译不过 - 性能上毫无优势 —— 编译器通常能把普通 for 循环优化到同样水平
真正需要索引时,老老实实用 size_t i = 0; i 最安全、最通用、最容易被同事理解。那些“更现代”的写法,要么还没落地,要么代价远超收益。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











