C++中遍历vector的多种方法详解

0 次阅读

std::vector 是 C++ 中最常用的动态数组容器之一。在实际开发中,读取、修改、筛选或统计 vector 中的数据时,遍历操作几乎不可避免。根据 C++ 版本、代码场景以及是否需要元素下标,遍历 vector 可以采用下标、迭代器、范围 forauto、反向迭代器以及标准算法等多种方式。

下面结合具体代码,对 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_iferase

C++
nums.erase(
    std::remove_if(nums.begin(), nums.end(),
                   [](int value) {
                       return value % 2 == 0;
                   }),
    nums.end()
);

这种经典的“erase-remove”模式在旧版本 C++ 项目中仍然非常常见。


15. 不同 vector 遍历方法对比

遍历方式支持版本可获取下标可修改元素代码简洁度
下标 forC++98一般
普通迭代器C++98间接一般
const_iteratorC++98间接一般
范围 forC++11
auto 迭代器C++11间接
反向迭代器C++98间接
std::for_eachC++98间接较高
ranges/viewsC++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_tvectorsize_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。掌握这些区别后,既能减少不必要的代码,也能有效避免迭代器失效、元素复制以及类型混用等常见问题。