C++ vector 遍历

在 C++ 中,当一个 vector 容器存放了十几个甚至上百个元素时,我们不可能一直手写 scores[0]、scores[1]、scores[2] 去逐个读取。把容器中的所有元素从头到尾按顺序访问一遍,这个过程就叫做 “遍历”。

遍历 vector 容器常用的有两种方式:一种是 “使用下标 for 循环遍历”,另一种是 “使用范围 for 循环遍历”。

使用下标 for 循环遍历

如果在遍历元素的同时还需要知道元素的下标位置,我们就可以像遍历数组那样使用普通的 for 循环。具体做法是让下标 i 从 0 开始,只要 i 小于元素个数 size(),就依次访问 容器名[i]。

语法:

for (int i = 0; i < 容器名.size(); i++) {
    // 循环体
}

说明:

由于 vector 的下标从 0 开始计算,最后一个元素的下标是 容器名.size() - 1。循环条件写成 i < 容器名.size() 正好覆盖所有有效下标,我们在循环体内通过 容器名[i] 即可读取或修改当前元素。

示例 1:使用下标循环遍历

#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> langs = {"C++", "Python", "Go"};

    for (int i = 0; i < langs.size(); i++) {
        std::cout << i << ": " << langs[i] << '\n';
    }
    return 0;
}

运行结果如下。

0: C++
1: Python
2: Go

分析:

在这个例子中,langs.size() 返回 3。循环变量 i 从 0 开始,每次循环后加 1,依次取 0、1、2,刚好对应容器的 3 个有效下标。当 i 增加到 3 时,条件 i < langs.size() 不再成立,循环随之结束。

因为 langs[i] 直接代表容器里第 i 个位置上的元素本身,所以在循环体里我们不仅可以读取 langs[i],也可以通过 langs[i] = 新值; 直接修改原容器中的数据。

在编写下标循环时,小伙伴们要注意:循环判断条件必须写小于号(<),不能写成小于等于号(<=)。如果误写成 i <= langs.size(),当 i 等于 3 时就会访问不存在的 langs[3],导致下标越界。

size() 与 std::size_t 类型

在日常练习中,下标循环写成 for (int i = 0; i < langs.size(); i++) 完全可以正常运行。不过开启编译器的严格警告后,有时会提示 “正在比较有符号整数与无符号整数”。

这是因为元素个数不可能是负数,size() 返回的是无符号整数类型 std::size_t,而不是 int。在正式项目中,把循环头写成 for (std::size_t i = 0; i < langs.size(); i++) 就能消除这条警告。

同时正因为 size() 返回无符号数,平时千万不要把条件写成 i <= langs.size() - 1。一旦遇到空容器,无符号数 0 - 1 不会变成 -1,反而会绕回一个极大的正整数引发越界,所以统一写 i < langs.size() 最稳妥。

使用范围 for 循环遍历(推荐)

如果我们只是想把 vector 里的元素从头到尾依次处理一遍,并不关心当前元素的下标是多少,那么使用范围 for 循环会更加简洁。它不需要我们手动维护下标 i,也不用担心写错循环边界。

1. 只读取元素

如果遍历的目的只是读取元素的值(比如打印输出或求和统计),不需要修改原容器里的数据,我们可以直接按值遍历。

语法:

for (元素类型 变量名 : 容器名) {
    // 循环体
}

说明:

每次循环时,程序都会从 vector 中按顺序取出一个元素的值,复制给冒号前面的循环变量。容器里有多少个元素,循环体就会自动执行多少次。

示例 2:计算元素总和

#include <iostream>
#include <vector>

int main() {
    std::vector<int> prices = {15, 25, 30};
    int total = 0;

    for (int price : prices) {
        total += price;
    }

    std::cout << "总价:" << total << '\n';
    return 0;
}

运行结果如下。

总价:70

分析:

for (int price : prices) 会依次把 prices 里的 15、25、30 取出来赋给变量 price,并累加到 total 中,最终算出总价 70。

此外,结合 auto 关键字,我们还可以让编译器根据容器自动推导循环变量的类型。对于上面这个例子来说,下面 2 种方式是等价的。

