C和C++語言基礎
參考書籍:《C++ primer》,《effective C++》,《STL源碼解析》,《深度搜索C++對象模型》
-
- extern聲明變量在在外部定義?
- extern修飾函數?
- extern C的作用?用法?
-
- 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的實現原理?但是還需要注意的是,之前看到過一個題說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++多態的實現?
多態分為靜態多態和動態多態。靜態多態是通過重載和模板技術實現,在編譯的時候確定。動態多態通過虛函數和繼承關系來實現,執行動態綁定,在運行的時候確定。
動態多態實現有幾個條件:
(1) 虛函數;
(2) 一個基類的指針或引用指向派生類的對象;
基類指針在調用成員函數(虛函數)時,就會去查找該對象的虛函數表。虛函數表的地址在每個對象的首地址。查找該虛函數表中該函數的指針進行調用。
每個對象中保存的只是一個虛函數表的指針,C++內部為每一個類維持一個虛函數表,該類的對象的都指向這同一個虛函數表。
虛函數表中為什么就能准確查找相應的函數指針呢?因為在類設計的時候,虛函數表直接從基類也繼承過來,如果覆蓋了其中的某個虛函數,那么虛函數表的指針就會被替換,因此可以根據指針准確找到該調用哪個函數。 - 虛函數的作用?
- 虛函數用於實現多態,這點大家都能答上來
但是虛函數在設計上還具有封裝和抽象的作用。比如抽象工廠模式。
動態綁定是如何實現的?
第一個問題中基本回答了,主要都是結合虛函數表來答就行。靜態多態和動態多態。靜態多態是指通過模板技術或者函數重載技術實現的多態,其在編譯器確定行為。動態多態是指通過虛函數技術實現在運行期動態綁定的技術。
- 虛函數表是針對類的還是針對對象的?同一個類的兩個對象的虛函數表是怎么維護的?
- 編譯器為每一個類維護一個虛函數表,每個對象的首地址保存着該虛函數表的指針,同一個類的不同對象實際上指向同一張虛函數表。
- 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是一種弱引用指針,其存在不會影響引用計數,從而解決循環引用的問題 */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 手寫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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 手寫strcat函數
char* strcat(char* dst, const char* src)
{
char* ret = dst;
while(*dst != '\0')
++dst;
while((*dst++ = *src) != '\0');
return ret;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 手寫strcmp函數
int strcmp(const char* str1, const char* str2)
{
while(*str1 == *str2 && *str1 != '\0')
{
++str1;
++str2;
}
return *str1 - *str2;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
數據結構與算法
這一塊考察范圍太廣,主要靠多刷題吧,牛客網,劍指OFFER,LeetCode等。
Hash表
- Hash表實現(拉鏈和分散地址)
- Hash策略常見的有哪些?
- STL中hash_map擴容發生什么?
(1) 創建一個新桶,該桶是原來桶兩倍大最接近的質數(判斷n是不是質數的方法:用n除2到 范圍內的數) ;
(2) 將原來桶里的數通過指針的轉換,插入到新桶中(注意STL這里做的很精細,沒有直接將數據從舊桶遍歷拷貝數據插入到新桶,而是通過指針轉換)
(3) 通過swap函數將新桶和舊桶交換,銷毀新桶。
樹
- 二叉樹結構,二叉查找樹實現;
- 二叉樹的六種遍歷;
- 二叉樹的按層遍歷;
- 遞歸是解決二叉樹相關問題的神級方法;
樹的各種常見算法題(http://blog.csdn.net/xiajun07061225/article/details/12760493);
什么是紅黑樹?
- 節點為紅色或者黑色;
- 根節點為黑色;
- 從根節點到每個葉子節點經過的黑色節點個數的和相同;
- 如果父節點為紅色,那么其子節點就不能為紅色。
紅黑樹與AVL樹的區別
- 紅黑樹與AVL樹都是平衡樹,但是AVL是完全平衡的(平衡就是值樹中任意節點的左子樹和右子樹高度差不超過1);
- 紅黑樹效率更高,因為AVL為了保證其完全平衡,插入和刪除的時候在最壞的情況下要旋轉logN次,而紅黑樹插入和刪除的旋轉次數要比AVL少。
-
- 每個節點保存一個字符
- 根節點不保存字符
- 每個節點最多有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狀態遷移過程
(1) 三次握手和四次揮手狀態變化;
(2) 2MSL是什么狀態?作用是什么?
(3)三次握手為什么不是兩次或者四次?-
- TCP重發機制,Nagle算法
- TCP的擁塞控制使用的算法和具體過程
- TCP的窗口滑動
-
- 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安全性較高;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
返回狀態碼
200:請求被正常處理
204:請求被受理但沒有資源可以返回
206:客戶端只是請求資源的一部分,服務器只對請求的部分資源執行GET方法,相應報文中通過Content-Range指定范圍的資源。
301:永久性重定向
302:臨時重定向
303:與302狀態碼有相似功能,只是它希望客戶端在請求一個URI的時候,能通過GET方法重定向到另一個URI上
304:發送附帶條件的請求時,條件不滿足時返回,與重定向無關
307:臨時重定向,與302類似,只是強制要求使用POST方法
400:請求報文語法有誤,服務器無法識別
401:請求需要認證
403:請求的對應資源禁止被訪問
404:服務器無法找到對應資源
500:服務器內部錯誤
503:服務器正忙
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
設計模式
- 單例模式線程安全的寫法
- STL里的迭代器使用了迭代器模式
- MVC的理解
分布式系統
- map_reduce原理 (這篇文章講的很通俗易懂)
- 負載均衡
- CDN
部分問題只是列出思考的概要,去書中和實踐中找到這些問題的答案才能真正的消化。
PS:歡迎轉載,轉載請標明出處!