1. 程式人生 > >阿裏、網易和騰訊面試題 C/C++

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

不同的 三次握手 進程組 HR 是什麽 ret 區分 point 兩個

一、線程、鎖

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;
  • 精確插入
    ```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分支有什麽區別?

  • 全局變量好用麽?

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

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

  • 如何檢查內存泄漏?

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

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

圖像

視覺

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