🐸 青蛙编程网
首页 / C++ / STL / 2. vector

vector:最常用的动态数组

这是什么:vector——C++ STL 里最常用的容器,一个会自己长大的动态数组,放多少都不会溢出。

为什么用:上一章认识了 STL 三件套,该请出出场率最高的这位了。普通数组得提前定好长度,元素一多就装不下;vector 不用改代码就能自动伸缩,堪称 C++ 版的 Python 列表。

如何用:一个 std::vector<int> 声明就能起步,本章从 push_back 增删、size() 取长度,一路讲到两种遍历和越界。

定义与增删

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {10, 20, 30};  // 定义时给初始值

    v.push_back(40);   // 尾部加一个 40
    v.push_back(50);   // 尾部加一个 50
    v.pop_back();      // 尾部删一个(删掉的正是 50)

    std::cout << "个数:" << v.size() << std::endl;
    std::cout << "第 1 个:" << v[0] << std::endl;
    std::cout << "最后一个:" << v[v.size() - 1] << std::endl;
    return 0;
}

你会看到:

个数:4
第 1 个:10
最后一个:40

push_back 加在尾部,pop_back 删掉尾部,size() 报个数,v[i] 按下标取值——下标从 0 起,和 Python 列表一样。

两种遍历

挨个处理叫遍历,vector 有两种常用写法:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {10, 20, 30, 40};

    for (int i = 0; i < (int)v.size(); i++) {  // 写法一:下标 for
        std::cout << v[i] << " ";
    }
    std::cout << std::endl;

    for (int x : v) {                          // 写法二:range-for
        std::cout << x << " ";
    }
    std::cout << std::endl;
    return 0;
}

你会看到:

10 20 30 40
10 20 30 40

要修改元素、关心"第几个",用下标 for;只读一遍,range-for 更省心。小提示:size() 返回的是无符号数,直接和 int 比较编译器会嘀咕,加个 (int) 转换就清净了。

修改元素

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {10, 20, 30};

    v[1] = 99;                    // 把第 2 个改成 99
    std::cout << v[1] << std::endl;  // 99

    // 插入、删除中间元素(较慢,因为要搬移后续元素)
    v.insert(v.begin() + 1, 55);  // 在下标 1 位置插入 55
    v.erase(v.begin() + 2);       // 删除下标 2 的元素

    for (int x : v) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    return 0;
}

你会看到:

99
10 55 30

自动扩容的直觉

vector 内部其实管着一小块连续内存。装满了还 push_back?它会悄悄换个更大的房子:申请一块差不多翻倍的空间,把老数据整体搬过去,再继续装。所以"会自己长大"不是魔法,是偶尔搬一次家——大部分时候 push_back 都飞快。

常用操作速查

操作 干什么 备注
v.push_back(x) 尾部加 x 最常用的加法
v.pop_back() 尾部删一个 删之前确认非空
v.size() 元素个数 无符号数
v[i] 取第 i 个 下标从 0 起,越界会出事
v.empty() 是否为空 比 size() == 0 好读
v.insert(pos, x) 在位置插入 较慢,会搬移元素
v.erase(pos) 删除位置元素 较慢,会搬移元素
v.clear() 清空所有元素 个数变 0

记忆口诀

vector 是长大的数组:尾进尾出 push/pop,size 数个数,下标从 0 起。

❓ 常见问题

Q1:v[i] 越界会怎样?

v[i] 不做边界检查,越界是未定义行为,可能打印乱数、可能崩溃。想安全访问用 v.at(i),越界会抛出异常(但性能略慢)。

Q2:vector 能存不同类型的数据吗?

不能。std::vector<int> 只能存 int,std::vector<std::string> 只能存 string。C++ 是静态类型,一个容器里元素类型必须一致。

Q3:push_back 很慢吗?

大部分时候很快(摊还 O(1))。偶尔触发扩容时会慢一点(要申请新内存、拷贝旧数据),但这种情况很少。

⚠️ 新手翻车现场

翻车一:v[v.size()] 越界

// 错误:size 是个数,最大有效下标是 size()-1
std::cout << v[v.size()] << std::endl;

问题:v.size() 是元素个数,比如 4 个元素,有效下标是 0~3,v[4] 已经越界。

修正:取最后一个用 v[v.size() - 1]。

翻车二:pop_back() 对空 vector 操作

std::vector<int> v;  // 空
v.pop_back();        // 未定义行为,可能崩溃

问题:空 vector 没有尾部元素可删。

修正:删之前判空:if (!v.empty()) v.pop_back();

🎯 什么时候用 vector

  • 需要一个可变长度的数组
  • 主要在尾部添加/删除元素
  • 需要按下标快速随机访问元素
  • 元素类型统一

📝 本章小结

  • vector 是会自动长大的动态数组,下标从 0 起
  • push_back 尾部加,pop_back 尾部删,size() 报个数
  • 遍历:下标 for(可修改)或 range-for(只读更省心)
  • v[i] 不做越界检查,越界未定义行为;安全访问用 v.at(i)
  • 扩容是自动的,大部分 push_back 都很快
  • 记忆口诀:vector 是长大的数组:尾进尾出 push/pop,size 数个数,下标从 0 起。

动手改

  1. 建一个 std::vector<std::string>(记得 #include <string>)装 3 种水果,加一种、删一种,打印 size 和剩下的全部元素
  2. 用下标 for 把每个元素乘 2(v[i] = v[i] * 2;),再打印整个 vector
  3. 试着打印 v[v.size()]——这就是越界,程序可能当场崩溃;取"最后一个"的正确写法是 v[v.size() - 1]
  4. 用 v.at(i) 代替 v[i],故意越界看看会抛出什么异常

下一章:string,处理文字也是容器思路 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。