目录
一.vector类的初步介绍:
二.vector类的成员函数介绍:
2.1基础成员函数使用:
2.1.1reserve():扩容函数
代码测验:
2.1.2resize()调整类对象的数据存储函数
代码实验:
结果:
编辑
2.1.3缩容函数:shrink_to_fit
2.2迭代器
代码实验:
运行结果:
2.3operator运算符重载、at()、back()、front()函数
代码实验:
at();函数的作用和operator[ ]作用一样,都是可以获取类对象数组中的某个元素。
代码实验:
运行结果:
对于front()和back()函数来说,就是获取类对象数组中的头元素和尾元素。
2.4assign()函数:
用法2:
用法1:使用迭代器做形参:
运行结果:
2.5增添数据
运行结果:
2.6删除元素
代码实验:
运行结果:
2.7查找
代码实验:
运行结果:
之前讲了关于String字符序列类的底层理解、成员函数及模拟实现,String类中只能用于存放字符或字符串,底层是通过使用创建堆区空间的数组实现的;而今天要讲的是STL中相当有含金量的一个类——vector,vector的英文是向量:
1. vector是表示可变大小数组的序列容器。
2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是:分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大 小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是 对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deque, list and ), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list统一的迭代器和引用更好。
#include
#include
using namespace std;
void Test1() {
vector v;
cout << v.size() << endl;
cout << v.capacity() << endl;
cout << v.empty() << endl; //empty为空则显示1,不为空则显示0
cout<<
v.push_back(10);
v.push_back(185.95); //舍弃小数,取整,不存在过.5进一
v.push_back('c'); //v是int类型,所以转换的是ASCII码值
v.push_back(7999);
v.push_back(0);
cout << v.size() << endl;
cout << v.capacity() << endl;
cout << v.empty() << endl;
cout << "遍历所得:";
for (auto& ch : v) {
cout << ch << " ";
}
cout << endl;
cout << v.max_size() << endl;
//获取的是可以存储的总个数,10亿多,最大值是42亿多字节,因为是int类型,占4个字节,所以总字节数/int类型
cout << "----------------------" << endl;
}
int main(){
Test1();
return 0;
}
结果:
reserve的形参只有一个size类型的数值n,该数值可以改变类对象的容量,只会使得容量增加(扩容),不会使容量减少。
1.如果 n 大于当前向量容量,则该函数会导致容器重新分配其存储,将其容量增加到 n(或更大)。
2.在所有其他情况下,函数调用不会导致重新分配,容量不受影响。
此函数对类对象的size大小没有影响,并且不能更改其元素。
还是根据上面所创建的对象v的例子:
cout << "使用reserve前: " << endl;
cout << "v.size(): " << v.size() << endl;
cout << "v.capacity(): " << v.capacity() << endl;
cout << "使用reserve(5)后: " << endl;
v.reserve(5); //——只改变容量
cout << "v.size(): "<
在vector类中,每当数据存储数量达到容量临界值后,再增添数据就会触发扩容机制,一般情况下扩容都是扩大到原来的1.5倍——2倍左右,这是计算机科学家们经过无数次的测试得出来的结果,扩大1.5倍——2倍是很合适的,可以避免扩少了需要频繁扩容的问题,也避免了扩多了导致空间浪费的问题!
如上是VS编译器中的扩容机制,按照大约1.5倍的方式进行的。
函数作用:调整容器的大小,使其包含 n 个元素。
1.如果 n 小于当前容器大小,则内容将减少到其前 n 个元素,删除超出的元素(并销毁它们)。
2.如果 n 大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val,则新元素将初始化为 val 的副本,否则,它们将被值初始化。
3.如果 n 也大于当前容器容量,则会自动重新分配分配的存储空间。value_type()是第二参数的缺省值,它表示类模板泛型T,会调用T模板的构造函数。在当初学习该函数的时候,我会想为什么不把它设置为0呢?
经过长时间的学习,我找到了一个重要的原因:对于内置类型char/int/double/size_t等,可以设置为0,但是若是修改自定义类的resize呢?也能设置为0吗?自定义的类是未知的,不确定的,所以使用匿名对象更好,对于内置类型的resize,匿名对象也能够调用内置类型的构造函数——默认为0
cout << "resize: " << endl;
cout << "遍历所得:";
for (auto& ch : v) {
cout << ch << " ";
}
cout << endl;
cout << "使用resize前: " << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
//使用resize,让v的顺序表只留下3个值
v.resize(3);
cout << "v.resize(3):缩减_size,容量不会变" << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
cout << "遍历所得:";
for (auto& ch : v) {
cout << ch << " ";
}
cout << endl;
cout << "----------" << endl;
cout << "resize(6)表示扩容,且剩余的几个数按a的ASCII码值填写:" << endl;
v.resize(6, 'a');
cout << "遍历所得:";
for (auto& ch : v) {
cout << ch << " ";
}
cout << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
cout << "----------" << endl;
//resize代表扩容15个数值,_size=15,‘
v.resize(15);
for (auto& ch : v) {
cout << ch << " ";
}
cout << endl;
cout << v.size() << endl;
cout << v.capacity() << endl;
resize(3); 类对象的数据由上知为5,实参设为3会删减类对象的存储数据量,但不会影响容量;
resize(6,'a'); 类对象的数据量由3-->6,多出来的3个数据,会根据写入的第二参数决定,第二参数为字符'a',经过整型提升从char-->int,‘a'的ASCII码值是97
resize(15); 由于没有设置第二参数,函数默认采用缺省值:value_type(),默认是0。
一般情况下,没人会使用shrink_to_fit()函数,除非你将类对象扩容扩的很大,并且也没有用上这么多的容量,可以使用该函数缩小容量。
缩容与扩容原理相同,都是重新创建一块新的堆区空间,将数据拷贝到新空间,然后释放原来的旧空间即可。缩容与扩容都是以时间换取空间,是有代价的。
这里就不演示代码了。
迭代器是每个STL容器都有的核心,有了迭代器,我们便可以轻松对其遍历,方便查看内容、做出修改。
如上是迭代器中的接口函数,我们可以把它们看作是指针,例如:begin指向vector类对象内容的开头,end指向vector类对象内容的结尾。在迭代器中,共有三种类型的begin和end,即正向迭代、反向迭代、const反向迭代。这三种类型的接口都受vector类域的限制,所以定义这些begin、end时需要加上类域才行!
使用迭代器,需要明白其格式:
std::vector:: iterator 迭代器对象名(自己取名字)=vector类对象.begin();
#include
#include
using namespace std;
void Test1() {
vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
//迭代器
cout << "正向迭代器的遍历:";
vector::iterator vit = v.begin();
while (vit != v.end()) {
cout << *vit << " ";
++vit;
}
cout << endl;
cout << "反向迭代器的遍历:";
vector::reverse_iterator rvit = v.rbegin();
while (rvit != v.rend()) {
cout << ++(*rvit) << " ";
++rvit;
}
cout << endl;
cout << "const正向迭代器的遍历:";
vector::const_iterator cvit = v.cbegin();
while (cvit != v.cend()) {
//cout << --(*cvit) << " "; 报错
cout << *(cvit) << " ";
++cvit;
}
cout << endl;
cout << "const反向迭代器的遍历:";
vector::const_reverse_iterator crvit = v.crbegin();
while (crvit != v.crend()) {
//cout << --(*crvit) << " "; 报错
cout << *(crvit) << " ";
++crvit;
}
cout << endl;
}
int main(){
Test1();
return 0;
}
通过结果可知:包含const的迭代器不能修改类对象的数据、reverse的迭代器相比较于正向迭代器来说,它的begin/end和正向的begin/end是位置相反的两个指针。
void Test2() {
vector v;
for (int i = 0; i < 10; ++i) {
v.push_back(i * 10);
}
//operator[]访问
cout << "operator[]遍历:";
for (int i = 0; i < v.size(); ++i) {
//读操作
cout << v[i] << " ";
}
cout << endl;
}
[ ],这对方括号可以方便我们对其遍历,正好vector的底层是数组的原因,使用[ ]重载很方便!
//at与operator[]几乎一样的作用
cout << "at函数遍历:";
for (int i = 0; i < v.size(); ++i) {
//读操作
cout << v.at(i) << " ";
}
cout << endl;
//但at函数操作不当越界时会报异常
vector v2;
for (int i = 0; i < 10; ++i) {
cout << v2.at(i) << " ";
}
cout << endl;
cout << "------------------------------------" << endl;
at和operator[ ]的区别在于:
但at函数操作不当越界时会报异常;——异常导致系统崩溃挂掉
operator[]越界会报asserion fail 错误;——错误会导致当前程序被迫终止运行。
#include
#include
using namespce std;
void Test3(){
vector v3;
vector v;
for (int i = 0; i < 10; ++i) {
v.push_back(i * 10);
cout << v[i] << " ";
}
cout << endl;
cout << v.front() << endl;
cout << v.back() << endl;
}
int main(){
Test3();
return 0;
}
assign函数作用:分配新内容,替换其当前内容,并相应地修改其大小。
assign有两种用法,一种是使用迭代器的方式;一种是输入n和val
在第二种方法中:assgin会将对象v原有的值全都删掉,只留下n(第一参数)个元素,且重新赋值为val(第二参数)
int main(){
vector v3;
int i=0;
while(i<10){
v3.push_back(i);
}
//此时v3的size变为10
//下面代码表示:将v3的_size变为5,且全都重新赋值为3,即 3 3 3 3 3
v3.assign(5,3);
for (auto& r : v3) {
cout << r << " ";
}
cout << endl;
cout << "-------------------------" << endl;
return 0;
}
string s1 = "hello world";
v3.assign(s1.begin(),s1.end());
//该代码表示:v3[0]='h'的ASCII码值,v3[1]='e'的ASCII码值,v3[2]='l'的ASCII码值......
for (auto& r : v3) {
cout << r << " ";
} //此时v3共有11个元素,每个元素值分别为s1各个字符的ASCII码值
cout << endl;
cout << v3.size() << endl;
cout << "-------------------------" << endl;
v3.assign(++s1.begin(), --s1.end());
//该代码表示:去掉了string s1的头元素和尾元素后将各个字符的ASCII码值赋值给v3
for (auto& r : v3) {
cout << r << " ";
} //此时v3共有9个元素,每个元素值分别为s1除去头尾后各个字符的ASCII码值
cout << endl;
cout << v3.size() << endl;
方式:push_back()、insert()
注:push_back为尾插函数,所以只需要输入想要添加的值即可;
而对于inert来说,可以头插、中间插、尾插,但是插入的方式仅限于用迭代器的方式,没有指定下标pos位置的插入了。
void Test5() {
vector v5;
v5.push_back(9);
v5.push_back(8);
v5.push_back(7);
v5.insert(v5.begin(), 100);//表示在v5容器的首元素位置插入值为100的元素——简称头插
for (auto& ch : v5) {
cout << ch << " ";
}
cout << endl;
v5.insert(v5.end(), 666); //表示在v5容器的末尾元素插入值为666的元素——简称尾插
for (auto& ch : v5) {
cout << ch << " ";
}
cout << endl;
//若想在中间某个位置插入则,
v5.insert(v5.begin()+2, 145); //在vector数组的第3个位置插入
v5.insert(v5.end() -3, 467); //在vector数组的倒数第四个位置插入
for (auto& ch : v5) {
cout << ch << " ";
}
cout << endl;
}
int main(){
Test5();
return 0;
}
方式也有两种:pop_back()、erase()
注:pop_back为尾删函数;
而对于erase来说,可以头删、中间删、尾删,并且删除的方式也仅限于用迭代器的方式,没有指定下标pos位置的删除。
void Test7() {
vector v7;
v7.push_back(100);
v7.push_back(200);
v7.push_back(300);
v7.push_back(400);
v7.push_back(500);
v7.push_back(600);
cout << "删除前:";
for (auto& ch : v7) {
cout << ch << " ";
}
cout << endl;
//删除
//方法1:pop删
cout << "pop删:";
v7.pop_back(); //尾删
//遍历
for (auto& ch : v7) {
cout << ch << " ";
}
cout << endl;
//方法2:erase指定某一位置删除
cout << "erase头删:";
v7.erase(v7.begin()); //v7.begin()头删
for (auto& ch : v7) {
cout << ch << " ";
}
cout << endl;
v7.erase(v7.end()-1);
cout << "erase尾删:";
for (auto& ch : v7) {
cout << ch << " ";
}
cout << endl;
cout << "erase中间删:";
v7.erase(v7.begin()+1); //v7.begin()头删
for (auto& ch : v7) {
cout << ch << " ";
}
cout << endl;
}
int main(){
Test7();
return 0;
}
其实,查找函数find()并不是vector的成员函数,它使用的std库中的find函数
对于库中的find来说,其形参也是按照迭代器指针去指定元素查找
void Test6() {
vector v6;
v6.push_back(100);
v6.push_back(200);
v6.push_back(300);
v6.push_back(400);
v6.push_back(500);
v6.push_back(600);
//find不是成员函数,所以find的参数是以迭代器为准,300是vector数组的元素值,不可填下标位置
vector ::iterator it1 = find(v6.begin(),v6.end(),300);
if(it1 != v6.end()) {
v6.insert(it1, 90); //在it1位置处插入数值90
}
//遍历
for (auto& ch : v6) {
cout << ch << " ";
}
cout << endl;
}