RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

c語言入門知識之STL篇

jf_78858299 ? 來源:阿Q正磚 ? 作者:阿Q正磚 ? 2023-03-10 09:31 ? 次閱讀

這周終于可以給大家把STL方面的面試題總結(jié)出來了,突然發(fā)現(xiàn)它里面的細(xì)節(jié)非常多,只有你想不到的,沒有它沒有的。對于C++程序員來說,STL庫里面的知識也是非常重要的,只要想在技術(shù)這條路線上有長遠(yuǎn)的發(fā)展,那么就一定要掌握它。不管是學(xué)習(xí)中,面試中,工作中,它都有不可估量的地位。

之前有寫過一片關(guān)于STL庫的文章,現(xiàn)在來看確實(shí)也是非常有用的知識點(diǎn)。

這篇文章主要是講解在面試過程中遇到的一些面試題,細(xì)節(jié)很多,量也很大,大家在看的時(shí)候,不僅僅只是看一遍而已,還要多了解背后的原理,在不懂的時(shí)候,還是需要看看相關(guān)權(quán)威書籍。

STL篇

1、講講STL的六大組件

  1. 容器(Containers):各種數(shù)據(jù)結(jié)構(gòu),如Vector,List,Deque,Set,Map,用來存放數(shù)據(jù),STL容器是一種Class Template,就體積而言,這一部分很像冰山載海面的比率。
  2. 算法(Algorithms):各種常用算法如Sort,Search,Copy,Erase,從實(shí)現(xiàn)的角度來看,STL算法是一種Function Templates。
  3. 迭代器(Iterators):扮演容器與算法之間的膠合劑,是所謂的“泛型指針”,共有五種類型,以及其它衍生變化,從實(shí)現(xiàn)的角度來看,迭代器是一種將:Operators*,Operator->,Operator++,Operator--等相關(guān)操作予以重載的Class Template。所有STL容器都附帶有自己專屬的迭代器——是的,只有容器設(shè)計(jì)者才知道如何遍歷自己的元素,原生指針(Native pointer)也是一種迭代器。
  4. 仿函數(shù)(Functors):行為類似函數(shù),可作為算法的某種策略(Policy),從實(shí)現(xiàn)的角度來看,仿函數(shù)是一種重載了Operator()的Class 或 Class Template。一般函數(shù)指針可視為狹義的仿函數(shù)。
  5. 適配器(配接器)(Adapters):一種用來修飾容器(Containers)或仿函數(shù)(Functors)或迭代器(Iterators)接口的東西,例如:STL提供的Queue和Stack,雖然看似容器,其實(shí)只能算是一種容器配接器,因?yàn)?它們的底部完全借助Deque,所有操作有底層的Deque供應(yīng)。改變Functor接口者,稱為Function Adapter;改變Container接口者,稱為Container Adapter;改變Iterator接口者,稱為Iterator Adapter。配接器的實(shí)現(xiàn)技術(shù)很難一言蔽之,必須逐一分析。
  6. 分配器(Allocators):負(fù)責(zé)空間配置與管理,從實(shí)現(xiàn)的角度來看,配置器是一個(gè)實(shí)現(xiàn)了動態(tài)空間配置、空間管理、空間釋放的Class Template。

2、簡單說說vector

動態(tài)開辟的二維數(shù)組。元素在內(nèi)存連續(xù)存放。隨機(jī)存取任何元素都能在常數(shù)時(shí)間完成。在尾端增刪元素具有較佳的性能。

3、vector底層原理

  • 數(shù)據(jù)安排及操作方式與array非常相似。兩者的唯一差別在于空間運(yùn)用的靈活性。
  • 靜態(tài)空間,一旦配置好了就不能改變了,如果程序需要一個(gè)更大的array,只能自己再申請一個(gè)更大的array,然后將以前的array中的內(nèi)容全部拷貝到新的array中。
  • 動態(tài)開辟的空間,隨著元素的加入,它的內(nèi)部機(jī)制會自動擴(kuò)充空間以容納新的元素。vector的關(guān)鍵技術(shù)在于對大小的控制以及重新分配時(shí)的數(shù)據(jù)移動效率。
  • 采用的數(shù)據(jù)結(jié)構(gòu)是線性的連續(xù)空間(泛型的動態(tài)類型順序表),他以兩個(gè)迭代器start和finish分別指向配置得來的連續(xù)空間中目前已將被使用的空間。迭代器end_of_storage指向整個(gè)連續(xù)的尾部。
  • 在增加元素時(shí),如果超過自身最大的容量,vector則將自身的容量擴(kuò)充為原來的兩倍。擴(kuò)充空間需要經(jīng)過的步驟:重新配置空間,元素移動,釋放舊的內(nèi)存空間。一旦vector空間重新配置,則指向原來vector的所有迭代器都失效了,因?yàn)関ector的地址改變了。

4、vector為什么要用加倍擴(kuò)容而不是每次增加一個(gè)固定的擴(kuò)容容量

  • vector在push_back以成倍增長可以在均攤后達(dá)到O(1)的事件復(fù)雜度,相對于增長指定大小的O(n)時(shí)間復(fù)雜度更好。
  • 為了防止申請內(nèi)存的浪費(fèi),現(xiàn)在使用較多的有2倍與1.5倍的增長方式,而1.5倍的增長方式可以更好的實(shí)現(xiàn)對內(nèi)存的重復(fù)利用。

5、vector擴(kuò)容的過程

  • 完全棄用現(xiàn)有的內(nèi)存空間,重新申請更大的內(nèi)存空間;
  • 將舊內(nèi)存空間中的數(shù)據(jù),按原有順序移動到新的內(nèi)存空間中;
  • 最后將舊的內(nèi)存空間釋放。

6、vector的擴(kuò)容方式為什么是1.5倍或2倍

  • 假如說我們是以2倍方式擴(kuò)容(1,2,4,8,16),則第i次擴(kuò)容期間所需要的空間總量就是2^i次方,如果第4次擴(kuò)容時(shí)總共需要8個(gè)元素大小的空間,但是前3次已經(jīng)釋放的空間加起來的總量,剛好是7,而7小于8,不足以我們第4次擴(kuò)容時(shí)所需要的空間,也就是說,如果恰巧以2倍方式擴(kuò)容,那么每次擴(kuò)容時(shí)前面釋放的空間它都不足以支持本次的擴(kuò)容?。?!那么如果是以更高倍數(shù)的方式進(jìn)行擴(kuò)容,則這個(gè)空間它的浪費(fèi)情況就會更高?。?!也就是說,以2倍或者更高倍數(shù)的方式進(jìn)行擴(kuò)容,會存在2個(gè)問題:
    • 空間浪費(fèi)可能比較大
    • 無法使用前面已經(jīng)釋放的空間
  • STL標(biāo)準(zhǔn)沒有嚴(yán)格說明我們應(yīng)該按照哪一種方式進(jìn)行擴(kuò)容,因此不同STL的實(shí)現(xiàn)廠商都是按照自己的方式進(jìn)行擴(kuò)容的。
  • 一般情況下,在Windows的VS系列編譯器下,是按照1.5倍的方式進(jìn)行擴(kuò)容,在Linux的g++中,是按照2倍的方式進(jìn)行擴(kuò)容的。

7、size、resize、reserve、capacity的區(qū)別

  • size表示當(dāng)前vector中有多少個(gè)元素(即finish – start);當(dāng)前容器所存儲的個(gè)數(shù),
  • resize可以改變有效空間的大小,也有改變默認(rèn)值的功能。capacity的大小也會隨著改變??梢杂卸鄠€(gè)參數(shù)。創(chuàng)建指定數(shù)量的元素并指定vector的存儲空間。既分配空間又創(chuàng)建對象。
  • reserve是直接擴(kuò)充到已經(jīng)確定的大小,可以減少多次開辟、釋放空間的問題(優(yōu)化push_back),從而達(dá)到提高效率的目的,其次還可以減少多次要拷貝數(shù)據(jù)的問題。reserve它只是保證vector中的空間大?。╟apacity)最少達(dá)到參數(shù)所指定的大小n。并且它只有一個(gè)參數(shù)。指定vector的元素總數(shù),不創(chuàng)建對象。
  • capacity函數(shù)表示它已經(jīng)分配的內(nèi)存中可以容納多少元素(即end_of_storage – start)。即容器在分配新的存儲空間能存儲的元素總數(shù)。返回vector中能存儲元素的最大數(shù)。

8、vector迭代器失效問題

迭代器的主要作用就是讓算法能夠不用關(guān)心底層數(shù)據(jù)結(jié)構(gòu),其底層實(shí)際就是一個(gè)指針,或者是對指針進(jìn)行了 封裝,比如:vector的迭代器就是原生態(tài)指針T*。因此迭代器失效,實(shí)際就是迭代器底層對應(yīng)指針?biāo)赶虻?空間被銷毀了,而使用一塊已經(jīng)被釋放的空間,造成的后果是程序崩潰(即如果繼續(xù)使用已經(jīng)失效的迭代器, 程序可能會崩潰)。

9、對于vector可能導(dǎo)致其迭代器失效的操作有哪些

  • resize、reserve、insert、assign、push_back等會引起其底層空間改變的操作,都有可能使迭代器失效。

  • 指定位置元素的刪除操作--erase

    #include 
    using namespace std;
    #include 
    int main()
    {
        int a[] = { 1, 2, 3, 4 };
        vector<int> v(a, a + sizeof(a) / sizeof(int));
        // 使用find查找3所在位置的iterator
        vector<int>::iterator pos = find(v.begin(), v.end(), 3);
        // 刪除pos位置的數(shù)據(jù),導(dǎo)致pos迭代器失效。
        v.erase(pos);
        cout << *pos << endl; // 此處會導(dǎo)致非法訪問
        return 0;
    }
    

erase刪除pos位置元素后,pos位置之后的元素會往前移動,沒有導(dǎo)致底層空間的改變,理論上講迭代器應(yīng)該不會失效,但是如果pos剛好是最后一個(gè)元素,刪完之后pos剛好是end位置,而end位置是沒有元素的,那么pos就失效了。所以刪除vector中任意位置上元素時(shí),vs就認(rèn)為該位置迭代器失效了。

10、push_back和emplace_back的區(qū)別

emplace_back() 和 push_back() 的主要區(qū)別,就在于底層實(shí)現(xiàn)的機(jī)制不同。push_back() 向容器尾部添加元素時(shí),首先會創(chuàng)建這個(gè)元素,然后再將這個(gè)元素拷貝或者移動到容器中(如果是拷貝的話,事后會自行銷毀先前創(chuàng)建的這個(gè)元素);而 emplace_back() 在實(shí)現(xiàn)時(shí),則是直接在容器尾部創(chuàng)建這個(gè)元素,省去了拷貝或移動元素的過程。

11、聊聊STL庫中的list

  • list 是順序容器的一種。list 是一個(gè)雙向鏈表。使用 list 需要包含頭文件 list。雙向鏈表的每個(gè)元素中都有一個(gè)指針指向后一個(gè)元素,也有一個(gè)指針指向前一個(gè)元素。
  • 在 list 容器中,在已經(jīng)定位到要增刪元素的位置的情況下,增刪元素能在常數(shù)時(shí)間內(nèi)完成。
  • list不支持根據(jù)下標(biāo)隨機(jī)存取元素。
  • 在任何位置都能高效地插入和刪除元素,只要改變元素的指針值,不需要拷貝元素。

12、list的底層原理

  • list的底層是一個(gè) 雙向鏈表 ,以結(jié)點(diǎn)為單位存放數(shù)據(jù),結(jié)點(diǎn)的地址在內(nèi)存中不一定連續(xù),每次插入或刪除一個(gè)元素,就配置或釋放一個(gè)元素空間。
  • 和vector容器迭代器的實(shí)現(xiàn)方式不同,由于 list 容器的元素并不是連續(xù)存儲的,所以該容器迭代器中,必須包含一個(gè)可以指向 list 容器的指針,并且該指針還可以借助重載的 *、++、--、==、!= 等運(yùn)算符,實(shí)現(xiàn)迭代器正確的遞增、遞減、取值等操作。

