1. 程式人生 > >收藏的C++技術面試 -- 24K純技術乾貨(解答)

收藏的C++技術面試 -- 24K純技術乾貨(解答)

https://blog.csdn.net/shanghairuoxiao/article/details/72876248

C和C++語言基礎
參考書籍:《C++ primer》,《effective C++》,《STL原始碼解析》,《深度搜索C++物件模型》

extern關鍵字作用

extern宣告變數在在外部定義?
extern修飾函式?
extern C的作用?用法?
static關鍵字作用

static修飾區域性變數?
static全域性變數?(限定變數在一個編譯單元內,一個編譯單元就是指一個cpp和它包含的標頭檔案,這個回答可以結合編譯需要經歷的幾個過程來答)
static修飾普通函式?
static修飾成員變數?
static修飾成員函式?
volatile是幹啥的

訪問暫存器要比訪問記憶體要塊,因此CPU會優先訪問該資料在暫存器中的儲存結果,但是記憶體中的資料可能已經發生了改變,而暫存器中還保留著原來的結果。為了避免這種情況的發生將該變數宣告為volatile,告訴CPU每次都從記憶體去讀取資料。
一個引數可以即是const又是volatile的嗎?可以,一個例子是隻讀狀態暫存器,是volatile是因為它可能被意想不到的被改變,是const告訴程式不應該試圖去修改他。
說說const的作用,越多越好

const修飾全域性變數;
const修飾區域性變數;
const修飾指標,const int *;
const修飾指標指向的物件, int * const;
const修飾引用做形參;
const修飾成員變數,必須在建構函式列表中初始化;
const修飾成員函式,說明該函式不應該修改非靜態成員,但是這並不是十分可靠的,指標所指的非成員物件值可能會被改變
new與malloc區別

new分配記憶體按照資料型別進行分配,malloc分配記憶體按照大小分配;
new不僅分配一段記憶體,而且會呼叫建構函式,但是malloc則不會。new的實現原理?但是還需要注意的是,之前看到過一個題說int* p = new int與int* p = new int()的區別,因為int屬於C++內建物件,不會預設初始化,必須顯示呼叫預設建構函式,但是對於自定義物件都會預設呼叫建構函式初始化。翻閱資料後,在C++11中兩者沒有區別了,自己測試的結構也都是為0;
new返回的是指定物件的指標,而malloc返回的是void*,因此malloc的返回值一般都需要進行型別轉化;
new是一個操作符可以過載,malloc是一個庫函式;
new分配的記憶體要用delete銷燬,malloc要用free來銷燬;delete銷燬的時候會呼叫物件的解構函式,而free則不會;
malloc分配的記憶體不夠的時候,可以用realloc擴容。擴容的原理?new沒用這樣操作;
new如果分配失敗了會丟擲bad_malloc的異常,而malloc失敗了會返回NULL。因此對於new,正確的姿勢是採用try…catch語法,而malloc則應該判斷指標的返回值。為了相容很多c程式設計師的習慣,C++也可以採用new nothrow的方法禁止丟擲異常而返回NULL;
new和new[]的區別,new[]一次分配所有記憶體,多次呼叫建構函式,分別搭配使用delete和delete[],同理,delete[]多次呼叫解構函式,銷燬陣列中的每個物件。而malloc則只能sizeof(int) * n;
如果不夠可以繼續談new和malloc的實現,空閒連結串列,分配方法(首次適配原則,最佳適配原則,最差適配原則,快速適配原則)。delete和free的實現原理,free為什麼直到銷燬多大的空間?
C++多型性與虛擬函式表

C++多型的實現? 
多型分為靜態多型和動態多型。靜態多型是通過過載和模板技術實現,在編譯的時候確定。動態多型通過虛擬函式和繼承關係來實現,執行動態繫結,在執行的時候確定。 
動態多型實現有幾個條件: 
(1) 虛擬函式; 
(2) 一個基類的指標或引用指向派生類的物件; 
基類指標在呼叫成員函式(虛擬函式)時,就會去查詢該物件的虛擬函式表。虛擬函式表的地址在每個物件的首地址。查詢該虛擬函式表中該函式的指標進行呼叫。 
每個物件中儲存的只是一個虛擬函式表的指標,C++內部為每一個類維持一個虛擬函式表,該類的物件的都指向這同一個虛擬函式表。 
虛擬函式表中為什麼就能準確查詢相應的函式指標呢?因為在類設計的時候,虛擬函式表直接從基類也繼承過來,如果覆蓋了其中的某個虛擬函式,那麼虛擬函式表的指標就會被替換,因此可以根據指標準確找到該呼叫哪個函式。
虛擬函式的作用?
虛擬函式用於實現多型,這點大家都能答上來
但是虛擬函式在設計上還具有封裝和抽象的作用。比如抽象工廠模式。

