C++語言學習之STL 的組成
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
· 默認構造函數,構造一個初始長度為0的空向量,如:vector
· 帶有單個整形參數的構造函數,此參數描述了向量的初始大小。這個構造函數還有一個可選的參數,這是一個類型為T的實例,描述了各個向量種各成員的初始值;如:vector
· 複製構造函數,構造一個新的向量,作為已存在的向量的完全複製,如:vector
· 帶兩個常量參數的構造函數,產生初始值為一個區間的向量。區間由一個半開區間[first,last) 來指定。如:vector
下面一個例子用的是第四種構造方法,其它的方法讀者可以自己試試。
為了幫助理解向量的概念,這裡寫了一個小例子,其中用到了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
上面我們演示了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
在集中,所有的成員都是排列好的。如果先後往一個集中插入: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
less
template
如:set<int ,less
複製構造函數:
set(const set<Key,Compare&>);
如:set<int ,less
下面我們來看一個簡單的集和多集的插入常式:
u 映射和多重映射(map 和multimap)
映射和多重映射(#include
include
include
using namespace std;
int main()
{
map<char,int,less
map<char,int,less
//char 是鍵的類型,int是值的類型
//下面是初始化,與數組類似
//也可以用map1.insert(map<char,int,less
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
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
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
流迭代器,可以直接輸出、輸入流中的值;
實際上,在前面的例子中,我們不停的在用迭代器。下面我們用幾個例子來幫助理解這些迭代器的用法。
下面的例子用到了輸入輸出迭代器:
這裡用到了輸入迭代器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組件。反向迭代器和插入迭代器都屬於迭代器適配器,迭代器適配器擴展了迭代器的功能;
函數適配器:通過轉換或者修改其他函數對象使其功能得到擴展。這一類適配器有否定器(相當於”非”操作)、幫定器、函數指針適配器。
私信小編「資料」有福利哦。