一.vector的介绍及使用
文档参考:

vector是一个可以动态增长、支持下标访问的连续数组容器。- 它和数组一样,元素在内存中连续存放,所以支持
[]下标访问,效率 O(1)。但它不像数组那样大小固定,可以自动增长,不需要手动管理容量。vector内部用三个指针管理内存:_start指向数组开头,_finish指向最后一个元素的下一个位置,_end_of_storage指向容量末尾的下一个位置。元素个数size就是_finish - _start,总容量capacity就是_end_of_storage - _start。- 当
size等于capacity时,空间已满,vector会触发扩容:新开一块更大的内存(通常是原容量的 1.5 或 2 倍),把旧元素拷贝或移动到新空间,释放旧内存,最后调整三个指针指向新位置。扩容有开销,但不是每次插入都触发,所以尾部插入的均摊时间仍然是 O(1)。vector用额外占用的空间换取了尾部操作的效率。- 优点是下标访问快(O(1)),尾部增删快(均摊 O(1)),内存连续对缓存友好。
- 缺点是中间插入删除需要移动元素(O(n)),效率低;扩容时有拷贝开销;可能占用比实际需要更多的内存。
- 和其他容器相比:
vector比list访问更快,但中间插入删除更慢;和deque比,vector的尾部操作更稳定,但头部操作不如deque。- 常见操作复杂度:下标访问
[]是 O(1),push_back和pop_back均摊 O(1),中间插入insert和删除erase是 O(n),查找find是 O(n)。
使用STL的三个境界:能用,明理,能扩展 ,那么下面学习vector,我也是按照这个方法去学
习
二.vector的使用
2.1 vector的定义

default (1) ------ 空间配置器 alloc
cpp
vector(const allocator_type& alloc = allocator_type());
alloc是缺省参数 ,绝大多数情况下不需要手动传入,它是 STL 六大组件中的空间配置器,负责底层内存的分配与释放简单点说就是:alloc**=**new/delete的封装版本,允许用户自定义内存管理策略日常使用完全可以忽略它,直接用默认值即可
cpp
vector<int> v; // 等价于 vector<int, allocator<int>> v;
fill (2) ------ value_type 和 '\0' 的问题
cpp
vector(size_type n, const value_type& val = value_type());
value_type就是vector的模板参数类型,好比vector<int>中value_type就是int这里的
'\0'是 char 类型的缺省值;
cpp
vector<char> v1(5); // 5 个 '\0'(缺省值)
vector<char> v2(5, 'a'); // 5 个 'a'
vector<int> v3(5); // 5 个 0(int 的缺省值)
vector<string> v4(3); // 3 个空字符串
更正 :缺省值不是固定的
'\0',而是该类型的默认构造值 (value_type())。对于char来说正好是'\0'。
range (3) ------ 迭代器是函数模板
cpp
template <class InputIterator>
vector(InputIterator first, InputIterator last);
这里的迭代器不一定是
vector::iterator,它可以是任意满足输入迭代器要求的类型比如可以用数组指针 、其他容器的迭代器 、istream_iterator
cpp
// 用数组指针初始化
int arr[] = {1, 2, 3, 4, 5};
vector<int> v1(arr, arr + 5);
// 用 list 的迭代器初始化
list<int> lst = {10, 20, 30};
vector<int> v2(lst.begin(), lst.end());
// 用另一个 vector 的迭代器(部分区间)
vector<int> v3(v1.begin() + 1, v1.end() - 1);
// 用 istream_iterator 从输入流读取
vector<int> v4((istream_iterator<int>(cin)), istream_iterator<int>());
copy (4)------ 拷贝构造函数
cpp
vector (const vector& x);
只有一个参数:另一个同类型的 vector 对象。
作用:用已有的 vector 对象 x 构造一个新的 vector,新对象是 x 的独立副本(深拷贝) 。
特点 :参数类型 : const vector&(常量引用,只读); 深拷贝(新对象有独立的内存);修改 v2 不会影响 v1
1. 无参构造 vector()
cpp
vector<int> v1; // 创建一个空的 vector
2. 构造并初始化 n 个 val
cpp
vector<int> v2(5, 10); // 创建包含 5 个 10 的 vector
// v2: [10, 10, 10, 10, 10]
vector<string> v3(3, "hello"); // 创建包含 3 个 "hello" 的 vector
// v3: ["hello", "hello", "hello"]
3. 拷贝构造
cpp
vector<int> v4(5, 10); // [10, 10, 10, 10, 10]
vector<int> v5(v4); // 用 v4 拷贝构造 v5
// v5: [10, 10, 10, 10, 10](独立)
4. 使用迭代器区间初始化
cpp
vector<int> v6(5, 10); // [10, 10, 10, 10, 10]
vector<int> v7(v6.begin(), v6.end()); // 用迭代器拷贝整个 v6
int arr[] = {1, 2, 3, 4, 5};
vector<int> v8(arr, arr + 5); // 用数组区间初始化
// v8: [1, 2, 3, 4, 5]
vector<int> v9(v6.begin() + 1, v6.end() - 1); // 部分区间
// v9: [10, 10, 10](去掉首尾)
2.2 vector iterator 的使用

