STL之vector容器
vector的介绍
1.vector是可变大小数组的容器
2.像数组一样,采用连续的空间存储,也就意味着可以通过下标去访问,但它的大小可以动态改变
3.每次的插入都要开空间吗?开空间就要意味着先开临时空间,然后在拷贝旧的到新的上面,在释放原来的,就时间而言,这是一个相对代价很高的任务;显然这是不行的,vector的分配策略:会分配一些而外的空间以适应可能的增长,例如我们平时说的以1.5或者2倍的方式增容,不同的库选择不同的策略权衡空间的使用和重新分配,以至于在插入一个元素时能在常数时间内插入;
vector的使用
构造函数:
注意:这里的allocator_type是空间配置器,与内存池相关,可以暂时忽略
这里的vector<int>要传模板,指明你每个元素里面存的类型是什么
第一个能构造一个空的vector数组
第二个能构造4个元素,每个元素里面存100的数组
第三个传迭代器能根据迭代器构造出一个对象
第四个是拷贝构造函数
如果第二个中我没有指定每个元素为100,它会根据你传的模板是什么初始化为啥,例如你传int就初始化为0
3种遍历方式
1.operator[]
因为底层是数组,所以重载了operator[]方便遍历
for(size_t i=0;i<vt.size();i++)
{cout<<vt[i]<<" ";
}
2.迭代器遍历
可以看到vector容器提供了begin/end和rbegin/rend,这说明我们可以正反向遍历
vector<int>::iterator it=vt.begin();
while(it!=vt.end())
{cout<<*it;it++;
}vector<int>::reverse_iterator rit=rbegin();
while(it!=rbegin())
{cout<<*it;it++;
}
3.返回for遍历:底层就是迭代器,只要能实现迭代器就能实现范围for
for(auto&e:vt)
{cout<<e;
}
注意:如果你有一个函数,例如打印函数,你是不想修改数组里面元素的值,那传参时需要传const修饰的数组,此时你在打印函数中使用迭代器,只能用const迭代器
迭代器分两个版本,一个是const迭代器一个是普通迭代器
void Print(const vector<int>&vt)
{vector<int>::const_iterator it=vt.begin();while(it!=vt.end())
{
cout<<*it;
*it+=1;//这句是错的,你传了const就意味着你不能通过迭代器去修改
}
}
capacity函数接口
size():顾名思义就是返回数组的大小,这里的大小是有效元素的大小
capacity():是返回这里数组的容量,也就是你动态开辟出来的大小
empty():判断是否为空;如果没有元素即返回true,有就返回false
resize():改变数组的size
reserve():改变数组的容量,如果提前知道空间可以减少增容的代价
注意:vs2022是以1.5倍增长,gcc是以两倍增长,两个用的STL版本不一样,vs使用PJ版本,gcc是用SGI版本
Modifiers函数接口
数组当然实现的是尾插和尾删,效率高,如果你有头插和头删最好别选vector(后面会介绍list和vector的优缺点)
push_back():尾插一个数据
pop_back():尾删一个数据
insert():任意位置的插入,在pos位置之前插入
注意:这里传迭代器
erase():任意位置的删除,删除pos位置的元素,返回下一个元素的迭代器
注意:这里我们不知道要删除的元素的位置该如何找到呢?vector没有实现find
可以调用<algorithm>库实现的find
找到返回pos位置的迭代器,找不到返回end();
swap():交换两个vector的数据空间
clear():清空元素,但开辟的空间没有释放
重点谈迭代器失效问题
迭代器的主要作用是让算法不用关系底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T*。因此,迭代器失效,实际就是迭代器底层对应的指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果就是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)
第一种情况:
如果你先定义了迭代器vector<int>::iterator it=vt.begin();
然后在进行各种扩容操作reserve,resize,insert,assign,push_back等
然后在使用迭代器,那原来的指针it指向的的那块空间已经被释放了,因为你进行了扩容开辟了新空间,你在使用迭代器去解引用或者it!=vt.end()等操作都可能会造成程序崩溃
第二种情况:
比如你使用库实现的find去查找某个元素,会返回一个迭代器类型,然后在释放这个位置的元素
vector<int>::iterator pos=find(vt.begin(),vt.end(),3);
if(pos!=vt.end())
{erase(pos);
}
cout<<*pos<<;//此处会导致非法访问
erase删除pos位置之后,pos位置之后的元素会往前挪动,没有导致底层空间的改变,理论上讲迭代器是不会失效的,但是,如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,pos就会失效,因此删除vector中任意位置的元素是,vs就认为该位置迭代器失效了
动态二维数组的理解:
我们如何创建一个二维数组,可以想象每个vector的元素都是一个vector<int>
vector<vector<int>> vv(5);
这里可以想象一个一维数组,有五个元素,每个元素都是一个vector<int>
那我们如何初始化呢
第一种:我们学过vector<int> v(3,5)//这是创建3个元素,每个元素都是5,因此我们可以这与初始化二维数组,rows是行,每个元素是vector<int>(cols),initial)
int main() {// 定义二维数组的行数和列数int rows = 3;int cols = 4;// 初始值int initialValue = 0;// 创建并初始化二维 vectorstd::vector<std::vector<int>> twoDArray(rows, std::vector<int>(cols, initialValue));// 输出二维数组for (const auto& row : twoDArray) {for (int element : row) {std::cout << element << " ";}std::cout << std::endl;}return 0;
}
第二种:
可以创建好二维数组,在用循环依次填入数据
int main() {// 定义二维数组的行数和列数int rows = 3;int cols = 4;// 初始值int initialValue = 0;// 创建空的二维 vectorstd::vector<std::vector<int>> twoDArray;// 逐行添加元素for (int i = 0; i < rows; ++i) {std::vector<int> row(cols, initialValue);twoDArray.push_back(row);}// 输出二维数组for (const auto& row : twoDArray) {for (int element : row) {std::cout << element << " ";}std::cout << std::endl;}return 0;
}
第三种:使用c++11的初始化列表进行初始化
int main() {// 使用列表初始化创建二维 vectorstd::vector<std::vector<int>> twoDArray = {{1, 2, 3},{4, 5, 6},{7, 8, 9}};// 输出二维数组for (const auto& row : twoDArray) {for (int element : row) {std::cout << element << " ";}std::cout << std::endl;}return 0;
}
注意:当我们创建好了之后,可以像一个二维数组一样使用vv[i][j]去访问每个元素
实质就是operator[]重载运算符,第一个[]先和vv结合,第二个后结合就可以访问了
简单模拟实现vector容器、
我已经把代码上传gitee,有需要可以自己拿
June: 这里包含我的c++和Linux及数据结构https://gitee.com/taifanshu/day2.git