C++STL的vector源碼分析、內(nèi)存管理及問答

標(biāo)準(zhǔn)模板庫由三個部分組成:容器、迭代器、算法

Q:容器分為哪幾種?

A:順序容器、關(guān)聯(lián)容器、容器適配器

Q:簡要闡述一下你理解的vector容器

A:它相當(dāng)于一個會自動增長的數(shù)組。與數(shù)組比較起來,它花費更多的內(nèi)存去有效的管理存儲和動態(tài)增長。

在問其他問題之前,我們先來理解一下vector的push_back源碼

vector的push_back源碼

1.首先先判斷該值的地址是否位于vector當(dāng)前已有數(shù)據(jù)的地址范圍內(nèi)

2.如果存在,計算出它與首地址的偏移量,使用這個值來初始化數(shù)據(jù)

3.如果不存在,那么先查看當(dāng)前容量是否已滿

4.如果沒有可用空間了,重新申請內(nèi)存(reserve)

5.如果有可用空間,就使用construct來初始化數(shù)據(jù)

6.最后,尾指針向后移動

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%或為插入的數(shù)量(1,這是在當(dāng)前容量等于0的情況下)

4.申請新內(nèi)存,將原始空間析構(gòu)函數(shù)釋放,重新計算頭尾指針的偏移量



Q:分析一下push_back與c11的emplace_back

A:push_back(A("cc")) 此時它創(chuàng)建一個臨時局部A類對象。而emplace_back("cc")可以在內(nèi)存中直接創(chuàng)建對象而不需要傳入對象。

? 總的來說,再插入值的時候,push_back會拷貝元素,emplace_back會構(gòu)造元素。但是當(dāng)插入的是對象的時候,emplace_back的形參里的參數(shù)應(yīng)該與對象的構(gòu)造函數(shù)相對應(yīng)


Q:vector的erase函數(shù)是如何實現(xiàn)的?

A:因為vector是以array為底層實現(xiàn),那么vector也是一段連續(xù)的代碼塊,所以當(dāng)我們按照某個位置擦除掉某個元素/某幾個元素時,容器會重新遷移剩余的元素,使它們依舊緊湊在一起。其中使用了copy算法(傳入要刪除的元素串的尾部、vector的尾部:注意這不是容量的尾部是大小的尾部、傳入要刪除的元素串的尾部),這樣就會把元素串的尾部到vector的尾部的元素串復(fù)制到地址從元素串的首部開始。


vector的resize函數(shù)(size)和reserve函數(shù)(capacity)

c++文檔中reserve函數(shù)有一個簡短的定義“Request a change in capacity”,這可以看出reserve函數(shù)主要是在容量上進行增加。

前面已經(jīng)解釋過了reserve的源碼,現(xiàn)在來看一看resize的源碼

1.如果傳入的新的大小大于舊的大小,那么在舊的大小的尾部插入長度為它們之間差值的元素串。

2.如果小于舊的大小,那么把舊的大小大于新大小的那一部分的元素刪去

所以resize是改變size大?。ㄓ锌赡芨淖僣apacity的大小,當(dāng)需要擴容的時候),而reserve是改變capacity大小。

那么它們哪個性能更優(yōu)呢?

因為resize可能會改變capacity的大小,那么這個時候它就需要擴容,擴容的關(guān)鍵就是新建一個vector對象,再將原本的數(shù)據(jù)拷貝進入該對象中,再把要插入的值寫入。因為新創(chuàng)建了對象所以它的性能會比較低下。reserve表示容器預(yù)留空間,在改變capacity大小時,因為沒有給該內(nèi)存進行初始化,所以不會去新建對象,所以它不能引用容器內(nèi)的元素,如果需要引用就需要使用push_back和insert。

如果還不理解它們之間的關(guān)系,這里有一個小例子:

? ? ? ? ? ? 正在建造的一輛公交車,車里面可以設(shè)置40個座椅(reserve(40);),這是它的容量,但并不是說它里面就有了40個座椅,只能說明這部車內(nèi)部空間大小可以放得下40張座椅而已。而車里面安裝了40個座椅(resize(40);),這個時候車里面才真正有了40個座椅,這些座椅就可以使用了


總的來說,reserve(預(yù)留空間、改變內(nèi)存、不建對象)

? ? resize(改變size、可能改變內(nèi)存、新建對象)

Q:vector的大?。╲ector::size)與它的容量(vector::capacity)是一樣的嗎?

A:不一樣。當(dāng)vector有特定容量且vector數(shù)據(jù)已經(jīng)存滿的時候,它們的大小和容量是相同的。但是如果此時再插入一個數(shù)據(jù),容量會按照一定的增長方式增加,此時大小就會小于容量。

Q:詳細描述一下vector的內(nèi)存管理

A:為了避免每一次插入都要擴容,vector在初始化時會讓容量大于預(yù)裝入的數(shù)據(jù)長。

? ? ? 當(dāng)插入數(shù)據(jù)的時候,如果此時容量已滿,那么就實現(xiàn)擴容(擴容并不是在原vector的基礎(chǔ)上,而是新建一個大小是舊容量的150%的新vector,然后再將舊vector的值拷貝到新vector,再將插入的值拷貝進vector。然后調(diào)用析構(gòu)函數(shù)釋放舊vector的內(nèi)存)。

