【C++】:STL详解 —— vector类
目录
vector的概念
vector的构造函数
vector的大小
size()和capacity()
empty()
resize()和reserver()
vector的插入
push_back()和emplace_back()
insert()和emplace()
vector的删除
pop_back()
earse()
clear()
vector的迭代器
迭代器类型
begin()和end()
rbegin()和end()
vector中元素的访问
[]+下标
at()
front()和back()
越界访问风险与防护
性能与适用场景对比
vector迭代器失效问题
迭代器失效的场景
安全操作的最佳实践
迭代器失效规则总结
vector的概念
- vector是表示可变大小数组的序列容器
- vector就像数组一样,也采用的连续空间来存储元素,这也意味着可以采用下标对vector的元素进行访问
- vector与普通数组不同的是,vector的大小是可以动态改变
- 当vector需要重新分配大小时,其做法是,分配一个新的数组,然后将全部元素移到这个数组当中,并释放原来的数组空间
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大。不同的库采用不同的策略权衡空间的使用和重新分配,以至于在末尾插入一个元素的时候是在常数的时间复杂度完成的
- 由于vector采用连续的空间来存储元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,在其末尾添加和删除元素相对高效,而对于不在其末尾进行的删除和插入操作效率则相对较低
vector的构造函数
// 默认构造
vector<int> v1;
// 指定元素个数和值
vector<int> v2(3, 100); // {100, 100, 100}
// 迭代器范围构造
list<int> lst = {1, 2, 3};
vector<int> v3(lst.begin(), lst.end()); // {1, 2, 3}
// 拷贝构造
vector<int> v4(v3); // {1, 2, 3}
// 移动构造
vector<int> v5(move(v4)); // v5 接管 v4 的内存,v4 变空
// 初始化列表构造
vector<int> v6 = {4, 5, 6}; // {4, 5, 6}
// 类型推导构造(C++17)
vector v7 = {7, 8, 9}; // vector<int>
构造函数形式 | 作用描述 | 时间复杂度 | C++版本要求 |
---|---|---|---|
vector<T>() | 创建空 vector | O(1) | C++98 |
vector<T>(n) | 创建 n 个默认元素的 vector | O(n) | C++98 |
vector<T>(n, val) | 创建 n 个 val 值的 vector | O(n) | C++98 |
vector<T>(first, last) | 迭代器范围初始化 | O(n) | C++98 |
vector<T>(const vector&) | 深拷贝构造 | O(n) | C++98 |
vector<T>(vector&&) | 移动构造(高效转移内存) | O(1) | C++11 |
vector<T>{elem1, elem2...} | 初始化列表构造 | O(n) | C++11 |
vector{...} | 类型自动推导 | O(n) | C++17 |
vector的大小
size()和capacity()
size()
:返回当前元素数量-
capacity()
:返回当前预分配的内存容量(可容纳的最大元素数)
vector<int> v = {1, 2, 3};
cout << v.size(); // 输出 3
cout << v.capacity(); // 输出 3
empty()
判断vector是否为空(size==0)
vector<int> v = {1, 2, 3};
cout << v.empty(); // 输出 0(false)
resize()和reserver()
-
resize(n)
:调整元素数量为n
。-
若
n > size()
,新增元素默认初始化(如int
初始化为 0)。 -
若
n < size()
,删除尾部多余元素。
-
-
reserve(n)
:预分配至少能容纳n
个元素的内存(减少扩容次数)。
vector<int> v = {1, 2, 3};
v.resize(5); // v = {1, 2, 3, 0, 0}
v.resize(2); // v = {1, 2}
v.reserve(100); // 预分配容量为 100
cout << v.capacity(); // 输出 100
操作/属性 | 描述 | 示例 |
---|---|---|
size() | 当前元素数量 | v.size() → 3 |
capacity() | 当前预分配内存可容纳的元素数量 | v.capacity() → 5 |
resize(n) | 调整元素数量(可能增删元素) | v.resize(5) → 5 个元素 |
reserve(n) | 预分配内存容量(不改变元素数量) | v.reserve(10) |
vector的插入
push_back()和emplace_back()
push_back(const T& value)
在尾部插入元素(拷贝构造)。
emplace_back(Args&&... args)(C++11 起)
在尾部直接构造元素(避免临时对象拷贝)
vector<int> v = {1, 2};
v.push_back(3); // v = {1, 2, 3}(拷贝)
v.emplace_back(4); // v = {1, 2, 3, 4}(直接构造)
insert()和emplace()
insert(iterator pos, const T& value)
在迭代器 pos 指向的位置前插入元素(拷贝构造)。
emplace(iterator pos, Args&&... args)(C++11 起)
在 pos 位置直接构造元素(更高效)。
vector<int> v = {1, 3};
auto it = v.begin() + 1;
v.insert(it, 2); // v = {1, 2, 3}(在位置1插入2)
v.emplace(it, 0); // v = {1, 0, 2, 3}(在位置1插入0)
vector的删除
pop_back()
移除最后一个元素
vector<int> v = {1, 2, 3};
v.pop_back(); // v = {1, 2}
earse()
erase(iterator pos)
删除迭代器 pos
指向的元素,返回指向下一个元素的迭代器。
vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 1;
it = v.erase(it); // 删除元素 2,v = {1, 3, 4}
// it 指向 3(需更新迭代器)
erase(iterator first, iterator last)
删除迭代器范围 [first, last)
内的元素,返回指向 last
之后元素的迭代器。
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.erase(v.begin() + 1, v.begin() + 3);
// 删除元素 2 和 3,v = {1, 4, 5}
// it 指向 4
clear()
移除所有元素,size()
变为 0,但 capacity()
保持不变。
vector<int> v = {1, 2, 3};
v.clear(); // v 为空,size=0,capacity 仍为 3
vector的迭代器
迭代器类型
vector<T>::iterator:普通迭代器(可读写元素)。
vector<T>::const_iterator:常量迭代器(只读元素)。
vector<T>::reverse_iterator:反向迭代器。
vector<T>::const_reverse_iterator:常量反向迭代器。
vector<int> v = {1, 2, 3};
vector<int>::iterator it = v.begin();
*it = 10; // 修改元素为 10
vector<int>::const_iterator cit = v.cbegin();
// *cit = 20; // 错误!不可修改
begin()和end()
-
begin()
:返回指向第一个元素的迭代器。 -
end()
:返回指向最后一个元素之后位置的迭代器(尾后迭代器)。
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
vector<int> v = {1, 2, 3};
// 正向遍历
for (auto it = v.begin(); it != v.end(); ++it) {
cout << *it << " "; // 输出 1 2 3
}
rbegin()和end()
-
rbegin()
:返回指向最后一个元素之后位置的迭代器(尾后迭代器)。 -
rend()
:返回指向第一个元素的迭代器。
iterator rbegin();
const_iterator begin() const;
iterator rend();
const_iterator end() const;
// 反向遍历
for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
cout << *rit << " "; // 输出 3 2 1
}
vector中元素的访问
[]+下标
-
高效:时间复杂度 O(1)(直接计算内存偏移)。
-
无边界检查:越界访问导致未定义行为(可能崩溃或数据损坏)。
reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
vector<int> v = {10, 20, 30};
cout << v[0]; // 输出 10
v[1] = 200; // 修改元素为 200
// cout << v[5]; // 危险!越界访问
at()
-
边界检查:若
index >= size()
,抛出std::out_of_range
异常。 -
性能略低:因额外检查,稍慢于
operator[]
。
vector<int> v = {1, 2, 3};
try {
cout << v.at(2); // 输出 3
cout << v.at(5); // 抛出异常
} catch (const out_of_range& e) {
cerr << "越界错误:" << e.what(); // 捕获异常
}
front()和back()
-
front()
:返回第一个元素的引用。 -
back()
:返回最后一个元素的引用。
vector<int> v = {5, 6, 7};
cout << v.front(); // 5
cout << v.back(); // 7
v.front() = 0; // v = {0, 6, 7}
// 若 vector 为空,行为未定义!
越界访问风险与防护
访问方法 | 越界行为 | 安全建议 |
---|---|---|
operator[] | 未定义行为(崩溃/数据损坏) | 确保索引有效(如 index < v.size() ) |
at() | 抛出异常 | 使用 try-catch 处理异常 |
front() /back() | 空容器导致未定义行为 | 先检查 !v.empty() |
性能与适用场景对比
方法 | 性能 | 安全性 | 典型场景 |
---|---|---|---|
operator[] | 高 | 无检查 | 已知索引安全的快速访问 |
at() | 中 | 高(异常) | 需边界检查的调试或不确定索引 |
迭代器/范围 for | 高 | 依赖遍历逻辑 | 顺序访问或修改元素 |
data() | 高 | 无检查 | 与 C 风格 API 交互(如 memcpy ) |
vector迭代器失效问题
迭代器的主要作用就是让我们在使用各个容器时不用关心其底层的数据结构,而vector的迭代器在底层实际上就是一个指针。迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向的是一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。
迭代器失效的场景
1、插入操作(Insert)
导致扩容:插入元素时若超出当前容量(size >= capacity
),vector会重新分配内存,导致所有迭代器、指针、引用失效。
vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4); // 假设容量不足,触发扩容
// it 失效!不可使用
未扩容时:仅插入位置之后的迭代器失效。
vector<int> v = {1, 2, 3};
v.reserve(5); // 预分配容量
auto it = v.begin() + 1;
v.insert(it, 4); // 插入后,it 及其后的迭代器失效
// 必须使用 insert 返回的新迭代器
auto new_it = v.insert(v.begin() + 1, 4);
2、删除操作(Erase)
删除元素时,被删除元素之后的迭代器失效,包括end()
。
vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 2; // 指向3
v.erase(it); // 删除3,it失效
// 必须使用 erase 返回的迭代器(指向4)
it = v.erase(it); // it 现在指向4
3、容量调整(Resize/Reserve)
-
reserve(n)
:若n > capacity
,重新分配内存,所有迭代器失效。 -
resize(n)
:若n > capacity
,同样触发扩容,导致所有迭代器失效
安全操作的最佳实践
使用迭代器时,永远记住一句话:每次使用前,对迭代器进行重新赋值
1、插入操作(Insert)
使用返回的新迭代器更新原迭代器:
vector<int> v = {1, 3, 4};
auto it = v.begin() + 1;
it = v.insert(it, 2); // v = {1,2,3,4}, it指向2
2、删除操作(Erase)
在循环中结合erase
的返回值更新迭代器:
vector<int> v = {2, 4, 6, 8};
for (auto it = v.begin(); it != v.end();)
{
if (*it % 2 == 0)
{
it = v.erase(it); // 更新迭代器
}
else
{
++it;
}
}
迭代器失效规则总结
操作 | 迭代器失效范围 | 内存是否重分配 |
---|---|---|
push_back /emplace_back | 若扩容则全部失效;否则仅end() 失效 | 可能 |
insert /emplace | 若扩容则全部失效;否则插入点之后失效 | 可能 |
erase /pop_back | 被删除元素之后失效 | 否 |
reserve(n > capacity) | 全部失效 | 是 |
resize(n > capacity) | 全部失效 | 是 |