第十六章 string類和標準模板庫(4)泛型編程

(四)泛型編程

STL是一種泛型編程,面向對象的編程關注的是數據結構,而泛型編程關注的是算法。它們的共同點是抽象和創建可重用代碼。

1.迭代器

基于算法的要求,來設計基本迭代器的特征和基本容器的特征。容器類模板是將算法獨立于存儲的數據類型,而迭代器是將算法獨立于各種容器類模板,使得某種算法可以使用各容器類的迭代器用相同的方式來處理不同的容器類型。模板提供了存儲在容器中的數據類型的通用表示,使得算法獨立于不同的數據類型,而迭代器則提供了遍歷不同容器類數據的通用表示,使得算法獨立于不同容器(比如數組和鏈表)的具體的數據結構。

對于普通的數組來說,相應類型的數組指針就可以作為迭代器,而對于更一般的類對象來說,迭代器也是相應的類對象(但是具有與指針相似的性質),可以對迭代器使用解除引用運算符(解除引用后就是相應的存儲的數據)以及++運算符等(這些運算符在迭代器類中進行了重載),一般來說,每個容器類都定義了自己的迭代器,而且迭代器的類型各不相同,為的是可以實現不同的功能。

為了區分++運算符的前綴版本和后綴版本,c++中規定了將operator++作為前綴版本,將operator++(int)作為后綴版本(括號中的參數永遠不會用到,只是后綴版本的一種標識,因此不必有變量名稱),前綴版本是先加后返回加了之后的值,而后綴版本是返回加之前的值,并進行++操作。

為了能夠統一不同的容器類,c++規定迭代器都有起始迭代器和超尾迭代器(超尾標記),比如鏈表容器類就要有個沒有數據的超尾結點。begin()和end()就是指向起始位置和超尾位置的迭代器,我們無需知道超尾是如何實現的,也無需知道迭代器是如何實現的,我們僅僅知道迭代器的通用方法即可。注意,一個類的迭代器,我們僅需要這樣使用,比如vector<double>::iterator pp;就是一個指向vector<double>類對象的迭代器,其他模板類也是如此。

2.迭代器的類型

輸入迭代器:這個輸入是對程序而言的,即從容器中讀取數據,解除引用可以讓迭代器得到容器的數據。輸入迭代器是單向迭代器,可以遞增,但不可以倒退。輸入迭代器還是單通行的,也就是說不依賴于前一次遍歷的值也不依賴于本次已經遍歷的值。

輸出迭代器:表示程序用這個迭代器將內容輸出到容器(也是對程序而言的),對其解除引用可以讓程序修改容器值,而不是讀取。簡而言之,對于單通行,只讀算法,可以使用輸入迭代器;而對于單通行,只寫算法,可以使用輸出迭代器。

正向迭代器:和輸入迭代器與輸出迭代器相同,正向迭代器只使用++運算符來遍歷容器,正向迭代器可以讀取也可以寫入,如果使用const修飾可以設置成只能讀取。正向迭代器就是輸入迭代器和輸出迭代器的結合體,并且正向迭代器是多次通行的。

雙向迭代器:可以雙向遍歷容器,比如前一個遞加,后一個遞減。

隨機訪問迭代器:也就是可以跳到容器中的任何一個元素,上面的迭代器可以遞加遞減等,但不可以加上一個整型,也就是隨機訪問。這里的隨機并不是指隨機指向一個元素,而是我希望指向哪個元素都可以立即指向它,也就是直接跳到容器的任何一個元素,這就叫作隨機訪問。隨機訪問迭代器支持上面的雙向迭代器的所有功能,同時還支持重載的加法運算,并且迭代器可以進行比較運算。

3.迭代器的層次結構

高層次的迭代器擁有低層次迭代器的全部功能,輸入和輸出迭代器是最低層次的迭代器;再是正向迭代器;再是雙向迭代器;再是隨機訪問迭代器。一般我們可以直接使用隨機訪問迭代器,只有在特定的具有安全性要求的場合下我們才使用特定類型的迭代器。隨機訪問迭代器層次下的迭代器沒有[]運算和加法減法等運算。

4.概念,改進和模型

各種迭代器的類型只是一種概念性的描述,并不是目前已經實現和存在的,比如list<double> a;就是一個雙向迭代器,而vector<double> b;就是一個隨機訪問迭代器。

