1. 程式人生 > 其它 >STL原始碼

STL原始碼

 

 

 

(1)請你講講STL有什麼基本組成:

 

容器、迭代器、仿函式、演算法、分配器、配接器

 

他們之間的關係:分配器給容器分配儲存空間,演算法通過迭代器獲取容器中的內容,仿函式可以協助演算法完成各種操作,配接器用來套接適配仿函式

 

 

 

13C++STL介紹(這個系列也很重要,建議侯捷老師的這方面的書籍與視訊),其中包括記憶體管理allocator,函式,實現機理,多執行緒實現等

 

STL的分配器用於封裝STL容器在記憶體管理上的底層細節。在C++中,其記憶體配置和釋放如下:

 

new運算分兩個階段:(1)呼叫::operator new配置記憶體(2)呼叫物件建構函式

構造物件內容

 

delete運算分兩個階段:(1)呼叫物件析構函數;(2)呼叫::operator delete釋放記憶體

 

為了精密分工,STL allocator將兩個階段操作區分開來:記憶體配置有alloc::allocate()負責,記憶體釋放由alloc::deallocate()負責;物件構造由::construct()負責,物件析構由::destroy()負責。

 

同時為了提升記憶體管理的效率,減少申請小記憶體造成的記憶體碎片問題,SGI STL採用了兩級配置器,當分配的空間大小超過128B時,會使用第一級空間配置器;當分配的空間大小小於128B時,將使用第二級空間配置器。第一級空間配置器直接使用

malloc()realloc()free()函式進行記憶體空間的分配和釋放,而第二級空間配置器採用了記憶體池技術,通過空閒連結串列來管理記憶體。

 

 

19C ++記憶體管理,記憶體池技術(熱門問題),與csapp中幾種記憶體分配方式對比學習加深理解

 

13STL的記憶體優化

1. 使用allocate向記憶體池請求size大小的記憶體空間,如果需要請求的記憶體大小大於128bytes,直接使用malloc

2. 如果需要的記憶體大小小於128bytesallocate根據size找到最適合的自由連結串列。

a. 如果連結串列不為空,返回第一個node,連結串列頭改為第二個

node

b. 如果連結串列為空,使用blockAlloc請求分配node

x. 如果記憶體池中有大於一個node的空間,分配竟可能多的node(但是最多20),將一個node返回,其他的node新增到連結串列中。

y. 如果記憶體池只有一個node的空間,直接返回給使用者。

z. 若果如果連一個node都沒有,再次向作業系統請求分配記憶體。

①分配成功,再次進行b過程。

②分配失敗,迴圈各個自由連結串列,尋找空間。

I. 找到空間,再次進行過程b

II. 找不到空間,丟擲異常。

3. 使用者呼叫deallocate釋放記憶體空間,如果要求釋放的記憶體空間大於128bytes,直接呼叫free

4. 否則按照其大小找到合適的自由連結串列,並將其插入。

 

 

 


14STL原始碼中的hash表的實現

 


15STLunordered_mapmap的區別和應用場景

 

Map底層結構是紅黑樹。unordered map底層結構是雜湊表

 

map map內部實現了一個紅黑樹(紅黑樹是非嚴格平衡二叉搜尋樹,而AVL是嚴格平衡二叉搜尋樹),紅黑樹具有自動排序的功能,因此map內部的所有元素都是有序的,紅黑樹的每一個節點都代表著map的一個元素。因此,對於map進行的查詢,刪除,新增等一系列的操作都相當於是對紅黑樹進行的操作。map中的元素是按照二叉搜尋樹儲存的,使用中序遍歷可將鍵值按照從小到大遍歷出來。

 

unordered_map: unordered_map內部實現了一個雜湊表(也叫散列表,通過把關鍵碼值對映到Hash表中一個位置來訪問記錄,查詢的時間複雜度可達到O(1),其在海量資料處理中有著廣泛應用)。因此,其元素的排列順序是無序的

 

 

 

