引言:
北京时间:2023/2/27/11:42,高数考试还在进行中,我充分意识到了学校的不高级,因为题目真的没什么意思,虽然挺平易近人,但是……,考试期间时间比较放松,所以不能耽误我更新博客,自从上篇博客,我们把string类的基本知识学习完之后,今天我们的主要目标就是学习一下什么是vector
复习模板(从模板开始学习vector)
首先vector是什么:
在C++中vector是一个类,是一个类似于string类的类,并且它们都是一个模板类,区别于string类,vector是一个可以管理任意数组的数组(只要数据类型合适),但string类是一个只针对于字符串数组的字符串类,所以本质上vector就是用一个动态增长的数组实现的模板类。
认识了vector是什么,现在我们可以知道,它就是一个动态数组,并且它可以通过类模板的形式,来管理不同的数据类型,所以我们意识到,类模板和vector是有一定的联系的,此时我们就需要类从模板的知识入手,以浅入深学习STL中的vector容器。
类模板:
定义模板形式:
template<class T>
这个代码就可以实现,函数模板和类模板定义,此时无论是函数中,还是类中,都可以通过替换 <>尖括号中的类型来替换整个函数或者整个类中的数据类型,以方便于给所有的类型的数据使用,如下图:
此时就可以如上图main函数中,通过在尖括号<> 中使用不同的数据类型实现对Stack整个类使用不同的数据类型,进而实现泛型编程,充分使用类模板。
所以可以看出我们的STL中的vector类就是通过类模板的形式,实现的一个可以针对所有数据类型的动态数组。
总:vector类区别于string类的本质原因就是vector类通过类模板的形式实现可以针对所有数据类型的动态数组
vector的学习
了解了什么是类模板,什么是vector的本质,此时正式开始学习vector,首先还是那句话,vector类和string类本质上是没有什么区别的,最大的区别就是数据类型的不同,所以此时我们学习vector类的过程中本质上还是在学习string类,又因为我们上篇博客已经学习了什么是string类,此时学习vector类时,我们就会变得更加的轻松,并且,使用vector类会变得更加的方便。
懂的上述,我们知道学习vector还是在吃旧饭,所以我们按照以前的方式来进行学习就行了,首先第一步打开C++官网,寻找vector的具体说明和各种成员函数,在vector的首页,我们就可以看到上图的内容,vector和我们所说的一样,是一个类模板,并且此时还使用了一个新的概念:空间配置器(内存池)的概念,目的就是为了可以提高数组申请空间方面的效率
注:空间配置器是 STL 源码中实现的一个小灶,用来应对 STL 容器频繁申请小块内存空间的问题。 他算是一个小型的内存池 ,以提升STL 容器在空间申请方面的效率
下面,我们就围绕着vector类的一些常用的接口,来深入了解一下vector的具体实现
首先是vector类中的构造函数:
如下图:就是一个简单的vector类的使用:
其中使用了vector类中的,尾插(push_back)、迭代器(iterator)、方括号加下标([])和拷贝构造函数(copy)
注:头文件 #include<vector>
了解了vector类的基本使用,此时我们就来看一看它构造函数中的 fill 的使用,初始化对象(往数组中填充数据)和迭代器区间(range)的使用:
迭代器区间
并且此时我们发现,该迭代器的类型不是以前我们所见的 iterator 而是 inputiterator,此时 inputiterator 代表的就是一个可以接收任意容器的迭代器区间,不单单只是vector容器,也可以是别的容器,例:string类(前提是数据类型可以匹配上),并且此时我们了解到迭代器是有非常多的种类的,具体可以参考该链接:迭代器种类和使用
如下图:证明我们的vector迭代器是可以接收任意容器的迭代器区间的(因为vector类中的迭代器是使用 inputiterator 类型的迭代器)
并且因为此时我们使用的是一个迭代器区间,所以我们可以规定,迭代器的开始位置和结束位置,通过begin()
和end()
来控制,注:此时char类型转化为int类型,本质上是类型转换,然后通过ASCII码值来进行打印,如下图:
逆向迭代器
搞定了上述迭代器区间的使用,此时我们来看一看以前了解过的逆向迭代器的使用,如下图:
所以我们可以发现,虽然范围for用起来非常的方便,但是并不可以支持逆向遍历,并且本质上范围for使用的是迭代器,所以本质上迭代器才是王者,并且以后我们遇到别的数据类型,例如:树状结构或者链表,这些遍历都是使用迭代器进行的。
开空间函数(reserve和resize)
reserve:
搞定了vector类中的构造函数,此时我们看一看,vector中别的函数,如:reserve
函数(提前开空间函数),如下图:
该图,充分展示了开空间函数(reserve
)的使用。
resize:
了解vector类中的resize函数,我们通过一个题目来搞定它:
题目:给定一个非负整数,生成杨辉三角的前numRows行,在杨辉三角中,每个数是它左上方的数的和
class Solution1 { public: vector<vector<int>> generate(int numRows)//此时就是一个类中类的使用,例:vector<int>中代表的是该vector中的每个 数据是int,而vector<vector<int>>代表的是,该vector中的数据是vector<int>而已 {//本质可以理解成一个二维数组:一个vector数组,指向了一个一个的vector的意思 vector<vector<int>> vv;//此时是可以直接在这里使用类里面的构造函数进行初始化的(但是为了控制类中的数据,我们只能使用resize) vv.resize(numRows, vector<int>());//此时这样写的意思就是:开辟numRows行,然后给一个匿名对象(vector<int>)去初始化这些行,当然不使用匿名对象,也可以使用vector<int> v;进行 for (size_t i = 0; i < vv.size(); ++i)//无论是上面的那个()的意思,还是下面这个()的意思,本质上都是为了取调用函数,获得一个返回值 { vv[i].resize(i + 1, 0);//和上面那句是一样的,开空间,然后用0去初始化(并且此时刚好符合题意:第一行一个数据,第二行两个数据,第三行三个数据,充分体现出for循环的好处) vv[i][0] = vv[i][vv[i].size() - 1] = 1;//此时就是根据杨辉三角的特性,把每一行的第一个数据和最后一个数据给成(1) }//此时vv[i].size(),其实本质上还是vector<int>类,不是int类,所以此时表示的是第几个vector<int>,然后把该类的大小减1给给它 for (size_t i = 0; i < vv.size(); ++i) { for (size_t j = 0; j < vv[i].size(); ++i)//vv[i].size()代表的就是vector<int>类中的那个vector数组的大小 { if (vv[i][j] == 0) { vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];//这句代码就是经典的杨辉三角代码(上一行本列的数据+上一行上一列的数据) } } } return vv; } };
从上题中,我们可以看出,resize函数在vector中,也具备开空间和初始化的作用,并且从上述代码和注释中,我们可以看出,vector<vector< int >>是可以当作一个指定空间大小的二维数组使用的,只不过此时vector类中不是int,而是一个vector< int >,一个类中类,并且该类内存的类是一个int类型的类。本质上还是模板的作用,只是此时vector类模板的数据类型改变了而已;如下图:
总而言之就是模板的数据类型不同而已