上面講到的迭代器是更多的是一種要求,而不是類型,這種要求就是概念。概念的類似繼承的關系叫作改進,比如雙向迭代器就是正向迭代器的改進;概念的具體化叫作模型。比如指向一個int類型的常規指針就是一個隨機訪問迭代器的模型。

可以將STL算法用于常規數組,因為數組指針可以看成是一種隨機訪問迭代器。STL提供了一組預定義的迭代器,比如copy(a,b,c);函數就可以將迭代器a,b的內容復制到c迭代器位置,也就是將一個容器的內容復制到另一個容器的相應位置;使用這種copy的方法,我們可以將容器的內容輸出到輸出流中,首先我們要定義一個輸出流的迭代器(或者可以被稱為適配器),比如可以包含文件:

#include <iterator>

ostream_iterator<int,char> out_iter(cout,””);//定義輸出流迭代器,輸出類型為int,輸出的字符類型為char,輸出以空格分隔,使用cout輸出到屏幕

copy(dice.begin(),dice.end(),out_iter);

這種方法可以使容器類的輸入輸出更為方便。

5.其他有用的迭代器

c++的STL中,迭代器也是一種類模板,比如istream_iterator<int,char> a(cout,”;”);這種輸出流迭代器。STL提供了一系列的迭代器來方便對數據的處理和輸入輸出等操作。除了istream_iterator和ostream_iterator迭代器之外,還有reverse_iterator,back_insert_iterator,front_insert_iterator和insert_iterator。

reverse_iterator:對reverse_iteratora執行遞增操作導致它遞減,這主要是為了簡化已有的函數(通過這種反轉的迭代器可以使遞增輸出的函數來遞減輸出)。比如vector模板類中有一個rebegin()和rend()的函數,這兩個函數分別返回超尾迭代器和指向第一個元素的迭代器,但是都是reverse_iterator類型的迭代器。因為對反向迭代器的遞增操作就是遞減,因此可以使用如下的方法來反向顯示容器的內容:copy(dice.rebegin(),rend(),out_iter);這樣甚至不必聲明反向迭代器。這里要注意的是對于反向迭代器來說,對它使用解除引用,相當于對它遞減1之后再使用解除引用(本質是指向它的前一個元素),這就是反向迭代器的特殊補償。

back_insert_iterator,會將內容插入到容器的尾部;front_insert_iterator,會將內容插入到容器的頭部;insert_iterator,可以將內容插入到容器的構造參數位置的前面,它有兩個構造參數,一個是容器的名稱,另一個是指向容器的要在前面插入內容的元素的迭代器。這三個迭代器都是輸出容器迭代器的概念模型(輸出迭代器就是只寫)。使用方法:要為名為dice的vector<double>容器創建一個back_insert_iterator迭代器,可以使用如下的方式,back_insert_iterator<vector<double>? > back_iter(dice);,可以看出,迭代器也是一種模板,需要使用具體化的容器模板來對它進行具體化,而構造函數的參數就是具體的容器對象。這個迭代器模板是在<interator>中定義的,而一般的迭代器是在相應的容器的模板文件中定義的。必須使用容器類型來進行聲明的原因是迭代器必須使用合適的容器類的方法。

6.容器種類

(1)容器概念:容器是存儲對象的對象。被存儲的對象必須是同一種類型的,可以是內置數據類型,也可以是OOP意義上的對象。不是任意類型的對象都可以添加到容器中的,只有那些具有復制構造函數和可賦值的類對象才可以添加到容器中(也就是復制構造函數和賦值運算符都是公有的)。

(2)容器要求:復雜度要求(線性復雜度,固定復雜度)(編譯時間就是在編譯的時候已經計算了所有的計算量);返回類型要求。

復制構造和復制賦值以及移動構造和移動賦值之間的差別主要是:復制操作保留源對象,而移動操作可能修改原對象,還可以轉讓所有權而不做任何復制。通常移動操作的效率要高于復制操作。

(3)序列:可以通過添加要求來改進基本的容器概念,序列是一種重要的改進。序列中的元素具有特定的順序(這里的順序并不是說要由小到大,而是具有固定的不變的順序)。

(4)有七種序列容器類型,下面加以介紹。

Vector:除序列外,vector還是一個可反轉容器,vector模板類是最簡單的序列類型,除非其他序列的優點能更好滿足程序的要求,否則我們優先選取vector模板類來構造對象