13、list的常用函數(shù)

list.push_back(elem)    在尾部加入一個(gè)數(shù)據(jù)
list.pop_back()         刪除尾部數(shù)據(jù)
list.push_front(elem)   在頭部插入一個(gè)數(shù)據(jù)
list.pop_front()        刪除頭部數(shù)據(jù)
list.size()             返回容器中實(shí)際數(shù)據(jù)的個(gè)數(shù)
list.sort()             排序,默認(rèn)由小到大 
list.unique()           移除數(shù)值相同的連續(xù)元素
list.back()             取尾部迭代器
list.erase(iterator)    刪除一個(gè)元素,參數(shù)是迭代器,返回的是刪除迭代器的下一個(gè)位置
14、vector插入、list插入問題
  • vector中插入大量連續(xù)的數(shù)據(jù)時(shí),如果預(yù)知數(shù)據(jù)量大小,可以通過resize來調(diào)整原先vector的空間大小,同時(shí)利用賦值“=”,可以減少耗時(shí)。
  • list可以通過遍歷list然后insert插入,但還可以通過成員方法splice來進(jìn)行插入,且效率較高,耗時(shí)較少。

15、vector和list的優(yōu)缺點(diǎn)

  1. vector的優(yōu)點(diǎn)
  • 下標(biāo)隨機(jī)訪問

vector的底層是一段連續(xù)的物理空間,所以支持隨機(jī)訪問。

  • 尾插尾刪效率高
    跟數(shù)組類似,我們能夠很輕易的找到最后一個(gè)元素,并完成各種操作。
  • cpu高速緩存命中率高
    因?yàn)橄到y(tǒng)在底層拿空間的時(shí)候,是拿一段進(jìn)cpu,不是只拿單獨(dú)一個(gè),會提前往后多拿一點(diǎn),vector的物理地址是連續(xù)的,所以我們再拿到數(shù)據(jù)的時(shí)候,cpu訪問后面的數(shù)據(jù)會更快。
  1. vector的缺點(diǎn)
  • 前面部分的插入刪除數(shù)據(jù)效率低
    如果我們要在前面或中間插入或者刪除數(shù)據(jù),我們不能直接刪,我們需要挪動數(shù)據(jù),去覆蓋或者增加一段空間,這樣我們挪動數(shù)據(jù)的效率就是O(N)。
  • 擴(kuò)容有消耗,可能存在一定的空間浪費(fèi)
    正常情況下我們vector的擴(kuò)容機(jī)制是一旦達(dá)到當(dāng)前空間的capacity(容量)那么我們擴(kuò)容原空間的1.5倍或者2倍數(shù)(vs一般是1.5倍而g++是2倍),這樣擴(kuò)容就有可能導(dǎo)致空間浪費(fèi),而且頻繁擴(kuò)容也會影響效率。
  1. list的優(yōu)點(diǎn)
  • 按需申請釋放,不需要擴(kuò)容
    list是一個(gè)帶頭雙向循環(huán)鏈表,那么鏈表就是一個(gè)個(gè)獨(dú)立的空間鏈接起的,需要多少,就new多少,不存在空間浪費(fèi)。
  • 任意位置的插入刪除效率高(對比vector)
    因?yàn)閘ist是雙向循環(huán)鏈表,我們需要插入新的元素只需要改變原數(shù)據(jù)的next和prev,所以我們的插入刪除效率是O(1)。
  1. list的缺點(diǎn)
  • 不支持下標(biāo)隨機(jī)訪問
    因?yàn)閘ist是鏈表,在存放數(shù)據(jù)的物理地址并不是連續(xù)的,所以我們也就不能支持隨機(jī)訪問。
  • CPU高速緩存命中率低
    主要還是跟它的物理地址不連續(xù)有關(guān),CPU提前存的一段數(shù)據(jù),可能跟下一個(gè)數(shù)據(jù)完全沒有聯(lián)系,因?yàn)樗鼈兛臻g不連續(xù),所以就命中率低。

16、vector和list中,如果刪除末尾的元素,其指針和迭代器如何變化?若刪除的是中間的元素呢?

  1. 迭代器和指針之間的區(qū)別
    1. 迭代器不是指針,是類模板,表現(xiàn)的像指針。 他只是模擬了指針的一些功能,重載了指針的一些操作符,-->、++、--等。迭代器封裝了指針,是一個(gè)”可遍歷STL( Standard Template Library)容器內(nèi)全部或部分元素”的對象,本質(zhì)是封裝了原生指針,是指針概念的一種提升,提供了比指針更高級的行為,相當(dāng)于一種智能指針,他可以根據(jù)不同類型的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)不同的++,--等操作。
    2. 迭代器返回的是對象引用而不是對象的值 ,所以cout只能輸出迭代器使用取值后的值而不能直接輸出其自身。
  2. vector和list特性
    1. vector特性 動態(tài)數(shù)組。元素在內(nèi)存連續(xù)存放。隨機(jī)存取任何元素都在常數(shù)時(shí)間完成。在尾端增刪元素具有較大的性能(大部分情況下是常數(shù)時(shí)間)。
    2. list特性 雙向鏈表。元素在內(nèi)存不連續(xù)存放。在任何位置增刪元素都能在常數(shù)時(shí)間完成。不支持隨機(jī)存取。
  3. vector和list增刪元素
    1. 對于vector而言,刪除某個(gè)元素以后,該元素后邊的每個(gè)元素的迭代器都會失效,后邊每個(gè)元素都往前移動一位,erase返回下一個(gè)有效的迭代器。
    2. 對于list而言,刪除某個(gè)元素,只有“指向被刪除元素”的那個(gè)迭代器失效,其它迭代器不受任何影響。

17、總結(jié)一下vector和list具體是怎么實(shí)現(xiàn)的呢?常見操作的時(shí)間復(fù)雜度是多少嘞

  1. vector 一維數(shù)組(元素在內(nèi)存連續(xù)存放)

    • 倍放開辟三倍的內(nèi)存
    • 舊的數(shù)據(jù)開辟到新的內(nèi)存
    • 釋放舊的內(nèi)存
    • 指向新內(nèi)存
    1. 是動態(tài)數(shù)組,在堆中分配內(nèi)存,元素連續(xù)存放,有保留內(nèi)存,如果減少大小后,內(nèi)存也不會釋放;如果新增大小當(dāng)前大小時(shí)才會重新分配內(nèi)存。
    2. 擴(kuò)容方式:
  2. list 雙向鏈表(元素存放在堆中)

    • 隨機(jī)訪問不方便
    • 刪除插入操作方便
    1. 元素存放在堆中,每個(gè)元素都是放在一塊內(nèi)存中,它的內(nèi)存空間可以是不連續(xù)的,通過指針來進(jìn)行數(shù)據(jù)的訪問,這個(gè)特點(diǎn),使得它的隨機(jī)存取變得非常沒有效率,因此它沒有提供[ ]操作符的重載。但是由于鏈表的特點(diǎn),它可以很有效的支持任意地方的刪除和插入操作。
    2. 特點(diǎn):
  3. 常見時(shí)間復(fù)雜度

    1. vector插入、查找、刪除時(shí)間復(fù)雜度分別為:O(n)、O(1)、O(n);
    2. list插入、查找、刪除時(shí)間復(fù)雜度分別為:O(1)、O(n)、O(1)。

18、簡單說說deque

  1. deque是一個(gè)雙向開口的容器,所謂雙向開口就是再頭尾兩端均可以做元素的插入和刪除操作。
  2. deque相比于vector最大的差異就在于支持常熟時(shí)間內(nèi)對首尾兩端進(jìn)行插入和刪除操作,而且deque沒有容量的概念,其內(nèi)部采用分段連續(xù)內(nèi)存空間來存儲元素,在插入元素的時(shí)候隨時(shí)都可以重新增加一段新的空間并鏈接起來。
  3. deque提供了Ramdon Access Iterator,同時(shí)也支持隨機(jī)訪問和存取,但是它也為此付出了昂貴的代價(jià),其復(fù)雜度不能跟vector的原生指針迭代器相提并論。在下面的講解中會一一為大家介紹STL是怎樣”辛苦地”維持一個(gè)隨機(jī)訪問迭代器的。

19、詳細(xì)講講deque實(shí)現(xiàn)原理

動態(tài)開辟的二維數(shù)組空間,第二維固定長度的數(shù)組空間,擴(kuò)容的時(shí)候(第一維的數(shù)組進(jìn)行2倍擴(kuò)容)。

deque內(nèi)部實(shí)現(xiàn)的是一個(gè) 雙向隊(duì)列 。元素在內(nèi)存連續(xù)存放。隨機(jī)存取任何元素都在常數(shù)時(shí)間完成(僅次于vector)。所有適用于vector的操作都適用于deque。在兩端增刪元素具有較佳的性能(大部分情況下是常數(shù)時(shí)間)。

20、你了解deque的中控器嗎

deque為了維持整體連續(xù)的假象,設(shè)計(jì)一個(gè)中控器,其用來記錄deque內(nèi)部每一段連續(xù)空間的地址。大體上可以理解為deque中的每一段連續(xù)空間分布在內(nèi)存的不連續(xù)空間上,然后用一個(gè)所謂的map作為主控,記錄每一段內(nèi)存空間的入口,從而做到整體連續(xù)的假象。

21、deque的迭代器是怎么回事呢

  1. deque提供的是一個(gè)隨機(jī)訪問迭代器,由于是分段連續(xù)空間,其必須記錄當(dāng)前元素所在段的信息,從而在該段連續(xù)空間的邊緣進(jìn)行前進(jìn)或者后退的時(shí)候能知道跳躍到的上一個(gè)或下一個(gè)緩沖區(qū)。deque必須完完整整的掌握和控制這些信息,以達(dá)到正確的跳躍。

  2. buffer_size函數(shù):

    static size_t buffer_size(){
        return __deque_buf_size(BufSiz, sizeof(T));
    }
    //如果n不為0,傳回n,表示buffer size 由自己定義
    如果n為0,表示buffer_size 采用默認(rèn)值
    如果sz(元素大小) < 512,傳回512/sz,如果不小于512 ,傳回1
    inline size_t __deque_buf_size(size_t n, size_t sz)
    {
        return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));
    }
    

  3. set_node函數(shù)

當(dāng)?shù)魈幵诋?dāng)前緩沖區(qū)的邊緣時(shí),一旦前進(jìn)或者后退,就要考慮超過當(dāng)前緩沖區(qū)的情況,此時(shí)需要跳轉(zhuǎn)到下一個(gè)緩沖區(qū),這時(shí)候set_node就派上用場了。

void set_node(map_pointer new_node)
{
  node = new_node;      // 跳轉(zhuǎn)到相應(yīng)緩沖區(qū)
  first = *new_node;    // 更新跳轉(zhuǎn)后緩沖區(qū)first信息
  last = first + difference_type(buffer_size());  // 更新跳轉(zhuǎn)后緩沖區(qū)last的信息
}

22、說一說deque的數(shù)據(jù)結(jié)構(gòu)

deque維護(hù)著一個(gè)map,用來記錄每個(gè)緩沖區(qū)的位置。除了map外,deque的數(shù)據(jù)結(jié)構(gòu)還維護(hù)著start和finish兩個(gè)迭代器,分別指向deque的首尾。此外,他還必須知道m(xù)ap的大小,一旦map提供的節(jié)點(diǎn)不足,就需要配置一塊更大的map。

23、deque常用的函數(shù)