Q:erase后的vector,它的容量依舊是不變的,只是存儲的數(shù)據(jù)變少了,所以如果存在容量100000的vector,釋放了99999的數(shù)據(jù),那么此時它的容量依舊一樣,而有效size只剩1。那么如何強制釋放vector的緩沖區(qū)?

A:方法1:std::vector<int>().swap(arr)? arr是待釋放的vector

? ? ? 解釋:首先vector()使用默認構(gòu)造函數(shù)建立了一個臨時的vector對象,當(dāng)這個臨時對象調(diào)用swap時,arr的大小變?yōu)槟J構(gòu)造對象的大小,而臨時對象的大小為arr的大小,因為臨時對象很快就會通過析構(gòu)函數(shù)被釋放掉,所以占用的空間就被釋放了。

? ? 方法2:<vector> int temp; arr.swap(temp);

? ? ? 解釋:此時temp為臨時對象,大小為0,arr與temp相交換,arr的緩沖區(qū)就變?yōu)?了。因為臨時變量會被析構(gòu),所以占用的空間也被釋放了。

Q:vector的容量過小后,是如何實現(xiàn)擴增容量的?

A:先按照一定的增長方式增加容量大小,重新分配一個符合該大小的vector,再將原本vector的數(shù)組拷貝進新的vector,再插入新的值。因為每一次重新分配vector和拷貝十分耗時,所以vector并不會在每次增加新數(shù)據(jù)時都重新分配,那么這就意味著我們需要定義一個比我們預(yù)期存儲范圍還大出一部分(這一部分用于可能的范圍增長)的vector,這樣就可以避免每次重新分配vector。

Q:在多線程時,對vector寫時可能會出現(xiàn)什么錯誤?

A:程序崩潰,因為線程A vector進行寫時,如果內(nèi)存已滿會重新申請內(nèi)存,此時它的地址已經(jīng)改變,而線程B依舊在寫入/讀入已經(jīng)無效的地址,就會造成崩潰。

有兩種解決辦法:1.暴力解決法:直接給vector定義一個較大的內(nèi)存,避免重新申請

? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.加同步鎖,每次寫入前都鎖住,執(zhí)行完本次寫入操作后,其他線程才能再次寫入或讀入。

所以!對容器的modify操作應(yīng)該是原子性(一旦操作開始,到結(jié)束之前都不會切換到其他線程)的!

Q:我們使用sort算法時,傳入容器的起始及結(jié)束。一般在沒有特殊約束下,sort都是由小到大排列,那么如何讓它由大到小排列?

A:sort還有另外一個函數(shù),存在第三個形參,可以傳入謂詞。謂詞有很多種,比如書上有寫到的庫定義的函數(shù)謂詞對象,也就是std::greater<int>() ,將它作為第三個形參傳入,就可以實現(xiàn)由大到小。還有很多種實現(xiàn)方式(函數(shù)謂詞,函數(shù)指針謂詞,函數(shù)對象謂詞,lambda表達式

Q:vector里的重要函數(shù)

A:push_back? pop_back? erase? clear? insert

Q:vector如何進行初始化

A:1.直接賦值

? ? vector<int> num(20,3)包含20個值為3的數(shù)

? ? 2.使用數(shù)組進行遍歷賦值

Q:vector如何訪問元素

A:可以通過迭代器訪問元素、下標(biāo)訪問、at(i)訪問

Q:簡要闡述一下deque

A:和vector類似,但是它可以在隊列的首部和尾部添加或刪除元素(push_front? pop_front),因為它內(nèi)存管理比較復(fù)雜,所以執(zhí)行速度慢于

Q:簡要闡述一下list容器

A:適合頻繁插入刪除元素,但是查找比較復(fù)雜。除了和vector一樣可以正向遍歷之外,還能使用rbegin rend反向遍歷,和deque一樣的就是也含有push_front pop_front等

Q:映射容器map可以用兩種方法插入,一種是insert,一種是下標(biāo)運算符,插入有重復(fù)鍵值的數(shù)據(jù)時,它們的效果是否相同?

A:不相同,insert會插入失敗,而下標(biāo)運算符會覆蓋以前重復(fù)鍵值對應(yīng)的對象

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 標(biāo)簽(空格分隔): STL 運用STL,可以充分利用該庫的設(shè)計,讓我為簡單而直接的問題設(shè)計出簡單而直接的解決方案,...
    認真學(xué)計算機閱讀 1,585評論 0 10
  • 前言: 詳細介紹: List:元素有放入順序,元素可重復(fù)Map:元素按鍵值對存儲,無放入順序Set:元素?zé)o放入順序...
    YBshone閱讀 8,872評論 0 17
  • 風(fēng),從未弄清她的身份 我也不相信,她是友善的 她的目光,游弋于我的骨子里 戳得我,扎心的疼 我蜷縮著,只為尋求避難...
    冷冬年閱讀 320評論 13 28
  • 墨池軒是一家專注于書畫研究與銷售的平臺,所簽約書畫家均是德高望重的國內(nèi)一線大師。今天我們有幸請到了墨池軒的創(chuàng)始人楊...
    墨池軒字畫閱讀 811評論 0 0
  • 這本書是讀者文摘精華本,名字取自韓國大熱的愛情片《來自星星的你》。講述愛情是什么?是一種美好的感覺;是牽掛、思念和...
    常生圓滿心閱讀 248評論 0 0

友情鏈接更多精彩內(nèi)容