阿里、網易和騰訊面試題 C/C++

一、線程、鎖

1、Posix Thread互斥鎖

線程鎖創建

a.靜態創建

pthread_mutex_tmutex = PTHREAD_MUTEX_INITIALIZER;

b.動態創建

pthread_mutex_tmutex = pthread_mutex_init(pthread_mutex_tmutex,constpthread_mutexattr_tmutexattr);

互斥鎖的屬性

互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。

a.PTHREAD_MUTEX_TIMED_NP這是缺省值,也就是普通鎖。當一個線程加鎖以後,其餘請求鎖的線程將形成一個等待隊列,並在解鎖後按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。

b.PTHREAD_MUTEX_RECURSIVE_NP嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。 c. PTHREAD_MUTEX_ERRORCHECK_NP檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。

d.PTHREAD_MUTEX_ADAPTIVE_NP適應鎖,動作最簡單的鎖類型,僅等待解鎖後重新競爭。

鎖操作

int pthread_mutex_lock(pthread_mutex_t *mutex)

int pthread_mutex_unlock(pthread_mutex_t *mutex)

int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock() 與pthread_mutex_lock()類似,不同的是在鎖已經被佔據時返回EBUSY而不是掛起等待

Linux線程

Linux系統下的多線程遵循POSIX線程接口,稱爲pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h

線程創建

int pthread_create((pthread_t *thread,constpthread_attr_t *__attr,  void (__start_routine)(void *), void *__arg))

第一個參數爲指向線程標識符的指針,第二個參數用來設置線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。返回0表示創建成功,否則不成功。

pthread_tid;pthread_create(&id,NULL,(void*) thread,NULL);

線程等待

int pthread_join __P ((pthread_t __th, void **__thread_return))

第一個參數爲被等待的線程標識符,第二個參數爲一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束爲止,當函數返回時,被等待線程的資源被收回。

線程終止

一個線程的結束有兩種途徑,一種是象我們上面的例子一樣,函數結束了,調用它的線程也就結束了;另一種方式是通過函數pthread_exit來實現。

void pthread_exit ((void *__retval)) attribute ((noreturn))

唯一的參數是函數的返回代碼。

2、死鎖及其預防和處理方法

死鎖的規範定義如下:如果一個進程在等待只能由該進程停止才能引發的事件,那麼該進程就是死鎖的。

(1)產生死鎖的原因

因爲系統資源不足。

進程運行推進的順序不合適。

資源分配不當等。

(2)產生死鎖的四個必要條件

互斥條件:每個資源要麼已經分配給了一個進程,要麼就是可用的。

佔有和等待條件:已經得到了某個資源的進程可以再請求新的資源。

不可搶佔條件:已經分配給一個進程的資源不能強制性地被搶佔,只能被佔有它的進程顯式地釋放;

環路等待條件:死鎖發生時,系統中一定有兩個或者兩個以上的進程組成的一條環路,該環路中的每個進程都在等待着下一個進程所佔有的資源。

這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

(3)處理死鎖的四種策略:

鴕鳥策略(忽略死鎖);

檢測死鎖並恢復;

仔細對資源進行分配,動態地避免死鎖;

通過破壞引起死鎖的四個必要條件之一,防止死鎖的產生。

(4)死鎖避免

死鎖避免的主要算法是基於一個安全狀態 的概念。在任何時刻,如果沒有死鎖發生,並且即使所有進程忽然請求對資源的最大請求,也仍然存在某種調度次序能夠使得每一個進程運行完畢,則稱該狀態是安全的。從安全狀態出發,系統能夠保證所有進程都能完成,而從不安全狀態出發,就沒有這樣的保證。

銀行家算法 :判斷對請求的滿足是否會進入不安全狀態,如果是,就拒絕請求,如果滿足請求後系統仍然是安全的,就予以分配。不安全狀態不一定引起死鎖,因爲客戶不一定需要其最大貸款額度。