動態繫結是如何實現的? 
第一個問題中基本回答了,主要都是結合虛擬函式表來答就行。

靜態多型和動態多型。靜態多型是指通過模板技術或者函式過載技術實現的多型,其在編譯器確定行為。動態多型是指通過虛擬函式技術實現在執行期動態繫結的技術。

虛擬函式表

虛擬函式表是針對類的還是針對物件的?同一個類的兩個物件的虛擬函式表是怎麼維護的?
編譯器為每一個類維護一個虛擬函式表,每個物件的首地址儲存著該虛擬函式表的指標,同一個類的不同物件實際上指向同一張虛擬函式表。
純虛擬函式如何定義,為什麼對於存在虛擬函式的類中解構函式要定義成虛擬函式 
為了實現多型進行動態繫結,將派生類物件指標繫結到基類指標上,物件銷燬時,如果解構函式沒有定義為解構函式,則會呼叫基類的解構函式,顯然只能銷燬部分資料。如果要呼叫物件的解構函式,就需要將該物件的解構函式定義為虛擬函式,銷燬時通過虛擬函式表找到對應的解構函式。

//純虛擬函式定義
virtual ~myClass() = 0;
1
2
解構函式能丟擲異常嗎 
答案肯定是不能。 
C++標準指明解構函式不能、也不應該丟擲異常。C++異常處理模型最大的特點和優勢就是對C++中的面向物件提供了最強大的無縫支援。那麼如果物件在執行期間出現了異常,C++異常處理模型有責任清除那些由於出現異常所導致的已經失效了的物件(也即物件超出了它原來的作用域),並釋放物件原來所分配的資源, 這就是呼叫這些物件的解構函式來完成釋放資源的任務,所以從這個意義上說,解構函式已經變成了異常處理的一部分。


(1) 如果解構函式丟擲異常,則異常點之後的程式不會執行,如果解構函式在異常點之後執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源洩漏的問題。
(2) 通常異常發生時,c++的機制會呼叫已經構造物件的解構函式來釋放資源,此時若解構函式本身也丟擲異常,則前一個異常尚未處理,又有新的異常,會造成程式崩潰的問題。

建構函式和解構函式中呼叫虛擬函式嗎?

指標和引用的區別

指標儲存的是所指物件的地址,引用是所指物件的別名,指標需要通過解引用間接訪問,而引用是直接訪問;
指標可以改變地址,從而改變所指的物件,而引用必須從一而終;
引用在定義的時候必須初始化,而指標則不需要;
指標有指向常量的指標和指標常量,而引用沒有常量引用;
指標更靈活,用的好威力無比,用的不好處處是坑,而引用用起來則安全多了,但是比較死板。
指標與陣列千絲萬縷的聯絡

一個一維int陣列的陣列名實際上是一個int* const 型別;
一個二維int陣列的陣列名實際上是一個int (*const p)[n];
陣列名做引數會退化為指標,除了sizeof
智慧指標是怎麼實現的?什麼時候改變引用計數?

建構函式中計數初始化為1;
拷貝建構函式中計數值加1;
賦值運算子中,左邊的物件引用計數減一,右邊的物件引用計數加一;
解構函式中引用計數減一;
在賦值運算子和解構函式中,如果減一後為0,則呼叫delete釋放物件。
share_prt與weak_ptr的區別?
//share_ptr可能出現迴圈引用,從而導致記憶體洩露
class A
{
public:
    share_ptr<B> p;
};

class B
{
public:
    share_ptr<A> p;
}

int main()
{
    while(true)
    {
        share_prt<A> pa(new A()); //pa的引用計數初始化為1
        share_prt<B> pb(new B()); //pb的引用計數初始化為1
        pa->p = pb; //pb的引用計數變為2
        pb->p = pa; //pa的引用計數變為2
    }
    //假設pa先離開,引用計數減一變為1,不為0因此不會呼叫class A的解構函式,因此其成員p也不會被析構,pb的引用計數仍然為2;
    //同理pb離開的時候,引用計數也不能減到0
    return 0;
}

/*
** weak_ptr是一種弱引用指標,其存在不會影響引用計數,從而解決迴圈引用的問題
*/

C++四種類型轉換:static_cast, dynamic_cast, const_cast, reinterpret_cast

