《STL源码剖析》读书笔记---第4章 序列式容器

序列式容器:

array(build-in)、vector、heap(以算法形式呈现)、priority-queue、list、slist(非标准)、deque、stack(配接器)、queue(配接器)

所谓序列式容器,其中的元素都可序,但是未必有序

---------------------------------------------------------------------------------------------------

vector:

vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。


vector的迭代器:

vector的迭代器是普通指针,其型别是Random Access Iterators:

template<class T ,class Alloc = alloc>
class vector{
public :
	typedef	T	value_type ;
	typedef value_type* iterator ; ;//vector的迭代器是普通指针
...
} ;

vector的数据结构:

vector采用的数据结构:线性连续空间。它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端(vector本身就是内含这三个东东)

template<class T,class Alloc = alloc>
class vector{
...
protected :
	iterator start ; //表示目前使用空间的头
	iterator finish ; // 表示目前使用空间的尾
	iterator end_of_storage ; //表示目前可用空间的尾
}

PS:由于经常在论坛上面逛,发现有很多人对vector的认识进了一个误区:1、vector不是用自身来储存元素的。2、一个空的vector,经sizeof(vector)的结果应该为0。

第一点:首先从书上的源码来看,vector并不是用自身来储存数据的,要是这样的话vector会变得很大。vector只是从堆那里获得内存,并用三个迭代器分别指向这块内存相应的位置,并进行管理。其次,如果用vector自身来储存元素,也难以达到重新配置空间等目的。

第二点:一个空的vector,并不指是vector本身是空的,指的是vector没有管理一块用于储存元素的内存。vector本身的大小是固定的,它内含三个迭代器,分别是start、finish、end_of_storage外加由于内存对齐需要填充的空位,以上组成了vector的大小。


------------------------------------------------------------------------------------------------

list:

list的节点(node)

list本身和list的节点是不同的结构,需要分开设计。以下是STL list的节点(node)结构:

template<class T>
struct __list_node{
	typedef	void *void_pointer ;
	void_pointer prev ; //型别为void * 。其实可设为__list_node<T>*
	void_pointer next ;
	T data ;
}
//由此结构可以看出,这是一个双向链表

list的迭代器:

由于STL list是一个双向链表(double linked-list),迭代器必须具备前移、后移的能力,所以list提供的是Bidirectional Iterators。

一个重要的性质:插入操作和接合操作都不会造成原有的list迭代器失效。甚至list的元素删除操作也只有“指向被删除元素”的那个迭代器失效,甚至迭代器不受任何影响。


template<class T,class Ref,class Ptr>
struct __list_iterator<T,T&,T*>{
	typedef	__list_iterator<T,T&,T*>	iterator ;
	typedef	__list_iterator<T,Ref,Ptr>	self ;

	typedef	bidirectional_iterator_tag	iterator_category ;
	typedef	T	value_type ;
	typedef	Ptr	pointer ;
	typedef	Ref	reference ;
	typedef	__list_node<T>*	link_type ;
	typedef	size_t	size_type ;	
	typedef	ptrdiff_t	difference_type ;

	link_type	node ; //迭代器内部当然要有一个普通指针,指向list的节点

	//construct
	__list_iterator(link_type x) : node(x) {} 
	__list_iterator() {}
	__list_iterator(const iterator &x):node(x.node) {}

	bool operator==(const self &x) const { return node == x.node ;}
	bool operator!=(const self &x) const { return node != x.node ;}
	//以下对迭代器取值(dereference),取的是节点的数据值
	reference operator*() const { return (*node).data ;}	

	//以下是迭代器的成员存取(member access)运算子的标准做法
	pointer operator->() const { return &(operator*()) ;}	

	//对迭代器累加1,就是前进一个节点
	self& operator++(){
		node = (link_typde)((*node).next) ;
		return *this ;
	}

	self& operator++(int){
		self tmp = *this ;
		++*this ;
		return tmp ;
	}

	//对迭代器递减1,就是后退一个节点
	self& operator--(){
		node = (link_type)((*node).prev) ;
		return *this ;
	}

	self& operator--(int){
		self tmp = *this ;
		--*this ;
		return tmp ;
	}
}



list的数据结构:

SGI list不仅是一个双向链表,而且还是一个环状双向链表。所以它只需要一个指针,便可以完整表现整个链表:

template<class T,class Alloc = alloc> //缺省使用alloc为配置器:w
class list{
protected :
	typedef	__list_node<T> list_node ;
public  :
	typedef	list_node* link_type ;

protected :
	link_type node ; //只要一个指针,便可以表示整个环状双向链表
};

如果让指针node指向刻意置于尾端的一个空白节点,node便能符合STL对于“前闭后开”区间的要求,成为last迭代器。图片请参考书本P132。

由于node的作用,以下几个函数便都可以轻易完成:

iterator begin() {return (link_type)((*node).next) ;}

iterator end() {return node ;}

bool empty() const {return node->next == node ;}

size_type size() const {
	size_type result = 0 ;
	distance(begin(),end(),result) ;
	return result ;
}

//取头节点的内容(元素值)
reference front() {return *begin() ;}
//取尾节点的内容(元素值)
reference back() {reeturn *(--end()) ;}


--------------------------------------------------------------------------------------------

deque:

vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作。

deque和vector的最大差异,一在于deque允许于常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓的容量(capacity)观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。换句话说,像vector那样“因旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque是不会发生的。也因此,deque没有必要提供所谓的空间保留(reserve)功能。(事实deque也会“因旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”,只不过是进行这些操作的对象不是被储存的元素,而是作为中控器的map)。


deque的中控器:

deque系由一段一段的定量连续空间构成。一旦有必要在dequer前端或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。deque采用一块所谓的map作为主控。这里所谓map是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。

template<class T, class Alloc = alloc, size_t BufSiz = 0>
class deque{
public :
	typedef	T value_type ;
	typedef	value_type* pointer ;
	...
protected :
	//元素的指针的指针(pointer of pointer of T)
	typedef	pointer* map_pointer ; //其实就是T**

protected :
	map_pointer map ; //指向map,map是块连续空间,其内的每个元素
					  //都是一个指针(称为节点),指向一块缓冲区
	size_type map_size ;
	...
} ;

图:


deque的迭代器:

template<class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator{ //未继承std::iterator
	typedef	__deque_iterator<T,T&,T*,BufSize>	iterator ;
	typedef	__deque_iterator<T,const T&,const T*,BufSize>	const_iterator ;
	static	size_t	buffer_size() {return __deque_buf_size(BufSize,sizeof(T)) ;} 

	//未继承std::iterator,所以必须自行撰写五个必要的迭代器相应型别
	typedef	random_access_iterator_tag	iterator_category ;
	typedef	T	value_type ;
	typedef	Ptr	pointer ;
	typedef	Ref	reference ;
	typedef	size_t	size_type ;
	typedef	ptrdiff_t	difference_type ;
	typedef	T**	map_pointer ;

	typedef	__deque_iterator	self ;

	//保持与容器的联结
	T *cut ; //此迭代器所指之缓冲区中的现行(current)元素
	T *first ; //此迭代器所指之缓冲区的头
	T *last ;	//此迭代器所指之缓冲区的尾(含备用空间)
	map_pointer node ; //指向管控中心
	...
} 
《STL源码剖析》读书笔记---第4章 序列式容器_第1张图片

《STL源码剖析》读书笔记---第4章 序列式容器_第2张图片



deque的数据结构:

deque除了维护一个先前说过的指向map的指针外,也维护start,finish两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素(下一个位置)。

template<class T, class Alloc = alloc, size_t BufSiz = 0>
class deque{
public :
	typedef	T	value_type ;
	typedef	value_type*	pointer ;
	typedef	size_t	size_type ;

public :
	typedef	__deque_iterator<T,T&,T*,BufSiz>	iterator ;

protected :
	//元素的指针的指针(pointer of pointer of T)
	typedef	pointer*	map_pointer ;

protected:
	iterator	start ; //表现第一节点
	iterator	finish ; //表现最后一个节点

	map_pointer	map ; //指向map,map是块连续空间,其每个元素都是个指针,指向一个节点(缓冲区)
	size_type	map_size ; //map内有多少指针
	...
} ;


--------------------------------------------------------------------------------------------

stack:

stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口。stack允许新增元素,移除元素、取得最顶端元素。但除了最顶端外,没有任何其它方法可以存取stack的其它元素。换言之,stack不允许有遍历行为

由于stack系以底部容器完成其所有工作,而具有这种“修改某物接口,形成另一种风貌”之性质者,称为adapter(配接器)。

template<class T, class Sequence = deque<T> >
class stack{

	friend bool operator== __STL_NULL_TMPL_ARGS(const stack& , const stack&) ;
	friend bool operator< __STL_NULL_TMPL_ARGS(const stack& , const stack&) ;

public :
	typedef	typename Sequence::value_type value_type ;
	typedef	typename Sequence::size_type size_type ;	
	typedef	typename Sequence::reference reference ;
	typedef	typename Sequence::const_reference	const_reference ;

protected:
	Sequence e ; //底层容器

public :
	//以下完全利用Sequence c 的操作,完成stack的操作
	bool empty() const {return c.empty() ;} 
	size_type size() {return c.size();}
	reference top() {return c.back();}
	const_reference top() const {return c.back();}
	
	//deque是两头可进出,stack是末端进,末端出。
	void push(const value_type& x) {c.push_back(x) ;}
	void pop() {c.pop_back() ;}
	
} ;
除了以deque作为底层容器之外,list也可以作为stack的底层容器。


--------------------------------------------------------------------------------------------

queue:

queue是一种先进先出(First In First Out,FIFO)的数据结构。它有两个出口。queue允许新增元素、移除元素、从最底端加入元素、取得最顶端元素。与stack相似,queue不允许遍历行为,因此也没有迭代器

template<class T, class Sequence = dique<T> >
class queue{
	
public :	
	typedef	typename Sequence::value_type value_type ;
	typedef	typename Sequence::size_type size_type ;
	typedef	typename Sequence::reference reference ;
	typedef	typename Sequence::const_reference const_reference ;

protected :
	Sequence c ; //底层容器

public :
	//以下完全利用Sequence c的操作,完成queue的操作
	bool empty() const {return c.empty();}
	size_type size() const {return c.size();}
	reference front() const {return c.front();}
	const_reference front() const {return c.front();}

	//deque是两头可进出,queue是末端进,前端出。
	void push(const value_type &x) {c.push_back(x) ;} 
	void pop() {c.pop_front();}
} ;
除了以deque作为底层容器之外,list也可以作为queue的底层容器。

-----------------------------------------------------------------------------------------------


heap:

heap并不归属于STL容器组件,它是个幕后英雄,扮演priority queue的助手。binary max heap适合作为priority queue的底层机制。

complete binary tree是一个vector/array和一组heap算法构成。

heap测试实例:

#include<vector>
#include<iostream>
#include<algorithm> //heap algorithm


using namespace std ;

int main(void)
{
	{
		//test heap(底层以vector完成)
		int ia[9] = {0,1,2,3,4,8,9,3,5};
		vector<int> ivec(ia,ia+9) ;

		make_heap(ivec.begin(),ivec.end()) ;
		for(int i = 0 ; i < ivec.size() ; ++i)
		{
			cout << ivec[i] << ' ' ;
		}
		cout << endl ;

		ivec.push_back(7) ;
		push_heap(ivec.begin(),ivec.end()) ;
		for(int i = 0 ; i < ivec.size() ; ++i)
		{
			cout << ivec[i] << ' ' ;
		}
		cout << endl ;

		pop_heap(ivec.begin(),ivec.end()) ;
		cout << ivec.back() << endl ;
		ivec.pop_back() ;

		for(int i = 0 ; i < ivec.size() ; ++i)
		{
			cout << ivec[i] << ' ' ;
		}
		cout << endl ;

		sort_heap(ivec.begin(),ivec.end()) ;
		for(int i = 0 ; i < ivec.size() ; ++i)
		{
			cout << ivec[i] << ' ' ;
		}
		cout << endl ;
	}

	{
		//test heap (底层以array 完成)
		int ia[9] = {0,1,2,3,4,8,9,3,5} ;
		make_heap(ia,ia+9);


		sort_heap(ia,ia+9) ;
		for(int i = 0 ; i < 9 ; ++i)
		{
			cout << ia[i] << ' ' ;
		}
		cout << endl ;
	}

	{
		//test heap (底层以array完成)
		int ia[6] = {4,1,7,6,2,5} ;
		make_heap(ia,ia+6) ;
		for(int i = 0 ; i < 6 ; ++i)
		{
			cout << ia[i] << ' ' ;
		}
		cout << endl ;
	}
	return 0 ;
}


priority_queue:

priority_queue是一个拥有权值观念的queue,它允许加入新元素、移除旧元素、审视元素值等功能。由于这是一个queue,所以只允许在底端加入元素,并从顶端取出元素,除此之外别无其它存取元素的途径。与queue不同,priority_queue缺省情况下是以vector为底层容器,并加上heap处理规则,因此,STL priority_queue往往不被归类为container(容器),而被归类为container adapter


template<class T, class Sequence = vector<T>, class Compare = less<typename Sequence::value_type> >
class priority_queue{
public :
	typedef	typename Sequence::value_type value_type ;
	typedef	typename Sequence::size_type size_type ;
	typedef	typename Sequence::reference reference ;
	typedef	typename Sequence::const_reference const_reference ;

protected :
	Sequence c ; //底层容器
	Compare comp ; //元素大小比较标准

public :
	priority_queue() : c() {}
	explicit priority_queue(const Compare &x) : c(),comp(x) {}

	//以下用到的make_heap(),push_heap(),pop_heap()都是泛型算法
	//注意,任一个构造函数都立刻于底层容器内产生一个implicit representation heap
	template<class InputIterator>
	priority_queue(InputIterator first,InputIterator last, const Compare &x)
		:c(first,last),comp(x) {make_heap(c.begin(),c.end(),comp) ;}

	template<class InputIterator>
	priority_queue(InputIterator first,InputIterator last, const Compare &x)
		:c(first,last) {make_heap(c.begin(),c.end(),comp) ;}

	bool empty() const { return c.empty();}
	size_type size() const {return c.size();}
	const_reference top() const {return c.front();}
	void push(const value_type& x){
		__STL_TRY{
			//push_heap是泛型算法,先利用底层容器的push_back()将新元素
			//推入末端,再重排heap。
			c.push_back(x) ;
			push_heap(c.begin(),c.end(),comp) ; //push_heap是泛型算法
		}
		__STL_UNWIND(c.clear()) ;
	}

	void pop() {
		__STL_TRY{
			//pop_heap是泛型算法,从heap内取出一个元素。它并不是真正将元素
			//弹出,而是重排heap,然后再以底层容器的pop_back()取得被弹出
			//的元素。
			pop_heap(c.begin(),c.end(),comp) ;	
			c.pop_back() ;
		}
		__STL_UNWIND(c.clear()) ;
	}
} ;


priority_queue测试实例:

#include<queue>
#include<iostream>
#include<algorithm>

using namespace std ;

int main(void)
{
	//test priority queue...
	int ia[9] = {0,1,2,3,4,8,9,3,5} ;
	priority_queue<int> ipq(ia,ia+9) ;

	cout << "size= " << ipq.size() << endl ;

	for(int i = 0 ; i < ipq.size() ; ++i)
	{
		cout << ipq.top() << ' ' ;
	}
	cout << endl ;

	while(!ipq.empty())
	{
		cout << ipq.top() << ' ' ;
		ipq.pop() ;
	}
	cout << endl ;

	return 0 ;
}

-------------------------------------------------------------------------------------------------------

slist:

STL list是个双向链表。SGI STL另提供一个单向链表slist。slist和list的主要差别在于,前者是迭代器属于单向的Forward Iterator,后者的迭代器属于双向的Bidirectional Iterator。


slist的节点:

//单向链表的节点基本结构
struct __slist_node_base
{
	__slist_node_base* next ;
} ;

//单向链表的节点结构
template<class T>
struct __slist_node : public __slist_node_base
{
	T data ;
} ;

//全局函数:已知某一节点,插入新节点于其后
inline	__slist_node_base* __slist_make_link(
		__slist_node_base* prev_node,
		__slist_node_base* new_node)
{
	//令new节点的下一节点为prev节点的下一节点
	new_node->next = prev_node->next ;
	prev_node->next = new_node ; //令prev节点的下一节点指向new节点
	return new_node ;
} 

//全局函数:单向链表的大小(元素个数)
inline size_t __slist_size(__slist_node_base* node)
{
	size_t result = 0 ;
	for(; node != 0 ; node = node->next)
	{
		++result ;
	}
	return result ;
}


slist的迭代器:

//单向链表的迭代器基本结构
struct __slist_iterator_base //没有继承那个标准的iterator,所以要自己定义
{
	typedef	size_t	size_type ;
	typedef	ptrdiff_t	difference_type ;
	typedef	forward_iterator_tag iterator_category ; /

	__slist_node_base* node ; //指向节点的基本结构
} ;

//单向链表的迭代器结构
template<class T, class Ref ,class Ptr>
struct __slist_iterator : public __slist_iterator_base
{
	typedef	__slist_iterator<T,T&,T*>	iterator ;
	typedef	__slist_iterator<T,const T&,const T*>	const_iterator ;
	typedef	__slist_iterator<T,Ref,Ptr>	self ;


	typedef	T	value_type ;
	typedef	Ptr	pointer ;
	typedef	Ref	reference ;
	typedef	__slist_node<T> list_node ;


	//定义了operator++(),operator++(int),没有实现operator--,因为是正向迭代器
	.....
} ;


slist的数据结构:

template<class T, class Alloc = alloc>
class slist
{
public :
	typedef	T	value_type ;
	typedef	value_type*	pointer ; 
	typedef	const	value_type*	const_pointer ;
	typedef	value_type&	reference ;
	typedef	const value_type& const_reference ;
	typedef	size_t	size_type ;
	typedef	ptrdiff_t	difference_type ;


	typedef	__slist_iterator<T,T&,T*>	iterator ;
	typedef	__slist_iterator<T,const T&,const T*> const_iterator ;

private :
	typedef	__slist_node<T>	list_node ;
	typedef	__slist_node_base	list_node_base ;
	typedef	__slist_iterator_base	iterator_base ;
	typedef simple_alloc<list_node,Alloc> list_node_allocator ;

	static	list_node* create_node(const value_type& x)
	{
		list_node* node = list_node_allocator:;allocate() ; //配置空间
		__STL_TRY{
			construct(&node->data,x) ;
			node->next = 0 ;
		}
		__STL_UNWIND(list_node_allocator:;deallocate(node)) ;
		return node ;
	}

	static void destroy_node(list_node* node)
	{
		destroy(&node->data) ; //将元素析构	
		list_node_allocator::deallocate(node) ; //释放空间
	}

private :
	list_node_base head  ; //头部。注意,它不是指针,是实物
			

public:
	slist() {head.next = 0 ;} 
	~slist(){clear() ;}

public :
	iterator begin() {return iterator((list_node*)head.next) ;}
	iterator end() {return iteator(0) ;}
	iterator size() {const __slist_size(head.next) ;}
	bool empty() const {return head.next == 0 ;} 

	//两个slist互换:只要将head交换互指即可
	void swap(slist &L)
	{
		list_node_base* tmp = head.next;
		head.next = L.head.next ;
		L.head.next = tmp ;
	}

public :
	//取头部元素
	reference front() {return ((list_node*)head.next)->data ;}

	//从头部插入元素(新元素成为slist的第一个元素)
	void push_front(const value_type& x)
	{
		__slist_make_link(&head,create_node(x)) ;
	}

	//注意,没有push_back()
	
	//从头部取走元素(删除之)。修改head
	void pop_front()
	{
		list_node* node = (list_node*)head.next ;
		head.next = node->next ;
		destroy_node(node);
	}
	.....
}  ;



你可能感兴趣的:(《STL源码剖析》读书笔记---第4章 序列式容器)