deque.push_back(elem)在尾部加入一個(gè)數(shù)據(jù)。
deque.pop_back()刪除尾部數(shù)據(jù)。
deque.push_front(elem)在頭部插入一個(gè)數(shù)據(jù)。
deque.pop_front()刪除頭部數(shù)據(jù)。
deque.size() 返回容器中實(shí)際數(shù)據(jù)的個(gè)數(shù)。
deque.at(idx)傳回索引idx所指的數(shù)據(jù),如果idx越界,拋出out_of_range。

24、vector、list、deque的選擇原則

  • vector可以隨機(jī)存儲元素(即可以通過公式直接計(jì)算出元素地址,而不需要挨個(gè)查找),但在非尾部插入刪除數(shù)據(jù)時(shí),效率很低,適合對象簡單,對象數(shù)量變化不大,隨機(jī)訪問頻繁。除非必要,我們盡可能選擇使用vector而非deque,因?yàn)閐eque的迭代器比vector迭代器復(fù)雜很多。
  • list不支持隨機(jī)存儲,適用于對象大,對象數(shù)量變化頻繁,插入和刪除頻繁,比如寫多讀少的場景。
  • 需要從首尾兩端進(jìn)行插入或刪除操作的時(shí)候需要選擇deque。

也即:

  • 需要對數(shù)據(jù)高效的隨機(jī)訪問(存?。?,而不在乎插入和刪除的效率,采用vector。
  • 需要大量插入、刪除數(shù)據(jù),而不關(guān)心隨機(jī)訪問數(shù)據(jù),采用list。
  • 需要隨機(jī)訪問數(shù)據(jù),而且關(guān)心前后增刪數(shù)據(jù)的能力,采用deque。
  • 對數(shù)據(jù)中間的增刪操作比較多,采用list,建議在排序的基礎(chǔ)上,批量進(jìn)行增刪可以對運(yùn)行效率提供最大的保證。

25、說說STL迭代器是怎么刪除元素的

  1. 對于序列容器vector,deque來說,使用erase后,后邊的每個(gè)元素的迭代器都會失效,后邊每個(gè)元素都往前移動一位,erase返回下一個(gè)有效的迭代器;
  2. 對于關(guān)聯(lián)容器map,set來說,使用了erase后,當(dāng)前元素的迭代器失效,但是其結(jié)構(gòu)是紅黑樹,刪除當(dāng)前元素,不會影響下一個(gè)元素的迭代器,所以在調(diào)用erase之前,記錄下一個(gè)元素的迭代器即可;
  3. 對于list來說,它使用了不連續(xù)分配的內(nèi)存,并且它的erase方法也會返回下一個(gè)有效的迭代器,因此上面兩種方法都可以使用。

26、了解優(yōu)先級隊(duì)列嗎?展開講講

底層數(shù)據(jù)結(jié)構(gòu)一般以vector為底層容器,heap為處理規(guī)則來管理底層容器實(shí)現(xiàn)。

優(yōu)先隊(duì)列(priority_queue)容器與隊(duì)列一樣,只能從隊(duì)尾插入元素,從隊(duì)首刪除元素。但是它有一個(gè)特性,隊(duì)列中最大的元素總是位于隊(duì)首。出隊(duì)時(shí),并非按照先進(jìn)先出的原則進(jìn)行,而是將當(dāng)前隊(duì)列中最大的元素出隊(duì)。這點(diǎn)類似于給隊(duì)列里的元素進(jìn)行了由大到小的順序排序。元素的比較規(guī)則默認(rèn)按元素值由大到小排序,可以重載“<”操作符來重新定義比較規(guī)則。在優(yōu)先隊(duì)列中,隊(duì)首元素一定是當(dāng)前隊(duì)列中優(yōu)先級最高的那一個(gè)。

27、你知道STL容器動態(tài)鏈接可能產(chǎn)生的問題嘛

  • 可能產(chǎn)生的問題

容器是一種動態(tài)分配內(nèi)存空間的一個(gè)變量集合類型變量。在一般的程序函數(shù)里,局部容器,參數(shù)傳遞容器,參數(shù)傳遞容器的引用,參數(shù)傳遞容器指針都是可以正常運(yùn)行的,而在動態(tài)鏈接庫函數(shù)內(nèi)部使用容器也是沒有問題的,但是給動態(tài)庫函數(shù)傳遞容器的對象本身,則會出現(xiàn)內(nèi)存堆棧破壞的問題。

  • 產(chǎn)生問題的原因

容器和動態(tài)鏈接庫相互支持不夠好,動態(tài)鏈接庫函數(shù)中使用容器時(shí),參數(shù)中只能傳遞容器的引用,并且要保證容器的大小不能超出初始大小,否則導(dǎo)致容器自動重新分配,就會出現(xiàn)內(nèi)存堆棧破壞問題。

28、你了解map和unordered_map嘛?底層實(shí)現(xiàn)呢

  • map實(shí)現(xiàn)機(jī)理

map內(nèi)部實(shí)現(xiàn)了一個(gè) 紅黑樹 (紅黑樹是非嚴(yán)格平衡的二叉搜索樹,而AVL是嚴(yán)格平衡二叉搜索樹),紅黑樹有自動排序的功能,因此map內(nèi)部所有元素都是有序的,紅黑樹的每一個(gè)節(jié)點(diǎn)都代表著map的一個(gè)元素。因此,對于map進(jìn)行的查找、刪除、添加等一系列的操作都相當(dāng)于是對紅黑樹進(jìn)行的操作。map中的元素是按照二叉樹(又名二叉查找樹、二叉排序樹)存儲的,特點(diǎn)就是左子樹上所有節(jié)點(diǎn)的鍵值都小于根節(jié)點(diǎn)的鍵值,右子樹所有節(jié)點(diǎn)的鍵值都大于根節(jié)點(diǎn)的鍵值。使用中序遍歷可將鍵值按照從小到大遍歷出來。

  • unordered_map實(shí)現(xiàn)機(jī)理

unordered_map內(nèi)部實(shí)現(xiàn)了一個(gè) 哈希表 (也叫散列表),通過把關(guān)鍵碼值映射到Hash表中一個(gè)位置來訪問記錄,查找時(shí)間復(fù)雜度可達(dá)O(1),其中在海量數(shù)據(jù)處理中有著廣泛應(yīng)用。因此,元素的排列順序是無序的。

29、map和unordered_map的優(yōu)缺點(diǎn)

  • map的優(yōu)點(diǎn)
    • 有序
    • 基于紅黑樹實(shí)現(xiàn),查找的時(shí)間復(fù)雜度是O(nlogn)
  • map的缺點(diǎn)
    • 空間占用率比較高,雖然說底層是紅黑樹實(shí)現(xiàn)的,提高了運(yùn)行效率,但是每個(gè)節(jié)點(diǎn)都要保存父節(jié)點(diǎn)和孩子節(jié)點(diǎn)和紅黑樹的性質(zhì),使得每一個(gè)節(jié)點(diǎn)都占用膽量的空間。
  • 適用情況
    • 對于有序的結(jié)構(gòu)
  • unordered_map的優(yōu)點(diǎn)
    • 底層是用哈希表實(shí)現(xiàn)的,查找效率非常高,時(shí)間復(fù)雜度為O(1)。
  • unordered_map的缺點(diǎn)
    • 哈希表的建立比較費(fèi)時(shí)。
  • 適用場景
    • 對于查找問題,使用unordered_map更好。

30、set的底層實(shí)現(xiàn)為什么不用哈希表而是用紅黑樹

  • set中元素是經(jīng)過排序的,紅黑樹也是有序的,哈希是無序的
  • 如果只是單純的查找元素的話,那么肯定要選哈希表了,因?yàn)楣1碓诘淖詈貌檎視r(shí)間復(fù)雜度為O(1),并且如果用到set中那么查找時(shí)間復(fù)雜度的一直是O(1),因?yàn)閟et中是不允許有元素重復(fù)的。而紅黑樹的查找時(shí)間復(fù)雜度為O(logn)。