const_cast用於將const變數轉為非const
static_cast用的最多,對於各種隱式轉換,非const轉const,void*轉指標等, static_cast能用於多型想上轉化,如果向下轉能成功但是不安全,結果未知;
dynamic_cast用於動態型別轉換。只能用於含有虛擬函式的類,用於類層次間的向上和向下轉化。只能轉指標或引用。向下轉化時,如果是非法的對於指標返回NULL,對於引用拋異常。要深入瞭解內部轉換的原理。
reinterpret_cast幾乎什麼都可以轉,比如將int轉指標,可能會出問題,儘量少用;
為什麼不使用C的強制轉換?C的強制轉換表面上看起來功能強大什麼都能轉,但是轉化不夠明確,不能進行錯誤檢查,容易出錯。
記憶體對齊的原則

從0位置開始儲存;
變數儲存的起始位置是該變數大小的整數倍;
結構體總的大小是其最大元素的整數倍,不足的後面要補齊;
結構體中包含結構體,從結構體中最大元素的整數倍開始存;
如果加入pragma pack(n) ,取n和變數自身大小較小的一個。
行內函數有什麼優點?行內函數與巨集定義的區別?

巨集定義在預編譯的時候就會進行巨集替換;
行內函數在編譯階段,在呼叫行內函數的地方進行替換,減少了函式的呼叫過程,但是使得編譯檔案變大。因此,行內函數適合簡單函式,對於複雜函式,即使定義了內聯編譯器可能也不會按照內聯的方式進行編譯。
行內函數相比巨集定義更安全,行內函數可以檢查引數,而巨集定義只是簡單的文字替換。因此推薦使用行內函數,而不是巨集定義。
使用巨集定義函式要特別注意給所有單元都加上括號,#define MUL(a, b) a * b,這很危險,正確寫法:#define MUL(a, b) ((a) * (b))
C++記憶體管理

C++記憶體分為那幾塊?(堆區,棧區,常量區,靜態和全域性區)
每塊儲存哪些變數?
學會遷移,可以說到malloc,從malloc說到作業系統的記憶體管理,說道核心態和使用者態,然後就什麼高階記憶體,slab層,夥伴演算法,VMA可以巴拉巴拉了,接著可以遷移到fork()。
STL裡的記憶體池實現 
STL記憶體分配分為一級分配器和二級分配器,一級分配器就是採用malloc分配記憶體,二級分配器採用記憶體池。

二級分配器設計的非常巧妙,分別給8k,16k,…, 128k等比較小的記憶體片都維持一個空閒連結串列,每個連結串列的頭節點由一個數組來維護。需要分配記憶體時從合適大小的連結串列中取一塊下來。假設需要分配一塊10K的記憶體,那麼就找到最小的大於等於10k的塊,也就是16K,從16K的空閒連結串列裡取出一個用於分配。釋放該塊記憶體時,將記憶體節點歸還給連結串列。 
如果要分配的記憶體大於128K則直接呼叫一級分配器。 
為了節省維持連結串列的開銷,採用了一個union結構體,分配器使用union裡的next指標來指向下一個節點,而使用者則使用union的空指標來表示該節點的地址。

STL裡set和map是基於什麼實現的。紅黑樹的特點?

set和map都是基於紅黑樹實現的。
紅黑樹是一種平衡二叉查詢樹,與AVL樹的區別是什麼?AVL樹是完全平衡的,紅黑樹基本上是平衡的。
為什麼選用紅黑數呢?因為紅黑數是平衡二叉樹,其插入和刪除的效率都是N(logN),與AVL相比紅黑數插入和刪除最多隻需要3次旋轉,而AVL樹為了維持其完全平衡性,在壞的情況下要旋轉的次數太多。 
紅黑樹的定義: 
(1) 節點是紅色或者黑色; 
(2) 父節點是紅色的話,子節點就不能為紅色; 
(3) 從根節點到每個頁子節點路徑上黑色節點的數量相同; 
(4) 根是黑色的,NULL節點被認為是黑色的。
STL裡的其他資料結構和演算法實現也要清楚 
這個問題,把STL原始碼剖析好好看看,不僅面試不慌,自己對STL的使用也會上升一個層次。

必須在建構函式初始化式裡進行初始化的資料成員有哪些 
(1) 常量成員,因為常量只能初始化不能賦值,所以必須放在初始化列表裡面 
(2) 引用型別,引用必須在定義的時候初始化,並且不能重新賦值,所以也要寫在初始化列表裡面 
(3) 沒有預設建構函式的類型別,因為使用初始化列表可以不必呼叫預設建構函式來初始化,而是直接呼叫拷貝建構函式初始化

模板特化 
(1) 模板特化分為全特化和偏特化,模板特化的目的就是對於某一種變數型別具有不同的實現,因此需要特化版本。例如,在STL裡迭代器為了適應原生指標就將原生指標進行特化。

