1. 程式人生 > >C++ 面試題彙總(二)

C++ 面試題彙總(二)

1. Epoll、poll及select的區別
相同點:三者均能夠提供多路I/O複用的解決方案,在linux平臺上都可以支援。
不同點:
<1> Select的本質是設定或者檢查存放檔案控制代碼(fd)的標誌位的資料結構來進行下一步處理。其缺陷是單個程序所開啟的fd是有限制的,預設值是1024;並且對socket掃描是線性掃描,即採用輪詢的方式,效率較低。當維護一個存放大量fd的資料結構時,這樣會使得使用者空間和核心空間在傳遞該結構時複製開銷大。
<2> poll本質和select沒有什麼區別。Poll最大的特點是“水平觸發”,如果報告了fd之後,沒有被處理,下次poll會再次報告該fd。
<3> epoll支援水平觸發和邊緣觸發(LT和ET),最大的特點在於邊緣觸發, 它只告訴程序哪些fd剛剛變為就緒態,並且只會通知一次。沒有最大併發連線的限制,能開啟的fd遠遠大於1024。效率大大提升,不在是輪詢方式,不會隨著fd數目的增加效率下降。

2. 執行緒、程序、多執行緒、併發及並行的概念及區別。
執行緒:程式的一個執行。
程序:CPU的基本排程單位。

執行緒與程序之間的關係:
1> 一個執行緒只能屬於一個程序,而一個程序可以有多個執行緒,但至少有一個執行緒。執行緒是作業系統可識別的最小執行和基本排程單位;
2> 資源分配給程序,同一程序的所有執行緒共享該程序的所有資源。同一程序中的多個執行緒共享程式碼段、資料段及擴充套件段。但是每個執行緒擁有自己的執行時段,用來存放所有區域性變數和臨時變數;
3> 執行緒在執行過程中,需要協作同步。不同程序的執行緒要利用訊息通訊的方式實現同步。

多執行緒:多執行緒是多工處理的一種特殊形式。多工處理允許讓電腦同時執行兩個或兩個以上的程式。

併發:指的是兩個或多個獨立的活動在同一時段內發生。(同一時間段內可以交替處理多個操作)

並行:指的是計算機在同一時刻,在某個時間點上處理兩個或以上的操作。(注重同時性)

多執行緒與併發的區別:
併發與多執行緒之間的關係就是目的與手段之間的關係。多執行緒就是將原本可能是序列的計算改為並行的手段、途徑或者模型,有時候我們稱多執行緒程式設計也為併發程式設計。當然,目的與手段往往是一對多的關係。併發程式設計還有其它方式,例如函數語言程式設計。

  1. 多執行緒變成下什麼情況下需要加volitile關鍵字?
    Volatile個關鍵字並不是用來解決多執行緒競爭問題,而是用來修飾一些因為程式不可控因素導致變化的變數,比如訪問底層硬體裝置的變數,以提醒編譯器不要對該變數進行優化。

如果對共享變數使用volitile關鍵字,可能存在競爭的操作中不加鎖或使用原子操作對解決多執行緒競爭並沒有什麼作用。這是因為volitile並不能保證操作的原子性,在讀入、寫入變數的過程中仍然可能被其他執行緒打斷導致意外結果。所以不建議加volite關鍵字。

4. 多執行緒實現的方式?同步機制?
多執行緒實現有四種方式,分別是:
1> 繼承QThread類,重寫QThread::run()函式;
2> 繼承QRunnable類(是所有可執行物件的基類),代表了由run()函式表示的一個任務或一段可執行的程式碼。使用該類通常情況下需要藉助QThreadPool來再另一個獨立的執行緒中執行該程式碼。如果QRunnable物件的autoDelete()設定為true的話,QRunnable會在run()娙結束後自動刪除該物件(前提條件是必須要在QThreadPool::start()之前設定)。如果有用到訊號槽,還得同時繼承QObject類;
3> 繼承QObject類,使用moveToRhread()方法;
4> 使用QtConcurrent::run()啟動新執行緒。

同步機制:
執行緒之間通訊的兩個基本問題是互斥和同步

執行緒同步:是執行緒之間所具有的的一種制約方式,一個執行緒的執行依賴另一個執行緒的訊息,當它沒有等到另一個執行緒的訊息時應等待,知道訊息到達時才被喚醒。

執行緒互斥:是對共享的作業系統資源,在哥哥執行緒訪問時的排他性。當有若干個執行緒都是用同一共享資源時,任何時刻只允許一個執行緒區使用,其它執行緒必須等待,直到佔用資源釋放後。

執行緒互斥是一種特殊的執行緒同步。實際上,互斥和同步對應著執行緒通訊發生的兩種情況:
a. 當有多個執行緒訪問共享資源而不使資源被破壞時;
b. 當一個執行緒將某個任務已完成情況通知給另外一個執行緒或者多個執行緒時;

WIN32系統中,同步機制主要有一下4種:
1> 事件(Event)
2> 訊號量(semaphore)
3> 互斥量(mutex)
4> 臨界區(Critical section)

5. 如何理解互斥鎖、條件鎖(條件變數)、讀寫鎖及自旋鎖?
互斥鎖:
用於保護臨界區,以保證任何時刻都只有一個執行緒在執行臨界區中的程式碼。如果多個執行緒在同一臨界區內活動,就有可能產生競態條件導致錯誤,其中包含遞迴鎖(同一個執行緒多次獲得該鎖,別的執行緒必須等待該執行緒釋放所有次數的鎖才可獲得)和非遞迴鎖。最常見的互斥場景就是多個執行緒訪問共享資源。

讀寫鎖:
從廣義上說,也可以認為是一種共享的互斥鎖,可以多個執行緒進行讀操作,但是寫操作必須單獨進行,不可多寫、不可邊讀邊寫。

條件變數:
允許執行緒以一種無競爭的方式等待某個條件的發生。當該條件沒有發生的時候,執行緒一直處於休眠狀態。當被去他執行緒通知條件已經發生時,執行緒才會被喚醒從而繼續進行下去。

自旋鎖:
當要獲取一把自旋鎖的時候又被別的執行緒持有時,不斷迴圈的去檢索是否可以獲得自旋鎖,一直佔CPU記憶體。

6. Socket套接字在多執行緒傳送資料時需要加鎖嗎?
1> 對於UDP,多執行緒讀寫同一個socket不用加鎖,不過更好的方法是每個執行緒都有自己的socket,避免競爭,可以用SO_REUSEPORT來實現這一點;
2> 對於TCP,通常多執行緒讀寫同一個socket是錯誤的設計,因為有short write的可能。假如加鎖,而又發生short write,必須等到整條資訊傳送完才能解鎖。

總而言之,對於UDP,加鎖是多餘的;對於TCP,加鎖是錯誤的。

7. Fork與vfork區別?exit()與_exit() 區別?
Fork與vfork區別:
A. 前者是子程序拷貝父程序的資料段、程式碼段。相反,後者是子程序和父程序的共享資料段;
B. 前者父子程序的執行次序不確定,後者是先保證子程序先執行,在呼叫exec和exit之前與父程序是共享的,之後父程序資料才可被呼叫執行;
C. vfork()保證子程序先執行,在它呼叫exec和exit之後父程序才可能被排程執行。如果在呼叫兩函式之前子程序依賴父程序的進一步動作,則會導致死鎖。

exit()與_exit() 區別:
前者是在呼叫exit系統呼叫前要檢查檔案的開啟情況,把檔案緩衝區內的內容寫會檔案;而後者是直接退出程序,清除其記憶體空間。

8. 執行緒安全與執行緒不安全
執行緒安全就是多執行緒訪問時,採用了加鎖機制,當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問知道該執行緒讀取完畢,其他執行緒才可使用。不會出現資料不一致或者資料汙染。

執行緒不安全就是不聽資料訪問保護,有可能出現多個執行緒先後更改資料造成資料被汙染。
9. 死鎖的四個必要條件?
死鎖:多個併發程序因爭奪系統資源而產生相互等待現象。
本質原因:系統資源有限;程序推進順序不合理。
必要條件:互斥;佔有且等待;不可搶佔;迴圈等待。
10. 如何編寫套接字?
套接字socket程式設計有三種方式,流式套接字、資料報套接字及原始套接字。基於TCP的socket程式設計採用流式套接字。

伺服器端一般步驟:
1> 建立一個socket,用函式socket();
2> 設定socket屬性,用函式setsockopt();
3> 繫結IP地址、埠等資訊到socket上,用函式bind();
4> 開啟監聽,用函式listen();
5> 接收客戶端上來的連線,用函式accept();
6> 收發資料,用函式send()和recv(),或者read()和write();
7> 關閉網路連線
8> 關閉監聽。

客戶端一般步驟:
1> 建立一個socket,用函式socket(); 2> 設定socket屬性,用函式setsockopt();* 可選 3> 繫結IP地址、埠等資訊到socket上,用函式bind();* 可選 4> 設定要連線的對方的IP地址和埠等屬性; 5> 連線伺服器,用函式connect(); 6> 收發資料,用函式send()和recv(),或者read()和write(); 7> 關閉網路連線;
11. 說一下TCP三次握手、四次揮手全過程理解
TC三次握手四次揮手全過程理解

12. Main函式執行前後還執行什麼程式碼?
Main函式執行之前,主要是初始化系統相關資源:
1> 設定棧指標;
2> 初始化static靜態和global全域性變數;
3> 將未初始化的全域性變數賦初值;
4> 執行全域性構造器;
Main函式執行之後:
1> 全域性物件的解構函式會在main函式之後執行。
13. C++如何阻止一個類被例項化?一般在什麼時候將建構函式宣告為private?
1> 將類定義為抽象類或者將建構函式宣告為private;
2> 不允許類外部建立類物件,只能內部建立物件。
14. STL中的vector容器是如何擴容的?
1> 計算容器大小的函式:
Size(): 返回當前vector元素的個數;
Capacity() : 返回當前vector中最大儲存元素的個數。

