騰訊技術崗位筆試&面試題(二)

AutoDriver發表於2024-12-08

說在前面

本篇文章是騰訊技術面試題目彙總第二篇
後續將持續推出網際網路大廠,如阿里,騰訊,百度,美團,頭條等技術面試題目,以及答案和分析。
歡迎大家點贊關注轉發。

1.STL中hash_map擴容發生什麼?

  1. hash table表格內的元素稱為桶(bucket),而由桶所連結的元素稱為節點(node),其中存入桶元素的容器為stl本身很重要的一種序列式容器——vector容器。之所以選擇vector為存放桶元素的基礎容器,主要是因為vector容器本身具有動態擴容能力,無需人工干預。
  2. 向前操作:首先嚐試從目前所指的節點出發,前進一個位置(節點),由於節點被安置於list內,所以利用節點的next指標即可輕易完成前進操作,如果目前正巧是list的尾端,就跳至下一個bucket身上,那正是指向下一個list的頭部節點。

2.map如何建立?

  1. vector 底層資料結構為陣列 ,支援快速隨機訪問
  2. list 底層資料結構為雙向連結串列,支援快速增刪
  3. deque 底層資料結構為一箇中央控制器和多個緩衝區,詳細見STL原始碼剖析P146,支援首尾(中間不能)快速增刪,也支援隨機訪問
    deque是一個雙端佇列(double-ended queue),也是在堆中儲存內容的. 它的儲存形式如下:
    [堆1] --> [堆2] -->[堆3] --> …
    每個堆儲存好幾個元素,然後堆和堆之間有指標指向,看起來像是list和vector的結合品.
  4. stack 底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時
  5. queue 底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時(stack和queue其實是介面卡,而不叫容器,因為是對容器的再封裝)
  6. priority_queue 的底層資料結構一般為vector為底層容器,堆heap為處理規則來管理底層容器實現
  7. set 底層資料結構為紅黑樹,有序,不重複
  8. multiset 底層資料結構為紅黑樹,有序,可重複
  9. map 底層資料結構為紅黑樹,有序,不重複
  10. multimap 底層資料結構為紅黑樹,有序,可重複
  11. hash_set 底層資料結構為hash表,無序,不重複
  12. hash_multiset 底層資料結構為hash表,無序,可重複
  13. hash_map 底層資料結構為hash表,無序,不重複
  14. hash_multimap 底層資料結構為hash表,無序,可重複

3.vector的增加刪除都是怎麼做的?為什麼是1.5倍?

  1. 新增元素:vector透過一個連續的陣列存放元素,如果集合已滿,在新增資料的時候,就要分配一塊更大的記憶體,將原來的資料複製過來,釋放之前的記憶體,在插入新增的元素;
  2. 對vector的任何操作,一旦引起空間重新配置,指向原vector的所有迭代器就都失效了 ;
  3. 初始時刻vector的capacity為0,塞入第一個元素後capacity增加為1;
  4. 不同的編譯器實現的擴容方式不一樣,VS2015中以1.5倍擴容,GCC以2倍擴容。

對比可以發現採用採用成倍方式擴容,可以保證常數的時間複雜度,而增加指定大小的容量只能達到O(n)的時間複雜度,因此,使用成倍的方式擴容。

  1. 考慮可能產生的堆空間浪費,成倍增長倍數不能太大,使用較為廣泛的擴容方式有兩種,以2二倍的方式擴容,或者以1.5倍的方式擴容。
  2. 以2倍的方式擴容,導致下一次申請的記憶體必然大於之前分配記憶體的總和,導致之前分配的記憶體不能再被使用,所以最好倍增長因子設定為(1,2)之間:
  3. 向量容器vector的成員函式pop_back()可以刪除最後一個元素.
  4. 而函式erase()可以刪除由一個iterator指出的元素,也可以刪除一個指定範圍的元素。
  5. 還可以採用通用演算法remove()來刪除vector容器中的元素.
  6. 不同的是:採用remove一般情況下不會改變容器的大小,而pop_back()與erase()等成員函式會改變容器的大小。

4.函式指標?

  1. 什麼是函式指標?
    函式指標指向的是特殊的資料型別,函式的型別是由其返回的資料型別和其引數列表共同決定的,而函式的名稱則不是其型別的一部分。
    一個具體函式的名字,如果後面不跟呼叫符號(即括號),則該名字就是該函式的指標(注意:大部分情況下,可以這麼認為,但這種說法並不很嚴格)。
  2. 函式指標的宣告方法
    int (pf)(const int&, const int&); (1)
    上面的pf就是一個函式指標,指向所有返回型別為int,並帶有兩個const int&引數的函式。注意pf兩邊的括號是必須的,否則上面的定義就變成了:
    int *pf(const int&, const int&); (2)
    而這宣告瞭一個函式pf,其返回型別為int *, 帶有兩個const int&引數。
  3. 為什麼有函式指標
    函式與資料項相似,函式也有地址。我們希望在同一個函式中透過使用相同的形參在不同的時間使用產生不同的效果。
  4. 一個函式名就是一個指標,它指向函式的程式碼。一個函式地址是該函式的進入點,也就是呼叫函式的地址。函式的呼叫可以透過函式名,也可以透過指向函式的指標來呼叫。函式指標還允許將函式作為變元傳遞給其他函式;
  5. 兩種方法賦值:
    指標名 = 函式名; 指標名 = &函式名

