STL——序列式容器
一、容器概述与分类 1. STL容器即是将运用最广的一些数据结构实现出来。常用的数据结构有array, list, tree, stack, queue, hash table, set, map……等等。根据“数据在容器中的排列”特性,这些数据结构分为序列式和关联式两种。本篇讨论序列式容器。
这里所谓的衍生,并非派生关系,而是内含关系。例如heap内含一个vector,priority-queue内含一个heap,stack和queue都内含一个deque,set/map/multiset/multimap都内含一个RB-tree,hash_x都内含一个hashtable。
2. 序列式容器 所谓的序列式容器,其中的元素都可序(ordered,有位置属性),但未必有序(sorted,值未必有序)。C++ 语言本身提供了一个序列式容器array,STL另外再提供vector,list, deque, stack, queue, priority-queue等等序列式容器。其中stack和queue由于只是将deque改头换面而成,技术上被归类为一种配接器(adapter)。
二、Vector
1. vector 概述
vector 的数据安排以及操作方式,与array非常相似。两者唯一差别在于空间运用的灵活性。array是静态空间、一旦配置了就不能改变,要修改空间大小,只能通过“配置新空间/数据移动/释还旧空间”这种成本很高的方法来进行。vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新的元素。
2. 要使用vector,必须先包括<vector>,但SGI STL 将vector实现于更底层的<stl_vector.h>。 参见相关源码。
3. vector 迭代器 vector维护的是一个连续显性空间,所以不论其元素型别为何,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector迭代器所需要的操作行为,如 operator*, operator->, operator++, operator--, operator+, operator-,operator+=, operator-=,普通指针天生就具备。vector支持随机存取,而普通指针正有着这样的能力。所以,vector提供的是Random Access Iterators。
4. vector数据结构 vector所采用的数据结构非常简单:线性连续空间。它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围(容器大小),并以迭代器end_of_storage指向整块连续空间(含备用空间,容器容量)的尾端。
5. vector的构造与内存管理:constructor,push_back
vector缺省使用alloc(第二章)作为空间配置器,并据此另外定义了一个data_allocator,为的是更方便以元素大小为配置单位:
template <class T, class Alloc = alloc>
class vector
{
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
....
};
于是,data_allocator::allocate(n) 表示配置n个元素空间。
vector提供许多constructors,其中一个允许我们指定空间大小及初值:
// fill_initialize(填充并予以初始化)-> 调用allocate_and_fll(配置而后填充)->
// 调用uninitialized_fill_n(), uninitialized_fill_n()会根据第一参数的型别特性(type traits),决定
// 使用算法fill_n() 或反复调用construct() 来完成任务(2.2.3节,图2-1),参见相关源码
vector(size_type n, const T& value) { fill_initialize(n, value); }
当我们以push_back() 将新元素插入于vector尾端是,函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish,使vector变大。如果没有备用空间了,就扩充空间(重新配置、移动数据、释还原空间)。参见相关源码。
注意:所谓动态增加大小,并不是在原空间之后连续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原始内容之后构造新元素,并释还原空间。因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。这是程序员易犯的一个错误,务需小心。
6. vector 的元素操作:pop_back,erase,clear, insert
(1)pop_back:将尾端标记往前移一格,表示将放弃尾端元素;释还尾端元素。
(2)erase(first, last):将last之后的n个元素赋值到以first为起点的区间内(copy(last, finish, first);),释还元素;
(3)insert操作图如下:
三、List
1. list概述 相较于vector的连续线性空间,list就显得复杂许多,它的好处是每次插入或删除一个元素,就配置或释还一个元素空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素移除,list永远是常数时间。
2. list的节点 参见相关源码
3. list的迭代器 list 不再能够像vector一样一普通指针作为迭代器,因为其节点不保证在存储空间中连续存在。list 迭代器必须有能力指向list的节点,并有能力进行正确的递增、递减、取值、成员存取等操作。所谓“list迭代器正确的递增、递减、取值、成员取用”操作是指,递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取用的是节点的成员。由于STL list 是一个双向链表,迭代器必须具备前移、后移的能力,所以list提供的是Bidirectional iterators。
list有一个重要性质:插入操作和接合操作都不会造成原有的list迭代器失效。这在vector是不成立的,因为vector 的插入操作可能造成容器重新配置,导致原有的迭代器全部失效。甚至list的元素删除操作,也只有“指向被删除元素”的那个迭代器失效,其他迭代器不受任何影响。
list迭代器的设计,参见相关源码
4. list 的数据结构
SGI list 不仅是一个双向链表,而且还是一个环状双向链表。所以它只需一个指针,便可完整表现整个链表。如果让指针node指向刻意置于尾端的一个空白节点,node便能符合STL 对于“前闭后开”区间的要求,成为last迭代器。
5. list的构造与内存管理:constructor、push_back、insert
list 缺省使用alloc 作为空间配置器,并据此另外定义了一个list_node_allocator,为的是更方便地以节点大小为配置单位:
template <class T, class Alloc = alloc>
class list
{
protected:
typedef __list_node<T> list_node;
// 专属之空间配置器,每次配置一个节点大小
typedef simple_alloc<list_node, Alloc> list_node_allocator;
....
};
push_back() 函数内部调用insert()。
6. list 的元素操作: push_front, push_back, erase, pop_front, pop_back, clear, remove, unique, splice, merge, reverse, sort
list 内部提供一个所谓的迁移操作(transfer):将某连续范围的元素迁移到某个特定位置之前。技术上很简单,节点间的指针移动而已。 参见相关源码
四、deque
1. deque 概述
vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。所谓双向开口,意思是可以在头尾两端分别作元素的插入和删除操作。vector当然也可以在头尾两端进行操作,但其头部操作效率奇差,无法被接受。
deque和vector最大差异,一在于deque允许于常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量(capacity)概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。
虽然deque也提供Random Access Iterator,但它的迭代器并不是普通指针,其复杂度和vector不可以道里计。因此,除非必要,我们应尽可能选择使用vector而非deque。
2. deque系由一段一段的定量连续空间构成。一旦有必要在deque的前段或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。deque的最大任务,便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随机存取接口。避开了“重新配置、复制、释放”的轮回,代价则是复杂的迭代器架构。
deque采用一块所谓的map(注意,不是STL的map容器)作为主控,这里的map是一小块连续空间,其中每个元素都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的存储空间主体。SGI STL 允许我们指定缓冲区大小,默认值0表示将使用512bytes缓冲区。map其实是一个T**,它是一个指针,所指之物又是一个指针。
参见deque容器相关源码;
3. deque 的迭代器
参见deque迭代器相关源码
4. deque的数据结构 deque除了维护一个先前说过的指向map的指针外,也维护start,finish两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素(的下一位置)。此外,它当然也必须记住目前的map大小。因为一旦map所提供的节点不足,就必须重新配置更大的一块map。 参见deque容器相关源码
5. deque的构造与内存管理:ctor, push_back, push_front deque自行定义了两个专属的空间配置器:
protected:
// 专属空间配置器,每次配置一个元素大小
typedef simple_alloc<value_type, Alloc> data_allocator;
// 专属空间配置器,每次配置一个指针大小
typedef simple_alloc<pointer, Alloc> map_allocator; // 并提供一个constructor如下:
deque(int n, const value_type& value)
:start(), finish(), map(), map_size()
{
// 参见相关源码
fill_initialize(n, value);
}
注意,在create_map_and_nodes()函数中:
(1)需要节点数 = ( 元素个数 / 每个缓冲区可容纳的元素个数) + 1 ;
(2)一个map要管理几个节点。最少8个,最多是“所需节点数加2”(前后各预留一个,扩充时可用);
(3)nstart和nfinish 指向map所拥有之全部节点的最中央区段,保持在最中央(已初始化节点位于map节点列表最中央区段。),可使头尾两端的扩充能力一样大。每一节点可对应一个缓冲区。
push_back/push_front 参见相关源码
什么时候map需要重新整治?这个问题的判断由reserve_map_at_back() 和 reserve_map_at_front() 进行,实际操作则由reallocate_map() 执行。
参见相关源码。
6. deque 的元素操作:pop_back, pop_front, clear, erase, insert
泛型算法find() 寻找deque某个元素
注意,deque的最初状态(无任何元素时)保有一个缓冲区,因此,clear() 完成之后回复初始状态,也一样要保留一个缓冲区。
参见相关源码
五、stack
1. stack概述
stack是一种先进后出(First In Last Out, FILO)的数据结构。它只有一个出口。stack允许新增元素、移除元素、取得最顶端元素。但除了最顶端外,没有任何其它方法可以存取stack的其他元素。换言之,stack不允许有遍历行为。
2. stack定义 以某种既有容器作为底部结构,将其接口改变,使之符合“先进后出”的特性,形成一个stack,是很容易做到的。deque是双向开口的数据结构,若以deque为底部结构并封闭其头端开口,便轻而易举地形成了一个stack。因此,SGI STL 便以deque作为缺省情况下的stack底部结构,stack的实现因而非常简单,源代码十分简短。 除了deque之外,list也可以作为stack的底部容器。
由于stack系以底部容器完成其所有工作,而具有这种“修改某物接口,形成另一种风貌”之性质者,称为adapter(配接器),因此,STL stack 往往不被归类为container(容器),而被归类为container/adapter。
3. stack没有迭代器 stack 所有元素的进出都必须符合“先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。stack不提供走访功能,也不提供迭代器。
六、queue
1. queue概述
queue 是一种先进先出(First In First Out, FIFO)的数据结构。queue允许新增元素、移除元素、从最底端加入元素、取得最顶端元素。但除了最底端可以加入、最顶端可以取出外,没有任何其他方法可以存取queue的其他元素。换言之,queue不允许有遍历行为。
2. queue定义 queue同样也是一种配接器,也没有迭代器。参见相关源码。
七、heap(隐式表述,implicit representation)
八、priority_queue
1. priority_queue 概述
顾名思义,priority_queue是一个拥有权值的queue,其内的元素并非依照被推入的次序排列,而是自动依照元素的权值排列(通常权值以实值表示)。权值最高者,排在最前面。缺省情况下priority_queue系利用一个max-heap完成,后者是一个以vector表现的complete binary tree(参见STL——heap结构及算法)。max-heap可以满足priority_queue所需要的“依权值高低自动递减排序”的特性。
2. 和queue一样,priority_queue 也被归类为container adapter。priority_queue的所有元素,进出都有一定的规则,只有queue顶端的元素(权值最高者),才有机会被外界取用。priority_queue不提供遍历功能,也不提供迭代器。
九、slist
1. slist 概述 STL list 是个双向链表。SGI STL 另提供了一个单向链表,名为slist。这个容器并不在标准规格之内。slist 和 list 的主要差别在于,前者的迭代器属于单向的Forward Iterator, 后者的迭代器属于双向的Bidirectional Iterator。由于slist 是一个单向链表,STL 默认的插入操作“将新元素插入于指定位置之前”对于slist来说并不适用,故而slist 特别提供了insert_after() 和 erase_after()供灵活运用。基于同样的(效率)考虑,slist 不提供push_back(), 只提供push_front()。(因为对于slist, push_back会导致遍历整个链表,才能找到最后一个节点的位置,执行插入;slist 默认提供链表头节点指针,push_front可以快速插入。)
2. slist 的节点 slist 节点和其迭代器的设计,架构上比list复杂许多,运用了继承关系,运用了继承关系,因此在型别转换上有复杂的表现。这种设计方式在第5章RB-tree将再一次出现。如下图:
slist node源码参见相关源码
3. slist 的迭代器
参见相关源码
注意:slist 没有实现operator--,因为这是一个forward iterator。
4. slist 的数据结构
参见相关源码
STL——序列式容器的更多相关文章
- STL序列式容器学习总结
STL序列式容器学习总结 参考资料:<STL源码剖析> 参考网址: Vector: http://www.cnblogs.com/zhonghuasong/p/5975979.html L ...
- 数据结构-STL序列式容器总结
根据序列在容器中的排列特性,将常见数据结构分为:序列式容器和关联式容器. 常见序列式容器有 1.array(build-in)c++內建 2.vector 3.heap(以算法方式呈现) 4.prio ...
- STL序列式容器
1.vector 空间运用的灵活性. 实现技术——关键是对大小的控制以及重新配置时的数据移动效率. 配置新空间.数据移动.释还旧空间 erase(int pos ...
- STL源码剖析读书笔记--第四章--序列式容器
1.什么是序列式容器?什么是关联式容器? 书上给出的解释是,序列式容器中的元素是可序的(可理解为可以按序索引,不管这个索引是像数组一样的随机索引,还是像链表一样的顺序索引),但是元素值在索引顺序的方向 ...
- STL源码剖析之序列式容器
最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...
- STL源码剖析——序列式容器#1 Vector
在学完了Allocator.Iterator和Traits编程之后,我们终于可以进入STL的容器内部一探究竟了.STL的容器分为序列式容器和关联式容器,何为序列式容器呢?就是容器内的元素是可序的,但未 ...
- STL源码剖析:序列式容器
前言 容器,置物之所也.就是存放数据的地方. array(数组).list(串行).tree(树).stack(堆栈).queue(队列).hash table(杂凑表).set(集合).map(映像 ...
- STL学习笔记(序列式容器)
Vector Vector是一个动态数组. 1.Vector的操作函数 构造.拷贝和析构 vector<Elem> c //产生一个空vector ,其中没有任何元素 vector< ...
- 7.5 C++基本序列式容器
参考:http://www.weixueyuan.net/view/6402.html 总结: vector可以理解为可以在两端插入.删除数据的数组,它提供了丰富的成员函数,用于操作数据. begin ...
随机推荐
- idea+maven无法自动加载jar包
没有配置maven的环境变量所致 执行mvn -version进行检测
- git之移除.idea
有时候不小心提交了.idea目录,git会一直track这个目录,可以通过一下命令移除: mv .idea ../.idea_backup rm -r .idea git rm -r .idea gi ...
- 容易出错的 if 语句
下面列举几个容易出错的if语句实例,如果后续还有新的发现,还会继续更新! 出错一:在括起控制表达式的括号后面加分号 ; ); printf("值为正"); 初次运行,感觉一切正常, ...
- Unity-------------------------关于GUI绘制的编程
转载:在这篇文章中我将给读者介绍Unity中的图形用户界面(GUI)编程.Unity有一个非常强大的GUI脚本API.它允许你使用脚本快速创建简单的菜单和GUI. 简介 Unity提供了使用脚本创建G ...
- nodejs基础 -- 常用工具util
util是nodejs的核心模块,提供常用函数的集合,用户弥补核心javascript的功能过于精简的不足 util.inherits 是一个实现对象间原型继承的函数 javascript的面向对象特 ...
- android http json请求3种不同写法
第一种: public static String invoke() { String result = null; try { final Str ...
- zookeeper ACL使用
生产环境中,经常会有多个项目使用zookeeper,例如多个hbase集群.每个项目搭建一套独立的zookeeper,无论从机器成本,还是运维成本,都是一笔额外的开销. 然而多项目,多集群共用zook ...
- 绘制你的第一个图表(jquery-flot-line-chart)
事前准备 首先, 请先确定你已经下载了Flot档案, 如果还没有的话可以先回到前一章 去下载. 这是你绘制的第一张图, 我们用最常用的折线图当例子, 折线图常被用来显示一段时间间隔趋势的走向, 常见的 ...
- springmvc接口ios网络请求
springmvc: application/json;charset=utf-8的ios网络请求: 后台使用 @RequestBody注解参数接收:
- jquery click事件,多次执行
用jquery绑定一个按钮click事件后,第一次点击后,一切正常,第二次点击,竟然执行两次,以后越来越多, 后来查看文档发现 jquery click 不是 替换原有的function 而是接 ...