(GeekBand)STL與泛型編程第二周筆記

體系結構與內核分析

OOP VS GP

<big>OOPdatasmethods關聯在一起,GP卻將datasmethods分開來。
  GP可以讓ContainersAlgorithms兩個團隊各自進行工作,只需要通過Iterator聯通,讓另一個團隊使用。
  對于list來說,由于其迭代器不能進行加減運算,所以不能采用Algorithem所提供的sort排序算法,所以list是通過OOP自己實現一個sort
  對于algorithms來說,無論哪種算法,幾乎都是通過元素的比較運算,通過迭代器來對容器進行各種操作。</big>

操作符重載與模板

<big>對于一些特殊的操作符如: :: . . ?: *這四個操作符不能進行重載。
  模板內容見前幾期筆記內容。</big>

分配器allocator

<big>C++的動態內存分配,最終都是通過調用malloc來實現的。malloc在分配內存空間的時候,不僅僅只是分配所需要的內存大小,還會存在其他的內存消耗,比如說內存對齊,記錄分配空間大小的區塊和debug內存等等。詳見上次作業operator new部分。
對于VC++6.0和BC5.0大部分的容器默認都是采用的allocator這個分配器。分配器通過allocatedeallocate來調用operator newoperator delete進行分配和釋放內存,而operator newoperator delete又是通過嗲用mallocfree進行的操作。allocatedeallocate可以直接通過對象調用,如下:

不推薦直接調用

  由于每一次申請內存空間都會調用malloc進行申請,這就造成了在多次空間分配的情況下有很大的空間浪費。對于GCC2.9也具有和上面一樣的分配器,但是一般都不使用。GCC還帶有另一種分配器,并且被默認使用,就是alloc,該分配器對內存分配做了相應的優化,可以減少很多額外的內存消耗,但是之后的版本取消了相應的優化設計,而alloc變成了__pool_alloc。其內存模型如下:</big>
alloc的實現模型

容器之間的實現關系與分類

部分容器是通過其他容器作為底層實現的

鏈表list

<big>list為雙向鏈表,每一個節點除了數據所占用的空間,還需要兩個指針對象,指向當前結點的上一個節點和下一個節點,最后一個節點的下一個節點的next指向第一個節點成為一個循環(為了實現前閉后開,所以特意加了一個空節點,尾后迭代器指向該節點)。

節點設計
鏈表模型

對于鏈表的迭代器iterator并不是一個指針,因為list并不是一個連續的空間,所以必須自定義操作符重載讓它具有類似于指針的作用,即為智能指針。
  對于++--操作符重載,由于這兩種操作符都只對應一個操作符,但是操作符可以前置也可以后置,對于這種操作符的重載就必須先,區分到底是前置還是后置,就以在重載的時候對后置運算符的參數列表里面加上一個int標記,后置++不能連續兩次在同一個語句中。

迭代器示例

對于GCC4.9與2.9的節點設計來說,2.9采用的是void,這種方式需要對指針類型進行轉換操作,而到了4.9有一個良好的改善,將指針類型轉變為*__list_node_base****;并且在創建迭代器的時候4.9也只需要一個模板參數。</big>

迭代器設計原則和iterator traits的作用與設計

<big>iterator是算法和容器之間的橋梁,算法需要知道迭代器的五個associated types(category; difference_type; value_type; reference_type; pointer_type)中的一個或幾個,才能進行運算。所以算法通過提問的方式,得到迭代器的associated types,即iterator必須提供這五種設計。

五種**associated types**

對于iterator不是一個class,而是一種單純的指針,就不能采用上面這種問答方式。這種情況下就需要單獨設計一種traitstraits必須能夠分別單純的指針迭代器和class迭代器。</big>

萃取機
區分過程

Vector

<big>vector可以認為是一種可以自動擴充的數組,每次擴充都是重新分配一塊原來大小雙倍空間,然后把數據拷貝過去,再刪除原有的內存空間,實現容量增大。vector的迭代器為普通的元素指針外覆一個iterator adapter,而不是設計好的智能指針。

vector iterator

到了后期,每一次空間成長和插入都會造成大量調用元素的拷貝構造和析構,所以在經常性在中間插入的情況下盡量不用vector容器。</big>

array