Deque類:在頭文件<deque>中定義,在頭文件中定義,表示雙端隊列,類似于vector容器,但與vector類相比不同點是,從起始位置執行插入和刪除操作的時間復雜度也是固定時間,而不是像vector類那樣從結尾處是固定時間,從開頭和中間是線性時間。

List類:表示雙向鏈表。與vector的區別是,所有的節點的插入與刪除的時間都是固定時間。而vector類只有在結尾處是固定時間的插入,但是在開頭和中間是線性時間的插入。因此,vector強調的是通過隨機訪問進行快速訪問,而list類(雙向鏈表)強調的是元素的快速插入和刪除(list雙向鏈表在任何位置的插入和刪除都是固定時間)。list的典型的成員函數:sort()排序,splice(iterator pos,list<T,Alloc> x)將鏈表x插入到迭代器pos之前,并且是移動插入(不改變指向x的迭代器),unique()函數,就是將連續的相同的元素合并成一個。需要注意的是非成員的函數sort,因為sort函數需要隨機訪問迭代器,而鏈表可以執行快速插入的代價就是放棄隨機訪問,因此不能將鏈表應用于非成員的sort函數。

Foward_list類:每個節點只連接到下一個節點,而沒有鏈接到上一個節點,因此是不可反轉的容器。

Queue容器類:隊列,功能更少,甚至不能遍歷容器。它是一個適配器接口,功能是讓底層類,默認為讓deque展示典型的隊列接口。也即是隊列只能進行從結尾加,從開頭刪,是否非空等操作。

Priority_queue類:也是在queue頭文件中聲明的。默認的底層類是vector,功能與queue差不多,不同點或者說最大特點是最大的元素被移到隊首(比如隊列中的vip用戶要優先服務)。使用方法:priority_queue<int> a;或者priority_queue<int> b(greater<int>);其中第二個構造函數是通過一個函數對象來進行初始化,里面的greater<int>()是一個預定義的函數對象,greater是一個函數對象類模板。

Stack:也是一個適配器類,它給底層類(我的理解是繼承關系),默認為vector類提供了典型的棧接口。方法有壓入,彈出,查看棧頂值(不能進行遍歷,也就是不能查找,只能查看棧頂這元素),檢查元素數目和檢查是否為空等。函數分別是push,pop,top,size,empty等。

Array類:在頭文件<array>中定義的,它并非STL容器,因為它的長度是固定的,也因此不能使用push_back和insert等調整容器大小的操作。但是它定義了operator []和at()函數,并且可以將很多標準STL算法用于對象,比如for_each()和copy()。

6.關聯容器和無序關聯容器

關聯容器是對容器概念的另一個改進,關聯容器將值與鍵關聯在一起,可以用鍵來查看值。通常,對于一個容器X來說,X::value_type通常表示容器中儲存的值的類型,對于關聯容器來說,表達式X::key_type表示容器中的鍵的類型。關聯容器的優點在于快速的檢索信息,一般來說,關聯容器有快速確定數據位置的算法,以便可以快速的插入和檢索信息。關聯容器通常是使用某種樹來實現的(也就是數據結構中的樹或二叉數的邏輯形式)。

STL提供有四種典型的關聯容器:set,multiset,map,multimap這四種。set和multiset是在頭文件<set>中定義的,而map和multimap是在頭文件<map>中定義的。set是鍵和值的類型相同的一種關聯容器,而multiset鍵和值的類型也是相同的,但是值可以有多個。同樣,map是鍵和值類型不同的關聯容器,值只有一個,multimap值可以有多個,鍵只能有一個。

set不能存儲多個相同的值,因為它的鍵和值其實是一樣的,而鍵只能有一個,是唯一的。set的模板構造函數和vector,list相似,都是用存儲的類型來進行模板具體化,還有另外一個模板參數,就是指定用來進行比較的函數,默認就是less<>模板函數,如set<string,less<> > A;。set的構造函數也可以是兩個迭代器,第一個指向第一個元素,第二個指向超尾元素,set的構造的對象會將相同的元素合并成一個,然后會將元素按照特定的規則排序(默認是less<>模板函數)。

無序關聯容器是對容器概念的另一種改進,無序關聯容器也將值和鍵連接在一起,并使用鍵來查找值。二者的區別是,關聯容器是基于樹結構的,而無序關聯容器是基于數據結構哈希表的。旨在提高添加和刪除元素的速度以及提高查找算法的效率。四種無序關聯容器是:unordered_set,unordered_multiset,unordered_map,unordered_multimap。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容