STL一些容器的底层特点及实现。

序列式容器:

1.vector:

数据结构:

数据成员只有三个:1.iterator start;//指向目前正在使用的空间头 2.iterator end;//指向目前正在使用的空间尾 3.iterator end_of_storage;//指向可用空间尾

迭代器:

vector的迭代器属于随机访问型迭代器,迭代器类型就是原始指针,即,当value_type为T时,迭代器类型就为T*。

内存管理:

使用前文所说的alloc为默认空间配置器(alloc根据宏来决定自己是第一级配置器还是第二级配置器)。

当可用空间已满时:

此时若往中push_back或者insert,将会溢出。每次push或插入前,都会先判断finish是否达到end_of_storage.若未达到则插入,否则会调用insert_aux对整个内存空间进行扩展并重新移动。

若无可用空间时,首先判断当前大小,若为0,则需重新配置一个为1大小的空间。若不为0,则配置一个原两倍大小的空间。调用allocate配置2n个空间,首先将原来的n个移动到新空间,然后释放掉原空间的vector,并调整start,end与end_of_storage三个迭代器成员变量。

注意,由于insert_aux整体迁移了空间,原vector的迭代器全部失效。erase也是如此,因为erase中会调用destory掉iterator,之后这个iterator会成为野指针,任何针对它的操作都是非法的。因此必须通过erase的返回值来重新获取指向position位置的迭代器。

vector迭代器的失效情况:

1.当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。

2.当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时begin和end操作返回的迭代器都会失效。

3.当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。 

2.list

STL中,list不仅仅是一个双向链表,更是一个环状双向链表。

数据成员:

数据成员只有一个指针,该指针指向整个链表的尾端的空白节点,返回end()时即返回此节点,又由于是环状列表,因此begin()返回此节点的next指针所指向的节点。

迭代器:

list的迭代器属于双向迭代器。迭代器内部维护一个指针,该指针指向list的节点。

内存管理:

依然使用alloc作为空间配置器。list的空间管理简单很多,创建一个节点,然后直接在插入位置插入即可,不用考虑vector那样整体搬移的问题。

且由于插入只是指针的更改操作,不会造成insert或push后因内存搬移而造成的迭代器失效问题。但erase某个迭代器后,该迭代器成为野指针,是不可使用的了。

由于STL的sort只支持随机访问迭代器,因此list的sort算法需要封装到自己的数据结构中。

3.deque

vector使用的是纯连续空间,list使用的是完全不连续的空间。而deque使用的是连续空间+指针链接的形式。

首先用一段连续空间(称为map,但这个map不是RBTree那个map),map中每个元素为一个指针,指向另一端连续空间,称为缓冲区。缓冲区才是存储deque中元素的主体。

总体来说,map等价于一个T**,map[i]为一个T*,map[i][j]才是真正存储元素的位置。

数据成员:

一个指针map(T**),该map指向map主控区;一个map_size,表示map指向的主控区中能容纳多少个缓冲区指针,以及一个start指向第一缓冲区的第一个元素和一个finish迭代器指向最后一个缓冲区的最后一个元素的下一个位置。

迭代器:

deque的迭代器属于随机访问迭代器。迭代器维护4个成员变量:一个T*型cur,指向现行(current)元素;T*型first,指向cur元素所在缓冲区的头;T*型last,指向cur元素所在缓冲区的尾;T**型node,指向该缓冲区在主控区map中所对应的位置。

虽然deque的迭代器属于随机访问迭代器,但本质上并不是连续空间,因此在某些地方出现断层时需要跳转。

如在某个缓冲区的边界,此时++,则调用set_node()跳一个缓冲区,set_node重新设置迭代器的node变量,并根据新的node重新设置first和last,然后设置cur=first。

内存管理:

最开始配置内存时,需要的节点个数为(元素个数/每个缓冲区可容纳元素个数)+1个节点。如果所需要配置的节点数不足8个,则会配置8个,超过8个时,配置所需节点+2个节点(这两个防范在头插入与在尾插入,预留)。也就是max(8,num_nodes+2);deque中的start指向map中第二个节点(第一个是预留给头部插入的),finish指向倒数第二个节点(最后一个预留给尾部插入)。

在尾部插入时,如果尾部缓冲区只剩一个元素备用空间了,即finish(deque维护的指向最后一个元素所在缓冲区的迭代器).cur==finish.last-1,此时等价于只剩下finish.last了,而last一般是不放元素只用来表示开区间[,)中的)的,因此需要增加缓冲区。如果finish所在的位置已经是map尾了,则需要重新配置一块map,并将数据迁移后再插入。

在头部插入时同理。

deque迭代器失效问题:

1.在deque容器首部或者尾部插入元素不会使得任何迭代器失效。
2.在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。
3.在deque容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器失效。

猜你喜欢

转载自www.cnblogs.com/lxy-xf/p/11061851.html