首先說一下,這些東西,有的是必須掌握的,有的是面試時你講出來就是閃光點。自己把握。把握不好的都搞懂。實在不行背下來。
由於時間關系,總結的比較隨意,有的就直接貼鏈接了,希望理解一下。
第一篇:基礎(必須熟稔於心)
1. const關鍵字(反義詞mutable)
(1)定義時必須初始化
(2)指針可以是const指針,也可以是指向const對象的指針
(3)定義為const的形參,在函數體內不能被修改
(4)后面加Const,表示該成員函數不會修改類的成員變量。本質是修飾隱藏的*this指針。加const的成員函數可以被const或非const對象調用,但是普通成員函數(無const修飾)只能被普通對象(無const修飾)調用。
(5)前面加const,表示返回值是const類型的
(6)Const修飾成員變量時,不能在聲明時初始化,必須在構造函數的列表里初始化
2. static關鍵字
(1)在函數中,一個static的變量在此函數被調用過程中維持其值不變
(2)在模塊中(不在函數中),一個static變量可以被模塊中所有函數訪問,但不可以被模塊外的其他函數訪問。
(3)在模塊內,一個static的函數只可以被這一模塊內的其他函數調用。
(4)類中的static成員變量屬於整個類,不能在類內進行定義,只能在類的作用域中進行定義。
(5)類中的static成員函數屬於整個類,不包含this指針,只能調用static成員函數。
(6)static全局變量只能在本文件中使用,限制了它的作用域;而普通全局變量可以在其他文件中使用。
(7)static局部變量必須初始化,普通局部變量不需要;前者所在的函數被多次調用時,依據上一次的結果進行計算,而后者所在的函數被調用時,還是原來的值。雖然靜態局部變量在函數調用結束后仍然存在,但其他函數不能引用它。
(8)static函數限定在本文件中使用,雖然其他文件可以知道它的存在,但不能使用;而普通函數默認是extern的,其他文件也可以使用。Static函數有兩個好處:一是其他文件可以定義相同名字的函數,不會沖突;二是靜態函數不能為其他函數使用。
3. extern關鍵字
(1)extern C,表示該段代碼以C語言進行編譯。
(2)extern 放在變量或函數前,說明該變量或函數定義在別的文件中,提示編譯器去其他模塊中找定義,相當於前向聲明。
4. 指針和引用的區別
(1)引用是直接訪問,指針是間接訪問。
(2)引用是變量的別名,本身不單獨分配自己的內存空間,而指針有自己的內存空間
(3)引用綁定內存空間(必須賦初值),是一個變量別名不能更改綁定,可以改變對象的值。
總的來說:引用既具有指針的效率,又具有變量使用的方便性和直觀性
5.explicit是干什么用的 ?
聲明為explicit的構造函數不能在隱式轉換中使用。可以阻止不應該允許的經過轉換構造函數進行的隱式轉換的發生。
6.inline的用法
https://www.cnblogs.com/fnlingnzb-learner/p/6423917.html
7.還有一些關鍵字,一時間想不起來了,有時間在整理吧。
第二篇:C++中的內存
1. new/delete與malloc/free之間的區別?
(1)malloc/free是C/C++語言的標准庫函數,new/delete是C++的運算符
(2)new能夠自動分配空間大小,malloc傳入參數。
(3)new/delete能進行對對象進行構造和析構函數的調用進而對內存進行更加詳細的工作,而malloc/free不能。
既然new/delete的功能完全覆蓋了malloc/free,為什么C++還保留malloc/free呢?因為C++程序經常要調用C函數,而C程序只能用malloc/free管理動態內存。
2. 淺拷貝與深拷貝?為什么要使用深拷貝?
(1)淺拷貝 char * arr[] = “hello”; char * a = arr;淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一內存空間。
(2)深拷貝 char * arr[] = “hello”; char * a = new char[]; a =arr; 深拷貝不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經過深拷貝后的指針是指向兩個不同地址的指針。
淺拷貝會出現的問題:(1)淺拷貝只是拷貝了指針,使得兩個指針指向同一地址,這樣在對象結束調用析構函數時,會造成同一份資源析構兩次,即delete同一塊內存兩次,造成程序崩潰;(2)淺拷貝使得兩個指針指向同一個地址,任何一方的改動都會影響另一方;(3)同一個空間,第二次釋放失敗,導師無法操作該空間,造成內存泄漏。
3.深入談談堆和棧?
(1)分配和管理方式不同 :
堆是動態分配的,其空間的分配和釋放都由程序員控制。
棧由編譯器自動管理。棧有兩種分配方式:靜態分配和動態分配。靜態分配由編譯器完成,比如局部變量的分配。動態分配由alloca()函數進行分配,但是棧的動態分配和堆是不同的,它的動態分配是由編譯器進行釋放,無須手工控制。
(2)產生碎片不同
對堆來說,頻繁的new/delete或者malloc/free勢必會造成內存空間的不連續,造成大量的碎片,使程序效率降低。
對棧而言,則不存在碎片問題,因為棧是先進后出的隊列,永遠不可能有一個內存塊從棧中間彈出。
(3)生長方向不同
堆是向着內存地址增加的方向增長的,從內存的低地址向高地址方向增長。
棧是向着內存地址減小的方向增長,由內存的高地址向低地址方向增長。
60.內存的靜態分配和動態分配的區別?
(1)時間不同。靜態分配發生在程序編譯和連接時。動態分配則發生在程序調入和執行時。
(2)空間不同。堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。alloca,可以從棧里動態分配內存,不用擔心內存泄露問題,當函數返回時,通過alloca申請的內存就會被自動釋放掉。
第三篇:類、繼承、多態
1. 實現string類
class string { public: String() //初始化 : _pstr(new char[1]) {} String(const char * pstr ); //普通構造函數 : _pstr(new char[strlen(pstr) + 1]()) { strcpy(_pstr,pstr); } String(const String & rhs); //復制構造函數 : _pstr(new char[strlen(pstr) + 1]()) { strcpy(_pstr, rhs.pstr); } String(String && rhs); //移動構造函數,右值引用 : _pstr(rhs._pstr) { rhs.pstr = NULL; } String & operator=(const String & rhs) //重載復制運算符函數 { if(this != & rhs) { delete [] _pstr; _pstr = new char[strlen(rhs._pstr) + 1](); strcpy(_pstr, rhs._pstr); } return *this; } String & operator=(String && rhs) //移動賦值運算符函數 { if(this != &rhs) { delete [] _pstr; _pstr = rhs._pstr; rhs._pstr = NULL; } return * this; } ~String() { delete [] _pstr; } friend std::ostream &operator<<(std::ostream & os, const String & rhs); private: char * _pstr; }; std::ostream & operator<<(std::ostream & os, const String & rhs) { os << rhs._pstr; return os; }
2. 什么是繼承?什么是多態?
(1)
(2)C++中多態機制主要體現在兩個方面,一個是函數的重載,一個是接口的重寫。接口多態指的是“一個接口多種形態”。每一個對象內部都有一個虛表指針,該虛表指針被初始化為本類的虛表。所以在程序中,不管你的對象類型如何轉換,但該對象內部的虛表指針是固定的,所以呢,才能實現動態的對象函數調用,這就是C++多態性實現的原理。
多態的基礎是繼承,需要虛函數的支持,簡單的多態是很簡單的。子類繼承父類大部分的資源,不能繼承的有構造函數,析構函數,拷貝構造函數,operator=函數,友元函數等等
多態作用:
隱藏實現細節,代碼能夠模塊化;2. 接口重用:為了類在繼承和派生的時候正確調用。
多態的兩個必要條件:
1. 一個基類的指針或者引用指向派生類的對象;2.虛函數
3. 什么是靜態關聯?什么是動態關聯?
靜態關聯是程序在編譯階段就能確定實際執行動作,程序運行時才能確定執行的動作叫動態關聯。
4. 虛函數是如何實現的?
編譯時若基類中有虛函數,編譯器為該的類創建一個一維數組的虛表,存放是每個虛函數的地址。基類和派生類都包含虛函數時,這兩個類都建立一個虛表。構造函數中進行虛表的創建和虛表指針的初始化。在構造子類對象時,要先調用父類的構造函數,初始化父類對象的虛表指針,該虛表指針指向父類的虛表。執行子類的構造函數時,子類對象的虛表指針被初始化,指向自身的虛表。每一個類都有虛表。虛表可以繼承,如果子類沒有重寫虛函數,那么子類虛表中仍然會有該函數的地址,只不過這個地址指向的是基類的虛函數實現。派生類的虛表中虛函數地址的排列順序和基類的虛表中虛函數地址排列順序相同。當用一個指針/引用調用一個函數的時候,被調用的函數是取決於這個指針/引用的類型。即如果這個指針/引用是基類對象的指針/引用就調用基類的方法;如果指針/引用是派生類對象的指針/引用就調用派生類的方法,當然如果派生類中沒有此方法,就會向上到基類里面去尋找相應的方法。這些調用在編譯階段就確定了。當涉及到多態性的時候,采用了虛函數和動態綁定,此時的調用就不會在編譯時候確定而是在運行時確定。不在單獨考慮指針/引用的類型而是看指針/引用的對象的類型來判斷函數的調用,根據對象中虛指針指向的虛表中的函數的地址來確定調用哪個函數。
5. 虛函數與純虛函數的區別?含有純虛函數的類叫什么?
(1)虛函數與純虛函數都可以在子類中重寫。
(2)純虛函數只有定義,沒有實現;虛函數既要有定義,也要有實現的代碼。
(3)純虛函數 vritual void print() = 0; 虛函數 vritual void print() { XXX };
(4)包含純虛函數的類叫抽象類,該類不可以創建對象;而含有虛函數的類可以創建對象。
6. 多重繼承如何解決?
虛擬繼承解決了多重繼承的問題。如:A是基類,B、C繼承自A,D多重繼承自B和C,那么D訪問A中的變量時,就會出現二義性錯誤。如果類B和類C虛擬繼承自A,那么類D只會有A的一個對象,這樣就解決了二義性問題。或者用成員限定符解決二義性。
7. 派生類與虛函數概述
(1)派生類繼承的函數不能定義為虛函數。虛函數是希望派生類重新定義。如果派生類沒有重新定義某個虛函數,則在調用的時候會使用基類中定義的版本。
(2)派生類中函數的聲明必須與基類中定義的方式完全匹配。
(3)基類中聲明為虛函數,則派生類也為虛函數。
8. 為什么析構函數要定義為虛函數?哪些函數不能是虛函數?
(1)如果析構函數不是虛函數,那么釋放內存時候,編譯器會使用靜態聯編,認為p就是一個基類指針,調用基類析構函數,這樣子類對象的內存沒有釋放,造成內存泄漏。定義成虛函數以后,就會動態聯編,先調用子類析構函數,再基類。
(2)1)普通函數只能重載,不能被重寫,因此編譯器會在編譯時綁定函數。
2)構造函數是知道全部信息才能創建對象,然而虛函數允許只知道部分信息。
3)內聯函數在編譯時被展開,虛函數在運行時才能動態綁定函數。
4)友元函數 因為不可以被繼承。
5)靜態成員函數 只有一個實體,不能被繼承。父類和子類共有。
9. 析構函數可以拋出異常嗎?為什么不能拋出異常?除了資源泄露,還有其他需考慮的因素嗎?
C++標准指明析構函數不能、也不應該拋出異常。C++異常處理模型最大的特點和優勢就是對C++中的面向對象提供了最強大的無縫支持。那么如果對象在運行期間出現了異常,C++異常處理模型有責任清除那些由於出現異常所導致的已經失效了的對象(也即對象超出了它原來的作用域),並釋放對象原來所分配的資源, 這就是調用這些對象的析構函數來完成釋放資源的任務,所以從這個意義上說,析構函數已經變成了異常處理的一部分。
1)如果析構函數拋出異常,則異常點之后的程序不會執行,如果析構函數在異常點之后執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源泄漏的問題。
2)通常異常發生時,c++的機制會調用已經構造對象的析構函數來釋放資源,此時若析構函數本身也拋出異常,則前一個異常尚未處理,又有新的異常,會造成程序崩潰的問題。
11.動態鏈接庫的兩種使用方法及特點?
1).載入時動態鏈接,模塊非常明確調用某個導出函數,使得他們就像本地函數一樣。這需要鏈接時鏈接那些函數所在DLL的導入庫,導入庫向系統提供了載入DLL時所需的信息及DLL函數定位。
2)運行時動態鏈接。
第四篇:STL
1. STL各類容器(3個順序+4個關聯+1個無序關聯)的實現原理及使用情形
(1)vector:可變數組大小。支持快速隨機訪問。在尾部之外的位置插入或刪除元素可能很慢。
(2)deque:雙端隊列。支持快速隨機訪問。在頭尾插入或刪除碎度很快。
(3)list:雙向鏈表。只支持雙向順序訪問。在list的任何位置進行插入或刪除操作速度都很快。
(4)set/multiset:只有鍵值,可以把set當做集合使用。multiset可以存放相同的元素,set只能存放不同的元素。
(5)map/multimap:鍵值對,每一個元素都是pair,pair的第一個元素是關鍵字,第二個元素是值。這兩者的區別就在於multimap可以存放多個相同的關鍵字,map則不可以。
(3)與(5)的底層實現都是紅黑樹,動態平衡二叉樹。插入和刪除等操作的時間復雜度是O(logn)(6)中的底層實現是哈希函數。
(6)unordered_map 映射
unordered_multimap 多重映射
unordered_set 集合
unordered_multiset 多重集合
- 什么是STL?(面試主要是深入某個點問你)
六大組件:容器、迭代器、適配器、算法、函數對象、配置器(透明)
(1)容器(略,自己看)
(2)迭代器:隨機訪問迭代器(Random Access Iterator)
雙向迭代器(Bidirectional Iterator)
前向迭代器(Forward Iterator)
輸入迭代器(Input Iterator)
輸出迭代器(Output Iterator)
(3)適配器就是Interface(接口),對容器、迭代器和算法進行包裝,但其實質還是容器、迭代器和算法,只是不依賴於具體的標准容器、迭代器和算法類型,容器適配器可以理解為容器的模板,迭代器適配器可理解為迭代器的模板,算法適配器可理解為算法的模板。
常見的容器適配器有:stack、queue、priority_queue(不支持迭代器訪問)
前面簡要提到了適配器的概念,適配器相當於提供了一個接口,使得某些不適用於特定對象的方法可以被該對象所用,適配器形象的功能圖解如所示,圖中,容器或函數對象無法直接應用於算法,因此,必須有一種中間過渡機制來實現兩者的匹配,這就是適配器,本質上,適配器是使一事物的行為類似於另一事物的行為的一種機制。
(4)STL將算法庫分為4組,前3個在algorithm頭文件中描述,而第4個在numeric頭文件中描述:
非修改式序列操作:不改變容器的內容,如find()、for_each()等。
修改式序列操作:可以修改容器中的內容,如transform()、random_shuffle()、copy等。
排序和相關操作:包括各種排序函數等,如sort()等。
通用數字運算:計算兩個容器的內部乘積等。
(5)函數對象是可以以函數方式與()結合使用的任意對象,包括:(functor-仿函數)
函數名;指向函數的指針;重載了()操作符的類對象(即定義了函數operator()()的類)。
(6)一級配置器和二級配置器
空間配置器,就是用來配置、管理和釋放空間的,給所有的容器包括算法提供生存空間。
作用:
(1)提高代碼復用率,功能模塊化。
(2)減少內存碎片問題。
(3)提高內存分配的效率。
(4)有內存不足時的應對措施。
(5)隱藏實際中對存儲空間的分配及釋放細節,確保所有被分配的存儲空間都最終獲得釋放。
(5)考慮多線程狀態。
考慮到小型區塊可能導致的內存碎片問題,設置了兩級空間配置器。分別為:一級空間配置器、二級空間配置器。當區塊大於128字節,調用一級空間配置器;小於等於128字節,為了降低額外開銷,用底層較復雜的二級空間配置器。
一級空間配置器
用malloc()、free()、realloc()等C函數執行內存配置、釋放、重配置操作,並實現出類似的C++new_hanle的機制
二級空間配置器
SGI二級空間配置器的原理是:當區塊小於128字節,則以內存池(memory pool)管理,回收時管理一個用戶歸還的空間,類似於哈希桶。每次配置一塊內存,並維護對應的自由鏈表(free_list)。為了方便管理,SGI二級配置器會對齊到8個字節。(例:需要30字節的空間,自動調整到32字節)。維護16個free_lists,各自管理大小分別為
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字節。
3. 什么是智能指針?底層實現?
(1)C++11中引入了智能指針的概念,方便管理堆內存。使用普通指針,容易造成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等,使用智能指針能更好的管理堆內存。
(2)理解智能指針需要從下面三個層次:
從較淺的層面看,智能指針是利用了一種叫做RAII(資源獲取即初始化)的技術對普通的指針進行封裝,這使得智能指針實質是一個對象,行為表現的卻像一個指針。
智能指針的作用是防止忘記調用delete釋放內存和程序異常的進入catch塊忘記釋放內存。另外指針的釋放時機也是非常有考究的,多次釋放同一個指針會造成程序崩潰,這些都可以通過智能指針來解決。
智能指針還有一個作用是把值語義轉換成引用語義。
(3)智能指針#include<memory>,unique_ptr,shared_ptr,weak_ptr(弱引用智能指針)。
(4)unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現)。相比與原始指針unique_ptr用於其RAII的特性,使得在出現異常的情況下,動態資源能得到釋放。unique_ptr指針本身的生命周期:從unique_ptr指針創建時開始,直到離開作用域。離開作用域時,若其指向對象,則將其所指對象銷毀(默認使用delete操作符,用戶可指定其他操作)。
(5)shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減為0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。
(6)weak_ptr是為了配合shared_ptr而引入的一種智能指針,因為它不具有普通指針的行為,沒有重載operator*和->,它的最大作用在於協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。
第五篇:操作系統編程
1. 多進程與多線程之間的區別?(最好要了解透徹)
1)進程數據是分開的:共享復雜,需要用IPC,同步簡單;多線程共享進程數據:共享簡單,同步復雜
2)進程創建銷毀、切換復雜,速度慢 ;線程創建銷毀、切換簡單,速度快
3)進程占用內存多, CPU利用率低;線程占用內存少, CPU利用率高
4)進程編程簡單,調試簡單;線程 編程復雜,調試復雜
5)進程間不會相互影響 ;線程一個線程掛掉將導致整個進程掛掉
6)進程適應於多核、多機分布;線程適用於多核
線程所私有的:
線程id、寄存器的值、棧、線程的優先級和調度策略、線程的私有數據、信號屏蔽字、errno變量、
2. 什么是進程池和線程池?
在面向對象程序編程中,對象的創建與析構都是一個較為復雜的過程,較費時間,所以為了提高程序的運行效率盡可能減少創建和銷毀對象的次數,特別是一些很耗資源的對象創建和銷毀。
所以我們可以創建一個進程池(線程池),預先放一些進程(線程)進去,要用的時候就直接調用,用完之后再把進程歸還給進程池,省下創建刪除進程的時間,不過當然就需要額外的開銷了。
利用線程池與進程池可以使管理進程與線程的工作交給系統管理,不需要程序員對里面的線程、進程進行管理。
以進程池為例
進程池是由服務器預先創建的一組子進程,這些子進程的數目在 3~10 個之間(當然這只是典型情況)。線程池中的線程數量應該和CPU數量差不多。
進程池中的所有子進程都運行着相同的代碼,並具有相同的屬性,比如優先級、 PGID 等。
當有新的任務來到時,主進程將通過某種方式選擇進程池中的某一個子進程來為之服務。相比於動態創建子進程,選擇一個已經存在的子進程的代價顯得小得多。至於主進程選擇哪個子進程來為新任務服務,則有兩種方法:
主進程使用某種算法來主動選擇子進程。最簡單、最常用的算法是隨機算法和Round Robin(輪流算法)。
主進程和所有子進程通過一個共享的工作隊列來同步,子進程都睡眠在該工作隊列上。當有新的任務到來時,主進程將任務添加到工作隊列中。這將喚醒正在等待任務的子進程,不過只有一個子進程將獲得新任務的“接管權”,它可以從工作隊列中取出任務並執行之,而其他子進程將繼續睡眠在工作隊列上。
當選擇好子進程后,主進程還需要使用某種通知機制來告訴目標子進程有新任務需要處理,並傳遞必要的數據。最簡單的方式是,在父進程和子進程之間預先建立好一條管道,然后通過管道來實現所有的進程間通信。在父線程和子線程之間傳遞數據就要簡單得多,因為我們可以把這些數據定義為全局,那么它們本身就是被所有線程共享的。
3. 進程間的通信方式有哪些?如何實現的?
信號和信號量是不同的,它們雖然都可以用來同步和互斥,但是信號是使用信號處理器來進行的,信號量是使用P,V操作來實現的。
消息隊列是比較高級的一種進程間通信方式,因為它真的是可以在進程間傳送message,傳送普通字符串也可以。
一個消息隊列可以被多個進程所共享(IPC((Inter-Process Communication,進程間通信))就是在這個基礎上進行的);如果一個進程消息太多,一個消息隊列放不下,也可以用多於一個的消息隊列(不管管理可能會比較復雜)。共享消息隊列的進程所發送的消息除了message本身外還有一個標志,這個標志可以指明該消息將由哪個進程或者哪類進程接受。每一個共享消息隊列的進程針對這個隊列也有自己的標志,可以用來申明自己的身份。
共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。
套接字( socket ) : 套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同機器間的進程通信
4. 簡述inux中的同步與異步機制?
同步:
所謂同步,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。也就是必須一件一件事做,等前一件做完了才能做下一件事。
例如普通B/S模式(同步):提交請求->等待服務器處理->處理完畢返回 這個期間客戶端瀏覽器不能干任何事
異步:
異步的概念和同步相對。當一個異步過程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者。
例如 ajax請求(異步): 請求通過事件觸發->服務器處理(這是瀏覽器仍然可以作其他事情)->處理完畢
5.簡述阻塞與非阻塞?
阻塞:
阻塞調用是指調用結果返回之前,當前線程會被掛起(線程進入非可執行狀態,在這個狀態下,cpu不會給線程分配時間片,即線程暫停運行)。函數只有在得到結果之后才會返回。
有人也許會把阻塞調用和同步調用等同起來,實際上他是不同的。對於同步調用來說,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回,它還會搶占cpu去執行其他邏輯,也會主動檢測io是否准備好。
非阻塞
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。
再簡單點理解就是:
1. 同步,就是我調用一個功能,該功能沒有結束前,我死等結果。
2. 異步,就是我調用一個功能,不需要知道該功能結果,該功能有結果后通知我(回調通知)
3. 阻塞,就是調用我(函數),我(函數)沒有接收完數據或者沒有得到結果之前,我不會返回。
4. 非阻塞,就是調用我(函數),我(函數)立即返回,通過select通知調用者
同步IO和異步IO的區別就在於:數據拷貝的時候進程是否阻塞
阻塞IO和非阻塞IO的區別就在於:應用程序的調用是否立即返回
綜上可知,同步和異步,阻塞和非阻塞,有些混用,其實它們完全不是一回事,而且它們修飾的對象也不相同。
6.簡述Linux中的5種I/O模式?
1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O復用(select 和poll,還有epoll) (I/O multiplexing)!!!!!(必須搞懂,超究極容易遇到)
4)信號驅動I/O (signal driven I/O (SIGIO))
5)異步I/O (asynchronous I/O (the POSIX aio_functions))
其中前4種都是同步,最后一種才是異步。
詳情見:https://www.cnblogs.com/chaser24/p/6112071.html
7. 什么是死鎖?四個死鎖的條件?避免死鎖的方法?
死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。
產生原因:競爭資源,和進程推進順序非法
四個條件:
1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。
2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。
3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。
4)環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。
四種解決辦法:預防(破壞死鎖發四種發生條件中的一個或多個)、避免(銀行家算法:如果一個進程增加的資源請求會導致死鎖,則不允許此分配,記住當時算的那張矩陣圖)、檢測與解除
8. Linux的任務調度機制是什么?
Linux 分實時進程和普通進程,實時進程應該先於普通進程而運行。實時進程:
1) FIFO(先來先服務調度)
2) RR(時間片輪轉調度)。
每個進程有兩個優先級(動態優先級和實時優先級),實時優先級就是用來衡量實時進程是否值得運行的。 非實時進程有兩種優先級,一種是靜態優先級,另一種是動態優先級。實時進程又增加了第三種優先級,實時優先級。優先級越高,得到CPU時間的機會也就越大。
9.標准庫函數與系統調用的區別?
系統調用:是操作系統為用戶態運行的進程和硬件設備(如CPU、磁盤、打印機等)進行交互提供的一組接口,即就是設置在應用程序和硬件設備之間的一個接口層。inux內核是單內核,結構緊湊,執行速度快,各個模塊之間是直接調用的關系。linux系統上到下依次是用戶進程->linux內核->硬件。其中系統調用接口是位於Linux內核中的,整個linux系統從上到下可以是:用戶進程->系統調用接口->linux內核子系統->硬件,也就是說Linux內核包括了系統調用接口和內核子系統兩部分;或者從下到上可以是:物理硬件->OS內核->OS服務->應用程序,操作系統起到“承上啟下”作用,向下管理物理硬件,向上為操作系服務和應用程序提供接口,這里的接口就是系統調用了。
庫函數:把函數放到庫里。是把一些常用到的函數編完放到一個lib文件里,供別人用。別人用的時候把它所在的文件名用#include<>加到里面就可以了。一類是c語言標准規定的庫函數,一類是編譯器特定的庫函數。
系統調用是為了方便使用操作系統的接口,而庫函數則是為了人們編程的方便。
第六篇:網絡編程
1. 分別簡述三次握手與四次揮手的過程?
三次握手:C----->SYN K
S------>ACK K+1 SYN J
C------->ACK J+1
DONE!
client 的 connect 引起3次握手
server 在socket, bind, listen后,阻塞在accept,三次握手完成后,accept返回一個fd,
2. tcp和udp之間的區別?
1)基於連接與無連接
2)對系統資源的要求(TCP較多,UDP少)
3)UDP程序結構較簡單
4)流模式與數據報模式
5)TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證
6)TCP有擁塞控制和流量控制,UDP沒有
TCP提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之后才能傳輸數據。TCP提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快
3. select、poll、epoll之間的區別?
https://www.cnblogs.com/Anker/p/3265058.html(參考閱讀)
4. epoll有哪些觸發模式?
(必須非常詳盡的解釋水平觸發和邊緣觸發的區別,以及邊緣觸發在編程中要做哪些更多的確認)
注意:epoll必須深入理解,必須要張口就來,必須隨心所欲說出來。
epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是“高速”模式。LT模式下,只要這個fd還有數據可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作,而在ET(邊緣觸發)模式中,它只會提示一次,直到下次再有數據流入之前都不會再提示了,無論fd中是否還有數據可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小於請求值。
也就是說在LT模式的情況下一定要確認收發的數據包的buffer是不是足夠大如果收發數據包大小大於buffer的大小的時候就可能會出現數據丟失的情況。
5. 若是有大規模的數據連接,並發模型如何設計?
Epoll+線程池(epoll可以采用libevent處理)