二、C++

  1. 虛函數的實現原理

虛函數表的創建和繼承

a. 基類的虛函數表的創建:首先在基類聲明中找到所有的虛函數,按照其聲明順序,編碼0,1,2,3,4……,然後按照此聲明順序爲基類創建一個虛函數表,其內容就是指向這些虛函數的函數指針,按照虛函數聲明的順序將這些虛函數的地址填入虛函數表中。例如若show放在虛函數聲明的第二位,則在虛函數表中也放在第二位。 b. 對於子類的虛函數表:首先將基類的虛函數表複製到該子類的虛函數表中。若子類重寫了基類的虛函數show,則將子類的虛函數表中存放show的函數地址(未重寫前存放的是子類的show虛函數的函數地址)更新爲重寫後函數的函數指針。若子類增加了一些虛函數的聲明,則將這些虛函數的地址加到該類虛函數表的後面。

通過虛函數表訪問對象的方法

當執行Base->show()時,要觀察show在Base基類中聲明的是虛函數還是非虛函數。若爲虛函數將使用動態聯編(使用虛函數表決定如何調用函數),若爲非虛函數則使用靜態聯編(根據調用指針Base的類型來確定調用哪個類的成員函數)。此處假設show爲虛函數,首先:由於檢查到Base指針類型所指的類Base中show定義爲虛函數,因此找到Base所指的對象,訪問對象得到該對象所屬類的虛函數表地址。其次:查找show在Base類中聲明的位置在Base類中所有虛函數聲明中的位序。然後到Base所指對象的所屬類的虛函數表中訪問該位序的函數指針,從而得到要執行的函數。

爲什麼要把析構函數定義爲虛函數?

new出來的是子類son的對象,採用一個父類father的指針來接收,故在析構的時候,編譯器因爲只知道這個指針是父類的,所以只將父類部分的內存析構了,而不會去析構子類的內存,就造成了內存泄露。基類析構函數定義爲虛擬函數的時候,在子類的對象的首地址開始會有一塊基類的虛函數表拷貝,在析構子類對象的時候會刪除此虛函數表,此時會調用基類的析構函數,所以此時內存是安全的。

爲什麼虛函數比普通函數慢?

因爲虛函數要通過查找虛函數表的方法訪問。

爲什麼構造函數不能是虛函數?

構造函數不可以是虛函數的,這個很顯然,畢竟虛函數都對應一個虛函數表,虛函數表是存在對象內存空間的,如果構造函數是虛的,就需要一個虛函數表來調用,但是類還沒實例化沒有內存空間就沒有虛函數表,這根本就是個死循環。

內聯函數、構造函數和靜態成員函數可以定義爲虛函數麼?爲什麼?

基類指針指向派生類時如何知道指向的是哪一個派生類?

  1. 正確區分重載、重寫和隱藏。

注意三個概念的適用範圍:處在同一個類中的函數纔會出現重載。處在父類和子類中的函數纔會出現重寫和隱藏。重載:同一類中,函數名相同,但參數列表不同。重寫:父子類中,函數名相同,參數列表相同,且有virtual修飾。隱藏:父子類中,函數名相同,參數列表相同,但沒有virtual修飾;函數名相同,參數列表不同,無論有無virtual修飾都是隱藏

基類中: (1)virtualvoidshow();//是虛函數(2)voidshow(int);//不是虛函數

子類中:(3)voidshow();//是虛函數(4)voidshow(int);//不是虛函數

1,2構成重載,3,4構成重載,1,3構成重寫,2,4構成隱藏。另外2,3也會構成隱藏,子類對象無法訪問基類的void show(int)成員方法,但是由於子類中4的存在導致了子類對象也可以直接調用void show(int)函數,不過此時調用的函數不在是基類中定義的void show(int)函數2,而是子類中的與3重載的4號函數。

3、C對象和C++對象的區別(struct和class)

