C++ 容器类 <vector>(一文讲透)

C++ 容器类 :动态数组的高效实现

在 C++ 的世界里,数组是基础数据结构,但它的“固定大小”特性常常让人束手无策。当你需要存储用户输入的学生成绩、处理从文件读取的配置项,或者动态管理一组数据时,静态数组就显得力不从心了。这时,C++ 标准库中的 vector 容器类便成为解决这类问题的首选方案。它既保留了数组的高效访问特性,又具备动态扩容的能力,是现代 C++ 编程中不可或缺的工具。

vector 是 C++ 标准模板库(STL)中最常用的容器之一,它本质上是一个动态数组。你可以把它想象成一个“会自己长大的数组”——你不需要事先知道它要多大,它会根据你添加的数据自动调整容量。这种灵活性让开发效率大幅提升,尤其适合处理不确定长度的数据集合。

本文将带你从零开始,深入理解 vector 的核心机制、常用操作和实际应用,帮助你在项目中更自信地使用它。

创建数组与初始化

在 C++ 中,传统数组的大小必须在编译时确定,例如 int arr[10];。而 vector 则允许你在运行时动态决定大小。这为处理不确定数据量的场景提供了极大的便利。

#include <vector>
#include <iostream>

int main() {
    // 方法1:创建一个空的 vector,类型为 int
    std::vector<int> vec1;
    
    // 方法2:创建一个大小为 5 的 vector,所有元素初始化为 0
    std::vector<int> vec2(5);
    
    // 方法3:创建一个大小为 3 的 vector,所有元素初始化为 10
    std::vector<int> vec3(3, 10);
    
    // 方法4:使用初始化列表创建 vector(C++ 11 起支持)
    std::vector<int> vec4 = {1, 2, 3, 4, 5};
    
    // 输出验证
    std::cout << "vec1 size: " << vec1.size() << std::endl;  // 输出 0
    std::cout << "vec2 size: " << vec2.size() << std::endl;  // 输出 5
    std::cout << "vec3 size: " << vec3.size() << std::endl;  // 输出 3
    std::cout << "vec4 size: " << vec4.size() << std::endl;  // 输出 5

    return 0;
}

注意std::vector 是模板类,必须指定元素类型。例如 std::vector<int> 表示存储整数的向量。

在初始化时,你可以选择不传参数(创建空容器)、传入大小(自动填充默认值)、传入大小和初始值,或者直接用 {} 初始化列表。这种方式让 vector 在创建阶段就表现出极强的适应性。

基本操作:添加、访问与遍历

vector 提供了丰富的方法来管理数据。最常用的操作包括添加元素、访问元素、获取大小和遍历。

添加元素

使用 push_back() 方法可以将元素添加到容器末尾。这是最常用的插入方式。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers;

    // 添加元素到末尾
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);

    std::cout << "当前元素个数: " << numbers.size() << std::endl;  // 输出 3

    return 0;
}

push_back() 是线程安全的吗?—— 不是。在多线程环境下,多个线程同时调用 push_back() 可能导致数据竞争。如果需要线程安全,应使用互斥锁保护。

访问元素

你可以通过下标访问或使用 at() 方法访问元素。

std::vector<int> scores = {85, 92, 78, 96};

// 使用下标访问(不检查越界)
std::cout << "第一科成绩: " << scores[0] << std::endl;  // 输出 85

// 使用 at() 方法访问(会检查越界,越界时抛出异常)
try {
    std::cout << "第四科成绩: " << scores.at(3) << std::endl;  // 输出 96
    std::cout << "第五科成绩: " << scores.at(4) << std::endl;  // 抛出 out_of_range 异常
} catch (const std::out_of_range& e) {
    std::cout << "访问越界了!" << std::endl;
}

建议:在调试阶段使用 at() 方法,能帮你尽早发现越界错误。发布版本中可改用 [] 以获得更好性能。

遍历元素

vector 支持多种遍历方式:

  1. 传统 for 循环(适合需要索引的场景)
for (size_t i = 0; i < numbers.size(); ++i) {
    std::cout << "第 " << i + 1 << " 个元素: " << numbers[i] << std::endl;
}
  1. 范围 for 循环(推荐,简洁且安全)
for (const int& value : numbers) {
    std::cout << value << " ";
}
std::cout << std::endl;
  1. 使用迭代器(适合复杂操作,如删除元素)
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;

