天天看點

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 ,vector是一種動态數組,是基本數組的類模闆。其内部定義了很多基本操作。既然這是一個類,那麼它就會有自己的構造函數。vector 類中定義了4中種構造函數:

·  預設構造函數,構造一個初始長度為0的空向量,如:vector v1;

·  帶有單個整形參數的構造函數,此參數描述了向量的初始大小。這個構造函數還有一個可選的參數,這是一個類型為T的執行個體,描述了各個向量種各成員的初始值;如:vector v2(n,0); 如果預先定義了:n,他的成員值都被初始化為0;

·  複制構造函數,構造一個新的向量,作為已存在的向量的完全複制,如:vector v3(v2);

·  帶兩個常量參數的構造函數,産生初始值為一個區間的向量。區間由一個半開區間[first,last) 來指定。如:vector v4(first,last)

下面一個例子用的是第四種構造方法,其它的方法讀者可以自己試試。

C++語言學習之STL 的組成

為了幫助了解向量的概念,這裡寫了一個小例子,其中用到了vector的成員函數:begin(),end(),push_back(),assign(),front(),back(),erase(),empty(),at(),size()。

C++語言學習之STL 的組成
C++語言學習之STL 的組成

push_back()是将資料放入vector(向量)或deque(雙端隊列)的标準函數。Insert()是一個與之類似的函數,然而它在所有容器中都可以使用,但是用法更加複雜。end()實際上是取末尾加一,以便讓循環正确運作--它傳回的指針指向最靠近數組界限的資料。

在Java裡面也有向量的概念。Java中的向量是對象的集合。其中,各元素可以不必同類型,元素可以增加和删除,不能直接加入原始資料類型。

u     雙端隊列(qeque容器類):

deque(讀音:deck,意即:double queue,#include)容器類與vector類似,支援随機通路和快速插入删除,它在容器中某一位置上的操作所花費的是線性時間。與vector不同的是,deque還支援從開始端插入資料:push_front()。此外deque也不支援與vector的capacity()、reserve()類似的操作。

C++語言學習之STL 的組成
C++語言學習之STL 的組成

上面我們示範了deque如何進行插入删除等操作,像erase(),assign()是大多數容器都有的操作。關于deque的其他操作請參閱其他書籍。

u     表(List容器類)

 List(#include)又叫連結清單,是一種雙線性清單,隻能順序通路(從前向後或者從後向前),圖2是list的資料組織形式。與前面兩種容器類有一個明顯的差別就是:它不支援随機通路。要通路表中某個下标處的項需要從表頭或表尾處(接近該下标的一端)開始循環。而且缺少下标預算符:operator[]。

C++語言學習之STL 的組成

 同時,list仍然包涵了erase(),begin(),end(),insert(),push_back(),push_front()這些基本函數,下面我們來示範一下list的其他函數功能。merge():合并兩個排序清單;splice():拼接兩個清單;sort():清單的排序。

C++語言學習之STL 的組成

上面并沒有示範splice()函數的用法,這是一個拗口的函數。用起來有點麻煩。圖3所示是splice函數的功能。将一個清單插入到另一個清單當中。list容器類定義了splice()函數的3個版本:

splice(position,list_value);

splice(position,list_value,ptr);

splice(position,list_value,first,last);

list_value是一個已存在的清單,它将被插入到源清單中,position是一個疊代參數,他目前指向的是要進行拼接的清單中的特定位置。

C++語言學習之STL 的組成

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);

下面我們來看一個簡單的集和多集的插入例程:

C++語言學習之STL 的組成

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:

C++語言學習之STL 的組成

在map中是不允許一個鍵對應多個值的,在multimap中,不支援operator[],也就是說不支援map中允許的下标操作。

2.       算法(algorithm):

inlcude

STL中算法的大部分都不作為某些特定容器類的成員函數,他們是泛型的,每個算法都有處理大量不同容器類中資料的使用。值得注意的是,STL中的算法大多有多種版本,使用者可以依照具體的情況選擇合适版本。中在STL的泛型算法中有4類基本的算法:

變序型隊列算法:可以改變容器内的資料;

非變序型隊列算法:處理容器内的資料而不改變他們;

排序值算法:包涵對容器中的值進行排序和合并的算法,還有二叉搜尋算法、通用數值算法。(注:STL的算法并不隻是針對STL容器,對一般容器也是适用的。)

變序型隊列算法:又叫可修改的序列算法。這類算法有複制(copy)算法、交換(swap)算法、替代(replace)算法、删除(clear)算法,移動(remove)算法、翻轉(reverse)算法等等。這些算法可以改變容器中的資料(資料值和值在容器中的位置)。

下面介紹2個比較常用的算法reverse()和copy()。

C++語言學習之STL 的組成

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():

C++語言學習之STL 的組成

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()

C++語言學習之STL 的組成

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等都是這種疊代器 ;

流疊代器,可以直接輸出、輸入流中的值;

實際上,在前面的例子中,我們不停的在用疊代器。下面我們用幾個例子來幫助了解這些疊代器的用法。

下面的例子用到了輸入輸出疊代器:

C++語言學習之STL 的組成

這裡用到了輸入疊代器istream_iterator,輸出疊代器ostream_iterator。程式完成了将一個檔案輸出到螢幕的功能,先将檔案讀入,然後通過輸入疊代器把檔案内容複制到類型為字元串的向量容器内,最後由輸出疊代器輸出。Inserter是一個輸入疊代器的一個函數(疊代器擴充卡),它的使用方法是:

inserter (container ,pos);

container是将要用來存入資料的容器,pos是容器存入資料的開始位置。上例中,是把檔案内容存入(copy())到向量v1中。

4.       STL的其他标準元件

函數對象(functor或者funtion objects)

include

函數對象又稱之為仿函數。函數對象将函數封裝在一個對象中,使得它可作為參數傳遞給合适的STL算法,進而使算法的功能得以擴充。可以把它當作函數來使用。使用者也可以定義自己的函數對象。下面讓我們來定義一個自己的函數對象.

C++語言學習之STL 的組成

這裡的int_max()就是一個函數對象,struct關鍵字也可以用class來代替,隻不過struct預設情況下是公有通路權限,而class定義的是預設私有通路權限。下面我們來定義一個STL風格的函數對象:

C++語言學習之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元件。反向疊代器和插入疊代器都屬于疊代器擴充卡,疊代器擴充卡擴充了疊代器的功能;

函數擴充卡:通過轉換或者修改其他函數對象使其功能得到擴充。這一類擴充卡有否定器(相當于"非"操作)、幫定器、函數指針擴充卡。

私信小編“資料”有福利哦。