C++中的struct對C中的struct進行了擴充,它已經不再只是一個包含不同數據類型的數據結構了,它已經獲取了太多的功能。struct能包含成員函數嗎? 能!struct能繼承嗎? 能!!struct能實現多態嗎? 能!!!

a. 默認的繼承訪問權限。struct是public的,class是private的。

b. struct作爲數據結構的實現體,它默認的數據訪問控制是public的,而class作爲對象的實現體,它默認的成員變量訪問控制是private的。

三、網絡

1、TCP和UDP

tcp是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。udp(用戶數據報協議)傳輸層協議,提供面向操作的簡單不可靠的非連接傳輸層服務,面向報文。

區別 a. tcp是基於連接的,可靠性高;udp是基於無連接的,可靠性較低; b. 由於tcp是連接的通信,需要有三次握手、重新確認等連接過程,會有延時,實時性差;同時過程複雜,也使其易於被攻擊;而udp無連接,無建立連接的過程,因而實時性較強,也稍安全; c. 在傳輸相同大小的數據時,tcp首部開銷20字節;udp首部開銷只有8個字節,tcp報頭比udp複雜,故實際包含的用戶數據較少。tcp無丟包,而udp有丟包,故tcp開銷大,udp開銷較小;

d .每條tcp連接只能是點到點的;udp支持一對一、一對多、多對一、多對多的交互通信。

TCP的三次握手

第一次握手:客戶端發送一個tcp的syn標誌位置爲1的包(連接請求),指明客戶打算連接服務器的端口;SYN=1,seq=client_isn

第二次握手:當服務器收到連接請求之後,返回確認包(ack)應答,即將syn和ack標誌位同時致爲1(授予連接),併爲這次連接分配資源;SYN=1,ACK=1,seq = server_isn

第三次握手:客戶端收到服務器的授予連接請求之後,再次發送確認包(ack)(syn標誌位爲0,ack標誌位爲1),並分配資源,這樣tcp就建立連接了SYN=0,ACK=1,seq=client_isn+1

TCP和UDP的數據結構

a.TCP

