- 指針和引用的區別
- extern,const,static,volatile關鍵字
- #define 和const的區別
- 關於typedef和#define;
- C++程序中內存使用情況分析(堆和棧的區別)
- new 與 malloc的異同處,new和delete是如何實現的。
- C和C++的區別
- C++中的重載,重寫,重定義(隱藏)的區別:
- 析構函數一般寫成虛函數的原因。
- 構造函數為什么一般不定義為虛函數
- 構造函數或者析構函數中調用虛函數會怎樣。
- 析構函數能拋出異常嗎
- 純虛函數和抽象類
- 多態的實現條件,虛指針vptr, 虛函數表vbtl
- 深拷貝和淺拷貝的區別(舉例說明深拷貝的安全性)
- 什么情況下會調用拷貝構造函數(三種情況)
- struct內存對齊方式,為什么要對齊?怎么修改默認對齊方式?struct,union
- 內存泄露的定義,如何檢測與避免?內存檢查工具的了解。
- 成員初始化列表的概念,為什么用成員初始化列表會快一些(性能優勢)
- 必須在構造函數初始化列表里進行初始化的數據成員有哪些?
- C++的調用慣例(C++函數調用的壓棧過程)
- C++的四種強制轉換 static_cast,const_cast,dynamic_cast,reinterpret_cast
- 多重繼承,菱形結構,虛基類,虛繼承,以及在多繼承虛繼承下多態的實現,虛繼承下對象的內存分布
- 內聯函數有什么優點?內聯函數與宏定義的區別?
- STL容器 迭代器失效總結.
- 對繼承和組合的理解
- c++ main函數執行之前需要做哪些准備
- 智能指針,shared_ptr的循環引用導致的內存泄漏怎么解決?
- 類成員變量用memset()設置會有什么問題?
- STL alloc實現,alloc的優勢和局限,STL中其他的配置器
- 單例模式;不能被繼承的類;定義一個只能在堆上(棧上)生成對象?
- cmake和makefile的區別 簡述cmake到可執行文件的過程
- 問有沒有用過shrink_to_fit,說一下作用,為什么用
- char (*p) [5] 、char *p[5]、char (*p)()的區別?
- 如何防止一個類被拷貝
- c++怎么實現一個函數先於main函數運行,后於main函數執行
- 如何刪除map中的奇數節點
- C++的分離式編譯 為什么C++類模板不支持分離式編譯?
- 兩個文件a,b,文件內部分別定義兩個全局變量,用g 編譯的時候如何保證兩個全局變量初化順序
- 哈希表的沖突處理和數據遷移
- vector的容量擴張為什么是2倍 最好的策略是什么?reverse()
- strcpy和strncpy的區別
- malloc涉及的系統調用
- C++11新特性? lambda表達式, =default;, =deleted 函數
- C語言程序能不能直接調用C++語言編寫的動態鏈接庫。
- C的restrict關鍵字:
- 虛函數表存儲在靜態存儲區
- 面向對象的三大特性,結合C++語言支持來講。
- 紅黑樹性質
- malloc的底層實現
- ++iter和iter++那個好
- 用 c 實現重載 ?
- 如何突破private的限制?
- 如何設計一個好的字符串hash函數
(1) 指針和引用的區別.
語法上:指針和引用沒有關系,引用就是一個已經存在的對象的別名。對引用的任何操作等價於對被引用對象的操作。
1.當引用被創建時,它必須被初始化。而指針則可以在任何時候被初始化。未初始化的引用不合法,未初始化的指針合法但危險。(懸空指針)
2.一旦一個引用被初始化為指向一個對象,它就不能被改變為對另一個對象的引用。而指針則可以在任何時候指向另一個對象。
3.不可能有NULL引用。必須確保引用是和一塊合法的存儲單元關聯。因為不存在指向空值的引用,所以在使用引用之前不需驗證它的合法性,而使用指針需要驗證合法性。所以使用引用的代碼效率要比使用指針的要高。
底層實現上(匯編層):引用是通過指針實現的。在程序一層只要直接涉及對引用變量的操作,操作的總是被引用變量,編譯器實現了一些操作,總是在引用前面加上*。實際上如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關鍵字(定義,用途)
extern關鍵字的作用:
1、extern用在變量或者函數的聲明前,用來說明“此變量/函數是在別處定義的,要在此處引用”。extern聲明不是定義,即不分配存儲空間。也就是說,在一個文件中定義了變量和函數, 在其他文件中要使用它們, 可以有兩種方式:1.使用頭文件,在頭文件中聲明它們,然后其他文件去包含頭文件;2.在其他文件中直接extern,就可以使用。
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的區別案例:
- typedef char *pStr1; #define pStr2 char *; pStr1 s1, s2; pStr2 s3, s4;
- 在上述的變量定義中,s1、s2、s3都被定義為char *,而s4則定義成了char,不是我們所預期的指針變量,根本原因就在於#define只是簡單的字符串替換typedef則是為一個類型起新名字。
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修飾后。表示該方法不能被子類重寫。比如涉及到某些需要統一處理的需求。
- cmake和makefile的區別 簡述cmake到可執行文件的過程
make: 一個自動化編譯工具,依據makefile文件(編譯規則) 批處理編譯多個源文件。
cmake:一個讀入源文件,自動生成makefile文件的工具,cmakelist文件是cmake工具生成makefile文件的規則,cmakelist通常由程序員編寫。
https://blog.csdn.net/weixin_42491857/article/details/80741060
- 問有沒有用過shrink_to_fit,說一下作用,為什么用
把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] 、char *p[5]、char (*p)()的區別?指向數組的指針,指針數組,函數指針,
char (*p) [5]:定義了個指針,指針指向一個有5個char的數組;
char *p[5]:定義了一個數組,里面有5個指向char的指針;
char (*p)():函數指針,指向 char fun();類型的函數;
- 如何防止一個類被拷貝
是將構造函數和拷貝構造函數聲明為private,或者采用c++11的delete關鍵字,
delete關鍵字可用來禁用某種類型的函數,unique_ptr只能使用移動構造函數,使用delete關鍵字禁用了拷貝構造函數。
- c++怎么實現一個函數先於main函數運行,后於main函數執行
main函數執行前:定義在main( )函數之前的全局對象、靜態對象的構造函數在main( )函數之前執行。
main函數執行后:全局/靜態對象的析構函數會在main函數之后執行;可以用atexit()來注冊程序正常終止時要被調用的函數,並且在main函數結束時,調用這些函數,調用順序與他們被注冊時相反
無論程序是否正常退出,都可以用atexit()來調用資源釋放的函數;
- 如何刪除map中的奇數節點
遍歷刪除,考慮迭代器失效問題
- for(ITER iter=mapTest.begin();iter!=mapTest.end();++iter)
{ if(iter指向的元素是奇數)
mapTest.erase(iter);
} //錯誤,erase會讓迭代器會失效!
- for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{ if(iter指向的元素是奇數)
mapTest.erase(iter++);//正確,iter值傳遞之后,再++;
}
- for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{ if(iter指向的元素是奇數)
iter=mapTest.erase(iter);// erase() 成員函數返回下一個元素的迭代器
}
- C++的分離式編譯 為什么C++類模板不支持分離式編譯?
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.5 倍增長,而不是以 3 倍或者 4 倍等增長呢?
以 大於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++支持重載,在編譯函數的聲明時,會改寫函數名(可以通過鏈接指示進行解決);另外,C語言不支持類,無法直接調用類的成員函數(可以通過加入中間層進行解決);C語言也不能調用返回類型或形參類型是類類型的函數。
- C的restrict關鍵字:
restrict是c99標准引入的,它只可以用於限定和約束指針,並表明指針是訪問一個數據對象的唯一且初始的方式. 即它告訴編譯器,所有修改該指針所指向內存中內容的操作都必須通過該指針來修改, 而不能通過其它途徑(其它變量或指針)來修改;這樣做的好處是,能幫助編譯器進行更好的優化代碼,生成更有效率的匯編代碼。
現在程序員用restrict修飾一個指針,意思就是“只要這個指針活着,我保證這個指針獨享這片內存,沒有‘別人’可以修改這個指針指向的這片內存,所有修改都得通過這個指針來”。由於這個指針的生命周期是已知的,編譯器可以放心大膽地把這片內存中前若干字節用寄存器cache起來。
- 通常情況下,不應該重載逗號、取地址、邏輯與和邏輯或運算符。
- 虛函數表存儲在靜態存儲區
https://www.cnblogs.com/chenhuan001/p/6485233.html
- 重載、重寫和重定義的區別
- 1.重載:函數名相同,參數列表不同
- 2.重寫:也稱為覆蓋,派生類覆蓋基類的虛函數。函數名,參數列表必須相同,返回類型一般也必須相同,存在一個例外(當類的虛函數返回類型是類本身的指針或引用時,上述規則無效。也就是說,如果D由B派生得到,則基類的虛函數可以返回B*而派生類的對應函數可以返回D*,只不過這樣的返回類型要求從D到B的類型裝換是可訪問的)。
注:(1)靜態函數不能被定義為虛函數,也不能被重載
(2)重寫函數的訪問修飾符可以不同
- 3.重定義:也稱為隱藏,派生類重新定義基類中有相同名稱的非虛函數(有相同名稱就可)
面向對象的三大特性,結合C++語言支持來講。
- 1.封裝:
- 1.封裝實現了類的接口和實現的分離。封裝后的類隱藏了它的實現細節,也就是說,類的用戶只能使用接口而無法訪問實現部分。
- 2.封裝有兩個重要的優點:確保用戶代碼不會無意間破壞封裝對象的狀態;被封裝的類的具體實現細節可以隨時改變,而無須調整用戶級別的代碼。
- 2.繼承
- 1.繼承使我們可以更容易地定義與其他類相似但不完全相同的新類。
- 2.繼承可以實現代碼重用,提高軟件開發的效率。
- 3.多態:“一個接口,多種實現”,同樣的消息被不同的對象接受時導致不同的行為。多態分為靜態多態和動態多態。
- 1.靜態多態通過函數重載、模板實現;
- 2.動態多態通過虛函數實現,當使用基類的指針(或引用)調用虛函數時將發生動態綁定,使用動態綁定,可以在一定程度上忽略相似類型的區別,而以統一的方式使用它們的對象。
多態的好處:可以忽略派生類和基類的區別,而以統一的方式使用派生類和基類的對象,提高了代碼的復用性和可拓展性。
紅黑樹性質:紅黑樹是許多“平衡”搜索樹中的一種,可以保證在最壞情況下基本動態操作的時間復雜度為O(lgn)。通過對任何一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其他路徑長出2倍,因而使近似於平衡。
紅黑樹須滿足條件:
- 每個結點或是紅色的,或是黑色的。
- 根節點是黑色的。
- 每個葉結點(NIL)是黑色的。
- 如果一個結點是紅色的,則它的兩個子結點都是黑色的。
- 對每個結點,從該結點到其所有后代結點的簡單路徑上,均包含相同數目的黑色結點。
AVL樹和紅黑樹的區別:
- 1.紅黑樹是近似平衡的二叉樹,每次插入和刪除操作最多只需要三次旋轉就能達到平衡,實現起來也更為簡單。
- 2.平衡二叉樹嚴格平衡的二叉樹,每次插入和刪除新節點需要旋轉的次數不可預知,實現起來比較復雜。
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