《STL源码剖析》相关面试题总结
一、STL簡(jiǎn)介
STL提供六大組件,彼此可以組合套用:
容器就是各種數(shù)據(jù)結(jié)構(gòu),我就不多說(shuō),看看下面這張圖回憶一下就好了,從實(shí)現(xiàn)角度看,STL容器是一種class template。
各種常見算法,如sort,search,copy,erase等,我覺得其中比較值得學(xué)習(xí)的就是sort,next_permutation,partition,merge sort,從實(shí)現(xiàn)角度看,STL算法是一種function template。
扮演容器與算法之間的膠合劑,是所謂的“泛型指針”。共有五種類型,從實(shí)現(xiàn)角度看,迭代器是一種將operator*,operator->,operator++,operator--等指針相關(guān)操作予以重載的class template。所有STL容器都附帶有自己專屬的迭代器,只有容器設(shè)計(jì)者才知道如何設(shè)計(jì)迭代器。原生指針也是一種迭代器。是設(shè)計(jì)模式的一種,所以被問(wèn)到了解的設(shè)計(jì)模式可以用來(lái)湊數(shù)。
行為類函數(shù),可作為算法的某種策略,從實(shí)現(xiàn)角度看,仿函數(shù)是一種重載了operator()的class或class template。一般函數(shù)指針可視為狹義的仿函數(shù)。
一種用來(lái)修飾容器或者仿函數(shù)或迭代器接口的東西。比如queue和stack,看著像容器,其實(shí)就是deque包了一層皮。
負(fù)責(zé)空間配置與管理。從實(shí)現(xiàn)角度看,配置器是一個(gè)實(shí)現(xiàn)了動(dòng)態(tài)空間配置、空間管理、空間釋放額class template。
二、關(guān)于容器的一些問(wèn)題
2.1 當(dāng)vector的內(nèi)存用完了,它是如何動(dòng)態(tài)擴(kuò)展內(nèi)存的?它是怎么釋放內(nèi)存的?用clear可以釋放掉內(nèi)存嗎?是不是線程安全的?
2.2 map是怎么實(shí)現(xiàn)的?查找的復(fù)雜度是多少?能不能邊遍歷邊插入?
紅黑樹和散列
O(logn)
不可以,map不像vector,它在對(duì)容器執(zhí)行erase操作后不會(huì)返回后一個(gè)元素的迭代器,所以不能遍歷地往后刪除。
2.3 寫多讀少應(yīng)該用什么容器?
私以為是鏈表,鏈表的插入操作時(shí)常數(shù)時(shí)間復(fù)雜度,訪問(wèn)操作是O(n),是最適合寫多讀少的容器。
2.4 vector每次insert或erase之后,以前保存的iterator會(huì)不會(huì)失效?
理論上會(huì)失效,理論上每次insert或者erase之后,所有的迭代器就重新計(jì)算的,所以都可以看作會(huì)失效,原則上是不能使用過(guò)期的內(nèi)存
但是vector一般底層是用數(shù)組實(shí)現(xiàn)的,我們仔細(xì)考慮數(shù)組的特性,不難得出另一個(gè)結(jié)論,
insert時(shí),假設(shè)insert位置在p,分兩種情況:
a) 容器還有空余空間,不重新分配內(nèi)存,那么p之前的迭代器都有效,p之后的迭代器都失效
b) 容器重新分配了內(nèi)存,那么p之后的迭代器都無(wú)效咯
erase時(shí),假設(shè)erase位置在p,則p之前的迭代器都有效并且p指向下一個(gè)元素位置(如果之前p在尾巴上,則p指向無(wú)效尾end),p之后的迭代器都無(wú)效
2.5 hash_map和map的區(qū)別在哪里?
hash_map底層是散列的所以理論上操作的平均復(fù)雜度是常數(shù)時(shí)間,map底層是紅黑樹,理論上平均復(fù)雜度是O(logn),下面是借鑒的網(wǎng)上的總結(jié):
這里總結(jié)一下,選用map還是hash_map,關(guān)鍵是看關(guān)鍵字查詢操作次數(shù),以及你所需要保證的是查詢總體時(shí)間還是單個(gè)查詢的時(shí)間。如果是要很多次操作,要求其整體效率,那么使用hash_map,平均處理時(shí)間短。如果是少數(shù)次的操作,使用 hash_map可能造成不確定的O(N),那么使用平均處理時(shí)間相對(duì)較慢、單次處理時(shí)間恒定的map,考慮整體穩(wěn)定性應(yīng)該要高于整體效率,因?yàn)榍疤嵩诓僮鞔螖?shù)較少。如果在一次流程中,使用hash_map的少數(shù)操作產(chǎn)生一個(gè)最壞情況O(N),那么hash_map的優(yōu)勢(shì)也因此喪盡了。
2.6 為何map和set不能像vector一樣有個(gè)reserve函數(shù)來(lái)預(yù)分配數(shù)據(jù)?
map和set內(nèi)部存儲(chǔ)的已經(jīng)不是元素本身了,而是包含元素的節(jié)點(diǎn)。也就是說(shuō)map內(nèi)部使用的Alloc并不是map聲明的時(shí)候從參數(shù)中傳入的Alloc。例如:
map<int, int,="" less, Alloc?> intmap;
這時(shí)候在intmap中使用的allocator并不是Alloc, 而是通過(guò)了轉(zhuǎn)換的Alloc,具體轉(zhuǎn)換的方法時(shí)在內(nèi)部通過(guò)
Alloc::rebind重新定義了新的節(jié)點(diǎn)分配器,詳細(xì)的實(shí)現(xiàn)參看徹底學(xué)習(xí)STL中的Allocator。
其實(shí)你就記住一點(diǎn),在map和set里面的分配器已經(jīng)發(fā)生了變化,reserve方法你就不要奢望了。
2.7 當(dāng)數(shù)據(jù)元素增多時(shí)(10000和20000個(gè)比較),map和set的插入和搜索速度變化如何?
算一下就知道了,首先你得知道m(xù)ap和set的底層都是紅黑樹,紅黑樹的搜索近似于二分查找,二分查找呢,平均時(shí)間復(fù)雜度是log2n,這里簡(jiǎn)寫成logn,
狂按計(jì)算器:
log(10000) = 13.3
log(20000) = 14.3
看到了不,理論上平均就多了一次,所以你懂的,插起來(lái)。。。
2.8 auto_ptr可以做vector的元素呢?為什么?
不能。因?yàn)镾TL的標(biāo)準(zhǔn)容器規(guī)定它所容納的元素必須是可以拷貝構(gòu)造和可被轉(zhuǎn)移賦值的。而auto_ptr不能,可以用shared_ptr智能指針代替。
三、關(guān)于迭代器的一些問(wèn)題
3.1 traits技術(shù)原理及應(yīng)用
這個(gè)問(wèn)題還真不知咋講,簡(jiǎn)單來(lái)說(shuō),在STL算法中用到迭代器時(shí)(肯定會(huì)用到),會(huì)用到迭代器所指之物的型別,假設(shè)算法中有必要聲明一個(gè)變量,以迭代器所指之物為型別,但是C++只支持sizeof()、并未
支持typeof()。即使typeid(),也只能獲得型別名稱,不能拿來(lái)聲明變量,所以這里就要用到作為”特性萃取機(jī)“的traits技術(shù),當(dāng)然,要讓traits有效運(yùn)作,每個(gè)迭代器設(shè)計(jì)的時(shí)候的遵守約定,自行以內(nèi)嵌型別定義的方式定義出相應(yīng)型別。
詳情請(qǐng)看書或者移步Traits技術(shù):類型的if-else-then(STL核心技術(shù)之一)
四、關(guān)于算法的一些問(wèn)題
4.1 快排算法的樞軸位置是怎么選擇的?
三點(diǎn)中值法,取整個(gè)序列的頭、尾、中央三個(gè)位置的元素,以其中值作為樞軸。
4.2 簡(jiǎn)單說(shuō)一下next_permutation和partition的實(shí)現(xiàn)?
next_permutation(下一個(gè)排列)
首先,從最尾端開始往前尋找兩個(gè)相鄰元素,另第一個(gè)元素為i,第二個(gè)元素為ii,且滿足i<ii。找到這樣一組相鄰元素后,再?gòu)奈捕碎_始往前檢驗(yàn),找出第一個(gè)大于i的元素j,將i,j元素對(duì)調(diào),再將ii之后的所有元素顛倒排列。此即所求“下一個(gè)”排列組合。
partition
令頭端迭代器first向尾部移動(dòng),尾部迭代器last向頭部移動(dòng)。當(dāng)first所指的值大于或等于樞軸時(shí)就停下來(lái),當(dāng)last所指的值小于或等于樞軸時(shí)也停下來(lái),然后檢驗(yàn)兩個(gè)迭代器是否交錯(cuò)。如果first仍然在last左邊,就將連著元素互換,然后各自調(diào)整一個(gè)位置(向中央逼近),再繼續(xù)進(jìn)行相同的行為。如果發(fā)現(xiàn)兩個(gè)迭代器叫錯(cuò)了,表示整個(gè)序列已經(jīng)調(diào)整完畢。
五、關(guān)于內(nèi)存配置的一些問(wèn)題
5.1 stl對(duì)于小內(nèi)存塊請(qǐng)求與釋放的處理
STL考慮到小型內(nèi)存區(qū)塊的碎片問(wèn)題,設(shè)計(jì)了雙層級(jí)配置器,第一級(jí)配置直接使用malloc()和free();第二級(jí)配置器則視情況采用不同的策略,當(dāng)配置區(qū)大于128bytes時(shí),直接調(diào)用第一級(jí)配置器;當(dāng)配置區(qū)塊小于128bytes時(shí),便不借助第一級(jí)配置器,而使用一個(gè)memory pool來(lái)實(shí)現(xiàn)。究竟是使用第一級(jí)配置器還是第二級(jí)配置器,由一個(gè)宏定義來(lái)控制。SGI STL中默認(rèn)使用第二級(jí)配置器。
二級(jí)配置器會(huì)將任何小額區(qū)塊的內(nèi)存需求量上調(diào)至8的倍數(shù),(例如需求是30bytes,則自動(dòng)調(diào)整為32bytes),并且在它內(nèi)部會(huì)維護(hù)16個(gè)free-list, 各自管理大小分別為8, 16, 24,…,128bytes的小額區(qū)塊,這樣當(dāng)有小額內(nèi)存配置需求時(shí),直接從對(duì)應(yīng)的free list中拔出對(duì)應(yīng)大小的內(nèi)存(8的倍數(shù));當(dāng)客戶端歸還內(nèi)存時(shí),將根據(jù)歸還內(nèi)存塊的大小,將需要?dú)w還的內(nèi)存插入到對(duì)應(yīng)free list的最頂端。
小結(jié):
STL中的內(nèi)存分配器實(shí)際上是基于空閑列表(free list)的分配策略,最主要的特點(diǎn)是通過(guò)組織16個(gè)空閑列表,對(duì)小對(duì)象的分配做了優(yōu)化。
1)小對(duì)象的快速分配和釋放。當(dāng)一次性預(yù)先分配好一塊固定大小的內(nèi)存池后,對(duì)小于128字節(jié)的小塊內(nèi)存分配和釋放的操作只是一些基本的指針操作,相比于直接調(diào)用malloc/free,開銷小。
2)避免內(nèi)存碎片的產(chǎn)生。零亂的內(nèi)存碎片不僅會(huì)浪費(fèi)內(nèi)存空間,而且會(huì)給OS的內(nèi)存管理造成壓力。
3)盡可能最大化內(nèi)存的利用率。當(dāng)內(nèi)存池尚有的空閑區(qū)域不足以分配所需的大小時(shí),分配算法會(huì)將其鏈入到對(duì)應(yīng)的空閑列表中,然后會(huì)嘗試從空閑列表中尋找是否有合適大小的區(qū)域,
但是,這種內(nèi)存分配器局限于STL容器中使用,并不適合一個(gè)通用的內(nèi)存分配。因?yàn)樗笤卺尫乓粋€(gè)內(nèi)存塊時(shí),必須提供這個(gè)內(nèi)存塊的大小,以便確定回收到哪個(gè)free list中,而STL容器是知道它所需分配的對(duì)象大小的,比如上述:
stl::vector?array;
array是知道它需要分配的對(duì)象大小為sizeof(int)。一個(gè)通用的內(nèi)存分配器是不需要知道待釋放內(nèi)存的大小的,類似于free(p)。
詳情請(qǐng)看書或移步STL源碼剖析---空間配置器
六、關(guān)于線程安全的一些問(wèn)題
總結(jié)
以上是生活随笔為你收集整理的《STL源码剖析》相关面试题总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: cpu与计算机其他的通信,PC与CPU2
- 下一篇: gooflow学习笔记