1. 程式人生 > >標準C++中的STL容器類簡介

標準C++中的STL容器類簡介

容器的概念

所謂STL容器,即是將最常運用的一些資料結構(data structures)實現出來。

容器是指容納特定型別物件的集合。根據資料在容器中排列的特性,容器可概分為序列式(sequence)和關聯式(associative)兩種。

迭代器是一種檢查容器內元素並遍歷元素的資料型別。它提供類似指標的功能,對容器的內容進行走訪。

#include<iterator>

例如:

std::vector<int> IntVector;

std::vector<int>::iterator first=IntVector.begin();

// begin()得到指向vector開頭的Iterator,*first得到開頭一個元素的值

std::vector<int>::iterator last=IntVector.end();

// end()得到指向vector結尾的Iterator,*last得到最後一個元素的值

 

序列式容器

所謂序列式容器,其中的元素都可序(ordered),但未必有序(sorted)。陣列為C++語言內建的序列容器,STL另外提供vector、list、deque(double-ended queue)。它們的差別在於訪問元素的方式,以及新增或刪除元素相關操作的執行代價。

標準庫還提供了三種容器介面卡(adapter),所謂介面卡是根據原始的容器型別所提供的操作,通過定義新的操作介面,來適應基礎的容器型別。順序容器介面卡包括stack、queue、priority_queue等序列式容器。其中stack和queue由於只是將deque改頭換面而成,技術上被歸類為一種配接器(adapter),priority_queue是有優先順序管理的佇列。

 

一. Vector

1.vector的基本概念

vector是標準C++建議替代C陣列的動態陣列模型,它維護的是一個連續線性空間。

vector所採用的資料結構非常簡單:線性連續空間。它以兩個迭代器start和finish分別指向配置得到的連續空間中目前已被使用的範圍,並以迭代器end_of_storage指向整塊連續空間(含備用空間)的尾端。

vector的實現技術,關鍵在於其對大小的控制以及重新分配時的資料移動效率。一旦vector原有空間用完,如果客戶端每新增一個元素,vector內部就只擴充一個元素的空間,實為不智。因為所謂擴充控制元件(不論多大),是“配置新空間(malloc)/拷貝移動資料(memcpy)/釋放舊空間(free)”的大工程,時間成本很高,應該採用某種未雨綢繆的空間配置策略。

注意,所謂動態增加大小,並不是在原空間之後接續新空間(因為無法保證之後尚有可供配置的空間),而是每次再分配原大小兩倍的記憶體空間。因此,對vector的任何操作,一旦引起控制元件重新配置,指向原vector的所有迭代器就都失效了。

由於vector維護的是一個連續線性空間,因此vector迭代器具備普通指標的功能,支援隨機存取,即vector提供的是Random Access Iterators。

2.向量類模板std::vector的成員函式

#include<vector>

std::vector<type> vec;

std::vector<type> vec(size);

std::vector<type> vec(size,value);

std::vector<type> vec(myvector);

std::vector<type> vec(first,last);

Operators:==、!=、<=、>=、<、>、[]

assign(first,last):用迭代器first,last所指定的元素取代向量元素

assign(num,val):用val的num份副本取代向量元素

at(n):等價於[]運算子,返回向量中位置n的元素,因其有越界檢查,故比[]索引訪問安全

front():返回向量中第一個元素的引用

back():返回向量中最後一個元素的引用

begin():返回向量中第一個元素的迭代器

end():返回向量中最後一個元素的下一個迭代器(僅作結束遊標,不可解引用)

max_size():返回向量型別的最大容量(2^30-1=0x3FFFFFFF)

capacity():返回向量當前開闢的空間大小(<= max_size,與向量的動態記憶體分配策略相關)

size():返回向量中現有元素的個數(<=capacity)

clear():刪除向量中所有元素

empty():如果向量為空,返回真

erase(start,end):刪除迭代器start end所指定範圍內的元素

erase(i):刪除迭代器i所指向的元素

    erase()返回指向刪除的最後一個元素的下一位置的迭代器

insert(i,x);把x插入到迭代器i所指定的位置之前

insert(i,n,x):把x的n份副本插入到迭代器i所指定的位置之前

