STL有三大核心部分:容器(Container)、算法(Algorithms)、疊代器(Iterator),容器擴充卡(container adaptor),函數對象(functor),除此之外還有STL其他标準元件。通俗的講:
容器:裝東西的東西,裝水的杯子,裝鹹水的大海,裝人的教室……STL裡的容器是可容納一些資料的模闆類。
算法:就是往杯子裡倒水,往大海裡排污,從教室裡攆人……STL裡的算法,就是處理容器裡面資料的方法、操作。
疊代器:往杯子裡倒水的水壺,排污的管道,攆人的那個物業管理人員……STL裡的疊代器:周遊容器中資料的對象。對存儲于容器中的資料進行處理時,疊代器能從一個成員移向另一個成員。他能按預先定義的順序在某些容器中的成員間移動。對普通的一維數組、向量、雙端隊列和清單來說,疊代器是一種指針。
下面讓我們來看看專家是怎麼說的:
容器(container):容器是資料在記憶體中組織的方法,例如,數組、堆棧、隊列、連結清單或二叉樹(不過這些都不是STL标準容器)。STL中的容器是一種存儲T(Template)類型值的有限集合的資料結構,容器的内部實作一般是類。這些值可以是對象本身,如果資料類型T代表的是Class的話。
算法(algorithm):算法是應用在容器上以各種方法處理其内容的行為或功能。例如,有對容器内容排序、複制、檢索和合并的算法。在STL中,算法是由模闆函數表現的。這些函數不是容器類的成員函數。相反,它們是獨立的函數。令人吃驚的特點之一就是其算法如此通用。不僅可以将其用于STL容器,而且可以用于普通的C++數組或任何其他應用程式指定的容器。
疊代器(iterator):一旦標明一種容器類型和資料行為(算法),那麼剩下唯一要他做的就是用疊代器使其互相作用。可以把達代器看作一個指向容器中元素的普通指針。可以如遞增一個指針那樣遞增疊代器,使其依次指向容器中每一個後繼的元素。疊代器是STL的一個關鍵部分,因為它将算法和容器連在一起。
下面我将依次介紹STL的這三個主要元件。
1. 容器
STL中的容器有隊列容器和關聯容器,容器擴充卡(congtainer adapters:stack,queue,priority queue),位集(bit_set),串包(string_package)等等。
在本文中,我将介紹list,vector,deque等隊列容器,和set和multisets,map和multimaps等關聯容器,一共7種基本容器類。
隊列容器(順序容器):隊列容器按照線性排列來存儲T類型值的集合,隊列的每個成員都有自己的特有的位置。順序容器有向量類型、雙端隊列類型、清單類型三種。
u 基本容器——向量
向量(vector容器類):#include ,vector是一種動态數組,是基本數組的類模闆。其内部定義了很多基本操作。既然這是一個類,那麼它就會有自己的構造函數。vector 類中定義了4中種構造函數:
· 預設構造函數,構造一個初始長度為0的空向量,如:vector v1;
· 帶有單個整形參數的構造函數,此參數描述了向量的初始大小。這個構造函數還有一個可選的參數,這是一個類型為T的執行個體,描述了各個向量種各成員的初始值;如:vector v2(n,0); 如果預先定義了:n,他的成員值都被初始化為0;
· 複制構造函數,構造一個新的向量,作為已存在的向量的完全複制,如:vector v3(v2);
· 帶兩個常量參數的構造函數,産生初始值為一個區間的向量。區間由一個半開區間[first,last) 來指定。如:vector v4(first,last)
下面一個例子用的是第四種構造方法,其它的方法讀者可以自己試試。
為了幫助了解向量的概念,這裡寫了一個小例子,其中用到了vector的成員函數:begin(),end(),push_back(),assign(),front(),back(),erase(),empty(),at(),size()。
push_back()是将資料放入vector(向量)或deque(雙端隊列)的标準函數。Insert()是一個與之類似的函數,然而它在所有容器中都可以使用,但是用法更加複雜。end()實際上是取末尾加一,以便讓循環正确運作--它傳回的指針指向最靠近數組界限的資料。
在Java裡面也有向量的概念。Java中的向量是對象的集合。其中,各元素可以不必同類型,元素可以增加和删除,不能直接加入原始資料類型。
u 雙端隊列(qeque容器類):
deque(讀音:deck,意即:double queue,#include)容器類與vector類似,支援随機通路和快速插入删除,它在容器中某一位置上的操作所花費的是線性時間。與vector不同的是,deque還支援從開始端插入資料:push_front()。此外deque也不支援與vector的capacity()、reserve()類似的操作。
上面我們示範了deque如何進行插入删除等操作,像erase(),assign()是大多數容器都有的操作。關于deque的其他操作請參閱其他書籍。
u 表(List容器類)
List(#include)又叫連結清單,是一種雙線性清單,隻能順序通路(從前向後或者從後向前),圖2是list的資料組織形式。與前面兩種容器類有一個明顯的差別就是:它不支援随機通路。要通路表中某個下标處的項需要從表頭或表尾處(接近該下标的一端)開始循環。而且缺少下标預算符:operator[]。
同時,list仍然包涵了erase(),begin(),end(),insert(),push_back(),push_front()這些基本函數,下面我們來示範一下list的其他函數功能。merge():合并兩個排序清單;splice():拼接兩個清單;sort():清單的排序。
上面并沒有示範splice()函數的用法,這是一個拗口的函數。用起來有點麻煩。圖3所示是splice函數的功能。将一個清單插入到另一個清單當中。list容器類定義了splice()函數的3個版本:
splice(position,list_value);
splice(position,list_value,ptr);
splice(position,list_value,first,last);
list_value是一個已存在的清單,它将被插入到源清單中,position是一個疊代參數,他目前指向的是要進行拼接的清單中的特定位置。
listn1:123,0,34,1123 listn2:12,100
執行listn1.splice(find(listn1.begin(),listn1.end(),0),listn2);之後,listn1将變為:123,12,100,34,1123。即把listn2插入到listn1的0這個元素之前。其中,find()函數找到0這個元素在listn1中的位置。值得注意的是,在執行splice之後,list_value将不複存在了。這個例子中是listn2将不再存在。
第二個版本當中的ptr是一個疊代器參數,執行的結果是把ptr所指向的值直接插入到position目前指向的位置之前.這将隻向源清單中插入一個元素。
第三個版本的first和last也是疊代器參數,并不等于list_value.begin(),list_value.end()。First指的是要插入的列的第一個元素,last指的是要插入的列的最後一個元素。
如果listn1:123,0,34,1123 listn2:12,43,87,100 執行完以下函數之後
listn1.splice(find(listn1.begin(),listn1.end(),0),++listn2.begin(),--listn2.end());
listn1:123,43,87,0,34,1123 listn2:12,100
以上,我們學習了vector,deque,list三種基本順序容器,其他的順序容器還有:slist,bit_vector等等。
u 集和多集(set 和multiset 容器類):
一個集合(#include)是一個容器,它其中所包含的元素的值是唯一的。這在收集一個資料的具體值的時候是有用的。集合中的元素按一定的順序排列,并被作為集合中的執行個體。如果你需要一個鍵/值對(pair)來存儲資料,map(也是一個關聯容器,後面将馬上要講到)是一個更好的選擇。一個集合通過一個連結清單來組織,在插入操作和删除操作上比向量(vector)快,但查找或添加末尾的元素時會有些慢。
在集中,所有的成員都是排列好的。如果先後往一個集中插入:12,2,3,123,5,65 則輸出該集時為:2,3,5,12,65,123
集和多集的差別是:set支援唯一鍵值,set中的值都是特定的,而且隻出現一次;而multiset中可以出現副本鍵,同一值可以出現多次。
Set和multiset的模闆參數:
template<class key, class compare, class Allocator=allocator>
第一個參數key是所存儲的鍵的類型,第二個參數是為排序值而定義的比較函數的類型,第三個參數是被實作的存儲配置設定符的類型。在有些編譯器的具體實作中,第三個參數可以省略。第二個參數使用了合适形式的疊代器為鍵定義了特定的關系操作符,并用來在容器中周遊值時建立順序。集的疊代器是雙向,同時也是常量的,是以疊代器在使用的時候不能修改元素的值。
Set定義了三個構造函數:
預設構造函數:
explicit set(const Compare&=compare());
如:set<int,less > set1;
less是一個标準類,用于形成降序排列函數對象。升序排列是用greater。通過指定某一預先定義的區間來初始化set對象的構造函數:
template set(InputIterator, InputIterator,/ const Compare&=compare());
如:set<int ,less >set2(vector1.begin(),vector1.end());
複制構造函數:
set(const set<Key,Compare&>);
如:set<int ,less >set3(set2);
下面我們來看一個簡單的集和多集的插入例程:
u 映射和多重映射(map 和multimap)
映射和多重映射(#include)基于某一類型Key的鍵集的存在,提供對T類型的資料進行快速和高效的檢索。對map而言,鍵隻是指存儲在容器中的某一成員。Map不支援副本鍵,multimap支援副本鍵。Map和multimap對象包涵了鍵和各個鍵有關的值,鍵和值的資料類型是不相同的,這與set不同。set中的key和value是Key類型的,而map中的key和value是一個pair結構中的兩個分量。Map支援下表運算符operator[],用通路普通數組的方式通路map,不過下标為map的鍵。在multimap中一個鍵可以對應多個不同的值。
下面的例程說明了map中鍵與值的關系。
include
include
using namespace std;
int main()
{
map<char,int,less > map1;
map<char,int,less >::iterator mapIter;
//char 是鍵的類型,int是值的類型
//下面是初始化,與數組類似
//也可以用map1.insert(map<char,int,less >::value_type(''c'',3));
map1['c']=3;
map1['d']=4;
map1['a']=1;
map1['b']=2;
for(mapIter=map1.begin();mapIter!=map1.end();++mapIter)
cout<<" "<<(mapIter).first<<": "<<(mapIter).second;
//first對應定義中的char鍵,second對應定義中的int值
//檢索對應于d鍵的值是這樣做的:
map<char,int,less >::const_iterator ptr;
ptr=map1.find('d');
cout<<'/n'<<" "<<(ptr).first<<" 鍵對應于值:"<<(ptr).second;
return 0;
}
從以上例程中,我們可以看到map對象的行為和一般數組的行為類似。Map允許兩個或多個值使用比較操作符。下面我們再看看multimap:
在map中是不允許一個鍵對應多個值的,在multimap中,不支援operator[],也就是說不支援map中允許的下标操作。
2. 算法(algorithm):
inlcude
STL中算法的大部分都不作為某些特定容器類的成員函數,他們是泛型的,每個算法都有處理大量不同容器類中資料的使用。值得注意的是,STL中的算法大多有多種版本,使用者可以依照具體的情況選擇合适版本。中在STL的泛型算法中有4類基本的算法:
變序型隊列算法:可以改變容器内的資料;
非變序型隊列算法:處理容器内的資料而不改變他們;
排序值算法:包涵對容器中的值進行排序和合并的算法,還有二叉搜尋算法、通用數值算法。(注:STL的算法并不隻是針對STL容器,對一般容器也是适用的。)
變序型隊列算法:又叫可修改的序列算法。這類算法有複制(copy)算法、交換(swap)算法、替代(replace)算法、删除(clear)算法,移動(remove)算法、翻轉(reverse)算法等等。這些算法可以改變容器中的資料(資料值和值在容器中的位置)。
下面介紹2個比較常用的算法reverse()和copy()。
revese()的功能是将一個容器内的資料順序翻轉過來,它的原型是:
template
void reverse(Bidirectional first, Bidirectional last);
将first和last之間的元素翻轉過來,上例中你也可以隻将arr中的一部分進行翻轉:
reverse(arr+3,arr+6); 這也是有效的。First和last需要指定一個操作區間。
Copy()是要将一個容器内的資料複制到另一個容器内,它的原型是:
Template<class InputIterator ,class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);
它把[first,last-1]内的隊列成員複制到區間[result,result+(last-first)-1]中。泛型交換算法:
Swap()操作的是單值交換,它的原型是:
template
void swap(T& a,T& b);
swap_ranges()操作的是兩個相等大小區間中的值,它的原型是:
template<class ForwardIterator1, class ForwardIterator2>
ForwardIterator2swap_ranges(ForwardIterator1 first1,ForwardIterator1 last1, ForwardIterator1 first2);
交換區間[first1,last1-1]和[first2, first2+(last1-first1)-1]之間的值,并假設這兩個區間是不重疊的。
非變序型隊列算法,又叫不可修改的序列算法。這一類算法操作不影響其操作的容器的内容,包括搜尋隊列成員算法,等價性檢查算法,計算隊列成員個數的算法。我将用下面的例子介紹其中的find(),search(),count():
find()的原型是:
template<class InputIterator,class EqualityComparable>
InputIterator find(InputIterator first, InputIterator last, const EqualityComparable& value);
其功能是在序列[first,last-1]中查找value值,如果找到,就傳回一個指向value在序列中第一次出現的疊代,如果沒有找到,就傳回一個指向last的疊代(last并不屬于序列)。
search()的原型是:
template <class ForwardIterator1, class ForwardIterator2>
ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator2 last2);
其功能是在源序列[first1,last1-1]查找目标序列[first2,last2-1]如果查找成功,就傳回一個指向源序列中目标序列出現的首位置的疊代。查找失敗則傳回一個指向last的疊代。
Count()的原型是:
template <class InputIterator, class EqualityComparable>
iterator_traits::difference_type count(InputIterator first,
InputIterator last, const EqualityComparable& value);
其功能是在序列[first,last-1]中查找出等于value的成員,傳回等于value得成員的個數。
排序算法(sort algorithm):這一類算法很多,功能強大同時也相對複雜一些。這些算法依賴的是關系運算。在這裡我隻介紹其中比較簡單的幾種排序算法:sort(),merge(),includes()
sort()的原型是:
template
void sort(RandomAccessIterator first, RandomAccessIterator last);
功能是對[first,last-1]區間内的元素進行排序操作。與之類似的操作還有:partial_sort(), stable_sort(),partial_sort_copy()等等。
merge()的原型是:
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator merge(InputIterator1 first1, InputIterator1 last1,InputIterator2 first2, InputIterator2 st2,OutputIterator result);
将有序區間[first1,last1-1]和[first2,last2-1]合并到[result, result + (last1 - first1) + (last2 - first2)-1]區間内。
Includes()的原型是:
template <class InputIterator1, class InputIterator2>
bool includes(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2);
其功能是檢查有序區間[first2,last2-1]内元素是否都在[first1,last1-1]區間内,傳回一個bool值。
通用數值算法(generalized numeric algorithms):這一類算法還不多,涉及到專業領域中有用的算術操作,獨立包涵于頭檔案中。
STL中的算法大都有多種版本,常見的版本有以下4中:
預設版本,假設給出了特定操作符;
一般版本,使用了成員提供的操作符;
複制版本,對原隊列的副本進行操作,常帶有 _copy 字尾;
謂詞版本,隻應用于滿足給定謂詞的隊列成員,常帶有 _if 字尾;
以上我們學習了STL容器和算法的概念,以及一些簡單的STL容器和算法。在使用算法處理容器内的資料時,需要從一個資料成員移向另一個資料成員,疊代器恰好實作了這一功能。下面我們來學習STL疊代器 。
3. 疊代器(itertor):
include
疊代器實際上是一種泛化指針,如果一個疊代器指向了容器中的某一成員,那麼疊代器将可以通過自增自減來周遊容器中的所有成員。疊代器是聯系容器和算法的媒介,是算法操作容器的接口。在運用算法操作容器的時候,我們常常在不知不覺中已經使用了疊代器。
STL中定義了6種疊代器:
輸入疊代器,在容器的連續區間内向前移動,可以讀取容器内任意值;
輸出疊代器,把值寫進它所指向的隊列成員中;
前向疊代器,讀取隊列中的值,并可以向前移動到下一位置(++p,p++);
雙向疊代器,讀取隊列中的值,并可以向前向後周遊容器;
随機通路疊代器, vector::iterator,list::iterator等都是這種疊代器 ;
流疊代器,可以直接輸出、輸入流中的值;
實際上,在前面的例子中,我們不停的在用疊代器。下面我們用幾個例子來幫助了解這些疊代器的用法。
下面的例子用到了輸入輸出疊代器:
這裡用到了輸入疊代器istream_iterator,輸出疊代器ostream_iterator。程式完成了将一個檔案輸出到螢幕的功能,先将檔案讀入,然後通過輸入疊代器把檔案内容複制到類型為字元串的向量容器内,最後由輸出疊代器輸出。Inserter是一個輸入疊代器的一個函數(疊代器擴充卡),它的使用方法是:
inserter (container ,pos);
container是将要用來存入資料的容器,pos是容器存入資料的開始位置。上例中,是把檔案内容存入(copy())到向量v1中。
4. STL的其他标準元件
函數對象(functor或者funtion objects)
include
函數對象又稱之為仿函數。函數對象将函數封裝在一個對象中,使得它可作為參數傳遞給合适的STL算法,進而使算法的功能得以擴充。可以把它當作函數來使用。使用者也可以定義自己的函數對象。下面讓我們來定義一個自己的函數對象.
這裡的int_max()就是一個函數對象,struct關鍵字也可以用class來代替,隻不過struct預設情況下是公有通路權限,而class定義的是預設私有通路權限。下面我們來定義一個STL風格的函數對象:
在這裡,我們定義了一個函數對象adder(),這也是一個類,它的基類是unary_function函數對象。unary_function是一個空基類,不包涵任何操作或變量。隻是一種格式說明,它有兩個參數,第一個參數是函數對象的使用資料類型,第二個參數是它的傳回類型。基于它所定義的函數對象是一進制函數對象。(注:用關鍵字struct或者class定義的類型實際上都是"類")
STL内定義了各種函數對象,否定器、限制器、一進制謂詞、二進制謂詞都是常用的函數對象。函數對象對于程式設計來說很重要,因為他如同對象類型的抽象一樣作用于操作。
擴充卡(adapter)
擴充卡是用來修改其他元件接口的STL元件,是帶有一個參數的類模闆(這個參數是操作的值的資料類型)。STL定義了3種形式的擴充卡:容器擴充卡,疊代器擴充卡,函數擴充卡。
容器擴充卡:包括棧(stack)、隊列(queue)、優先(priority_queue)。使用容器擴充卡,stack就可以被實作為基本容器類型(vector,dequeue,list)的适配。可以把stack看作是某種特殊的vctor、deque或者list容器,隻是其操作仍然受到stack本身屬性的限制。queue和priority_queue與之類似。容器擴充卡的接口更為簡單,隻是受限比一般容器要多;
疊代器擴充卡:修改為某些基本容器定義的疊代器的接口的一種STL元件。反向疊代器和插入疊代器都屬于疊代器擴充卡,疊代器擴充卡擴充了疊代器的功能;
函數擴充卡:通過轉換或者修改其他函數對象使其功能得到擴充。這一類擴充卡有否定器(相當于"非"操作)、幫定器、函數指針擴充卡。
私信小編“資料”有福利哦。