C++ vector 容器浅析:https://www.runoob.com/w3cnote/cpp-vector-container-analysis.html (非常全面)
C++STL容器之Vector详解:https://blog.csdn.net/nilaodie110/article/details/88537555?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
目录
0vector容器
vector迭代器
vector数据结构
头文件
1vector常用API介绍
1.1构造函数
1.2赋值
1.3交换
1.4 常用
vector扩容机制:
其他常用
1.5迭代器的使用
补充:find
vector删除机制
vector(向量容器)的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用灵活性,Array是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间。
vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的合理利用与灵活性相较array有很大帮助。
vector会自动调整大小,根据添加元素,自动扩容,下面是示例代码。
vector V;
for(int i = 0;i<10;++i)
{
V.push_back(i);
cout<
输出:
并不是单纯的扩大二倍,而是有一套自己的算法。
STL中的哪种结构在增加成员时可能会引起原有成员的存储位置发生改变?
答案:vector
vector是STL中最常见的容器,它是一种顺序容器,支持随机访问。vector是一块连续分配的内存,从数据安排的角度来讲,和数组极其相似,
不同的地方就是:数组是静态分配空间,一旦分配了空间的大小,就不可再改变了;而vector是动态分配空间,随着元素的不断插入,它会按照自身
的一套机制不断扩充自身的容量。
vector的扩充机制:按照容器现在容量的一倍进行增长。vector容器分配的是一块连续的内存空间,每次容器的增长,并不是在原有连续
的内存空间后再进行简单的叠加,而是重新申请一块更大的新内存,并把现有容器中的元素逐个复制过去,然后销毁旧的内存。这时原有指向旧内存空
间的迭代器已经失效,所以当操作容器时,迭代器要及时更新。
(牛客网@MyGoodHelper)
器使用一个内存分配器对象来动态地处理它的存储需求。
vector维护一个线性空间,不论元素的类别如何,普通指针都可以作为vector的迭代器
vector :: iterator it1;
vector::iterator it2;
显然it1是int *类型,it2是Teacher *类型
当然,也有const_iterator和reverse_iterator
vector所采用的数据结构非常简单,线性连续空间,它以两个迭代器_Myfrist和_Mylast分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器_Myend指向整块连续内存空间的尾端
为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这边是容量的概念,
一个vector的容量永远大于或等于其大小,一旦容量等于其大小,就是满栈,下次再有新增元素,整个vector容器就得另觅居所。
所谓的另觅居所,就是要重新找一块内存空间,将目前的所有内容重新拷贝过去,此时,整个vector的首指针要改变指向。(见1.4)
支持对序列中的任意元素进行快速直接访问,甚至可以通过指针算述进行该操作。
操供了在序列末尾相对快速地添加/删除元素的操作。 只能从队尾插入元素
所以可以在API中看到,没有替换API,如果需要在中间插入或替换元素,则比较麻烦。
using namespace std;
#include;
vector():创建一个空vector
vector(int nSize):创建一个vector,元素个数为nSize
vector(int nSize,const t& t):创建一个vector,元素个数为nSize,且值均为t
vector(const vector&):复制构造函数
vector(begin,end):复制[begin,end)区间内另一个数组的元素到vector中
操作:
vector V1;
int arr[] = {1,2,3,4,5,6};
vector V2(arr,arr+sizeof(arr)/sizeof(int));
qDebug()<<"V2";
Print(V2);
vectorV3(V2.begin(),V2.end());
qDebug()<<"V3";
Print(V3);
vector V4(10,300);
qDebug()<<"V4";
Print(V4);
其中的Print函数会在之后介绍
vector的构造函数很有趣,我们看看API的解释:
vector(begin,end):复制[begin,end)区间内另一个数组的元素到vector中
前闭后开
vector V;
V.push_back(1);
V.push_back(2);
V.push_back(3);
V.push_back(4);
V.push_back(5);
V.push_back(6);
V.push_back(7);
vector V2(V.begin(),V.begin());
qDebug()<<"V2.size():"<
如何赋予相同的值,size大小只能是0;
void assign(int n,const T& x):设置向量中第n个元素的值为x
void assign(const_iterator first,const_iterator last):向量中[first,last)中元素设置成当前向量元素
具体操作
//赋值
vector V5;
V5.assign(V3.begin(),V3.end());
qDebug()<<"V5";
Print(V5);
void swap(vector&):交换两个同类型向量的数据
具体操作:
//互换
V4.swap(V5);
qDebug()<<"V4";
Print(V4);
qDebug()<<"V5";
Print(V5);
判断vector内是否为空
bool empty() const:判断向量是否为空,若为空,则向量中无元素
返回大小,容量,最大允许值
int size() const:返回向量中元素的个数
int capacity() const:返回当前向量所能容纳的最大元素值
int max_size() const:返回最大可允许的vector元素数量值
示例:
vectorv;
for(int i = 0;i<300;i++)
{
v.push_back(i);
}
qDebug()<<"V的容量"<
vectorv;
int * p = nullptr;
int num=0;
for(int i = 0;i<300;i++)
{
v.push_back(i);
if(p != &v[0])
{
p = &v[0];
num++;
}
}
qDebug()<<"更换地址次数:"<
解释程序:因为每次扩容,如果原始内存不够,会迁移内容到新的一块更大的内存中去,那么显然首地址的指向也会变好,if判断中的就是当vector扩容后,迁移地址的次数。
但是,当我们知道,vector即将放入300个数据,可以添加如下操作:reserve
vectorv;
v.reserve(300);
int * p = nullptr;
int num=0;
for(int i = 0;i<300;i++)
{
v.push_back(i);
if(p != &v[0])
{
p = &v[0];
num++;
}
}
qDebug()<<"更换地址次数:"<
一步到位,不需要其他变更地址的操作,节省空间和时间。
at(int i);//访问第i个元素,越界时,抛出out_of_range异常
operator[]//同上,越界时,直接报错
front();//返回容器中的第一个元素
back();//返回容器中的最后一个元素
insert(const_interator pos,int count,ele);//迭代器指向的位置pos插入count个元素ele
push_back(index);//在尾部插入元素index
void pop_back();//最后一个元素出队,删除最后一个元素
clear();//清空
erase(const_interator start,const_interator end);//删除迭代器从start到end之间的元素
erase(const_interator pos);//删除迭代器指向的位置的元素
clear();//删除容器中所有元素
查看接口,push_back()和pop_back(),第一个是从尾部压入,第二个是从尾部弹出,即先进后出。
但是pop_back()只能弹出,不会返回任何值。此处可以对比stack。
terator begin():返回向量头指针,指向第一个元素
iterator end():返回向量尾指针,指向向量最后一个元素的下一个位置
reverse_iterator rbegin():反向迭代器,指向最后一个元素
reverse_iterator rend():反向迭代器,指向第一个元素之前的位置
打印(正向遍历)
void Print(vector &V)
{
for(vector::iterator it = V.begin();it!=V.end();++it)
{
qDebug()<<*it<<" ";
}
}
(反向遍历)
void A_Print(vector &V)
{
for(vector::reverse_iterator ti = V.rbegin();ti!=V.rend();++ti)
{
qDebug()<<*ti<<" ";
}
}
此处只是简单的介绍了几种非常常见的vector的API
更加全面的API介绍请移步:
https://www.runoob.com/w3cnote/cpp-vector-container-analysis.html
因为vector中没有封装find函数,所以只能使用algorithm中的find函数:
迭代器 = find(vector迭代器begin,vector迭代器end,要找的值(类型为vector中元素类型));
vector V;
V.push_back(1);
V.push_back(2);
V.push_back(3);
V.push_back(4);
V.push_back(5);
V.push_back(6);
V.push_back(7);
vector::iterator B = V.begin();
vector::iterator Result = std::find(V.begin(),V.end(),*B);
下面用LeetCode的一道题感受一下
结题思路都很简单,前序第一个一定是根结点,在中序中找到根结点,左侧全是左子树,右侧全是右子树。递归即可。
class Solution {
public:
TreeNode* buildTree(vector& preorder, vector& inorder) {
return BuildProcess(preorder.begin(),preorder.end(),inorder.begin(),inorder.end());
}
TreeNode* BuildProcess(vector::iterator PreBegin, vector::iterator PreEnd,vector::iterator InBegin, vector::iterator InEnd)
{
if(InBegin == InEnd)
return nullptr;
TreeNode* Root = new TreeNode(*PreBegin);//前序第一个元素必然是根结点
vector::iterator Proot = find(InBegin,InEnd,*PreBegin);
Root->left = BuildProcess(PreBegin+1,PreBegin+1+(Proot-InBegin),InBegin,Proot);
Root->right = BuildProcess(PreBegin+1+(Proot-InBegin),PreEnd,Proot+1,InEnd);
return Root;
}
};
迭代器就是特殊的指针。
而vector中的begin是指向vector第一个元素的,即下标为0,但是end指向的是最后一个元素后面的
qDebug()<<*V.end();
使用vector的时候一定要注意。
从一道题目入手,我们来看看vector在删除过程中会发生什么样的变化
下列哪段代码是实现在一个vector中查找匹配值value并删除:
erase()函数的特点就是删除当前的元素,并自动指向下一个元素,所以删除完这个结点后,不需要自增,迭代器已经指向了下一个内容。
对于deque,双向队列也是如此。
使用erase后,后面每个元素的迭代器都会失效,但是后面每个元素都会往前移动一个位置
就像本题一样,如果继续自增,显然是会错过一个内容。erase指向下一个有效迭代器,所以不要自增
首先对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效。那为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了10个元素,现在又要添加一个元素到容器中,但是内存中紧跟在这10个元素后面没有一个空闲空间,而vector的元素必须顺序存储一边索引访问,所以我们不能在内存中随便找个地方存储这个元素。于是vector必须重新分配存储空间,用来存放原来的元素以及新添加的元素:存放在旧存储空间的元素被复制到新的存储空间里,接着插入新的元素,最后撤销旧的存储空间。这种情况发生,一定会导致vector容器的所有迭代器都失效。我们看到实现上述所说的分配和撤销内存空间的方式以实现vector的自增长性,效率是极其低下的。为了使vector容器实现快速的内存分配,实际分配的容器会比当前所需的空间多一些,vector容器预留了这些额外的存储区,用来存放新添加的元素,而不需要每次都重新分配新的存储空间。你可以从vector里实现capacity和reserve成员可以看出这种机制。
1.当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。
2.当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时begin和end操作返回的迭代器都会失效。
3.当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。
(来源:https://www.cnblogs.com/llxblogs/p/7472241.html)