structTCP_HEADER{shortm_sSourPort;// 源端口號16bitshortm_sDestPort;// 目的端口號16bitunsignedintm_uiSequNum;// 序列號32bitunsignedintm_uiAcknowledgeNum;// 確認號32bitshortm_sHeaderLenAndFlag;// 前4位:TCP頭長度;中6位:保留;後6位:標誌位shortm_sWindowSize;// 窗口大小16bitshortm_sCheckSum;// 檢驗和16bitshortm_surgentPointer;// 緊急數據偏移量16bit}

b.UDP

structUDP_HEADER{shortm_sSourPort;// 源端口號16bitshortm_sDestPort;// 目的端口號16bitshortm_size;//長度16bitshortm_sCheckSum;// 檢驗和16bit}

四、組合數學

五、數據庫

1、範式

常見的範式:第一範式(1NF),第二範式(2NF),第三範式(3NF)。下面就簡單介紹下這三個範式。

| name | tel | age |

| :–: | :-----: | :–: |

| 大寶 | 136 | 22 |

| 小明 | 132,158 | 21 |

第一範式(1NF):強調的是列的原子性,即列(屬性)不能夠再分成其他幾列。

考慮這樣一個表:【聯繫人】(姓名,性別,電話)

如果在實際場景中,一個聯繫人有家庭電話和公司電話,那麼這種表結構設計就沒有達到 1NF。要符合 1NF 我們只需把列(電話)拆分,即:【聯繫人】(姓名,性別,家庭電話,公司電話)。1NF 很好辨別,但是 2NF 和 3NF 就容易搞混淆。

第二範式(2NF):首先是 1NF,另外包含兩部分內容,一是表必須有一個主鍵;二是不能只依賴於主鍵的一部分。

學生課程 老師 老師職稱 教材 教室 上課時間

小明一年級語文(上) 大寶 副教授 《小學語文1》 101 14:30

一個學生上一門課,一定在特定某個教室。所以有(學生,課程)->教室

一個學生上一門課,一定是特定某個老師教。所以有(學生,課程)->老師

一個學生上一門課,他老師的職稱可以確定。所以有(學生,課程)->老師職稱

一個學生上一門課,一定是特定某個教材。所以有(學生,課程)->教材

一個學生上一門課,一定在特定時間。所以有(學生,課程)->上課時間

因此(學生,課程)是一個碼。

◆ 第三範式(3NF):首先是 2NF,另外非主鍵列必須直接依賴於主鍵,不能存在傳遞依賴。即不能存在:非主鍵列 A 依賴於非主鍵列 B,非主鍵列 B 依賴於主鍵的情況。

六、數據結構

1、鏈表

(1)鏈表建立、插入、刪除

建立

Node *L;L =newNode; L =NULL;//不帶頭節點的初始化L->=NUll;//帶頭結點的初始化

頭尾插入元素

p->next = L; L = p;//頭插法

Node *tail =newNode;//尾插法Node *cur = L;while(cur->next!=NULL){ cur = cur->next;}tail = cur;tail->next = p;p->next =NULL;

精確插入

boolListInsert(List *L,intpos,Node *node){inti=1;Node *cur=newNode;cur = L;while(cur->next != Null && i != pose){ cur = cur->next; i++; }if(cur->next =NULL)returnfalse;else{ node->next = cur->next; cur ->next = node;returntrue; }}

精確刪除

boolListDelete(List *L,intpos){inti=1;Node *cur=newNode;cur = L;while(cur->next != Null && i != pose){ cur = cur->next; i++; }if(cur->next =NULL||i>pos)returnfalse;else{ cur->next = cur->next->nextdeletecur->nextreturntrue; }}

(2)鏈表反轉,環、

反轉

boolListReverse(List *L){ Node cur = L->next; Node *temp =newNode;while(cur->next!=NULL){ temp = cur; L->next->next = L; L->next = temp->next; cur = L->next; }returntrue;}

算法的思想是設定兩個指針p, q,其中p每次向前移動一步,q每次向前移動兩步。那麼如果單鏈表存在環,則p和q相遇;否則q將首先遇到NULL。

如何定義一個只能在棧上或者堆上生成對象的類?

sizeof(類),如何計算類的大小?

sizeof(結構體),如何計算結構體的大小?

結構體和類有什麼區別?

extern“C”有什麼作用?原理是什麼?

const修飾的變量和#define有什麼區別?

static有什麼作用?如何改變變量的生命週期和作用域?

volitale什麼作用?

new 和malloc有什麼不一樣?

指針和引用的區別?

STL容器有哪些?

vector內部數據結構是什?List/Map/Queue

STL,vector和List有什麼不一樣?map是用什麼數據結構實現的?

switch和if分支有什麼區別?

全局變量好用麼?

面向對象有哪些特點?如何體現?

面向對象的設計原則有哪些?

如何檢查內存泄漏?

從源代碼到可執行二進制文件,要經過哪些過程?

二維數組爲題

http://c.biancheng.net/cpp/html/79.html

//這是一個關於二維數組指針的問題。//假設定義一個int型的二維數組指針。intSec[2][3]={4,6,3,7,2,7};int**P = Sec; **p 等價 Sec[0][0] *p 等價 Sec[0] (p+n) 等價 Sec[n] ((p+n)+m) 等價 Sec[n][m]//所以++m,爲行數增加爲afternoon一行,而m輸出整行。

C++中不能重載的運算符:“?:”、“.”、“::”、“sizeof”和”.*”

各個容器的內部實現數據結構

數組越界

死循環

棧溢出

內存泄露

首先所謂的接口是指只包含純虛函數的抽象類,和普通的抽象類含不一樣
文末也分享給大家也是Linuxc++開發的學習資料

主要有C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK技術,面試技巧方面的資料技術討論。

感興趣的朋友戳這裏:等待領取

發佈了41 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章