由于STL容器的相关接口使用太多了太多了,后续STL部分我就不采取像string那样分开详细举例子写了,只会在必要的地方给出代码例子,全汇总在一起,也方便后续学习回顾,毕竟我有强迫症,难受。。。。。。
目录
[5. 使用初始化列表构造(C++11及以上)](#5. 使用初始化列表构造(C++11及以上))
[6. 移动构造(C++11及以上)](#6. 移动构造(C++11及以上))
[解释 boolalpha(重要!!!)](#解释 boolalpha(重要!!!))
问题分析:(详细的过程可以画图走一下该程序,就能发现存在下面的问题)
一、Vector(向量)容器
1、基本概念
vector是C++标准模板库(STL)中表示可变大小数组的序列容器 ,它提供了动态数组的功能,同时具备自动内存管理的特性。
2、存储特性
vector采用连续的内存空间存储元素,这种连续存储的特性带来两个重要优势:
-
支持随机访问,可以通过下标直接访问元素(时间复杂度O(1))
-
缓存友好性,由于内存连续性,能获得更好的缓存局部性
3、动态扩容机制
与静态数组不同,vector具有动态改变大小的能力:
-
当现有空间不足时,vector会执行重新分配
-
重新分配过程包括:
a) 分配新的更大的内存空间
b) 将原有元素移动(或拷贝)到新空间
c) 释放原有内存空间 -
典型的扩容策略是按当前大小的某个比例(如2倍)增长
4、空间分配策略
vector采用智能的空间预分配策略:
-
实际分配的容量(capacity)通常大于当前元素数量(size)
-
这种超额分配使得在尾部插入操作(push_back)能在平摊常数时间(O(1))内完成
-
不同实现可能有不同的增长因子,在空间利用和重分配频率间取得平衡
5、操作效率分析
vector在各种操作上的效率表现:
-
访问元素:高效,O(1)时间复杂度
-
尾部操作:插入/删除:高效,平摊O(1)
-
非尾部操作:插入/删除:较低效,O(n)(需要移动后续元素)
-
与其他序列容器(如list, deque)相比,vector在随机访问和尾部操作上表现最优
6、适用场景
vector特别适合以下场景:
-
需要频繁随机访问元素
-
主要在序列尾部进行插入/删除操作
-
需要内存紧凑布局的场合
-
元素数量变化较大但大致范围可预估
补充说明:
现代C++标准建议使用vector作为默认的首选容器,除非有特殊需求需要使用其他容器类型。其卓越的性能表现和简单的接口使其成为STL中最常用的容器之一。

二、Vector的定义方式

在C++中,vector
是一个动态数组容器,提供了多种灵活的初始化方式。以下是vector
的主要定义方式:
1、构造空容器
cpp
vector<int> v1; // 构造一个int类型的空vector
-
创建一个不包含任何元素的vector
-
初始容量为0,当添加元素时会自动扩容
2、构造包含n个相同元素的容器
cpp
vector<int> v2(10, 2); // 构造包含10个值为2的int类型vector
-
第一个参数指定元素数量
-
第二个参数指定每个元素的值
-
如果省略第二个参数,将使用值初始化(对于int是0)
3、拷贝构造
cpp
vector<int> v3(v2); // 通过拷贝v2构造v3
-
创建一个与另一个vector完全相同的新vector
-
新vector独立于原vector,修改不会相互影响
4、使用迭代器范围构造
cpp
vector<int> v4(v2.begin(), v2.end()); // 使用v2的迭代器范围构造v4
-
通过一对迭代器指定范围来构造vector
-
范围是左闭右开区间[begin, end)
-
特别强大,可以用于从其他容器复制数据:
cpp
string s("hello world");
vector<char> v5(s.begin(), s.end()); // 从string构造vector<char>
5. 使用初始化列表构造(C++11及以上)
cpp
vector<int> v6{1, 2, 3, 4, 5}; // 使用初始化列表构造vector
-
直接列出所有初始元素
-
更简洁直观的初始化方式
6. 移动构造(C++11及以上)
cpp
vector<int> v7(std::move(v6)); // 使用移动语义构造v7
-
将资源从v6转移到v7
-
操作后v6为空,但有效状态
选择哪种初始化方式取决于具体场景和需求,每种方式都有其适用的场合。
三、Vector容器的空间管理与大小

