阿里、網易和騰訊面試題 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而不是掛起等待
  1. 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)產生死鎖的四個必要條件

  1. 互斥條件:每個資源要么已經分配給了一個進程,要么就是可用的。
  2. 占有和等待條件:已經得到了某個資源的進程可以再請求新的資源。
  3. 不可搶占條件:已經分配給一個進程的資源不能強制性地被搶占,只能被占有它的進程顯式地釋放;
  4. 環路等待條件:死鎖發生時,系統中一定有兩個或者兩個以上的進程組成的一條環路,該環路中的每個進程都在等待着下一個進程所占有的資源。

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

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

  1. 鴕鳥策略(忽略死鎖);
  2. 檢測死鎖並恢復;
  3. 仔細對資源進行分配,動態地避免死鎖;
  4. 通過破壞引起死鎖的四個必要條件之一,防止死鎖的產生。

(4)死鎖避免

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

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

二、C++

1. 虛函數的實現原理

虛函數表的創建和繼承

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

  • 通過虛函數表訪問對象的方法
    當執行Base->show()時,要觀察showBase基類中聲明的是虛函數還是非虛函數。若為虛函數將使用動態聯編(使用虛函數表決定如何調用函數),若為非虛函數則使用靜態聯編(根據調用指針Base的類型來確定調用哪個類的成員函數)。此處假設show為虛函數,首先:由於檢查到Base指針類型所指的類Baseshow定義為虛函數,因此找到Base所指的對象,訪問對象得到該對象所屬類的虛函數表地址。其次:查找showBase類中聲明的位置在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.TCP
struct 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;
  • 精確插入
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;
  }
}

  • 精確刪除
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)鏈表反轉,環、

  • 反轉
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分支有什么區別?

  • 全局變量好用么?

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

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

  • 如何檢查內存泄漏?

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

  1. 二維數組為題
    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輸出整行。
  1. C++中不能重載的運算符:“?:”、“.”、“::”、“sizeof”和”.*”
  2. 各個容器的內部實現數據結構
    1. 數組越界
      死循環
      棧溢出
      內存泄露
  3. 首先所謂的接口是指只包含純虛函數的抽象類,和普通的抽象類含不一樣

圖像

視覺


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM