C++ STL(第七篇:容器 -- list)

1、list的概述

相比較於 vector 的連續線型空間,list 就顯得複雜的多,它的好處是每次插入或刪除一個元素,就配置或釋放一個元素空間。因此,list對於空間的運用有絕對的精準,一點也不浪費。而且,對於任何位置的元素插入或元素移除,list永遠是常數時間。

list 和 vector 是兩個最常用被使用的容器 。什麼時機下最適合使用哪一種容器,我們最後進行整理。

list 本身和 list 的節點是不同的結構,需要分開設計。以下是 STL list 的節點結構:

template <class T>
struct __list_node
{
	typedef void* void_pointer;	//類型爲void* 其實可以設計成 T*
	void_pointer prev;
	void_pointer next;
	T data;
};

2、list 的迭代器

list 不再能夠像 vector 一樣用普通指針作爲迭代器,因爲其節點不保證在儲存空間中連續存在。 list 迭代器必須有能力指向 list 的節點,並有能力進行正確的遞增、遞減、取值、成員存取等操作。

由於 STL list 是一個雙向鏈表,迭代器必須具備前移、後移的能力,所以 list 提供的是 bidirectional iterators(雙向迭代器)類型

list 有一個重要性質:插入操作和 接合操作 都不會造成原有的 list 迭代器失效。這在 vector 是不成立的,因爲 vector 的插入操作可能造內存的重新配置,導致原有迭代器全部失效。甚至 list 的元素刪除操作也只有“指向被刪除元素” 的那個迭代器失效,其它迭代器不受任何影響。

list 迭代器的設計如下:

template<class T, class Ref, class Ptr>
struct __list_iterator
{
	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 size_t		size_type;
	
	typedef ptrdiff_t		difference_type;
	typedef __list_node<T>*	link_type;    	

	link_type	node;	//迭代器內部當然要有一個普通指針,指向list的節點
	
	bool 	operator==(const self& x) const;
	bool 	operator!=(const self& x) const;
	reference 	operator*() const;
	pointer 	operator->() const;
	self& 		operator++();
	self		operator++( int );
	self& 		operator--();
	self		operator--( int );
};

3、list的數據結構

SGI list 不僅是一個雙向鏈表,而且還是一個環狀雙向列表。所以它只要一個指針,便可以完整表現整個鏈表:

template<class T, class Alloc = alloc >
class list
{
protected:
	typedef	 __list_node<T>	list_node;
public:
	typedef  list_node*		link_type;
protected:
	link_type	node;	//只要一個指針,便可表示整個環狀雙向鏈表
};

如果讓指針 node 指向刻意置於尾端的一個空白節點,node便能符合STL 對於“前閉後開”區間的要求,稱爲 last 迭代器。這麼一來,以下幾個函數變都可以輕易完成:

iterator begin() { return (link_type)((*node).next); }	//返回頭部迭代器
iterator end() { return node;}							//返回尾部迭代器
bool empty() const { return node->next == node; }		//判空
reference front() { return *begin(); }					//返回第一個元素數據
reference back() { return *(--end()); }					//返回最後一個元素數據

4、list的其它操作

因爲 list 要分配和釋放單獨的一個節點,所以 list 提供了以下四個函數分別用來配置、釋放、構造、銷燬一個節點,當然這是底層的操作,我們是看不見的:

link_type 	get_node();					//配置一個節點並傳回
void 		put_node();					//釋放一個節點
link_type 	create_node(const T& x);	//產生一個節點,並構造對象
void 		destroy_node(link_type p);	//銷燬一個幾點,並析構對象

我們能看見的是,對於元素 和 迭代器 操作的接口,如下:

void	pop_front();				//移除頭節點
void	pop_back();					//移除尾節點
void	push_back( const T& x); 	//將新元素插入到 list 尾端,作爲尾節點
void	push_front( const T& x);	//將新元素插入到list,作爲頭節點
iterator	insert(...);			//多種形式,將元素插入到列表中
iterator	earse( iterator position);	//移除迭代器position所指節點

void	clear(); 					//清除所有節點
void	remove( const T& value);	//將數值爲value之所有元素移除
void	unique();					//移除數值相同的連續元素

由於list 是一個雙向環狀鏈表,只要我們把邊際條件處理好,那麼,在頭部或尾部插入元素,操作幾乎是一樣的,在頭部或尾部移除元素,操作也幾乎是一樣的。

list內部提供一個所謂的遷移操作(transfer):將某連續範圍的元素遷移到某個特定位置之前。技術上很簡單,節點間的指針移動而已。這個操作位其它的複雜操作如 splice,sort,merge 等奠定良好的基礎。

//將[first,last)內的所有元素移動到position之前
void	transfer( iterator position, iterator first, iterator last);	
void	splice(...);	//多種形式,接合操作
void	merge(...);		//合併操作,兩個list都必須先經過遞增排序
void	reverse();		//將鏈表逆向重置
void	sort();			// 排序操作,使用了 quick sort

因爲STL 算法 sort() 只接受 RamdonAccessIterator(隨機訪問迭代器),所以list 只能使用自己的。

感謝大家,我是假裝很努力的YoungYangD(小羊)

參考資料:
《STL源碼剖析》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章