定位記憶體洩露 
(1)在windows平臺下通過CRT中的庫函式進行檢測; 
(2)在可能洩漏的呼叫前後生成塊的快照,比較前後的狀態,定位洩漏的位置 
(3)Linux下通過工具valgrind檢測

手寫strcpy

char* strcpy(char* dst, const char* src)
{
    assert(dst);
    assert(src);
    char* ret = dst;
    while((*dst++ = *src++) != '\0');
    return ret;
}
//該函式是沒有考慮重疊的

char* strcpy(char* dst, const char* src)
{
    assert((dst != NULL) && (src != NULL));
    char* ret = dst;
    int size = strlen(src) + 1;
    if(dst > src || dst < src + len)
    {
        dst = dst + size - 1;
        src = src + size - 1;
        while(size--)
        {
            *dst-- = *src--;
        }
    }
    else
    {
        while(size--)
        {
            *dst++ = *src++;
        }
    }
    return ret;
}

手寫memcpy函式
void* memcpy(void* dst, const void* src, size_t size)
{
    if(dst == NULL || src == NULL)
    {
        return NULL;
    }
    void* res = dst;
    char* pdst = (char*)dst;
    char* psrc = (char*)src;

    if(pdst > psrc && pdst < psrc + size) //重疊
    {
        pdst = pdst + size - 1;
        psrc = pdst + size - 1;
        while(size--)
        {
            *pdst-- = *psrc--;
        }
    }
    else //無重疊
    {
        while(size--)
        {
            *dst++ = *src++;
        }
    }
    return ret;
}

手寫strcat函式
char* strcat(char* dst, const char* src)
{
    char* ret = dst;

    while(*dst != '\0')
        ++dst;

    while((*dst++ = *src) != '\0');
    return ret;
}

手寫strcmp函式
int strcmp(const char* str1, const char* str2)
{

    while(*str1 == *str2 && *str1 != '\0')
    {
        ++str1;
        ++str2;
    }
    return *str1 - *str2;
}

資料結構與演算法
這一塊考察範圍太廣,主要靠多刷題吧,牛客網,劍指OFFER,LeetCode等。

Hash表
Hash表實現(拉鍊和分散地址)
Hash策略常見的有哪些?
STL中hash_map擴容發生什麼? 
(1) 建立一個新桶,該桶是原來桶兩倍大最接近的質數(判斷n是不是質數的方法:用n除2到sqrt(n)sqrt(n)範圍內的數) ; 
(2) 將原來桶裡的數通過指標的轉換,插入到新桶中(注意STL這裡做的很精細,沒有直接將資料從舊桶遍歷拷貝資料插入到新桶,而是通過指標轉換) 
(3) 通過swap函式將新桶和舊桶交換,銷燬新桶。

