STL筆試面試題總結node
一.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包了一層皮。dom
六、空間配置器
負責空間配置與管理。從實現角度看,配置器是一個實現了動態空間配置、空間管理、空間釋放額class template。函數
二.STL經常使用的容器有哪些以及各自的特色是什麼?學習
1.vector:底層數據結構爲數組 ,支持快速隨機訪問。
2.list:底層數據結構爲雙向鏈表,支持快速增刪。
3.deque:底層數據結構爲一箇中央控制器和多個緩衝區,詳細見STL源碼剖析P146,支持首尾(中間不能)快速增刪,也支持隨機訪問。
4.stack:底層通常用23實現,封閉頭部便可,不用vector的緣由應該是容量大小有限制,擴容耗時
5.queue:底層通常用23實現,封閉頭部便可,不用vector的緣由應該是容量大小有限制,擴容耗時(stack和queue實際上是適配器,而不叫容器,由於是對容器的再封裝)
6.priority_queue:的底層數據結構通常爲vector爲底層容器,堆heap爲處理規則來管理底層容器實現
7.set:底層數據結構爲紅黑樹,有序,不重複。
8.multiset:底層數據結構爲紅黑樹,有序,可重複。
9.map:底層數據結構爲紅黑樹,有序,不重複。(一篇好的文章:https://blog.csdn.net/sevenjoin/article/details/81943864)
10.multimap:底層數據結構爲紅黑樹,有序,可重複。
11.hash_set:底層數據結構爲hash表,無序,不重複。
12.hash_multiset:底層數據結構爲hash表,無序,可重複 。
13.hash_map :底層數據結構爲hash表,無序,不重複。
14.hash_multimap:底層數據結構爲hash表,無序,可重複。
3、說說std::vector的底層(存儲)機制。
vector就是一個動態數組,裏面有一個指針指向一片連續的內存空間,當空間不夠裝下數據時,會自動申請另外一片更大的空間(通常是增長當前容量的50%或100%),而後把原來的數據拷貝過去,接着釋放原來的那片空間;當釋放或者刪除裏面的數據時,其存儲空間不釋放,僅僅是清空了裏面的數據。
4、vector插入刪除和list有什麼區別?
vector插入和刪除數據,須要對現有數據進行復制移動,若是vector存儲的對象很大或者構造函數很複雜,則開銷較大,若是是簡單的小數據,效率優於list。
list插入和刪除數據,須要對現有數據進行遍歷,但在首部插入數據,效率很高。
5、什麼狀況下用vector,什麼狀況下用list。
vector能夠隨機存儲元素(便可以經過公式直接計算出元素地址,而不須要挨個查找),但在非尾部插入刪除數據時,效率很低,適合對象簡單,對象數量變化不大,隨機訪問頻繁。
list不支持隨機存儲,適用於對象大,對象數量變化頻繁,插入和刪除頻繁。
6、vector中begin和end函數返回的是什麼?
begin返回的是第一個元素的迭代器,end返回的是最後一個元素後面位置的迭代器。
7、爲何vector的插入操做可能會致使迭代器失效?
vector動態增長大小時,並非在原空間後增長新的空間,而是以原大小的兩倍在另外配置一片較大的新空間,而後將內容拷貝過來,並釋放原來的空間。因爲操做改變了空間,因此迭代器失效。
8、說說std::list的底層(存儲)機制。
以結點爲單位存放數據,結點的地址在內存中不必定連續,每次插入或刪除一個元素,就配置或釋放一個元素空間
9、list自帶排序函數的排序原理。
將前兩個元素合併,再將後兩個元素合併,而後合併這兩個子序列成4個元素的子序列,重複這一過程,獲得8個,16個,...,子序列,最後獲得的就是排序後的序列。
時間複雜度:O(nlgn)
void List::sort()
{
List carry;
List counter[64]; //數組元素爲鏈表
int fill = 0;
while (head->next != tail)
{
//head是哨兵,不存放有效值
//head->next元素被移走,因此while循環不須要head=head->next;
carry.transfer(carry.getHead()->next, head->next, head->next->next);
int i = 0;
while (i < fill && counter[i].getHead()->next != counter[i].getHead())
//counter[i]不是空
{
counter[i].merge(carry);
carry.swap(counter[i++]);
}
carry.swap(counter[i]);
if (i == fill) ++fill;
}
for (int i = 1; i < fill; i++)
counter[i].merge(counter[i - 1]);
//經過這個實現排序(將有序的鏈表合成一個新的有序鏈表)
swap(counter[fill - 1]);
}
10、deque與vector的區別。
1)vector是單向開口的連續線性空間,deque是雙向開口的連續線性空間。(雙向開口是指能夠在頭尾兩端分別作元素的插入和刪除操做)。
2)deque沒有提供空間保留功能,而vector則要提供空間保留功能。
3)deque也提供隨機訪問迭代器,可是其迭代器比vector迭代器複雜不少。
11、不容許有遍歷行爲的容器有哪些(不提供迭代器)?
1)queue,除了頭部外,沒有其餘方法存取deque的其餘元素。
2)stack(底層以deque實現),除了最頂端外,沒有任何其餘方法能夠存取stack的其餘元素。
3)heap,全部元素都必須遵循特別的排序規則,不提供遍歷功能。
12、STL容器的參數allocate是用來作什麼的?
通常用在容器中,做爲容器的一個成員,但通常是用模版參數傳入,這樣纔可讓咱們換成咱們自定義的allocator;分配器用於封裝STL容器在內存管理上的低層細節
十3、你怎樣理解迭代器?
Iterator(迭代器)用於提供一種方法順序訪問一個聚合對象中各個元素, 而又不需暴露該對象的內部表示,至關於智能指針。包括Input Iterator, Output Iterator, Forward Iterator, Bidirectional Iterator, Random Access Iterator.
十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以後的迭代器都無效
十5、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)。
十6、vector和list的區別
vector和數組相似,擁有連續的內存空間,支持隨機的存取,在中間進行元素的插入和刪除的操做時間複雜度是O(n)
list是由雙向鏈表實現的,只能經過數組指針來進行數據訪問,遍歷中間的元素,時間的複雜度是O(n).
十7、vector中erase方法與algorithn中的remove方法區別
vector中erase方法真正刪除了元素,迭代器不能訪問了
remove只是簡單地將元素移到了容器的最後面,迭代器仍是能夠訪問到。由於algorithm經過迭代器進行操做,不知道容器的內部結構,因此沒法進行真正的刪除。
十8、STL中的容器都有哪些,優缺點?
介紹一下STL,詳細說明STL如何實現vector。
STL(標準模板庫)可分爲容器(containers)、迭代器(iterators)、空間配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函數(functors)六個部分。
STL (標準模版庫,Standard Template Library.它由容器、算法、迭代器組成。
STL有如下的一些優勢:
能夠方便容易地實現搜索數據或對數據排序等一系列的算法;
調試程序時更加安全和方便;
即便是人們用STL在UNIX平臺下寫的代碼你也能夠很容易地理解(由於STL是跨平臺的)。
vector實質上就是一個動態數組,會根據數據的增長,動態的增長數組空間。
deque從邏輯上來看是連續的內存,本質上是由一段段固定大小 的連續空間組成。deque採用相似索引的結構管理內存。vector有capacity和reserve函數,deque和list同樣,沒有capacity和reserve函數。
總結以下:
1. 在deque中間 插入或者刪除將使全部deque元素的迭代器、引用、指針失效
2. 在deque首部或者尾部插入元素會使迭代器失效,但不會引發引用和指針失效
3. 在其首部或尾部刪除元素則只會使指向被刪除元素的迭代器失效
vector模板的數據在內存中連續的排列,因此隨機存取元素(即經過[]運算符存取)的速度最快,這一點和數組是一致的。一樣因爲它的連續排列,因此它在除尾部之外的位置刪除或添加元素的速度很慢,在使用vector時,要避免這種操做。
list模板的數據是鏈式存儲,因此不能隨機存取元素。它的優點在於任意位置添加 刪除元素的速度。
deque模板是經過連接若干片連續的數據實現的,因此均衡了以上兩個容器的特色
十9、說說std::deque的底層機制。
deque動態地以分段連續空間組合而成,隨時能夠增長一段新的連續空間並連接起來。不提供空間保留功能。
注意:除非必要,咱們儘量選擇使用vector而非deque,由於deque的迭代器比vector迭代器複雜不少。對deque排序,爲了提升效率,可先將deque複製到一個vector上排序,而後再複製回deque。
deque採用一塊map(不是STL的map容器)做爲主控,其爲一小塊連續空間,其中每一個元素都是指針,指向另外一段較大的連續空間(緩衝區)。
deque的迭代器包含4個內容:
1)cur:迭代器當前所指元素
2)first:此迭代器所指的緩衝區的頭。
3)last:緩衝區尾。
4)node:指向管控中心。
二10、說說std::map底層機制。
map以RB-TREE爲底層機制。RB-TREE是一種平衡二叉搜索樹,自動排序效果不錯。
經過map的迭代器不能修改其鍵值,只能修改其實值。因此map的迭代器既不是const也不是mutable。
二11、紅黑樹有什麼性質?
1)每一個結點是紅色或者黑色。
2)根結點爲黑色。
3)葉結點爲黑色的NULL結點。
4)若是結點爲紅,其子節點必須爲黑。
5)任一結點到NULL的任何路徑,所含黑結點數必須相同。
二12、vector、list、map、deque用erase(it)後,迭代器的變化。
vector和deque是序列式容器,其內存分別是連續空間和分段連續空間,刪除迭代器it後,其後面的迭代器都失效了,此時it及其後面的迭代器會自動加1,使it指向被刪除元素的下一個元素。
list刪除迭代器it時,其後面的迭代器都不會失效,將前面和後面鏈接起來便可。
map也是隻能使當前刪除的迭代器失效,其後面的迭代器依然有效。
二十3、hash_map與map的區別?何時用hash_map,何時用map?
構造函數:hash_map須要hash function和等於函數,而map須要比較函數(大於或小於)。
存儲結構:hash_map以hashtable爲底層,而map以RB-TREE爲底層。
總的說來,hash_map查找速度比map快,並且查找速度基本和數據量大小無關,屬於常數級別。而map的查找速度是logn級別。但不必定常數就比log小,並且hash_map還有hash function耗時。
若是考慮效率,特別當元素達到必定數量級時,用hash_map。
考慮內存,或者元素數量較少時,用map。
二十4、hashtable,hash_set,hash_map的區別。
hash_set以hashtable爲底層,不具備排序功能,能快速查找。其鍵值就是實值。(set以RB-TREE爲底層,具備排序功能。)
hash_map以以hashtable爲底層,沒有自動排序功能,能快速查找,每個元素同時擁有一個實值和鍵值。(map以RB-TREE爲底層,具備排序功能。)
二十5、map和set的3個問題。
1)爲什麼map和set的插入刪除效率比其餘序列容器高。
由於不須要內存拷貝和內存移動
2)爲什麼map和set每次Insert以後,之前保存的iterator不會失效?
由於插入操做只是結點指針換來換去,結點內存沒有改變。而iterator就像指向結點的指針,內存沒變,指向內存的指針也不會變。
2)當數據元素增多時(從10000到20000),map的set的查找速度會怎樣變化?
RB-TREE用二分查找法,時間複雜度爲logn,因此從10000增到20000時,查找次數從log10000=14次到log20000=15次,多了1次而已。
二十6、map是怎麼實現的?查找的複雜度是多少?能不能邊遍歷邊插入?
紅黑樹
O(logn)
不能夠,map不像vector,它在對容器執行erase操做後不會返回後一個元素的迭代器,因此不能遍歷地日後刪除。
二十7、hash_map和map的區別在哪裏?
hash_map底層是散列的因此理論上操做的平均複雜度是常數時間,map底層是紅黑樹,理論上平均複雜度是O(logn),這裏總結一下,選用map仍是hash_map,關鍵是看關鍵字查詢操做次數,以及你所須要保證的是查詢整體時間仍是單個查詢的時間。若是是要不少次操做,要求其總體效率,那麼使用hash_map,平均處理時間短。若是是少數次的操做,使用 hash_map可能形成不肯定的O(N),那麼使用平均處理時間相對較慢、單次處理時間恆定的map,考慮總體穩定性應該要高於總體效率,由於前提在操做次數較少。若是在一次流程中,使用hash_map的少數操做產生一個最壞狀況O(N),那麼hash_map的優點也所以喪盡了。
二十8、爲什麼map和set不能像vector同樣有個reserve函數來預分配數據?
map和set內部存儲的已經不是元素自己了,而是包含元素的節點。也就是說map內部使用的Alloc並非map聲明的時候從參數中傳入的Alloc。例如:map, Alloc > intmap;這時候在intmap中使用的allocator並非Alloc, 而是經過了轉換的Alloc,具體轉換的方法時在內部經過Alloc::rebind從新定義了新的節點分配器,詳細的實現參看完全學習STL中的Allocator。其實你就記住一點,在map和set裏面的分配器已經發生了變化,reserve方法你就不要奢望了。————————————————版權聲明:本文爲CSDN博主「zzb2019」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。原文連接:https://blog.csdn.net/zzb2019/article/details/81195294