15)請你說說STLMultimapMap的區別

 

1Map對映,map 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。不允許鍵值重複。

 

底層實現:紅黑樹

 

適用場景:有序鍵值對不重複對映

 

2Multimap

 

多重對映。multimap 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。允許鍵值重複。

 

底層實現:紅黑樹

 

適用場景:有序鍵值對可重複對映

 

16STLvector的實現

 

關於vector簡單的說就是一個動態增長的陣列,裡面有一個指標指向一片連續的記憶體空間,當空間裝不下要容納的資料的時候會自動申請一片更大的空間(空間配置器)將原來的資料拷貝到新的空間,然後就會釋放舊的空間。當刪除的時候空間並不會釋放只是清空了裡面的資料。

 

vector的資料安排以及操作方式與陣列非常相似,兩者唯一區別在於空間運用的靈活性,陣列是靜態空間一旦配置了就不能再改變大小,如果要增容的話,就要把資料搬到新的數組裡面,然後再把原來的空間釋放掉還給作業系統。vector是動態的隨著元素的增加,它的內部機制會自動的擴充空間來容納新的元素。因此,vector的運用對於記憶體的合理利用與運用的靈活性有很大的幫助,我們不必害怕空間不足而一開始就開闢一塊很大的記憶體。

 

vector的實現技術,關鍵在於其對大小的控制以及重新配置時的資料移動效率。一旦vector的舊空間滿載了,如果客戶端每新增加一個元素,vector的內部只是擴充了一個元素空間,其實這樣是比較不明智的。因為所謂的擴充空間(無論多大),過程都是配置新空間——資料移動——釋放舊空間,成本還是比較高的。vector維護的是一個連續的線性空間,所以vector支援隨機訪問。

 

 

vector的動態增加大小的時候,並不是在原有的空間上持續增加成新的空間(無法保證原空間的後面還有可供配置的空間),而是以原大小的兩倍另外配置一塊較大的空間,然後將原來的內容拷貝過來,並釋放原來的空間。因此,對vector的任何操作一旦引起了空間的重新配置,指向原vector的所有迭代器會都失效了,這是比較容易犯的一個錯誤。

 

 

 

17STL容器的幾種迭代器以及對應的容器(輸入迭代器,輸出迭代器,前向迭代器,雙向迭代器,隨機訪問迭代器)

 

順序容器:vector,deque是隨機訪問迭代器;list是雙向迭代器

 

容器介面卡:stack,queue,priority_queue沒有迭代器

 

關聯容器:set,map,multiset,multimap是雙向迭代器

 

unordered_set,unordered_map,unordered_multiset,unordered_multimap是前向迭代器

 

 

18STL中的traits技法

STL原始碼剖析

type_traits

iterator_traits

char traits

allocator_traits

pointer_traits

array_traits

 

19 vector使用的注意點及其原因,頻繁對vector呼叫push_back()對效能的影響和原因。

在一個vector的尾部之外的任何位置新增元素,都需要重新移動元素。而且,向一個vector新增元素可能引起整個物件儲存空間的重新分配。重新分配一個物件的儲存空間需要分配新的記憶體,並將元素從舊的空間移到新的空間

 

 

20)請你來說一下mapset有什麼區別,分別又是怎麼實現的?

 