二叉樹結構,二叉查詢樹實現;
二叉樹的六種遍歷;
二叉樹的按層遍歷;
遞迴是解決二叉樹相關問題的神級方法;
樹的各種常見演算法題(http://blog.csdn.net/xiajun07061225/article/details/12760493);

什麼是紅黑樹?

節點為紅色或者黑色;
根節點為黑色;
從根節點到每個葉子節點經過的黑色節點個數的和相同;
如果父節點為紅色,那麼其子節點就不能為紅色。
紅黑樹與AVL樹的區別

紅黑樹與AVL樹都是平衡樹,但是AVL是完全平衡的(平衡就是值樹中任意節點的左子樹和右子樹高度差不超過1);
紅黑樹效率更高,因為AVL為了保證其完全平衡,插入和刪除的時候在最壞的情況下要旋轉logN次,而紅黑樹插入和刪除的旋轉次數要比AVL少。
Trie樹(字典樹)

每個節點儲存一個字元
根節點不儲存字元
每個節點最多有n個子節點(n是所有可能出現字元的個數)
查詢的複雜父為O(k),k為查詢字串長度
連結串列
連結串列和插入和刪除,單向和雙向連結串列都要會
連結串列的問題考慮多個指標和遞迴 
(1) 反向列印連結串列(遞迴) 
(2) 列印倒數第K個節點(前後指標) 
(3) 連結串列是否有環(快慢指標)等等。b ggg
棧和佇列
佇列和棧的區別?(從實現,應用,自身特點多個方面來闡述,不要只說一個先入先出,先入後出,這個你會別人也會,要展現出你比別人掌握的更深)
典型的應用場景
海量資料問題
十億整數(隨機生成,可重複)中前K最大的數 
類似問題的解決方法思路:首先雜湊將資料分成N個檔案,然後對每個檔案建立K個元素最小/大堆(根據要求來選擇)。最後將檔案中剩餘的數插入堆中,並維持K個元素的堆。最後將N個堆中的元素合起來分析。可以採用歸併的方式來合併。在歸併的時候為了提高效率還需要建一個N個元素構成的最大堆,先用N個堆中的最大值填充這個堆,然後就是彈出最大值,指標後移的操作了。當然這種問題在現在的網際網路技術中,一般就用map-reduce框架來做了。 
大資料排序相同的思路:先雜湊(雜湊是好處是分佈均勻,相同的數在同一個檔案中),然後小檔案裝入記憶體快排,排序結果輸出到檔案。最後建堆歸併。

十億整數(隨機生成,可重複)中出現頻率最高的一千個

排序演算法
排序演算法當然是基礎內容了,必須至少能快速寫出,快排,建堆,和歸併
每種演算法的時間空間複雜度,最好最差平均情況
位運算
布隆過濾器
幾十億個數經常要查詢某一個數在不在裡面,使用布隆過濾器,布隆過濾器的原理。布隆過濾器可能出現誤判,怎麼保證無誤差?

網路與TCP/IP
參考書籍:《圖解TCP/IP》,《TCP/IP詳解 卷一》,《圖解HTTP》,《HTTP權威指南》

TCP與UDP之間的區別 
(1) IP首部,TCP首部,UDP首部 
(2) TCP和UDP區別 
(3) TCP和UDP應用場景 
(4) 如何實現可靠的UDP

TCP三次握手與四次揮手

詳細說明TCP狀態遷移過程 
(1) 三次握手和四次揮手狀態變化; 
(2) 2MSL是什麼狀態?作用是什麼? 
(3)三次握手為什麼不是兩次或者四次?

TCP相關技術

TCP重發機制,Nagle演算法
TCP的擁塞控制使用的演算法和具體過程
TCP的視窗滑動
TCP客戶與伺服器模型,用到哪些函式

UDP客戶與伺服器模型,用到哪些函式

域名解析過程,ARP的機制,RARP的實現

RARP用於無盤伺服器,開機後通過傳送RARP包給RARP伺服器,通過mac地址得到IP地址
Ping和TraceRoute實現原理

(1) Ping是通過傳送ICMP報文回顯請求實現。
(2) TraceRoute通過傳送UDP報文,設定目的埠為一個不可能的值,將IP首部中的TTL分別設定從1到N,每次逐個增加,如果收到埠不可達,說明到達目的主機,如果是因為TTL跳數超過,路由器會發送主機不可達的ICMP報文。
1
2
HTTP
http/https 1.0、1.1、2.0
http的主要特點: 
簡單快速:當客戶端向伺服器端傳送請求時,只是簡單的填寫請求路徑和請求方法即可,然後就可以通過瀏覽器或其他方式將該請求傳送就行了 
靈活: HTTP 協議允許客戶端和伺服器端傳輸任意型別任意格式的資料物件 
無連線:無連線的含義是限制每次連線只處理一個請求。伺服器處理完客戶的請求,並收到客戶的應答後,即斷開連線,採用這種方式可以節省傳輸時間。(當今多數伺服器支援Keep-Alive功能,使用伺服器支援長連線,解決無連線的問題) 
無狀態:無狀態是指協議對於事務處理沒有記憶能力,伺服器不知道客戶端是什麼狀態。即客戶端傳送HTTP請求後,伺服器根據請求,會給我們傳送資料,傳送完後,不會記錄資訊。(使用 cookie 機制可以保持 session,解決無狀態的問題)

http1.1的特點 
a、預設持久連線節省通訊量,只要客戶端服務端任意一端沒有明確提出斷開TCP連線,就一直保持連線,可以傳送多次HTTP請求 
b、管線化,客戶端可以同時發出多個HTTP請求,而不用一個個等待響應 
c、斷點續傳

http2.0的特點 
a、HTTP/2採用二進位制格式而非文字格式 
b、HTTP/2是完全多路複用的,而非有序並阻塞的——只需一個HTTP連線就可以實現多個請求響應 
c、使用報頭壓縮,HTTP/2降低了開銷 
d、HTTP/2讓伺服器可以將響應主動“推送”到客戶端快取中

get/post 區別
區別一:
get重點在從伺服器上獲取資源,post重點在向伺服器傳送資料;
區別二:
get傳輸資料是通過URL請求,以field(欄位)= value的形式,置於URL後,並用"?"連線,多個請求資料間用"&"連線,如http://127.0.0.1/Test/login.action?name=admin&password=admin,這個過程使用者是可見的;
post傳輸資料通過Http的post機制,將欄位與對應值封存在請求實體中傳送給伺服器,這個過程對使用者是不可見的;
區別三:
Get傳輸的資料量小,因為受URL長度限制,但效率較高;
Post可以傳輸大量資料,所以上傳檔案時只能用Post方式;
區別四:
get是不安全的,因為URL是可見的,可能會洩露私密資訊,如密碼等;
post較get安全性較高;

返回狀態碼
200:請求被正常處理
204:請求被受理但沒有資源可以返回
206:客戶端只是請求資源的一部分,伺服器只對請求的部分資源執行GET方法,相應報文中通過Content-Range指定範圍的資源。
301:永久性重定向
302:臨時重定向
303:與302狀態碼有相似功能,只是它希望客戶端在請求一個URI的時候,能通過GET方法重定向到另一個URI上
304:傳送附帶條件的請求時,條件不滿足時返回,與重定向無關
307:臨時重定向,與302類似,只是強制要求使用POST方法
400:請求報文語法有誤,伺服器無法識別
401:請求需要認證
403:請求的對應資源禁止被訪問
404:伺服器無法找到對應資源
500:伺服器內部錯誤
503:伺服器正忙

http 協議頭相關
http資料由請求行,首部欄位,空行,報文主體四個部分組成 
首部欄位分為:通用首部欄位,請求首部欄位,響應首部欄位,實體首部欄位

https與http的區別?如何實現加密傳輸?
https就是在http與傳輸層之間加上了一個SSL
對稱加密與非對稱加密
瀏覽器中輸入一個URL發生什麼,用到哪些協議?
瀏覽器中輸入URL,首先瀏覽器要將URL解析為IP地址,解析域名就要用到DNS協議,首先主機會查詢DNS的快取,如果沒有就給本地DNS傳送查詢請求。DNS查詢分為兩種方式,一種是遞迴查詢,一種是迭代查詢。如果是迭代查詢,本地的DNS伺服器,向根域名伺服器傳送查詢請求,根域名伺服器告知該域名的一級域名伺服器,然後本地伺服器給該一級域名伺服器傳送查詢請求,然後依次類推直到查詢到該域名的IP地址。DNS伺服器是基於UDP的,因此會用到UDP協議。

得到IP地址後,瀏覽器就要與伺服器建立一個http連線。因此要用到http協議,http協議報文格式上面已經提到。http生成一個get請求報文,將該報文傳給TCP層處理。如果採用https還會先對http資料進行加密。TCP層如果有需要先將HTTP資料包分片,分片依據路徑MTU和MSS。TCP的資料包然後會發送給IP層,用到IP協議。IP層通過路由選路,一跳一跳傳送到目的地址。當然在一個網段內的定址是通過乙太網協議實現(也可以是其他物理層協議,比如PPP,SLIP),乙太網協議需要直到目的IP地址的實體地址,有需要ARP協議。

安全相關
至少了解攻擊的原理和基本的防禦方法,常見的攻擊方法有一下幾種

SQL注入
XSS
CSRF
SYN洪水攻擊
APR欺騙
資料庫
主要參考書籍:《資料庫系統概念》,《高效能MySQL》

SQL語言(內外連線,子查詢,分組,聚集,巢狀,邏輯)
MySQL索引方法?索引的優化?
InnoDB與MyISAM區別?
事務的ACID
事務的四個隔離級別
查詢優化(從索引上優化,從SQL語言上優化)
B-與B+樹區別?
MySQL的聯合索引(又稱多列索引)是什麼?生效的條件?
分庫分表
Linux
主要參考書籍:《現代作業系統》,《APUE》,《UNP》,《LINUX核心設計與實現》,《深入理解LINUX核心》

程序與執行緒
(1) 程序與執行緒區別? 
(2) 執行緒比程序具有哪些優勢? 
(3) 什麼時候用多程序?什麼時候用多執行緒? 
(4) LINUX中程序和執行緒使用的幾個函式? 
(5) 執行緒同步? 
在Windows下執行緒同步的方式有:互斥量,訊號量,事件,關鍵程式碼段 
在Linux下執行緒同步的方式有:互斥鎖,自旋鎖,讀寫鎖,屏障(併發完成同一項任務時,屏障的作用特別好使) 
知道這些鎖之間的區別,使用場景?

程序間通訊方式
管道( pipe ):管道是一種半雙工的通訊方式,資料只能單向流動,而且只能在具有親緣關係的程序間使用。程序的親緣關係通常是指父子程序關係。

命名管道 (FIFO) : 有名管道也是半雙工的通訊方式,但是它允許無親緣關係程序間的通訊。

訊號量:訊號量用於實現程序間的互斥與同步,而不是用於儲存程序間通訊資料,有XSI訊號量和POSIX訊號量,POSIX訊號量更加完善。

訊息佇列( message queue ) : 訊息佇列是由訊息的連結串列,存放在核心中並由訊息佇列識別符號標識。訊息佇列克服了訊號傳遞資訊少、管道只能承載無格式位元組流以及緩衝區大小受限等缺點。

共享記憶體( shared memory ) :共享記憶體就是對映一段能被其他程序所訪問的記憶體,這段共享記憶體由一個程序建立,但多個程序都可以訪問。共享記憶體是最快的 IPC 方式,它是針對其他程序間通訊方式執行效率低而專門設計的。它往往與其他通訊機制,如訊號兩,配合使用,來實現程序間的同步和通訊。(原理一定要清楚,常考)

訊號 ( sinal ) : 訊號是一種比較複雜的通訊方式,用於通知接收程序某個事件已經發生,常見的訊號。

套接字( socket ) : 套解口也是一種程序間通訊機制,與其他通訊機制不同的是,它可用於不同及其間的程序通訊。

匿名管道與命名管道的區別:匿名管道只能在具有公共祖先的兩個程序間使用。
共享檔案對映mmap 
mmap建立程序空間到檔案的對映,在建立的時候並不直接將檔案拷貝到實體記憶體,同樣採用缺頁終端。mmap對映一個具體的檔案可以實現任意程序間共享記憶體,對映一個匿名檔案,可以實現父子程序間共享記憶體。

常見的訊號有哪些?:SIGINT,SIGKILL(不能被捕獲),SIGTERM(可以被捕獲),SIGSEGV,SIGCHLD,SIGALRM

記憶體管理
虛擬記憶體的作用?
虛擬記憶體的實現?
作業系統層面對記憶體的管理?
記憶體池的作用?STL裡記憶體池如何實現?
程序空間和核心空間對記憶體的管理不同?
Linux的slab層,VAM?
夥伴演算法
高階記憶體
程序排程
Linux程序分為兩種,實時程序和非實時程序;
優先順序分為靜態優先順序和動態優先順序,優先順序的範圍;
排程策略,FIFO,LRU,時間片輪轉
互動程序通過平均睡眠時間而被獎勵;
死鎖
(1) 死鎖產生的條件; 
(2) 死鎖的避免;

命令列
Linux命令 在一個檔案中,倒序列印第二行前100個大寫字母
cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev
1
與CPU,記憶體,磁碟相關的命令(top,free, df, fdisk)

網路相關的命令netstat,tcpdump等

sed, awk, grep三個超強大的命名,分別用與格式化修改,統計,和正則查詢

ipcs和ipcrm命令

查詢當前目錄以及字母下以.c結尾的檔案,且檔案中包含”hello world”的檔案的路徑

建立定時任務

IO模型
五種IO模型:阻塞IO,非阻塞IO,IO複用,訊號驅動式IO,非同步IO

select,poll,epoll的區別

select:是最初解決IO阻塞問題的方法。用結構體fd_set來告訴核心監聽多個檔案描述符,該結構體被稱為描述符集。由陣列來維持哪些描述符被置位了。對結構體的操作封裝在三個巨集定義中。通過輪尋來查詢是否有描述符要被處理,如果沒有返回** 
存在的問題: 
1. 內建陣列的形式使得select的最大檔案數受限與FD_SIZE; 
2. 每次呼叫select前都要重新初始化描述符集,將fd從使用者態拷貝到核心態,每次呼叫select後,都需要將fd從核心態拷貝到使用者態; 
3. 輪尋排查當檔案描述符個數很多時,效率很低;

poll:通過一個可變長度的陣列解決了select檔案描述符受限的問題。陣列中元素是結構體,該結構體儲存描述符的資訊,每增加一個檔案描述符就向陣列中加入一個結構體,結構體只需要拷貝一次到核心態。poll解決了select重複初始化的問題。輪尋排查的問題未解決。**

epoll:輪尋排查所有檔案描述符的效率不高,使伺服器併發能力受限。因此,epoll採用只返回狀態發生變化的檔案描述符,便解決了輪尋的瓶頸。 
- 為什麼使用IO多路複用,最主要的原因是什麼? 
- epoll有兩種觸發模式?這兩種觸發模式有什麼區別?程式設計的時候有什麼區別? 
- 上一題中程式設計的時候有什麼區別,是在邊緣觸發的時候要把套接字中的資料讀乾淨,那麼當有多個套接字時,在讀的套接字一直不停的有資料到達,如何保證其他套接字不被餓死(面試網易遊戲的時候問的一個問題,答不上來,印象賊深刻)。

select/poll/epoll區別
幾種網路伺服器模型的介紹與比較
epoll為什麼這麼快(搞懂這篇文章,關於IO複用的問題就信手拈來了)
執行緒池,記憶體池 自己動手實現一遍
Linux的API
fork與vfork區別 
fork和vfork都用於建立子程序。但是vfork建立子程序後,父程序阻塞,直到子程序呼叫exit()或者excle()。 
對於核心中過程fork通過呼叫clone函式,然後clone函式呼叫do_fork()。do_fork()中呼叫copy_process()函式先複製task_struct結構體,然後複製其他關於記憶體,檔案,暫存器等資訊。fork採用寫時拷貝技術,因此子程序和父程序的頁表指向相同的頁框。但是vfork不需要拷貝頁表,因為父程序會一直阻塞,直接使用父程序頁表。

exit()與_exit()區別 
exit()清理後進入核心,_exit()直接陷入核心。

孤兒程序與僵死程序

孤兒程序是怎麼產生的?
僵死程序是怎麼產生的?
僵死程序的危害?
如何避免僵死程序的產生?
Linux是如何避免記憶體碎片的

夥伴演算法,用於管理實體記憶體,避免記憶體碎片;
快取記憶體Slab層用於管理核心分配記憶體,避免碎片。
共享記憶體的實現原理? 
共享記憶體實現分為兩種方式一種是採用mmap,另一種是採用XSI機制中的共享記憶體方法。mmap是記憶體檔案對映,將一個檔案對映到程序的地址空間,使用者程序的地址空間的管理是通過vm_area_struct結構體進行管理的。mmap通過對映一個相同的檔案到兩個不同的程序,就能實現這兩個程序的通訊,採用該方法可以實現任意程序之間的通訊。mmap也可以採用匿名對映,不指定對映的檔案,但是隻能在父子程序間通訊。XSI的記憶體共享實際上也是通過對映檔案實現,只是其對映的是一種特殊檔案系統下的檔案,該檔案是不能通過read和write訪問的。

二者區別:

1、 系統V共享記憶體中的資料,從來不寫入到實際磁碟檔案中去;而通過mmap()對映普通檔案實現的共享記憶體通訊可以指定何時將資料寫入磁碟檔案中。注:前面講到,系統V共享記憶體機制實際是通過對映特殊檔案系統shm中的檔案實現的,檔案系統shm的安裝點在交換分割槽上,系統重新引導後,所有的內容都丟失。

2、 系統V共享記憶體是隨核心持續的,即使所有訪問共享記憶體的程序都已經正常終止,共享記憶體區仍然存在(除非顯式刪除共享記憶體),在核心重新引導之前,對該共享記憶體區域的任何改寫操作都將一直保留。

3、 通過呼叫mmap()對映普通檔案進行程序間通訊時,一定要注意考慮程序何時終止對通訊的影響。而通過系統V共享記憶體實現通訊的程序則不然。注:這裡沒有給出shmctl的使用範例,原理與訊息佇列大同小異。

系統呼叫與庫函式(open, close, create, lseek, write, read)

同步方法有哪些?

互斥鎖,自旋鎖,訊號量,讀寫鎖,屏障
互斥鎖與自旋鎖的區別:互斥鎖得不到資源的時候阻塞,不佔用cpu資源。自旋鎖得不到資源的時候,不停的查詢,而然佔用cpu資源。
死鎖
其他
++i是否是原子操作 
明顯不是,++i主要有三個步驟,把資料從記憶體放在暫存器上,在暫存器上進行自增,把資料從暫存器拷貝會記憶體,每個步驟都可能被中斷。

判斷大小端

union un
{
    int i;
    char ch;
};

void fun()
{
    union un test;
    test.i = 1;
    if(ch == 1)
        cout << "小端" << endl;
    else
        cout << "大端" << endl;
}

設計模式
單例模式執行緒安全的寫法
STL裡的迭代器使用了迭代器模式
MVC的理解
分散式系統
map_reduce原理 (這篇文章講的很通俗易懂)
負載均衡
CDN
部分問題只是列出思考的概要,去書中和實踐中找到這些問題的答案才能真正的消化。

PS:歡迎轉載,轉載請標明出處!
--------------------- 
作者:oscarwin 
來源:CSDN 
原文:https://blog.csdn.net/shanghairuoxiao/article/details/72876248 
版權宣告:本文為博主原創文章,轉載請附上博文連結!