cpp
//正向迭代器
iterator begin(); // 返回第一个元素的迭代器
const_iterator begin() const; // const 对象调用,只能读
iterator end(); // 返回最后一个元素后面的位置(不能解引用)
const_iterator end() const; // const 对象调用
//反向迭代器
reverse_iterator rbegin(); // 返回最后一个元素的迭代器(反向遍历起点)
const_reverse_iterator rbegin() const;
reverse_iterator rend(); // 返回第一个元素**前面**的位置(反向遍历终点)
const_reverse_iterator rend() const;


1. begin() 和 end()
begin()返回指向第一个元素的迭代器,end()返回指向最后一个元素后面一个位置的迭代器。
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 正向遍历
for (auto it = v.begin(); it != v.end(); ++it)
{
cout << *it << " "; // 输出:1 2 3 4 5
}
cout << endl;
// 修改元素
for (auto it = v.begin(); it != v.end(); ++it)
{
*it += 10; // 每个元素加 10
}
输出:11, 12, 13, 14, 15
// const 版本(只读)
const vector<int> v2 = {1, 2, 3};
auto cit = v2.begin(); // 返回 const_iterator,不能修改
*cit = 100; 报错
2. rbegin() 和 rend()


rbegin()返回指向最后一个元素的反向迭代器,rend()返回指向第一个元素前面一个位置的反向迭代器。
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 反向遍历(从后往前)
for (auto it = v.rbegin(); it != v.rend(); ++it)
{
cout << *it << " "; // 输出:5 4 3 2 1
}
cout << endl;
// 修改元素
for (auto it = v.rbegin(); it != v.rend(); ++it)
{
*it += 10; // 每个元素加 10
}
输出:11, 12, 13, 14, 15

2.3 vector 空间增长问题

cpp
// 获取数据个数
size_t size() const;
// 获取容量大小
size_t capacity() const;
// 判断是否为空
bool empty() const;
// 改变 vector 的 size
void resize(size_t n, const T& val = T());
// 改变 vector 的 capacity
void reserve(size_t n);

size() ------ 获取元素个数
cpp
vector<int> v = {1, 2, 3, 4, 5};
cout << v.size() << endl; // 输出:5
v.push_back(6);
cout << v.size() << endl; // 输出:6
v.pop_back();
cout << v.size() << endl; // 输出:5

empty() ------ 判断是否为空
cpp
vector<int> v1;
cout << v1.empty() << endl; // 输出:1
vector<int> v2 = {1, 2, 3};
cout << v2.empty() << endl; // 输出:0
if (!v2.empty())
{
cout << "vector 不为空" << endl;
}


resize() ------ 改变 size(重点)
new_size < size:发生截断,会删除多余元素;new_size > size:发生扩容 size,会新增元素用默认值或指定值填充
reserve() ------ 改变 capacity(重点)
只改变容量,不改变元素个数;
n <= capacity:无效果;n > capacity:扩容到至少 n
cpp
vector<int> v(10, 1); // size=10, capacity=10(10个1)
v.reserve(20); // 扩容到 20
cout << v.size() << endl; // 10(不变)
cout << v.capacity() << endl; // 20
v.reserve(15); // 15 ≤ 20,不缩容
cout << v.size() << endl; // 10(不变)
cout << v.capacity() << endl; // 20(不变)
v.reserve(5); // 5 ≤ 20,不缩容
cout << v.size() << endl; // 10(不变)
cout << v.capacity() << endl; // 20(不变)

