標(biāo)準(zhǔn)模板庫由三個(gè)部分組成:容器婆殿、迭代器、算法
Q:容器分為哪幾種抓谴?
A:順序容器寞缝、關(guān)聯(lián)容器荆陆、容器適配器
Q:簡要闡述一下你理解的vector容器
A:它相當(dāng)于一個(gè)會(huì)自動(dòng)增長的數(shù)組。與數(shù)組比較起來帜消,它花費(fèi)更多的內(nèi)存去有效的管理存儲(chǔ)和動(dòng)態(tài)增長浓体。
在問其他問題之前命浴,我們先來理解一下vector的push_back源碼
1.首先先判斷該值的地址是否位于vector當(dāng)前已有數(shù)據(jù)的地址范圍內(nèi)
2.如果存在,計(jì)算出它與首地址的偏移量媳溺,使用這個(gè)值來初始化數(shù)據(jù)
3.如果不存在悬蔽,那么先查看當(dāng)前容量是否已滿
4.如果沒有可用空間了,重新申請內(nèi)存(reserve)
5.如果有可用空間录语,就使用construct來初始化數(shù)據(jù)
6.最后难衰,尾指針向后移動(dòng)
7.析構(gòu)函數(shù)釋放原有的vector內(nèi)存
下面我們來看一下是如何重新申請內(nèi)存的
1.如果當(dāng)前可使用的容量小于要插入的數(shù)量(1)盖袭,那么就需要重新申請內(nèi)存
2.如果最大容量減去當(dāng)前容量小于1,那么拋出vector過長異常
3.申請內(nèi)存之前弟塞,容量需要按照增長50%或?yàn)椴迦氲臄?shù)量(1拙已,這是在當(dāng)前容量等于0的情況下)
4.申請新內(nèi)存倍踪,將原始空間析構(gòu)函數(shù)釋放,重新計(jì)算頭尾指針的偏移量
Q:分析一下push_back與c11的emplace_back
A:push_back(A("cc")) 此時(shí)它創(chuàng)建一個(gè)臨時(shí)局部A類對象。而emplace_back("cc")可以在內(nèi)存中直接創(chuàng)建對象而不需要傳入對象潮罪。
? 總的來說领斥,再插入值的時(shí)候月洛,push_back會(huì)拷貝元素,emplace_back會(huì)構(gòu)造元素导而。但是當(dāng)插入的是對象的時(shí)候隔崎,emplace_back的形參里的參數(shù)應(yīng)該與對象的構(gòu)造函數(shù)相對應(yīng)
Q:vector的erase函數(shù)是如何實(shí)現(xiàn)的爵卒?
A:因?yàn)関ector是以array為底層實(shí)現(xiàn),那么vector也是一段連續(xù)的代碼塊实牡,所以當(dāng)我們按照某個(gè)位置擦除掉某個(gè)元素/某幾個(gè)元素時(shí)创坞,容器會(huì)重新遷移剩余的元素受葛,使它們依舊緊湊在一起。其中使用了copy算法(傳入要?jiǎng)h除的元素串的尾部纲堵、vector的尾部:注意這不是容量的尾部是大小的尾部席函、傳入要?jiǎng)h除的元素串的尾部)冈涧,這樣就會(huì)把元素串的尾部到vector的尾部的元素串復(fù)制到地址從元素串的首部開始。
vector的resize函數(shù)(size)和reserve函數(shù)(capacity)
c++文檔中reserve函數(shù)有一個(gè)簡短的定義“Request a change in capacity”营曼,這可以看出reserve函數(shù)主要是在容量上進(jìn)行增加溶推。
前面已經(jīng)解釋過了reserve的源碼奸攻,現(xiàn)在來看一看resize的源碼
1.如果傳入的新的大小大于舊的大小睹耐,那么在舊的大小的尾部插入長度為它們之間差值的元素串。
2.如果小于舊的大小响委,那么把舊的大小大于新大小的那一部分的元素刪去
所以resize是改變size大凶阜纭(有可能改變capacity的大小,當(dāng)需要擴(kuò)容的時(shí)候)荸哟,而reserve是改變capacity大小瞬捕。
那么它們哪個(gè)性能更優(yōu)呢鞍历?
因?yàn)閞esize可能會(huì)改變capacity的大小,那么這個(gè)時(shí)候它就需要擴(kuò)容肪虎,擴(kuò)容的關(guān)鍵就是新建一個(gè)vector對象劣砍,再將原本的數(shù)據(jù)拷貝進(jìn)入該對象中,再把要插入的值寫入扇救。因?yàn)樾聞?chuàng)建了對象所以它的性能會(huì)比較低下刑枝。reserve表示容器預(yù)留空間,在改變capacity大小時(shí)爵政,因?yàn)闆]有給該內(nèi)存進(jìn)行初始化仅讽,所以不會(huì)去新建對象,所以它不能引用容器內(nèi)的元素洁灵,如果需要引用就需要使用push_back和insert。
如果還不理解它們之間的關(guān)系掺出,這里有一個(gè)小例子:
? ? ? ? ? ? 正在建造的一輛公交車徽千,車?yán)锩婵梢栽O(shè)置40個(gè)座椅(reserve(40);),這是它的容量汤锨,但并不是說它里面就有了40個(gè)座椅双抽,只能說明這部車內(nèi)部空間大小可以放得下40張座椅而已。而車?yán)锩姘惭b了40個(gè)座椅(resize(40);)闲礼,這個(gè)時(shí)候車?yán)锩娌耪嬲辛?0個(gè)座椅牍汹,這些座椅就可以使用了
總的來說,reserve(預(yù)留空間柬泽、改變內(nèi)存慎菲、不建對象)
? ? resize(改變size、可能改變內(nèi)存锨并、新建對象)
Q:vector的大新陡谩(vector::size)與它的容量(vector::capacity)是一樣的嗎?
A:不一樣第煮。當(dāng)vector有特定容量且vector數(shù)據(jù)已經(jīng)存滿的時(shí)候解幼,它們的大小和容量是相同的抑党。但是如果此時(shí)再插入一個(gè)數(shù)據(jù),容量會(huì)按照一定的增長方式增加撵摆,此時(shí)大小就會(huì)小于容量底靠。
Q:詳細(xì)描述一下vector的內(nèi)存管理
A:為了避免每一次插入都要擴(kuò)容,vector在初始化時(shí)會(huì)讓容量大于預(yù)裝入的數(shù)據(jù)長台汇。
? ? ? 當(dāng)插入數(shù)據(jù)的時(shí)候苛骨,如果此時(shí)容量已滿篱瞎,那么就實(shí)現(xiàn)擴(kuò)容(擴(kuò)容并不是在原vector的基礎(chǔ)上苟呐,而是新建一個(gè)大小是舊容量的150%的新vector,然后再將舊vector的值拷貝到新vector俐筋,再將插入的值拷貝進(jìn)vector牵素。然后調(diào)用析構(gòu)函數(shù)釋放舊vector的內(nèi)存)。
Q:erase后的vector澄者,它的容量依舊是不變的笆呆,只是存儲(chǔ)的數(shù)據(jù)變少了,所以如果存在容量100000的vector粱挡,釋放了99999的數(shù)據(jù)赠幕,那么此時(shí)它的容量依舊一樣,而有效size只剩1询筏。那么如何強(qiáng)制釋放vector的緩沖區(qū)榕堰?
A:方法1:std::vector<int>().swap(arr)? arr是待釋放的vector
? ? ? 解釋:首先vector()使用默認(rèn)構(gòu)造函數(shù)建立了一個(gè)臨時(shí)的vector對象,當(dāng)這個(gè)臨時(shí)對象調(diào)用swap時(shí)嫌套,arr的大小變?yōu)槟J(rèn)構(gòu)造對象的大小逆屡,而臨時(shí)對象的大小為arr的大小,因?yàn)榕R時(shí)對象很快就會(huì)通過析構(gòu)函數(shù)被釋放掉踱讨,所以占用的空間就被釋放了魏蔗。
? ? 方法2:<vector> int temp; arr.swap(temp);
? ? ? 解釋:此時(shí)temp為臨時(shí)對象,大小為0痹筛,arr與temp相交換莺治,arr的緩沖區(qū)就變?yōu)?了。因?yàn)榕R時(shí)變量會(huì)被析構(gòu)帚稠,所以占用的空間也被釋放了谣旁。
Q:vector的容量過小后,是如何實(shí)現(xiàn)擴(kuò)增容量的翁锡?
A:先按照一定的增長方式增加容量大小蔓挖,重新分配一個(gè)符合該大小的vector,再將原本vector的數(shù)組拷貝進(jìn)新的vector馆衔,再插入新的值瘟判。因?yàn)槊恳淮沃匦路峙鋠ector和拷貝十分耗時(shí)怨绣,所以vector并不會(huì)在每次增加新數(shù)據(jù)時(shí)都重新分配,那么這就意味著我們需要定義一個(gè)比我們預(yù)期存儲(chǔ)范圍還大出一部分(這一部分用于可能的范圍增長)的vector拷获,這樣就可以避免每次重新分配vector篮撑。
Q:在多線程時(shí),對vector寫時(shí)可能會(huì)出現(xiàn)什么錯(cuò)誤匆瓜?
A:程序崩潰赢笨,因?yàn)榫€程A vector進(jìn)行寫時(shí),如果內(nèi)存已滿會(huì)重新申請內(nèi)存驮吱,此時(shí)它的地址已經(jīng)改變茧妒,而線程B依舊在寫入/讀入已經(jīng)無效的地址,就會(huì)造成崩潰左冬。
有兩種解決辦法:1.暴力解決法:直接給vector定義一個(gè)較大的內(nèi)存桐筏,避免重新申請
? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.加同步鎖,每次寫入前都鎖住拇砰,執(zhí)行完本次寫入操作后梅忌,其他線程才能再次寫入或讀入。
所以除破!對容器的modify操作應(yīng)該是原子性(一旦操作開始牧氮,到結(jié)束之前都不會(huì)切換到其他線程)的!
Q:我們使用sort算法時(shí)瑰枫,傳入容器的起始及結(jié)束踱葛。一般在沒有特殊約束下,sort都是由小到大排列躁垛,那么如何讓它由大到小排列剖毯?
A:sort還有另外一個(gè)函數(shù),存在第三個(gè)形參教馆,可以傳入謂詞逊谋。謂詞有很多種,比如書上有寫到的庫定義的函數(shù)謂詞對象土铺,也就是std::greater<int>() 胶滋,將它作為第三個(gè)形參傳入,就可以實(shí)現(xiàn)由大到小悲敷。還有很多種實(shí)現(xiàn)方式(函數(shù)謂詞究恤,函數(shù)指針謂詞,函數(shù)對象謂詞后德,lambda表達(dá)式)
Q:vector里的重要函數(shù)
A:push_back? pop_back? erase? clear? insert
Q:vector如何進(jìn)行初始化
A:1.直接賦值
? ? vector<int> num(20,3)包含20個(gè)值為3的數(shù)
? ? 2.使用數(shù)組進(jìn)行遍歷賦值
Q:vector如何訪問元素
A:可以通過迭代器訪問元素部宿、下標(biāo)訪問、at(i)訪問
Q:簡要闡述一下deque
A:和vector類似,但是它可以在隊(duì)列的首部和尾部添加或刪除元素(push_front? pop_front)理张,因?yàn)樗鼉?nèi)存管理比較復(fù)雜赫蛇,所以執(zhí)行速度慢于
Q:簡要闡述一下list容器
A:適合頻繁插入刪除元素,但是查找比較復(fù)雜雾叭。除了和vector一樣可以正向遍歷之外悟耘,還能使用rbegin rend反向遍歷,和deque一樣的就是也含有push_front pop_front等
Q:映射容器map可以用兩種方法插入织狐,一種是insert暂幼,一種是下標(biāo)運(yùn)算符,插入有重復(fù)鍵值的數(shù)據(jù)時(shí)移迫,它們的效果是否相同旺嬉?
A:不相同,insert會(huì)插入失敗起意,而下標(biāo)運(yùn)算符會(huì)覆蓋以前重復(fù)鍵值對應(yīng)的對象