2> 產生擴容的情況
A. 當新增元素時導致擴容;
B. 使用reserve或者resize函式導致的擴容(前者擴大的只是容器的預留空間,空間內不正真建立元素物件;後者改變容器大小並建立物件)
3> 容器的回收
使用clear()和erase()兩個函式只是清空元素,並不回收記憶體;
先使用clear()在使用swap(),釋放空間並回收記憶體。
15. Vector中的earse()函式在使用的時候需要注意什麼?
Vector::earse()表示從指定容器刪除指定位置的元素或某段範圍內的元素。
如果是從指定容器刪除指定位置的元素,那麼返回值是一個迭代器,指向被刪除元素的下一個元素;

如果是從指定容器刪除某段範圍內的元素時,返回值也是一個迭代器,指向最後一個刪除元素的下一個元素。

16. C++中記憶體洩漏的幾種情況以及解決方法?
1> 在類的建構函式和解構函式中沒有匹配的呼叫new和delete;
2> 沒有正確的清楚巢狀的物件指標;
3> 在釋放物件陣列時,delete未加方括號;
4> 指向物件的指標陣列不等同於物件陣列(解決方法,通過一個迴圈將每個物件釋放了,然後再把指標釋放了);
5> 缺少拷貝建構函式(按值傳遞會呼叫拷貝建構函式,引用傳遞不會呼叫)
6> 缺少過載賦值運算子;
7> 沒有將基類的解構函式定義為虛擬函式。

17. 堆疊溢位的原因以及解決方法?
棧溢位是由於C語言系列沒有內建檢查機制來確保複製到緩衝區的資料不得大於緩衝區的大小,因此資料足夠大時,將會溢位緩衝區的範圍;

堆溢位的產生是由於過多的函式呼叫,導致呼叫堆疊無法容納這些呼叫的返回地址,一般在遞迴中產生。
18. TCP與UDP的區別?
1> 基於連線與無連線;
2> 對系統資源的要求TCP較多,UDP較少;
3> UDP程式結構較簡單;
4> 流模式與資料報模式;
5> TCP保證資料的正確性,UDP可能丟失,TCP保證資料順序,UDP不保證。
19. 為什麼建構函式不能宣告為虛擬函式?
1> 構造一個物件的時候,必須知道物件的實際型別,而虛擬函式行為是在執行期間確定實際型別的。而在構造一個物件時,由於物件還未構造成功。編譯器無法知道物件 的實際型別,是該類本身,還是該類的一個派生類,或是更深層次的派生類。無法確定。。。 2> 虛擬函式的執行依賴於虛擬函式表。而虛擬函式表在建構函式中進行初始化工作,即初始化vptr,讓他指向正確的虛擬函式表。而在構造物件期間,虛擬函式表還沒有被初 始化,將無法進行。
20. 多執行緒情況下,Qt中的訊號槽分別在什麼執行緒中執行,如何控制?
通過connect函式的第五個引數connectType來控制。 connect用於連線qt的訊號和槽,在qt程式設計過程中不可或缺。它其實有第五個引數,只是一般使用預設值,在滿足某些特殊需求的時候可能需要手動設定。 Qt::AutoConnection: 預設值,使用這個值則連線型別會在訊號傳送時決定。如果接收者和傳送者在同一個執行緒,則自動使用Qt::DirectConnection型別。如果接收者和傳送者不在一個執行緒,則自動使用Qt::QueuedConnection型別。 Qt::DirectConnection:槽函式會在訊號傳送的時候直接被呼叫,槽函式運行於訊號傳送者所線上程。效果看上去就像是直接在訊號傳送位置呼叫了槽函式。這個在多執行緒環境下比較危險,可能會造成奔潰。 Qt::QueuedConnection:槽函式在控制回到接收者所線上程的事件迴圈時被呼叫,槽函式運行於訊號接收者所線上程。傳送訊號之後,槽函式不會立刻被呼叫,等到接收者的當前函式執行完,進入事件迴圈之後,槽函式才會被呼叫。多執行緒環境下一般用這個。 Qt::BlockingQueuedConnection:槽函式的呼叫時機與Qt::QueuedConnection一致,不過傳送完訊號後傳送者所線上程會阻塞,直到槽函式執行完。接收者和傳送者絕對不能在一個執行緒,否則程式會死鎖。在多執行緒間需要同步的場合可能需要這個。 Qt::UniqueConnection:這個flag可以通過按位或(|)與以上四個結合在一起使用。當這個flag設定時,當某個訊號和槽已經連線時,再進行重複的連線就會失敗。也就是避免了重複連線。