结论:
reserve只能扩大 容量,不能缩小;且不改变size。
cpp
vector<int> v(10, 1); // size=10, capacity=10
v.reserve(20); // 先扩容到 20
cout << v.size() << endl; // 10
cout << v.capacity() << endl; // 20
v.resize(15, 2); // 扩大到 15 个元素,多出的填 2
cout << v.size() << endl; // 15
cout << v.capacity() << endl; // 20(容量够,不变)
v.resize(25, 3); // 扩大到 25 个元素,容量不够
cout << v.size() << endl; // 25
cout << v.capacity() << endl; // 可能 30 或 40(自动扩容)
v.resize(5); // 缩小到 5 个元素
cout << v.size() << endl; // 5
cout << v.capacity() << endl; // 不变(还是之前的容量)

resize改变size,可能改变capacity
小结一下二者的区别:
reserve只改容量(只能扩大),不改元素个数;resize改元素个数(可扩大可缩小),容量不够时会自动扩容,但缩小 size 不会缩容量。

capacity() ------ 获取当前容量大小
cpp
void TestVectorExpand()
{
size_t sz;
vector<int> v;
//v.reserve(100);
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
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下运行结果:

- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
- resize在开空间的同时还会进行初始化,影响size。
2.4vector 增删查改

cpp
// 尾插
void push_back(const T& val);
// 尾删
void pop_back();
// 查找(算法,不是 vector 成员)
template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& val);
// 在 position 之前插入 val
iterator insert(iterator position, const T& val);
// 在 position 之前插入 n 个 val
void insert(iterator position, size_type n, const T& val);
// 在 position 之前插入迭代器区间 [first, last)
template <class InputIterator>
void insert(iterator position, InputIterator first, InputIterator last);
// 删除 position 位置的元素
iterator erase(iterator position);
// 删除 [first, last) 区间内的元素
iterator erase(iterator first, iterator last);
// 交换两个 vector
void swap(vector& x);
// 下标访问(不检查边界)
reference operator[](size_type n);
const_reference operator[](size_type n) const;
// 下标访问(检查边界,越界抛异常)
reference at(size_type n);
const_reference at(size_type n) const;
// 第一个元素
reference front();
const_reference front() const;
// 最后一个元素
reference back();
const_reference back() const;
// 返回底层数组指针
T* data();
const T* data() const;
T是模板参数,表示这个vector存储的元素类型
value_type是 STL 标准中对"容器元素类型"的统一定义名称所以**
T就是value_type**

push_back ------ 尾插
作用:在 vector 末尾添加一个元素
cpp
vector<int> v;
v.push_back(10); // v: [10]
v.push_back(20); // v: [10, 20]
v.push_back(30); // v: [10, 20, 30]
底层原理
检查当前
size是否等于capacity;如果容量够:直接把元素放到
_finish位置,然后_finish++;如果容量不够:先扩容(新容量 ≈ 1.5 倍或 2 倍),再添加元素.
扩容时 O(n)(需要拷贝所有元素

pop_back ------ 尾删
作用:删除
vector末尾的一个元素
cpp
vector<int> v = {10, 20, 30, 40};
v.pop_back(); // v: [10, 20, 30]
v.pop_back(); // v: [10, 20]
底层原理
检查是否非空(不能对空 vector 执行
pop_back)
_finish--(只是指针前移,不释放内存)元素个数减 1,容量不变
O(1)(只是指针移动,不需要拷贝)

find是 算法(在<algorithm>中),不是vector的成员函数。它用于在容器中查找值。
cpp
template <class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& val);
first :查找范围的起始迭代器
last :查找范围的结束迭代器(不包含)
val :要查找的值
返回值:找到返回指向该元素的迭代器,找不到返回 last。
cpp
vector<int> v = {10, 20, 30, 40};
auto it = find(v.begin(), v.end(), 30);
if (it != v.end())
cout << *it << endl;
底层实现:
cpp
template<class InputIterator, class T>
InputIterator find(InputIterator first, InputIterator last, const T& val)
{
while (first != last)
{
if (*first == val) // 用 == 比较
return first;
++first;
}
return last;
}
std::find 是算法,用迭代器范围 [first, last) 查找值,返回迭代器;找到返回位置,找不到返回 last。vector 本身没有 find 成员函数。