mapset都是C++的關聯容器,其底層實現都是紅黑樹(RB-Tree。由於 map set所開放的各種操作介面,RB-tree 也都提供了,所以幾乎所有的 map set的操作行為,都只是轉調 RB-tree 的操作行為。

 

mapset區別在於:

 

map中的元素是key-value(關鍵字—值)對:關鍵字起到索引的作用,值則表示與索引相關聯的資料;Set與之相對就是關鍵字的簡單集合,set中每個元素只包含一個關鍵字。

 

set的迭代器是const的,不允許修改元素的值;map允許修改value,但不允許修改key。其原因是因為mapset是根據關鍵字排序來保證其有序性的,如果允許修改key的話,那麼首先需要刪除該鍵,然後調節平衡,再插入修改後的鍵值,調節平衡,如此一來,嚴重破壞了mapset的結構,導致iterator失效,不知道應該指向改變前的位置,還是指向改變後的位置。所以STL中將set的迭代器設定成const,不允許修改迭代器的值;而map的迭代器則不允許修改key值,允許修改value值。

 

map支援下標操作,set不支援下標操作map可以用key做下標,map的下標運算子[ ]將關鍵碼作為下標去執行查詢,如果關鍵碼不存在,則插入一個具有該關鍵碼和mapped_type型別預設值的元素至map中,因此下標運算子[ ]map應用中需要慎用,const_map不能用,只希望確定某一個關鍵值是否存在而不希望插入元素時也不應該使用,

 

mapped_type型別沒有預設值也不應該使用。如果find能解決需要,儘可能用find

 

 

21)請你來說一說STL迭代器刪除元素

 

這個主要考察的是迭代器失效的問題。

 

1.對於序列容器vector,deque來說,使用erase(itertor)後,後邊的每個元素的迭代器都會失效,但是後邊每個元素都會往前移動一個位置,erase會返回下一個有效的迭代器

 

2.對於關聯容器map set來說,使用了erase(iterator)後,當前元素的迭代器失效,但是其結構是紅黑樹,刪除當前元素的,不會影響到下一個元素的迭代器,所以在呼叫erase之前,記錄下一個元素的迭代器即可。

 

3.對於list來說,它使用了不連續分配的記憶體,並且它的erase方法也會返回下一個有效的iterator,因此上面兩種正確的方法都可以使用。

 

 

 

22)請你來說一下STL中迭代器的作用,有指標為何還要迭代器

 

1、迭代器

 

Iterator(迭代器)模式又稱Cursor(遊標)模式,用於提供一種方法順序訪問一個聚合物件中各個元素, 而又不需暴露該物件的內部表示。或者這樣說可能更容易理解:Iterator模式是運用於聚合物件的一種模式,通過運用該模式,使得我們可以在不知道物件內部表示的情況下,按照一定順序(由iterator提供的方法)訪問聚合物件中的各個元素。

 

由於Iterator模式的以上特性:與聚合物件耦合,在一定程度上限制了它的廣泛運用,一般僅用於底層聚合支援類,如STLlistvectorstack等容器類及ostream_iterator等擴充套件iterator

 

 

 

2、迭代器和指標的區別

 

迭代器不是指標,是類模板,表現的像指標。他只是模擬了指標的一些功能,通過過載了指標的一些操作符,->*++--等。迭代器封裝了指標,是一個“可遍歷STLStandard Template Library)容器內全部或部分元素”的物件,本質是封裝了原生指標,是指標概念的一種提升(lift),提供了比指標更高階的行為,相當於一種智慧指標,他可以根據不同型別的資料結構來實現不同的++--等操作。

 

迭代器返回的是物件引用而不是物件的值,所以cout只能輸出迭代器使用*取值後的值而不能直接輸出其自身。

 

3、迭代器產生原因

 

Iterator類的訪問方式就是把不同集合類的訪問邏輯抽象出來,使得不用暴露集合內部的結構而達到迴圈遍歷集合的效果。

 

 

 

23)請你回答一下STLresizereserve的區別

 

resize():改變當前容器內含有元素的數量(size())eg: vector<int>v; v.resize(len);vsize變為len,如果原來vsize小於len,那麼容器新增(len-size)個元素,元素的值為預設為0.v.push_back(3);之後,則是3是放在了v的末尾,即下標為len,此時容器是sizelen+1

 

reserve():改變當前容器的最大容量(capacity,它不會生成元素,只是確定這個容器允許放入多少物件,如果reserve(len)的值大於當前的capacity(),那麼會重新分配一塊能存len個物件的空間,然後把之前v.size()個物件通過copy construtor複製過來,銷燬之前的記憶體;