C++ 容器类 <vector>(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言

在 C++ 编程中,C++ 容器类 是最常用且功能强大的动态数组实现之一。它解决了传统静态数组的局限性,例如固定大小和扩容困难等问题,为开发者提供了灵活且高效的内存管理能力。无论是处理数据集合、实现算法,还是构建复杂的应用程序,掌握 <vector> 的核心特性与使用技巧都至关重要。本文将从基础概念、核心操作、底层原理到实际案例,系统性地解析这一容器类的使用方法与设计逻辑,帮助开发者循序渐进地提升编程能力。


一、基础概念:什么是 vector?

vector 是 C++ 标准库中的一种序列式容器,底层基于动态数组实现。它能够自动管理内存,根据实际需求动态扩展或收缩存储空间。与静态数组相比,vector 的优势在于:

  1. 动态容量:无需预先指定大小,元素数量可随程序运行实时调整;
  2. 随机访问:支持通过下标快速访问任意位置的元素;
  3. 自动内存管理:开发者无需手动分配或释放内存,降低内存泄漏风险。

形象地比喻,vector 就像一个“智能收纳盒”:当你需要存放更多物品时,它会自动扩展空间;当你取出物品后,它会根据需要收缩,避免浪费存储资源。


二、vector 的核心操作与代码示例

1. 初始化与赋值

vector 可以通过多种方式初始化,例如直接声明、列表推导或通过其他容器构造:

// 空初始化  
std::vector<int> vec;  

// 初始容量指定  
std::vector<int> vec(5); // 创建包含 5 个 0 的整型 vector  

// 列表初始化  
std::vector<int> vec = {1, 2, 3, 4, 5};  

// 通过其他容器初始化  
std::vector<int> vec2(vec.begin(), vec.end());  

2. 元素添加与删除

添加元素

  • push_back():将元素追加到容器末尾,时间复杂度为 O(1)(平均情况)。
    vec.push_back(6); // 将 6 添加到 vec 末尾  
    
  • insert():在指定位置插入元素或多个元素:
    vec.insert(vec.begin() + 2, 10); // 在索引 2 处插入 10  
    vec.insert(vec.end(), {11, 12}); // 在末尾插入多个元素  
    

删除元素

  • pop_back():移除最后一个元素,时间复杂度 O(1):
    vec.pop_back(); // 移除 vec 的最后一个元素  
    
  • erase():通过迭代器或索引删除指定位置的元素:
    vec.erase(vec.begin() + 3); // 删除索引 3 处的元素  
    

3. 访问与查询

访问元素

  • operator[]:通过下标访问,但无边界检查:
    int value = vec[2]; // 获取索引 2 的元素  
    
  • at():提供安全访问,超出范围时抛出 std::out_of_range 异常:
    int safe_value = vec.at(2); // 安全访问  
    

容量与状态查询

size_t size = vec.size(); // 返回元素数量  
bool is_empty = vec.empty(); // 判断是否为空  
size_t capacity = vec.capacity(); // 返回当前分配的内存容量  

三、vector 的迭代器与遍历

迭代器(Iterator) 是 C++ 中用于访问容器元素的指针类对象。vector 支持以下迭代器类型:

  • 正向迭代器:通过 begin()end() 获取,从起始到末尾;
  • 反向迭代器:通过 rbegin()rend() 获取,从末尾到起始。

遍历示例

// 正向遍历  
for (auto it = vec.begin(); it != vec.end(); ++it) {  
    std::cout << *it << " ";  
}  

// 范围 for 循环(C++11 起)  
for (int num : vec) {  
    std::cout << num << " ";  
}  

// 反向遍历  
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {  
    std::cout << *it << " ";  
}  

四、vector 的内存管理与动态扩容机制

vector 的动态扩容机制是其核心特性之一。当元素数量超过当前容量时,vector 会申请更大的内存块,并将原有数据迁移至新地址。这一过程称为“扩容”。

扩容策略

vector 的扩容通常遵循以下规则:

  1. 初始容量:默认初始容量为 0 或根据构造函数指定;
  2. 扩容因子:当容量不足时,新容量通常为当前容量的 1.5~2 倍(具体实现可能因编译器而异);
  3. 内存迁移:扩容会导致所有元素重新拷贝,因此频繁扩容可能影响性能。

示例:观察扩容行为

std::vector<int> vec;  
std::cout << "Capacity: " << vec.capacity() << std::endl; // 初始容量可能为 0  

for (int i = 0; i < 10; ++i) {  
    vec.push_back(i);  
    std::cout << "After " << i+1 << " elements: Capacity = " << vec.capacity() << std::endl;  
}  

输出可能显示容量随元素数量增长呈指数级增加,例如:0 → 1 → 2 → 4 → 8 → 16...

优化技巧

  • 预分配容量:使用 reserve() 减少扩容次数:
    vec.reserve(100); // 预分配 100 个元素的容量  
    
  • 避免不必要的拷贝:使用 emplace_back() 直接在容器内构造对象,而非拷贝:
    vec.emplace_back(10); // 直接在容器末尾构造元素,效率更高  
    

五、vector 的底层实现解析

内部结构

vector 的底层通常由三个指针组成:

  1. start:指向容器首元素的指针;
  2. finish:指向容器末尾元素后一个位置的指针;
  3. end_of_storage:指向容器当前分配内存末尾的指针。

通过这三个指针,vector 可以动态管理其容量和大小:

  • size()finish - start
  • capacity()end_of_storage - start

扩容流程图示

size() 接近 capacity() 时:

  1. 计算新容量(通常为原容量的 1.5~2 倍);
  2. 分配新内存块;
  3. 将旧数据拷贝到新内存;
  4. 释放旧内存,更新指针。

六、常见问题与最佳实践

1. 越界访问与迭代器失效

  • 越界访问:通过 operator[] 访问超出 size() 的索引会导致未定义行为。
  • 迭代器失效:在 push_back()insert()erase() 后,原有迭代器可能失效。

2. 性能优化建议

  • 预分配容量:避免频繁扩容,尤其是循环中多次调用 push_back()
  • 避免拷贝 vector:传递或返回 vector 时,优先使用引用或 std::move()
  • 使用 reserve()shrink_to_fit():根据场景调整内存占用。

七、实际案例:统计单词频率

以下案例演示如何用 vector 实现单词频率统计:

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

struct WordCount {  
    std::string word;  
    int count = 0;  
};  

void count_words(const std::vector<std::string>& words) {  
    std::vector<WordCount> counts;  
    for (const auto& word : words) {  
        auto it = std::find_if(counts.begin(), counts.end(),  
            [&](const WordCount& wc) { return wc.word == word; });  
        if (it != counts.end()) {  
            ++it->count;  
        } else {  
            counts.emplace_back(WordCount{word, 1});  
        }  
    }  
    // 输出结果  
}  

此案例展示了 vector 在存储自定义结构体、结合算法库(如 std::find_if)时的灵活性。


八、结论

C++ 容器类 是动态数据管理的核心工具,其易用性、高效性和灵活性使其成为 C++ 开发的基石。通过掌握初始化、迭代、内存管理等核心操作,并结合实际案例理解其底层逻辑,开发者能够更高效地解决复杂问题。建议在项目中合理使用 reserve()emplace_back() 等优化技巧,并注意迭代器失效等潜在陷阱,以最大化 vector 的性能优势。

掌握 vector 仅仅是 C++ 容器学习的起点,后续可进一步探索 listmap 等其他容器,逐步构建面向对象编程的完整知识体系。

最新发布