对vector内部结构的解析(二)

3 篇文章 0 订阅
2 篇文章 0 订阅

    在上一篇中我们对vector的原理有了一个概念上的认识 , 在这一篇中我们将揭示vector内部的运作原理。(对vector内部结构的解析(一)

    首先我们必须明白vector是一个线性顺序表的结构 , 与数组非常相似 , 就是数据都是连续且按顺序储存在内存连续区域中的结构 , 与数组不同的是 , 当vector超出容量时会自动调节长度 , 这在上一篇文章中已经演示过了 , 这里要说的是它是如何增长的 。在这里先介绍一个名为allocator的模板类 , 这个类可以为对象分配足够大小的空间却不构造对象 , 分配完空间后该类会返回一个指向分配的开始地址的指针(指针类型与实例化的allocator的类型是一样的) , 要使用这个类必须导入memory这个头文件 。 下面看一个简单的例子:

	allocator<string> sa;   //将该模板类实例化为string类型
	string * p = sa.allocate(5);    //分配足以储存5个string类型的空间却不构造string , 返回指针
	string * q = p;         //另一个指针
	for( ; q != p + 5 ; q++){
		sa.construct(q , "It's a test.");    //构造5个string
	}
	for(string * ps = p ; ps != q ; ps++){
		cout << *ps << endl;    //打印
	}
	for( ; q != p ; q--){
		sa.destroy(q);    //对每个string调用析构函数
	}
	sa.deallocate(p , 5);    //回收5个string的空间
打印出5行 “It ' s a test."

    这个例子就能够告诉我们allocator这个类的基本原理 , 它能够分配足够的内存用于储存程序员指定数量的对象 , 却不会调用构造函数 , 即在构造这个对象之前该位置的内容是未定义的 , 调用construct构造对象后再调用destroy就能销毁对象 , 最后的deallocate是用来回收空间的。

    为什么要在这里说这个呢 , 其实vector内部就是这样分配的 , vector内部有三个指针(类型为用来实例化的指定类型) ,其实就是我们所说的迭代器 , 第一个指针名为start , 保存了顺序表的第一个元素的起始地址 。 第二个指针名为finish , 保存的是已经构造的最后一个元素的下一个地址 。第三个指针名为end_of_storage , 保存的是已分配的空间的末尾的下一个地址 , 用一张图来看(数字表示已经构造的对象)

    配合在上一篇中讲的内容 , 很容易就理解了这种数据结构 , size()函数时间上就是 finish - start , capacity() 实际上就是 end_of_storage - start 。每push_back一个对象 , 就调用一次 construct并把finish向后移一个单位 , 调用一次pop_back就调用一次destroy并把finish指针向前移一个单位。

    现在讲一下vector是如何动态增长的 , 当我们在push_back新对象时 , 如果size() 等于capacity()  , 说明分配的对象空间已经用完 , 这是vector会要求内存分配一块比原来大两倍的空间给它(使用allocate分配 , 再把原来的数据拷贝或移动进去 , 再把旧空间用deallocate回收) , 注意新分配的空间绝不一定是end_of_storage接下来的空间 , 这点一定要注意。


    为什么插入对象会消耗那么多时间(见第一篇) , 这是因为vector是顺序线性表结构 , 当在中间插入一个元素时 , 它就必须把后面的所有元素都向后移一位 , 给插如的元素留出位置 , 并不破坏线性顺序结构 , 请看下图示意:


    移位的操作可能会先调用用一个临时量保存对象 , 然后再调用destroy , 最后再调用construct , 这是一个很繁琐也很染费时间的过程 , 尤其是元素越多越浪费时间 , 插入的位置越前也越浪费时间 , 所以在vector中尽量不要用插入操作。

    至于迭代器实际上就是返回start和finish指针 , 因为vector本身是线性顺序表 , 元素全是一个接一个排列的 , 所以迭代器不需要重载++和--等符号 , 因为下一个地址一定就是需要的内存位置 。

    而对于erase操作 , 比较类似于insert , 实际上就是调用destroy把元素销毁 , 如果是最后一个元素 , 则erase效率会很高 , 但如果要销毁的元素在中间 , 那又是一件很麻烦的事情 , 因为erase后会出现一个空格  , 所以要把后面的所有元素都移上来 , 原理与插入是一样的。

    而上文最后介绍的resize则是一个判断 , 当要求保留大于现有个数时 , 调用construct(如果大于容量则先分配)构造多出来的对像 , 反之调用destroy销毁对象。

    本文只是让读者对vector内部有一个清楚的认识 , 具体的实现方法还需要参考源码。
  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值