insert ------ 在 pos 前插入
容量不足则扩容,将
[pos, _finish)往后挪一位,然后构造新元素。
cpp
iterator insert(iterator position, const T& val);
cpp
vector<int> v = {10, 20, 40};
auto it = v.begin() + 2;
v.insert(it, 30);
// 输出v: [10, 20, 30, 40]

erase ------ 删除 pos 位置的数据
将
[pos+1, _finish)往前挪一位,覆盖被删除元素,然后_finish--。
cpp
iterator erase(iterator position);
cpp
vector<int> v = {10, 20, 30, 40};
auto it = v.begin() + 2;
v.erase(it);
// v: [10, 20, 40]

swap ------ 交换两个 vector 的数据空间
只交换三个指针(
_start、_finish、_end_of_storage),不拷贝元素。
cpp
void swap(vector& x);
cpp
vector<int> v1 = {1, 2, 3};
vector<int> v2 = {4, 5, 6};
v1.swap(v2);
//输出: v1: [4, 5, 6], v2: [1, 2, 3]

operator[] ------ 下标访问
return _start[n],直接指针偏移,不检查越界。
cpp
reference operator[](size_type n);
const_reference operator[](size_type n) const;
cpp
vector<int> v = {10, 20, 30};
cout << v[0] << endl; // 10
v[1] = 100; // 进行修改
// v: [10, 100, 30]
2.4 vector 迭代器失效问题(重点)
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于vector可能会导致其迭代器失效的操作有:
会引起其底层空间改变的操作**,都有可能是迭代器失效,比如:resize、reserve、insert、
assign、push_back等。**
- 1.insert
第一种:插入操作导致的内存重新分配(扩容)
cpp
void TestInsert1()
{
// 迭代器失效问题 -- 类似野指针问题
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
if (pos != v.end())
{
// 第一种:扩容导致失效
// 当 v.capacity() < v.size() + 1 时,vector 重新分配内存
// 所有迭代器(包括 pos)指向已释放的旧内存
v.insert(pos, 20);
// pos 已失效,不能使用
// cout << *pos << endl;
// 解决方法:重新赋值
// pos = v.insert(pos, 20);
}
}
当
size() + 1 > capacity()时,vector 会重新分配更大内存;所有迭代器(包括 pos)都失效(指向已释放的旧内存)
图解


第二种**:插入操作导致的位置移动(即使不扩容)**
cpp
void TestInsert2()
{
// 迭代器失效问题 -- 类似野指针问题
vector<int> v;
v.reserve(10); // 预留足够空间,避免扩容
v.push_back(1);
v.push_back(2);
v.push_back(3);
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
if (pos != v.end())
{
// 虽然没扩容,但 insert 导致 pos 之后的所有元素后移
// pos 仍然指向原位置,但该位置现在放的是新插入的 20
// 原本的 2 已经被后移,pos 的语义改变了
v.insert(pos, 20);
// pos 现在指向 20,而不是原来的 2
// cout << *pos << endl; // 输出 20,不是 2
// 解决方法:重新赋值
// pos = v.insert(pos, 20); // pos 指向新插入的 20
}
}
pos 仍指向原位置,但该位置现在是新插入的元素;pos 原本指向的元素已经被后移,所以 pos 语义改变
图解:

