跳到主要内容 STL 容器:vector 动态数组全面解析 | 极客日志
C++ 算法
STL 容器:vector 动态数组全面解析 C++ STL vector 动态数组是常用序列容器,基于动态数组实现,支持随机访问和自动扩容。详解初始化方式、核心成员函数(增删改查)、容量管理(size/capacity/reserve)及底层扩容机制。涵盖自定义类型存储、排序去重算法应用,并总结越界访问、迭代器失效等常见错误及优化方案,帮助开发者高效安全地使用 vector。
laoliangsh 发布于 2026/3/24 0 浏览STL 容器:vector 动态数组全面解析
在 C++ STL 的序列式容器中,vector 无疑是最常用、最灵活、最贴合日常开发需求的容器之一。无论是笔试面试中的算法题,还是实际项目中的数据存储,vector 都能凭借其'动态扩容''随机访问'的核心优势,成为开发者的首选。
很多开发者对 vector 的认知停留在'可以自动变长的数组',但其实它的底层实现、扩容机制、接口用法以及性能优化,都有很多值得深究的细节。本文将全面解析 vector——从基础用法到底层原理,从实操示例到避坑指南,助您深入掌握这个 STL'高频容器',做到灵活运用、规避隐患。
一、vector 是什么?核心定位与优势
vector 是 STL 标准模板库中的序列式容器,底层基于动态数组 实现,本质是封装了动态分配内存的模板类。它可以存储任意数据类型(内置类型 int、float,自定义结构体、类等),支持动态扩容(无需手动管理内存大小),同时提供了便捷的接口,实现数据的增、删、改、查等操作。
vector 的核心优势(为什么首选它?)
随机访问高效 :支持像普通数组一样通过下标 [] 直接访问元素,时间复杂度为 O(1),这是 vector 区别于 list(双向链表)的核心优势,适合频繁读取元素的场景。
动态扩容省心 :无需手动计算和分配内存,当存储的数据超过当前容量时,vector 会自动扩容,开发者只需专注于数据操作,无需关心底层内存管理。
接口丰富易用 :STL 为 vector 提供了完善的成员函数(如插入、删除、排序、清空等),接口命名规范、用法简洁,上手成本低。
内存连续 :底层数组的内存是连续的,缓存命中率高,相较于非连续内存的容器(如 list),在遍历、访问时性能更优。
vector 的适用场景 结合其优势,vector 适合以下场景,也是日常开发中最常遇到的场景:
需要频繁通过下标访问元素(如存储用户列表、成绩排名、日志数据);
数据量不确定,需要动态增减(如接收用户输入、读取文件内容);
频繁在尾部插入/删除元素(尾部操作效率极高,时间复杂度 O(1));
需要与算法协同使用(如 sort 排序、find 查找,vector 支持随机访问迭代器,适配所有 STL 算法)。
二、vector 基础用法:从初始化到常用操作 在使用 vector 之前,需包含对应的头文件 #include <vector>,并建议使用 std 命名空间(或显式调用 std::vector)。下面从初始化开始,逐一讲解 vector 的核心基础操作,搭配示例代码,方便直接测试。
1. vector 的初始化(5 种常用方式) vector 的初始化方式灵活,可根据实际需求选择,不同方式的效率和适用场景略有差异,重点掌握前 3 种即可。
#include <iostream>
#include <vector>
using namespace std;
int main () {
vector<int > v1;
vector<int > v2 (5 ) ;
vector<int > v3 (5 , 3 ) ;
int arr[] = {1 , 2 , 3 , 4 , 5 };
vector<int > v4 (arr, arr + 5 ) ;
vector<int > v5 (v4) ;
vector<int > v6 = {1 , 2 , 3 , 4 , 5 };
vector<int > v7{10 , 20 , 30 };
for (int i = 0 ; i < v7. size (); i++) {
cout << v7[i] << " " ;
}
return 0 ;
}
2. vector 核心成员函数(必学) vector 的成员函数众多,但常用的只有 10 个左右,我们按'访问、插入、删除、容量、其他'分类讲解,结合示例说明用法,避免记混。
(1)访问元素(3 种方式)
operator[]:下标访问,与普通数组一致,无越界检查(高效,但不安全);
at():下标访问,有越界检查,越界会抛出 out_of_range 异常(安全,效率略低);
front()/back():访问第一个/最后一个元素,时间复杂度 O(1)(高频使用)。
vector<int > v = {1 , 2 , 3 , 4 , 5 };
cout << v[0 ] << endl;
cout << v.at (2 ) << endl;
cout << v.front () << endl;
cout << v.back () << endl;
(2)插入元素(2 种高频方式)
push_back():在 vector 尾部插入一个元素,时间复杂度 O(1)(无扩容时),最常用 ;
insert():在指定位置插入元素/元素组,时间复杂度 O(n)(需移动后续元素,效率较低)。
vector<int > v = {1 , 2 , 3 };
v.push_back (4 );
v.push_back (5 );
v.insert (v.begin () + 1 , 10 );
v.insert (v.begin () + 2 , 2 , 20 );
vector<int > v2 = {6 , 7 , 8 };
v.insert (v.end (), v2. begin (), v2. end ());
(3)删除元素(3 种高频方式)
pop_back():删除尾部最后一个元素,时间复杂度 O(1),最常用 ;
erase():删除指定位置/指定区间的元素,时间复杂度 O(n)(需移动后续元素);
clear():清空 vector 中所有元素,但不释放内存 (容量不变,size 变为 0)。
vector<int > v = {1 , 10 , 20 , 20 , 2 , 3 , 4 , 5 };
v.pop_back ();
v.erase (v.begin () + 3 );
v.erase (v.begin () + 1 , v.begin () + 3 );
v.clear ();
cout << v.size () << endl;
cout << v.capacity () << endl;
(4)容量相关(4 个核心函数) vector 的'容量(capacity)'和'大小(size)'是极易混淆的两个概念,必须重点区分:
size() :返回 vector 中当前元素的个数 (实际存储的数据量);
capacity() :返回 vector 底层数组能容纳的最大元素个数 (已分配的内存大小);
empty() :判断 vector 是否为空(size() == 0),返回 bool 值(高效,推荐使用);
resize() :调整 vector 的 size(元素个数),若 size 增大,新增元素为默认值/指定值;若 size 减小,删除末尾多余元素(不改变 capacity);
reserve() :提前分配内存,调整 vector 的 capacity(容量),不改变 size (用于优化扩容效率)。
vector<int > v;
cout << v.size () << " " << v.capacity () << endl;
v.push_back (1 );
cout << v.size () << " " << v.capacity () << endl;
v.push_back (2 );
cout << v.size () << " " << v.capacity () << endl;
v.push_back (3 );
cout << v.size () << " " << v.capacity () << endl;
v.resize (5 );
cout << v.size () << " " << v.capacity () << endl;
v.resize (3 );
cout << v.size () << " " << v.capacity () << endl;
v.reserve (10 );
cout << v.size () << " " << v.capacity () << endl;
(5)其他常用操作
swap():交换两个 vector 的元素、size 和 capacity(高效,时间复杂度 O(1));
data():返回 vector 底层数组的首地址(可用于与 C 语言数组交互)。
3. vector 的遍历方式(4 种常用方式) vector 支持多种遍历方式,可根据场景选择,其中'范围 for 循环'和'迭代器遍历'最常用,适配不同需求。
#include <iostream>
#include <vector>
using namespace std;
int main () {
vector<int > v = {1 , 2 , 3 , 4 , 5 };
for (int i = 0 ; i < v.size (); i++) {
cout << v[i] << " " ;
}
cout << endl;
for (int val : v) {
cout << val << " " ;
}
cout << endl;
for (int &val : v) {
val *= 2 ;
}
vector<int >::iterator it;
for (it = v.begin (); it != v.end (); ++it) {
cout << *it << " " ;
}
cout << endl;
vector<int >::const_iterator cit;
for (cit = v.cbegin (); cit != v.cend (); ++cit) {
cout << *cit << " " ;
}
cout << endl;
vector<int >::reverse_iterator rit;
for (rit = v.rbegin (); rit != v.rend (); ++rit) {
cout << *rit << " " ;
}
cout << endl;
return 0 ;
}
三、vector 底层原理:扩容机制(核心重点) 很多开发者使用 vector 时,只知道它能自动扩容,但不清楚扩容的底层逻辑,导致在频繁插入元素时,出现性能瓶颈或迭代器失效问题。这一部分,我们就来拆解 vector 的扩容机制,搞懂'为什么扩容''怎么扩容''扩容有什么隐患'。
1. 扩容的本质:重新分配内存 + 拷贝元素 vector 底层是连续的动态数组,一旦数组被分配,内存地址就固定了。当我们通过 push_back() 插入元素,且当前 size == capacity 时,vector 就会触发'扩容',扩容的核心步骤是:
分配一块更大的新内存 (通常是原容量的 2 倍,不同编译器略有差异,如 VS 是 1.5 倍,GCC 是 2 倍);
将原内存中的所有元素,拷贝到新内存 中;
释放原内存空间;
将 vector 的指针指向新内存,并更新 capacity(容量)。
2. 扩容的性能损耗 从扩容的步骤可以看出,扩容是一个'耗时操作'——涉及内存分配、元素拷贝、原内存释放,这些操作的时间复杂度是 O(n)(n 为当前元素个数)。如果频繁插入元素,会触发多次扩容,导致性能下降。
举个例子:如果我们要插入 1000 个元素,vector 初始容量为 0,每次扩容 2 倍,那么会触发的扩容次数为:0→1→2→4→8→16→32→64→128→256→512→1024,共 11 次扩容,每次扩容都要拷贝当前所有元素,累计拷贝次数较多。
3. 扩容优化:提前 reserve 分配内存 针对扩容的性能损耗,STL 提供了 reserve() 函数,允许我们提前分配足够的容量 ,避免频繁扩容。比如上面的例子,我们提前 reserve(1000),就可以只分配一次内存,插入 1000 个元素时无需触发任何扩容,大幅提升性能。
#include <iostream>
#include <vector>
using namespace std;
int main () {
vector<int > v1;
vector<int > v2;
v2. reserve (1000 );
for (int i = 0 ; i < 1000 ; i++) {
v1. push_back (i);
v2. push_back (i);
}
cout << v1. capacity () << endl;
cout << v2. capacity () << endl;
return 0 ;
}
注意:reserve() 只能增大容量,不能减小容量;如果传入的参数小于当前容量,reserve() 不会做任何操作。
4. 扩容导致的迭代器失效问题 这是 vector 使用中最常见的'坑'——当 vector 触发扩容后,原内存被释放,指向原内存的迭代器、指针、引用都会变成'野指针',继续使用会导致程序崩溃或未定义行为。
vector<int > v = {1 , 2 , 3 };
vector<int >::iterator it = v.begin ();
v.push_back (4 );
v.push_back (5 );
v.push_back (6 );
v.push_back (7 );
it = v.begin ();
cout << *it << endl;
四、vector 进阶用法:自定义类型、排序与去重 vector 不仅支持内置类型,还支持自定义结构体、类,同时可以与 STL 算法协同使用,实现排序、去重等高级操作,这也是 vector 高频使用的场景之一。
1. vector 存储自定义结构体 存储自定义结构体时,需确保结构体有默认构造函数(如果手动定义了带参构造,需显式定义默认构造),否则初始化时会报错。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
struct Student {
string name;
int age;
int score;
Student () : name ("" ), age (0 ), score (0 ) {}
Student (string n, int a, int s) : name (n), age (a), score (s) {}
};
int main () {
vector<Student> students;
students.push_back (Student ("张三" , 18 , 90 ));
students.push_back (Student ("李四" , 19 , 85 ));
students.push_back (Student ("王五" , 18 , 95 ));
for (const auto & stu : students) {
cout << "姓名:" << stu.name << ",年龄:" << stu.age << ",成绩:" << stu.score << endl;
}
return 0 ;
}
2. vector 排序(配合 sort 算法) sort 是 STL 最常用的排序算法,vector 支持随机访问迭代器,因此可以直接使用 sort 对 vector 进行排序。默认是升序排序,也可以自定义排序规则(降序、按结构体字段排序等)。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool cmpDesc (int a, int b) {
return a > b;
}
struct Student {
string name;
int score;
bool operator <(const Student& other) const {
return score > other.score;
}
};
int main () {
vector<int > v1 = {3 , 1 , 4 , 1 , 5 , 9 };
sort (v1. begin (), v1. end ());
sort (v1. begin (), v1. end (), cmpDesc);
vector<Student> v2;
v2. push_back ({"张三" , 90 });
v2. push_back ({"李四" , 85 });
v2. push_back ({"王五" , 95 });
sort (v2. begin (), v2. end ());
sort (v2. begin (), v2. end (), [](const Student& a, const Student& b) {
return a.score > b.score;
});
for (const auto & stu : v2) {
cout << stu.name << ":" << stu.score << endl;
}
return 0 ;
}
3. vector 去重(配合 unique + erase 算法) vector 本身没有去重接口,但可以通过 STL 的 unique 算法(去重)+ erase 函数(删除重复元素)实现去重。注意:unique 算法只能去除'相邻的重复元素',因此去重前必须先排序。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main () {
vector<int > v = {3 , 1 , 4 , 1 , 5 , 9 , 5 , 3 };
sort (v.begin (), v.end ());
auto it = unique (v.begin (), v.end ());
v.erase (it, v.end ());
for (int val : v) {
cout << val << " " ;
}
return 0 ;
}
五、vector 使用避坑指南(高频错误总结) 结合前面的讲解,总结 vector 使用中最容易出现的 5 个错误,以及对应的解决方案,帮你规避隐患,写出更安全、高效的代码。
1. 越界访问(最常见错误) 错误:使用 v[i] 访问下标超出 size()-1 的元素(如 v.size() 为 5,访问 v[5]),无越界检查,导致程序崩溃或未定义行为。
访问前判断下标是否合法(i < v.size());
使用 at() 访问(有越界检查,抛出异常,便于调试);
避免在循环中使用固定下标(如 for (int i = 0; i < 10; i++),忽略 v.size() 可能小于 10)。
2. 迭代器失效(最隐蔽错误) 错误:扩容后继续使用原迭代器、指针、引用;或在 erase() 后继续使用被删除位置的迭代器。
扩容后,重新获取迭代器(如 it = v.begin());
erase() 会返回指向被删除元素下一个位置的迭代器,可利用返回值更新迭代器(如 it = v.erase(it));
提前 reserve() 分配足够容量,避免频繁扩容。
3. 混淆 size() 和 capacity() 错误:认为 v.capacity() == v.size(),或用 capacity() 判断元素个数(如 for (int i = 0; i < v.capacity(); i++))。
size():当前元素个数,用于遍历、判断元素是否存在;
capacity():底层容量,仅用于扩容优化(reserve()),不用于元素访问。
4. 频繁在中间插入/删除元素 错误:在 vector 中间频繁插入/删除元素(如 v.insert(v.begin() + 1, val)),导致大量元素移动,性能低下。
如果需要频繁在中间插入/删除,优先使用 list(双向链表,任意位置插入删除效率 O(1));
若必须使用 vector,可尽量将插入/删除操作集中在尾部(push_back/pop_back)。
5. 未释放内存(内存泄漏隐患) 错误:vector 存储指针类型(如 vector<int*>)时,只 clear() 清空元素,未释放指针指向的内存,导致内存泄漏。
清空 vector 前,遍历所有元素,手动释放指针指向的内存;
优先使用智能指针(如 shared_ptr、unique_ptr),避免手动管理内存。
vector<int *> v;
v.push_back (new int (1 ));
v.push_back (new int (2 ));
v.clear ();
vector<int *> v;
v.push_back (new int (1 ));
v.push_back (new int (2 ));
for (int * p : v) {
delete p;
p = nullptr ;
}
v.clear ();
六、总结:vector 的核心价值与使用建议 vector 作为 STL 中最常用的容器,其核心价值在于'平衡了效率与便捷性'——既有普通数组的随机访问高效性,又有动态数组的灵活性,同时封装了内存管理,让开发者无需关注底层细节。
最后,给大家几点使用建议,帮助你更好地运用 vector:
优先使用 vector,除非有明确的场景需求(如频繁中间插入删除用 list,高频查找用 unordered_map);
插入大量元素时,提前用 reserve() 分配容量,优化性能;
访问元素优先用 [](高效),调试时可用 at()(安全);
遍历元素优先用范围 for 循环(简洁),需要迭代器时用普通迭代器(通用);
规避迭代器失效、越界访问、内存泄漏三大坑,写出安全、高效的代码。
vector 的用法看似简单,但想要真正用好、用活,需要深入理解其底层扩容机制和注意事项。希望本文解析助您深入掌握 vector,在笔试面试和实际开发中,都能灵活运用这个 STL'利器'。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
void testInitAndBase () {
vector<int > v1;
vector<int > v2 (5 , 3 ) ;
vector<int > v3 = {1 , 2 , 3 , 4 , 5 };
v3. push_back (6 );
v3. insert (v3. begin () + 2 , 10 );
v3. pop_back ();
v3. erase (v3. begin () + 1 );
for (int val : v3) {
cout << val << " " ;
}
cout << endl;
}
void testCapacity () {
vector<int > v;
v.reserve (10 );
for (int i = 0 ; i < 10 ; i++) {
v.push_back (i);
}
cout << "size: " << v.size () << ", capacity: " << v.capacity () << endl;
}
struct Student {
string name;
int score;
Student (string n, int s) : name (n), score (s) {}
};
void testSort () {
vector<Student> v = {{"张三" , 90 }, {"李四" , 85 }, {"王五" , 95 }};
sort (v.begin (), v.end (), [](const Student& a, const Student& b) {
return a.score > b.score;
});
for (const auto & stu : v) {
cout << stu.name << ":" << stu.score << endl;
}
}
void testUnique () {
vector<int > v = {3 , 1 , 4 , 1 , 5 , 9 , 5 , 3 };
sort (v.begin (), v.end ());
auto it = unique (v.begin (), v.end ());
v.erase (it, v.end ());
for (int val : v) {
cout << val << " " ;
}
cout << endl;
}
void testPointer () {
vector<int *> v;
v.push_back (new int (1 ));
v.push_back (new int (2 ));
for (int * p : v) {
delete p;
p = nullptr ;
}
v.clear ();
cout << "内存释放完成" << endl;
}
int main () {
testInitAndBase ();
testCapacity ();
testSort ();
testUnique ();
testPointer ();
return 0 ;
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online