31、為什么map和set和插入刪除效率比其他序列容器高,而且每次insert之后,以前保存的iterator不會失效

因?yàn)榇鎯Φ氖枪?jié)點(diǎn),不需要內(nèi)存拷貝和內(nèi)存移動。

插入操作只是節(jié)點(diǎn)指針換來換去,節(jié)點(diǎn)內(nèi)存沒有改變,而iterator就像指向節(jié)點(diǎn)的指針,內(nèi)存沒變,指向內(nèi)存de指針也不會變。

32、為什么map和set不能像vector一樣有個(gè)reserve函數(shù)來預(yù)分配數(shù)據(jù)

因?yàn)樵趍ap和set內(nèi)部存儲的已經(jīng)不是元素本身了,而是包含元素的節(jié)點(diǎn)。也就是說map內(nèi)部使用的Alloc并不是map聲明的時(shí)候從參數(shù)中傳入的Alloc。

33、你知道m(xù)ap和set有什么區(qū)別嘛?分別是怎么實(shí)現(xiàn)的呢

  • set是一種關(guān)聯(lián)式容器,其特性如下:
    • set以RBTree作為底層容器
    • 所得元素的只有key沒有value,value就是key
    • 不允許出現(xiàn)鍵值重復(fù)
    • 所有的元素都會被自動排序
    • 不能通過迭代器來改變set的值,因?yàn)閟et的值就是鍵,set的迭代器是const的
  • map和set一樣是關(guān)聯(lián)式容器,其特性如下:
    • map以RBTree作為底層容器
    • 所有元素都是鍵+值存在
    • 不允許鍵重復(fù)
    • 所有元素是通過鍵進(jìn)行自動排序的
    • map的鍵是不能修改的,但是其鍵對應(yīng)的值是可以修改的

綜上所述,map和set底層實(shí)現(xiàn)都是紅黑樹;map和set的區(qū)別在于map的值不作為鍵,鍵和值是分開的。

34、vector越界訪問下標(biāo),map越界訪問下標(biāo),vector刪除元素時(shí)會不會釋放空間

  • 通過下標(biāo)訪問vector中的元素時(shí)會做邊界檢查,但該處的實(shí)現(xiàn)方式要看具體IDE,不同IDE的實(shí)現(xiàn)方式不一樣,確保不可訪問越界地址。
  • map的下標(biāo)運(yùn)算符[]的作用是:將key作為下標(biāo)去執(zhí)行查找,并返回相應(yīng)的值;如果不存在這個(gè)key,就將一個(gè)具有該key和value的某人值插入這個(gè)map。
  • erase()函數(shù),只能刪除內(nèi)容,不能改變?nèi)萘看笮?

erase成員函數(shù),它刪除了itVect迭代器指向的元素,并且返回要被刪除的itVect之后的迭代器,迭代器相當(dāng)于一個(gè)智能指針;clear()函數(shù),只能清空內(nèi)容,不能改變?nèi)萘看笮?如果要想在刪除內(nèi)容的同時(shí)釋放內(nèi)存,那么你可以選擇deque容器。

35、map中[ ]與find的區(qū)別

  • map的下標(biāo)運(yùn)算符[ ]的作用是:將關(guān)鍵碼作為下標(biāo)去執(zhí)行查找,并返回對應(yīng)的值;如果不存在這個(gè)關(guān)鍵碼,就將一個(gè)具有該關(guān)鍵碼和值類型的默認(rèn)值的項(xiàng)插入這個(gè)map。
  • map的find函數(shù):用關(guān)鍵碼執(zhí)行查找,找到了返回該位置的迭代器;如果不存在這個(gè)關(guān)鍵碼,就返回尾迭代器。

36、你了解STL的內(nèi)存優(yōu)化嘛