insert(i,start,end):把迭代器start和end所指定的範圍內的值插入到迭代器i所指定的位置之前

push_back(x):把x推入(插入)到向量的尾部

pop_back():彈出(刪除)向量最後一個元素

rbegin():返回一個反向迭代器,該迭代器指向的元素越過了向量中的最後一個元素

rend():返回一個反向迭代器,該迭代器指向向量中第一個元素

reverse():反轉元素順序

resize(n,x):把向量的大小改為n,新元素的初值賦為x

swap(vectorref):交換2個向量的內容

3.動態字串類std::string

string是標準C++建議替代C字串(以零結束的字元陣列)的動態字串模型,可以簡單的看做vector<char>。

#include<string>

std::string str1;

std::string str3(str2);

std::string str2("this is a string");

以下未列出與vector相同的通用操作。

Operators:+、+=

length():和size()函式功能相同

data():取得字串指標

c_str():取得C風格字串指標

c_str()的流程是先呼叫terminate(),然後再返回data()。因此如果你對效率要求比較高,而且你的處理又不一定需要以/0的方式結束,最好選擇data()。但是對於一般的C函式中,需要以const char*為輸入引數,要使用c_str()函式。

operator=:賦值操作符

append():追加字串

replace():替換字元

copy():拷貝自己的num個字元到str中(從索引index開始)。

find():在字串中查詢指定字元,返回基於0的索引號

rfind():反向查詢

find_first_of():查詢包含子串中的任何字元,返回第一個位置

find_first_not_of():查詢不包含子串中的任何字元,返回第一個位置

find_last_of():查詢包含子串中的任何字元,返回最後一個位置

find_last_not_of():查詢不包含子串中的任何字元,返回最後一個位置

substr(n1,len):得到字串從n1開始的長度為len的子串

比較字串(支援所有的關係運算符)

compare 比較字串

operator+  字串銜接

operator+= 增加操作符

operator== 判斷是否相等

operator!= 判斷是否不等於

operator< 判斷是否小於

operator>> 從輸入流中讀入字串

operator<< 字串寫入輸出流

getline 從輸入流中讀入一行

 

二.list

1.list的基本概念

相對於vector的連續線性空間,list就顯得複雜許多,與向量(vector)相比, 它允許快速的插入和刪除,且每次插入或刪除一個元素,就配置或釋放一個元素空間。因此,list對於空間的運用絕對的精準,一點也不浪費。而且,對於任何位置的元素插入或元素移除,list永遠是常數時間。

list不再能夠像vector那樣以普通指標作為迭代器,因為其節點不保證在儲存空間中連續存在。list迭代器必須有能力指向list的節點,並有能力進行正確的遞增、遞減、取值、成員存取等操作。所謂“list迭代器正確的遞增、遞減、取值、成員取用”操作是指,遞增時指向下一個節點,遞減時指向上一個節點,取值時取的是節點的資料值,成員取用時取用的是節點的成員。

list不僅是一個雙向連結串列,而其還是一個環狀雙向連結串列。所以它只需要一個指標,便可以完整實現整個連結串列。由於list是一個雙向連結串列(double linked-list),迭代器必須具備前移、後移的能力,所以list提供的是Bidirectional Iterators。

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

2.連結串列類模板std::list成員函式

#include<list>

std::list<type> lst;

std::list<type> lst(size);

std::list<type> lst(size,value);

std::list<type> lst(mylist);

std::list<type> lst(first,last);

以下未列出與vector相同的通用操作。

push_front(x):把元素x推入(插入)到連結串列頭部

pop_front():彈出(刪除)連結串列首元素

merge(listref):把listref所引用的連結串列中的所有元素插入到連結串列中,可指定合併規則

splice():把lst連線到pos的位置

remove(val):刪除連結串列中所有值為val的元素

remove_if(pred):刪除連結串列中謂詞pred為真的元素

(謂詞即為元素儲存和檢索的描述,如std::less<>,std::greater<>那麼就按降序/升序排列,你也可以定義自己的謂詞)

sort():根據預設的謂詞對連結串列排序

sort(pred):根據給定的謂詞對連結串列排序

