(1) 指針和引用的區別.html
語法上:指針和引用沒有關係,引用就是一個已經存在的對象的別名。對引用的任何操做等價於對被引用對象的操做。c++
1.當引用被建立時,它必須被初始化。而指針則能夠在任什麼時候候被初始化。未初始化的引用不合法,未初始化的指針合法但危險。(懸空指針)程序員
2.一旦一個引用被初始化爲指向一個對象,它就不能被改變爲對另外一個對象的引用。而指針則能夠在任什麼時候候指向另外一個對象。web
3.不可能有NULL引用。必須確保引用是和一塊合法的存儲單元關聯。由於不存在指向空值的引用,因此在使用引用以前不需驗證它的合法性,而使用指針須要驗證合法性。因此使用引用的代碼效率要比使用指針的要高。redis
底層實現上(彙編層):引用是經過指針實現的。在程序一層只要直接涉及對引用變量的操做,操做的老是被引用變量,編譯器實現了一些操做,老是在引用前面加上*。實際上如int a=0;int &b=a;中變量b中存放的是a的地址,int*const b=&a;但編譯器讓對b的操做都自動爲*b,算法
指針的大小:在32位系統中是4字節,在64位系統中是8字節。由於指針指示的是一個內存地址,因此與操做系統有關。但這個也不是絕對正確的,由於64位系統兼容32位,對應的32程序的指針也是32位的,此時使用sizeof()獲得的即是4(即32位),例如編寫win32程序時,指針就是32位。數據庫
(2) extern,const,static,volatile關鍵字(定義,用途)express
extern關鍵字的做用:編程
1、extern用在變量或者函數的聲明前,用來講明「此變量/函數是在別處定義的,要在此處引用」。extern聲明不是定義,即不分配存儲空間。也就是說,在一個文件中定義了變量和函數, 在其餘文件中要使用它們, 能夠有兩種方式:1.使用頭文件,在頭文件中聲明它們,而後其餘文件去包含頭文件;2.在其餘文件中直接extern,就可使用。windows
2、extern C做用:extern 「C」 不但具備上述傳統的聲明外部變量的功能,還具備告知C++連接器使用C函數規範來連接的功能。 還具備告知C++編譯器使用C規範來命名的功能。(由於C++支持函數的重載,C++編譯器若是以C++規範翻譯這個函數名時會把函數名翻譯得很亂。)
static關鍵字的做用:
1、函數體內static變量的做用範圍爲該函數體,該變量的內存只被分配一次,其值在下次調用的時候仍然維持原始值,要是函數體內有對該變量進行更改的行爲,再次訪問時變量的值是更改後的值。
2、 在文件內的static全局變量和static全局函數能夠被文件內可見,不能被其餘文件可見。其餘文件內能夠有相同名字的其餘的對象和函數,即文件範圍的static能夠限定變量在在文件範圍內部,對其餘文件不可見。而非static全局變量和全局函數能夠在文件間使用。
3、在類中的static成員變量屬於整個類全部,對類的全部對象只有一份拷貝。存儲在靜態存儲區。靜態數據成員能夠被初始化,初始化在類體外進行,而前面不加static,以避免與通常靜態變量或對象相混淆;若未對靜態數據成員賦初值,則編譯器會自動爲其初始化爲0。全局變量和靜態變量存儲在靜態數據區,在全局靜態數據區,內存中全部的字節默認值都是0x00。
4、在類中的static成員函數屬於整個類全部,static成員函數不接受this指針,沒有this指針,於是只能訪問類的static成員變量和static成員函數。不能做爲虛函數。
不能將靜態成員函數定義爲虛函數:虛函數依靠vptr和vtable來處理。vptr是一個指針,在類的構造函數中初始化,而且只能用this指針來訪問它,由於它是類的一個成員,而且vptr指向保存虛函數地址的vtable.對於靜態成員函數,它沒有this指針,因此沒法訪問vptr. 這就是爲什麼static函數不能爲virtual。
虛函數的調用關係:this -> vptr -> vtable ->virtual function
const關鍵字的做用:const意味着「只讀」, const離誰近,誰就不能被修改;
1、想要阻止一個變量被改變,可使用const關鍵字。在定義該const關鍵字時,一般要對它進行初始化,由於之後再也沒有機會去改變它。
2、對於指針來講,能夠指定指針自己爲const,也能夠指定指針所指向的數據爲const,或者兩者同時指定爲const。
3、在一個函數聲明中,const能夠修飾形參,代表它是一個輸入參數,在函數內部不能改變其值。若是形參是一個指針,爲了防止在函數內部修改指針指向的數據,就能夠用 const 來限制。
4、對於類的成員函數,若指定爲const,則代表其實一個常函數,只有類的成員函數有常函數的說法,不能修改類的非靜態成員變量。當肯定類成員函數不會改變成員變量時,必定將其設爲const的;類的const的對象只能調用其const成員函數,由於調用非const函數就有改變變量屬性的風險。
5、對於類的成員函數,有時候必須制定其返回值爲const,以使得其返回值不能爲左值。效率考慮,參數傳遞,返回值儘可能返回const&,除了必須值返回(返回的是一個函數內的臨時對象,離開做用域對象清除,此時不能引用返回,必須值返回。)和可變引用&(如對象的操做符重載,須要連續賦值的狀況,或cout的狀況,必須使用可變引用)
6. const修飾成員變量,必須在構造函數列表中初始化;同時成員數據爲引用的也必須在構造函數列表中初始化;static成員數據的初始化,放在類定義外,不加static,若static成員數據沒有初始化,則默認爲0;
volatile關鍵字的做用:
volatile int iNum = 10;
volatile 指出 iNum 是隨時可能發生編譯器覺察不到的變化的變量,變量可能被某些編譯器未知的因素(好比:操做系統、硬件或者其它線程等更改),編譯器覺察不到。
程序執行中每次使用它的時候必須從原始內存地址中去讀取,於是編譯器生成的彙編代碼會從新從iNum的原始內存地址中去讀取數據。而不是隻要編譯器發現iNum的值沒有發生變化(由於多是已經發生了變化編譯器覺察不到),就只讀取一次數據,並放入寄存器中,下次直接從寄存器中去取值(優化作法),而是從新從內存中去讀取(再也不優化).
(3) #define 和const的區別(編譯階段、安全性、內存佔用等)
1編譯器處理方式不一樣
define宏是在預處理階段展開。
const常量是編譯運行階段使用。
2 類型和安全檢查不一樣
define宏沒有類型,不作任何類型檢查,僅僅是展開。
const常量有具體的類型,在編譯階段會執行類型檢查。
3 存儲方式不一樣
define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配內存。
const常量會在內存中分配(能夠是堆中也能夠是棧中)
(4)關於typedef和#define;
typedef 定義一種類型的別名,不一樣於宏,它不是簡單的字符串替換。如:
typedef void (*pFunParam)(int); pFunx b[10]; 定義了一個函數指針類型的數組,該函數指針指向的函數原型void fun(int)的函數實體
typedef 與 #define的區別案例:
STL中經過將typedef 寫在類內部和模板的泛化偏特化,特別針對指針類型實現迭代器的特性萃取。 struct裏邊寫typedef int Aa並不會使得 對象的空間增大。
(5)C++程序中內存使用狀況分析(堆和棧的區別)
C++中,內存分紅5個區,他們分別是棧、堆、自由存儲區(能夠和堆不區分)、全局/靜態存儲區,常量存儲區。一個由C/C++編譯的程序佔用的內存分爲如下幾個部分 :
棧(堆棧):由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。 。
堆: 通常由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配組織方式卻是相似於鏈表。經常使用C++的new/delete 運算符C的malloc()/free(),realloc等函數;
全局區(靜態區)(static): 全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。 程序結束後有系統釋放 。函數和成員函數代碼也存儲在靜態區。
常量區:存放常量,字符串,保證常量不被修改; 程序結束後由系統釋放 。
程序代碼區:存放函數體的二進制代碼。靜態區。
(6)new 與 malloc的異同處,new和delete是如何實現的。
new的實現過程:
1.調用相應的 operator new(size_t) 函數,若是 operator new(size_t) 不能成功得到內存,則調用 new_handler() 函數用於處理new失敗問題。能夠用set_ new_handler()函數設置不一樣的new_handler() 函數實現不一樣的內存分配失敗時的處理策略。operator new(size_t) 函數能夠重載,可是必須包含size_t參數,不一樣的重載形式,對應到不一樣形式的new,如placement_new。 operator new的內存分配底層實現調用的也是malloc()函數。
2.在分配到的動態內存塊上 調用相應類型的構造函數構造對象並返回其首地址。若是構造函數調用失敗。則自動調用operate new對應的operator delete;釋放內存。
new包含的分配內存和構造對象兩個過程必須都要完成。
delete的實現過程:
1,先調用對應內存上對象的析構函數、2調用相應的 operator delete(void *) 函數。 operator delete(void *)也是調用free()釋放內存。
new 與 malloc的異同處:1,new/delete屬於運算符,malloc/free屬於庫函數。2.malloc在申請內存空間失敗之後會返回一個null指針,而new在申請內存空間失敗之後會返回一個異常。也可使用nothrow讓new失敗返回空指針,照顧c程序員的編程習慣。3.malloc只負責申請內存,他不能對內存進行初始化,new不只能申請內存,還能夠對內存進行初始化和調用對應對象的構造函數。new是C++的運算符,底層的內存分配動做仍然是經過malloc()實現,經過new_handle引入對內存分配失敗的處理機制。New沒有相似relloc的機制。
malloc分配的內存不夠的時候,能夠用realloc擴容。realloc是從堆上分配內存的。當擴大一塊內存空間時,realloc()試圖直接從堆上現存的數據後面的那些字節中得到附加的字節,若是可以知足,天然天下太平;若是數據後面的字節不夠,那麼就使用堆上第一個有足夠大的自由塊,現存的數據而後就被拷貝至新的位置,而老塊則放回到堆上。這句話傳遞的一個重要的信息就是數據可能被移動。使用realloc無需手動把舊的內存空間釋放. 由於realloc 函數改變地址後會自動釋放舊的內存。
new若是分配失敗了會拋出bad_alloc的異常,而malloc失敗了會返回NULL。所以對於 new,正確的姿式是採用try...catch語法,而malloc則應該判斷指針的返回值。爲了兼容不少c程序員的習慣,C++也能夠採用new(nothrow)的方法禁止拋出異常而返回NULL。 new(nothrow)也是經過重載operator new實現的一種placement new。New是使用new hander 處理內存分配失敗的狀況。
assert 一種預處理宏,使用單個表達式做爲斷言條件。若條件爲false, assert輸出信息並終止程序的執行。爲true do nothing。
(7) C和C++的區別
C++ =C+OOP(面向對象,多態)+GP(泛型編程,模板,STL,模板元編程)+異常處理。
sizeof()類的大小:
class A {};: sizeof(A) = 1;//空類大小爲1;g++中每一個空類型的實例佔1字節空間。
class A { virtual Fun(){} }; sizeof(A) = 4;: //存在虛函數,即存在一個虛指針
class A { static int a; };: sizeof(A) = 1;//靜態成員不算類的大小,和空類同樣
class A { int a; };: sizeof(A) = 4;
class A { static int a; int b; };: sizeof(A) = 4;
(8)C++中的重載,重寫,重定義(隱藏)的區別:
重載:全局函數之間或同一個類體裏的成員函數之間,函數名相同,參數不一樣(參數個數,類型)。注意成員函數是不是const的也是不一樣的重載。函數是不是const只有成員函數。函數返回值不參與重載斷定。
重寫:子類對父類的重寫,要求子類函數與父類函數徹底相同,除了修飾符能夠不一樣,好比父類private,子類能夠是public。此外,最重要的一點就是,父類的函數必須是虛函數,也就是要有virtual來修飾。父類的虛函數,子類能夠重寫出本身的版本,能夠不重寫直接繼承父類的版本。對於父類的純虛函數,子類必須重寫本身的版本;有純虛函數函數的類爲抽象類,抽象類不可實例化。抽象基類相似於Java的接口,都不可實例化。抽象基類中的純虛函數相似於接口中的方法,實現接口的類必須實現接口中的方法。
重定義:子類有父類同名函數,父類的函數就會被隱藏,調用子類對象只能調用子類的函數。這種狀況只是簡單的做用域限制,不具備面向對象的特性。
(9)析構函數通常寫成虛函數的緣由。
何時類須要定義析構函數:若是類的數據成員中不存在成員(指針)與動態分配的內存相關聯,咱們通常不用本身定義析構函數,而是採用默認的析構函數析構類對象。一旦與動態分配的內存相關聯,爲了防止內存泄露,咱們須要本身定義析構函數,手動釋放動態分配的內存。由於系統默認的析構函數是沒法幫助釋放動態內存的。由於系統只會釋放棧內存,分配的動態內存(堆內存)必須由程序手頭釋放。
三法則:若是一個類須要析構函數,幾乎也須要定義賦值構造函數和重載賦值操做符。由於此時類的成員有指針,此時不能使用默認的複製構造,賦值運算符。
析構函數通常寫成虛函數的緣由:
在類的繼承體系中,在分析基類析構函數爲何要定義爲虛析構函數以前,咱們要先明白虛函數存在的意義就是爲了動態綁定,實現面向對象的特性之一 :多態。
咱們知道經過基類的指針或者引用能夠實現對虛函數的動態綁定,那麼當咱們經過一個基類指針或者引用來析構一個對象時,咱們是沒法判斷基類如今綁定的對象是基類仍是派生類,若是析構函數不是虛函數,那麼基類指針只會調用基類的析構函數,如此就發生了一些不應發生的事。只有將析構函數定義爲虛函數,才能經過動態綁定,調用對應的析構函數版本,正確的析構類對象。
能夠這麼說:任何class只要有virtual函數都幾乎肯定也要有一個virtual析構函數(引用自Effective C++ 條款7)
(10)構造函數爲何通常不定義爲虛函數
構造函數不能爲虛函數主要有如下兩點:
1、必要性分析:當定義派生類對象時,它會主動依次調用構造函數,順序爲基類的構造函數->一級派生類構造函數->二級派生類構造函數….直到當前派生類的構造函數調用完畢爲止,到此派生類對象生成。 而虛函數存在的意義爲動態綁定,從上一段話可知,它會從基類開始依次自動調用相應的構造函數,根本就不存在動態綁定的必要。
2、內存角度分析:
構造函數的做用是生成相應的類對象。虛函數的動態綁定是依據一張虛函數表來確認的最終綁定到哪個虛函數版本。 而調用構造函數以前,咱們對類對象所作的操做僅限於分配內存,尚未對內存進行初始化。此時,內存空間上也不存在虛函數表,所以,按照這樣的執行順序,虛函數的動態綁定是實現不了的。
(11)構造函數或者析構函數中調用虛函數會怎樣。
從語法上講,調用徹底沒有問題。可是從效果上看,每每不能達到須要的目的。
1.構造:派生類對象構造期間會首先進入基類的構造函數,在基類構造函數執行時繼承類的成員變量還沒有初始化,此時調用虛函數,調用的必定是基類的虛函數版本,由於繼承類的成員變量還沒有初始化,此時對象類型是基類類型,vptr指向的也是基類的vptb,調用不到派生類的虛函數版本。此時虛函數和普通函數沒有區別了。起不到多態的效果。
2.析構:假設一個派生類的對象進行析構,首先調用了派生類的析構,而後再調用基類的析構時,遇到了一個虛函數,這個時候有兩種選擇:Plan A是編譯器調用這個虛函數的基類版本,那麼虛函數則失去了運行時調用正確版本的意義;Plan B是編譯器調用這個虛函數的派生類版本,可是此時對象的派生類部分已經完成析構,「數據成員就被視爲未定義的值」,這個函數調用會致使未知行爲。
總結:調用虛函數時,對應的基類或者派生類對象都必須是一個完整正確的對象狀態。而在構造或者析構的過程當中對象不是一個完整的狀態。
(12)析構函數能拋出異常嗎
不能。C++標準指明析構函數不能、也不該該拋出異常。
(1) 若是析構函數拋出異常,則異常點以後的程序不會執行,若是析構函數在異常點以後執行了某些必要的動做好比釋放某些資源,則這些動做不會執行,會形成諸如資源泄漏的問題。
(2) 一般異常發生時,c++的異常處理機制會調用已經構造對象的析構函數來釋放資源,此時若析構函數自己也拋出異常,則前一個異常還沒有處理,又有新的異常,會形成程序崩潰的問題。
(13)純虛函數和抽象類
virtual ~myClass()=0;有純虛函數的類是抽象類,不能實例化,抽象類的功能相似於Java的接口。
(14)多態的實現條件,虛指針vptr, 虛函數表vbtl
靜態綁定和動態綁定:
靜態綁定是經過重載和模板技術實現,在編譯的時候肯定。動態綁定經過虛函數和繼承關係來實現,執行動態綁定,在運行的時候肯定。
多態實現有幾個條件:1.虛函數 2.一個基類的指針或引用指向派生類的對象
基類指針在調用成員函數(虛函數)時,就會經過對象的虛指針vptr去查找該對象的vptl虛函數表。虛函數表的地址vptr在每一個對象的首地址。查找該虛函數表中該虛函數的函數指針進行調用。
每一個對象中保存的只是一個虛函數表的指針,C++內部爲每個類維持一個虛函數表,該類的對象都指向這同一個虛函數表。
虛函數表中爲何就能準確查找相應的函數指針呢?由於在類設計的時候,派生類的虛函數表是直接從基類繼承過來的,若是派生類的虛函數overiide某個基類的虛函數,那麼虛函數表的函數指針就會被替換,所以能夠根據指針準確找到該調用哪一個函數。
虛函數在設計上還具備封裝和抽象的做用。好比抽象工廠模式???
(15)深拷貝和淺拷貝的區別(舉例說明深拷貝的安全性)
淺拷貝在針對有指針的類時,會致使一個後果。兩個指針指向同一塊內存,在釋放內存時,該內存會被釋放兩次,這就會有內存泄露的危險。
深拷貝,指先獲取一塊內存,而後將要拷貝的內容複製過去。兩個指針指向不一樣的內存,就不會有內存泄露的風險了。
淺拷貝是沒有定義拷貝構造函數時系統的默認拷貝構造函數的拷貝方式。
因此,在對含有指針成員的對象(有動態分配內存的對象)進行拷貝時,必需要本身定義拷貝構造函數,使拷貝後的對象指針成員有本身的內存空間,即進行深拷貝,這樣就避免了內存泄漏發生。
(16)什麼狀況下會調用拷貝構造函數(三種狀況)
用類的一個對象去初始化另外一個對象時;當函數的形參值傳遞對象時;當函數的返回值是以值傳遞對象。
(17)struct內存對齊方式,爲何要對齊?怎麼修改默認對齊方式?struct,union
從0位置開始存儲; 成員變量存儲的起始位置是該變量大小的整數倍; 結構體總的大小是其最大元素的整數倍,不足的後面要補齊;。當CPU訪問正確對齊的數據時,它的運行效率最高。
Struct和union: union:
(1). 共用體和結構體都是由多個不一樣的數據類型成員組成, 但在任何同一時刻, 共用體只存放了一個被選中的成員, 而結構體的全部成員都存在。
(2). 對於共用體的不一樣成員賦值,原來成員的值就不存在了,成爲了無定義狀態。 而對於結構體的不一樣成員賦值是互不影響的
修改對齊方式:#pragma pack (2) /*指定按2字節對齊*/
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
(18)內存泄露的定義,如何檢測與避免?內存檢查工具的瞭解。
內存泄漏:內存泄漏指的是在程序裏動態申請的內存在使用完後,沒有進行釋放,致使這部份內存沒有被系統回收,長此以往,可能致使程序內存不斷增大,系統內存不足。排除內存泄漏對於程序的穩健型特別重要,尤爲是程序須要長時間、穩定地運行時。
檢查工具:1.Linux下經過工具valgrind檢測。2.VS中的
定位內存泄露:
2. 在windows平臺下經過CRT中的庫函數進行檢測;(只適用於Debug環境下)
VS2013中在Debug環境下,經過CRT庫自己的內存泄漏檢測函數可以分析出內存泄漏,定位內存泄露的位置。
檢查方法:一.在main函數最後一行,加上一句_CrtDumpMemoryLeaks()。調試程序,天然關閉程序讓其退出(不要定製調試),查看輸出以下:
{453} normal block at 0x02432CA8, 868 bytes long.
被{}包圍的453就是咱們須要的內存泄漏定位值(編譯器的內存分配編號),868 bytes long就是說這個地方有868比特內存沒有釋放。此時只能知道在哪一次的內存分配(編譯器的內存分配編號)在程序結束沒有釋放發生內存泄露,並無定位到具體的內存泄露的代碼行。
接下來,定位代碼位置:
在main函數第一行加上:_CrtSetBreakAlloc(453); 意思就是讓程序執行到申請453這塊內存的位置中斷。而後調試程序,……程序中斷了。查看調用堆棧。雙擊咱們的代碼調用的最後一個函數(棧頂),這裏是CDbQuery::UpdateDatas(),就定位到了申請內存的代碼:
在線上運行的時候:
對象計數
方法:在對象構造時計數++,析構時--,每隔一段時間打印對象的數量;若發現對象的個數只增不減的異常,則能夠推測該類的對象發生了內存泄露。
優勢:沒有性能開銷,幾乎不佔用額外內存。定位結果精確。
缺點:侵入式方法,需修改現有代碼,並且對於第三方庫、STL容器、腳本泄漏等因沒法修改代碼而沒法定位。
Hook Windows系統API
方法:使用windows分配內存的系統Api:HeapAlloc/HeapRealloc/HeapFree(new/malloc的底層調用),記錄分配點,按期打印。
優勢:非侵入式方法,無需修改現有文件,檢查全面,對第三方庫、腳本庫等等都能統計到。
缺點:記錄內存須要佔用大量內存,並且多線程環境須要加鎖。
(19)成員初始化列表的概念,爲何用成員初始化列表會快一些(性能優點)?
使用成員初始化列表定義構造函數是顯式地初始化類的成員,若是不用成員初始化列表,那麼類對象對本身的類成員分別進行的是一次隱式的默認構造函數的調用(在進入函數體以前)初始化類的成員,和一次拷貝賦值運算符的調用(進入函數體以後),若是是類對象,這樣作效率就得不到保障。
類類型的數據成員對象在進入構造函數體前己經構造完成,也就是說在成員初始化列表處進行對象的構造工做,調用構造函數,在進入函數體以後,進行的是對己構造好的類對象賦值,又調用個拷貝賦值操做符才能完成(若是並未提供,則使用編譯器默認的按成員賦值行爲))。
(20)必須在構造函數初始化列表裏進行初始化的數據成員有哪些?
(1) 常量成員,由於常量只能初始化不能賦值,因此必須放在初始化列表裏面
(2) 引用類型,引用必須在定義的時候初始化,而且不能從新賦值,因此也要寫在初始化列表裏面
(3) 沒有默認構造函數的類類型,若沒有提供顯示初始化式,則編譯器隱式使用成員類型的默認構造函數,若成員類沒有默認構造函數,則編譯器嘗試使用默認構造函數將會失敗。使用初始化列表能夠沒必要調用默認構造函數來初始化,而是直接調用拷貝構造函數初始化。
對象成員:A類的成員是B類的對象,在構造A類時需對B類的對象進行構造,當B類沒有默認構造函數時須要在A類的構造函數初始化列表中對B類對象初始化
類的繼承:派生類在構造函數中要對自身成員初始化,也要對繼承過來的基類成員進行初始化,當基類沒有默認構造函數的時候,經過在派生類的構造函數初始化列表中調用基類的構造函數實現。
(21) C++的調用慣例(C++函數調用的壓棧過程)
在函數調用時,第一個進棧的是主函數中調用點後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。 靜態變量在全局靜態局。
當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
(22)C++的四種強制轉換static_cast,const_cast,dynamic_cast,reinterpret_cast
C的強制轉換表面上看起來功能強大什麼都能轉,可是轉化不夠明確,不能進行錯誤檢查,容易出錯。
static_cast:
static_cast用的最多,對於各類隱式轉換如int轉double,非const轉const,void*轉類型指針等。
C轉換 :(type) expression C++轉換:static_cast<type>(expression)
const_cast:
只能夠用來移除表達式的常量性;
dynamic_cast:在多態的環境下向下轉型。
用在繼承體系多態環境中,將「指向基類對象的指針或引用」轉型爲「指向派生類對象的指針或引用」。若指向基類對象的指針或引用在運行時接受的一個派生類的對象,則轉型成功。不然轉型失敗,會以一個null指針(當轉型對象是指針)或一個exception(當轉型對象是引用)表現出來。
reinterpret_cast:轉換函數指針類型:
例:設有一數組,存儲的是函數指針,有特定類型;
//FuncPtr是函數指針,指向某個函數,該函數無參數,返回類型爲void。
typedef void (*FuncPtr)();
FuncPtr funcPtrArray[10];//funcPtrArray是個數組,有10個FuncPt
若想將如下函數的一個指針n放入該數組:
dosomething的類型與funcPtrArray接受的類型不一樣。funcPtrArray內各函數指針所指向的函數返回類型是void。
funcPtrArray[0]=&dosomething;//錯
funcPtrArray[0]=reinterpret_cast<FuncPtr>(&dosomething);//對。
注:函數指針的轉型動做不具移植性(C++不保證全部的函數指針都能以此方式從新呈現)。某些狀況下這樣的轉型可能會致使不正確的結果。
(23)多重繼承,菱形結構,虛基類,虛繼承,以及在多繼承虛繼承下多態的實現,虛繼承下對象的內存分佈
多重繼承在菱形結構的情形下,每每致使virtual base classes(虛擬基類)的需求。在non-virtual base的狀況下,若是派生類對於基類有多條繼承路徑,那麼派生類會有不止一個基類部分,使用虛繼承,讓基類爲virtual能夠消除這樣的複製現象。然而虛基類也可能致使另外一成本:其實現作法經常利用指針,指向"virtual base class"部分,所以對象內可能出現一個(或多個)這樣的指針。
(24)內聯函數有什麼優勢?內聯函數與宏定義的區別?
虛函數不能夠內聯:內聯在編譯的時候替換,但只有在運行時才能肯定調用哪一個虛函數)
(25)STL容器迭代器失效總結.
vector 迭代器失效狀況:
1.push_back()必定使得end()返回的迭代器失效,若發生capacity()增加,致使vector容器的全部迭代器都失效。由於發生了數據移動。
2. erase()使得刪除點和刪除點後面的迭代器都失效。失效的迭代器不能夠進行迭代器操做,如++iter,*iter,指向的是位置內存。但erase(iter)能夠返回下一個有效的迭代器。erase的返回值是刪除元素下一個元素的迭代器。這個迭代器是vector內存調整事後新的有效的迭代器。
list,set, map 迭代器失效狀況:
使用了不連續分配的內存,刪除當前的iterator,僅僅會使當前的iterator失效, erase迭代器返回值爲void,因此要採用erase(iter++)的方式刪除迭代器。如:
解析dataMap.erase(iter++);這句話分三步走,先把iter值傳遞到erase裏面,而後iter自增,而後執行erase,因此iter在失效前已經自增了。
list中,erase(*iter)會返回下一個有效迭代器的值, erase(iter)也會返回void,也需使用erase(iter++)的方式刪除迭代器。
deque迭代器失效狀況:
1.在deque容器首部或者尾部插入元素不會使得任何迭代器失效。2. 在其首部或尾部刪除元素則只會使指向被刪除元素的迭代器失效。3.在deque容器的任何其餘位置的插入和刪除操做將使指向該容器元素的全部迭代器失效。
(26.)對繼承和組合的理解
繼承是一種is-a關係,組合是一種has-a關係。在功能上來看,它們都是實現功能重用,代碼複用的最經常使用的有效的設計技巧,都是在設計模式中的基礎結構。類繼承容許咱們根據本身的實現來覆蓋重寫父類的實現細節,父類的實現對於子類是可見的,因此咱們通常稱之爲白盒複用。對象持有一般用來實現配接,如,STL中deque和stack,總體類對部分類的功能的複用接口的修飾,使其成爲另外一種特定的面貌。總體類和部分類之間不會去關心各自的實現細節。即它們之間的實現細節是不可見的,故成爲黑盒複用。
繼承中父類定義了子類的部分實現,而子類中又會重寫這些實現,修改父類的實現,設計模式中認爲這是一種破壞了父類的封裝性的表現。這個結構致使結果是父類實現的任何變化,必然致使子類的改變。然而組合這不會出現這種現象。對象的組合還有一個優勢就是有助於保持每一個類被封裝,並被集中在單個任務上(類設計的單一原則)。這樣類的層次結構不會擴大,通常不會出現不可控的龐然大類。而類的繼承就可能出來這些問題,因此通常編碼規範都要求類的層次結構不要超過3層。
通常優先優先使用對象組合,而不是類繼承。
(27)c++ main函數執行以前須要作哪些準備
1. 設置棧指針
2. 將non-local static對象構造完成。
non-local static對象包括文件下(全局),命名空間下,類的static對象成員,non-local static對象要在main函數以前構造。函數中的static對象是local static對象,local static對象直到方法被調用的時候,才進行初始化,並且只初始化一次。local static 變量(局部靜態變量)一樣是在main前就已分配內存,第一次使用時初始化。全部的static對象都分配在全局區,程序結束才釋放內存。
3. 將未初始化部分的賦初值:數值型short,int,long等爲0,bool爲FALSE,指針爲NULL,等等,即.bss段的內容
4..運行全局構造器,估計是C++中構造函數之類的吧
5.將main函數的參數,argc,argv等傳遞給main函數,而後才真正運行main函數。,
全局變量、non-local static變量在main執行以前就已分配內存並初始化;local static 變量一樣是在main前就已分配內存,第一次使用時初始化。
(28)手寫智能指針?shared_ptr何時改變引用計數?weak_ptr如何解決引用傳遞?這些是線程安全的嗎?線程安全的智能指針是哪個?
智能指針:使用普通指針,容易形成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等,使用智能指針能更好的管理堆內存。
shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,可是對象的讀取須要加鎖。
引用計數改變:構造函數中計數初始化爲1;拷貝構造函數中計數值加1;析構函數中引用計數減1;賦值運算符中,左邊的對象引用計數減/1,右邊的對象引用計數加1;在賦值運算符和析構函數中,若是減1後爲0,則調用delete銷燬對象並釋放它佔用的內存
unique_ptr「惟一」擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(經過禁止拷貝語義、只有移動語義來實現)。
weak_ptr是爲了配合shared_ptr而引入的一種智能指針,不具備普通指針的行爲,沒有重載operator*和->,做用在於協助shared_ptr工做,觀測資源的使用狀況。成員函數use_count()能夠觀測資源的引用計數,成員函數lock()從被觀測的shared_ptr得到一個可用的shared_ptr對象,從而操做資源。
shared_ptr的循環引用致使的內存泄漏怎麼解決?
https://www.cnblogs.com/itZhy/archive/2012/10/01/2709904.html
使用weak_ptr
http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html
類成員變量用memset()設置會有什麼問題?
不能,由於memset會破壞成員變量對象的內部結構(都賦值爲0),當類對象析構時,析構到該成員變量對象時,該成員變量對象不能正常進行析構操做,最終致使crash。
注:若是類包含虛函數,則不能用 memset 來初始化類對象。由於包含虛函數的類對象都有一個虛指針指向虛函數表(vtbl),進行memset操做時,虛指針的值也要被overwrite,這樣一來,只要一調用虛函數,程序便崩潰。
(30)STL alloc實現,alloc的優點和侷限,STL中其餘的配置器
gnuC中使用了內存池設計,減少了小內存分配的分配次數,提升效率。減小內存的碎片化。可是同時內存池的設計只分配不釋放(只拿不還,服務容器),alloc在運行期間不會釋放分配的內存。這種佔用可能使得其餘的進程不能得到足夠的內存。在gunc4.9 中有其餘的配置器。給8k,16k,..., 128k比較小的內存片都維持一個空閒鏈表。
_pool_alloc, loki_allocator
(31)模板的用法與適用場景,模板泛化,偏特化,特化,可變模板參數,舉出實例。
(29)單例模式,C++實現一個線程安全的單例類;用C++設計一個不能被繼承的類;如何定義一個只能在堆上(棧上)生成對象? fianl對象
單例模式:一個類只能被實例化一次,並提供一個訪問它的全局訪問點。
餓漢和懶漢:懶漢式在第一次用到類實例的時候纔會去實例化,一般須要用加鎖機制實現線程安全。餓漢式在單例類定義的時候就進行實例化。使用no-local static變量存儲單例對象,類一加載就實例化。會提早佔用系統資源。
特色與選擇:因爲要進行線程同步,因此在訪問量比較大,或者可能訪問的線程比較多時,採用餓漢實現,能夠實現更好的性能。這是以空間換時間。在訪問量較小時,採用懶漢實現。這是以時間換空間。
分析:instance是非局部靜態變量,在main執行前就分配內存並初始化,是線程安全的。潛在問題在於no-local static對象(函數外的static對象)在不一樣編譯單元(可理解爲cpp文件和其包含的頭文件)中的初始化順序是未定義的。
使用場景: 在整個項目中須要一個共享訪問點或共享數據,或者相似的實體(有且只有一個,且須要全局訪問),那麼就能夠將其實現爲一個單例。
例如一個Web頁面上的計數器,能夠不用把每次刷新都記錄到數據庫中,使用單例模式保持計數器的值,並確保是線程安全的;
日誌類,一個應用每每只對應一個日誌實例;
管理器,好比windows系統的任務管理器就是一個例子,老是隻有一個管理器的實例。
單例模式經常與工廠模式結合使用,由於工廠只須要建立產品實例就能夠了,在多線程的環境下也不會形成任何的衝突,所以只須要一個工廠實例就能夠了。
只能創建在堆上:將析構函數設爲私有,類對象就沒法創建在棧上了。當對象創建在棧上時,是由編譯器分配內存空間的,編譯器調用構造函數來構造棧對象。當對象使用完後,編譯器會調用析構函數來釋放棧對象所佔的空間。編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性,其實不光是析構函數,只要是非靜態的函數,編譯器都會進行檢查。若是類的析構函數是私有的,則編譯器不會在棧空間上爲類對象分配內存。
只能創建在棧上:在類的內部重載operator new(),並設爲私有便可。只有使用new運算符,對象纔會創建在堆上,所以,只要禁用new運算符就能夠實現類對象只能創建在棧上。
設計一個fianl類: 法一:構造析構放在私有,public中放一個static接口函數,用來建立和釋放類的實例。,可是該類只能獲得位於堆上的實例,而得不到位於棧上實例。
法二:友元函數;https://www.cnblogs.com/luxiaoxun/archive/2013/06/07/3124948.html
C++11中已經有了final關鍵字:做用是指定一個類成爲一個不能被繼承的類(final class),或者指定類的虛函數不能被該類的繼承類重寫(override),。
使用場景:當一個方法被final修飾後。表示該方法不能被子類重寫。好比涉及到某些須要統一處理的需求。
make: 一個自動化編譯工具,依據makefile文件(編譯規則) 批處理編譯多個源文件。
cmake:一個讀入源文件,自動生成makefile文件的工具,cmakelist文件是cmake工具生成makefile文件的規則,cmakelist一般由程序員編寫。
https://blog.csdn.net/weixin_42491857/article/details/80741060
把capicity減小到元素個數,減小容量
,如:vector<int>(ivec).swap(ivec);將 ivec shrink_to_fit
表達式vector<int>(ivec)創建一個臨時vector,它是ivec的一份拷貝:vector的拷貝構造函數作了這個工做。可是,vector的拷貝構造函數只分配拷貝的元素須要的內存,因此這個臨時vector沒有多餘的容量。而後咱們讓臨時vector和ivec交換數據,這時,ivec只有臨時變量的修整過的容量,而這個臨時變量則持有了曾經在ivec中的沒用到的過剩容量。最後,臨時vector被銷燬,所以釋放了之前ivec使用的內存,收縮到合適。
char (*p) [5]:定義了個指針,指針指向一個有5個char的數組;
char *p[5]:定義了一個數組,裏面有5個指向char的指針;
char (*p)():函數指針,指向 char fun();類型的函數;
是將構造函數和拷貝構造函數聲明爲private,或者採用c++11的delete關鍵字,
delete關鍵字可用來禁用某種類型的函數,unique_ptr只能使用移動構造函數,使用delete關鍵字禁用了拷貝構造函數。
main函數執行前:定義在main( )函數以前的全局對象、靜態對象的構造函數在main( )函數以前執行。
main函數執行後:全局/靜態對象的析構函數會在main函數以後執行;能夠用atexit()來註冊程序正常終止時要被調用的函數,而且在main函數結束時,調用這些函數,調用順序與他們被註冊時相反
不管程序是否正常退出,均可以用atexit()來調用資源釋放的函數;
遍歷刪除,考慮迭代器失效問題
{ if(iter指向的元素是奇數)
mapTest.erase(iter);
} //錯誤,erase會讓迭代器會失效!
{ if(iter指向的元素是奇數)
mapTest.erase(iter++);//正確,iter值傳遞以後,再++;
}
{ if(iter指向的元素是奇數)
iter=mapTest.erase(iter);// erase() 成員函數返回下一個元素的迭代器
}
C++的分離式編譯:c++開發中普遍使用聲明和實現分開的開發形式,其編譯過程是分離式編譯,就是說各個cpp文件徹底分開編譯,而後生成各自的obj目標文件,最後經過鏈接器link生成一個可執行的exe文件。一個編譯單元(translation unit)是指一個.cpp文件以及它所#include的全部.h文件,.h文件裏的代碼將會被擴展到包含它的.cpp文件裏,而後編譯器編譯該.cpp文件爲一個.obj文件。.obj文件已是二進制碼,可是不必定可以執行,由於並不保證其中必定有main函數。當編譯器將一個工程裏的全部.cpp文件以分離的方式編譯完畢後,再由鏈接器(linker)進行鏈接成爲一個.exe文件。
C++類模板不支持分離式編譯:模板代碼的實現體在一個文件裏,而實例化模板的測試代碼在另外一個文件裏,編譯器編譯一個文件時並不知道另外一個文件的存在,所以,模板代碼就沒有進行實例化,編譯器天然不會爲其生成代碼,所以會拋出一個連接錯誤!
C++類模板不支持分離式編譯,即咱們必須把類模板的聲明和定義寫在同一個.h文件中;
函數重載考慮參數個數,參數類型,不考慮函數返回值類型(函數調用時獨立於上下文),
兩個文件a,b,文件內部分別定義兩個全局變量,用g 編譯的時候如何保證兩個全局變量初化順序
全局變量 int a = 5; int b = a; 在不一樣文件中,不能保證b也等於5,也就是說不能保證a先初始化。
解決這種問題的方法是不直接使用全局變量,而改用一個包裝函數來訪問,例如
int get_a()
{
static int a = 5;
return a;
}
int get_b()
{
static int b = get_a();
return b;
}
這樣的話,不管get_a和get_b是否認義在同一個文件中,get_b老是可以返回正確的結果,緣由在於,函數內部的靜態變量是在第一次訪問的時候來初始化。
哈希表的衝突處理和數據遷移。
處理衝突:hash表實際上由size個的桶組成一個桶數組table[0...size-1] 。當一個對象通過哈希以後。獲得一個對應的value , 因而咱們把這個對象放到桶table[ value ]中。當一個桶中有多個對象時。咱們把桶中的對象組織成爲一個鏈表。這在衝突處理上稱之爲拉鍊法。
負載因子: 若是一個hash表中桶的個數爲 size , 存儲的元素個數爲used .則咱們稱 used / size 爲負載因子loadFactor . 通常的狀況下,當loadFactor<=1時,hash表查找的指望複雜度爲O(1). 所以。每次往hash表中加入元素時。咱們必須保證是在loadFactor <1的狀況下,才能夠加入。
數據遷移:Hash表中每次發現loadFactor==1時,就開闢一個原來桶數組的兩倍空間(稱爲新桶數組),而後把原來的桶數組中元素全部轉移過來到新的桶數組中。注意這裏轉移是需要元素一個個又一次哈希到新桶中的。
缺點:容量擴張是一次完畢的,期間要花很長時間一次轉移原hash表中的所有元素。
改進: redis中的dict.c中的設計思路是用兩個hash表來進行擴容和轉移的工做:當第一個hash表的loadFactor=1時,假設要往字典裏插入一個元素。首先爲第二個hash表開闢2倍第一個hash表的容量。同一時候將第一個hash表的一個非空桶中全部元素轉移到第二個hash表中。而後把待插入元素存儲到第二個hash表裏。繼續往字典裏插入第二個元素,又會將第一個hash表的一個非空桶中全部元素轉移到第二個hash表中,而後把元素存儲到第二個hash表裏……直到第一個hash表爲空。
這樣的策略就把第一個hash表所有元素的轉移分攤爲屢次轉移,而且每次轉移的指望時間複雜度爲O(1)。
vector的容量擴張爲何是2倍 最好的策略是什麼?reverse()
vector 在須要的時候會擴容,在 VS 下是 1.5倍,在 GCC 下是 2 倍。
答:採用成倍方式擴容,能夠保證push_back 常數的時間複雜度,而增長指定大小的容量只能達到O(n)的時間複雜度
以 大於2 倍的方式擴容,下一次申請的內存會大於以前分配內存的總和,致使以前分配的內存不能再被使用。因此,最好的增加因子在 (1,2)之間。
數學上的證實:當 k =1.5 時,在幾回擴展之後,能夠重用以前的內存空間了
reserve(n):因爲vector動態增加會引發從新分配內存空間、拷貝原空間、釋放原空間,這些過程會下降程序效率。所以,可使用reserve(n)預先分配一塊較大的指定大小的內存空間,這樣當指定大小的內存空間未使用完時,是不會從新分配內存空間的,這樣便提高了效率。只有當n>capacity()時,調用reserve(n)纔會改變vector容量。
C語言裏面字符串,strcpy和strncpy的區別?哪一個函數更安全?
strcpy函數:把從src地址開始且含有NULL結束符的字符串賦值到以dest開始的地址空間,返回dest(地址中存儲的爲複製後的新值)。要求:src和dest所指內存區域不能夠重疊且dest必須有足夠的空間來容納src的字符串。
strncpy函數:將字符串src中最多n個字符複製到字符數組dest中(它並不像strcpy同樣遇到NULL才中止複製,而是等湊夠n個字符纔開始複製),返回指向dest的指針。要求:若是n > dest串長度,dest棧空間溢出產生崩潰異常。
安全性分析:strncpy要比strcpy安全得多,strcpy沒法控制拷貝的長度,不當心就會出現dest的大小沒法容納src的狀況,就會出現越界的問題,程序就會崩潰。而strncpy就控制了拷貝的字符數避免了這類問題,可是要注意的是dest依然要注意要有足夠的空間存放src,並且src 和 dest 所指的內存區域不能重疊,
malloc涉及的系統調用(說了brk指針和mmap,沒說清楚,很是不滿意)。
malloc調用brk或mmap系統調用去獲取內存。malloc小於128k的內存,使用brk分配內存,malloc大於128k的內存,使用mmap分配內存,在堆和棧之間找一塊空閒內存分配(對應獨立內存,並且初始化爲0),
C++11新特性? lambda表達式, =default;, =deleted 函數
Lambda 表達式就是用於建立匿名函數的。
lambda表達式的本質就是重載了()運算符的類,這種類一般被稱爲functor,即行爲像函數的類。所以lambda表達式對象其實就是一個匿名的functor。編譯器自動將lambda表達式轉換成函數對象執行
=default; 指示編譯器生成該函數的默認實現。這有兩個好處:一是讓程序員輕鬆了,少敲鍵盤,二是有更好的性能。
與 defaulted 函數相對的就是 =deleted 函數, 實現 non copy-able 防止對象拷貝,要想禁止拷貝,用 =deleted 聲明一下兩個關鍵的成員函數就能夠了:
不能,由於C++支持重載,在編譯函數的聲明時,會改寫函數名(能夠經過連接指示進行解決);另外,C語言不支持類,沒法直接調用類的成員函數(能夠經過加入中間層進行解決);C語言也不能調用返回類型或形參類型是類類型的函數。
restrict是c99標準引入的,它只能夠用於限定和約束指針,並代表指針是訪問一個數據對象的惟一且初始的方式. 即它告訴編譯器,全部修改該指針所指向內存中內容的操做都必須經過該指針來修改, 而不能經過其它途徑(其它變量或指針)來修改;這樣作的好處是,能幫助編譯器進行更好的優化代碼,生成更有效率的彙編代碼。
如今程序員用restrict修飾一個指針,意思就是「只要這個指針活着,我保證這個指針獨享這片內存,沒有‘別人’能夠修改這個指針指向的這片內存,全部修改都得經過這個指針來」。因爲這個指針的生命週期是已知的,編譯器能夠放心大膽地把這片內存中前若干字節用寄存器cache起來。
http://www.javashuo.com/article/p-ktkcsbqc-ha.html
注:(1)靜態函數不能被定義爲虛函數,也不能被重載
(2)重寫函數的訪問修飾符能夠不一樣
面向對象的三大特性,結合C++語言支持來說。
多態的好處:能夠忽略派生類和基類的區別,而以統一的方式使用派生類和基類的對象,提升了代碼的複用性和可拓展性。
紅黑樹性質:紅黑樹是許多「平衡」搜索樹中的一種,能夠保證在最壞狀況下基本動態操做的時間複雜度爲O(lgn)。經過對任何一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其餘路徑長出2倍,於是使近似於平衡。
紅黑樹須知足條件:
AVL樹和紅黑樹的區別:
malloc的底層實現:
malloc函數將可用的內存塊鏈接爲一個空閒鏈表。調用malloc函數時,它沿着空閒鏈表尋找一個大到足以知足用戶所須要的內存塊。而後,將該內存塊一分爲二。一塊分配給用戶使用,另外一個塊從新鏈接到空閒鏈表。當用戶申請一個大的內存片斷,而內存塊被切分爲小的內存片斷,沒法知足用戶的請求時,malloc函數請求延時,將相鄰的小的空閒塊合併成大的內存塊。若是找不到合適的內存塊,就經過系統調用brk,將break指針向高地址移動,獲取新的內存塊,鏈接到空閒鏈表中。另外,若是所申請的內存大於128k,調用mmap在文件映射區域找一塊空閒的虛擬內存。若是分配內存失敗,會返回NULL指針。
++iter和iter++那個好?
前置版本的遞增運算符避免了沒必要要的工做,它把值加1後直接返回改變了的運算對象。與之相比,後置版本須要將原始值存儲下來以便於返回這個位修改的內容,若是咱們不須要修改前的值,那麼後置版本的操做就是一種浪費。對於相對複雜的迭代器類型,這種後置版本的操做就是一種浪費。
用 c 實現重載 ? 函數指針
如何突破private的限制?友元函數
用 C 模擬虛函數?
如何設計一個好的字符串hash函數
對於一個Hash函數,評價其優劣的標準應爲隨機性或離散性,即對任意一組標本,進入Hash表每個單元(cell)之機率的平均程度,由於這個機率越平均,兩個字符串計算出的Hash值相等hash collision的可能越小,數據在表中的分佈就越平均,表的空間利用率就越高。
C++ 11 定義了一個新增的哈希結構模板定義於頭文件 <functional>:std::hash<T>,模板類,(重載了operator()),實現了散列函數: unordered_map和unordered_multimap 默認使用std::hash; std::hash;實現太簡單
同時,C++ STL 裏面實現了一個萬用的hash function 針對任何類型的
boost::hash 的實現也是簡單取值。
DJBHash是一種很是流行的算法,俗稱"Times33"算法。Times33的算法很簡單,就是不斷的乘33,原型以下:
hash(i) = hash(i-1) * 33 + str[i],Time33在效率和隨機性兩方面上俱佳
https://blog.csdn.net/g1036583997/article/details/51910598