STL內(nèi)存管理使用二級內(nèi)存配置器。

  1. 第一級配置器:
  • 第一級配置器以malloc(),free(),realloc()等C函數(shù)執(zhí)行實(shí)際的內(nèi)存配置、釋放、重新配置等操作,并且能在內(nèi)存需求不被滿足的時(shí)候,調(diào)用一個(gè)指定的函數(shù)。一級空間配置器分配的是大于128字節(jié)的空間,如果分配不成功,調(diào)用句柄釋放一部分內(nèi)存,如果還不能分配成功,拋出異常。
  • 第一級配置器只是對malloc函數(shù)和free函數(shù)的簡單封裝,在allocate內(nèi)調(diào)用malloc,在deallocate內(nèi)調(diào)用free。同時(shí)第一級配置器的oom_malloc函數(shù),用來處理malloc失敗的情況。
  1. 第二級配置器:

第一級配置器直接調(diào)用malloc和free帶來了幾個(gè)問題:

  • 內(nèi)存分配/釋放的效率低。
  • 當(dāng)配置大量的小內(nèi)存塊時(shí),會導(dǎo)致內(nèi)存碎片比較嚴(yán)重。
  • 配置內(nèi)存時(shí),需要額外的部分空間存儲內(nèi)存塊信息,所以配置大量的小內(nèi)存塊時(shí),還會導(dǎo)致額外內(nèi)存負(fù)擔(dān)。

如果分配的區(qū)塊小于128bytes,則以內(nèi)存池管理,第二級配置器維護(hù)了一個(gè)自由鏈表數(shù)組,每次需要分配內(nèi)存時(shí),直接從相應(yīng)的鏈表上取出一個(gè)內(nèi)存節(jié)點(diǎn)就完成工作,效率很高。

  • 自由鏈表數(shù)組:自由鏈表數(shù)組其實(shí)就是個(gè)指針數(shù)組,數(shù)組中的每個(gè)指針元素指向一個(gè)鏈表的起始節(jié)點(diǎn)。數(shù)組大小為16,即維護(hù)了16個(gè)鏈表,鏈表的每個(gè)節(jié)點(diǎn)就是實(shí)際的內(nèi)存塊,相同鏈表上的內(nèi)存塊大小都相同,不同鏈表的內(nèi)存塊大小不同,從8一直到128。如下所示,obj為鏈表上的節(jié)點(diǎn),free_list就是鏈表數(shù)組。
  • 內(nèi)存分配:allocate函數(shù)內(nèi)先判斷要分配的內(nèi)存大小,若大于128字節(jié),直接調(diào)用第一級配置器,否則根據(jù)要分配的內(nèi)存大小從16個(gè)鏈表中選出一個(gè)鏈表,取出該鏈表的第一個(gè)節(jié)點(diǎn)。若相應(yīng)的鏈表為空,則調(diào)用refill函數(shù)填充該鏈表。默認(rèn)是取出20個(gè)數(shù)據(jù)塊。
  • 填充鏈表 refill:若allocate函數(shù)內(nèi)要取出節(jié)點(diǎn)的鏈表為空,則會調(diào)用refill函數(shù)填充該鏈表。refill函數(shù)內(nèi)會先調(diào)用chunk_alloc函數(shù)從內(nèi)存池分配一大塊內(nèi)存,該內(nèi)存大小默認(rèn)為20個(gè)鏈表節(jié)點(diǎn)大小,當(dāng)內(nèi)存池的內(nèi)存也不足時(shí),返回的內(nèi)存塊節(jié)點(diǎn)數(shù)目會不足20個(gè)。接著refill的工作就是將這一大塊內(nèi)存分成20份相同大小的內(nèi)存塊,并將各內(nèi)存塊連接起來形成一個(gè)鏈表。
  • 內(nèi)存池:chunk_alloc函數(shù)內(nèi)管理了一塊內(nèi)存池,當(dāng)refill函數(shù)要填充鏈表時(shí),就會調(diào)用chunk_alloc函數(shù),從內(nèi)存池取出相應(yīng)的內(nèi)存。
    • 在chunk_alloc函數(shù)內(nèi)首先判斷內(nèi)存池大小是否足夠填充一個(gè)有20個(gè)節(jié)點(diǎn)的鏈表,若內(nèi)存池足夠大,則直接返回20個(gè)內(nèi)存節(jié)點(diǎn)大小的內(nèi)存塊給refill;
    • 若內(nèi)存池大小無法滿足20個(gè)內(nèi)存節(jié)點(diǎn)的大小,但至少滿足1個(gè)內(nèi)存節(jié)點(diǎn),則直接返回相應(yīng)的內(nèi)存節(jié)點(diǎn)大小的內(nèi)存塊給refill;
    • 若內(nèi)存池連1個(gè)內(nèi)存節(jié)點(diǎn)大小的內(nèi)存塊都無法提供,則chunk_alloc函數(shù)會將內(nèi)存池中那一點(diǎn)點(diǎn)的內(nèi)存大小分配給其他合適的鏈表,然后去調(diào)用malloc函數(shù)分配的內(nèi)存大小為所需的兩倍。若malloc成功,則返回相應(yīng)的內(nèi)存大小給refill;若malloc失敗,會先搜尋其他鏈表的可用的內(nèi)存塊,添加到內(nèi)存池,然后遞歸調(diào)用chunk_alloc函數(shù)來分配內(nèi)存,若其他鏈表也無內(nèi)存塊可用,則只能調(diào)用第一級空間配置器。

37、頻繁對vector調(diào)用push_back()對性能的影響和原因

在一個(gè)vector的尾部之外的任何位置添加元素,都需要重新移動元素。而且,向一個(gè)vector添加元素可能引起整個(gè)對象存儲空間的重新分配。重新分配一個(gè)對象的存儲空間需要分配新的內(nèi)存,并將元素從舊的空間移到新的空間。

