C++:深入剖析vector
希望文章能对你有所帮助,有不足的地方请在评论区留言指正,一起交流学习!
目录
1.vector介绍
1.1.vector与顺序表
1.2.vector与数组
2.vector的使用
2.1.vector的定义
2.2.vector的访问以及遍历(迭代器)
2.3vector的空间容量函数
2.4.vector 增删查改函数
2.5.sort函数
前言:
本片博客主要介绍vector的成员函数,包含vector常用函数代码演示以及结果分析。包含sort以及find标准库函数。
1.vector介绍
Vector 是 C++ 标准模板库(STL)中一种非常重要的序列容器,它本质上是一个能够动态增长和缩小的数组,在内存中连续存储元素。因为它封装了内存管理,所以使用起来比原始数组更灵活、更安全。
1.1.vector与顺序表
vector 的底层数据结构就是顺序表(也常称为 “动态数组”),其设计核心是通过 “连续的内存空间” 存储元素,支持高效的随机访问和动态扩容。
顺序表特征 | vector 的实现细节 |
---|---|
连续内存 | vector 内部维护一个指向 “连续内存块” 的指针,所有元素依次存放在该内存块中。 |
随机访问 | 支持通过 [] 运算符或 at() 方法直接按下标访问元素,时间复杂度为 O (1) |
逻辑与物理顺序一致 | 插入元素时默认追加到尾部,删除尾部元素时直接缩减有效范围,元素的逻辑顺序始终与内存存储顺序一致。 |
动态扩容 | 当调用 push_back()扩容 等方法导致元素数量超过当前容量(capacity )时,vector 会触发扩容:1. 申请新内存(通常是原容量的 1.5~2 倍,平衡性能与内存浪费); 2. 将原内存中的所有元素拷贝到新内存; 3. 释放原内存,并更新指针指向新内存。 |
1. 优势(顺序表的天然优势)
高效随机访问:这是 vector 最核心的优势 —— 访问任意下标元素的时间复杂度为 O (1),远优于链表(如 C++ 的
std::list
)。尾部操作高效:
push_back()
(尾部插入)和pop_back()
(尾部删除)的时间复杂度为 O (1)(仅需移动 “有效元素尾指针”,无需移动其他元素)。内存局部性好:由于元素存储连续,CPU 缓存能更高效地加载相邻元素(缓存命中效率高),遍历(如
for
循环遍历)速度比链表更快。
2. 局限(顺序表的固有缺陷)
中间 / 头部操作低效:若在 vector 中间或头部插入 / 删除元素(如
insert(pos, val)
、erase(pos)
),需要移动该位置之后的所有元素(以保持内存连续),时间复杂度为 O (n)(n 为元素总数)。扩容开销:扩容时需要申请新内存、拷贝旧数据、释放旧内存,若元素数量大,会产生明显的时间开销;同时,扩容后原内存会被释放,若存在指向原内存的指针 / 引用,会变成 “野指针”,迭代器失效问题。
内存浪费:为了减少扩容频率,vector 通常会预分配比 “当前元素数量” 更多的内存(即
capacity > size
),这部分未使用的内存会造成浪费。
总结
vector 是 “动态顺序表” 的典型实现—— 它通过连续内存保证高效随机访问,通过动态扩容适应元素数量的变化,同时也继承了顺序表 “中间操作低效”“扩容有开销” 的局限。正因为这一特性,vector 更适合 “频繁随机访问、主要在尾部增删” 的场景,而非 “频繁在中间增删” 的场景(后者更适合链表类容器)。
1.2.vector与数组
vector 的底层依赖 “动态分配的连续内存块”,而这个内存块在物理存储形式上与静态数组相似(都是连续空间),但管理方式完全不同。
物理存储形式:与静态数组一致
vector 底层通过一块连续的内存空间存储元素,和静态数组(如int arr[10]
)的物理形式完全相同 —— 元素在内存中依次排列,地址连续,因此支持通过 “首地址 + 下标偏移” 实现 O (1) 时间复杂度的随机访问。管理方式:与静态数组完全不同
静态数组的内存是编译期确定或手动分配后固定不变的(大小、地址都不可变),而 vector 的底层内存是动态管理的:
- 内存块的大小(即 capacity)会根据元素数量自动调整(扩容 / 缩容);
- 当需要扩容时,vector 会重新申请一块更大的连续内存(而非在原内存后追加空间,因为原内存后可能已被其他数据占用),并将旧数据拷贝到新内存,再释放旧内存;
- 整个过程对用户完全透明,无需手动干预。
因此,vector 的底层内存虽然 “形式上是连续块(类似静态数组)”,但本质是 “动态分配、可替换的连续块”,而非 “固定不变的静态数组”。
核心区别:“静态” 与 “动态” 的本质
静态数组的 “静态” 体现在内存一旦分配就不可变更(大小、地址固定),而 vector 的底层内存可以被 “替换”(通过重新分配新内存块),这使得它能适配元素数量的动态变化。
特性 | Vector(动态数组) | 传统静态数组 |
---|---|---|
内存管理 | 自动管理,动态分配和释放 | 手动管理,编译时确定大小 |
大小 | 动态,可根据需要增长和缩小 ( | 固定,声明后无法改变 |
容量 | 有容量概念 ( | 无此概念,大小即容量 |
插入/删除效率 | 尾部操作高效,头部或中间效率低 (O(n)) | 固定大小,无法直接添加/删除元素 |
访问方式 | 支持下标 | 仅支持下标 |
传递与返回 | 可作为函数参数或返回值,方便传递数据集合 | 传递时通常退化为指针,丢失大小信息 |
总结
vector 的底层确实依赖 “连续内存块”(这一点与静态数组的物理形式一致),但这块内存是动态管理、可替换的,因此不能简单说是 “最底层是静态数组”,而是 “底层使用动态分配的连续内存块,其物理存储形式与静态数组相似”。
2.vector的使用
vector在实际中非常的重要,在实际中我们熟悉常 见的接口就可以,下面列出了哪些接口是要重点掌握的。
注意:加粗字体标识为重要函数。
2.1.vector的定义
构造函数 | 接口说明 |
---|---|
vector() | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
vector (const vector& x); | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
注意:vector是模板类,创建新的对象的时候需要显示实例化。
1. 默认构造:创建空vector
std::vector<int> v1;
2.数量初始化:创建10个元素,每个初始为0
std::vector<int> v2(10);
3.指定值初始化:创建10个元素,每个都是1
std::vector<int> v3(10, 1);
4.拷贝构造:创建v3的副本
std::vector<int> v4(v3);
5.迭代器范围初始化:用数组初始化
int arr[] = {1, 2, 3}; std::vector<int> v5(arr, arr + 3);
6.C++11 列表初始化:直接使用初始化列表
std::vector<int> v6 = {1, 2, 3, 4, 5};
代码演示:
void test_vector1() {vector<int> v1;vector<int> v2(10); vector<int> v3(10, 1);vector<int> v4(v3);int arr[] = { 1,2,3,4,5 };vector<int> v5(arr, arr + 3); // 数组指针就是天然的迭代器//也可以利用string的迭代器初始化vectorstring str("hello world");vector<char> v6(str.begin(), str.end());//其中使用的迭代器初始化不一定是vector的迭代器vector<int> v7 = { 1,2,3,4,5,6 };//迭代器 }
总结:
一般使用就是创建空的vector容器,然后插入数据常用或者拷贝构造函数。
2.2.vector的访问以及遍历(迭代器)
iterator的使用 | 接口说明 |
---|---|
operator[] | 像数组一样访问 |
at | 返回对向量中位置 n 处的元素的引用。该函数会自动检查 n 是否在向量中有效元素的范围内,如果不是( n 大于或等于其大小),则抛出 out_of_range 异常 |
rbegin + rend | rbegin获取最后一个数据位置的reverse_iterator,rend获取第一个数据前一个位置的 reverse_iterator |
begin + end | begin获取第一个数据位置的iterator/const_iterator, end获取最后一个数据的下一个位置 的iterator/const_iterator |
1.operator[ ]
reference operator[] (size_type n); const_reference operator[] (size_type n) const;
reference
具体定义为value_type&
。value_type
就是该容器所管理对象的类型也就是模板参数。T& operator[] (size_type n); const T& operator[] (size_type n) const;
v
void test_vector2() {vector<int> v;v.push_back(1);//push_back 为尾插函数 v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//vector中的数据为 1 2 3 4 5//T& operator[] (size_type n);可以读,可以写for (size_t i = 0; i < v.size(); i++)//size() 为vector中数据的大小{cout << v[i] << " ";}cout << endl;for (size_t i = 0; i < v.size(); i++)//size() 为vector中数据的大小{cout << v[i]++ << " ";// }cout << endl;//const T& operator[] (size_type n) const; 可以读,不可以写//当然对于 const 类型的vector 是不可以修改的const vector<int> v1(v);for (size_t i = 0; i < v.size(); i++)//size() 为vector中数据的大小{cout << v1[i] << " ";}cout << endl;for (size_t i = 0; i < v.size(); i++)//size() 为vector中数据的大小{cout << v1[i]++ << " ";// }cout << endl; }
说明:
v1为const修饰的常量对象,因此修改的时候会报错
2.at()不常用
reference at (size_type n); const_reference at (size_type n) const;
- 和[ ]访问方式是一样的,唯一的不同是会捕捉异常。
- 访问数据的均会调用函数一个为函数重载函数,一个为构造的at函数
代码演示:
void test_vector3() {vector<int> v;v.push_back(1);//push_back 为尾插函数 v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//vector中的数据为 1 2 3 4 5for (size_t i = 0; i < v.size(); i++)//size() 为vector中数据的大小{cout << v.at(i) << " ";}cout << endl;for (size_t i = 0; i < v.size(); i++)//size() 为vector中数据的大小{v.at(i) += 1;cout << v.at(i) << " ";// }cout << endl;}
可以正常的遍历访问,以及修改数据
3.迭代器遍历
- begin指向vector的首个数据地址,end指向最后一个数据的下一个地址。
- rbegin指向vector的最后一个数据的地址,rend指向首个数据的地址的前一个地址。
- 对于反向遍历, rit++ 或者++ rit 迭代器会想着rend的方向移动(反向遍历),rit--(--rit)迭代器向容器末尾方向移动,(正向遍历)。可以理解为rbegin就是数据的开始,++操作符,迭代器向数据结束的方向移动。
void test_vector4() {vector<int> v;v.push_back(1);//push_back 为尾插函数 v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//vector中的数据为 1 2 3 4 5//正向迭代器vector<int>::iterator it = v.begin();while (it!= v.end()){cout << (*it) << " ";it++;}cout << endl;//反向迭代器vector<int>::reverse_iterator rit = v.rbegin();while (rit != v.rend()){cout << (*rit) << " ";rit++;}cout << endl; }
4.范围for (详细剖析了for的使用)
- · 范围 for 循环是 C++11 引入的一种简化循环语法,其本质是编译器对迭代器(Iterator)的封装,通过自动获取容器的起始和结束迭代器,简化对容器元素的遍历过程。
- 范围 for 本质上是迭代器遍历的 “语法糖”,它并没有引入新的遍历机制,而是通过编译器自动处理迭代器的获取、比较、递增和解引用,让代码更简洁。
- 使用前提:
标准容器包含list以及vector/map等均定义了begin()和end()方法,返回对应的迭代器。原生数组也是可以的,编译器会自动转换为指针形式的迭代器。自定义类型:有begin()以及end()函数均可以使用范围for。
- 使用细节:值传递与引用传递
(1)值传递(
auto elem : container
):elem
是容器元素的副本,修改elem
不会影响原容器。(2)引用传递(
auto& elem : container
):elem
是容器元素的引用,修改elem
会直接改变原容器中的元素。当然对于string存储空间较大的类型建议使用引用返回。(3)常量引用(
const auto& elem : container
):适用于只读场景,避免拷贝大对象的性能开销。for (auto& e: v){cout << e << " ";}cout << endl;
2.3vector的空间容量函数
容量空间 | 接口说明 |
---|---|
size | 实际元素数量 |
capacity | 预分配容量大小 |
empyt | 判断是否为空 |
resize(重点) | 改变vector的size |
reserve (重点) | 改变vector的capacity |
1.size()函数
size()
返回vector当前实际存储的元素数量vector<int> v;v.push_back(1);//push_back 为尾插函数 v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//vector中的数据为 1 2 3 4 5cout << v.size() << endl; // 5
2. capacity函数
capacity()
表示vector预分配的内存容量(元素数量上限),直接调用函数即可使用cout << v.capacity() << endl;
动态扩容机制
对于不同的平台扩容存在差异,主要区别在stl版本,
vs下capacity是按1.5倍增长的,g++是按2倍增长的。 不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。验证程序:VS版本(减少内存碎片)void test_vector6() {size_t sz;vector<int> v;sz = v.capacity();cout << "making v grow:\n";for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}} }
linux下使用的STL基本是按照2倍方式扩容(提高效率)
内存容量存在的问题
capacity()是vector内存管理的核心指标,理解其机制可避免:
• 频繁扩容导致的性能骤降(使用reserve开辟空间)
• 迭代器失效引发的崩溃风险(指迭代器指向的内存失效或数据位置改变)
• 内存浪费问题 (使用reserve存在多开辟的问题)
3.empty
empty()
是判断 vector 是否无元素cout << v.empty() << endl;
4.reserve
- 预分配内存空间,避免频繁扩容
- 不改变元素的数量:不影响
size()
,仅修改capacity()。输入的n是要大于size()以及当前的capacity才有扩容的机会的。不要考虑到缩容上,不能使用。
- 性能价值:消除插入操作中的扩容开销
- 如果 n 大于当前向量容量,则该函数会导致容器重新分配其存储,将其容量增加到 n(或更大)。
v.reserve(100); cout << v.capacity() << endl;//100
5.resize()
动态调整
size()
(元素数量)初始化控制:对新元素赋默认值或指定值
使用说明void resize (size_type n, value_type val = value_type());
- 调整容器的大小,使其包含 n 个元素。
- 如果 n 小于当前容器大小,则内容将减少到其前 n 个元素,删除超出的元素(并销毁它们)。
- 如果 n 大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val,则新元素将初始化为 val 的副本,否则,它们将被值初始化。
- 如果 n 也大于当前容器容量,则会自动重新分配已分配的存储空间。
代码演示
void test_vector7() {vector<int> v;v.push_back(1);//push_back 为尾插函数 v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//vector中的数据为 1 2 3 4 5cout << "原始数据:";cout << v.size() << endl;for (auto& e : v){cout << e << " ";}cout << endl;v.resize(3);cout << "缩小之后:";cout << v.size() << endl;for (auto& e : v){cout << e << " ";}cout << endl;v.resize(10, 1);cout << "扩张之后:";cout << v.size() << endl;for (auto& e : v){cout << e << " ";}cout << endl; }
新空间无法访问的原因: vector 的size()没有被reserve改变,访问数据也是随机值不准确的值。
特性
resize(n)
reserve(n)
核心目标
控制元素数量(size)
控制内存容量(capacity)
新增元素
初始化新增元素
不创建新元素
内存操作
可能触发扩容
仅预分配内存
元素访问
新元素可立即访问
新空间不可直接访问
vector<int> v;v.reserve(10);cout << "reserve: " << v.size() << " ";cout << v.capacity() << endl;v.resize(10);cout << "resize: " << v.size() << " ";cout << v.capacity() << endl;cout << endl;
结果为
2.4.vector 增删查改函数
vector修改函数 | 接口说明 |
---|---|
push_back(重点) | 尾插 |
pop_back(重点) | 尾删 |
find | 查找。(算法模块实现,不是vector的成员接口) |
insert | 在指定位置之前插入 |
erase | 删除指定位置的数据 |
swap | 交换两个vector的数据 |
operator[ ](重点) | 像数组一样访问 |
在其当前最后一个元素之后添加一个新元素。val 的内容被复制到新元素。演示代码:vector<int> v;v.resize(5,5);for (auto& e : v){cout << e << " ";}cout << endl;v.push_back(6);for (auto& e : v){cout << e << " ";}cout << endl;
删除向量中的最后一个元素,有效地将容器大小减少 1。
演示代码:
// 原始数据 5 5 5 5 5 6for (size_t i = 0; i < 5; i++){v.pop_back();}for (auto& e : v){cout << e << " ";}cout << endl;
3.find
在 C++ 标准库中,
find
是一个用于查找元素的通用算法,定义在<algorithm>
头文件中。它的核心功能是在指定范围内查找第一个与目标值相等的元素,并返回指向该元素的迭代器;如果未找到,则返回范围的结束迭代器(end()
)。
- 参数:
first
和last
:输入迭代器,定义了查找的范围[first, last)
(左闭右开,包含first
指向的元素,不包含last
指向的元素)。value
:要查找的目标值。- 返回值:
- 若找到匹配元素,返回指向该元素的迭代器;
- 若未找到,返回
last
迭代器。演示代码
void test_vector13() {vector<int> v = { 1,2,3,4,5,6,7,8,9,10 };auto pos = find(v.begin(), v.end(), 4);if (pos!=v.end()){v.erase(pos);}for (auto& e : v){cout << e << " ";}cout << endl; }
4.insert
(1)单元素插入
iterator insert (iterator position, const value_type& val);
功能:在 position 指向的位置之前插入一个值为 val 的元素
返回值:指向新插入元素的迭代器(2)填充插入 (fill)
void insert (iterator position, size_type n, const value_type& val);
功能:在 position 指向的位置前插入 n 个值为 val 的元素
无返回值
(3)范围插入 (range)template <class InputIterator> void insert (iterator position, InputIterator first, InputIterator last);
功能:在 position 指向的位置前插入 [first, last) 范围内的所有元素
无返回值演示代码
vector<int> v;v.reserve(50);for (size_t i = 1; i < 5; i++) {v.push_back(i);}for (auto& e : v){cout << e << " ";}cout << endl;//在第三个位置插入一个7v.insert(v.begin() + 2, 7);for (auto& e : v){cout << e << " ";}cout << endl;//在第五个位置插入3个 6v.insert(v.begin() + 4, 3, 6);for (auto& e : v){cout << e << " ";}cout << endl;
第三种方式使用较少,这里将使用数组为例
//在第五个位置插入3个 8int a[] = { 8,8,8 };v.insert(v.begin() + 4, a, a + 3);for (auto& e : v){cout << e << " ";}cout << endl;
存在迭代器失效问题,将在后续分析。
5.erase
在vector中删除单个元素(位置)或一系列元素([first,last))。
由于vector使用数组作为其底层存储,因此擦除有效数据以外的位置的元素会导致程序崩溃。操作较为低效。iterator erase (iterator position); iterator erase (iterator first, iterator last);
演示代码:
void test_vector11() {vector<int> v;v.reserve(50);for (size_t i = 1; i < 10; i++){v.push_back(i);}for (auto& e : v){cout << e << " ";}cout << endl;v.erase(v.begin());for (auto& e : v){cout << e << " ";}cout << endl;//删除前4个v.erase(v.begin(), v.begin() + 4);for (auto& e : v){cout << e << " ";}cout << endl; }
删除数据为左开右闭,begin()+ 4指向第五个元素,但是只删除了前四个元素,因此是左开右闭区间。
6.swap
- 将容器的内容交换为 x 的内容,x 是相同类型的另一个vector对象,大小可能不同。
- std算法库中也存在swap函数。
void swap (vector& x);
void test_vector12() {vector<int> v1;v1.resize(5, 1);vector<int> v2;v2.resize(5, 2);cout << "交换前:" << endl;for (auto& e : v1){cout << e << " ";}cout << endl;for (auto& e : v2){cout << e << " ";}cout << endl;v1.swap(v2);cout << "交换后:" << endl;for (auto& e : v1){cout << e << " ";}cout << endl;for (auto& e : v2){cout << e << " ";}cout << endl; }
2.5.sort函数
C++ 标准库中的
sort
函数提供了两种主要形式,用于满足不同的排序需求。这两种形式都定义在<algorithm>
头文件中,并且都要求使用随机访问迭代器(RandomAccessIterator),这意味着它们可以用于vector
、array
、原生数组等支持随机访问的容器。默认形式
template <class RandomAccessIterator> void sort (RandomAccessIterator first, RandomAccessIterator last);
- 这种形式的
sort
函数使用默认的升序排序,通过元素类型的<
运算符进行比较。即对于范围内的元素a
和b
,如果a < b
为true
,则a
会被排在b
之前。自定义比较形式
template <class RandomAccessIterator, class Compare> void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
- 这种形式允许通过第三个参数
comp
指定自定义的比较规则。comp
是一个比较函数(或函数对象),它接受两个与容器元素类型相同的参数,并返回一个bool
值。如果返回true
,表示第一个参数应该排在第二个参数之前。降序的实现
void sort (RandomAccessIterator first, RandomAccessIterator last, greater<int>());
- 在 C++ 中,
std::greater
是标准库提供的一个函数对象(functor),定义在<functional>
头文件中,可直接作为sort
函数的第三个参数,实现降序排序(从大到小)。std::greater
的工作原理是:对于两个元素a
和b
,std::greater<T>()(a, b)
会返回a > b
的结果,即当a
大于b
时,a
应排在b
前面。
演示代码:
void test_vector14() {vector<int> v{ 2,58,96,25,12,5,19,8,7 };cout << "原始:";for (auto& e : v){cout << e << " ";}cout << endl;sort(v.begin(), v.end());cout << "升序:";for (auto& e : v){cout << e << " ";}cout << endl;vector<int> v1{ 2,58,96,25,12,5,19,8,7 };sort(v1.begin(), v1.end(), greater<int>());cout << "降序:";for (auto& e : v1){cout << e << " ";}cout << endl;vector<int> v2{ 2,58,96,25,12,5,19,8,7 };sort(v2.rbegin(), v2.rend());//反向迭代器排的是反向顺序cout << "降序:";for (auto& e : v1){cout << e << " ";}cout << endl; }
为什么反向迭代器排出来的是降序?
反向迭代器的行为
rbegin()
指向容器的最后一个元素(相当于正向迭代器的end() - 1
)。rend()
指向容器的第一个元素的前一个位置(相当于正向迭代器的begin() - 1
)。- 反向迭代器的
++
操作等价于正向迭代器的--
操作(即向容器开头移动)。sort 函数的排序逻辑
sort
函数始终按照比较器的规则对迭代器范围内的元素进行升序排序(默认使用<
运算符)。当传入反向迭代器时:
- 排序范围变成了
[rbegin(), rend())
,即从容器末尾到开头的 “反向区间”。sort
对这个 “反向区间” 进行升序排序,等价于对原容器进行降序排序。问题总结:
排序从rbegin开始,排的是升序,从rbegin到rend是升序,因此反向的rend到rbegin为降序。