<big>將單純的數組通過容器來實現,可以是數組能夠與算法進行聯系,使所有數據結構都有一個統一的運行方式。array實例化的時候最少都要有一個元素空間并且之后空間大小不可改變。array類沒有構造與析構函數且其迭代器也是一個普通的指針。</big>

array實現

deque&queue和stack

<big>deque在內存空間并不是一整塊連續的空間,是多塊連續內存的集合。所以需要對迭代器的操作符進行重載,當迭代器指向當前緩沖區的邊界的時候,會自動移動到下一緩沖區的開始位置,其迭代器不是單純的指針而是一個帶有四個指針和操作符重載的class。</big>

deque的內存分布

queue一種先進后出的容器(適配器),默認通過deque來實現,封鎖deque的某些功能就能實現一個queue,所有對于queue的操作都是通過底層的deque的相關操作實現的。stack的實現也是這樣的,通過deque實現。

queue內存模型
queue的實現

stackqueue都不提供遍歷過程,也不提供迭代器,其內部實現不一定要使用deque,也都可以采用list作為類模板的第二模板參數。對于stack可以用vector作為底層結構,但是queue不可以。</big>

紅黑樹(RB-tree)

<big>紅黑樹是一種平衡式二分查找樹,其排列規則對查找和插入有很大優勢,并且保持平衡,即最大深度與最小深度只差最多為1。對紅黑樹通過迭代器進行遍歷之后,一定是排序后的狀態。對于紅黑樹的迭代器,不應用于修改紅黑樹的節點數值,更改之后會破壞搜索結構。

紅黑樹的結構

紅黑樹具有五個模板參數,key作為查找和排序;value用于key所對應的節點的值的結合;keyofvaluekey所對應的打他值;compare為比較運算規則,為仿函數對象;如果keyvalue的類型設置為相同,則節點沒有data部分,key就是data。</big>

image.png

set/multiset

<big>set/multiset都是以紅黑樹為底層結構的容器,因此具有自動排序的特性,依據key排序,且setvalue就是keykey也是value。不能使用迭代器來改變元素的值,其迭代器也是const iterator,禁止用戶對元素賦值。set需要三個模板參數:</big>

set實現

map/multimap

<big>map/multimapset的差別不大,不過是mapvalue是由keydata通過pair組合而成,pair.first keypair.second data,其中key不能改變,通過pairfirst的類型前加const實現,而data可以改變,通過key進行排序和查找,對于map來說,key可以作為下標。map需要默認四個模板參數,后兩個有默認值。

**map**實現

map有重載操作符[]進行下標運算,[]接受的下標為KEY值,如果** key** 不存在就會創建一個,以下標為** key pair節點。但是multi map不支持[]運算符,因為multi map是可以有重復key**值的。</big>

hash table

**hash table**

<big>給定每一個對象設置一個號碼,那么至少需要sizeof(T)2^32大的空間才能完全容納下所有對象。但是并不是每一個號碼都有相應的對象,那么我么可以給定一個小于剛剛所需的最大空間,但是要大于對象個數的空間來存放所有對象。而之后的對象在內存空間所在的位置通過號碼對空間大小取余之后的位置上。比如說有兩個int型整數1112如果按照編號放的話就需要12int型對象的空間,但是由于只有兩個數值,卻占用了更大的空間,所以我們可以只分配兩個空間(buckets),讓他們對2取余的到兩個編號12,那么11放在buckets1的位置,12放在buckets2的位置,這樣不僅方便查找還省內存空間。這就是hash table的基礎。但是如果兩個數字是1113那么對2取余就會造成,兩個數值都放在了編號為1buckets*,但是一個空間不能放兩個數據,所以用一個鏈表把他們串聯起來,如下圖:

**buckets**比較小時內存分配

在這種情況下,如果鏈表太長又會影響到查找的速度,那么就可以擴大buckets的范圍,使整個空間變大,那么元素取余之后便會變得更分散,鏈表就會變短,鏈表越短查找越快。如下圖:

**buckets**比較大時內存分配

buckets的大小一般都采用大的素數來實現一個hash tablehash table容器一般都是作為其它容器的底層,不會單獨拿出來使用。hash table的實現中hashfcn又稱作散列函數,用于算出元素所處bukets位置即hash codeExtractkey 去除對象的編號函數,Equalkey用于對象比較的函數。** hash table**的源代碼如下圖:</big>