1、size与capacity的区别
在C++的vector容器中,size()
和capacity()
是两个重要但不同的概念:
-
size()
:返回当前容器中实际存储的元素数量 -
capacity()
:返回容器在不重新分配内存的情况下可以容纳的最大元素数量
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 2); // 创建包含10个值为2的元素的vector
cout << "Size: " << v.size() << endl; // 输出: 10
cout << "Capacity: " << v.capacity() << endl; // 输出: 10
return 0;
}

2、reserve与resize函数
reserve函数
reserve()
用于预分配内存空间,改变容器的capacity但不影响size:
-
当参数大于当前capacity时,将capacity扩大到该值
-
当参数小于等于当前capacity时,不做任何操作
cpp
vector<int> v(10, 2);
cout << "Initial size: " << v.size() << endl; // 10
cout << "Initial capacity: " << v.capacity() << endl; // 10
v.reserve(20); // 预分配空间为20
cout << "After reserve(20):" << endl;
cout << "Size: " << v.size() << endl; // 仍为10
cout << "Capacity: " << v.capacity() << endl; // 变为20

resize函数
resize()
用于改变容器中元素的数量(size):
-
当参数大于当前size时:将size扩大到该值,新增元素初始化为第二个参数值(若不显示给,则默认为0)
-
当参数小于当前size时:将size缩小到该值,多余元素被销毁
cpp
v.resize(15); // 将元素数量增加到15
cout << "After resize(15):" << endl;
cout << "Size: " << v.size() << endl; // 变为15
cout << "Capacity: " << v.capacity() << endl; // 仍为20
v.resize(5); // 将元素数量减少到5
cout << "After resize(5):" << endl;
cout << "Size: " << v.size() << endl; // 变为5
cout << "Capacity: " << v.capacity() << endl; // 仍为20

3、empty函数
empty()
用于检查容器是否为空(即size是否为0):
cpp
vector<int> v(10, 2);
cout << "Is empty? " << boolalpha << v.empty() << endl; // 输出: false
vector<int> emptyV;
cout << "Is empty? " << emptyV.empty() << endl; // 输出: true

解释 boolalpha``(重要!!!)
boolalpha
boolalpha
是 C++ 的一个 I/O 操纵符(manipulator) ,用于控制 bool
值的输出格式。
默认情况下,bool
值在输出时会以 0
(false
)或 1
(true
)的形式显示:
cpp
cout << false; // 输出: 0
cout << true; // 输出: 1
但使用 boolalpha
后,bool
值会以字符串 "false"
或 "true"
的形式输出,持续性的:
cpp
cout << boolalpha << false; // 输出: false
cout << boolalpha << true; // 输出: true
在代码中:
cpp
cout << "Is empty? " << boolalpha << v.empty() << endl;
-
v.empty()
返回false
(因为v
有 10 个元素)。 -
boolalpha
确保false
以"false"
形式输出,而不是0
。
boolalpha
是一个 持久性(sticky) 的 I/O 操纵符(manipulator),这意味着一旦设置后,它会 持续影响后续的 bool
输出 ,直到被显式关闭。可以使用 noboolalpha
来恢复默认的 0
/1
输出格式。
cpp
#include <iostream>
using namespace std;
int main() {
cout << boolalpha << true << endl; // 输出: true
cout << noboolalpha << false << endl; // 输出: 0(恢复默认)
cout << true << endl; // 输出: 1(仍然保持默认)
return 0;
}

4、实际应用建议
-
当知道大致元素数量时,使用
reserve()
预分配空间可以避免多次重新分配 -
resize()
会改变容器内容,而reserve()
只影响内存分配 -
频繁插入操作时,适当预留空间可以提高性能
-
shrink_to_fit()
(C++11)可以请求减少capacity以匹配size
5、增长机制(了解即可)
在VS和g++环境下运行测试vector的capacity增长机制会发现:VS遵循1.5倍增长策略,而g++采用2倍增长。需要注意的是,vector的扩容倍数并非固定为2倍,具体增长比例由不同版本的STL实现决定。其中VS使用的是PJ版STL,g++采用SGI版STL实现。
关于reserve和resize的区别:
- reserve仅预先分配内存空间,不进行初始化,适合预先知道所需空间大小的场景,能有效减少vector扩容带来的性能开销;
- resize在分配空间的同时会进行初始化操作,这会直接影响vector的size属性。
cpp
// 测试vector的默认扩容机制
void TestVectorExpand() {
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "Vector capacity growth pattern:\n";
for (int i = 0; i < 100; ++i) {
v.push_back(i);
if (sz != v.capacity()) {
sz = v.capacity();
cout << "Capacity changed to: " << sz << endl;
}
}
}
VS运行结果(1.5倍扩容):

