阿裏、網易和騰訊面試題 C/C++
一、線程、鎖
1、Posix Thread互斥鎖
線程鎖創建
a.靜態創建pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
b.動態創建
pthread_mutex_t mutex = pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
- 互斥鎖的屬性
互斥鎖的屬性在創建鎖的時候指定,在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, const pthread_attr_t *__attr, void *(*__start_routine)(void *), void *__arg))
第一個參數為指向線程標識符的指針,第二個參數用來設置線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。返回0表示創建成功,否則不成功。
pthread_t id; 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的指針來接收,故在析構的時候,編譯器因為只知道這個指針是父類的,所以只將父類部分的內存析構了,而不會去析構子類的內存,就造成了內存泄露。基類析構函數定義為虛擬函數的時候,在子類的對象的首地址開始會有一塊基類的虛函數表拷貝,在析構子類對象的時候會刪除此虛函數表,此時會調用基類的析構函數,所以此時內存是安全的。
為什麽虛函數比普通函數慢?
因為虛函數要通過查找虛函數表的方法訪問。
為什麽構造函數不能是虛函數?
構造函數不可以是虛函數的,這個很顯然,畢竟虛函數都對應一個虛函數表,虛函數表是存在對象內存空間的,如果構造函數是虛的,就需要一個虛函數表來調用,但是類還沒實例化沒有內存空間就沒有虛函數表,這根本就是個死循環。
- 內聯函數、構造函數和靜態成員函數可以定義為虛函數麽?為什麽?
基類指針指向派生類時如何知道指向的是哪一個派生類?
2. 正確區分重載、重寫和隱藏。
註意三個概念的適用範圍:處在同一個類中的函數才會出現重載。處在父類和子類中的函數才會出現重寫和隱藏。
重載:同一類中,函數名相同,但參數列表不同。
重寫:父子類中,函數名相同,參數列表相同,且有virtual修飾。
隱藏:父子類中,函數名相同,參數列表相同,但沒有virtual修飾;函數名相同,參數列表不同,無論有無virtual修飾都是隱藏
基類中:
(1) virtual void show(); //是虛函數
(2) void show(int); //不是虛函數
子類中:(3) void show(); //是虛函數
(4) void show(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.TCPstruct TCP_HEADER { short m_sSourPort; // 源端口號16bit short m_sDestPort; // 目的端口號16bit unsigned int m_uiSequNum; // 序列號32bit unsigned int m_uiAcknowledgeNum; // 確認號32bit short m_sHeaderLenAndFlag; // 前4位:TCP頭長度;中6位:保留;後6位:標誌位 short m_sWindowSize; // 窗口大小16bit short m_sCheckSum; // 檢驗和16bit short m_surgentPointer; // 緊急數據偏移量16bit }
b.UDP
struct UDP_HEADER { short m_sSourPort; // 源端口號16bit short m_sDestPort; // 目的端口號16bit short m_size; //長度16bit short m_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 = new Node; L = NULL;//不帶頭節點的初始化 L->=NUll;//帶頭結點的初始化
頭尾插入元素
p->next = L; L = p;//頭插法
Node *tail = new Node;//尾插法 Node *cur = L; while(cur->next!=NULL){ cur = cur->next; } tail = cur; tail->next = p; p->next = NULL;
精確插入
```C++
bool ListInsert(List L,int pos,Node node){
int i=1;Node *cur=new Node;cur = L;
while(cur->next != Null && i != pose){
cur = cur->next;
i++;
}
if(cur->next = NULL) return false;
else{
node->next = cur->next;
cur ->next = node;
return true;
}
}
- 精確刪除
C++
bool ListDelete(List L,int pos){
int i=1;Node cur=new Node;cur = L;
while(cur->next != Null && i != pose){
cur = cur->next;
i++;
}
if(cur->next = NULL||i>pos) return false;
else{
cur->next = cur->next->next
delete cur->next
return true;
}
}
####(2)鏈表反轉,環、 - 反轉
C++
bool ListReverse(List L){
Node cur = L->next;
Node temp = new Node;
while(cur->next!=NULL){
temp = cur;
L->next->next = L;
L->next = temp->next;
cur = L->next;
}
return true;
}
```
環
算法的思想是設定兩個指針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型的二維數組指針。 int Sec[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”和”.*”
- 各個容器的內部實現數據結構
- 數組越界
死循環
棧溢出
內存泄露
- 數組越界
首先所謂的接口是指只包含純虛函數的抽象類,和普通的抽象類含不一樣
圖像
視覺
阿裏、網易和騰訊面試題 C/C++