C++ vector 容器(完整教程)

C++ vector 容器:动态数组的高效之选

在 C++ 编程中,数组是处理数据最基础的工具之一。但传统的 C 风格数组存在一个致命缺陷:大小固定,无法在运行时动态扩展。这就像你买了一只固定容量的水杯,一旦装满就再也加不进去了。为了解决这个问题,C++ 标准库提供了 vector 容器——一个智能、灵活、高效的动态数组。

vector 容器不仅支持自动扩容,还封装了内存管理、边界检查(可选)、丰富的操作接口。它就像一个“会自己长大的水桶”,你往里倒水,它会自动变大,不需要手动扩容,也不用担心内存泄漏。对于初学者来说,掌握 vector 是迈向高效 C++ 编程的第一步。


什么是 C++ vector 容器?

vector 是 C++ 标准模板库(STL)中最重要的容器之一,它本质上是一个动态数组。与普通数组不同,vector 在运行时可以自动调整大小,支持在末尾添加或删除元素,同时提供高效的随机访问能力。

想象一下你在写一个学生成绩管理系统。你不知道今天要录入多少个学生,但你又不想每次重新定义数组大小。这时,vector 就是你最好的选择。它能根据实际需要自动“生长”,无需预估最大容量。

要使用 vector,需要包含头文件 <vector>

#include <vector>

vector 是模板类,因此你可以定义存储任意类型数据的容器,例如 vector<int>vector<string>vector<double> 等。


创建数组与初始化

创建 vector 有多种方式,每种适用于不同场景。

1. 默认构造(空容器)

std::vector<int> scores; // 创建一个空的整型 vector

这相当于创建了一个“空水桶”,还没有装水。你可以后续通过 push_back 添加元素。

2. 指定大小并初始化

std::vector<int> grades(5, 0); // 创建大小为 5 的 vector,所有元素初始化为 0

这个操作相当于准备了 5 个空杯子,并且每个杯子都倒满了 0 毫升水。适用于初始化一个固定长度的数组,比如记录 5 门课程的成绩。

3. 使用初始化列表(C++ 11 起支持)

std::vector<std::string> names = {"Alice", "Bob", "Charlie", "Diana"};

这是最简洁的方式,就像直接把名字列成清单放进桶里。C++ 11 引入的初始化列表让代码更直观、更现代。

4. 从其他容器或数组复制

int raw_array[] = {10, 20, 30};
std::vector<int> numbers(raw_array, raw_array + 3); // 从数组复制前 3 个元素

这相当于从一个旧水桶里舀水,倒入新桶中。注意要指定起始和结束位置,避免越界。


常用操作与方法详解

掌握 vector 的核心操作,才能真正驾驭它。

添加元素:push_back 与 emplace_back

std::vector<int> scores;

// 添加一个元素到末尾
scores.push_back(95);     // 添加 95
scores.push_back(88);     // 添加 88
scores.push_back(92);     // 添加 92

push_back 是最常用的插入方法,它在容器末尾添加一个副本。但注意,如果容量不足,vector 会自动扩容(通常是翻倍),这会带来一定性能开销。

更高效的方式是 emplace_back,它直接在容器末尾构造对象,避免了拷贝或移动开销:

std::vector<std::string> names;
names.emplace_back("Eve"); // 直接构造 "Eve" 对象,无需临时对象

访问元素:下标与 at 方法

std::vector<int> values = {10, 20, 30, 40};

// 使用下标访问(速度快,但不检查边界)
std::cout << values[1] << std::endl; // 输出 20

// 使用 at() 方法访问(会检查边界,越界会抛异常)
std::cout << values.at(2) << std::endl; // 输出 30

// 若越界,例如 values.at(10),会抛出 std::out_of_range 异常

⚠️ 建议:在调试阶段使用 at(),确保索引正确;上线后可改用 [] 提升性能。

获取大小与容量

std::vector<int> data = {1, 2, 3};

std::cout << "当前元素个数: " << data.size() << std::endl;     // 输出 3
std::cout << "当前容量: " << data.capacity() << std::endl;     // 输出 4(通常大于 size)
std::cout << "是否为空: " << (data.empty() ? "是" : "否") << std::endl; // 输出 否
  • size():返回当前元素数量。
  • capacity():返回当前分配的内存能容纳的元素数量。当 size() == capacity() 时,下次 push_back 会触发扩容。
  • empty():判断是否为空。

