作者 | 梁唐
大家好,我是梁唐。
昨天介绍了vector的相关原理,我们今天接着来聊vector,今天来聊聊它的使用。
我们还是按照惯例,由浅入深。
首先是一些最基础的操作。
#include<vector> // 头文件
vector<T> vec; // 创建vector对象
vec.push_back(T{}); // 向vector尾部插入数字
cout<<vec[0]<<endl; // 通过下标访问元素
vector最基础的操作就只有这四条语句,也是使用得最多的四条。
我们一条一条来说,include没的说,引入vector的包,也是使用vector的基础。
第二条是创建一个vector的对象,vector支持多种构造函数,这个只是其中最简单的一种,即默认构造函数。vector支持泛型,我们可以自定义我们需要的类型,我们甚至还可以定义一个元素是vector的vector,即二维vector,如:vector<vector<int>>
。
第三条是插入语句,vector是一个可变长度的数组,我们可以使用push_back操作往数组的末尾插入元素。只有往数组的末尾插入元素才能保证这个插入操作是
的。当然因为插入可能会引起vector重新分配内存,所以实际上push_back的复杂度并不是
,不过我们使用的时候可以忽略这其中的细微区别,就当做是
即可。
第四条是vector中的元素访问,vector创建之后我们完全可以当做是数组一样通过下标来访问,vector中的下标也是从0开始的。
很长一段时间内我都是用这4条语句打比赛的,作为入门已经够用了。当然作为学习者来说,我们不能追求一知半解,关于vector也有很多更高端的用法。
下面介绍一些进阶用法。
首先是构造函数,我们有了更多的选择,我简单列举一下:
vector(int nSize);
vector(int nSize, const t &t);
vector(const vector&);
vector(begin, end);
我们一条一条来说,第一条是在创建vector时传入一个整数n,初始化一个长度为n的vector。如:vector<int> vec(10);
但是这样有一个问题是我们创建出来的vector当中的数据是脏数据,我们不知道里面的值是多少。如果希望创建vector的时候还能够将其中的元素指定为自己想要的值,可以使用第二条命令。也就是我们既传入长度,也传入一个我们用来初始化的值,这样初始化之后得到的vector当中的所有元素都是我们传入的t。
第三条和第四条都是拷贝构造函数,也即拷贝其他的vector,得到新的vector。
然后是一些其他的函数:
vec.pop_back();
vec.clear();
vec.empty();
vec.size();
vec.begin();
vec.end();
首先是pop_back
。
pop_back
和push_back
是相反的操作,它会从末尾弹出一个元素。也就是说vector的长度不仅可以增长,也可以变短,删除多余的我们不需要的数据。另外使用pop_back
,我们也可以实现一些特殊的数据结构,比如栈。
第二个常用的是clear
,即清除vector中所有数据。
第三个是empty
,用来判断vector是否为空。
第四个是size
,它会返回vector当前的元素个数。
最后是begin
和end
这两个函数,它们会分别返回vector的头尾位置,这里要注意end
返回的是最后一个元素的后一个位置,可以理解成一个左闭右开的区间的左右端点。
很多同学会很疑惑,我要知道vector的左右端点有什么用?其实用处很大,因为C++当中很多的内置函数接收的参数就是数组的起始位置,比如说最常用的排序函数sort,它接受的就是一个数组的头尾位置。如果我们的vector是int或者是float类型,那么就可以直接调用sort函数对数组中元素进行排序。
#include<algorithm>
sort(vec.begin(), vec.end());
只要这一行语句就实现了排序操作,算法内置了快速排序,我们就不需要自己实现了。
关于sort函数的使用我们之后会在写一篇文章好好聊聊,今天就只是一笔带过提一下。
最后我们来看看vector另外一个非常内容——迭代器。
迭代器严格说起来是一种设计模式,可以在不暴露对象内部实现的情况下访问对象中的内部元素。比如说我们通过迭代器访问一个数据结构,我们不需要关心这个数据结构内部的实现,它究竟是一棵树还是一个线性表,是二叉树还是多叉树,我们只需要按照规定的顺序迭代访问即可。
如果不使用迭代器想要遍历数据结构,就必然需要对数据结构的实现方式有所了解,也就有了某种意义上的耦合。
说起来玄乎其玄,其实迭代器的使用非常简单,和指针有些类似我们可以通过自增和自减操作来前后移动迭代器。
首先我们来看一个例子:
vector<int> vec(10, 4);
for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++) {
cout<<*it<<endl;
}
短短几行代码就已经表明了迭代器的创建和使用,首先创建的时候我们通过vector的类型加上::iterator
来表明我们创建的迭代器类型。迭代器默认从头指针移动向尾指针,也就是从begin移动向end,每次自增表示移动了一位 。
如果我们要反向移动也可以,vector也提供了反向的begin和end,叫做rbegin
和rend
。
有了迭代器除了可以访问vector中的元素之外,还可以调用一些迭代器函数实现一些特有的功能。比如说insert
和erase
函数,大家都看得出来可以插入或者删除一个元素。
vector<int> vec(10, 10);
vector<int>::iterator it = vec.begin();
vec.insert(it, 10); // 往it之前插入10
vec.erase(it); // 删除it位置的值
除此之外还可以批量插入以及批量删除,只不过用的频率不高,我就不展开细讲了,感兴趣的同学可以自行百度一下。
说起来vector很简单,但是细讲下来内容还是不少的。不知道大家有没有发现一点,在计算机领域当中很多知识都是耦合的,你想要把这个东西理解透往往需要先了解另外的知识。有一个小知识点没理解呢,可能会导致一大片的知识盲区。
比如这里你想要理解透vector的使用,需要了解迭代器,而了解迭代器呢,你可能又需要先搞清楚设计模式,以及指针。如果你对指针就一头雾水的话,那么设计模式、迭代器以及指针相关的一系列内容都无法掌握。
正因为计算机系统内的知识有这样的耦合性,所以需要我们在学习的时候深入理解、深入思考,尤其是一些基础内容,尽量不留下知识盲区,否则会给以后的学习带来巨大的麻烦。
好了,今天的内容就到这里,感谢大家的阅读。