g++运行结果(2倍扩容):

若已知vector需要存储的元素数量,建议提前预留足够空间。这样可避免插入时频繁扩容导致的性能损耗:
cpp
// 使用reserve预分配空间优化性能
void TestVectorExpandOP() {
vector<int> v;
size_t sz = v.capacity();
v.reserve(100); // 预分配空间避免频繁扩容
cout << "Vector growth with reserve:\n";
for (int i = 0; i < 100; ++i) {
v.push_back(i);
if (sz != v.capacity()) {
sz = v.capacity();
cout << "Capacity changed to: " << sz << endl;
}
}
}
四、Vector迭代器使用

1、迭代器概述
迭代器是STL中用于遍历容器元素的工具,它提供了类似指针的接口来访问容器中的元素。vector提供了多种迭代器类型,可以满足不同的遍历需求。

2、正向迭代器
begin()和end()
-
begin()
: 返回指向容器第一个元素的正向迭代器 -
end()
: 返回指向容器最后一个元素之后位置的迭代器(不是最后一个元素)
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 2); // 创建包含10个2的vector
// 使用正向迭代器遍历容器
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
// 更简洁的for循环写法(C++11起)
for(auto num : v) {
cout << num << " ";
}
cout << endl;
return 0;
}

3、反向迭代器
rbegin()和rend()
-
rbegin()
: 返回指向容器最后一个元素的反向迭代器 -
rend()
: 返回指向容器第一个元素之前位置的反向迭代器
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 2); // 创建包含10个2的vector
// 使用反向迭代器遍历容器
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
// 使用auto简化迭代器声明(C++11起)
for(auto rit = v.rbegin(); rit != v.rend(); ++rit) {
cout << *rit << " ";
}
cout << endl;
return 0;
}

4、常量迭代器
vector还提供了常量迭代器版本,用于不允许修改元素的情况:
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 常量正向迭代器
vector<int>::const_iterator cit = v.cbegin();
while(cit != v.cend()) {
cout << *cit << " ";
// *cit = 10; // 错误:不能修改常量迭代器指向的值
cit++;
}
// 常量反向迭代器
vector<int>::const_reverse_iterator crit = v.crbegin();
while(crit != v.crend()) {
cout << *crit << " ";
crit++;
}
5、迭代器使用注意事项
-
有效性 :当vector发生扩容操作后,所有迭代器都会失效
-
边界检查:确保迭代器在有效范围内使用
-
性能:迭代器访问与下标访问性能相当
-
兼容性:迭代器使算法可以独立于具体容器实现
6、实际应用示例
cpp
#include <algorithm>
vector<int> v = {5, 3, 1, 4, 2};
// 使用迭代器配合STL算法
sort(v.begin(), v.end()); // 排序
// 查找元素
auto found = find(v.begin(), v.end(), 3);
if(found != v.end()) {
cout << "Found at position: " << distance(v.begin(), found) << endl;
}
// 使用迭代器区间构造新vector
vector<int> v2(v.begin()+1, v.end()-1);
通过合理使用各种迭代器,可以灵活高效地操作vector容器中的元素。
五、Vector的增删查改操作

1、尾部操作:push_back和pop_back
push_back
和pop_back
是vector最基本的操作函数,用于在容器尾部进行元素的添加和删除。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
// 使用push_back在尾部插入元素
v.push_back(1); // 尾插元素1
v.push_back(2); // 尾插元素2
v.push_back(3); // 尾插元素3
v.push_back(4); // 尾插元素4
for (auto num : v)
{
cout << num << " ";
}
cout << endl;
// 使用pop_back删除尾部元素
v.pop_back(); // 尾删元素4
v.pop_back(); // 尾删元素3
v.pop_back(); // 尾删元素2
v.pop_back(); // 尾删元素1
for (auto num : v)
{
cout << num << " ";
}
cout << endl;
return 0;
}