💡 小贴士:如果知道要添加多少元素,可以提前用 reserve() 预分配内存,避免频繁扩容:

std::vector<int> scores;
scores.reserve(100); // 预留 100 个元素的存储空间

这就像提前买一个大水桶,避免中途换桶浪费时间。


遍历与迭代器使用

遍历 vector 有多种方式,每种适用于不同场景。

1. 基于范围的 for 循环(推荐)

std::vector<std::string> fruits = {"apple", "banana", "orange"};

for (const std::string& fruit : fruits) {
    std::cout << fruit << " ";
}
// 输出: apple banana orange

这种写法简洁、安全,自动处理迭代逻辑,是现代 C++ 的首选方式。

2. 使用迭代器

std::vector<int> numbers = {1, 2, 3, 4, 5};

// 使用正向迭代器
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
    std::cout << *it << " ";
}
// 输出: 1 2 3 4 5

迭代器相当于“指针”,可以指向容器中的元素。begin() 指向第一个元素,end() 指向最后一个元素的下一个位置。

3. 反向遍历

for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
    std::cout << *it << " ";
}
// 输出: 5 4 3 2 1

rbegin()rend() 提供反向迭代器,适合从后往前处理数据。


删除与修改元素

vector 支持灵活的修改操作。

删除末尾元素

std::vector<int> scores = {90, 85, 95, 88};

scores.pop_back(); // 删除最后一个元素,现在是 {90, 85, 95}

删除指定位置元素

scores.erase(scores.begin() + 1); // 删除索引为 1 的元素(85)

erase 接受迭代器,删除指定位置的元素。注意:删除后,后面的所有元素会自动前移。

清空容器

scores.clear(); // 清空所有元素,size 变为 0,但 capacity 仍保留

⚠️ 注意:clear() 不会释放内存,若要释放内存,可配合 shrink_to_fit()

scores.shrink_to_fit(); // 请求释放多余内存

性能与内存管理小贴士

vector 的性能优势主要体现在以下几点:

  • 随机访问快:支持 [] 操作,时间复杂度 O(1)。
  • 缓存友好:元素连续存储,利于 CPU 缓存预取。
  • 自动扩容:无需手动管理内存。

但也要注意:

  • 频繁的 push_back 会导致多次内存分配,影响性能。
  • 在循环中频繁插入或删除中间元素时,vector 效率不如 list
  • 使用 reserve() 提前分配空间,可减少扩容次数。

性能对比表

操作 时间复杂度 说明
push_back 均摊 O(1) 扩容时为 O(n),但平均仍为 O(1)
insert(中间) O(n) 需移动后续元素
erase(中间) O(n) 同上
at() / [] O(1) 随机访问
size() / capacity() O(1) 直接返回成员变量

实际案例:学生成绩管理系统

下面是一个完整的 vector 应用示例,展示其在真实项目中的价值:

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

struct Student {
    std::string name;
    double score;
};

int main() {
    std::vector<Student> students;

    // 添加学生
    students.push_back({"Alice", 95.5});
    students.push_back({"Bob", 87.0});
    students.push_back({"Charlie", 92.3});

    // 按分数排序(降序)
    std::sort(students.begin(), students.end(), 
              [](const Student& a, const Student& b) {
                  return a.score > b.score;
              });

    // 输出排名
    std::cout << "学生成绩排名:" << std::endl;
    for (size_t i = 0; i < students.size(); ++i) {
        std::cout << (i + 1) << ". " << students[i].name 
                  << " - " << students[i].score << " 分" << std::endl;
    }

    return 0;
}

输出结果:

学生成绩排名:
1. Alice - 95.5 分
2. Charlie - 92.3 分
3. Bob - 87.0 分

这个例子展示了 vector 在实际开发中的强大能力:动态存储、可排序、易遍历。


总结

C++ vector 容器 是现代 C++ 编程中不可或缺的工具。它解决了传统数组大小固定的问题,提供了动态扩展、自动内存管理、丰富的接口支持。无论是处理用户输入、读取文件数据,还是构建复杂的数据结构,vector 都是首选。

掌握 vector 的核心操作——创建、添加、访问、遍历、删除——并理解其性能特性,能让你的代码更安全、更高效。建议在日常练习中多使用 vector,逐步培养对动态数据结构的直觉。

记住:编程不是死记硬背,而是理解背后的逻辑。当你能用 vector 自如地处理数据时,你已经迈出了成为优秀 C++ 开发者的坚实一步。