栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

vector使用上的细节一览

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

vector使用上的细节一览

本文是写给那些,会使用vector,但是对vector的细节,了解不多的人。所以对于vector基本的操作如insert(),push_back(),emplace(),begin(),clear(),此处不再解释。本文重点介绍:vector的结构,vector的动态扩容机制,vector的使用场景,还有一些注意事项。

vector的结构:

vector即动态数组,它的动态,基于它的动态扩容机制。本质上它还是一个数组,是连续存储在内存空间中的,他在内存中的结构如下:

 

图2 vector 的三个指针,分别指向vector的三处关键节点

vector内部有三个迭代器维护着vector的缓存区域:

  • _M_start:它指向vector缓存区的第一个对象。

  • _M_finish:它指向vector缓存区中已插入的最后一个对象的下一个内存块。

  • _M_end_of_storage,它指向vector缓存区的末端(指向的是已分配内存的下一个块)。

vector扩容,获取大小,获取容量,判空,这些基础操作,都是根据这三个迭代器设计的。

_M_finish和 _M_end_of_storage指向的内存块是一样的时候,就会触发内存扩容。

vector的动态扩容:

_M_finish和 _M_end_of_storage指向的内存块是一样的时候,也就是当 vector 的大小和容量相等(size==capacity)时,再向其添加对象,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:

  1. 依据当前对象的个数,重新申请更大的内存空间(依据不同的编译器,可能为当前对象的1.5倍或者2倍);

  2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中(拷贝);

  3. 将旧的内存空间释放。

这也就解释了,为什么 vector 容器在进行插入后,与其相关的指针、引用以及迭代器可能会失效的原因。原先指向的空间已经被释放了。那么指针或者引用也就会失效。

同时可以预测,vector 扩容非常耗时的。为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间,以便后期使用。同时这个内存空间又不能过大,造成申请到的内存,迟迟得不到利用,造成内存的浪费。一个比较合理的值是1.5倍。具体为何选择1.5作为增长因子。在此处有详细的解释:(14条消息) STL中vector 扩容为什么要以1.5倍或者2倍扩容?bryant-xw的博客-CSDN博客vector扩容为什么是两倍

vector的使用场景:

由vector的结构特点:简单,顺序存储。决定了他查找的效率较优,而插入删除的效率较差,因为插入删除会造成多个对象的移动。因此它适用于:在某一特定范围内,经常进行查询,但是很少对这个范围进行扩大或减小。

比如:当班里有100个人,我们需要不断查看这100个人的某项数据,这时我们使用vector最好。因为这就是相对于某个范围。

注意事项: 1.vector insert() 与emplace()对比:

vector容器提供了 insert() 和 emplace() 这 2 个成员函数,用来实现在容器指定位置处插入对象。总体来说,insert()支持的插入方式更多,功能更加强大(insert()底层调用了emplace()),功能如下所示。而emplace()一次只能插入一个对象,但是支持直接传入classT的构造函数的参数,在要插入的位置直接构造对象。

insert()

insert() 函数的功能是在 vector 容器的指定位置插入一个或多个对象。该函数的语法格式有多种:

语法格式用法说明
iterator insert(pos,elem)在迭代器 pos 指定的位置之前插入一个新对象elem,并返回表示新插入对象位置的迭代器。
iterator insert(pos,n,elem)在迭代器 pos 指定的位置之前插入 n 个对象 elem,并返回表示第一个新插入对象位置的迭代器。
iterator insert(pos,first,last)在迭代器 pos 指定的位置之前,插入其他容器(不仅限于vector)中位于 [first,last) 区域的所有对象,并返回表示第一个新插入对象位置的迭代器。
iterator insert(pos,initlist)在迭代器 pos 指定的位置之前,插入初始化列表(用大括号{}括起来的多个对象,中间有逗号隔开)中所有的对象,并返回表示第一个新插入对象位置的迭代器。

下面的例子,演示了如何使用 insert() 函数向 vector 容器中插入对象:

#include
#include
using namespace std;
​
class Example{
    public:
    Example(int x,int y):a(x),b(y){
        cout<<"构造函数"<v;
    auto it=v.begin();
    Example e(3,2);
    Example x(3,2);
    vector arr={e,x};
    cout<<"insert(pos,elem):"< 
emplace() 

emplace() 用于在 vector 容器指定位置之前插入一个新的对象。

该函数的语法格式如下:

1.iterator emplace (const_iterator pos, args...);

其中,pos 为指定插入位置的迭代器;args... 表示与新插入对象的构造函数相对应的多个参数,args可以只填入一个对象本身,那么他会直接将该对象在指定位置拷贝;该函数会返回表示新插入对象位置的迭代器。关于args...,使用了C++11中的变参模板,此处不介绍。感兴趣的同学可以自行查找。

2.vector的初始化

初始化时需要注意,初始化后的vector,他的size==capcity。即他的每一个对象都非空。此时要给他增加对象,执行insert()或者emplace(),都会引起扩容。

vector vec1;//不带参数的构造函数初始化。
​
vector vec2(5);//带参数的构造函数初始化。
​
vector vec3(5,1);//带参数,且设置初始值的构造函数初始化。
​
vector vec4={1,2,3};//通过初始化列表初始化。
​
int arr[5]={1,2,3,4,5};
vector vec5(arr,arr+5);//通过数组地址初始化。
​
vector vec6(vec5);//通过同类型的vector初始化。
​
vectorvec7(vec6.begin(),vec6.end());//通过同类型vector的指定范围初始化(指定迭代器)。
​
//补充:二维数组初始化
int length=5;
int width=4;
vector>vec8(width,vector(length,0));//第二个参数称为匿名对象,他的生存周期,仅仅在这一行语句中
3.vector访问指向迭代器的对象
    vectorv(10);
    auto it=v.begin();
    cout<<(*it).a< 

通过迭代器访问对象,需要使用解引用符*,如果对象非内置类型,那么还需要加上()以访问对象的成员。

4.vector的迭代器失效

vecto迭代器是原生态类型的指针,迭代器失效,可以认为是指针失效,如果指针失效则是指针指向了一段非法的空间,说明该空间已经被释放了。由此可以推出是vector中的空间改变了引起的迭代器失效,那么便是vector中的接口操作改变了vector中的空间,在此归类出三种可能会引起迭代器失效的操作:

(1) 扩容操作可能会引起迭代器失效: vector中涉及到扩容的接口方法:resize() / reserve() / push_back() / insert() / assign() /emplace()/emplace_back()、下面演示insert()方法引起迭代器失效:

引起迭代器失效:

(2)vector之中的删除方法:erase()/pop_back()。下面演示erase()方法引起迭代器失效:

int main()
{  
    vectorv(2);
    auto it=v.end();
    Example e(2,3);
    v.erase(it);
    cout<<(*it).a< 

引起迭代器失效:

(3)swap()/clear(),也会引起迭代器失效。

解决迭代器失效:

在有可能引起迭代器失效的位置,重新对迭代器进行赋值。

int main()
{  
    vectorv(2);
    auto it=v.end();
    Example e(2,3);
    v.erase(it);
    it=v.end();
    cout<<(*it).a< 

小技巧:

当时用 erase(it++)删除it指向的对象时,迭代器会指向下一个对象,不会失效。

这是因为,后缀形式++运算符的内部实现类似:

const int int::operator++(int) //函数返回值是一个非左值型的,与前缀形式的差别所在。
{//函数带参,说明有另外的空间开辟   
    int oldValue = *this;  
    (*this)+=1;  // 增加1   
    return oldValue;  // 返回old的值
 }

它使用一个临时变量保存了原先的迭代器,再将it后移一位,并返回这个临时变量,这样删除该元素,不会使这个迭代器失效,因为这个时候它已经指向了下一个对象。

这样便可以解决迭代器失效的问题。

本blog的部分图片,文字参考:

C语言中文网:C语言程序设计门户网站(入门教程、编程软件)

vector 容器初始化的几种方式_xixi up!的博客-CSDN博客_vector容器初始化

vector迭代器失效_小明学编程~的博客-CSDN博客

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/873047.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号