offer--刷題之路(持續更新)

C++

1. 虛函數和純虛函數
定義一個函數爲虛函數,不代表函數爲不被實現的函數;定義他爲虛函數是爲了允許用基類的指針來調用子類的這個函數;定義一個函數爲純虛函數,才代表函數沒有被實現;定義純虛函數是爲了實現一個接口,起到一個規範的作用,規範繼承這個類的程序員必須實現這個函數。

  1. 虛函數和純虛函數可以定義在同一個類(class)中,含有純虛函數的類被稱爲抽象類(abstract class),而只含有虛函數的類(class)不能被稱爲抽象類(abstract class)。
  2. 虛函數可以被直接使用,也可以被子類(sub class)重載以後以多態的形式調用,而純虛函數必須在子類(sub class)中實現該函數纔可以使用,因爲純虛函數在基類(base class)只有聲明而沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型後加 =0:。
  3. 虛函數和純虛函數都可以在子類(sub class)中被重載,以多態的形式被調用。
  4. 虛函數和純虛函數通常存在於抽象基類(abstract base class -ABC)之中,被繼承的子類重載,目的是提供一個統一的接口。
  5. 虛函數的定義形式:virtual {method body}
      純虛函數的定義形式:virtual void funtion1()=0
    在虛函數和純虛函數的定義中不能有static標識符,原因很簡單,被static修飾的函數在編譯時候要求前期bind,然而虛函數卻是動態綁定(run-time bind),而且被兩者修飾的函數生命週期(life recycle)也不一樣。
  6. 虛函數必須實現,如果不實現,編譯器將報錯,錯誤提示爲:
    error LNK****: unresolved external symbol “public: virtual void __thiscall
    ClassName::virtualFunctionName(void)”
  7. 對於虛函數來說,父類和子類都有各自的版本。由多態方式調用的時候動態綁定。
  8. 實現了純虛函數的子類,該純虛函數在子類中就編程了虛函數,子類的子類即孫子類可以覆蓋該虛函數,由多態方式調用的時候動態綁定。
  9. 虛函數是C++中用於實現多態(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函數
  10. 多態性指相同對象收到不同消息或不同對象收到相同消息時產生不同的實現動作。C++支持兩種多態性:編譯時多態性,運行時多態性。
    a.編譯時多態性:通過重載函數實現
    b 運行時多態性:通過虛函數實現。
  11. 如果一個類中含有純虛函數,那麼任何試圖對該類進行實例化的語句都將導致錯誤的產生,因爲抽象基類(ABC)是不能被直接調用的。必須被子類繼承重載以後,根據要求調用其子類的方法。

2. C/C++程序內存的各種變量存儲區域和各個區域詳解
1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。
2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表,呵呵。
3、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域(RW), 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域(ZI)。 - 程序結束後有系統釋放
4、文字常量區 —常量字符串就是放在這裏的。 程序結束後由系統釋放 (RO)
5、程序代碼區—存放函數體的二進制代碼。 (RO)

3. new/delete與malloc/free的區別與聯繫
1、new/delete是C++的操作符,而malloc/free是C中的函數。
2、new做兩件事,一是分配內存,二是調用類的構造函數;同樣,delete會調用類的析構函數和釋放內存。而malloc和free只是分配和釋放內存。
3、new建立的是一個對象,而malloc分配的是一塊內存;new建立的對象可以用成員函數訪問,不要直接訪問它的地址空間;malloc分配的是一塊內存區域,用指針訪問,可以在裏面移動指針;new出來的指針是帶有類型信息的,而malloc返回的是void指針。
4、new/delete是保留字,不需要頭文件支持;malloc/free需要頭文件庫函數支持。

4. map、set分別幹什麼的?
map和set都是C++的關聯容器,其底層實現都是紅黑樹(RB-Tree)。
由於 map 和set所開放的各種操作接口,RB-tree 也都提供了,所以幾乎所有的 map 和set的操作行爲,都只是轉調 RB-tree 的操作行爲。
map和set區別在於:
map中的元素是key-value(關鍵字—值)對:關鍵字起到索引的作用,值則表示與索引相關聯的數據;Set與之相對就是關鍵字的簡單集合,set中每個元素只包含一個關鍵字。
set的迭代器是const的,不允許修改元素的值;map允許修改value,但不允許修改key。其原因是因爲map和set是根據關鍵字排序來保證其有序性的,如果允許修改key的話,那麼首先需要刪除該鍵,然後調節平衡,再插入修改後的鍵值,調節平衡,如此一來,嚴重破壞了map和set的結構,導致iterator失效,不知道應該指向改變前的位置,還是指向改變後的位置。所以STL中將set的迭代器設置成const,不允許修改迭代器的值;而map的迭代器則不允許修改key值,允許修改value值。
map支持下標操作,set不支持下標操作。map可以用key做下標,map的下標運算符[]將關鍵碼作爲下標去執行查找,如果關鍵碼不存在,則插入一個具有該關鍵碼和mapped_type類型默認值的元素至map中,因此下標運算符[ ]在map應用中需要慎用,const_map不能用,只希望確定某一個關鍵值是否存在而不希望插入元素時也不應該使用,mapped_type類型沒有默認值也不應該使用。如果find能解決需要,儘可能用find。

5. 數組和鏈表的區別和聯繫
1.數組:
數組是將元素在內存中連續存放,由於每個元素佔用內存相同,可以通過下標迅速訪問數組中任何元素。但是如果要在數組中增加一個元素,需要移動大量元素,在內存中空出一個元素的空間,然後將要增加的元素放在其中。同樣的道理,如果想刪除一個元素,同樣需要移動大量元素去填掉被移動的元素。如果應用需要快速訪問數據,很少插入和刪除元素,就應該用數組。
2.鏈表:
鏈表中的元素在內存中不是順序存儲的,而是通過存在元素中的指針聯繫到一起,每個結點包括兩個部分:一個是存儲數據元素 的數據域,另一個是存儲下一個結點地址的 指針。如果要訪問鏈表中一個元素,需要從第一個元素始,一直找到需要的元素位置。但是增加和刪除一個元素對於鏈表數據結構就非常簡單了,只要修改元素中的指針就可以了。如果應用需要經常插入和刪除元素你就需要用鏈表。
3.區別:
(1)存儲位置上:
數組邏輯上相鄰的元素在物理存儲位置上也相鄰,而鏈表不一定;
(2)存儲空間上:
鏈表存放的內存空間可以是連續的,也可以是不連續的,數組則是連續的一段內存空間。一般情況下存放相同多的數據數組佔用較小的內存,而鏈表還需要存放其前驅和後繼的空間。
(3)長度的可變性:
鏈表的長度是按實際需要可以伸縮的,而數組的長度是在定義時要給定的,如果存放的數據個數超過了數組的初始大小,則會出現溢出現象。
(4)按序號查找時,數組可以隨機訪問,時間複雜度爲O(1),而鏈表不支持隨機訪問,平均需要O(n); 
(5)按值查找時,若數組無序,數組和鏈表時間複雜度均爲O(1),但是當數組有序時,可以採用折半查找將時間複雜度降爲O(logn); 
(6)插入和刪除時,數組平均需要移動n/2個元素,而鏈表只需修改指針即可; 
(7)空間分配方面:
數組在靜態存儲分配情形下,存儲元素數量受限制,動態存儲分配情形下,雖然存儲空間可以擴充,但需要移動大量元素,導致操作效率降低,而且如果內存中沒有更大塊連續存儲空間將導致分配失敗; 即數組從棧中分配空間,,對於程序員方便快速,但自由度小。
鏈表存儲的節點空間只在需要的時候申請分配,只要內存中有空間就可以分配,操作比較靈活高效;即鏈表從堆中分配空間, 自由度大但申請管理比較麻煩。

6. const 與 #define的比較
C++ 語言可以用const來定義常量,也可以用 #define來定義常量。但是前者比後者有更多的優點:
(1) const常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,並且在字符替換可能會產生意料不到的錯誤(邊際效應)。
(2) 有些集成化的調試工具可以對const常量進行調試,但是不能對宏常量進行調試。
如果有一個常量,是定義爲宏常量好還是定義爲const常量(應該是定義爲const常量,宏常量的話符號會被替換掉不易查錯。使用const定義,既可以保證值的唯一性,又便於調試,同時還可以對數據類型進行檢查,藉助編譯器來減少錯誤的發生

7. STL之vector中push_back的時間複雜度分析
vector是STL中的一種序列式容器,採用的數據結構爲線性連續空間,它以兩個迭代器 start 和 finish 分別指向配置得來的連續空間中目前已被使用的範圍,並以迭代器end_of_storage 指向整塊連續空間(含備用空間)的尾端
我們在使用 vector 時​,最常使用的操作恐怕就是插入操作了(push_back),那麼當執行該操作時,該函數都做了哪些工作呢?
該函數首先檢查是否還有備用空間,如果有就直接在備用空間上構造元素,並調整迭代器 finish,使 vector變大。如果沒有備用空間了,就擴充空間,重新配置、移動數據,釋放原空間。​
其中​判斷是否有備用空間,就是判斷 finish是否與 end_of_storage 相等.如果
finish != end_of_storage,說明還有備用空間,否則已無備用空間。
當執行 push_back 操作,該 vector 需要分配更多空間時,它的容量(capacity)會增大到原來的 m 倍。​現在我們來均攤分析方法來計算 push_back 操作的時間複雜度。
假定有 n 個元素,倍增因子爲 m。那麼完成這 n 個元素往一個 vector 中的push_back​操作,需要重新分配內存的次數大約爲 logm(n),第 i 次重新分配將會導致複製 m^i (也就是當前的vector.size() 大小)箇舊空間中元素,因此 n 次 push_back操作所花費的總時間約爲 nxm/(m - 1):
時間複雜度計算。很明顯這是一個等比數列.那麼 n 個元素,n 次操作,每一次操作需要花費時間爲 m / (m - 1),這是一個常量。
所以,我們採用均攤分析的方法可知,vector 中 push_back 操作的時間複雜度爲常量時間.​
在STL中,vector的每次擴容都是2倍,也就是m=2.這樣,n次總的時間約爲n*2/(2-1) = 2n;那麼每一操作要花的時間就是2,因此是常量級。

8. string的實際數據存放在哪?vector呢?
string的內存肯定是在堆上的,string內部自己維護一個申請和釋放的指針,你外部應用是不用關心空間的申請,它內部會從堆上申請控件,析構時釋放空間。如果一直在棧上,肯定會爆掉。
vector這個對象存在棧中,然後棧中有指向vector所存數據的地址,數據保存在堆中。

9. vector和list的區別
vector和數組類似,擁有一段連續的內存空間,並且起始地址不變。vector和數組類似,擁有一段連續的內存空間,並且起始地址不變。
因此,它能夠高效地進行隨機存取,時間複雜度是O(1)。
但是,因爲其內存空間是連續的,所以在進行插入和刪除操作時,會造成內存塊的拷貝,因此時間複雜度爲O(n)。
另外,當數組內存空間不夠時,會重新申請一塊內動空間並進行內存拷貝。
list是由雙向鏈表實現的,因此內存空間是不連續的。
其只能通過指針訪問數據,所以list的隨機存取效率很低,時間複雜度爲O(n)。
不過由於鏈表自身的特點,能夠進行高效的插入和刪除。
vector和list對於迭代器的支持不同。
相同點在於,vector< int >::iterator和list< int >::iterator都重載了 “++ ”操作。
而不同點在於,在vector中,iterator支持 ”+“、”+=“,”<"等操作。而list中則不支持。

10. new和malloc的區別
new/delete是C++關鍵字,需要編譯器支持。malloc/free是庫函數,需要頭文件支持。
使用new操作符申請內存分配時無須指定內存塊的大小,編譯器會根據類型信息自行計算。而malloc則需要顯式地指出所需內存的尺寸
new會先調用operator new函數,申請足夠的內存(通常底層使用malloc實現)。然後調用類型的構造函數,初始化成員變量,最後返回自定義類型指針。delete先調用析構函數,然後調用operator delete函數釋放內存(通常底層使用free實現)。
C++允許重載new/delete操作符,特別的,佈局new的就不需要爲對象分配內存,而是指定了一個地址作爲內存起始區域,new在這段內存上爲對象調用構造函數完成初始化工作,並返回此地址。而malloc不允許重載。
new操作符從自由存儲區(free store)上爲對象動態分配內存空間,而malloc函數從堆上動態分配內存。自由存儲區是C++基於new操作符的一個抽象概念,凡是通過new操作符進行內存申請,該內存即爲自由存儲區。而堆是操作系統中的術語,是操作系統所維護的一塊特殊內存,用於程序的內存動態分配,C語言使用malloc從堆上分配內存,使用free釋放已分配的對應內存。自由存儲區不等於堆,如上所述,佈局new就可以不位於堆中。

數據結構

1. 哈希表和紅黑樹的對比
權衡三個因素: 查找速度, 數據量, 內存使用,可擴展性,有序性。
hash查找速度會比RB樹快,而且查找速度基本和數據量大小無關,屬於常數級別;而RB樹的查找速度是log(n)級別。並不一定常數就比log(n) 小,因爲hash還有hash函數的耗時。當元素達到一定數量級時,考慮hash。但若你對內存使用特別嚴格, 希望程序儘可能少消耗內存,那麼hash可能會讓你陷入尷尬,特別是當你的hash對象特別多時,你就更無法控制了,而且 hash的構造速度較慢。
紅黑樹是有序的,Hash是無序的,根據需求來選擇。
紅黑樹佔用的內存更小(僅需要爲其存在的節點分配內存),而Hash事先應該分配足夠的內存存儲散列表,即使有些槽可能棄用
紅黑樹查找和刪除的時間複雜度都是O(logn),Hash查找和刪除的時間複雜度都是O(1)。

2. 排序算法及其穩定性
十大經典排序算法
排序

3. AVL樹,紅黑樹,B樹,B+樹,Trie樹都分別應用在哪些現實場景中?
AVL樹: 最早的平衡二叉樹之一。應用相對其他數據結構比較少。windows對進程地址空間的管理用到了AVL樹。
紅黑樹: 平衡二叉樹,廣泛用在C++的STL中。如map和set都是用紅黑樹實現的。
B/B+樹: 用在磁盤文件組織 數據索引和數據庫索引。
Trie樹(字典樹): 用在統計和排序大量字符串,如自動機。

數據庫

1. 爲什麼數據庫用b+樹不用b樹和紅黑樹
首先說紅黑樹爲什麼不行:
1.紅黑樹必須存在內存裏的,數據庫表太大了,存不進去。
2.即使你找到了把紅黑樹存進硬盤的方法,紅黑樹查找一個節點最多要查logN層,每一層都是一個內存頁(雖然你只是想找一個節點,但硬盤必須一次讀一個頁。。),那麼一共logN次IO,傷不起阿!
先講下b樹和b+樹的區別:
b樹的所有節點都是數據節點,但b+樹只有葉子節點是數據節點,非葉子(內部)節點只起導向作用,不存儲實際數據。
b+樹的所有數據節點都在最下層(葉子節點層),相鄰節點有鏈表相連。
再說b樹爲什麼不如b+樹:
1.b樹的內部節點都是存儲實際數據的,比如一個節點是一個頁4096字節,其中每條數據128字節,那麼一個節點只能存32個數據項,那麼對應的孩子節點數最多爲33個,這顯然不夠用。而b+樹內部節點只作爲導向作用,只存一個整數就可以,4096/4=1024個數據項。這樣b+樹的每個節點的孩子數更多,整個樹的高度就更低,大大增加查詢效率。
2.b+樹的葉子節點有鏈表相連,適合範圍查詢,因爲相鄰頁直接讀取就好了。但b樹做不到這一點。
B樹
在這裏插入圖片描述
B+樹
在這裏插入圖片描述

2. 索引不宜太多
索引雖然是爲提高查詢的速度而設計, 它對insert/delete/update有負面影響,
但使用不當, 同樣會降低查詢的速度。索引太多,文件過大。還有第二點沒答出來,更新數據的時候索引也要更新,耗時.

計算機網絡

1.TCP和UDP的最完整的區別
TCP的優點: 可靠,穩定 TCP的可靠體現在TCP在傳遞數據之前,會有三次握手來建立連接,而且在數據傳遞時,有確認、窗口、重傳、擁塞控制機制,在數據傳完後,還會斷開連接用來節約系統資源。 **TCP的缺點: 慢,效率低,佔用系統資源高,易被攻擊 TCP在傳遞數據之前,要先建連接,這會消耗時間,而且在數據傳遞時,確認機制、重傳機制、擁塞控制機制等都會消耗大量的時間,**而且要在每臺設備上維護所有的傳輸連接,事實上,每個連接都會佔用系統的CPU、內存等硬件資源。 而且,因爲TCP有確認機制、三次握手機制,這些也導致TCP容易被人利用,實現DOS、DDOS、CC等攻擊。

UDP的優點: 快,比TCP稍安全 UDP沒有TCP的握手、確認、窗口、重傳、擁塞控制等機制,UDP是一個無狀態的傳輸協議,所以它在傳遞數據時非常快。沒有TCP的這些機制,UDP較TCP被攻擊者利用的漏洞就要少一些。但UDP也是無法避免攻擊的,比如:UDP Flood攻擊…… UDP的缺點: 不可靠,不穩定 因爲UDP沒有TCP那些可靠的機制,在數據傳遞時,如果網絡質量不好,就會很容易丟包。 基於上面的優缺點,那麼: 什麼時候應該使用TCP: 當對網絡通訊質量有要求的時候,比如:整個數據要準確無誤的傳遞給對方,這往往用於一些要求可靠的應用,比如HTTP、HTTPS、FTP等傳輸文件的協議,POP、SMTP等郵件傳輸的協議。 在日常生活中,常見使用TCP協議的應用如下: 瀏覽器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件傳輸 ………… 什麼時候應該使用UDP: 當對網絡通訊質量要求不高的時候,要求網絡通訊速度能儘量的快,這時就可以使用UDP。 比如,日常生活中,常見使用UDP協議的應用如下: QQ語音 QQ視頻 TFTP ……
有些應用場景對可靠性要求不高會用到UPD,比如長視頻,要求速率

小結TCP與UDP的區別:
1.基於連接與無連接;
2.對系統資源的要求(TCP較多,UDP少);
3.UDP程序結構較簡單;
4.流模式與數據報模式 ;
5.TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。

1.1. 爲什麼視頻用UDP
tcp需要進bai行三次握手,du建立會話需要時間,tcp在網絡擁塞的情況zhi下會進行tcp全局dao同步,根據網絡帶寬調整tcp滑動窗口大小,引起tcp傳輸速度下降,甚至有可能會導致tcp報文沒有帶寬可用,導致tcp餓死,而視頻傳輸對帶寬的需求比較大,對時延要求比較高,對丟包率要求不是那麼高,udp是面向無連接的傳輸協議,不需要進行三次握手,也沒有tcp的滑動窗口,報文也比tcp小,正好滿足了對視頻傳輸的要求。
明顯,當數據傳輸的性能必須讓位於數據傳輸的完整性、可控制性和可靠性時,TCP協議是當然的選擇。當強調傳輸性能而不是傳輸的完整性時,如:音頻和多媒體應用,UDP是最好的選擇。在數據傳輸時間很短,以至於此前的連接過程成爲整個流量主體的情況下,UDP也是一個好的選擇,如:DNS交換。把 SNMP建立在UDP上的部分原因是設計者認爲當發生網絡阻塞時,UDP較低的開銷使其有更好的機會去傳送管理數據。

TCP UDP
是否連接 面向連接 面向非連接
傳輸可靠性 可靠 不可靠
應用場合 少量數據 傳輸大量數據
速度

2.TCP還是UDP,網絡遊戲應該用哪種協議
1.根據各個論壇大家的經驗,用檢測工具檢測,流行的大型網絡遊戲用TCP和UDP的都有。也有的是client發送給server用TCP,server發給client用UDP(大航海時代)
2.普遍的推薦是,實時性非常強的遊戲(FPS遊戲如CS)才需要用UDP,因爲要儘量減少延遲,TCP特有的可靠處理機制導致丟包重發會有較大的延遲。否則用TCP,例如MMORPG遊戲用TCP,棋牌類遊戲更不必說就是用TCP。
3.著名開源網絡引擎RakNet用的是UDP實現,但它同時註明了UDP有很多的麻煩,RakNet用了很多方法來克服這些麻煩。http://gafferongames.com/networking-for-game-programmers/udp-vs-tcp/這篇文章講得不錯,說明了爲什麼實時性強的網絡遊戲(CS等)需要用UDP而不是TCP。
4.但UDP協議因爲太過基礎,如果不是購買商業級網絡引擎而是自行開發,會在這上面花費很大的人力並且還不一定達到低延遲的效果,在研發週期短壓力大情況下更是如此。如果這樣那還不如TCP,簡單穩定,乾脆直接,畢竟TCP是經過了周密設計和多年考驗。對玩家來說,偶爾的延遲或許比較討厭,但總掉線或出錯可就是難以忍受了。

3. OSI七層模型和TCP/IP五層模型
第7層應用層—直接對應用程序提供服務,應用程序可以變化,但要包括電子消息傳輸,應用程序,如FTP,SMTP,HTTP
第6層表示層—格式化數據,以便爲應用程序提供通用接口。這可以包括加密服務,編碼方式,圖像編解碼、URL字段傳輸編碼
第5層會話層—在兩個節點之間建立端連接。此服務包括建立連接是以全雙工還是以半雙工的方式進行設置,儘管可以在層4中處理雙工方式,建立會話,SESSION認證、斷點續傳
第4層傳輸層—常規數據遞送-面向連接或無連接。包括全雙工或半雙工、流控制和錯誤恢復服務 ,進程和端口
第3層網絡層—本層通過尋址來建立兩個節點之間的連接,它包括通過互連網絡來路由和中繼數據,路由器,防火牆、多層交換機
第2層數據鏈路層—在此層將數據分幀,並處理流控制。本層指定拓撲結構並提供硬件尋址,網卡,網橋,交換機
第1層物理層—原始比特流的傳輸電子信號傳輸和硬件接口數據發送時,從第七層傳到第一層,接受方則相反。中繼器,集線器、網線、HUB

4.ping下面是基於哪個協議
使用的是ICMP協議,是“Internet Control Message Protocol”(Internet控制消息協議)的縮寫,是TCP/IP協議族的一個子協議,用於在IP主機、路由器之間傳遞控制消息。控制消息是指網絡通不通、主機是否可達、路由是否可用等網絡本身的消息。這些控制消息雖然並不傳輸用戶數據,但是對於用戶數據的傳遞起着重要的作用。
它是用來檢查網絡是否通暢或者網絡連接速度的命令。它所利用的原理是這樣的:利用網絡上機器IP地址的唯一性,給目標IP地址發送一個數據包,再要求對方返回一個同樣大小的數據包來確定兩臺網絡機器是否連接相通,時延是多少。

計算機操作系統

1. 線程間通信的幾種實現方式
同步和互斥
當有多個線程的時候,經常需要去同步這些線程以訪問同一個數據或資源。例如,假設有一個程序,其中一個線程用於把文件讀到內存,而另一個線程用於統計文件中的字符數。當然,在把整個文件調入內存之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的線程,操作系統會把兩個線程當作是互不相干的任務分別執行,這樣就可能在沒有把整個文件裝入內存時統計字數。爲解決此問題,你必須使兩個線程同步工作。

所謂同步,是指在不同進程之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先後次序來運行,這種先後次序依賴於要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。

所謂互斥,是指散佈在不同進程之間的若干程序片斷,當某個進程運行其中一個程序片段時,其它進程就不能運行它們之中的任一程序片段,只能等到該進程運行完這個程序片段後纔可以運行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

多線程同步和互斥有幾種實現方法
線程間的同步方法大體可分爲兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用系統內核對象的單一性來進行同步,使用時需要切換內核態與用戶態,而用戶模式就是不需要切換到內核態,只在用戶態完成操作。
用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區。
內核模式下的方法有:事件,信號量,互斥量。
1、臨界區:通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。
2、互斥量:爲協調共同對一個共享資源的單獨訪問而設計的。
3、信號量:爲控制一個具有有限數量用戶資源而設計。
4、事 件:用來通知線程有一些事件已發生,從而啓動後繼任務的開始。

2. 進程間通信的幾種實現方式
(1)管道(pipe)及有名管道(named pipe):管道可用於具有親緣關係的父子進程間的通信,有名管道除了具有管道所具有的功能外,它還允許無親緣關係進程間的通信。
(2)信號(signal):信號是在軟件層次上對中斷機制的一種模擬,它是比較複雜的通信方式,用於通知進程有某事件發生,一個進程收到一個信號與處理器收到一箇中斷請求效果上可以說是一致的。
(3)消息隊列(message queue):消息隊列是消息的鏈接表,它克服了上兩種通信方式中信號量有限的缺點,具有寫權限得進程可以按照一定得規則向消息隊列中添加新信息;對消息隊列有讀權限得進程則可以從消息隊列中讀取信息。
(4)共享內存(shared memory):可以說這是最有用的進程間通信方式。它使得多個進程可以訪問同一塊內存空間,不同進程可以及時看到對方進程中對共享內存中數據得更新。這種方式需要依靠某種同步操作,如互斥鎖和信號量等。
(5)信號量(semaphore):主要作爲進程之間及同一種進程的不同線程之間得同步和互斥手段。
(6)套接字(socket):這是一種更爲一般得進程間通信機制,它可用於網絡中不同機器之間的進程間通信,應用非常廣泛。

3. 協程瞭解
協程是一種線程工作的機制。正常情況下,一個線程處理一個函數或者是一個程序,但是協程是在處理函數的時候,這個函數還有調用其他函數,也就是還有子函數,所以協程在處理的時候使用這一個線程去處理這兩個函數,在處理的時候呢,並不是按順序執行完一個函數再去執行另一個函數,而是執行A函數到一半的時候又去執行函數,這兩個函數相互交替執行,這麼個機制,叫做協程。
之前說過多線程在執行的時候,是搶奪資源式的執行任務,在讀取同一個變量的時候可能會發生衝突,所以爲了防止發生衝突,我們用到了線程鎖。通過隊列,一個線程寫信息,一個線程讀消息,線程鎖控制線程的等待和隊列的讀寫。
但是協程不會發生這種衝突,因爲只有一個線程在進行讀取數據的操作,不存在同時讀寫衝突。所以協程是控制自身,在子程序之間進行切換
協程的特點在於是一個線程執行,那和多線程比,協程有何優勢?
最大的優勢就是協程極高的執行效率。因爲子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優勢就越明顯。
第二大優勢就是不需要多線程的鎖機制,因爲只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多

因爲協程是一個線程執行,那怎麼利用多核CPU呢?最簡單的方法是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的性能。
Python對協程的支持是通過generator實現的。
在generator中,我們不但可以通過for循環來迭代,還可以不斷調用next()函數獲取由yield語句返回的下一個值。
但是Python的yield不但可以返回一個值,它還可以接收調用者發出的參數。

4. 進程和線程的區別
1.一個線程只能屬於一個進程,而一個進程可以有多個線程,但至少有一個線程。線程依賴於進程而存在。
2.進程在執行過程中擁有獨立的內存單元,而多個線程共享進程的內存。(資源分配給進程,同一進程的所有線程共享該進程的所有資源。同一進程中的多個線程共享代碼段(代碼和常量),數據段(全局變量和靜態變量),擴展段(堆存儲)。但是每個線程擁有自己的棧段,棧段又叫運行時段,用來存放所有局部變量和臨時變量。)
3.進程是資源分配的最小單位,線程是CPU調度的最小單位
4.系統開銷: 由於在創建或撤消進程時,系統都要爲之分配或回收資源,如內存空間、I/o設備等。因此,操作系統所付出的開銷將顯著地大於在創建或撤消線程時的開銷。類似地,在進行進程切換時,涉及到整個當前進程CPU環境的保存以及新被調度運行的進程的CPU環境的設置。而線程切換隻須保存和設置少量寄存器的內容,並不涉及存儲器管理方面的操作。可見,進程切換的開銷也遠大於線程切換的開銷。
5.通信:由於同一進程中的多個線程具有相同的地址空間,致使它們之間的同步和通信的實現,也變得比較容易。進程間通信IPC,線程間可以直接讀寫進程數據段(如全局變量)來進行通信——需要進程同步和互斥手段的輔助,以保證數據的一致性。在有的系統中,線程的切換、同步和通信都無須操作系統內核的干預
6.進程編程調試簡單可靠性高,但是創建銷燬開銷大;線程正相反,開銷小,切換速度快,但是編程調試相對複雜。
7.進程間不會相互影響 ;線程一個線程掛掉將導致整個進程掛掉
8.進程適應於多核、多機分佈;線程適用於多核

5. 調度算法
1、先來先服務(FCFS)/先進先出(FIFO)調度算法
2、短作業優先調度算法(SJF)
3、高優先權調度算法
4、高響應比優先調度算法
5、簡單的時間片輪轉法(RR—Round Robin)
調度算法詳解

Linux

1. gcc/g++編譯文件過程
.c文件 -----> 預處理後的.i文件
內容: 頭文件的展開,宏定義的替換,備註的去除

gcc  -E hello.c -o hello.i  

.i文件----->彙編語言的.s文件
內容:預處理後的文件轉換爲彙編語言代碼

gcc -S hello.i -o hello.s 

.s文件----->二進制文件.o
內容:彙編文件編譯爲二進制文件

gcc -c hello.s -o hello.o

生成執行文件
內容:組合函數庫等的代碼到目標文件中。

gcc hello.o -o hello 

1.1 gcc鏈接的作用
鏈接主要是爲了解決多個文件之間符號引用的問題。編譯時編譯器只對單個文件進行處理,如果該文件裏面需要引用到其他文件中符號(例如全局變量或者某個函數庫中的函數),那麼這時在這個文件中該符號的地址是沒法確定的,只能等鏈接器把所有的目標文件連接到一起才能確定最終的地址,最終生成可執行文件。
當所有目標文件都生成之後,gcc就在內部調用鏈接器ld完成鏈接工作。在鏈接階段,所有的目標文件被安排在可執行文件的恰當位置。

2. 靜態庫和動態庫的作用
在這裏插入圖片描述
靜態庫:之所以成爲【靜態庫】,是因爲在鏈接階段,會將彙編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中。因此對應的鏈接方式稱爲靜態鏈接。
試想一下,靜態庫與彙編生成的目標文件一起鏈接爲可執行文件,那麼靜態庫必定跟.o文件格式相似。其實一個靜態庫可以簡單看成是一組目標文件(.o/.obj文件)的集合,即很多目標文件經過壓縮打包後形成的一個文件。靜態庫特點總結:
l 靜態庫對函數庫的鏈接是放在編譯時期完成的。
l 程序在運行時與函數庫再無瓜葛,移植方便。
l 浪費空間和資源,因爲所有相關的目標文件與牽涉到的函數庫被鏈接合成一個可執行文件。
動態庫:l 空間浪費是靜態庫的一個問題。
l 另一個問題是靜態庫對程序的更新、部署和發佈頁會帶來麻煩。如果靜態庫liba.lib更新了,所以使用它的應用程序都需要重新編譯、發佈給用戶(對於玩家來說,可能是一個很小的改動,卻導致整個程序重新下載,全量更新)。
動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入。不同的應用程序如果調用相同的庫,那麼在內存裏只需要有一份該共享庫的實例,規避了空間浪費問題。動態庫在程序運行是才被載入,也解決了靜態庫對程序的更新、部署和發佈頁會帶來麻煩。用戶只需要更新動態庫即可,增量更新。
動態庫特點總結:
l 動態庫把對一些庫函數的鏈接載入推遲到程序運行的時期。
l 可以實現進程之間的資源共享。(因此動態庫也稱爲共享庫)
l 將一些程序升級變得簡單。
l 甚至可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯示調用)。
Window與Linux執行文件格式不同,在創建動態庫的時候有一些差異。
l 在Windows系統下的執行文件格式是PE格式,動態庫需要一個DllMain函數做出初始化的入口,通常在導出函數的聲明時需要有_declspec(dllexport)關鍵字。
l Linux下gcc編譯的執行文件默認是ELF格式,不需要初始化入口,亦不需要函數做特別的聲明,編寫比較方便。
與創建靜態庫不同的是,不需要打包工具(ar、lib.exe),直接使用編譯器即可創建動態庫。

3. 用戶態和內核態
內核態:cpu可以訪問內存的所有數據,包括外圍設備,例如硬盤,網卡,cpu也可以將自己從一個程序切換到另一個程序。
用戶態:只能受限的訪問內存,且不允許訪問外圍設備,佔用cpu的能力被剝奪,cpu資源可以被其他程序獲取。
爲什麼要有用戶態和內核態?
由於需要限制不同的程序之間的訪問能力, 防止他們獲取別的程序的內存數據, 或者獲取外圍設備的數據, 併發送到網絡, CPU劃分出兩個權限等級 – 用戶態和內核態。

牛客C++面經
牛客C++面試題
leetcode面試題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章