注意事项:
-
push_back
的时间复杂度为O(1)(平均情况),当容量不足时会触发重新分配内存 -
对空vector使用
pop_back
会导致未定义行为
2、任意位置操作:insert和erase
通过insert函数可以在所给迭代器位置插入一个或多个元素,通过erase函数可以删除所给迭代器位置的元素,或删除所给迭代器区间内的所有元素(左闭右开)。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (auto num : v)
{
cout << num << " ";
}
cout << endl;
// 在开头插入元素0
v.insert(v.begin(), 0);
// 在开头插入5个-1
v.insert(v.begin(), 5, -1);
for (auto num : v)
{
cout << num << " ";
}
cout << endl;
// 删除第一个元素
v.erase(v.begin());
// 删除前5个元素(左闭右开区间)
v.erase(v.begin(), v.begin() + 5);
for (auto num : v)
{
cout << num << " ";
}
cout << endl;
return 0;
}

时间复杂度分析:
-
insert
和erase
操作的时间复杂度为O(n),因为可能需要移动后续元素 -
在vector头部操作效率最低,尾部操作效率最高
以上是按位置进行插入或删除元素的方式,若要按值进行插入或删除(在某一特定值位置进行插入或删除),则需要用到find函数。
3、查找操作:find函数
- 标准库的
find
函数可以用于在vector中查找特定值。find函数是在算法模块(algorithm)当中实现的,不是vector的成员函数。 - find函数接受三个参数,前两个参数指定一个左闭右开的迭代器范围,第三个参数是要查找的值。该函数会在给定范围内查找第一个匹配的元素,并返回其迭代器;若未找到,则返回第二个参数。
cpp
#include <iostream>
#include <vector>
#include <algorithm> // 包含find函数
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
// 查找值为2的元素
auto pos = find(v.begin(), v.end(), 2);
if (pos != v.end()) {
// 在2的位置插入10
v.insert(pos, 10);
}
for (auto num : v)
{
cout << num << " ";
}
cout << endl;
// 查找值为3的元素
pos = find(v.begin(), v.end(), 3);
if (pos != v.end()) {
// 删除3
v.erase(pos);
}
for (auto num : v)
{
cout << num << " ";
}
cout << endl;
return 0;
}

注意事项:
-
find
是线性查找,时间复杂度为O(n) -
对于有序vector,可以使用
binary_search
等更高效的算法 -
务必检查返回值是否为
v.end()
,否则可能导致未定义行为
4、交换操作:swap
swap
函数可以高效交换两个vector的内容。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v1(10, 1); // 10个1
vector<int> v2(10, 2); // 10个2
// 交换v1和v2的内容
v1.swap(v2);
for (auto num : v1)
{
cout << num << " ";
}
cout << endl;
for (auto num : v2)
{
cout << num << " ";
}
cout << endl;
// 也可以使用标准库的swap
swap(v1, v2);
for (auto num : v1)
{
cout << num << " ";
}
cout << endl;
for (auto num : v2)
{
cout << num << " ";
}
cout << endl;
return 0;
}

特点:
-
交换操作非常高效,时间复杂度O(1)
-
仅交换内部指针,不涉及元素的实际移动
5、元素访问方式
vector提供了多种元素访问方式,各有适用场景。
下标访问
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 1);
// 使用下标访问
for (size_t i = 0; i < v.size(); i++) {
cout << v[i] << " "; // 使用operator[]
// 也可以使用v.at(i),会进行边界检查
}
cout << endl;
return 0;
}

迭代器访问
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 1);
// 使用迭代器
for (auto it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
cout << endl;
return 0;
}

我们上面说到vector是支持迭代器的,所以我们还可以用范围for对vector容器进行遍历。(支持迭代器就支持范围for,因为在编译时编译器会自动将范围for替换为迭代器的形式)
范围for循环
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, 1);
// 范围for循环(底层使用迭代器实现)
for (auto e : v) {
cout << e << " ";
}
cout << endl;
return 0;
}

访问方式比较:
-
下标访问:最直观,性能好,但不进行边界检查
-
at()
方法:进行边界检查,越界时抛出异常 -
迭代器:更通用的访问方式,支持所有标准容器
-
范围for:C++11引入,代码最简洁
总结
vector作为C++中最常用的序列容器,提供了丰富的操作接口:
-
增 :
push_back
,insert
-
删 :
pop_back
,erase
,clear
-
查 :
find
,operator[]
,at
-
改:通过迭代器或下标直接修改
-
其他 :
swap
,size
,empty
,reserve
等
合理选择操作方式可以显著提高程序效率,特别是在处理大量数据时。
六、Vector迭代器失效问题
1、迭代器失效的概念
迭代器是STL中用于遍历容器元素的工具,它提供了一种统一的方式来访问各种容器,而不需要关心底层的数据结构实现。 对于vector而言,其迭代器在底层实际上就是一个原生指针(T*)。
迭代器失效指的是迭代器底层对应的指针所指向的空间已经被销毁,导致迭代器指向了一块无效的内存区域。如果继续使用已经失效的迭代器,可能会导致程序崩溃或产生未定义行为。