5.說說你對c和c++的看法,c和c++的區別?

  1. 第一點就應該想到C是程序導向的語言,而C++是物件導向的語言,一般簡歷上第一條都是熟悉C/C++基本語法,瞭解C++物件導向思想,那麼,請問什麼是物件導向?
  2. C和C++動態管理記憶體的方法不一樣,C是使用malloc/free函式,而C++除此之外還有new/delete關鍵字;(關於malooc/free與new/delete的不同又可以說一大堆,最後的擴充套件_1部分列出十大區別);
  3. 接下來就不得不談到C中的struct和C++的類,C++的類是C所沒有的,但是C中的struct是可以在C++中正常使用的,並且C++對struct進行了進一步的擴充套件,使struct在C++中可以和class一樣當做類使用,而唯一和class不同的地方在於struct的成員預設訪問修飾符是public,而class預設的是private;
  4. C++支援函式過載,而C不支援函式過載,而C++支援過載的依仗就在於C++的名字修飾與C不同,例如在C++中函式int fun(int ,int)經過名字修飾之後變為 _fun_int_int ,而C是
    _fun,一般是這樣的,所以C++才會支援不同的引數呼叫不同的函式;
  5. C++中有引用,而C沒有;這樣就不得不提一下引用和指標的區別(文後擴充套件_2);
  6. 當然還有C++全部變數的預設連結屬性是外連結,而C是內連線;
  7. C 中用const修飾的變數不可以用在定義陣列時的大小,但是C++用const修飾的變數可以(如果不進行&,解引用的操作的話,是存放在符號表的,不開闢記憶體);
  8. 當然還有區域性變數的宣告規則不同,多型,C++特有輸入輸出流之類的,很多,下面就不再列出來了; “`

6.c/c++的記憶體分配,詳細說一下棧、堆、靜態儲存區?

1、棧區(stack)— 由編譯器自動分配釋放,存放函式的引數值,區域性變數的值等
其操作方式類似於資料結構中的棧。
2、堆區(heap) — 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由OS(作業系統)回收。注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列。
3、全域性區(靜態區)(static)—,全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。程式結束後由系統釋放。
4、文字常量區 —常量字串就是放在這裡的。程式結束後由系統釋放。
5、程式程式碼區 —存放函式體的二進位制程式碼。

7.堆與棧的區別?

  1. 管理方式:對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程式設計師控制,容易產生memory leak。
  2. 空間大小:一般來講在32位系統下,堆記憶體可以達到4G的空間,從這個角度來看堆記憶體幾乎是沒有什麼限制的。但是對於棧來講,一般都是有一定的空間大小的,例如,在VC6下面,預設的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改: 開啟工程,依次操作選單如下:Project->Setting->Link,在Category 中選中Output,然後在Reserve中設定堆疊的最大值和commit。 注意:reserve最小值為4Byte;commit是保留在虛擬記憶體的頁檔案裡面,它設定的較大會使棧開闢較大的值,可能增加記憶體的開銷和啟動時間。
  3. 碎片問題:對於堆來講,頻繁的new/delete勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式效率降低。對於棧來講,則不會存在這個問題,因為棧是先進後出的佇列,他們是如此的一一對應,以至於永遠都不可能有一個記憶體塊從棧中間彈出,在他彈出之前,在他上面的後進的棧內容已經被彈出,詳細的可以參考資料結構,這裡我們就不再一一討論了。
  4. 生長方向:對於堆來講,生長方向是向上的,也就是向著記憶體地址增加的方向;對於棧來講,它的生長方向是向下的,是向著記憶體地址減小的方向增長。
  5. 分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配由alloca函式進行分配,但是棧的動態分配和堆是不同的,它的動態分配是由編譯器進行釋放,無需我們手工實現。
  6. 分配效率:棧是機器系統提供的資料結構,計算機會在底層對棧提供支援:分配專門的暫存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函式庫提供的,它的機制是很複雜的,例如為了分配一塊記憶體,庫函式會按照一定的演算法(具體的演算法可以參考資料結構/作業系統)在堆記憶體中搜尋可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於記憶體碎片太多),就有可能呼叫系統功能去增加程式資料段的記憶體空間,這樣就有機會分到足夠大小的記憶體,然後進行返回。顯然,堆的效率比棧要低得多。

相關文章