《C++初阶之STL》【vector容器:详解 + 实现】
【vector容器:详解 + 实现】目录
- 前言:
- ------------标准接口介绍------------
- 标准模板库中的vector容器是什么样的呢?
-
- 1. 常见构造
- 2. 容量操作
-
- std::vector::size
- std::vector::capacity
- std::vector::empty
- std::vector::resize
- std::vector::reserve
- 3. 访问操作
-
- std::vector::operator[]
- 4. 修改操作
-
- std::vector::push_back
- std::vector::pop_back
- std::vector::insert
- std::vector::erase
- std::vector::swap
- std::vector::clear
- ------------模拟实现展示------------
-
- vector的存储结构的设计
- 头文件:vector.h
- 测试文件:Test.cpp
- 运行结果:
- ----------------
- 1. 为什么vector容器的功能实现都是在头文件中?
- 2. 为什么普通类可以分离实现,而模板不行?
- ------------核心问题深究------------
- 一、迭代器失效问题
-
- 1. 什么是迭代器失效?
- 2. vector容器中哪些操作会导致迭代器失效?
- 3. 案例一:容量改变
- 4. 案例二:删除操作
- 5. 案例三:小测试
- 二、memcpy浅拷贝问题
- 三、二维vector的存储问题
- ------------代码片段剖析------------
- 片段一:=default默认构造函数
-
- 1. 什么是默认构造函数?
- 2. 为什么需要显式使用 = default?
- 片段二:迭代器范围构造函数
-
- 1. 什么是迭代器范围构造函数?
- 2. 迭代器范围构造函数怎么使用?
- 片段三:填充构造函数
-
- 1. 什么是填充构造函数?
- 2. 内置类型的变量是怎么进行初始化的?
- 3. T()的含义是什么?
- 4. 内置类型和类类型使用T()的不同之处是什么?
- 5. 填充构造函数怎么使用?
- 片段四:reserve()的迭代器失效
- 片段五:print_vector(v);无法打印std::vector的原因
- 片段六:二维数组的实现原理
-
- 1. 二维vector的访问机制是什么?
- 2. 二维vector和原生二维数组的区别?
往期《C++初阶》回顾:
/------------ 入门基础 ------------/
【C++的前世今生】
【命名空间 + 输入&输出 + 缺省参数 + 函数重载】
【普通引用 + 常量引用 + 内联函数 + nullptr】
/------------ 类和对象 ------------/
【类 + 类域 + 访问限定符 + 对象的大小 + this指针】
【类的六大默认成员函数】
【初始化列表 + 自定义类型转换 + static成员】
【友元 + 内部类 + 匿名对象】
【经典案例:日期类】
/------------ 内存管理 ------------/
【内存分布 + operator new/delete + 定位new】
/------------ STL ------------/
【泛型编程 + STL简介】
【auto关键字 + 范围for循环 + 迭代器】
【string类:详解 + 实现】
前言:
hi~ 小伙伴们大家好呀 (◍・ᴗ・◍) ゝ !偷偷告诉你们一个小秘密✨,今天可是超特别的 “闰六月” 哦~ 先给大家简单科普一下什么是 “闰六月” 吧~
“闰六月”
:是农历(阴历)中的一种置闰现象🌙,专门用来协调农历与回归年(地球绕太阳公转一周的时间)的差异而设置的呢~
简单一点说: “闰六月” 就是指在农历六月之后再增加一个六月哦~(≧▽≦) 这个增加的月份就称为 “闰六月”,这一年的农历就会有 13 个月啦,其中会有两个六月(前一个是正六月,后一个就是闰六月啦~)
这种情况可不常见哦~ 间隔时间也不固定,可能几年到几十年才会遇到一次呢
~⌛
好了好了 (≧∇≦)ノ 我们开始今天的学习吧~ 【vector 容器:详解 + 实现】,这篇博客将近 2w 字哦~ 内容分为两部分:vector 容器的
详解
和实现
✨温馨提示✨:实现
才是这篇博客的精髓哟~,所以希望小伙伴们一定要坚持看到最后呀~ 千万不要错过精彩内容啦 (≧▽≦)~
本来博主是想分成两篇来写的,但为了让大家能一口气学完,不影响阅读体验,就选择写成一篇啦~
咳咳………… 如果小伙伴们一口气学不完的话,点个收藏慢慢学也可以呀~(◍・ᴗ・◍) ゝ
嗯… 其实关注博主也非常推荐的呦~ 哈哈哈 (≧▽≦)
------------标准接口介绍------------
标准模板库中的vector容器是什么样的呢?
cplusplus网站上关于C++的vector容器的介绍:vector - C++ Reference
C++ 标准模板库(STL)中的
vector容器
相关知识,主要可以分为以下三个部分:
- 成员函数:提供了vector容器的各类操作接口,涵盖元素访问、添加、删除、修改、容量管理等功能
- 非成员函数重载:通过重载与vector相关的运算符和函数,实现vector与其他数据类型或容器之间的交互,以及对vector进行便捷的通用操作
- 模板特化:针对特定类型对vector模板进行定制化处理,优化性能或满足特殊需求
1. 常见构造
vector(size_type n, const value_type& val = value_type())
填充构造函数
std::vector v2(5, 10);
std::vector v3(3);
vector(const vector& x)
拷贝构造函数
std::vector v4(v2);
std::vector v5 = v2;
vector(InputIterator first, InputIterator last)
迭代器范围构造函数
int arr[] = {1,2,3};
std::vector v6(arr, arr+3);
#include #include using namespace std;int main(){ // 1. 默认构造函数 vector<int> first; /* 说明: * 1. 创建一个空的int类型vector容器 * 2. 此时容器中没有任何元素,size为0,capacity可能为0 */ // 2. 填充构造函数 vector<int> second(4, 100); /* 说明: * 1. 创建一个包含4个元素的vector,每个元素初始值为100 * 2. 构造后容器size为4,所有元素都是100 */ // 3. 迭代器范围构造函数 vector<int> third(second.begin(), second.end()); /* 说明: * 1. 通过另一个容器的迭代器范围来构造 * 2. 这里使用second容器的begin()和end()迭代器,构造一个与second元素相同的vector * 3. 本质是将second中的所有元素逐个拷贝到third中 */ // 4. 拷贝构造函数 vector<int> fourth(third); /* 说明: * 1. 通过拷贝另一个vector来构造 * 2. 这里直接拷贝third容器,构造出一个完全相同的新容器fourth */ // 5. 数组构造: int myints[] = { 16, 2, 77, 29 }; vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int)); /* 说明:通过数组的指针范围来构造vector * 1. 先定义一个int数组 * 2. 计算数组元素个数:总字节数 / 单个元素字节数 * 3. 然后使用数组的起始地址和结束地址(myints + 4)来构造fifth */ // 使用迭代器遍历fifth容器并输出fifth容器中的内容 cout << \"fifth容器中的内容:\"; //1)vector::iterator是vector的迭代器类型,用于访问容器元素 //2)begin()返回指向第一个元素的迭代器,end()返回指向最后一个元素后一位的迭代器 for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it) { //3)*it获取迭代器指向的元素值 cout << \' \' << *it; } cout << \'\\n\'; return 0;}
2. 容量操作
即:能够容纳元素的最大数量
若其中没有存储任何元素则返回 true,否则返回 false
当
增加
元素时,可指定新元素的默认值当
减少
元素时,多余元素会被截断即:提前分配一定数量的存储空间,以避免在后续插入元素时频繁进行内存重新分配和数据拷贝操作
std::vector::size
// vector::size#include #include using namespace std; int main(){ //1. 创建一个空的 vector 容器,用于存储 int 类型的数据 vector<int> myints; cout << \"0. size: \" << myints.size() << \'\\n\'; //2. 循环 10 次,每次向 myints 的末尾添加一个元素 for (int i = 0; i < 10; i++) { myints.push_back(i); } cout << \"1. size: \" << myints.size() << \'\\n\'; //3. insert 成员函数用于在指定位置插入元素 myints.insert(myints.end(), 10, 100); //注意:这里是在 myints 的末尾(myints.end() 指向的位置)插入 10 个值为 100 的元素 cout << \"2. size: \" << myints.size() << \'\\n\'; //4. pop_back 成员函数用于移除 vector 末尾的一个元素 myints.pop_back(); cout << \"3. size: \" << myints.size() << \'\\n\'; return 0;}
std::vector::capacity
#include #include using namespace std;int main(){ //1.创建一个空的int类型vector容器 vector<int> myvector; //2.向vector中添加100个元素(值从0到99) for (int i = 0; i < 100; i++) { myvector.push_back(i); // push_back()会在容器末尾插入元素,当容量不足时会自动扩容 } //3.1:size():返回当前容器中实际存储的元素个数 cout << \"size: \" << (int)myvector.size() << \'\\n\'; //注:这里添加了100个元素,所以size为100 //3.2:capacity():返回当前容器在不重新分配内存的情况下,最多能容纳的元素个数 cout << \"capacity: \" << (int)myvector.capacity() << \'\\n\'; /* 说明: * 1. 由于vector的扩容策略(通常是翻倍增长),capacity会大于或等于size * 2. 此处添加100个元素后,capacity可能是128(取决于具体实现) */ return 0;}
std::vector::empty
#include #include using namespace std;int main(){ //1.创建一个空的int类型vector容器 vector<int> myvector; //2.用于存储容器元素的总和 int sum(0); //3.向vector中添加10个元素,值从1到10 for (int i = 1; i <= 10; i++) { myvector.push_back(i); } //4.empty()方法用于判断容器是否为空,返回true表示空,false表示非空 while (!myvector.empty()) //循环条件:当容器不为空时继续执行 { //4.1:back()方法返回容器中最后一个元素的引用 sum += myvector.back(); //4.2:pop_back()方法移除容器中最后一个元素(不会返回元素值) myvector.pop_back(); } //5.输出所有元素的总和 cout << \"total: \" << sum << \'\\n\'; return 0;}
std::vector::resize
#include #include using namespace std;int main(){ //1.创建一个存储int类型的vector容器 vector<int> myvector; //2.向vector中添加初始元素:1到9 for (int i = 1; i < 10; i++) { myvector.push_back(i); } //3.调整vector大小为5,超出部分的元素会被删除 myvector.resize(5); //4.调整vector大小为8,新增的3个元素使用100填充 myvector.resize(8, 100); //5.调整vector大小为12,新增的4个元素使用默认值0填充(int类型的默认值) myvector.resize(12); //6.输出vector中的所有元素 cout << \"myvector容器中内容为:\"; for (int i = 0; i < myvector.size(); i++) { cout << \' \' << myvector[i]; } cout << \'\\n\'; return 0;}
std::vector::reserve
#include #include using namespace std;int main(){ //1.定义一个size_type类型的变量,用于存储容器的容量 vector<int>::size_type sz; /*-------------------------情况一:未进行reserve()-------------------------*/ cout << \"-------未进行reserve()-------\" << endl; //2.创建第一个vector容器foo vector<int> foo; //3.获取foo的初始容量(此时为空,容量通常为0) sz = foo.capacity(); cout << \"making foo grow:\\n\"; //4.向foo中添加100个元素(0到99) for (int i = 0; i < 100; ++i) { //4.1:向vector容器尾插入元素 foo.push_back(i); //4.1:检容量是否发生变化 ---> 如果发生了变化 if (sz != foo.capacity()) { //4.2.1:更新容量值 sz = foo.capacity(); //4.2.2:输出新的容量 cout << \"capacity changed: \" << sz << \'\\n\'; } } /* 说明: * 1. foo没有预先分配空间,每次容量不足时会自动扩容 * 2. 通常vector的扩容策略是翻倍增长(不同编译器可能有差异) */ /*-------------------------情况二:进行reserve()-------------------------*/ cout << \"\\n-------进行reserve()-------\" << endl; //5.创建第二个vector容器bar vector<int> bar; //6.获取bar的初始容量 sz = bar.capacity(); cout << \"making bar grow:\\n\"; //7.预先为bar分配至少能容纳100个元素的空间 bar.reserve(100); //8.向bar中添加100个元素(0到99) for (int i = 0; i < 100; ++i) { //8.1:向vector容器尾插入元素 bar.push_back(i); //8.1:检容量是否发生变化 ---> 如果发生了变化 if (sz != bar.capacity()) { //8.2.1:更新容量值 sz = bar.capacity(); //8.2.2:输出新的容量 cout << \"capacity changed: \" << sz << \'\\n\'; } } /* 说明: * 1. bar预先分配了足够的空间,添加100个元素过程中不会发生容量变化 */ return 0;}
3. 访问操作
提供高效的随机访问方式
std::vector::operator[]
#include #include using namespace std;int main(){ //1.创建一个包含10个int元素的vector,所有元素初始化为0 vector<int> myvector(10); //2.获取vector的大小(元素数量) vector<int>::size_type sz = myvector.size(); //3.使用operator[]访问并修改vector中的元素 for (unsigned i = 0; i < sz; i++) { myvector[i] = i; } //4.使用operator[]反转vector中的元素 for (unsigned i = 0; i < sz / 2; i++) //只需要循环到vector的中间位置即可完成反转 { //4.1:定义临时变量,用于交换元素 int temp; //4.2:通过索引访问要交换的两个元素(对称位置) temp = myvector[sz - 1 - i]; // 获取右侧元素 myvector[sz - 1 - i] = myvector[i]; // 将左侧元素放到右侧位置 myvector[i] = temp; // 将右侧元素放到左侧位置 } //5.使用operator[]访问元素并输出vector中的元素 cout << \"myvector容器中的内容为:\"; for (unsigned i = 0; i < sz; i++) { cout << \' \' << myvector[i]; } cout << \'\\n\'; return 0;}
4. 修改操作
之前
插入一个值为 val 的元素,插入后原位置及之后元素后移,可能触发扩容std::vector::push_back
#include #include using namespace std;int main(){ //1.创建一个存储int类型的空vector容器 vector<int> myvector; //2.定义一个用于临时存储用户输入的整数 int myint; cout << \"请输入一些整数(输入 0 结束):\\n\"; //3.循环读取用户输入并添加到vector中 do { cin >> myint; // 从标准输入读取一个整数 myvector.push_back(myint); // 将读取的整数添加到vector的末尾 } while (myint); // 当输入的整数为0时,结束循环 //4.输出vector中存储的整数数量 cout << \"myvector容器中存储了 \" << int(myvector.size()) << \" 个数字.\\n\"; //注:size()返回元素个数,这里转换为int类型以便输出 return 0;}
std::vector::pop_back
#include #include using namespace std;int main(){ //1.创建一个存储int类型的vector容器 vector<int> myvector; //2.定义用于存储vector中所有元素总和的变量 int sum(0); //3.向vector中添加三个元素 myvector.push_back(100); myvector.push_back(200); myvector.push_back(300); //4.循环:当vector不为空时继续执行 while (!myvector.empty()) { //4.1:取出vector的最后一个元素并加到sum中 sum += myvector.back(); //4.2:移除vector的最后一个元素 myvector.pop_back(); } //5.输出所有元素的总和 cout << \"myvector 中所有元素的和为\" << sum << \'\\n\'; return 0;}
std::vector::insert
#include #include using namespace std;int main(){ //1.创建一个包含3个元素的vector,每个元素初始值为100 vector<int> myvector(3, 100); //2.定义一个迭代器 vector<int>::iterator it; //3.1:将迭代器指向vector的起始位置(第一个元素) it = myvector.begin(); //3.2:在迭代器it指向的位置插入元素200 it = myvector.insert(it, 200); //注:插入后迭代器自动指向新插入的元素 //4.在当前it指向的位置(即第一个元素位置)插入2个300 myvector.insert(it, 2, 300); it = myvector.begin(); /* 注意: * 1. 经过上面的插入操作,之前的it可能已经失效 * 2. 重新获取迭代器,指向vector的起始位置 */ //5.1:创建另一个vector,包含2个元素,每个元素为400 vector<int> anothervector(2, 400); //5.2:在it+2的位置(第三个元素位置)插入anothervector的所有元素 myvector.insert(it + 2, anothervector.begin(), anothervector.end()); //6.1:定义一个数组,包含3个元素 int myarray[] = { 501, 502, 503 }; //6.2:在vector的起始位置插入数组中的所有元素 myvector.insert(myvector.begin(), myarray, myarray + 3); //7.输出vector中的所有元素 cout << \"myvector 包含:\"; for (it = myvector.begin(); it < myvector.end(); it++) { cout << \' \' << *it; } cout << \'\\n\'; return 0;}
std::vector::erase
#include #include using namespace std;int main(){ //1.创建一个存储int类型的vector容器 vector<int> myvector; //2.向vector中添加元素(1到10) for (int i = 1; i <= 10; i++) { myvector.push_back(i); } //3.删除第6个元素 myvector.erase(myvector.begin() + 5); /* 说明: * 1. begin()返回指向第一个元素的迭代器,+5表示向后移动5个位置 * 2. 删除后myvector包含:1, 2, 3, 4, 5, 7, 8, 9, 10 */ //4.删除从第一个元素开始的前3个元素 myvector.erase(myvector.begin(), myvector.begin() + 3); /* 说明: * 1. 范围是[begin(), begin()+3),即包含begin()但不包含begin()+3 * 2. 删除后myvector包含:4, 5, 7, 8, 9, 10 */ //5.输出vector中的所有元素 cout << \"myvector中包含:\"; for (unsigned i = 0; i < myvector.size(); ++i) { cout << \' \' << myvector[i]; } cout << \'\\n\'; return 0;}
std::vector::swap
#include #include using namespace std;int main(){ //1.创建第一个vector:包含3个元素,每个元素的值都是100 vector<int> foo(3, 100); //2.创建第二个vector:包含5个元素,每个元素的值都是200 vector<int> bar(5, 200); //3.交换foo和bar两个vector的内容 foo.swap(bar); //4.输出交换后的foo cout << \"foo 包含:\"; for (unsigned i = 0; i < foo.size(); i++) { cout << \' \' << foo[i]; } cout << \'\\n\'; //5.输出交换后的bar cout << \"bar 包含:\"; for (unsigned i = 0; i < bar.size(); i++) { cout << \' \' << bar[i]; } cout << \'\\n\'; return 0;}
std::vector::clear
#include #include using namespace std;int main(){ //1.创建一个存储int类型的vector容器 vector<int> myvector; //2.向vector中添加三个元素 myvector.push_back(100); myvector.push_back(200); myvector.push_back(300); //3.输出clear前vector中的元素 cout << \"myvector 包含:\"; for (unsigned i = 0; i < myvector.size(); i++) { cout << \' \' << myvector[i]; } cout << \'\\n\'; //4.清空vector中的所有元素 myvector.clear(); // 注意:clear()会删除所有元素,使size()变为0,但不会改变capacity() //5.清空后添加新的元素 myvector.push_back(1101); myvector.push_back(2202); //6.输出clear后vector中的元素 cout << \"myvector 包含:\"; for (unsigned i = 0; i < myvector.size(); i++) { cout << \' \' << myvector[i]; } cout << \'\\n\'; return 0;}
------------模拟实现展示------------
vector的存储结构的设计
C++ 里
vector
容器的存储结构设计,围绕动态数组核心,通过巧妙的指针(迭代器)管理、扩容策略,在性能与灵活性间找平衡。
一、核心结构:三指针(迭代器)管理
vector
内部靠start
、finish
、end_of_storage
三个关键标记,划分内存区域:
start
:指向数据区起始位置,是首个元素的 “入口”,类似数组名(但更灵活,支持动态调整)
finish
:指向当前最后一个元素的下一个位置,finish - start
就是size()
(实际存储元素数量)
end_of_storage
:指向已分配内存的末尾位置,end_of_storage - start
对应capacity()
(总可用容量,含未用 “备用空间”)总结:这三指针,让 vector 清晰区分
“已存数据”
和“备用空间”
,为动态扩容、高效访问铺路。
二、动态扩容:空间预分配与 “倍增” 策略
“新增元素超容量时,容量扩充至两倍(或足够大)”,这是
vector
性能优化的关键:
预分配备用空间
:创建 vector 或扩容时,不只为当前元素分配内存,会多留 “备用空间”。
- 比如:初始
vector iv(2,9)
,后续push_back
没立刻扩容,因为预分配了空间
“倍增” 扩容
:默认扩容策略是容量翻倍(若翻倍不够,直接扩到所需大小)。
- 这么做是为了减少扩容次数—— 每次扩容要 “重新分配内存 → 搬移元素 → 释放原内存”,成本极高,翻倍扩容能降低 “搬移频率”,平衡时间开销。
注意:扩容不是简单 “在原内存后追加”,而是要:
新分配更大内存(通常是原容量 2 倍)
把原数据拷贝/搬移到新内存
释放原内存
这过程涉及大量元素复制,所以频繁扩容会拖慢程序,需结合业务场景合理用
reserve
预分配空间。
三、内存连续性:模拟数组的高效访问
vector 数据在内存里连续存储,和数组一样:
- 随机访问 O ( 1 ) O(1) O(1):通过
[]
(或at()
)访问元素,直接用start + 索引
计算地址,效率和数组一致。- 缓存友好:连续内存让 CPU 缓存更 “友好”—— 预读机制能批量加载相邻元素,减少内存访问次数,遍历、运算时更快。
这种设计,让 vector 既有数组的高效,又摆脱了 “固定大小” 的束缚。
四、设计权衡:灵活与成本的平衡
vector
的结构设计,本质是“空间换时间”
的权衡:
- 优势:动态扩容 + 连续存储,让它兼具 “动态数组” 的灵活(随时增删元素)和 “数组” 的高效(随机访问、缓存友好)
- 劣势:扩容时的 “搬移操作” 有性能损耗,且若 vector 存大对象(或大量元素),频繁扩容可能导致内存碎片、拖慢程序
所以实际用 vector,常结合这些技巧:
- 用 reserve(n) 提前分配足够空间,
避免多次扩容
- 若元素是复杂对象,优先用移动语义(C++11+)
减少搬移成本
总结来说:
vector
的存储结构设计,围绕动态数组的 “灵活扩容” 与 “高效访问”
展开:用三指针管理内存区域,靠 “预分配 + 倍增扩容” 减少性能损耗,借内存连续性实现数组级访问效率。
理解这些,才能在写代码时避开 “频繁扩容坑”,让
vector
发挥最大价值~
/*==========================================任务2.1:实现类模板vector==========================================*/template <class T>class vector{public:/*-------------------------------第一部分:类型定义-------------------------------*///1.定义“正向迭代器”(原始指针,支持随机访问)typedef T* iterator;//2.定义“常量的正向迭代器”(指向常量的指针,禁止通过迭代器修改元素)typedef const T* const_iterator;/*-------------------------------第二部分:迭代器接口-------------------------------*//*-------------------------------第三部分:构造and析构默认函数-------------------------------*//*-------------------------------第四部分:容量管理操作-------------------------------*//*-------------------------------第五部分:元素访问操作-------------------------------*//*-------------------------------第六部分:元素修改操作-------------------------------*/private:/*-----------------------私有成员:三指针结构-----------------------*/iterator _start;//指向数据起始位置iterator _finish;//指向有效数据的下一个位置iterator _end_of_storage;//指向容量末尾的下一个位置};
头文件:vector.h
#pragma once/*==========================================任务1:包含需要使用的头文件==========================================*/#include #include #include #include using namespace std;/*==========================================任务2:定义mySpace自定义命名空间==========================================*///任务2:定义mySpace自定义命名空间// 任务2.1:实现类模板:vector// 任务2.2:实现函数模板:print_vector// 任务2.3:实现函数模板:print_containernamespace mySpace{/*==========================================任务2.1:实现类模板vector==========================================*/template <class T>class vector{public:/*-------------------------------第一部分:类型定义-------------------------------*///1.定义“正向迭代器”(原始指针,支持随机访问)typedef T* iterator;//2.定义“常量的正向迭代器”(指向常量的指针,禁止通过迭代器修改元素)typedef const T* const_iterator;/*-------------------------------第二部分:迭代器接口-------------------------------*///1.实现:获取正向迭代器的首部iterator begin(){return _start;}//2.实现:获取正向迭代器的尾部iterator end(){return _finish;}//3.实现:获取常量正向迭代器的首部const_iterator begin()const //注意:在C++11中使用的是cbegin()进行返回:常量正向迭代器,我们这里还是直接使用begin来返回常量正向迭代器{return _start;}//4.实现:获取常量迭代器的尾部const_iterator end()const{return _finish;}/*-------------------------------第三部分:构造and析构默认函数-------------------------------*///1.实现:C++11默认构造函数vector() = default; //自动生成无参构造,初始化指针为nullptr//2.实现:拷贝构造函数vector(const vector<T>& v) //深拷贝另一个vector的内容{//注意:vector是模板实例化后的具体“类类型”//1.先预留目标容量,避免多次扩容reserve(v.size());//2.再遍历原vector容器,逐个将容器中的元素插入for (auto& it : v){push_back(it); //调用push_back实现元素拷贝(支持自定义类型拷贝)}}//3.实现:迭代器范围构造函数//template //通过任意输入迭代器范围初始化vector//vector(InputIterator first, InputIterator last) //注意:类模板的成员函数,还可以继续是函数模版//{////遍历迭代器的范围逐个插入元素//while (first != last)//{////1.插入//push_back(*first);////2.移动//++first;//}//}//4.实现:填充构造函数(size_t版本)vector(size_t n, const T& val = T()) //创建n个值为val的元素{//1.先预留目标容量,避免多次扩容reserve(n);//2.再使用for循环,连续插入n个valfor (size_t i = 0; i < n; i++){push_back(val);}}//5.实现:填充构造函数(int版本)//vector(int n, const T& val = T()) //创建n个值为val的元素//{////1.先预留目标容量,避免多次扩容//reserve(n);////2.再使用for循环,连续插入n个val//for (int i = 0; i < n; i++)//{//push_back(val);//}//}/*//6.1.实现:“拷贝赋值运算符重载函数”(传统写法:引用传递+赋值)vector& operator=(const vector& v) //注意:这里的vector的类类型是vecotor,而并不是vector{//这是因为:vector不是普通的类,而是类模板//1.先判断是否对自身进行赋值操作if (this != &v){//情况1:传入的容器的容量比较“小”//2.将当前的容器的中的内容清空 ---> 应对“小”容量的情况clear();//情况2:传入的容器的容量比较“大”//3.使用reserve开辟指定容量的空间 ---> 应对“大”容量的情况reserve(v.size());//4.处理了上面的情况之后我们才可以进行赋值for (auto& it : v){push_back(it);}}return *this;}*///6.2.实现:拷贝赋值运算符重载函数(现代写法:值传递+交换)//vector& operator=(vector v) //注意:参数为值传递,自动触发拷贝构造//{////1.交换当前对象与临时对象v的数据//swap(v);////2.返回当前对象(临时对象v离开作用域后自动析构)//return *this;//}vector<T>& operator=(const vector<T>& v) {//1.先判断是否是对自身进行的赋值操作if (this != &v) {//2.再使用拷贝构造出临时对象vector<T> temp(v); //3.然后进行交换swap(temp); }//4.最后返回自身对象即可return *this;}//7.实现:析构函数~vector(){//1.确保指针非空if (_start){//2.释放_start指针指向的资源delete[] _start;//2.将三指针置为空指针_start = _finish = _end_of_storage = nullptr;}}/*-------------------------------第四部分:容量管理操作-------------------------------*///1.实现:“获取有效元素的个数的函数”size_t size() const{return _finish - _start;}//2.实现:“获取容器的总的容量的函数”size_t capacity()const{return _end_of_storage - _start;}//3.实现:“判断容器是否为空的函数”bool empty() const{return _start == _finish; //起始指针与结束指针重合}//4.实现:“预留容量的函数”void reserve(size_t n) //预留至少n个元素的容量(不改变有效元素个数){//注意:预留容量的操作只有在预留的容量n 大于 原vector容器的容量时候才会进行操作if (n > capacity()){/*---------第一步:保存原有元素的个数---------*/size_t old_size = size();/*---------第二步:分配新内存---------*/T* tmp = new T[n];/*---------第三步:拷贝旧数据---------*///注意:使用for循环支持自定义类型的拷贝构造for (size_t i = 0; i < size(); i++){tmp[i] = _start[i]; //调用T的拷贝赋值运算符}/*---------第四步:释放旧内存---------*/delete[] _start;/*---------第五步:更新指针成员---------*///方法一:使用一个变量来记录之前vector中的元素的数量(三个迭代器的更新顺序没有要求)_start = tmp;//_finish = _start + size(); //失败//_finish = _start + old_size; //成功//_finish = tmp + size(); //失败_finish = tmp + old_size; //成功_end_of_storage = tmp + n;/* 方法二:也可以不记录原vector中元素的数量来避免迭代器失效的问题,但是必须确保:_finish 的更新一定要在 _start 之前_finish = tmp + size();_start = tmp;_end_of_storage = tmp + n; */}}//5.实现:“调整有效元素的数量的函数”void resize(size_t n, T val = T()) //调整有效元素个数为n,不足时用val填充,多余时截断{//情景1:截断场景if (n < size()){//1.1:直接缩短有效长度为n_finish = _start + n;}//情景2:扩容场景else{//2.1:直接开辟最大容量为nreserve(n);//2.2:填充新元素while (_finish < _start + n){//1.赋值*_finish = val; //可能调用拷贝赋值//2.移动++_finish;}}//注意:这里的resize和reserve的区别://reserve:对于每种调用情况并不是都一定会执行 (只有指定的扩容容量n大于当前vector容量是才会进行扩容)//resize:对于每种调用情况都会执行}/*-------------------------------第五部分:元素访问操作-------------------------------*///1.实现:“下标访问运算符重载函数”(可读可写)T& operator[](size_t pos){//1.使用断言:检查下标是否越界assert(pos < size());//2.通过指针偏移访问return *(_start + pos);//return _start[pos];}//2.实现:“下标访问运算符重载函数”(只读)const T& operator[](size_t pos)const{//1.使用断言:检查下标是否越界assert(pos < size());//2.通过指针偏移访问return *(_start + pos);//return _start[pos];}/*-------------------------------第六部分:元素修改操作-------------------------------*///1.实现:“清空vector容器的函数”void clear() //逻辑上清空,容量不变{_start = _finish;}//2.实现:“交换vector容器元素的函数”void swap(vector<T>& v) //对于自定义对象实现高效赋值{//使用标准库中的swap交换vector的三个私有的指针成员std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}//3.实现:“尾插元素到vector容器的函数”void push_back(const T& x){//1.检查vector是否需要进行扩容if (_finish == _end_of_storage){//扩容策略://1.vector容器的容量为0时:为vector容器分配4字节的容量//2.vector容器的容量不为0时:为vector容器分匹配原容量的2倍容量//1.1:进行扩容reserve(capacity() == 0 ? 4 : 2 * capacity());//注意:push_back这里的扩容的大小是使用一个三目运算计算出来的//而并不像我们在operator=和resize中的直接使用reserve(v.size())或reserve(n)就可以了}//2.将新元素直接插入到vector容器的尾部*_finish = x; //直接赋值,要求T支持拷贝赋值//3.有效指针后移++_finish;}//4.实现:“尾删vector容器中元素的函数”void pop_back(){//1.使用断言:确保vector容器非空容器assert(!empty());//2.有效指针前移--_finish;}//5.实现:“在pos位置插入元素的函数”(返回新元素的迭代器)iterator insert(iterator pos, const T& x){//注意:这里我们可以对比我们在实现string类中的insert函数时候传入的pos的类型,一个是size_t,一个是iterator//1.使用断言:确保迭代器的有效性assert(pos >= _start && pos <= _finish); //注意:取等号的时候类似于“头插”“尾插”//2.记录插入位置相对于起始的偏移量size_t len = pos - _start;//3.检查vector是否需要进行扩容if (_finish == _end_of_storage){//扩容策略://1.vector容器的容量为0时:为vector容器分配4字节的容量//2.vector容器的容量不为0时:为vector容器分匹配原容量的2倍容量//3.1:进行扩容reserve(capacity() == 0 ? 4 : 2 * capacity());//3.2:更新pos指针pos = _start + len;}//4.从末尾到pos位置的元素都向后挪动一位//4.1:定义一个迭代器指向我们需要挪动的元素中的最后一元素iterator end = _finish - 1; //注意:回想一下我们string中实现的insert函数中的定义的end的类型是size_t,但是这里使用是迭代器while (end >= pos){//4.2:赋值后移//_start[end + 1] = _start[end]; 注意:这里不要使用[],因为我们重载[]运算符时候,使用了断言:assert(pos < size());*(end+1)=*end; //这就意味着我们使用[]时候,必须是[0~size-1]//4.3:end迭代器前移--end;}//5.插入元素//_start[pos] = x; //这里也是和上面一样的情况*(pos)=x;//6.有效元素个数增加++_finish;//7.返回新元素的迭代器return pos;}//6.实现:“删除pos位置元素的函数”void erase(iterator pos){//1.使用断言:确保迭代器的有效性assert(pos >= _start && pos < _finish);//注意:这里pos迭代器不能等于_finish,//这是因为_finish指向的其实是vecotr中最后一元素的下一个位置,所以我们不能删除_finish指向的值//2.从pos+1到末尾的元素都向前挪动一位//2.1:定义一个迭代器指向我们需要挪动的元素中的第一个元素iterator begin = pos + 1;while (begin != end()){//2.2:赋值交换_start[begin - 1] = _start[begin];//*(begin-1)=*(begin);//2.3:begin迭代器后移++begin;}//3.有效元素个数减少--_finish;}private:/*-----------------------私有成员:三指针结构-----------------------*/iterator _start;//指向数据起始位置iterator _finish;//指向有效数据的下一个位置iterator _end_of_storage;//指向容量末尾的下一个位置};/*==========================================任务2.2:实现函数模板:print_vector==========================================*///1.不使用模板实现print_vector函数void print_vector(const vector<int>& v){//注意:下面我们可以使用两种不同的方式实现vector容器的遍历/*----------------方法一:使用迭代器遍历----------------*///1.定义vector容器的迭代器//vector::const_iterator it = v.begin();auto it = v.begin(); //注意:也可写成这个形式,其实范围for中的auto it本质上就是这行代码(所以:范围for的使用依赖迭代器)//2.使用while循环进行迭代遍历vector容器while (it != v.end()){//1.输出遍历到的元素std::cout << *it << \" \";//2.让迭代器向后移动++it;}std::cout << std::endl;/*----------------方法二:使用范围for循环遍历----------------*/for (auto it : v){std::cout << it << \" \";}std::cout << std::endl;}//2.使用模板实现print_vector函数template<class T>void print_vector(const vector<T>& v){//注意:下面我们可以使用两种不同的方式实现vector容器的遍历/*//----------------方法一:使用迭代器遍历----------------//1.定义vector容器的迭代器//vector::const_iterator it = v.begin();auto it = v.begin();//2.使用while循环进行迭代遍历vector容器while (it != v.end()){//1.输出遍历到的元素std::cout << *it << \" \";//2.让迭代器向后移动++it;}std::cout << std::endl;*//*----------------方法二:使用范围for循环遍历----------------*/for (auto it : v){std::cout << it << \" \";}std::cout << std::endl;}/*==========================================任务2.3:实现函数模板:print_container==========================================*/template<class container>void print_container(const container& con){/*//----------------方法一:使用迭代器遍历----------------auto it = con.begin();while (it != con.end()){//1.输出cout << *it << \" \";//2.移动++it;}cout << endl;*//*----------------方法二:使用范围for循环遍历----------------*/for (auto it : con){std::cout << it << \" \";}std::cout << std::endl;}}
测试文件:Test.cpp
#include\"vector.h\" namespace mySpace{void test_vector01(){//1.vector<int> v;//2.尾插元素v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);/*------------------测试:三种遍历方式------------------*/cout << \"==========测试:vector的遍历方式==========\" << endl;//3.1:使用“下标”进行访问cout << \"使用“下标”进行访问\" << endl;for (size_t i = 0; i < v.size(); i++){cout << v[i] << \" \";}cout << endl;//3.2:使用“迭代器”进行访问cout << \"使用“迭代器”进行访问\" << endl;vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << \" \";++it;}cout << endl;//3.3:使用“范围for”进行访问cout << \"使用“范围for”进行访问\" << endl;for (auto e : v){cout << e << \" \";}cout << endl;//4.调用模板函数打印cout << \"调用模板函数打印\" << endl;print_vector(v);//测试存储double类型cout << \"==========测试:存储double类型==========\" << endl;vector<double> vd;vd.push_back(1.1);vd.push_back(2.1);vd.push_back(3.1);vd.push_back(4.1);vd.push_back(5.1);print_vector(vd);}void test_vector02(){cout << \"==========测试:查找元素的操作==========\" << endl;std::vector<int> v; // 测试标准库vector与自定义vector的兼容性v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << \"vector中原始的元素为:\" << endl;//print_vector(v); 错误,无法打印出vector容器v中的元素print_container(v);/*------------------测试:查找元素的操作------------------*/cout << \"请输入要查找的元素>:\";//1.输入要查找的元素int x;cin >> x;//2.使用标准算法find查找元素(依赖迭代器兼容性)auto p = find(v.begin(), v.end(), x);if (p != v.end()){//3.在p位置插入40,返回新元素的迭代器cout << \"在\" << x << \"元素位置的前面的插入元素40\" << endl;p = v.insert(p, 40); //注意:插入元素后迭代器p会失效,需用insert返回值更新//4.修改插入位置的下一个元素(演示迭代器算术操作)cout << \"将\" << x << \"元素+1后再*10\" << endl;(*(p + 1)) *= 10;}print_container(v);}void test_vector03(){cout << \"==========测试:删除元素的操作==========\" << endl;std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);cout << \"vector中原始的元素为:\" << endl;print_container(v);/*------------------测试:删除元素的操作------------------*/cout << \"删除所有偶数:\" << endl;//删除所有偶数(经典erase-remove惯用法)auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it); //注意:erase返回下一个元素的迭代器}else{++it;}}print_container(v);}void test_vector04(){cout << \"==========测试:resize和reserve的区别==========\" << endl;/*------------------测试:resize和reserve的区别------------------*/vector<int> v;v.resize(10, 1); //有效元素10个,值为1,容量至少10v.reserve(20); //容量扩大到20,有效元素仍为10cout << \"vector中原始容器的:“内容/长度/容量” 为:\" << endl;print_container(v);cout << v.size() << endl; // 输出10cout << v.capacity() << endl; // 输出≥20(具体值取决于扩容策略)cout << \"调用:resize(15, 2)后:\" << endl;v.resize(15, 2); //有效元素15个,新增5个元素值为2print_container(v);cout << \"调用:resize(25, 3)后:\" << endl;v.resize(25, 3); //容量不足25,先扩容再填充print_container(v);cout << \"调用:resize(5)后:\" << endl;v.resize(5); //截断到5个元素,剩余元素被\"销毁\"(此处为简单类型,无实际销毁操作)print_container(v);}void test_vector05(){cout << \"==========测试:拷贝构造和赋值运算符==========\" << endl;cout << \"v1中原始的元素为:\" << endl;vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);print_container(v1);cout << \"v3中原始的元素为:\" << endl;vector<int> v3;v3.push_back(10);v3.push_back(20);v3.push_back(30);v3.push_back(40);v3.push_back(50);print_container(v3);/*------------------测试:拷贝构造和赋值运算符------------------*/cout << \"调用拷贝构造函数:vector v2 = v1 后,v2为:\" << endl;vector<int> v2 = v1; //调用拷贝构造函数print_container(v2);cout << \"调用拷贝赋值运算符:v1 = v3 后,v1为:\" << endl;v1 = v3; //调用拷贝赋值运算符print_container(v1);}void test_vector06(){cout << \"==========测试:各种构造函数==========\" << endl;cout << \"v1中原始的元素为:\" << endl;vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(4);v1.push_back(4);print_container(v1);/*------------------测试:各种构造函数------------------*//*--------------迭代器范围构造--------------*///cout << \"迭代器范围构造:\" << endl;////1.截取部分数据//vector v2(v1.begin(), v1.end());//print_container(v1);//print_container(v2);////2.从其他容器复制数据//list lt;//lt.push_back(10);//lt.push_back(10);//lt.push_back(10);//lt.push_back(10);//vector v3(lt.begin(), lt.end()); //注意:list迭代器是双向迭代器,支持输入迭代器操作//print_container(lt);//print_container(v3);/*--------------填充构造--------------*/cout << \"填充构造:\" << endl;cout << \"vector v4(5, \\\"1111111\\\")\" << endl;vector<string> v4(5, \"1111111\"); //填充构造:5个string类型元素,初始值为\"1111111\"print_container(v4);//不同参数类型的填充构造(int和size_t)cout << \"vector v5(10)\" << endl;vector<int> v5(10); //10个默认构造的int(值为0)print_container(v5);cout << \"vector v6(10u, 1)\" << endl;vector<int> v6(10u, 1); //10个int,值为1(size_t参数)print_container(v6);cout << \"vector v7(10, 1)\" << endl;vector<int> v7(10, 1); //同上(int参数自动转换为size_t)print_container(v7);}void test_vector07(){/*------------------测试:大容量字符串的存储与扩容------------------*/cout << \"==========测试:大容量字符串的存储与扩容==========\" << endl;vector<string> v;v.push_back(\"11111111111111111111\"); // 20个字符的字符串v.push_back(\"11111111111111111111\");v.push_back(\"11111111111111111111\");v.push_back(\"11111111111111111111\");print_container(v);cout << \"此时v的长度为:\" << v.size() << endl;cout << \"此时v的容量为:\" << v.capacity() << endl;cout << \"-----------向v中再添加一个字符串-----------\" << endl;v.push_back(\"11111111111111111111\"); // 触发扩容(假设当前容量为4,扩容后为8)print_container(v);cout << \"此时v的长度为:\" << v.size() << endl;cout << \"此时v的容量为:\" << v.capacity() << endl;}/*-----------------------测试:vector的扩容策略-----------------------*/void test_vector08(){cout << \"==========测试:vector的扩容策略==========\" << endl;/*---------------第一部分:准备阶段---------------*///1.创建一个空vector容器vector<int> v;//2.定义一个变量的用来记录容量的变化size_t sz;//3. 预分配100个元素的容量v.reserve(100); //注意:直接设置容量为100,不触发多次扩容/*---------------第二部分:初始状态输出---------------*///1.获取当前容器的初始容量sz = v.capacity();cout << \"容器的初始容量为: \" << sz << \'\\n\'; //输出:100(一次分配到位)/*---------------第三部分:不断的向容器中添加元素同时记录容量的变化---------------*/cout << \"容器的容量的变化……:\\n\";for (int i = 0; i < 100; ++i){//1.插入元素v.push_back(i);//容量足够,不会触发扩容//2.监测容器的容量的变化if (sz != v.capacity()) //容量变化时打印(此处不会执行){sz = v.capacity();cout << \"容量的变为: \" << sz << \'\\n\';}}}/*-----------------------测试:reserve函数对容量的影响(不改变有效元素个数)-----------------------*/void test_vector09(){cout << \"==========测试:reserve函数对容量的影响==========\" << endl;//TestVectorExpand(); // 注释:如需测试扩容,取消此行注释cout << \"v容器的初始状态为:\" << endl;vector<int> v(10, 1); //构造包含10个1的vector,初始容量≥10(具体由自定义vector实现决定)print_container(v);cout << \"调用v.reserve(20)之后,\" << endl;v.reserve(20);//确保容量至少20(若原容量不足则扩容)cout << \"长度:\" << v.size() << endl; //输出:10 (有效元素个数不变)cout << \"容量:\" << v.capacity() << endl; //输出:≥20(例如:自定义vector可能扩容到20或更大)cout << \"调用v.reserve(15)之后,\" << endl;v.reserve(15); //容量已≥15,无操作cout << \"长度:\" << v.size() << endl; //仍为10cout << \"容量:\" << v.capacity() << endl; //保持不变cout << \"调用v.reserve(5)之后,\" << endl;v.reserve(5); //容量≥5,无操作(reserve不会减少容量)cout << \"长度:\" << v.size() << endl; //仍为10cout << \"容量:\" << v.capacity() << endl; //保持不变(不会回退到5)}/*-----------------------测试:resize函数对元素个数和容量的影响-----------------------*/void test_vector10(){cout << \"==========测试:resize函数对元素个数和容量的影响==========\" << endl;//TestVectorExpand(); // 注释:如需测试扩容,取消此行注释cout << \"v容器的初始状态为:\" << endl;vector<int> v(10, 1); // 10个1,容量≥10print_container(v);cout << \"调用v.reserve(20)之后,\" << endl;v.reserve(20);cout << \"长度:\" << v.size() << endl;cout << \"容量:\" << v.capacity() << endl;cout << \"调用v.resize(15, 2)之后,\" << endl;v.resize(15, 2);cout << \"长度:\" << v.size() << endl;cout << \"容量:\" << v.capacity() << endl;cout << \"调用v.resize(25, 3)之后,\" << endl;v.resize(25, 3);cout << \"长度:\" << v.size() << endl;cout << \"容量:\" << v.capacity() << endl;cout << \"调用v.resize(5)之后,\" << endl;v.resize(5);cout << \"长度:\" << v.size() << endl;cout << \"容量:\" << v.capacity() << endl;}/*-----------------------测试:插入、输入输出及不同类型存储-----------------------*/void test_vector11(){cout << \"==========测试:插入、输入输出及不同类型存储==========\" << endl;cout << \"v容器的初始状态为:\" << endl;vector<int> v(10, 1);//10个1v.push_back(2);//尾插2,变为11个元素v.insert(v.begin(), 0); //在头部插入0,变为12个元素(0,1,1,...,1,2)print_container(v);cout << \"在索引为3处插入10之后的vector为:\";v.insert(v.begin() + 3, 10); //在索引3处插入10,元素后移for (auto it : v){cout << it << \" \"; //输出:0 1 1 10 1 ... 1 2(索引3变为10)}cout << endl;cout << \"请输入5个整数>:\" << endl;vector<int> v1(5, 0);for (size_t i = 0; i < 5; i++){cin >> v1[i]; //输入5个整数,覆盖默认值0}cout << \"该vector中的内容为:\" << endl;for (auto e : v1){cout << e << \",\"; // 例如输入1 2 3 4 5,输出1,2,3,4,5,}cout << endl;}/*-----------------------测试:vector存储自定义类型(如:string)及二维vector-----------------------*/void test_vector12(){cout << \"==========测试:vector存储自定义类型(如:string)及二维vector==========\" << endl;cout << \"vector中存储的自定义类型的string为:\" << endl;//1.定义一个存储string的vector容器vector<string> v1;/*------------情况1:插入字符串对象------------*///2.1:构造string对象string s1(\"xxxx\");//2.2:插入string对象(深拷贝)v1.push_back(s1);/*------------情况2:插入字符串字面量------------*///2.v1.push_back(\"yyyyy\");//3.for (const auto& it : v1) //常量引用遍历,避免拷贝大字符串{cout << it << \" \";}cout << endl;cout << \"修改二维数组的第3行第2列的元素为2\" << endl;/*-------------------第一部分:创建一个二维数组-------------------*///构造二维vector:7个元素,每个元素是vector(5,1)/*-------方法一:填充构造法-------*/std::vector<int> v(5, 1); //5个1的一维vectorstd::vector<std::vector<int>> vv(7, v); //7个v的拷贝,形成“7行5列”的二维数组/*-------方法二:嵌套构造法-------*///std::vector<std::vector> vv(7, std::vector(5, 1)); // 直接指定每行的初始值/*-------------------第二部分:修改二维数组-------------------*/vv[2][1] = 2; //修改第3行(索引2)第2列(索引1)的元素为2//等价于:vv.operator[](2).operator[](1) = 2; 注释:operator[]的嵌套调用/*-------------------第三部分:打印二维数组-------------------*///遍历二维vector并输出for (size_t i = 0; i < vv.size(); i++){for (size_t j = 0; j < vv[i].size(); ++j){cout << vv[i][j] << \" \"; //前两行全为1,第三行第二个元素为2,其余为1}cout << endl;}cout << endl;}}int main(){mySpace::test_vector01(); // 调用命名空间mySpace中的test_vector1函数mySpace::test_vector02();mySpace::test_vector03();mySpace::test_vector04();mySpace::test_vector05();mySpace::test_vector06();mySpace::test_vector07();mySpace::test_vector08();mySpace::test_vector09();mySpace::test_vector10();mySpace::test_vector11();mySpace::test_vector12();return 0;}
运行结果:
----------------
1. 为什么vector容器的功能实现都是在头文件中?
在 C++ 中:
类模板的成员函数通常需要在头文件中实现,而不是像普通类那样分离到源文件(.cpp)中。这是由 C++ 的
模板实例化机制
和编译模型
决定的。
模板实例化的工作原理:
1. 编译期生成代码
模板(如:
vector
)本身不是完整的代码,而是一个编译期蓝图。编译器在遇到模板的使用(如:
vector v;
)时,会根据实参类型(如:int
)实例化出具体的类和函数。示例:
// 模板定义(头文件中)template <typename T>T add(T a, T b){return a + b;}// 使用模板(源文件中)int sum = add(1, 2); // 编译器生成 add(int, int)
2. 实例化的必要条件
编译器在实例化模板时,必须同时看到模板的
定义
和使用
如果定义和使用分散在不同文件中,会导致编译错误
2. 为什么普通类可以分离实现,而模板不行?
一、普通类的编译模型:
对于普通类(非模板),编译器分两步处理:
编译阶段
:将每个源文件(.cpp)单独编译为目标文件(.o),此时只需要类的声明(头文件)链接阶段
:将所有目标文件链接成可执行文件,通过函数符号表解析跨文件的调用示例:
// MyClass.h(声明)class MyClass{public: void func();};// MyClass.cpp(实现)#include \"MyClass.h\"void MyClass::func() { /* ... */ }// main.cpp(使用)#include \"MyClass.h\"int main(){ MyClass obj; obj.func(); //编译时只需知道func的声明,链接时解析到MyClass.cpp中的实现}
二、模板类的编译困境:
模板的实例化发生在编译期,而非链接期,如果模板的实现放在源文件中,编译器在编译主源文件
main.cpp
时无法看到模板的实现,导致无法实例化。
假如:模板的实现(如:
vector::push_back
)、容器源文件(如:vector.cpp
)、主源文件(如:
main.cpp
)、模板的实例化(如:vector::push_back
)如果模板实现不在头文件中,编译器在:
- 编译时:
main.cpp
时只能看到模板声明(由于先经过了的预处理过程),无法生成vector::push_back
的具体代码- 链接时:链接器找不到该函数的实现,导致未定义符号错误
错误示例:
// vector.h(只包含声明)template <typename T>class vector{public: void push_back(const T& x);};// vector.cpp(实现)template <typename T>void vector<T>::push_back(const T& x){ /* ... */}// main.cpp(使用)#include \"vector.h\"int main(){ vector<int> v; v.push_back(42); //编译时无法实例化push_back,因为看不到vector.cpp中的实现}
解决方案:将模板实现放在头文件中
为了让编译器在实例化模板时能看到完整定义,模板的实现必须与声明放在同一文件中(通常是头文件)
- 此时,当
main.cpp
包含vector.h
时,经过预处理阶段之后,在编译阶段编译器能同时看到声明
和实现
,成功实例化vector::push_back
- 这虽然可能导致头文件变大,但能保证模板的通用性和编译正确性。在实际开发中,这是实现模板类的标准做法,也是大多数开源库(如:Boost、Eigen)采用的方式
正确示例:
// vector.h(声明+实现)template <typename T>class vector{public: void push_back(const T& x);};template <typename T>void vector<T>::push_back(const T& x){ // 实现代码}
------------核心问题深究------------
一、迭代器失效问题
1. 什么是迭代器失效?
迭代器失效
:是指在 C++ 中,当容器的结构发生某些变化时,原本有效的迭代器变得不再有效,无法再正确地指向容器中的元素 。
迭代器失效的原理:
迭代器本质上是一种
抽象的指针————指向容器内部元素的指针或指针封装
,用于指向容器中的元素,方便对容器元素进行遍历
和操作
容器内部通过维护一定的数据结构来存储元素,当容器执行某些操作(如:插入、删除元素,改变容量等)时,其内部数据结构可能发生改变
- 例如:内存重新分配、元素位置调整等。
这就会导致原本迭代器所指向的内存地址、元素顺序等情况发生变化,使得迭代器无法再准确指向预期元素,即:迭代器失效
总结:
内存重新分配
:容器扩容时,元素被移动到新内存,旧内存被释放元素位置移动
:插入/删除操作导致元素位置整体前移或后移
迭代器失效的表现:
访问错误:当使用失效的迭代器去访问容器元素时,程序可能会出现未定义行为。
比如:崩溃、获取到错误的数据值等 。
#include #include int main() { std::vector<int> v = {1, 2, 3}; auto it = v.begin(); v.push_back(4); // 可能导致扩容,it失效 std::cout << *it << std::endl; // 使用失效迭代器,行为未定义 return 0;}
迭代错误:在迭代过程中,如果迭代器失效,会导致迭代逻辑出错。
- 比如:在循环遍历容器时,迭代器失效后继续进行迭代操作,可能无法正确遍历完容器或者跳过某些元素等 。
2. vector容器中哪些操作会导致迭代器失效?
一、容量改变操作
1. 扩容操作
push_back()
:当size达到capacity时insert()
:插入元素导致扩容时emplace_back()
:同push_backresize()
:当新size大于当前capacity时reserve()
:当新capacity大于当前capacity时assign()
:几乎总会重新分配内存2. 缩容操作
shrink_to_fit()
:可能重新分配更小的内存空间
二、元素修改操作
1. 插入操作
insert()
:在任意位置插入元素
- 插入点之后的所有迭代器都会失效
- 如果触发扩容,则所有迭代器失效
2. 删除操作
erase()
:删除任意位置元素
- 被删除元素之后的所有迭代器都会失效
pop_back()
:删除尾部元素
- 只使指向最后一个元素的迭代器失效
clear()
:清空所有元素
- 使所有迭代器失效
3. 案例一:容量改变
#include #include using namespace std;int main(){ //1.初始化一个包含6个元素的vector,元素值为1-6 vector<int> v{ 1,2,3,4,5,6 }; //2.获取vector的起始迭代器 auto it = v.begin(); /* * 注意:以下操作都会可能导致vector扩容,使原有迭代器失效: */ /*-----------------resize操作-----------------*/ // 1. resize操作:将有效元素个数增加到100个,多出的位置用8填充 // 如果新大小大于当前容量,会触发扩容 // v.resize(100, 8); /*-----------------reserve操作-----------------*/ // 2. reserve操作:预留100个元素的空间 // 只改变容量不改变元素数量,如果100大于当前容量会重新分配内存 // v.reserve(100); /*-----------------插入操作-----------------*/ // 3. 插入操作: // 在头部插入0,可能触发扩容 // v.insert(v.begin(), 0); // 在尾部插入8,可能触发扩容 // v.push_back(8); /*-----------------assign操作-----------------*/ // 4. assign操作:重新赋值100个8 // 这会完全替换原有内容,几乎肯定会重新分配内存 v.assign(100, 8); /* * 问题分析: * 以上操作都可能导致vector扩容,导致: * 1. 原内存空间被释放 * 2. it迭代器仍指向已释放的旧内存空间 * 3. 使用失效的迭代器会导致未定义行为(通常崩溃) * * 解决方案: * 在执行可能导致扩容的操作后,必须重新获取迭代器 */ // 正确做法:在操作后重新获取迭代器 //it = v.begin(); // 使用重新获取的迭代器遍历vector while (it != v.end()) { cout << *it << \" \"; ++it; } cout << endl; return 0;}
4. 案例二:删除操作
#include #include #include using namespace std;int main(){ /*-----------1.定义一个原始数组-----------*/ int a[] = { 1, 2, 3, 4 }; /*-----------2.使用数组初始化vector-----------*/ // a: 数组首地址 // a + sizeof(a)/sizeof(int): 数组尾后地址 // sizeof(a)/sizeof(int)计算数组元素个数 vector<int> v(a, a + sizeof(a) / sizeof(int)); /*-----------3.使用STL的find算法查找值为3的元素-----------*/ // find返回一个迭代器指向找到的元素 // 如果没找到,返回end()迭代器 vector<int>::iterator pos = find(v.begin(), v.end(), 3); /*-----------4.检查是否找到元素-----------*/ if (pos != v.end()) { //4.1:删除pos位置的元素 //erase操作会使pos及其后所有迭代器失效 //但是erase会返回一个新的有效迭代器,指向被删除元素的下一个位置 //演示:删除操作erase导致的迭代器失效的情况: v.erase(pos); // 危险操作:直接使用已失效的迭代器 cout << *pos << endl; // 会导致未定义行为(通常是崩溃) //4.2:使用返回值更新迭代器 pos = v.erase(pos); //正确做法:使用返回值更新迭代器 //4.3:使用经过更新的之后的迭代器 if (pos != v.end()) { cout << \"删除后下一个元素是: \" << *pos << endl; } else { cout << \"删除的是最后一个元素\" << endl; } } else { cout << \"未找到元素3\" << endl; } return 0;}/*关键点说明:1. 迭代器失效问题: - erase操作会使被删除元素及其后所有迭代器失效 - 但erase会返回一个新的有效迭代器,指向被删元素的下一个位置2. 安全实践: - 总是使用erase的返回值更新迭代器 - 不要继续使用失效的迭代器3. 其他注意事项: - 使用范围初始化vector是C++中常见的容器初始化方式*/
5. 案例三:小测试
看到这里,相信小伙伴们已经对迭代器失效有了一定了解。
下面有两段代码,它们的功能均为删除
vector
中所有的偶数 。那么,哪段代码是正确的呢?原因是什么呢?
#include #include using namespace std;// 第一个main函数 - “错误的删除偶数元素方式”int main(){ vector<int> v{ 1, 2, 3, 4 }; //初始化vector包含1,2,3,4 auto it = v.begin(); //获取vector的起始迭代器 while (it != v.end()) //遍历整个vector { if (*it % 2 == 0) //检查当前元素是否为偶数 v.erase(it); //删除当前元素 - 危险操作!使it迭代器失效 //问题所在:在it可能已经失效的情况下仍然递增它 //如果执行了erase,it已经失效,++it是未定义行为 ++it; } return 0;}// 第二个main函数 - “正确的删除偶数元素方式”int main(){ vector<int> v{ 1, 2, 3, 4 }; //初始化vector包含1,2,3,4 auto it = v.begin(); //获取vector的起始迭代器 while (it != v.end()) //遍历整个vector { if (*it % 2 == 0) //检查当前元素是否为偶数 it = v.erase(it); //正确做法:使用erase返回的新迭代器 else ++it; //只有不删除时才正常递增迭代器 } // 验证结果 for (auto num : v) { cout << num << \" \"; //输出: 1 3 } return 0;}/*关键区别说明:1. 错误版本的问题: - erase会使当前迭代器失效 - 失效后继续使用++it会导致未定义行为(通常程序崩溃)2. 正确版本的做法: - erase会返回一个指向被删除元素下一个位置的有效迭代器 - 只有不删除元素时才手动递增迭代器 - 这样可以安全地遍历并删除元素3. 迭代器失效规则: - 对于vector,erase会使被删除位置及其后的所有迭代器失效 - 但erase会返回一个新的有效迭代器 - 其他容器(如:list)的erase可能不会使其他迭代器失效*/
二、memcpy浅拷贝问题
假设:在模拟实现的
vector
中,reserve
接口使用了memcpy
进行拷贝。以那么下面的代码会出现什么问题呢?
int main(){mySpace::vector v;v.push_back(\"1111\");v.push_back(\"2222\");v.push_back(\"3333\");return 0;}
问题分析如下:
memcpy
是内存的二进制格式拷贝,它会将一段内存空间中的内容原封不动地拷贝到另一段内存空间中
- 当拷贝的是
普通自定义类型元素
时,memcpy
既高效又不会出错。- 但如果
自定义类型元素涉及资源管理
时,使用memcpy
就会出错,因为memcpy
本质上是浅拷贝
总结:当对象涉及资源管理时,切不可使用
memcpy
来进行对象间的拷贝。因为:memcpy
执行的是
浅拷贝`操作,这可能会引发内存泄漏,甚至导致程序崩溃。
三、二维vector的存储问题
下面的动图展示了经典的杨辉三角,我们可以直观地看到这个三角的规模在不断变化。
因此,如果要存储杨辉三角,我们就要使用
二维vector
,但是二维vector又是怎么存储的呢?
mySpace::vector> vv(n);
:构造一个vv动态二维数组。
- vv中总共有n个元素
- 每个元素都是
vector类型
每行没有包含任何元素,如果n为5时如下所示:
vv中元素填充完成之后,如下图所示:
------------代码片段剖析------------
片段一:=default默认构造函数
//1.实现:C++11的 =default默认构造函数vector() = default; //自动生成无参构造,初始化指针为nullptr
这段代码
vector() = default;
是 C++11 引入的显式默认构造函数定义
,用于自动生成类的默认构造行为。
1. 什么是默认构造函数?
默认构造函数
:是 无需参数即可调用的构造函数,用于创建类的对象。vector<int> v; // 调用默认构造函数
在 C++ 中,编译器会 自动生成默认构造函数,但仅在以下条件满足时:
- 类没有显式定义任何构造函数(包括带参数的构造函数)
- 类的所有非静态成员和基类都可以被默认初始化
2. 为什么需要显式使用 = default?
当类中 显式定义了其他构造函数(如:带参数的构造函数)时,编译器 不再自动生成默认构造函数
此时,若仍需要默认构造函数,需手动定义
class vector {public: // 显式定义带参数的构造函数 vector(size_t n, const T& value); // 由于上面定义了构造函数,必须显式声明默认构造函数 vector() = default; // 显式要求编译器生成默认实现};
片段二:迭代器范围构造函数
//3.实现:迭代器范围构造函数template <class InputIterator> //通过任意输入迭代器范围初始化vectorvector(InputIterator first, InputIterator last) //注意:类模板的成员函数,还可以继续是函数模版{//遍历迭代器的范围逐个插入元素while (first != last){//1.插入push_back(*first);//2.移动++first;}}
1. 什么是迭代器范围构造函数?
迭代器范围构造函数(Iterator Range Constructor)
:是 C++ STL容器(如:vector
、list
、map
等)提供的一种特殊构造函数,允许通过 两个迭代器界定的范围 来初始化容器。
- 迭代器范围构造函数通常接受 输入迭代器(InputIterator) 或更高级别的迭代器。
- 这种设计是 C++ 泛型编程的核心体现,极大地增强了容器间的数据传递灵活性。
构造函数签名:
template <class InputIterator>container(InputIterator first, InputIterator last);
first
:是指向数据源起始位置的迭代器last
:是指向数据源结束位置的迭代器
- 不包含该位置的元素,即:左闭右开区间
[first, last)
2. 迭代器范围构造函数怎么使用?
//方法1:从其他容器复制数据vector<int> src = {1, 2, 3};vector<int> dst(src.begin(), src.end()); //用 src 的全部元素初始化 dst//方法2:截取部分数据vector<int> sub(src.begin() + 1, src.begin() + 3); //截取 src 的中间部分//方法3:从数组或其他数据源初始化int arr[] = {4, 5, 6};vector<int> vec(arr, arr + 3); //用数组的前3个元素初始化 vec
片段三:填充构造函数
//4.实现:填充构造函数(size_t版本)vector(size_t n, const T& val = T()) //创建n个值为val的元素{//1.先预留目标容量,避免多次扩容reserve(n);//2.再使用for循环,连续插入n个valfor (size_t i = 0; i < n; i++){push_back(val);}}
1. 什么是填充构造函数?
填充构造函数(Fill Constructor)
:是 C++ STL 容器(如:vector
、list
、deque
等)提供的一种特殊构造函数,用于创建一个包含 指定数量的相同值元素 的容器。
- 它是初始化容器的高效方式之一,尤其适合批量生成默认数据。
构造函数签名:
container(size_type n, const T& value = T());
n
:是容器的初始大小(元素数量)value
:是每个元素的初始值(默认值为T()
,即:类型T
的默认构造值)
函数参数解析:
vector(size_t n, const T& val = T())
size_t n
:指定容器的初始大小const T& val
:指定元素的初始值,使用引用避免拷贝= T()
:默认参数,使用类型T
的默认构造值(如:int
为 0,string
为空字符串)
2. 内置类型的变量是怎么进行初始化的?
此时我相信有一部分小伙伴对上面的内容会感觉有点疑惑?
啊?
= T()
:默认参数,使用类型T
的默认构造值。“那如果模板参数
T
被编译器在编译期间的类型推导为了内置类型的话,还能调用构造函数吗?”太棒了,这个问题问的非常的好O(∩_∩)O
在 C++ 中:
内置类型(如:
int
、double
、char
等)没有传统意义上的构造函数。但它们确实支持
值初始化
和默认初始化
,这使得它们在语法上可以表现得类似有构造函数。
内置类型的初始化规则:
1. 默认初始化(Default Initialization)
发生场景:当变量被声明但未显式初始化时。
行为:
- 局部变量:值未定义(垃圾值)
- 静态变量:初始化为
0
(或nullptr
对于指针)示例:
int x; // 局部变量,值未定义static int y; // 静态变量,初始化为 0
2. 值初始化(Value Initialization)
发生场景:使用 空括号 或 花括号 进行初始化(如:
T()
或T{}
)行为:初始化为 该类型的零值(如:
0
、0.0
、nullptr
)示例:
int x = int(); // 值初始化为 0double y = double();// 值初始化为 0.0int* ptr = int*(); // 值初始化为 nullptr int x = 0; // 直接赋值int y = int(); // 值初始化(等效于 0)
3. T()的含义是什么?
在模板代码中,
T()
表示类型 T 的值初始化
:
- 对于
类类型
:调用默认构造函数。- 对于
内置类型
:初始化为零值。这是 C++ 的 统一初始化语法 特性:
示例:
template <typename T>void func(const T& val = T()) //默认参数为 T 的值初始化{ // ...}//1.类类型func<string>(); // val 默认为 \"\"(string 的默认构造)//2.内置类型func<int>(); // val 默认为 0(int 的零值)
4. 内置类型和类类型使用T()的不同之处是什么?
虽然
T()
对内置类型和类类型的语法相同,但本质不同:
int
)string
)int x = int();
int x{};
string s = string();
string s{};
5. 填充构造函数怎么使用?
//1.创建包含5个100的vectorvector<int> v(5, 100); //v = {100, 100, 100, 100, 100}//2.创建包含3个空字符串的vectorvector<string> s(3); //s = {\"\", \"\", \"\"}//3.创建包含10个默认构造的对象vector<MyClass> objs(10); //每个元素都是 MyClass()
片段四:reserve()的迭代器失效
//4.实现:“预留容量的函数”void reserve(size_t n) //预留至少n个元素的容量(不改变有效元素个数){//注意:预留容量的操作只有在预留的容量n 大于 原vector容器的容量时候才会进行操作if (n > capacity()){/*---------第一步:保存原有元素的个数---------*/size_t old_size = size();/*---------第二步:分配新内存---------*/T* tmp = new T[n];/*---------第三步:拷贝旧数据---------*///注意:使用for循环支持自定义类型的拷贝构造for (size_t i = 0; i < size(); i++){tmp[i] = _start[i]; //调用T的拷贝赋值运算符}/*---------第四步:释放旧内存---------*/delete[] _start;/*---------第五步:更新指针成员---------*///方法一:使用一个变量来记录之前vector中的元素的数量(三个迭代器的更新顺序没有要求)_start = tmp;//_finish = _start + size(); //失败//_finish = _start + old_size; //成功//_finish = tmp + size(); //失败_finish = tmp + old_size; //成功_end_of_storage = tmp + n;/* 方法二:也可以不记录原vector中元素的数量来避免迭代器失效的问题,但是必须确保:_finish 的更新一定要在 _start 之前_finish = tmp + size();_start = tmp;_end_of_storage = tmp + n;*/}}
小朋友你是不是有很多问好?:
1. 为什么在
reserve
函数中,使用_finish = tmp + size();
不能避免迭代器失效问题,而_finish = tmp + old_size;
却可以?2. reserve不是只会改变 vector的容量吗?,所以使用 reserve()扩完容之后 size()不应该还是和之前的值一样吗?难道说不一样了,啊,这是怎么回事?
哈哈,就是你想的那个样子,扩容之后
size()
接口函数确实无法再正确获取当前vector
容器中元素的数量了,下面我们来详细分析一下其中的原因。
size()
函数在扩容过程中的语义变化:
在
reserve
函数执行过程中,当进入扩容逻辑时,原有的vector
底层数组内存已经被释放(delete[] _start;
这一步)而此时,
size()
函数获取的是当前对象中_finish
和_start
差值
- 但由于
_start
已经指向新分配的内存(没有使用变量old_size保留之前vector中元素的数量,并且先更新了_start = tmp
; —> 将会造成_finish
迭代器失效)- 而
_finish
尚未更新,已经更新的_start
和未进行更新的_finish
相减得到的值,也即是扩容后调用size()
返回的值,必然不再是原vector中的元素的数量所以:扩容后如果使用
_finish = tmp + size();
,因为size()
返回值不对,会导致_finish
指向错误位置,这样后续对vector
的操作(如:插入元素等)就会出现越界等错误,迭代器也会失效。
old_size
的正确语义:
而
old_size
在函数一开始通过size_t old_size = size();
获取,它记录的是扩容前vector
中有效元素的个数。在扩容过程中,虽然底层数组内存改变,但有效元素个数并未改变(只是被拷贝到新内存 )
在扩容后使用
_finish = tmp + old_size;
,能够正确地将_finish
指向新分配内存中,存放完所有原有效元素后的下一个位置,保证了vector
内部结构的正确性,从而避免迭代器失效。
总结:
简单来说,
size()
在扩容过程中因内部状态变化导致迭代器失效,从而不能使用迭代器准确反映有效元素个数。而
old_size
记录的是扩容前的有效元素个数,能正确用于更新_finish
的位置,维持vector
的正确结构和迭代器有效性。
片段五:print_vector(v);无法打印std::vector的原因
//2.使用模板实现print_vector函数template<class T>void print_vector(const vector<T>& v){//注意:下面我们可以使用两种不同的方式实现vector容器的遍历/*//----------------方法一:使用迭代器遍历----------------//1.定义vector容器的迭代器//vector::const_iterator it = v.begin();auto it = v.begin();//2.使用while循环进行迭代遍历vector容器while (it != v.end()){//1.输出遍历到的元素std::cout << *it << \" \";//2.让迭代器向后移动++it;}std::cout << std::endl;*//*----------------方法二:使用范围for循环遍历----------------*/for (auto it : v){std::cout << it << \" \";}std::cout << std::endl;}/*==========================================任务2.3:实现函数模板:print_container==========================================*/template<class container>void print_container(const container& con){/*//----------------方法一:使用迭代器遍历----------------auto it = con.begin();while (it != con.end()){//1.输出cout << *it << \" \";//2.移动++it;}cout << endl;*//*----------------方法二:使用范围for循环遍历----------------*/for (auto it : con){std::cout << it << \" \";}std::cout << std::endl;}cout << \"==========测试:查找元素的操作==========\" << endl;std::vector<int> v; // 测试标准库vector与自定义vector的兼容性v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout << \"vecto中原始的元素为:\" << endl;//print_vector(v); 错误,无法打印出vector容器v中的元素print_container(v);
上面的代码中,
print_container(v)
能正常工作而print_vector(v)
报错的根本原因在于命名空间
和模板参数
匹配规则 的差异。
(1) 模板参数类型严格匹配问题
print_vector
函数定义:template<class T>void print_vector(const vector<T>& v)
这里的
vector
是 非限定名称,编译器会优先在 当前命名空间(mySpace
)中查找。当传入
std::vector
时:
- 编译器期望
mySpace::vector
- 实际传入
std::vector
- 类型不匹配导致编译错误
(2) print_container 的通用性设计
print_container
函数定义:template<class container>void print_container(const container& con)
这是完全泛型的:
接受 任何具有
begin()
/end()
接口的类型不限制具体容器类型
因此兼容
std::vector
和mySpace::vector
print_vector
const vector&
mySpace::vector
print_container
const container&
片段六:二维数组的实现原理
/*-------------------第一部分:创建一个二维数组-------------------*///构造二维vector:7个元素,每个元素是vector(5,1)/*-------方法一:填充构造法-------*/std::vector<int> v(5, 1); //5个1的一维vectorstd::vector<std::vector<int>> vv(7, v); //7个v的拷贝,形成“7行5列”的二维数组/*-------方法二:嵌套构造法-------*///std::vector<std::vector> vv(7, std::vector(5, 1)); // 直接指定每行的初始值/*-------------------第二部分:修改二维数组-------------------*/vv[2][1] = 2; //修改第3行(索引2)第2列(索引1)的元素为2//等价于:vv.operator[](2).operator[](1) = 2; 注释:operator[]的嵌套调用
1. 二维vector的访问机制是什么?
//这是一个简化的vector模板类示意,非实际代码中的实现,只是为了说明二维vector的实现原理template<class T>class vector{T& operator[](int pos) //下标运算符,返回元素引用{assert(pos < _size); //断言下标有效return _a[pos]; //返回数组中对应位置的元素}private:T* _a; //数据指针size_t _size; //有效元素个数size_t _capacity; //总容量};/*---------------当T为int时,vector的operator[]返回int&---------------*///当T为int时 -----> vector的operator[]返回“int&”class vector{int& operator[](int i){assert(i < _size);return _a[i];}private:int* _a;size_t _size;size_t _capacity;};/*---------------当T为vector时,vector<vector>的operator[]返回vector&---------------*///当T为vector时 -----> vector<vector>的operator[]返回“vector&”class vector{vector<int>& operator[](int i) // 返回内层vector的引用{assert(i < _size);return _a[i];}private:vector<int>* _a; // 指针数组,每个元素是vectorsize_t _size;size_t _capacity;};
通过上面的这段代码,我们可以深入理解 C++ 中
模板类的实例化过程
,以及二维 vector 的内存结构和访问机制
模板类的实例化机制:
模板类
vector
在编译时会根据实际使用的类型参数(如:int
或vector
)生成对应的具体类。
vector
:T
被替换为int
,_a
成为int*
,operator[]
返回int&
vector<vector>
:T
被替换为vector
,_a
成为vector*
,operator[]
返回vector&
这种实例化过程是
静态的
(编译期完成),不同类型参数生成的类是独立的。
二维 vector 的内存结构:
二维 vector
:vector<vector>
本质是一个动态数组的动态数组
- 外层:
vector
管理一个vector*
类型的指针数组_a
,每个元素是一个指向内层vector
的指针。- 内层:
vector
各自管理一个int*
类型的指针数组,存储实际数据。内存示意图:
vector<vector<int>> vv: _a ───> [vector<int>] ───> [1, 2, 3] // 第一行 [vector<int>] ───> [4, 5, 6] // 第二行 [vector<int>] ───> [7, 8, 9] // 第三行
多级下标访问的原理:
通过
vv[i][j]
访问元素时:
vv[i]
:调用外层vector<vector>
的operator[]
,返回第i
个内层vector
的引用。[j]
:调用内层vector
的operator[]
,返回第j
个int
的引用。代码示例:
vv[1][2] = 10; // 等价于:auto& row = vv.operator[](1); // 获取第二行(vector&)row.operator[](2) = 10; // 修改第二行第三列的元素
2. 二维vector和原生二维数组的区别?
二维vector
原生二维数组
vv[i][j]
(两次函数调用)arr[i][j]
(直接偏移)总结:
二维vector
vector<vector>
的本质是:模板类的嵌套实例化
,通过多级指针和引用实现灵活的多维数据管理。