// 方式 1
for (int price : prices) {
    total += price;
}

// 方式 2(推荐)
for (auto price : prices) {
    total += price;
}

其中,方式 2 使用 auto 自动识别元素类型。即使以后修改了 vector 的数据类型,循环头也不需要跟着改动,在实际开发中更加省心。

2. 修改原元素

如果想通过范围 for 循环直接修改 vector 中的原元素,这里有一个初学者很容易踩坑的地方。

在上面的普通写法中,循环变量每次拿到的只是容器元素的一份 “副本”,就像你拿到了原件的复印件一样。我们在循环体里修改这个变量,改掉的只是手里的复印件,原容器里的数据并不会发生变化。要想直接修改原元素,需要在循环变量的类型后面加上 & 符号。

语法:

for (元素类型& 变量名 : 容器名) {
    // 循环体
}

说明:

加上 & 之后,循环变量不再复制一份新数据,而是作为原元素的引用,直接绑定到原容器里的每一个元素本身。关于引用的更多细节,后面章节会详细介绍。这里我们先记住 “加 & 才能改原值” 即可,平时常简写为 for (auto& 变量名 : 容器名)。

示例 3:修改容器中的原元素

#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores = {75, 80, 85};

    // 给每个成绩加 5 分
    for (auto& score : scores) {
        score += 5;
    }

    for (auto score : scores) {
        std::cout << score << ' ';
    }
    std::cout << '\n';
    return 0;
}

运行结果如下。

80 85 90

分析:

从运行结果可以看出,原容器中的 75、80、85 直接改成了 80、85、90。这是因为第 1 个循环写成了 for (auto& score : scores),score 直接对应原元素,执行 score += 5; 就会修改容器中的数据。

对于这个例子来说,如果把第 1 个循环里的 & 去掉,改成下面这样:

for (auto score : scores) {
    score += 5;
}

此时程序虽然不会报错,但每次加 5 修改的都只是临时复制出来的副本。最后打印出来的依然是原来的 75 80 85,原容器里的数据不会发生任何变化。

3. 只读较大元素(const auto&)

前面我们看到,不加 & 会把每个元素复制一份,加上 & 则可以直接操作原元素。那如果 vector 里存放的是 std::string 字符串这类占用空间较大的数据,而我们在遍历时只希望读取、不希望修改,应该怎么写呢?

这时最推荐的做法是在 auto& 前面再加上 const,写成 const auto&。

语法:

for (const auto& 变量名 : 容器名) {
    // 循环体
}

说明:

其中,& 避免了每次循环都把字符串重新复制一份,节省了运行开销。而前面的 const 起到了只读保护作用,一旦我们在循环体内不小心写了修改该变量的代码,编译器就会直接报错提醒。

示例 4:只读遍历字符串容器

#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> names = {"Jack", "Lucy", "Tony"};

    for (const auto& name : names) {
        std::cout << "Hello, " << name << '\n';
    }
    return 0;
}

运行结果如下。

Hello, Jack
Hello, Lucy
Hello, Tony

分析:

在这个例子中,const auto& name 直接读取容器里的原字符串来打印,既省去了多余的字符串复制,又能防止我们在循环中误改原数据。

到这里,小伙伴们可以把范围 for 循环的 3 种常见写法按需求这样区分:

  • 只读基本类型:写成 for (auto x : v),适合只读访问 int、double 等简单数值。
  • 修改原元素:写成 for (auto& x : v),我们需要直接修改容器中的原数据时使用。
  • 只读较大类型:写成 for (const auto& x : v),适合只读访问 std::string 等较大数据,避免重复复制。

至于下标 for 循环和范围 for 循环应该怎么选,判断标准也很清晰。如果在循环里需要用到元素下标,或者只遍历其中一部分元素,我们就使用下标 for 循环。如果只是从头到尾依次访问所有元素,优先使用范围 for 循环即可。

注意 在遍历 vector 的过程中,不要一边循环遍历同一个容器,一边在循环体里对它调用 push_back()、pop_back()、insert() 或 erase() 等改变元素数量的函数,否则会导致遍历位置错乱甚至引发程序崩溃。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号