关于STL容器的使用,也是需要熟练的,例如说做力扣题等。
vector前言
可以理解为高级版本的arr,它的功能有很多,并且使用起来很方便。
容器,是类模板。可以表示任何类型的数组。
头文件#include<vector>
称为长度根据需要而自动改变的数组。
与数组相同,可以用下标访问。
我们可以理解为,如果对于arr的增删,我们需要手动的申请空间,然后移动,然后释放原始的,但是vector自动就可以做到这一点。
初始化
关于初始化问题,我认为是一直不被大家重视的环节,但有时候一做题反而就忘记了怎么办了。
vector<int>
vector<int>vec1;
//什么也不加,就是默认初始化,但是里面没内容,size和capacity都是空的
vector<int>vec2(10);
//小括号,是表示有10个元素,每个都默认是0
vector<int>vec3(10, 5);
//10个元素,每一个都是5
vector<int>vec4(vec3.begin() + 3, vec3.end() - 5);
//利用迭代器,表示从起始和终止位置之间的全部内容,都放进去
//注意的是,end表示的是最后的结尾,因此end没有值
vector<int>vec5(vec3);
//类似于拷贝构造的感觉
vector<int>vec6{ 1,2,3,4,5 };
//直接初始化,5个元素,分别是12345
反正就是呢,第一种用的最多,你不初始化,你里面就啥也没有。
如果有需要,我们就自己去添加就好了。
或者理解为:你既没给我空间大小,又没给我内容,我不可能给你初始化。
你哪怕说给个空间大小,我给你全变成0呢是不是(第二种情况)。
但是如果是未初始化的第一种,如果你后续也不继续赋值的话,在vs里可能会报错。
vector<vector<int>>
vector<vector<int>>vec0;
//二维数组,未初始化,啥也没有,没有行没有列没有元素
vec0.push_back(vector<int>());
//可以理解为换行
//因为我每个元素都是数组,那么我就要来一个数组类型的空变量来接住
vector<vector<int>>vec(5, vector<int>(5));
//5行的二维数组,每一行都是5个数字,每个数字都是0
这个就可以理解为容器套娃,那么就当做是二维数组就好了。
而其中的换行理解也很重要。
vector<string>
vector<string>vec1;
//默认初始化,里面啥也没有
vector<string>vec2{ 10 };
//里面有东西!只不过都是"",10个默认为空的内容
//vector<string>vec2("haha");
//这个是错误的,在string容器中,是不存在小括号初始化的
vector<string>vec3{ 10,"haha" };
//10个元素,每个都是"haha"
vector<string>vec4(vec1.begin(), vec1.end());
vector<string>vec5(vec1);
//4和5与int类似,迭代器和拷贝构造
vector<string>vec6{ "xiangwan","wanxiang" };
//逐一初始化
vector函数
有了容器的函数,对我们的使用有了极大的提升。
size/capacity
首先是size和capacity。
size顾名思义,就是数组的大小,我们舍弃了arr也是因为可以直接获取到容器的真实长度。
而capacity是容量,用来扩容的时候使用,不过也几乎不用这个东西。
例如说我们的空间满了,那么我们新变化的容器容量,就会是原来的1.5倍。
例如:
vector<int>vec(10);
vec.push_back();
vec.size();//11
vec.capacity();//15
就是这样的结果了,但是capacity几乎没怎么用过,size用的还是很频繁。
迭代器iterator/begin/end
输入法居然打不出这个单词我还以为我拼错了...
对于除了stack和queue之外的所有STL容器来讲,都存在迭代器。
可以理解为是指针,指向对应的位置。
因此既然是指针,那么使用的时候就要使用间接引用的方式来进行输出其中的值。
例如begin和end,就是头和尾,但是要注意的是,end表示的是结尾,可以理解为是一个空的标识,类似于EOF,我们看到end就表示结束了。
但是例如说像end()-3这种,怎么快速的理解呢?
也就是说,我们移动到了某一个位置,而他表示的是这个位置之前的一个位置当中的内容。
vector<int>vec{ 1,2,3,4 };
vector<int>::iterator ite = vec.begin() + 2;
cout << *ite << endl;//得到3
但是我习惯用auto了,这么写有点麻烦了属于是。
对于迭代器的增加减少,貌似是可以直接使用的。但是有的说法是除了vector和string以外的迭代器是不可以这样操作的,等我写到哪里一定会去验证一下,到时候再回来修改。
front/back
这个长得与begin和end类似,但是这个并不是迭代器,或者说不是指针,这个就是真真儿的元素,例如说一个1到10的容器,那么vec.front()那就是1,back那就是10。
push_back/pop_back
见名知意,这个就是在容器的尾部进行添加或者删除操作,但是在力扣上发现,习惯用emplace_back来代替push_back,理由说是可能不会那么暴力的使用空间,会柔和一点...反正这个是c++11的标准,尽量用这个写就好了。
clear
将vector的内容清理干净,可以理解为,让size变成0,但是capacity还是不变的。
insert/erase
这个可以理解为,在特定迭代器位置之前,进行添加或删除的操作。
vector<int>vec{ 1,2,3,4 };
auto i = vec.begin() + 1;
cout << *i << endl;
auto x = vec.insert(i, 5);
print(vec);
cout << *x << endl;
cout << *i << endl;
首先我们看到,是一个1234的容器,我们在第二个位置取到迭代器,然后先进行输出,结果会是2。之后在这个位置进行insert操作,我们发现结果是15234,那么就验证了,是在2所处位置的前面进行添加,这也就可以稍微理解end的原理了。
其中,print是我自己写的一个输出vector的模板类函数,并没有给出过程。
对于倒数第二行,我们看出,insert返回的是迭代器,就是更新之后的位置,得到结果是5。同理于之后的erase,返回的也是迭代器。
这里说一下,如果正常运行的话,最后一行是会报错的,为什么呢?
我的理解是,你开始取到的迭代器,是vec的,但是,你对vec进行了添加,如上文所说,vector内部进行自己更新,申请空间再删除,那么,你的i就根本取不到了。
你大妈已经不是你六年前的大妈了。这个容器的地址他换了。
vector<int>vec{ 1,2,3,4 };
auto i = vec.begin() + 1;
cout << *i << endl;
auto x = vec.erase(i);
print(vec);
cout << *x << endl;
cout << *i << endl;
而对于erase,是删除指针所指的内容。
而最后一行还是不好使,那就说明:无论是增删,vector都要重新指定一个空间来进行替换。