作者:C++新手入门笔记
专栏:C++从入门到精通
标签:C++、STL、vector、动态数组、容器用法、迭代器、内存管理
简介:零基础吃透vector的核心用法、底层原理、常用接口与性能优化,从初始化到增删改查,从迭代器使用到内存管理,结合实战案例解决90%的vector高频使用场景,适配笔试/面试/项目开发需求。
前言
在前八天掌握了const修饰别名、模板、内部类、匿名对象等核心知识点后,第九天我们正式进入C++ STL(标准模板库)的学习------从最常用、最基础的容器vector开始。
STL是C++的"宝藏工具库",而vector是STL中最常用的容器,没有之一。它本质是动态数组 ,既能像普通数组一样随机访问,又能自动扩容,完美解决了普通数组"长度固定、扩容麻烦"的痛点。无论是笔试中的算法题(如排序、查找),还是项目中的数据存储,vector都是首选工具。
本文将从「底层原理→核心用法→迭代器→内存管理→实战案例→避坑指南」,全方位拆解vector,帮你从"会用"到"吃透",轻松掌握这个STL入门必备容器。
一、vector的核心定义与底层原理
1. 什么是vector?
vector是C++ STL中的序列式容器 ,底层实现是动态开辟的连续内存空间(和普通数组一样是连续存储),但支持自动扩容------当存储的元素超过当前容量时,会自动申请更大的内存空间,将原有元素拷贝过去,释放旧空间。
简单理解:vector = 普通数组 + 自动扩容功能,兼顾了数组的高效访问和链表的灵活扩容。
2. 底层核心结构(必记)
vector的底层由三个核心指针维护,这也是理解它所有特性的关键:
-
start:指向vector底层内存的起始地址(第一个元素的地址); -
finish:指向vector中最后一个元素的下一个地址(标记有效元素的末尾); -
end_of_storage:指向vector底层内存的末尾地址(标记总容量的末尾)。
核心公式(高频考点):
-
有效元素个数:
size() = finish - start; -
总容量:
capacity() = end_of_storage - start; -
扩容时机:当
size() == capacity()时,插入元素会触发扩容。
3. 扩容机制(重点!笔试常考)
vector的扩容不是"按需扩容"(增加1个元素就扩容1个),而是"倍增扩容",不同编译器扩容倍数不同(vs是1.5倍,gcc是2倍),核心目的是减少扩容次数,提高效率。
扩容的完整流程(以gcc为例,扩容2倍):
-
当插入元素导致
size() == capacity(),触发扩容; -
申请一块大小为"原容量×2"的新内存;
-
将原内存中的所有元素,拷贝到新内存中;
-
释放原内存空间;
-
更新
start、finish、end_of_storage三个指针,指向新内存。
注意:扩容会触发元素拷贝,频繁扩容会影响性能(这是vector的唯一缺点)。
二、vector的核心用法(必会,结合代码)
使用vector前,必须包含头文件:#include <vector>,并使用std命名空间(using namespace std;),否则需写std::vector。
1. 初始化(5种常用方式)
vector的初始化方式灵活,根据场景选择,以下是最常用的5种:
cpp
#include <vector>
using namespace std;
int main() {
// 方式1:默认初始化,空vector,size=0,capacity=0(vs下默认capacity=1)
vector<int> v1;
// 方式2:初始化n个元素,每个元素默认值为0(int类型)
vector<int> v2(5); // size=5,capacity=5,元素:0,0,0,0,0
// 方式3:初始化n个元素,每个元素为指定值(如3)
vector<int> v3(5, 3); // size=5,capacity=5,元素:3,3,3,3,3
// 方式4:用普通数组/其他vector初始化(迭代器方式,最灵活)
int arr[] = {1,2,3,4,5};
vector<int> v4(arr, arr+5); // 从arr[0]到arr[4],size=5,元素:1,2,3,4,5
vector<int> v5(v4); // 用v4初始化v5,完全拷贝
// 方式5:列表初始化(C++11及以上支持,最简洁)
vector<int> v6 = {1,2,3}; // 等价于vector<int> v6{1,2,3};
return 0;
}
2. 核心接口(增删改查,必记)
vector的接口非常多,重点掌握以下常用接口,覆盖90%的使用场景,按"增→删→改→查→其他"分类整理:
(1)增:插入元素
-
push_back(val):在vector末尾插入一个元素val(最常用); -
insert(pos, val):在迭代器pos位置插入一个元素val(效率较低,需移动后续元素); -
insert(pos, n, val):在迭代器pos位置插入n个元素val; -
insert(pos, first, last):在迭代器pos位置插入[first, last)区间的元素(如其他vector的一段)。
cpp
vector<int> v = {1,2,3};
v.push_back(4); // v:[1,2,3,4]
v.insert(v.begin()+1, 5); // 在第2个位置插入5,v:[1,5,2,3,4]
v.insert(v.end(), 2, 6); // 在末尾插入2个6,v:[1,5,2,3,4,6,6]
(2)删:删除元素
-
pop_back():删除vector末尾的元素(最常用,效率高); -
erase(pos):删除迭代器pos位置的元素(效率低,需移动后续元素); -
erase(first, last):删除[first, last)区间的元素; -
clear():删除所有元素,size变为0,但capacity不变(仅清空元素,不释放内存)。
cpp
vector<int> v = {1,5,2,3,4,6,6};
v.pop_back(); // 删除末尾6,v:[1,5,2,3,4,6]
v.erase(v.begin()+1); // 删除第2个元素5,v:[1,2,3,4,6]
v.erase(v.begin()+2, v.end()); // 删除从第3个元素到末尾,v:[1,2]
v.clear(); // 清空所有元素,v:[],size=0,capacity不变
(3)改:修改元素
vector支持随机访问(和普通数组一样用[]访问),修改元素直接通过下标或迭代器:
cpp
vector<int> v = {1,2,3,4};
v[0] = 10; // 通过下标修改,v:[10,2,3,4]
v.at(1) = 20; // 通过at()修改(和[]类似,会做越界检查),v:[10,20,3,4]
// 通过迭代器修改
vector<int>::iterator it = v.begin()+2;
*it = 30; // v:[10,20,30,4]
注意:[]不做越界检查(越界会崩溃),at()会做越界检查(越界抛异常),日常开发建议用at(),笔试为了效率可用[]。
(4)查:访问元素
-
v[i]:随机访问第i个元素(下标从0开始); -
v.at(i):随机访问第i个元素,越界抛异常; -
v.front():访问第一个元素; -
v.back():访问最后一个元素; -
find(first, last, val):查找val在[first, last)区间的位置(需包含头文件#include <algorithm>),找到返回迭代器,找不到返回v.end()。
cpp
#include <algorithm> // find函数需要包含这个头文件
vector<int> v = {10,20,30,40};
cout << v[0] << endl; // 输出:10
cout << v.front() << endl; // 输出:10
cout << v.back() << endl; // 输出:40
// 查找元素30
vector<int>::iterator it = find(v.begin(), v.end(), 30);
if (it != v.end()) {
cout << "找到元素:" << *it << endl; // 输出:找到元素:30
} else {
cout << "未找到元素" << endl;
}
(5)其他常用接口
-
size():返回有效元素个数(重点); -
capacity():返回当前总容量(重点); -
empty():判断vector是否为空(size==0返回true,否则false); -
resize(n, val):将size调整为n,多余元素删除,不足元素补val(不改变capacity); -
reserve(n):手动设置capacity为n(提前预留内存,避免频繁扩容,重点优化手段); -
swap(v):交换两个vector的内容(效率高,仅交换三个底层指针)。
三、vector的迭代器(核心工具,必学)
1. 什么是迭代器?
迭代器是STL容器的"通用访问工具",本质是"智能指针",可以像指针一样访问容器中的元素,同时适配不同容器的底层结构(vector、list、map等都可用迭代器访问)。
vector的迭代器是随机访问迭代器(和普通指针用法几乎一致),支持++、--、+、-等操作。
2. 迭代器的类型与用法
vector常用的4种迭代器,按场景分类:
|------------|---------------------------------------|----------------|
| 迭代器类型 | 语法 | 核心用途 |
| 普通迭代器 | vector<int>::iterator | 访问、修改元素 |
| const迭代器 | vector<int>::const_iterator | 仅访问元素,不可修改 |
| 反向迭代器 | vector<int>::reverse_iterator | 反向访问元素(从末尾到开头) |
| const反向迭代器 | vector<int>::const_reverse_iterator | 反向访问,不可修改 |
3. 迭代器实战示例(遍历vector)
遍历是vector最常用的操作,推荐3种方式,优先用迭代器(通用、适配所有容器):
cpp
vector<int> v = {1,2,3,4,5};
// 方式1:普通迭代器(可修改元素)
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " "; // 输出:1 2 3 4 5
*it *= 2; // 修改元素,v变为[2,4,6,8,10]
}
cout << endl;
// 方式2:const迭代器(仅访问,不可修改)
for (vector<int>::const_iterator it = v.begin(); it != v.end(); ++it) {
cout << *it << " "; // 输出:2 4 6 8 10
// *it = 1; ❌ 错误:const迭代器不可修改
}
cout << endl;
// 方式3:反向迭代器(反向遍历)
for (vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
cout << *it << " "; // 输出:10 8 6 4 2
}
cout << endl;
// 方式4:C++11及以上:范围for(最简洁,底层也是迭代器)
for (int val : v) {
cout << val << " "; // 输出:2 4 6 8 10
}
4. 迭代器失效问题(重点!笔试必考)
vector的迭代器会因为扩容、删除元素而失效(迭代器指向的内存地址无效),使用时必须避免,否则会导致程序崩溃。
常见的迭代器失效场景及解决方法:
-
扩容导致失效:
-
场景:push_back/insert时触发扩容,原迭代器指向旧内存(已被释放);
-
解决:扩容后重新获取迭代器,或提前用reserve()预留足够容量。
-
-
删除元素导致失效:
-
场景:erase(pos)后,pos及后续的迭代器全部失效(元素移动,地址变化);
-
解决:erase()会返回"删除位置的下一个有效迭代器",用迭代器接收返回值。
-
cpp
// 错误示例:删除元素后迭代器失效
vector<int> v = {1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it == 3) {
v.erase(it); // it失效,后续++it会崩溃
}
}
// 正确示例:接收erase()的返回值,更新迭代器
vector<int> v = {1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ) { // 不写++it,在循环内控制
if (*it == 3) {
it = v.erase(it); // 接收返回值,it指向删除位置的下一个元素
} else {
++it; // 未删除,迭代器正常++
}
}
// 最终v:[1,2,4,5]
四、vector的内存管理(优化技巧,面试常考)
1. 内存分配的特点
-
vector的内存是连续分配的,支持随机访问(O(1)效率);
-
capacity >= size,多余的内存是"预留空间",用于后续插入元素,避免频繁扩容;
-
clear()仅清空元素(size=0),不释放内存(capacity不变);
-
vector不会自动缩小容量(即使size远小于capacity),需手动释放。
2. 内存优化技巧(必学)
-
提前预留容量(reserve()): 如果知道vector要存储的元素个数,提前用reserve(n)设置容量,避免频繁扩容(减少元素拷贝)。
// 未优化:频繁扩容(假设插入10000个元素,会扩容多次) ``vector<int> v1; ``for (int i = 0; i < 10000; ++i) { `` v1.push_back(i); ``} `` ``// 优化:提前预留10000容量,无扩容 ``vector<int> v2; ``v2.reserve(10000); // 手动设置capacity=10000 ``for (int i = 0; i < 10000; ++i) { `` v2.push_back(i); // 无扩容,效率更 -
手动释放多余内存 : 当vector的size远小于capacity时,可用"swap技巧"释放多余内存(不影响有效元素)。
``vector<int> v = {1,2,3,4,5}; ``v.reserve(100); // capacity=100,size=5,有95个多余内存 ``cout << "优化前:capacity=" << v.capacity() << endl; // 输出:100 `` ``// swap技巧:创建临时vector,拷贝有效元素,临时对象销毁时释放内存 ``vector<int>(v).swap(v); ``cout << "优化后:capacity=" << v.capacity() << endl; // 输出:5(和size一致) ``
五、vector实战案例(笔试/项目常用)
案例1:vector存储自定义类型(如结构体)
cpp
#include <vector>
#include <iostream>
#include <string>
using namespace std;
// 自定义结构体
struct Student {
string name;
int id;
int score;
};
int main() {
vector<Student> v;
// 插入自定义对象
v.push_back({"张三", 202501, 90});
v.push_back({"李四", 202502, 85});
v.push_back({"王五", 202503, 95});
// 遍历输出
for (auto& s : v) { // 用引用,避免拷贝,效率高
cout << "姓名:" << s.name << ",学号:" << s.id << ",成绩:" << s.score << endl;
}
return 0;
}
案例2:vector排序(结合algorithm库)
cpp
#include <vector>
#include <iostream>
#include <algorithm> // sort函数需要包含这个头文件
using namespace std;
int main() {
vector<int> v = {3,1,4,2,5};
// 1. 默认升序排序
sort(v.begin(), v.end());
for (int val : v) {
cout << val << " "; // 输出:1 2 3 4 5
}
cout << endl;
// 2. 自定义降序排序(用lambda表达式,C++11及以上)
sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // 降序:a>b时交换
});
for (int val : v) {
cout << val << " "; // 输出:5 4 3 2 1
}
return 0;
}
案例3:vector去重(笔试高频)
cpp
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1,2,2,3,3,3,4,5,5};
// 去重步骤:先排序→再去重→再删除多余元素
sort(v.begin(), v.end()); // 第一步:排序,相同元素聚集在一起
// 第二步:去重,返回去重后最后一个元素的迭代器
auto it = unique(v.begin(), v.end());
// 第三步:删除去重后的多余元素
v.erase(it, v.end());
// 输出去重结果
for (int val : v) {
cout << val << " "; // 输出:1 2 3 4 5
}
return 0;
}
六、vector核心避坑指南(笔试/面试高频)
-
迭代器失效陷阱:
-
扩容、删除元素会导致迭代器失效,删除时必须接收erase()的返回值;
-
避免在遍历过程中直接修改vector的容量(如push_back/erase)。
-
-
内存浪费陷阱:
-
clear()不释放内存,仅清空元素,需用swap技巧手动释放;
-
避免频繁扩容,提前用reserve()预留容量。
-
-
越界访问陷阱:
-
\]不做越界检查,越界会崩溃,日常开发优先用at();
-
-
效率陷阱:
-
insert(pos)在非末尾位置插入元素,效率低(需移动后续元素),优先用push_back();
-
遍历自定义类型vector时,用引用(&)避免拷贝,提高效率。
-
-
初始化陷阱:
-
vector<int> v(5):初始化5个0,不是初始化一个元素5;
-
vector<int> v{5}:C++11及以上,初始化一个元素5(列表初始化)。
-
七、vector总结(核心必记)
vector是STL中最基础、最常用的容器,核心优势是"连续存储+随机访问+自动扩容",适合大多数场景的数据存储和访问。
-
底层:连续内存空间,由三个指针维护size和capacity;
-
核心接口:push_back、pop_back、erase、size、capacity、reserve(重点);
-
迭代器:随机访问迭代器,注意扩容、删除导致的失效问题;
-
优化技巧:提前reserve()预留容量,用swap()释放多余内存;
-
适用场景:频繁访问、少量插入删除(末尾插入删除效率高)、需要随机访问的场景。
掌握vector,不仅能解决笔试中的算法题、项目中的数据存储问题,还能为后续学习list、map等其他STL容器打下基础------STL容器的用法有很多共性,学会vector,其他容器就能触类旁通。