38、hash_map與map的區(qū)別?什么時(shí)候用hash_map,什么時(shí)候用map

  • 構(gòu)造函數(shù):hash_map需要hash function和等于函數(shù),而map需要比較函數(shù)(大于或小于)。
  • 存儲結(jié)構(gòu):hash_map以hashtable為底層,而map以RB-TREE為底層。
  • 總的說來,hash_map查找速度比map快,而且查找速度基本和數(shù)據(jù)量大小無關(guān),屬于常數(shù)級別。而map的查找速度是logn級別。但不一定常數(shù)就比log小,而且hash_map還有hash function耗時(shí)。
  • 如果考慮效率,特別當(dāng)元素達(dá)到一定數(shù)量級時(shí),用hash_map。
  • 考慮內(nèi)存,或者元素?cái)?shù)量較少時(shí),用map。

39、講一講set的用法和它的特點(diǎn)

  • 用法:count()-返回某個(gè)值元素的個(gè)數(shù)(set中最多為1)、find()-返回一個(gè)指向被查找到元素的迭代器、equal_range()-返回集合中與給定值相等的上下限的兩個(gè)迭代器。
  • 特點(diǎn):元素不允許有重復(fù),在默認(rèn)情況下會對元素進(jìn)行自動排序,數(shù)據(jù)被組織成一棵紅黑樹,查找的速度非??欤ǘ郑?,時(shí)間復(fù)雜度是O(logN),set中的元素不能被修改,只能刪除后再添加。
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7604

    瀏覽量

    136680
  • 容器
    +關(guān)注

    關(guān)注

    0

    文章

    495

    瀏覽量

    22060
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2108

    瀏覽量

    73618
  • STL
    STL
    +關(guān)注

    關(guān)注

    0

    文章

    86

    瀏覽量

    18319
收藏 人收藏

    評論

    相關(guān)推薦

    C++STL算法(二)

    C++STL算法(二)
    的頭像 發(fā)表于 07-18 14:49 ?1034次閱讀
    <b class='flag-5'>C</b>++<b class='flag-5'>之</b><b class='flag-5'>STL</b>算法(二)

    c++STL算法(三)

    c++STL算法(三)
    的頭像 發(fā)表于 07-18 15:00 ?1278次閱讀
    <b class='flag-5'>c</b>++<b class='flag-5'>之</b><b class='flag-5'>STL</b>算法(三)

    手把手教你學(xué)單片機(jī)AVR入門視頻教程

    第02講 AVR硬件電路設(shè)計(jì)教程_手把手教你學(xué)單片機(jī)AVR入門篇第03講 AVR開發(fā)基礎(chǔ)知識_手把手教你學(xué)單片機(jī)AVR入門篇第04講
    發(fā)表于 03-02 11:04

    PCB設(shè)計(jì)技巧入門篇

    PCB設(shè)計(jì)技巧入門篇
    發(fā)表于 08-05 21:44

    C++ STL的概念及舉例

      本篇文章是作者本人使用STL 后的一些看法, 對於想要靠此文章學(xué)習(xí)STL, 是不可能的. 建議叁后面介紹的一些書入門.   STL的概念   在
    發(fā)表于 08-30 11:39 ?1410次閱讀

    C語言入門教程

    很好的C語言入門教程,可以肯定的說這個(gè)教程只是為初學(xué)或入門者準(zhǔn)備的
    發(fā)表于 01-22 14:46 ?7次下載

    第01章 C語言程序設(shè)計(jì)預(yù)備知識

    C語言入門教學(xué)知識簡介,第一章,設(shè)計(jì)預(yù)備知識。
    發(fā)表于 03-21 09:53 ?0次下載

    C語言入門經(jīng)典-C語言編程

    C語言入門經(jīng)典-C語言編程,感興趣的可以看看哦。
    發(fā)表于 08-16 18:54 ?126次下載

    C語言編程開發(fā)入門基礎(chǔ)教程

    電子專業(yè)單片機(jī)相關(guān)知識學(xué)習(xí)教材資料——C語言編程開發(fā)入門基礎(chǔ)教程
    發(fā)表于 08-23 15:23 ?0次下載

    51單片機(jī)c51語言入門教程C語言入門教程

    51單片機(jī)c51語言入門教程,C語言入門教程
    發(fā)表于 08-29 15:02 ?32次下載

    c語言入門書籍推薦

    本文主要介紹c語言入門書籍,首先講解了C語言的優(yōu)點(diǎn),其次詳細(xì)的推薦了幾款適合C
    的頭像 發(fā)表于 04-13 09:52 ?4.8w次閱讀

    適合C語言小白看的基礎(chǔ)知識梳理總結(jié)

    C語言是當(dāng)代人學(xué)習(xí)及生活中的必備基礎(chǔ)知識,應(yīng)用十分廣泛,下面為大家?guī)?b class='flag-5'>C語言基礎(chǔ)知識梳理總結(jié),
    的頭像 發(fā)表于 01-04 11:07 ?4734次閱讀

    單片機(jī)的C語言基礎(chǔ)入門和應(yīng)用知識點(diǎn)教程免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是單片機(jī)的C語言基礎(chǔ)入門和應(yīng)用知識點(diǎn)教程免費(fèi)下載包括了:1.進(jìn)制轉(zhuǎn)換,2.C
    發(fā)表于 05-21 08:00 ?10次下載
    單片機(jī)的<b class='flag-5'>C</b><b class='flag-5'>語言</b>基礎(chǔ)<b class='flag-5'>入門</b>和應(yīng)用<b class='flag-5'>知識</b>點(diǎn)教程免費(fèi)下載

    Linux下C語言編程入門教程詳細(xì)說明

    本文是Linux 下C 語言編程入門教程。主要介紹了Linux 的發(fā)展與特點(diǎn)、C語言的基礎(chǔ)知識
    發(fā)表于 08-25 18:05 ?39次下載
    Linux下<b class='flag-5'>C</b><b class='flag-5'>語言</b>編程<b class='flag-5'>入門</b>教程詳細(xì)說明

    C語言教程:STL-for-each算法

    C語言教程:STL-for-each算法(電源技術(shù)版面費(fèi)5400)-文檔為C語言教程:STL-f
    發(fā)表于 09-17 12:42 ?3次下載
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>教程:<b class='flag-5'>STL</b>-for-each算法
    RM新时代网站-首页