【C++进阶】vector 类从入门到精通:核心接口与内存机制实战指南


✨ 把代码写进星轨,
用逻辑丈量宇宙。

导航 链接
个人主页 🏠 星轨初途
基础语言专栏 💻 C语言📚 数据结构
C++ 进阶专栏 🏆 C++学习(竞赛类)⚙️ C++专栏(开发类)
刷题实战专栏 🚀 算法及编程题分享

文章目录

  • [一、什么是 vector?](#一、什么是 vector?)
  • 二、迭代器的使用
  • [三、vector 的几种构造方式](#三、vector 的几种构造方式)
  • [四、vector 空间增长问题](#四、vector 空间增长问题)
  • [五、vector 增删查改](#五、vector 增删查改)
  • 六、总结与展望

前言:

在前两篇文章中,我们详细讲解了 STL 库中的 string 类。今天,我们将把目光转向另一个极其重要且常用的容器------vector。因为 STL 的设计具有高度的统一性,有了学习 string 的基础,你会发现攻克 vector 其实非常轻松!

一、什么是 vector?

在 C 语言阶段,我们最常用的基础容器是原生数组。然而,原生数组存在一个极其明显的缺陷:大小固定且缺乏内存管理能力。一旦在声明时确定了长度,后续如果数据量超出预期,开发者就必须手动执行"开辟新空间 -> 拷贝旧数据 -> 释放旧空间"的繁琐流程,这不仅增加了代码的冗余度,还极易引发内存泄漏或越界访问等安全隐患。

C++ 标准模板库(STL)中的 vector 完美解决了这一痛点。vector 的本质是一个动态分配内存的顺序表(动态数组),它在保留了原生数组优势的同时,弥补了其在空间管理上的不足。

具体而言,vector 支持并具备以下核心特性:

  1. 空间自动扩容(动态管理) :当不断向容器中添加元素导致容量不足时,vector 会在底层自动申请一块更大的连续内存空间,并将原有数据安全迁移,彻底免去了开发者手动干预内存的烦恼。
  2. 极速的随机访问 :由于其底层依然维护着一块物理上连续的内存空间,vector 支持像原生数组一样,通过 operator[] 或迭代器进行 O ( 1 ) O(1) O(1) 时间复杂度的极速下标访问。
  3. 丰富的成员接口 :STL 为其封装了大量开箱即用的操作接口(如尾插 push_back、插入 insert、删除 erase、获取大小 size 等),极大提升了工程开发效率。
  4. 强大的泛型支持 :作为类模板,vector 可以用来存储任意类型的数据,无论是基础类型(如 vector<int>)、自定义类,还是嵌套容器(如 vector<vector<int>>),都能完美兼容。

总而言之,在现代 C++ 开发中,vector 是日常使用频率最高、最不可或缺的底层基础容器。

🔗 官方文档参考: cplusplus.com - vector 学习


二、迭代器的使用

string 一样,vector 最标准、最安全的遍历方式就是使用迭代器(Iterator)。

迭代器组合 适用场景
begin() / end() 正向遍历 :获取指向首元素的 iterator,以及尾元素下一个位置(越界标志)的 iterator
rbegin() / rend() 反向遍历 :获取指向尾元素的 reverse_iterator,以及首元素前一个位置的 reverse_iterator

实战代码演示:

C++

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    // 使用初始化列表创建一个包含 6 个整数的 vector
    vector<int> arr = { 1, 2, 3, 4, 5, 6 };
    
    // ==========================================
    // 1. 正向遍历 (Forward Iteration)
    // ==========================================
    vector<int>::iterator it1 = arr.begin();
    
    // 循环条件:只要迭代器还没有到达末尾的越界位置
    while (it1 != arr.end())
    {
        cout << *it1 << " "; // 解引用获取数据
        ++it1;               // 迭代器向前移动一步
    }
    cout << endl; // 预期输出: 1 2 3 4 5 6

    // ==========================================
    // 2. 反向遍历 (Reverse Iteration)
    // ==========================================
    // 注意类型是 reverse_iterator
    vector<int>::reverse_iterator it2 = arr.rbegin();
    
    while (it2 != arr.rend())
    {
        cout << *it2 << " ";
        // 反向迭代器执行 ++ 操作时,实际上是向容器的"头部"移动
        ++it2;
    }
    cout << endl; // 预期输出: 6 5 4 3 2 1

    return 0;
}

三、vector 的几种构造方式

vector 提供了极其灵活的构造方式,以下四种是我们在实际开发中最常用的:

构造函数声明 (Constructor) 接口说明
vector() (重点) 无参构造:构造一个空的动态数组。
vector(size_type n, const value_type& val = value_type()) 填充构造 :构造并初始化 n n n 个值为 val 的元素。
vector(const vector& x) (重点) 拷贝构造 :用另一个 vector 深拷贝初始化自己。
vector(InputIterator first, InputIterator last) 迭代器区间构造 :使用一段左闭右开区间 [first, last) 拷贝数据。

实战代码演示:

C++

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
using namespace std;

// 辅助打印函数
void print(const vector<int>& v, const string& name) 
{
    cout << name << ": ";
    for (auto e : v) cout << e << " ";
    cout << "\n";
}

int main() 
{
    // 1. 无参构造:创建一个空的 vector,常配合 push_back 使用
    vector<int> v1;
    v1.push_back(100);

    // 2. 填充构造:开辟 5 个空间,并将每个元素初始化为 8
    vector<int> v2(5, 8);

    // 3. 拷贝构造:用 v2 深拷贝出一个全新的 v3
    vector<int> v3(v2);

    // 4. 迭代器区间构造:传入左闭右开区间 [first, last) 拷贝数据
    int arr[] = { 1, 2, 3, 4, 5 };
    vector<int> v4(arr, arr + 5);

    // 打印验证
    print(v1, "v1 (无参构造)");
    print(v2, "v2 (填充构造)");
    print(v3, "v3 (拷贝构造)");
    print(v4, "v4 (区间构造)");

    return 0;
}

四、vector 空间增长问题

容量管理是 vector 的核心。掌握以下接口,能帮你写出性能更高、内存更安全的代码。

容量接口 接口说明
size() 获取当前有效数据的个数。
capacity() 获取当前底层的总容量大小。
empty() 判断容器是否为空(size == 0)。
resize(n) (重点) 改变 vector有效数据个数 (size)。若放大则填充默认值,若缩小则截断。
reserve(n) (重点) 提前开辟空间,仅改变 vector底层容量 (capacity),不改变有效数据个数。

实战代码演示:

C++

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<int> v;

    // 1. reserve: 提前开辟空间,减少频繁扩容带来的性能开销
    v.reserve(10);

    v.push_back(1);
    v.push_back(2);

    // 2. size: 获取当前有效元素个数
    cout << "Size: " << v.size() << endl; // 输出: 2

    // 3. capacity: 获取底层总容量
    cout << "Capacity: " << v.capacity() << endl; // 输出: 10

    // 4. empty: 判空
    cout << "Is empty? " << (v.empty() ? "Yes" : "No") << endl; // 输出: No

    // 5. resize: 改变有效元素个数
    // 若放大:多出的位置用 5 填充;若缩小:截断超出部分
    v.resize(5, 5);

    return 0;
}

💡 避坑小结:reserve vs resize

  • reserve只买房不住人 :扩充了容量,但里面没有有效数据,不能直接用 [] 访问。
  • resize既买房又住人 :不仅扩充了容量,还会往里面填入默认数据,可以直接用 [] 访问。

五、vector 增删查改

日常开发中最离不开的就是对数据的修改。需要特别注意的是,STL 将"查找"功能剥离了出去

接口名称 接口说明
push_back (重点) 尾部插入元素(效率最高)。
pop_back (重点) 尾部删除元素。
find 查找特定元素。 (注意:它是 <algorithm> 库里的全局算法函数,不是 vector 的成员接口)。
insert 在指定的迭代器 position 之前插入元素。
erase 删除指定的迭代器 position 位置的元素。
swap 极速交换两个 vector 的底层数据空间。
operator[] (重点) 像原生数组一样,通过下标随机访问元素。

实战代码演示:

C++

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm> // 使用 find 必须包含此算法库头文件
using namespace std;

int main()
{
    vector<int> v = { 1, 2, 3 };

    // 1. push_back: 尾插
    v.push_back(4); // v 变成: {1, 2, 3, 4}

    // 2. pop_back: 尾删
    v.pop_back();   // v 变成: {1, 2, 3}

    // 3. operator[]: 下标访问并修改
    v[0] = 10;      // v 变成: {10, 2, 3}

    // 4. find: 查找元素 2 的位置
    // 返回值是一个迭代器,如果没有找到,则返回 v.end()
    auto it = find(v.begin(), v.end(), 2);

    // 5. insert: 在指定位置前插入
    if (it != v.end()) {
        v.insert(it, 20); // 在 2 之前插入 20 -> v 变成: {10, 20, 2, 3}
    }

    // 6. erase: 删除指定位置数据
    v.erase(v.begin());   // 删除第一个数据 -> v 变成: {20, 2, 3}

    // 7. swap: 交换两个 vector 内容
    vector<int> v2 = { 7, 8 };
    v.swap(v2);           // 此时 v 变成 {7, 8}, v2 变成 {20, 2, 3}

    return 0;
}

六、总结与展望

通过对 vector 核心接口的学习,我们可以清晰地感受到 C++ STL 在设计上的优雅与高效:

  1. 告别手动内存管理vector 作为动态数组,完美接管了底层空间的扩容与数据迁移,彻底终结了 C 语言时代原生数组"定长不可变"的痛点,极大提升了开发效率与代码安全性。
  2. 精准的空间控制 :深刻理解 size(有效数据个数)与 capacity(底层总容量)的区别,是 vector 进阶使用的关键。在已知数据规模的情况下,善用 reserve 提前开辟空间,能有效避免频繁的内存搬家,榨干程序的最后一滴性能。
  3. 算法与容器的分离 :STL 将 find 等通用逻辑剥离到了 <algorithm> 算法库中,通过**迭代器(Iterator)**作为桥梁与容器无缝连接,完美展现了泛型编程(Generic Programming)解耦合的魅力。

🎯 下期预告:

掌握 vector 的接口用法,仅仅是我们征服 STL 的第一步。"只会开车不懂修车"是不够的。在下一篇文章中,我们将深入底层源码,从零开始手写模拟实现一个 vector 容器

我们将抛开表面的魔法,去亲眼看看底层的 _start_finish_end_of_storage 这三个原生指针是如何相互配合完成扩容与增删查改的,敬请期待!

相关推荐
Bruce_kaizy6 分钟前
c++ linux环境编程——文件io介绍以及open 、write 、read 三剑客深度详解
linux·服务器·c++·ubuntu·操作系统·文件io
ouliten1 小时前
[Triton笔记6]层标准化
笔记
PAK向日葵2 小时前
我用 C++ 写了一个轻量级 Python 虚拟机,刚刚开源
c++·python·开源
玖釉-2 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法
枕星而眠3 小时前
数据结构八大排序详解(一):四大简单排序
c语言·数据结构·c++·后端
玄米乌龙茶1233 小时前
思维导图笔记:Prompt工程
笔记·prompt
努力努力再努力wz3 小时前
【Qt入门系列】:按钮组件全解析:从 QAbstractButton 到快捷键事件、单选与复选机制
c语言·开发语言·数据结构·c++·git·qt·github
zhangrelay4 小时前
ROS 2 Lyrical Luth启程-Ubuntu26.04-
linux·笔记·学习·ubuntu
skywalk81634 小时前
言知(Yanzhi)系统提升建议报告和完工报告 by AutoCoder
开发语言·编程
Undergoer_TW4 小时前
SLAM实战避坑笔记:基础矩阵退化场景分析与解决方案
笔记·线性代数·矩阵