《STL源碼剖析》相關面試題總結
一、STL簡介
STL提供六大組件,彼此可以組合套用:
- 容器
容器就是各種數據結構,我就不多說,看看下面這張圖回憶一下就好了,從實現角度看,STL容器是一種class template。 - 算法
各種常見算法,如sort,search,copy,erase等,我覺得其中比較值得學習的就是sort,next_permutation,partition,merge sort,從實現角度看,STL算法是一種function template。 - 叠代器
扮演容器與算法之間的膠合劑,是所謂的“泛型指針”。共有五種類型,從實現角度看,叠代器是一種將operator*,operator->,operator++,operator--等指針相關操作予以重載的class template。所有STL容器都附帶有自己專屬的叠代器,只有容器設計者才知道如何設計叠代器。原生指針也是一種叠代器。是設計模式的一種,所以被問到了解的設計模式可以用來湊數。 - 仿函數
行為類函數,可作為算法的某種策略,從實現角度看,仿函數是一種重載了operator()的class或class template。一般函數指針可視為狹義的仿函數。 - 配接器
一種用來修飾容器或者仿函數或叠代器接口的東西。比如queue和stack,看著像容器,其實就是deque包了一層皮。 - 配置器
負責空間配置與管理。從實現角度看,配置器是一個實現了動態空間配置、空間管理、空間釋放額class template。
二、關於容器的一些問題
2.1 當vector的內存用完了,它是如何動態擴展內存的?它是怎麽釋放內存的?用clear可以釋放掉內存嗎?是不是線程安全的?
- vector內存用完了,會以當前size大小重新申請2*size的內存,然後把原來的元素復制過去,把新元素插上,然後釋放原來的內存。
- 一般我們釋放vector裏的元素使用clear,其實它不能釋放內存,要想釋放內存要使用swap,這樣:
vector<type> v;
//.... 這裏添加許多元素給v
//.... 這裏刪除v中的許多元素
vector<type>(v).swap(v);
//此時v的容量已經盡可能的符合其當前包含的元素數量
//對於string則可能像下面這樣
string(s).swap(s);
- 引用《effective stl》的第十二條:當涉及 STL容器和線程安全性時,你可以指望一個 STL庫允許多個線程同時讀一個容器,以及多個線程對不同的容器做寫入操作。你不能指望 STL庫會把你從手工同步控制中解脫出來,而且你不能依賴於任何線程支持。必須自己去寫多線程安全措施。
2.2 map是怎麽實現的?查找的復雜度是多少?能不能邊遍歷邊插入?
-
紅黑樹和散列
-
O(logn)
-
不可以,map不像vector,它在對容器執行erase操作後不會返回後一個元素的叠代器,所以不能遍歷地往後刪除。
2.3 寫多讀少應該用什麽容器?
私以為是鏈表,鏈表的插入操作時常數時間復雜度,訪問操作是O(n),是最適合寫多讀少的容器。
2.4 vector每次insert或erase之後,以前保存的iterator會不會失效?
理論上會失效,理論上每次insert或者erase之後,所有的叠代器就重新計算的,所以都可以看作會失效,原則上是不能使用過期的內存
但是vector一般底層是用數組實現的,我們仔細考慮數組的特性,不難得出另一個結論,
-
insert時,假設insert位置在p,分兩種情況:
a) 容器還有空余空間,不重新分配內存,那麽p之前的叠代器都有效,p之後的叠代器都失效
b) 容器重新分配了內存,那麽p之後的叠代器都無效咯 -
erase時,假設erase位置在p,則p之前的叠代器都有效並且p指向下一個元素位置(如果之前p在尾巴上,則p指向無效尾end),p之後的叠代器都無效
2.5 hash_map和map的區別在哪裏?
hash_map底層是散列的所以理論上操作的平均復雜度是常數時間,map底層是紅黑樹,理論上平均復雜度是O(logn),下面是借鑒的網上的總結:
這裏總結一下,選用map還是hash_map,關鍵是看關鍵字查詢操作次數,以及你所需要保證的是查詢總體時間還是單個查詢的時間。如果是要很多次操作,要求其整體效率,那麽使用hash_map,平均處理時間短。如果是少數次的操作,使用 hash_map可能造成不確定的O(N),那麽使用平均處理時間相對較慢、單次處理時間恒定的map,考慮整體穩定性應該要高於整體效率,因為前提在操作次數較少。如果在一次流程中,使用hash_map的少數操作產生一個最壞情況O(N),那麽hash_map的優勢也因此喪盡了。
2.6 為何map和set不能像vector一樣有個reserve函數來預分配數據?
map和set內部存儲的已經不是元素本身了,而是包含元素的節點。也就是說map內部使用的Alloc並不是map<key, data,="" compare,="" alloc="">聲明的時候從參數中傳入的Alloc。例如:
map<int, int,="" less<int="">, Alloc > intmap;
這時候在intmap中使用的allocator並不是Alloc, 而是通過了轉換的Alloc,具體轉換的方法時在內部通過
Alloc::rebind重新定義了新的節點分配器,詳細的實現參看徹底學習STL中的Allocator。
其實你就記住一點,在map和set裏面的分配器已經發生了變化,reserve方法你就不要奢望了。
2.7 當數據元素增多時(10000和20000個比較),map和set的插入和搜索速度變化如何?
算一下就知道了,首先你得知道map和set的底層都是紅黑樹,紅黑樹的搜索近似於二分查找,二分查找呢,平均時間復雜度是log2n,這裏簡寫成logn,
狂按計算器:log(10000) = 13.3
log(20000) = 14.3
看到了不,理論上平均就多了一次,所以你懂的,插起來。。。
2.8 auto_ptr可以做vector的元素呢?為什麽?
不能。因為STL的標準容器規定它所容納的元素必須是可以拷貝構造和可被轉移賦值的。而auto_ptr不能,可以用shared_ptr智能指針代替。
三、關於叠代器的一些問題
3.1 traits技術原理及應用
這個問題還真不知咋講,簡單來說,在STL算法中用到叠代器時(肯定會用到),會用到叠代器所指之物的型別,假設算法中有必要聲明一個變量,以叠代器所指之物為型別,但是C++只支持sizeof()、並未
支持typeof()。即使typeid(),也只能獲得型別名稱,不能拿來聲明變量,所以這裏就要用到作為”特性萃取機“的traits技術,當然,要讓traits有效運作,每個叠代器設計的時候的遵守約定,自行以內嵌型別定義的方式定義出相應型別。
詳情請看書或者移步Traits技術:類型的if-else-then(STL核心技術之一)
四、關於算法的一些問題
4.1 快排算法的樞軸位置是怎麽選擇的?
三點中值法,取整個序列的頭、尾、中央三個位置的元素,以其中值作為樞軸。
4.2 簡單說一下next_permutation和partition的實現?
-
next_permutation(下一個排列)
首先,從最尾端開始往前尋找兩個相鄰元素,另第一個元素為i,第二個元素為ii,且滿足i<ii。找到這樣一組相鄰元素後,再從尾端開始往前檢驗,找出第一個大於i的元素j,將i,j元素對調,再將ii之後的所有元素顛倒排列。此即所求“下一個”排列組合。 -
partition
令頭端叠代器first向尾部移動,尾部叠代器last向頭部移動。當first所指的值大於或等於樞軸時就停下來,當last所指的值小於或等於樞軸時也停下來,然後檢驗兩個叠代器是否交錯。如果first仍然在last左邊,就將連著元素互換,然後各自調整一個位置(向中央逼近),再繼續進行相同的行為。如果發現兩個叠代器叫錯了,表示整個序列已經調整完畢。
五、關於內存配置的一些問題
5.1 stl對於小內存塊請求與釋放的處理
STL考慮到小型內存區塊的碎片問題,設計了雙層級配置器,第一級配置直接使用malloc()和free();第二級配置器則視情況采用不同的策略,當配置區大於128bytes時,直接調用第一級配置器;當配置區塊小於128bytes時,便不借助第一級配置器,而使用一個memory pool來實現。究竟是使用第一級配置器還是第二級配置器,由一個宏定義來控制。SGI STL中默認使用第二級配置器。
二級配置器會將任何小額區塊的內存需求量上調至8的倍數,(例如需求是30bytes,則自動調整為32bytes),並且在它內部會維護16個free-list, 各自管理大小分別為8, 16, 24,…,128bytes的小額區塊,這樣當有小額內存配置需求時,直接從對應的free list中拔出對應大小的內存(8的倍數);當客戶端歸還內存時,將根據歸還內存塊的大小,將需要歸還的內存插入到對應free list的最頂端。
小結:
STL中的內存分配器實際上是基於空閑列表(free list)的分配策略,最主要的特點是通過組織16個空閑列表,對小對象的分配做了優化。
1)小對象的快速分配和釋放。當一次性預先分配好一塊固定大小的內存池後,對小於128字節的小塊內存分配和釋放的操作只是一些基本的指針操作,相比於直接調用malloc/free,開銷小。
2)避免內存碎片的產生。零亂的內存碎片不僅會浪費內存空間,而且會給OS的內存管理造成壓力。
3)盡可能最大化內存的利用率。當內存池尚有的空閑區域不足以分配所需的大小時,分配算法會將其鏈入到對應的空閑列表中,然後會嘗試從空閑列表中尋找是否有合適大小的區域,
但是,這種內存分配器局限於STL容器中使用,並不適合一個通用的內存分配。因為它要求在釋放一個內存塊時,必須提供這個內存塊的大小,以便確定回收到哪個free list中,而STL容器是知道它所需分配的對象大小的,比如上述:
stl::vector array;
array是知道它需要分配的對象大小為sizeof(int)。一個通用的內存分配器是不需要知道待釋放內存的大小的,類似於free(p)。
本文來自https://www.cnblogs.com/raichen/p/5817158.html
《STL源碼剖析》相關面試題總結