本文大約有 6385 字。
這是面試題筆記系列的第一篇文章,記錄一些常見的面試題。其中主要是一些一線互聯網大廠的面試題,大部分收集於 LeetCode、CSDN、知乎、GitHub 等技術社區。
答案皆根據我的理解進行編寫解答,水平有限,可能會存在一定的錯誤,歡迎指正。
我將這些面試題主要分爲三類:概念題、算法題、理解應用題。
01|面試題來源
-
騰訊 - 提前批 - 客戶端開發
-
深信服 - 優招 - C++ 研發(物聯網方向)
-
老虎 - 正式批 - Windows 開發一面
-
360 - 提前批 - 軟件開發
02|概念題
1. 函數傳值的方式?
按值傳遞、按引用傳遞、按指針傳遞。
2. 引用和拷貝的區別?
引用的效率要高於拷貝,這也是爲什麼如果函數參數是類對象,通常使用引用傳遞。若不想被改變,則加上 const 爲常引用。
3. 引用的優缺點和拷貝的優缺點?
拷貝需要大量原始數據的移動,帶來時間和空間上的消耗,這是它的缺點。
C++11 所支持的移動語義(move semantics),其思想是避免原始數據的移動,而只是修改記錄。move 的本質是一個轉換函數,將給定類型轉換爲右值引用。
引用的缺點可以從引用與指針的區別的角度來看。如:構造時必須初始化、不能爲 NULL、初始化後無法再改變、引用的創建和銷燬不會調用類的拷貝構造函數和析構函數 等等。
4. C++ 內存分區?未初始化的全局變量放在哪裏?
C++ 內存分區:堆、棧、自由存儲區、全局/靜態存儲區、常量存儲區。
關於自由存儲區(Free Store)和堆(Heap)
-
文本段:程序的二進制代碼,只讀。
-
初始化數據段:global/static/const/extern 初始化的變量。
-
未初始化數據段:未初始化的變量。
-
初始化數據段和未初始化數據段統稱爲數據段。
-
堆:當使用 malloc 在堆上分配的內存。
-
棧:局部變量、函數返回所需的一些東西。
5. 野指針是什麼?有什麼可以檢測嗎?
-
聲明指針但是沒有初始化⚠️。
-
釋放空間後沒有置 NULL,產生懸掛指針,存在安全隱患⚠️。
-
當返回一個基於棧分配的局部變量的地址時,num 是一個局部變量,函數結束後自動釋放其空間,編譯器會給出警告⚠️。
如何檢測野指針?方法是提高野指針的崩潰率,將訪問時的隨機崩潰變成訪問必然崩潰,儘早的定位到野指針。macOS 下 Xcode 提供了內存塗鴉(Malloc Scrobble)。
Linux 下有一個工具可以解決常見的 C++ 內存問題——「Valgrind 」,其中的 memcheck 工具提供:檢查程序中的內存問題,如泄漏、越界、非法指針等。
6. 進程間通信方式,知道互斥鎖和自旋鎖嗎?
-
管道(Pipe)
`| >` :簡單,阻塞,半雙工通信。
-
消息隊列
發送數據塊,數據塊有長度限制,數據拷貝消耗大。
-
共享內存
解決消息隊列的數據拷貝產生的性能消耗。兩個擁有獨立的虛擬內存空間,但是映射到相同的物理內存中。
-
信號量
Semaphore。記錄型信號量(阻塞隊列);互斥信號量。信號量實現進程的互斥和同步。經典的生產者消費者問題。
-
Socket
程序進行 Socket 通信。
自旋鎖🔒
指當一個線程在獲取鎖時,此時的鎖已經被其他線程獲取,那麼該線程循環等待,然後不斷判斷是否能夠被成功獲取,知道獲取到鎖纔會退出循環。
核心:線程反覆檢查鎖變量是否可用,忙等待。
while (test_and_set(lock) == 1);
缺點:消耗 CPU、“飢餓”現象
優點:線程不會進入阻塞狀態、減少了不必要的上下文切換。
互斥鎖🔒
防止兩條線程同時對同一臨界資源進行讀寫的機制。類似互斥信號量機制提供的阻塞隊列,實現讓權等待。
7. 路由器(R)和二層交換機(S)的區別?
交換機,嚴格定義:以太網交換機。
-
工作層不同:R - 網絡層,S - 數據鏈路層轉發機制:R - IP addr,S - MAC addr
-
主要功能:
R - 隔離廣播域,S - 不能
R - 用於連接不同網絡,S - 組建局域網
R - 防火牆,配置複雜
交換機能幹的事情,路由器也都可以。
8. ARP 協議的功能幹嘛用的?
ARP:Address Resolution Protocol 地址解析協議。
所謂的地址解析,就是主機在發送幀前將目標 IP 地址轉換成目標 MAC 地址的過程。ARP 請求就是:根據目的主機的 IP 地址,獲得其 MAC 地址。
終端 PING 一個 IP 地址,使用 WireShark 關鍵字 arp 過濾,可以看到 ARP 請求。
9. C 和 C++ 的區別?
C++ 融合了 3 種不同的編程方式:C 語言代表的過程性語言、C++ 在 C 語言基礎上添加的類代表的面嚮對象語言、C++ 模板支持的泛型編程。
首先,C 語言編程原理:其理念是將大型程序分解成小型、便於管理的任務,是一種自頂向下的設計。結構化編程技術反映了過程性編程的思想,根據執行的操作來構思一個程序。即:數據 + 算法 = 程序。
其次,面向對象編程:OOP(Object-Oriented Programming)強調的是數據。在 C++ 中,類是一種規範,它描述了這種新型的數據格式,對象是根據這種規範構造的特定數據結構。OOP 程序設計方法首先設計類,它準確的表示了程序要處理的東西,並不僅僅是將數據和方法合併爲類的定義,OOP 還有助於構建可重用代碼。
OOP 特性:抽象、封裝和數據隱藏、多態、繼承、代碼的可重用性。
最後,C++ 和範型編程:範型編程(Generic Programming)。C++ 有多種數據類型 —— 整數、浮點數、字符、字符串、用戶定義的、由多種類型組成的複合結構。例如,要對不同類型的數據進行排序,通常必須爲每種類型創建一個排序函數。泛型編程可以只編寫一個泛型(即不是特定類型的)函數,並將其用於各種實際類型。C++ 模板(Template)提供了完成這種任務的機制。
10. 你對多態的理解?
多態(Polymorphism)是 OOP 編程的一個重要思想在面嚮對象語言中,一個接口,多種方法即爲多態。
C++ 的多態分爲兩種,靜態多態和動態多態。
靜態多態是通過函數重載實現的,在編譯過程完成後就知道了應該調用哪個函數。
動態多態即運行時的多態性,通過虛函數(virtual)來實現的。虛函數允許子類重新定義成員函數,而子類重新定義父類的做法稱爲覆蓋(Override),或者稱爲重寫。
多態最常見的用法就是聲明基類類型的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實現不同的方法。
11. 講一下 tcp 的擁塞控制?
TCP 的擁塞控制主要來避免兩種現象,即包丟失和超時重傳。一旦出現了這些現象就說明,發送的速度太快了,要慢一些。
一條 TCP 連接開始,cwnd 設置爲一個報文段,一次只能發送一個;當收到這一個確認的時候,cwnd 加一,於是一次能夠發送兩個;當這兩個的確認到來的時候,每個確認 cwnd 加一,兩個確認 cwnd 加二,於是一次能夠發送四個;當這四個的確認到來的時候,每個確認 cwnd 加一,四個確認 cwnd 加四,於是一次能夠發送八個。可以看出這是指數性的增長。
漲到什麼時候是個頭呢?有一個值叫做門限(ssthresh)爲 65535 個字節,當超過這個值的時候,就要小心一點了,就不能增長那麼快了。
於是,每收到一個確認後,cwnd 增加 1/cwnd,我們接着上面的過程來,一次發送八個,當八個確認到來的時候,每個確認增加 1/8,八個確認一共 cwnd 增加 1,於是一次能夠發送九個,變成了線性增長。
但是線性增長還是增長,還是越來越多,直到發送擁塞。擁塞的一種表現形式是丟包,需要超時重傳。
有一種方式是,將 sshresh 設爲 cwnd/2,將 cwnd 設爲 1,重新開始慢啓動。。但是這種方式太激進了,將一個高速的傳輸速度一下子停了下來,會造成網絡卡頓。
快速重傳算法。當接收端發現丟了一箇中間包的時候,發送三次前一個包的 ACK,於是發送端就會快速的重傳,不必等待超時再重傳。TCP 認爲這種情況不嚴重,因爲大部分沒丟,只丟了一小部分,cwnd 減半爲 cwnd/2,然後 sshthresh = cwnd,呈線性增長。
TCP BBR 擁塞算法,它佔滿帶寬,但是不佔滿緩存,時延較低。
12. 左值引用和右值引用的區別?
-
左值存放在對象中,有持久的狀態;
-
而右值要麼是字面常量,要麼是在表達式求值過程中創建的臨時對象,沒有持久的狀態。
-
右值引用的對象,是臨時的,即將被銷燬;
如果我們將一個對象的內存空間綁定到另一個變量上,那麼這個變量就是左值引用。在建立引用的時候,我們是「將內存空間綁定」,因此我們使用的是一個對象在內存中的位置,這是一個左值。
const int& qux = 42; // OK: 42 是右值,但是編譯器可以爲它開闢一塊內存空間,綁定在 qux 上
右值引用也是引用,但是它只能且必須綁定在右值上。右值引用應用在語義移動上,語義移動避免了數據的再拷貝帶來的花銷,提供一種數據的所有權的轉交。
13. 四種 cast 強制類型轉換的區別和使用?
-
static_cast:
普通的類型轉換
static_cast<type-name>(value);
不提供運行時的檢查,要在編寫程序時確認轉換的安裝性。
(1)把子類對象的指針/引用轉換爲父類指針/引用(上行安全),這種轉換是安全的。若把父類對象的指針/引用轉換成子類指針/引用(下行不安全),這種轉換是不安全的,需要編寫程序時來確認。
(2)基本的數據類型轉換。安全由程序編寫人員保證。
-
dynamic_cast:
用於處理多態
dynamic_cast<type-name>(expression);
指針轉換失敗,則返回空指針,若引用轉換失敗,則拋出異常。
把父類對象的指針/引用轉換成子類指針/引用,即基類轉換爲派生類時使用 dynamic_cast。
-
const_cast:
用於移除類型的 const、volatile 屬性。
const_cast<type-name>(expression);
常量指針被轉換成非常量指針,並且仍然指向原來的對象;常量引用被轉換成非常量引用,並且仍然引用原來的對象。
說明:使得 *pb 成爲一個可用於修改 bar 對象值的指針,它刪除 const 標籤。
-
reinterpret_cast
reinterpret_cast<type-name>(expression);
可以轉換任何類型的指針,不安全。不改變括號中運算對象的值,而是對該對象從位模式上進行重新解釋。
14. 講一下智能指針?
-
auto_ptr
auto_ptr 會導致所有權的轉移,於此同時,原指針不再指向有效數據(後續再訪問會出現錯誤)。
-
unique_ptr
unique_ptr 是 auto_ptr 的升級,策略更加的嚴格。unique_ptr 同樣採取建立所有權(ownership)的概念,對於特定對象,只能有一個智能指針可擁有它,只有擁有對象的智能指針的構造函數會刪除該對象。
編譯階段即可發現錯誤, unique_ptr 相比 auto_ptr 有更加安全的機制
。move 函數可以移交所有權。
-
share_ptr
shared_ptr 採用引用計數(reference counting)的策略。賦值時,計數加一,指針過期時,計數減一。僅當最後一個指針過期時,才調用 delete 。
-
weak_ptr
weak_ptr 可以維持一種弱引用。
-
對象被析構了,weak_ptr 會自動等於 nullptr。
-
weak_ptr 可以還原成 shared_ptr 而不會讓引用計數錯亂。
自己動手實現智能指針:
-
模版化(Template)
-
運算符重載(operator)【* 解引用】 | 【-> 指向對象成員】 | 【bool 用在布爾表達式】
-
拷貝構造和賦值:拷貝時轉移指針所有權(捨棄 auto_ptr,選擇 unique_ptr)
-
引用計數
15. 瞭解完美轉發嗎?
一個函數某一組參數原封不動地傳遞給另一個函數。這裏不僅需要參數的值不變,而且需要參數的類型屬性(左值/右值)保持不變,這叫做 Perfect Forwarding(完美轉發)。
C++11 開始,完美轉發由 std::forward 函數實現。底層實現和引用reference 密切相關:remove_reference 、右值引用、引用聚合。
16. future 和 thread 瞭解嗎?
與併發(Concurrency)編程相關,future 是一個更高的抽象,同樣的結構,代碼將大大簡化,變量減少到只有一個未來量。
調用 async 可以獲得一個未來量。在未來量上調用 get 成員函數可以獲得結果。
17. tcp/ip 四層模型和 osi 七層模型具體是哪些?
18. 進程和線程的區別?
操作系統的設計,因此可以歸結爲三點:
(1)以多進程形式,允許多個任務同時運行;
(2)以多線程形式,允許單個任務分成不同的部分運行;
(3)提供協調機制,一方面防止進程之間和線程之間產生衝突,另一方面允許進程之間和線程之間共享資源。
a.地址空間和其它資源:進程間擁有獨立內存,進程是資源分配的基本單位;線程隸屬於某一進程,且同一進程的各線程間共享內存(資源),線程是cpu調度的基本單位。
b.通信:進程間相互獨立,通信困難,常用的方法有:管道,信號,套接字,共享內存,消息隊列等;線程間可以直接讀寫進程數據段(如全局變量)來進行通信——需要進程同步和互斥手段的輔助,以保證數據的一致性。
c.調度和切換:線程上下文切換比進程上下文切換要快。進程間切換要保存上下文,加載另一個進程;而線程則共享了進程的上下文環境,切換更快。
04|算法題
1. KMP 算法?
KMP 算法和核心在於求解模式串的 next 數組,使得模式串與主串在匹配過程中出現失配情況時,大大降低沒有必要的回退。
2. 知道二叉樹有哪些遍歷的方式嗎?
先序/中序/後序/層序遍歷。非遞歸:先序/中序/後序需要使用棧(Stack)輔助,層序遍歷需要使用隊列(Queue)輔助。
線索後遍歷(線性結構),但是改變了樹的指針結構。
4. 快速排序的思想?
快速排序是一種基於分治法的重要的排序算法,歸併排序也是基於分治技術。快速排序的核心在於切分(Partition)操作。根據切分的不同,可以分爲普通快排(單路切分)、雙路快排(雙路切分)、三路快排(大於、等於、小於三路)。
5. 冒泡排序
6. 手撕 strcpy?
注:如果 dest 事先未分配足夠的一片連續空間,則 `dest++` 可能會覆蓋其他的數據。
05|理解應用題
1. 一個結構體,能夠用 memcpy 判斷兩個結構體存的東西是不一樣的嗎?
我不知道能不能?但是可以瞭解一下 memset 和 memcpy 函數。在對結構體變量都使用 memset 初始化的前提下,使用 memcmp 函數進行一個一個字節的比較,相同則返回 0。
memset 按字節賦值,對每個字節賦值同樣的值。0 的二進制代碼爲全 0,-1 的二進制補碼全爲 1。所以一般賦初值要賦爲 0 或 -1。如果對一個整型賦值爲 1,int 佔 4 個字節,最後結果爲十進制的 16843009。
0000 0001|0000 0001|0000 0001|0000 0001
int a;
memset(&a, 1, sizeof(a)); // a is 16843009
如果有結構體成員有指針的情況,則還要再考慮,就如同深拷貝的淺拷貝問題。
memcpy sizeof(char) 等於 1,逐個字節的拷貝。
2. 知道哈希表嗎?怎麼解決衝突?如果只有 32 個槽,怎麼存放幾千個數據?
hashtable,將關鍵字 K,通過散列函數 F(K) 映射到表中一個位置上。設計優秀的哈希函數,哈希表可以實現接近於 O(1) 的查詢速度。當 F(K1) == F(K2) 時,稱爲衝突。
解決 Hash 衝突的方法有:線性探測法、二次探出法、開鏈法、再散列法。
如果只有 30 個槽,說明存在大量的衝突,平均每個槽會產生上百的衝突,此時第一級的哈希函數已經沒有多大的優化的空間了,要麼進行再 hash。我覺得采用開鏈法也是可取的,可以在每個槽後接上樹結構衝突發生時,往樹中添加即可,
採用樹結構可以保證查詢的時間複雜度爲 O(logn)。
3. 用過 shell 腳本嗎?
用過一點點,基於 Github Page 搭建了個人博客,本地編寫博客後,執行一個腳本,自動提交和部署到原廠倉庫。有一定的腳本語言基礎:Python、Lua。
4. 學習了哪些數據結構?
-
線性數據結構
動態數組:Vector 擴容/縮容
鏈表(LinkedList):頭節點、尾指針、循環鏈表
棧(Stack):鏈表棧、動態數組棧
隊列(Queue):鏈表隊列、動態數組隊列、循環隊列
-
樹結構
堆(Heap):優先隊列,最大/最小堆
二分搜索樹(BST)
平衡二叉樹(AVL)
紅黑樹(RB-Tree)
B 樹和 B+ 樹(B-Tree)
字典樹(Trie)
-
圖結構
圖的表示:鄰接矩陣、鄰接表。
-
其他數據結構
並查集(Union Find)
哈希表(Hash-table)
集合(Set):HashSet、TreeSet
映射(Map):HashMap、TreeMap
5. 你對 mfc 的印象?
微軟基礎類庫(Microsoft Foundation Classes,簡稱 MFC)。
MFC 可以用面向對象的方法來調用 Windows API,以及能夠更加敏捷的開發應用程序。
MFC 出現的時間比較早(1992),而 C++ 的第一個標準是 1998 年(C++98)。C++ 的發展比 MFC 更加迅猛發展,結果 MFC 的一些好的東西就成了它的陋習。
身邊也沒有同學學過 MFC。我對桌面程序開發有過一點點的經驗,我採用的方案的 C++ 和 Qt,做的是一個可視化的排序的窗口。
6. 用過哪些 UI 界面?
圖形化方面瞭解一點點的 Qt 和 OpenCV。但是時間比較早了,大三的時候。
7. 用過 qt 嗎?
排序算法的可視化,採用的是 C++ 和 Qt:QPainter、自動重繪函數。
8. 如何判斷大小端,手寫代碼?
在 Socket 網絡編程時,會涉及到大端序和小端序的轉換。
-
大端序(Big Endian):高位在前,低位在後。這是人類讀寫數值的方式。
-
小端序(Little Endian):地位字節在前,高位字節在後。
計算機正常的內存增長方式是從低到高(棧不是),取數據方式是從基地址根據偏移找到它們的位置。小端序第一個字節是它的低位,符號位在最後一位,在做運算時,從地位開始,最終刷新符號位,運算較爲高效。
大端序第一個字節就是高位,很容易知道它是正數還是負數,對於數值判斷會很迅速。除了計算機內部處理,其他場合幾乎都是大端序,比如網絡傳輸和文件存儲。
9. 做了哪些面向對象編程的工作?
-
大學對課程設計
-
動手實現 C++ STL 的部分容器類,遵循 OOP 思想,類的設計,方法接口設計。
05|參考
1. What is the difference between new/delete and malloc/free? - Stack Overflow
https://stackoverflow.com/questions/240212/what-is-the-difference-between-new-delete-and-malloc-free
2. 二分搜索樹(BST)的前/中/後序遍歷-遞歸+非遞歸 - 藍本本
https://zhuanlan.zhihu.com/p/100926726
3. 十大排序算法 - 藍本本
https://zhuanlan.zhihu.com/p/100435276
PS:關注微信公衆號「藍本本」,和我一起學習、進修和放縱好奇心。
題圖:Photo by Mohamed Khaled from Pexels.
Landon
——
學習、進修和放縱好奇心!
@ 藍本本
——