**hash table**

對本周內容的理解

<big>在STL中常用容器有: vector、list、deque、set、map等。根據他們的特性不同有不同的適用范圍,比如:
  set map都是無序的保存元素,只能通過它提供的接口對里面的元素進行訪問。set:集合, 用來判斷某一個元素是不是在一個組里面時比較好用,占用空間少,查找快;map:映射,相當于字典,把一個值映射成另一個值,如果想創建字典的話使用它好了。setmap底層多采用的是樹型結構,多數使用平衡二叉樹實現,查找某一值是常數時間,遍歷起來效果也不錯, 只是每次插入值的時候,會重新構成底層的平衡二叉樹,效率有一定影響,也有的是通過hash_table來實現,占用空間比通過樹來實現要多,但是訪問速度更快,特別是空間很大時訪問速度最快。
  vector、list、deque、set、array 是有序容器。
  
vector
就是動態數組.它也是在堆中分配內存,元素連續存放,有保留內存,如果減少大小后內存也不會釋放.如果添加新值后占用空間大于當前大小時才會再分配內存。它擁有一段連續的內存空間,并且起始地址不變,因此它能非常好的支持隨即存取,即[]操作符,但由于它的內存空間是連續的,所以在中間進行插入和刪除會造成內存塊的拷貝,另外,當該數組后的內存空間不夠時,需要重新申請一塊足夠大的內存并進行內存的拷貝。這些都大大影響了vector的效率。對最后元素操作最快(在后面添加刪除最快 ), 此時一般不需要移動內存,只有保留內存不夠時才需要對中間和開始處進行添加刪除元素操作需要移動內存,如果你的元素是結構或是類,那么移動的同時還會進行構造和析構操作,所以性能不高 (最好將結構或類的指針放入vector中,而不是結構或類本身,這樣可以避免移動時的構造與析構)。 訪問方面,對任何元素的訪問都是O(1),也就是是常數的,所以vector常用來保存需要經常進行隨機訪問的內容,并且不需要經常對中間元素進行添加刪除操作。
  list就是雙向鏈表,元素也是在堆中存放,每個元素都是放在一塊內存中,它的內存空間可以是不連續的,通過指針來進行數據的訪問,這個特點使得它的隨即存取變的非常沒有效率,因此它沒有提供[]操作符的重載。但由于鏈表的特點,它可以以很好的效率支持任意地方的刪除和插入。list沒有空間預留習慣,所以每分配一個元素都會從內存中分配,每刪除一個元素都會釋放它占用的內存。list在哪里添加刪除元素性能都很高,不需要移動內存,當然也不需要對每個元素都進行構造與析構了,所以常用來做隨機操作容器.。但是訪問list里面的元素時就開始和最后訪問最快,訪問其它元素都是O(n) ,所以如果需要經常隨機訪問的話,還是使用其它的好。如果你喜歡經常添加刪除大對象的話,那么請使用list。要保存的對象不大,構造與析構操作不復雜,那么可以使用vector代替。list<指針>完全是性能最低的做法,這種情況下還是使用vector<指針>好,因為指針沒有構造與析構,也不占用很大內存 。
  deque是一個雙端隊列(double-ended queue),也是在堆中保存內容的。每個堆保存好幾個元素,然后堆和堆之間有指針指向,看起來像是listvector的結合品.它支持[]操作符,也就是支持隨即存取,可以讓你在前面快速地添加刪除元素,或是在后面快速地添加刪除元素,然后還可以有比較高的隨機訪問速度,和vector的效率相差無幾,它支持在兩端的操作:push_back,push_front,pop_back,pop_front等,并且在兩端操作上與list的效率也差不多。在標準庫中vectordeque提供幾乎相同的接口,在結構上它們的區別主要在于這兩種容器在組織內存上不一樣,deque是按頁或塊來分配存儲器的,每頁包含固定數目的元素.相反vector分配一段連續的存,vector只是在序列的尾段插入元素時才有效率,而deque的分頁組織方式即使在容器的前端也可以提供常數時間的inserterase操作,而且在體積增長方面也比vector更具有效率。

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

推薦閱讀更多精彩內容