std::vector 是 C++ 中最常用的动态数组容器之一。在实际开发中,读取、修改、筛选或统计 vector 中的数据时,遍历操作几乎不可避免。根据 C++ 版本、代码场景以及是否需要元素下标,遍历 vector 可以采用下标、迭代器、范围 for、auto、反向迭代器以及标准算法等多种方式。
下面结合具体代码,对 C++ 中遍历 vector 的常见方法进行详细介绍,并分析不同写法的适用场景。
1. 使用下标遍历 vector
最传统的方式是通过 vector 的下标访问元素。vector 的第一个元素下标为 0,最后一个元素下标为 size() - 1。
C++#include#include int main() { std::vector<int> nums = {10, 20, 30, 40, 50}; for (size_t i = 0; i < nums.size(); ++i) { std::cout << nums[i] << std::endl; } return 0; }
这种写法最大的优点是可以直接获得元素下标,因此特别适合需要同时处理“索引 + 元素”的场景。
例如:
C++for (size_t i = 0; i < nums.size(); ++i) { std::cout << "index = " << i << ", value = " << nums[i] << std::endl; }
使用下标遍历时的注意事项
不要将 vector::size() 保存到 int 中进行无意义的类型混用:
C++for (int i = 0; i < nums.size(); ++i) { // ... }
更推荐使用:
C++for (size_t i = 0; i < nums.size(); ++i) { // ... }
或者使用 C++20 中的相关类型:
C++for (std::vector<int>::size_type i = 0; i < nums.size(); ++i) { // ... }
如果只需要顺序读取元素,而不需要下标,那么使用范围 for 通常更加简洁。
2. 使用迭代器遍历 vector
迭代器是 C++ 标准容器的重要组成部分。通过 begin() 和 end() 可以获取遍历范围。
C++#include#include int main() { std::vector<int> nums = {10, 20, 30, 40, 50}; for (std::vector<int>::iterator it = nums.begin(); it != nums.end(); ++it) { std::cout << *it << std::endl; } return 0; }
这里的 it 是一个迭代器,通过 *it 可以取得当前元素。
遍历过程可以理解为:
begin() ↓ 10 → 20 → 30 → 40 → 50 ↑ end()
end() 并不指向最后一个元素,而是指向最后一个元素之后的位置,因此循环条件必须写成:
C++it != nums.end()
而不是:
C++it <= nums.end()
迭代器适合什么场景
迭代器在需要进行插入、删除、查找等容器操作时非常实用。例如删除所有偶数:
C++for (auto it = nums.begin(); it != nums.end(); ) { if (*it % 2 == 0) { it = nums.erase(it); } else { ++it; } }
这里需要特别注意 erase() 的返回值。删除当前元素后,原来的迭代器可能失效,因此应该使用:
C++it = nums.erase(it);
而不是删除后继续无条件执行:
C++++it;
3. 使用 const_iterator 遍历只读 vector
如果遍历过程中不会修改元素,可以使用 const_iterator:
C++std::vector<int> nums = {10, 20, 30, 40, 50}; for (std::vector<int>::const_iterator it = nums.begin(); it != nums.end(); ++it) { std::cout << *it << std::endl; }
对于 const vector,只能使用常量迭代器:
C++const std::vector<int> nums = {10, 20, 30}; for (auto it = nums.begin(); it != nums.end(); ++it) { std::cout << *it << std::endl; }
由于 nums 本身是 const,此时 begin() 返回的也是 const_iterator。
现代 C++ 中通常不需要手动写出完整的迭代器类型,直接使用 auto 即可。
4. 使用范围 for 循环遍历 vector
C++11 引入范围 for 循环后,遍历 vector 变得更加简单。
C++std::vector<int> nums = {10, 20, 30, 40, 50}; for (int value : nums) { std::cout << value << std::endl; }
这种方式会依次取得 vector 中的每个元素。
与传统下标循环相比,代码更加简洁,也不需要手动管理迭代器。
修改 vector 中的元素
如果需要修改元素,应当使用引用:
C++for (int& value : nums) { value *= 2; }
执行后:
20 40 60 80 100
如果写成:
C++for (int value : nums) { value *= 2; }
修改的只是元素副本,并不会改变 vector 中的数据。
因此,两者有明显区别:
C++for (int value : nums)
表示复制元素。
C++for (int& value : nums)
表示直接引用原元素。
如果只读且不希望发生复制,可以使用:
C++for (const int& value : nums) { std::cout << value << std::endl; }
对于 int 这类体积较小的类型,直接按值读取通常也没有明显问题;对于 std::string、大型结构体等对象,使用 const& 可以避免不必要的复制。
5. 使用 auto 简化 vector 遍历
现代 C++ 中非常推荐使用 auto 简化迭代器代码:
C++std::vector<int> nums = {10, 20, 30, 40, 50}; for (auto it = nums.begin(); it != nums.end(); ++it) { std::cout << *it << std::endl; }
相比:
C++for (std::vector<int>::iterator it = nums.begin(); it != nums.end(); ++it)
auto 可以避免书写冗长的类型名称。
只读场景也可以使用:
C++for (auto it = nums.cbegin(); it != nums.cend(); ++it) { std::cout << *it << std::endl; }
其中:
C++begin()
返回普通迭代器。
C++cbegin()
返回 const_iterator。
对应的结束迭代器分别是:
C++end() cend()
6. 使用 auto& 遍历并修改元素
如果元素类型比较复杂,使用 auto& 会非常方便:
C++std::vector<std::string> names = { "Tom", "Jack", "Lucy" }; for (auto& name : names) { name += "_user"; }
遍历结束后:
Tom_user Jack_user Lucy_user
这里的 auto& 会自动推导元素类型,同时保持引用关系。
如果只读,则推荐:
C++for (const auto& name : names) { std::cout << name << std::endl; }
这是一种非常常见的现代 C++ 写法。
7. 使用 auto&& 进行泛化遍历
在模板代码或者泛型编程中,还可以看到:
C++for (auto&& value : nums) { std::cout << value << std::endl; }
auto&& 可以根据遍历对象的值类别进行引用类型推导,因此在泛型代码中更加灵活。
普通业务代码中,如果明确知道只读还是修改,通常优先使用:
C++const auto&
或者:
C++auto&
没有必要为了追求复杂写法而全部使用 auto&&。
8. 使用反向迭代器遍历 vector
如果希望从最后一个元素开始遍历,可以使用 rbegin() 和 rend()。
C++std::vector<int> nums = {10, 20, 30, 40, 50}; for (auto it = nums.rbegin(); it != nums.rend(); ++it) { std::cout << *it << std::endl; }
输出:
50 40 30 20 10
其中:
C++rbegin()
指向最后一个元素。
C++rend()
表示反向遍历结束位置。
这种方法适合需要倒序处理数据的场景。
例如处理最近添加的数据时,可以直接从 rbegin() 开始,而不需要手动计算:
C++nums.size() - 1
9. 使用 const_reverse_iterator 进行只读倒序遍历
如果只需要读取数据,可以使用:
C++for (auto it = nums.crbegin(); it != nums.crend(); ++it) { std::cout << *it << std::endl; }
crbegin() 和 crend() 返回常量反向迭代器。
相比手动写下标:
C++for (int i = static_cast<int>(nums.size()) - 1; i >= 0; --i) { std::cout << nums[i] << std::endl; }
反向迭代器不需要处理有符号整数与 size_t 混用的问题,因此代码更加安全直观。
10. 使用 C++20 ranges 遍历 vector
如果项目使用 C++20,可以利用 ranges 提供的功能。
最基本的遍历仍然可以写成:
C++#include#include #include int main() { std::vector<int> nums = {10, 20, 30, 40, 50}; for (int value : nums) { std::cout << value << std::endl; } }
对于更复杂的数据处理,可以使用 std::views。
例如只遍历偶数:
C++for (int value : nums | std::views::filter([](int x) { return x % 2 == 0; })) { std::cout << value << std::endl; }
这种写法将“遍历”和“数据筛选”组合起来,非常适合现代 C++ 的函数式风格。
还可以使用 transform:
C++for (int value : nums | std::views::transform([](int x) { return x * 2; })) { std::cout << value << std::endl; }
需要注意,views 通常采用惰性求值,并不会简单地创建一个新的 vector 保存中间结果。
11. 使用 std::for_each 遍历 vector
标准库中的 std::for_each 也可以用于遍历:
C++#include#include #include int main() { std::vector<int> nums = {10, 20, 30, 40, 50}; std::for_each(nums.begin(), nums.end(), [](int value) { std::cout << value << std::endl; }); return 0; }
如果需要修改元素,可以使用引用:
C++std::for_each(nums.begin(), nums.end(), [](int& value) { value *= 2; });
这种方式将“遍历过程”和“处理逻辑”分离开来,在算法组合场景中比较方便。
不过,如果只是简单打印所有元素:
C++for (const auto& value : nums) { std::cout << value << std::endl; }
通常比 std::for_each 更容易阅读。
12. vector 中同时获取下标和元素
实际开发中经常遇到一个问题:既想使用现代范围 for,又希望知道当前元素的下标。
传统方式可以直接使用:
C++for (size_t i = 0; i < nums.size(); ++i) { std::cout << i << ": " << nums[i] << std::endl; }
也可以使用迭代器计算距离:
C++for (auto it = nums.begin(); it != nums.end(); ++it) { auto index = std::distance(nums.begin(), it); std::cout << index << ": " << *it << std::endl; }
对于 vector,迭代器属于随机访问迭代器,因此还可以直接使用:
C++auto index = it - nums.begin();
不过,如果代码的核心需求就是索引,直接使用下标循环往往更清楚。
13. 遍历 vector 时修改元素
如果目的是修改已有元素,下面几种写法都比较常见。
使用下标
C++for (size_t i = 0; i < nums.size(); ++i) { nums[i] += 10; }
使用引用
C++for (auto& value : nums) { value += 10; }
使用迭代器
C++for (auto it = nums.begin(); it != nums.end(); ++it) { *it += 10; }
其中范围 for + auto& 通常最简洁:
C++for (auto& value : nums) { value += 10; }
14. 遍历 vector 时删除元素
删除元素是遍历 vector 时最容易出现问题的场景之一。
错误示例:
C++for (auto it = nums.begin(); it != nums.end(); ++it) { if (*it % 2 == 0) { nums.erase(it); } }
这种写法存在迭代器失效问题。
正确方式是接收 erase() 返回的新迭代器:
C++for (auto it = nums.begin(); it != nums.end(); ) { if (*it % 2 == 0) { it = nums.erase(it); } else { ++it; } }
C++20 中,如果条件比较简单,还可以使用 std::erase_if:
C++std::erase_if(nums, [](int value) { return value % 2 == 0; });
这比手动编写删除循环更加简洁。
对于 C++17 及以前的代码,也可以结合 remove_if 和 erase:
C++nums.erase( std::remove_if(nums.begin(), nums.end(), [](int value) { return value % 2 == 0; }), nums.end() );
这种经典的“erase-remove”模式在旧版本 C++ 项目中仍然非常常见。
15. 不同 vector 遍历方法对比
| 遍历方式 | 支持版本 | 可获取下标 | 可修改元素 | 代码简洁度 |
|---|---|---|---|---|
下标 for | C++98 | 是 | 是 | 一般 |
| 普通迭代器 | C++98 | 间接 | 是 | 一般 |
const_iterator | C++98 | 间接 | 否 | 一般 |
范围 for | C++11 | 否 | 是 | 高 |
auto 迭代器 | C++11 | 间接 | 是 | 高 |
| 反向迭代器 | C++98 | 间接 | 是 | 高 |
std::for_each | C++98 | 间接 | 是 | 较高 |
| ranges/views | C++20 | 视具体实现 | 是 | 高 |
如果项目使用 C++11 或更高版本,普通业务代码通常优先考虑范围 for。
16. 实际开发中的选择建议
只需要依次读取元素时:
C++for (const auto& value : nums) { // ... }
需要修改元素时:
C++for (auto& value : nums) { // ... }
需要元素下标时:
C++for (size_t i = 0; i < nums.size(); ++i) { // ... }
需要倒序遍历时:
C++for (auto it = nums.rbegin(); it != nums.rend(); ++it) { // ... }
需要遍历过程中删除元素时:
C++for (auto it = nums.begin(); it != nums.end(); ) { if (/* 删除条件 */) { it = nums.erase(it); } else { ++it; } }
C++20 项目中,如果涉及筛选、转换、截取等组合操作,则可以进一步考虑 ranges。
17. 常见问题
vector 能不能使用 foreach 遍历?
C++ 没有 Java、C# 那种名称为 foreach 的内置关键字,但 C++11 提供了范围 for:
C++for (const auto& value : nums) { std::cout << value << std::endl; }
它可以实现类似 foreach 的效果。
vector.size() 为什么不推荐直接和 int 混用?
vector::size() 返回的是无符号类型 size_type。如果与 int 混合比较,可能产生编译器警告,也可能在边界情况下带来逻辑问题。因此下标循环最好使用 size_t 或 vector 的 size_type。
for (auto value : vector) 会修改原数据吗?
不会。
C++for (auto value : nums) { value++; }
这里的 value 是元素副本。
如果需要修改原数据,应写成:
C++for (auto& value : nums) { value++; }
遍历 vector 哪种方法性能最好?
对于简单顺序遍历,使用下标、迭代器和范围 for 通常都能获得非常接近的性能。实际代码中不应该单纯为了追求所谓“最快遍历方式”而牺牲可读性。
对于现代 C++ 项目,范围 for 往往是简单遍历的首选。
遍历 vector 时为什么不能随意删除元素?
因为 vector 的元素存储在连续内存中,删除元素后后续元素可能发生移动,同时相关迭代器可能失效。因此删除操作必须按照容器规定的迭代器失效规则处理。
18. 总结
C++ 中遍历 vector 并没有唯一写法,不同方式适用于不同场景。传统下标遍历适合需要索引的业务逻辑;迭代器适合需要精细控制容器操作的场景;范围 for 简洁直观,适合绝大多数普通遍历;反向迭代器适合倒序处理数据;std::for_each 和 C++20 ranges 则更适合算法化、泛型化的数据处理。
如果使用现代 C++,可以优先建立这样的选择习惯:只读使用 const auto&,修改使用 auto&,需要下标使用索引循环,需要倒序使用反向迭代器,需要复杂筛选或转换时考虑 ranges。掌握这些区别后,既能减少不必要的代码,也能有效避免迭代器失效、元素复制以及类型混用等常见问题。