元素访问对比表

访问方式 是否检查越界 性能 是否推荐
[] 一般场景推荐
at() 较低 调试阶段推荐
迭代器 否(需手动判断) 复杂操作推荐

容量与大小管理

vector 有两个关键概念:大小(size)容量(capacity)

  • size():当前实际存储的元素个数。
  • capacity():当前分配的内存空间能容纳的元素个数。

size() 达到 capacity() 时,vector 会自动扩容(通常是原容量的 1.5 到 2 倍),并复制所有元素。这个过程会带来性能开销,因此在已知数据量时,可提前分配容量。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec;

    std::cout << "初始大小: " << vec.size() << std::endl;
    std::cout << "初始容量: " << vec.capacity() << std::endl;

    // 添加 10 个元素
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }

    std::cout << "添加后大小: " << vec.size() << std::endl;
    std::cout << "添加后容量: " << vec.capacity() << std::endl;

    // 预分配容量,避免频繁扩容
    vec.reserve(100);  // 提前分配 100 个元素的空间
    std::cout << "reserve 后容量: " << vec.capacity() << std::endl;

    return 0;
}

小技巧:如果你知道要存储 1000 个整数,建议在开始时调用 reserve(1000),这样可以避免中间多次扩容带来的性能损耗。

常用方法与实用案例

vector 提供了大量实用方法,掌握它们能极大提升编码效率。

常用方法列表

方法 说明
clear() 清空所有元素
empty() 判断是否为空
resize(n) 调整大小为 n,不足补默认值
shrink_to_fit() 释放多余内存(建议使用)
insert(pos, value) 在指定位置插入元素
erase(pos) 删除指定位置元素
swap(other) 与另一个 vector 交换内容

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

假设你需要管理一个班级的学生成绩,支持添加、删除、查找和排序。

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

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

void addStudent(std::vector<Student>& students, const std::string& name, double score) {
    students.push_back({name, score});
}

void removeStudent(std::vector<Student>& students, const std::string& name) {
    auto it = std::find_if(students.begin(), students.end(),
        [&name](const Student& s) { return s.name == name; });
    
    if (it != students.end()) {
        students.erase(it);
        std::cout << "已删除学生: " << name << std::endl;
    } else {
        std::cout << "未找到学生: " << name << std::endl;
    }
}

void displayStudents(const std::vector<Student>& students) {
    std::cout << "\n当前学生列表:\n";
    for (const auto& s : students) {
        std::cout << s.name << ": " << s.score << std::endl;
    }
}

void sortStudentsByScore(std::vector<Student>& students) {
    std::sort(students.begin(), students.end(),
        [](const Student& a, const Student& b) {
            return a.score > b.score;  // 降序排列
        });
}

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

    addStudent(classA, "张三", 88.5);
    addStudent(classA, "李四", 92.0);
    addStudent(classA, "王五", 76.5);

    displayStudents(classA);

    sortStudentsByScore(classA);
    std::cout << "\n按成绩排序后:\n";
    displayStudents(classA);

    removeStudent(classA, "王五");
    displayStudents(classA);

    return 0;
}

这个例子展示了 vector 在实际项目中的强大能力:支持自定义类型、结合算法库进行排序、动态增删元素。

总结与进阶建议

C++ 容器类 <vector> 是现代 C++ 编程中最为基础且核心的工具之一。它不仅解决了静态数组的大小限制问题,还提供了丰富的接口和高效的内存管理机制。无论是简单的数据存储,还是复杂的算法实现,vector 都能胜任。

通过本文的学习,你应该已经掌握了:

  • 如何创建和初始化 vector
  • 如何添加、访问和遍历元素
  • 容量与大小的区别及优化技巧
  • 常用方法的实际应用
  • 如何结合 STL 算法进行高效编程

在后续学习中,建议深入研究 vector 的内存布局、拷贝语义(深拷贝 vs 浅拷贝)、移动语义(move semantics)以及与 std::move 的配合使用。这些知识将帮助你写出更高效、更安全的 C++ 代码。

记住:vector 虽强大,但并非万能。对于频繁插入/删除中间元素的场景,std::liststd::deque 可能更合适。选择合适的容器,是写出高质量 C++ 代码的关键一步。