2、迭代器失效的典型场景
场景一:插入操作导致的失效
cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
vector<int> v{1, 2, 3, 4, 5}; // 初始化vector: 1 2 3 4 5
// 获取值为2的元素的迭代器
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
v.insert(pos, 10); // 在2的位置插入10 → 1 10 2 3 4 5
// 危险!pos已经失效,可能指向错误位置或已释放的内存
v.erase(pos);
return 0;
}
在该代码中,我们本意是使用元素2的迭代器在原序列中2的位置插入一个10,然后将2删除,但我们实际上获取的是指向2的指针,当我们在2的位置插入10后,该指针就指向了10,所以我们之后删除的实际上是10,而不是2。
问题分析:
-
当在vector中间插入元素时,可能导致内存重新分配
-
即使没有重新分配内存,插入点后的所有元素都会向后移动
-
原迭代器
pos
仍然指向原来的内存地址,但该位置的内容可能已经改变 -
继续使用失效的迭代器会导致未定义行为
场景二:删除操作导致的失效
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v{1, 2, 3, 4, 5, 6};
vector<int>::iterator it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) { // 删除所有偶数
v.erase(it); // 删除后it失效
}
it++; // 危险!对失效迭代器进行递增
}
return 0;
}
问题分析:(详细的过程可以画图走一下该程序,就能发现存在下面的问题)
-
erase
操作会删除指定位置的元素,并使该位置之后的所有元素前移 -
删除操作会使指向被删除元素及其后所有元素的迭代器失效
-
直接对失效的迭代器进行递增操作会导致未定义行为
-
此外,这种写法会跳过对某些元素的检查(因为删除后it++会跳过一个元素)
3、迭代器失效的解决方案
通用原则
每次修改容器后,都应该重新获取迭代器,不要继续使用旧的迭代器。
解决方案一:插入操作后的处理
cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
vector<int> v{1, 2, 3, 4, 5};
// 获取值为2的元素的迭代器
vector<int>::iterator pos = find(v.begin(), v.end(), 2);
v.insert(pos, 10); // 插入后pos失效
// 重新获取值为2的元素的迭代器
pos = find(v.begin(), v.end(), 2);
if (pos != v.end()) {
v.erase(pos); // 安全删除
}
// 输出结果: 1 10 3 4 5
for (auto num : v) {
cout << num << " ";
}
return 0;
}

解决方案二:删除操作时的正确写法
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v{1, 2, 3, 4, 5, 6};
vector<int>::iterator it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) {
// erase返回指向被删除元素下一个位置的迭代器
it = v.erase(it);
} else {
it++; // 只有未删除时才递增
}
}
// 输出结果: 1 3 5
for (auto num : v) {
cout << num << " ";
}
return 0;
}
对于实例二,我们可以接收erase函数的返回值(erase函数返回删除元素的后一个元素的新位置),并且控制代码的逻辑:当元素被删除后继续判断该位置的元素(因为该位置的元素已经更新,需要再次判断)。

关键点:
-
使用
erase
的返回值更新迭代器 -
只有不删除元素时才手动递增迭代器
-
确保每次循环都正确处理迭代器状态
4、其他可能导致迭代器失效的操作
-
push_back/emplace_back:当导致vector容量改变时,所有迭代器都会失效
-
resize/reserve:可能改变vector的存储位置
-
assign:替换所有元素,使所有迭代器失效
-
clear:清空容器,使所有迭代器失效
5、最佳实践建议
-
在修改容器后,总是假定所有迭代器都可能失效
-
尽量在修改操作后重新获取迭代器
-
对于删除操作,使用
erase
的返回值更新迭代器 -
考虑使用索引而非迭代器进行遍历和修改(在某些简单场景下)
-
在C++11及以后版本,可以考虑使用基于范围的for循环,但注意不能在循环内修改容器结构
6、不同平台对迭代器失效的处理差异
在Linux环境下,g++编译器对迭代器失效的检测相对宽松,处理方式没有Visual Studio那么严格。这意味着在某些迭代器失效的情况下,程序可能不会立即崩溃,但会导致未定义行为和错误的结果。
记住:
预防迭代器失效的关键在于理解容器操作对内存布局的影响,并在每次修改操作后谨慎处理迭代器。