unique():刪除連結串列中所有重複的元素

unique(pred):根據謂詞pred刪除所有重複的元素,使連結串列中沒有重複元素

注意:vector和deque支援隨機訪問,而list不支援隨機訪問,因此不支援[]訪問!

 

三.deque

1.deque的基本概念

vector是單向開口的連續線性空間,deque則是以中雙向開口的連續線性空間。所謂雙向開口,意思是可以在頭尾兩端分別做元素的插入和刪除操作。從技術的角度而言,vector當然也可以在頭尾兩端進行操作,但是其頭部操作效率奇差、令人無法接受。

deque和vector的最大差異,一在於deque允許於常數時間內對頭端進行元素的插入或移除操作,二在於deque沒有所謂容量(capacity)觀念,因為它是動態地以分段連續空間組合而成,隨時可以增加一段新的空間並連結起來。換句話說,像vector那樣“因舊空間不足而重新配置一塊更大空間,然後複製元素,再釋放舊空間”這樣的事情在deque中是不會發生的。也因此,deque沒有必要提供所謂的空間預留(reserved)功能。

雖然deque也提供Random Access Iterator,但它的迭代器並不是普通指標,其複雜度和vector不可同日而語,這當然涉及到各個運算層面。因此,除非必要,我們應儘可能選擇使用vector而非deque。對deque進行的排序操作,為了最高效率,可將deque先完整複製到一個vector身上,將vector排序後(利用STL的sort演算法),再複製回deque。

deque是由一段一段的定量連續空間構成。一旦有必要在deque的前端或尾端增加新空間,便配置一段定量的連續空間,串接在整個deque的頭端或尾端。deque的最大任務,便是在這些分段的定量連續空間上,維護其整體連續的假象,並提供隨機存取的介面。避開了“重新配置、複製、釋放”的輪迴,代價則是複雜的迭代器架構。

2.雙端佇列類模板std::deque成員函式

#include<deque>

std::deque<type> deq;

std::deque<type> deq(size);

std::deque<type> deq(size,value);

std::deque<type> deq(mydeque);

std::deque<type> deq(first,last);

其成員函式如下:

Operators:[]用來訪問雙向佇列中單個的元素

front():返回第一個元素的引用

push_front(x):把元素x推入(插入)到雙向佇列的頭部

pop_front():彈出(刪除)雙向佇列的第一個元素

back():返回最後一個元素的引用

push_back(x):把元素x推入(插入)到雙向佇列的尾部

 

pop_back():彈出(刪除)雙向佇列的最後一個元素

 

四.基於deque的順序容器介面卡stack、queue(priority_queue)

stack

1.stack的基本概念

stack是一種後進先出(First In Last Out,FILO)的資料結構,它只有一個出口。stack允許新增元素、移除元素、取得最頂端元素。但除了最頂端外,沒有任何其他方法可以存取stack的其他元素,換言之,stack不允許隨機訪問。

STL以deque作為stack的底層結構,對deque封閉期頭端開口,稍作修改便形成了stack。   

將元素插入stack的操作稱為push,將元素彈出stack的操作稱為pop。stack所有元素的進出都必須符合“後進先出”的條件,只有stack頂端的元素,才有機會被外界取用。stack不提供走訪功能,也不提供迭代器。

2.容器介面卡堆疊類std::stack成員函式

#include<stack>

stack實現後進先出的操作

std::stack<type,container> stk;

type為堆疊操作的資料型別

container為實現堆疊所用的容器型別,預設基於deque,還可以為std::vector和std::list

例如std::stack<int,std::list<int>> IntStack;

其成員函式如下:

top():返回頂端元素的引用

push(x):將元素壓入棧(頂)

pop():彈出(刪除)頂端元素

 

queue

1.queue的基本概念

queue是一種先進先出(First In First Out,FIFO)的資料結構,它有兩個出口。queue允許新增元素、移除元素、從最底端加入元素、取得最頂端元素。但除了最底端可以加入、最頂端可以取出,沒有任何其他方法可以存取queue的其他元素。換言之,queue不支援隨機訪問。

STL以deque作為queue的底層結構,對deque封閉其底端的出口和前端的入口,稍作修改便形成了queue。