insert 操作导致迭代器失效的两种场景:
扩容导致的失效:当插入元素时发生扩容,vector 会重新分配内存空间,将旧数据迁移到新空间后释放旧空间。此时迭代器 pos 仍然指向已被释放的旧空间,变成了野指针,无法再使用。
不扩容但语义改变导致的失效:当插入元素时没有发生扩容,vector 会将 pos 位置及其之后的元素向后移动,为插入腾出空间。此时 pos 仍然指向原来的内存位置,但该位置已经被新插入的元素占据,pos 不再指向原来的值,其语义发生了改变。
补充:
发生扩容时,当前迭代器和之后所有迭代器都会失效
无论哪种场景,迭代器失效后都不能再进行访问(读取、写入、比较等操作)
解决方法:使用 insert 函数返回的新迭代器(指向新插入元素)重新赋值
不同平台的表现:
VS2019/VS2022(Debug 模式):程序崩溃,触发迭代器失效断言
VS2019/VS2022(Release 模式):可能不会立即崩溃,但属于未定义行为
Linux(GCC/Clang):通常不会崩溃,但迭代器确实已失效
- 2.erase

cpp
// erase 操作导致迭代器失效演示
void TestEraseDemo()
{
vector<int> v{ 10, 20, 30, 40, 50 };
// 查找值为30的元素
vector<int>::iterator it = find(v.begin(), v.end(), 30);
if (it != v.end())
{
// 删除it指向的元素(值为30)
v.erase(it);
// 此时it已失效,因为it后面的元素(40,50)都向前移动了
// it指向的位置现在存放的是40,不再是原来的30
// 错误:访问已失效的迭代器
// cout << *it << endl; // 虽然可能输出40,但这是未定义行为
// *it = 100; // 危险操作
// 正确:使用erase返回值更新迭代器
// it = v.erase(it); // it指向被删除元素的下一个位置(也就是40)
}
// 删除后,it及其之后的所有迭代器都失效了
// 不能再用it去访问vector中的元素
}
erase 导致迭代器失效的原因:
erase 删除元素时,pos 位置之后的元素会向前移动填补空缺。pos 仍然指向原来的内存位置,但该位置的数据已经被覆盖或删除,因此 pos 不再指向原来的值,迭代器失效。
不同平台的表现:
VS2019/VS2022(Debug 模式):程序崩溃,触发迭代器失效断言
VS2019/VS2022(Release 模式):可能不会立即崩溃,但属于未定义行为
Linux(GCC/Clang):通常不会崩溃,但迭代器确实已失效
图解:

erase 导致迭代器失效的两种场景:
缩容引发失效:比如 vector 当前有 100 个容量且存满 100 个数据,删除操作后有效数据只剩 30 个,这时编译器可能会触发缩容,重新开辟一块 50 个容量的新空间,把数据搬过去后释放原来的 100 个容量空间。之前保存的迭代器 pos 还指着被释放的旧空间,就成了野指针。
元素搬移导致意义改变:删除 pos 位置的数据后,pos 后面的所有元素会依次前移填补空缺。pos 还是指向原来那个内存地址,但里面的数据已经不是原来的值了,迭代器的含义发生了变化。如果 pos 正好指向最后一个元素,删除后 pos 就变成了 end 位置,end 位置没有有效数据,去访问它也是非法的。
补充:
- 缩容并不是 C++ 标准规定的行为,不同的 STL 实现可能不同,有的编译器会缩容,有的不会
- 但无论缩容与否,erase 之后的迭代器都应该认为是失效的,因为即使没有缩容,迭代器指向的位置意义也已经发生了改变
- VS 系列(Debug 模式):做了非常严格的检查,即使迭代器仅仅是意义改变(并非野指针),也会被检测出来并触发断言,导致程序崩溃
- G++(Linux):检查相对宽松,即使迭代器已经失效,访问时可能也不会报错,能正常输出结果,但这不代表迭代器是有效的
迭代器失效小结:
vector 插入或删除操作对迭代器的影响:
插入或删除元素会导致当前迭代器及其之后所有元素的迭代器失效。这是因为:
插入时:元素后移或扩容搬移数据,迭代器指向的位置或内存发生变化
删除时:元素前移填补空缺,迭代器指向的数据或语义发生改变
解决办法:
在使用迭代器之前重新赋值即可。
对于
insert:pos = v.insert(pos, value);获取指向新插入元素的迭代器对于
erase:pos = v.erase(pos);获取指向被删除元素下一个位置的迭代器重点就是:
进行插入或删除操作后,原有的迭代器不再可靠。如果后续还需要通过迭代器操作 vector 中的元素,必须用函数返回值对迭代器重新赋值,获取新的有效迭代器。