2.容器介面卡佇列類std::queue成員函式

#include<queue>

queue實現先進先出的操作

std::queue<type,container> que;

type為佇列操作的資料型別

container為實現佇列所用的容器型別,只能為提供了push_front操作的std::deque或std::list,預設基於std::deque

其成員函式如下:

front():返回隊首元素的引用

back():返回隊尾元素的引用

push(x):把元素x推入(插入)到隊尾

pop():隊首元素出列(彈出(刪除)隊首元素)

 

priority_queue

1.priority_queue的基本概念

priority_queue為優先順序佇列,它允許使用者為佇列中儲存的元素設定優先順序。這種佇列不是直接將新元素放置在佇列尾部,而是放置在比它優先順序低的元素前面,即提供了一種插隊策略。標準庫預設使用<操作符來確定他們之間的優先順序關係。即權重大的排在隊首。

使用priority_queue時,包含<queue>檔案。

2.容器介面卡佇列類std::priority_queue成員函式

#include<queue>

priority_queue實現先進先出的操作

std::priority_queue<type, container, comp> pri_que;

type為佇列操作的資料型別

container為實現佇列所用的容器型別,可以為std::vector,std::deque,預設基於deque

comp為排隊策略,預設為std::less<>,即插到小於它的元素前

例如std::priority_queue<int,std::vector<int>,std::greater<int> > IntPriQue;

其成員函式如下:

top():返回隊首(優先順序最高)元素的引用

push(x):將元素推入(按插隊策略插排)佇列(尾部)

pop():彈出(刪除)隊首(優先順序最高)元素

 

關聯式容器

所謂關聯式容器,概念上類似關聯式資料庫(實際上則簡單許多):每項資料(元素)包含一個鍵值(key)和一個實值(value)。當元素被插入到關聯式容器中時,容器內部資料結構(可能是RB-tree,也可能是hash-table)便依照其鍵值大小,以某種特定規則將這個元素放置於適當位置。關聯式容器沒有所謂頭尾(只有最大元素和最小元素),所以不會有push_back(),push_front(),pop_back(),pop_front(),begin(),end()這樣的操作。

一般而言,關聯式容器的內部結構是一個balanced binary tree(平衡二叉樹),以便獲得良好的搜尋效率。balanced binary tree有很多種型別,包括AVL-tree、RB-tree、AA-tree,其中廣泛運用於STL的是RB-tree(紅黑樹)。

標準的STL關聯式容器分為set(集合)和map(對映類)兩大類,以及這兩大類的衍生體multiset(多鍵集合)和multimap(多鍵對映表)。這些容器的底層機制均以RB-tree完成(紅黑樹)。RB-tree也是一個獨立容器,但並不開放給外界使用。

此外,SGI STL還提供了一個不在標準規格之列的關聯式容器:hash table(散列表,雜湊表),以及以此hash table為底層機制而完成的hash_set(雜湊集合)、hash_map(雜湊對映表)、hash_multiset(雜湊多鍵集合)、hash_multimap(雜湊多鍵對映表)。

 

map

關聯式容器std::map成員函式

#include<map>

map建立key-value對映

std::map<key, value> mp;

std::map<key, value, comp> mp;

key為鍵值

value為對映值

comp可選,為鍵值對存放策略,例如可為std::less<>,鍵值對映對將按鍵值從小到大儲存

其成員函式如下:

count():返回map中鍵值等於key的元素的個數

equal_range():函式返回兩個迭代器——一個指向第一個鍵值為key的元素,另一個指向最後一個鍵值為key的元素

erase(i):刪除迭代器所指位置的元素(鍵值對)

lower_bound():返回一個迭代器,指向map中鍵值>=key的第一個元素

upper_bound():函式返回一個迭代器,指向map中鍵值>key的第一個元素

find(key):返回鍵值為key的鍵值對迭代器,如果沒有該對映則返回結束遊標end()

注意map的[]操作符,當試圖對於不存在的key進行引用時,將新建鍵值對,值為空。

 

通用演算法(對以上STL均適用)

#include<algorithm>

1.非修正序列演算法:

2.修正序列演算法:

3.排序演算法:

4.數值演算法: