首先鄭重聲明,這些面試題的答案都是參考網上的答案和自己理解的部分整合起來,如有錯誤,歡迎指針。
1 多態的實現
存在虛函數的類至少有一個(多繼承會有多個)一維的虛函數表叫做虛表(virtual table),屬于類成員,虛表的元素值是虛函數的入口地址,在編譯時就已經為其在數據端分配了空間。編譯器另外還為每個類的對象提供一個虛表指針(vptr),指向虛表入口地址,屬于對象成員。在實例化派生類對象時,先實例化基類,將基類的虛表入口地址賦值給基類的虛表指針,當基類構造函數執行完時,再將派生類的虛表入口地址賦值給基類的虛表指針(派生類和基類此時共享一個虛表指針,并沒有各自都生成一個),在執行父類的構造函數。
以上是C++多態的實現過程,可以得出結論:
- 1 有虛函數的類必存在一個虛表。
- 2 虛表的構建:基類的虛表構建,先填上虛析構函數的入口地址,之后所有虛函數的入口地址按在類中聲明順序填入虛表;派生類的虛表構建,先將基類的虛表內容復制到派生類虛表中,如果派生類覆蓋了基類的虛函數,則虛表中對應的虛函數入口地址也會被覆蓋,為了后面尋址的一致性。
class Person{
. . .
public :
Person (){}
virtual ~Person (){};
virtual void speak (){};
virtual void eat (){};
};
class Girl : public Person{
. . .
public :
Girl(){}
virtual ~Girl(){};
virtual void speak(){};
virtual void sing(){};
虛函數表中有序放置了父類和子類中的所有虛函數,并且相同虛函數在類繼承鏈中的每一個虛函數表中的偏移量都是一致的。所以確定的虛函數對應virtual table中一個固定位置n,n是一個在編譯時期就確定的常量,所以,使用vptr加上對應的n,就可以得到對應的函數入口地址。C++采用的這種絕對地址+偏移量的方法調用虛函數,查找速度快執行效率高,時間復雜度為O(1)
這里概括一下虛函數的尋址過程:
1、獲取類型名和函數名
2、從符號表中獲得當前虛函數的偏移量
3、利用偏移量得到虛函數的訪問地址,并調用虛函數。vptrn
2 C/C++的區別
C面向過程,C++面向對象。C++幾乎是C的一個超集,幾乎包含了C。
3 const 關鍵字
常變量: const 類型說明符 變量名
常引用: const 類型說明符 &引用名
常對象: 類名 const 對象名
常成員函數: 類名::fun(形參) const
常數組: 類型說明符 const 數組名[大小]
常指針: const 類型說明符* 指針名 ,類型說明符* const 指針名
用法1:常量
取代了C中的宏定義,聲明時必須進行初始化(!c++類中則不然)。const限制了常量的使用方式,并沒有描述常量應該如何分配。如果編譯器知道了某const的所有使用,它甚至可以不為該const分配空間。最簡單的常見情況就是常量的值在編譯時已知,而且不需要分配存儲。―《C++ Program Language》
用const聲明的變量雖然增加了分配空間,但是可以保證類型安全
用法2:指針和常量
使用指針時涉及到兩個對象:該指針本身和被它所指的對象。將一個指針的聲明用const“預先固定”將使那個對象而不是使這個指針成為常量。要將指針本身而不是被指對象聲明為常量,必須使用聲明運算符*const。
所以出現在 * 之前的const是作為基礎類型的一部分:
char *const cp; //到char的const指針
char const *pc1; //到const char的指針
const char pc2; //到const char的指針(后兩個聲明是等同的)
從右向左讀的記憶方式:
cp is a const pointer to char. 故pc不能指向別的字符串,但可以修改其指向的字符串的內容
pc2 is a pointer to const char. 故pc2的內容不可以改變,但pc2可以指向別的字符串
且注意:允許把非 const 對象的地址賦給指向 const 對象的指針,不允許把一個 const 對象的地址賦給一個普通的、非 const 對象的指針。
用法3:const修飾函數傳入參數
將函數傳入參數聲明為const,以指明使用這種參數僅僅是為了效率的原因,而不是想讓調用函數能夠修改對象的值。同理,將指針參數聲明為const,函數將不修改由這個參數所指的對象。
通常修飾指針參數和引用參數:
void Fun( const A *in); //修飾指針型傳入參數
void Fun(const A &in); //修飾引用型傳入參數
用法4:修飾函數返回值
可以阻止用戶修改返回值。返回值也要相應的付給一個常量或常指針。
用法5:const修飾成員函數(c++特性)
const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數;
const對象的成員是不能修改的,而通過指針維護的對象確實可以修改的;
const成員函數不可以修改對象的數據,不管對象是否具有const性質。編譯時以是否修改成員數據為依據進行檢查。
4 malloc/free 和new/delete 區別
相同點:都可用于申請動態內存和釋放內存
不同點:
簡單點說,malloc只分配指定大小的堆內存空間,而new可以根據對象類型分配合適的堆內存空間,當然還可以通過重載operator new 自定義內存分配策略,其次還能夠構造對象,free釋放對應的堆內存空間,delete,先執行對象的析構函數,在釋放對象所占空間。
malloc與free是C++/C 語言的標準庫函數,new/delete 是C++的運算符。malloc分配時的大小是人為計算的,返回類型是void*,使用時需要類型轉換,而new在分配時,編譯器能夠根據對象類型自動計算出大小,返回類型是指向對象類型的指針,其封裝了sizeof和類型轉換功能,實際上new分為兩步,第一步是通過調用operator new函數分配一塊合適,原始的,未命名的內存空間,返回類型也是void *,而且operator new可以重載,可以自定義內存分配策略,甚至不做內存分配,甚至分配到非內存設備上,而malloc無能為力,第二步,調用構造函數構造對象,new將調用constructor,而malloc不能;delete將調用destructor,而free不能
5 指針和引用的區別
1、引用在創建時必須初始化,引用到一個有效對象,不是對象,不占用內存空間;而指針在定義時不必初始化,可以在定義后的任何地方重新賦值,是對象,占用內存空間。
2、指針可以是NULL,引用不行
3、引用貌似一個對象的小名,一旦初始化指向一個對象,就不能將其他對象重新賦值給該引用,這樣引用和原對象的值都會被更改。
6 C++中堆和棧的區別
一、預備知識—程序的內存分配
一個由C/C++編譯的程序占用的內存分為以下幾個部分
1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。
2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表。
3、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的 全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另 一塊區域。 - 程序結束后由系統釋放。
4、文字常量區 —常量字符串就是放在這里的。程序結束后由系統釋放
5、程序代碼區—存放函數體的二進制代碼。
二、例子程序
這是一個前輩寫的,非常詳細
//main.cpp
int a = 0; 全局初始化區
char *p1; 全局未初始化區
main()
{
int b; 棧
char s[] = "abc"; 棧
char *p2; 棧
char *p3 = "123456"; 123456/0在常量區,p3在棧上。
static int c =0; 全局(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20字節的區域就在堆區。
strcpy(p1, "123456"); 123456/0放在常量區,編譯器可能會將它與p3所指向的"123456"
優化成一個地方。
}
二、堆和棧的理論知識
2.1申請方式
stack:
由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中為b開辟空間
heap:
需要程序員自己申請,并指明大小,在c中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = new char[10];
但是注意p1、p2本身是在棧中的。
2.2
申請后系統的響應
棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間,另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。
2.3申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意 思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲
的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小
受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。
2.4申請效率的比較:
棧由系統自動分配,速度較快。但程序員是無法控制的。
堆是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是
直接在進程的地址空間中保留一塊內存,雖然用起來最不方便。但是速度快,也最靈活。
2.5堆和棧中的存儲內容
棧: 在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可
執行語句)的地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧
的,然后是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地
址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。
2.6存取效率的比較
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運行時刻賦值的;
而bbbbbbbbbbb是在編譯時就確定的;
但是,在以后的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
對應的匯編代碼
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到
edx中,再根據edx讀取字符,顯然慢了。
- 管理方式不同
棧是編譯器管理,堆的占用和釋放都是由程序員進行控制的; - 空間大小不同
在32位系統下,一般堆的內存可以達到4G的空間,可以說堆內存幾乎是沒有限制的。但是對于棧,一般都有一定空間大小(跟編譯器有關),比如在VC6下默認的棧空間大小是1M - 能否產生碎片不同
對于堆來說,頻繁的new/delete操作會造成內存空間的不連續,從而造成大量碎片,使程序效率降低;
但是對于棧來說,因為總是先進后出不存在內存塊不連續的問題。 - 生長方向不同
堆的生長方向是向上的,即向著內存地址增加的方向;
棧的生長方向是向下的,即向著內存地址減小的方向增長。 - 分配方式不同
堆總是動態分配的,需要程序員手動釋放;
棧存在靜態分配和動態分配的:
其中靜態分配是由編譯器完成的(比如局部變量的分配);
動態分配是由alloca函數進行分配的(這個函數會在棧幀的調用處上分配一定空間,當調用alloca的函數返回到調用位置時,這些臨時空間會被自動釋放),棧的動態分配是由編譯器自己進行釋放的。 - 分配效率不同:
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持,包括:分配專門的寄存器來存放棧的地址、入出棧都有專門指令,因此棧的效率會比較高。
堆是C/C++函數庫提供的,其機制非常復雜,比如為了分配一塊內存,庫函數會按照一定的算法在堆內存中搜索可用的足夠大小的空間,如果找不到(可能是因為內存碎片過多),就可能調用系統功能區(用戶模式和內核模式的切換)增加程序數據段的內存空間,如此便有機會分到足夠大小的內存,然后進行返回。
7 關鍵字static
C++的static有兩種用法:面向過程程序設計中的static和面向對象程序設計中的static。前者應用于普通變量和函數,不涉及類;后者主要說明static在類中的作用。
1.面向過程設計中的static
1.1靜態全局變量
靜態全局變量有以下特點:
? 該變量在全局數據區分配內存;
? 未經初始化的靜態全局變量會被程序自動初始化為0(自動變量的值是隨機的,除非它被顯式初始化);
? 靜態全局變量在聲明它的整個文件都是可見的,而在文件之外是不可見的;
1.2靜態局部變量
靜態局部變量有以下特點:
? 該變量在全局數據區分配內存;
? 靜態局部變量在程序執行到該對象的聲明處時被首次初始化,即以后的函數調用不再進行初始化;
? 靜態局部變量一般在聲明處初始化,如果沒有顯式初始化,會被程序自動初始化為0;
? 它始終駐留在全局數據區,直到程序運行結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域隨之結束;
1.3靜態函數
定義靜態函數的好處:
? 靜態函數不能被其它文件所用;
? 其它文件中可以定義相同名字的函數,不會發生沖突
二、面向對象的static關鍵字(類中的static關鍵字)
2.1靜態數據成員
靜態數據成員有以下特點:
? 對于非靜態數據成員,每個類對象都有自己的拷貝。而靜態數據成員被當作是類的成員。無論這個類的對象被定義了多少個,靜態數據成員在程序中也只有一份拷貝,由該類型的所有對象共享訪問;
? 靜態數據成員存儲在全局數據區。靜態數據成員定義時要分配空間,所以不能在類聲明中定義;
? 因為靜態數據成員在全局數據區分配內存,屬于本類的所有對象共享,所以,它不屬于特定的類對象,在沒有產生類對象時其作用域就可見,即在沒有產生類的實例時,我們就可以操;
? 靜態數據成員主要用在各個對象都有相同的某項屬性的時候
同全局變量相比,使用靜態數據成員有兩個優勢:
- 靜態數據成員沒有進入程序的全局名字空間,因此不存在與程序中其它全局名字沖突的可能性;
- 可以實現信息隱藏。靜態數據成員可以是private成員,而全局變量不能
2.2靜態成員函數
一個靜態成員函數,它為類的全部服務而不是為某一個類的具體對象服務,靜態成員函數由于不是與任何的對象相聯系,因此它不具有this指針
關于靜態成員函數,可以總結為以下幾點:
? 出現在類體外的函數定義不能指定關鍵字static;
? 靜態成員之間可以相互訪問,包括靜態成員函數訪問靜態數據成員和訪問靜態成員函數;
? 非靜態成員函數可以任意地訪問靜態成員函數和靜態數據成員;
? 靜態成員函數不能訪問非靜態成員函數和非靜態數據成員;
? 由于沒有this指針的額外開銷,因此靜態成員函數與類的全局函數相比速度上會有少許的增長;
在C++程序中調用被C 語言修飾的函數,為什么要加extern “C”?
extern "C"指令非常有用,因為C和C++的近親關系。注意:extern "C"指令中的C,表示的一種編譯和連接規約,而不是一種語言。C表示符合C語言的編譯和連接規約的任何語言,如Fortran、assembler等。
extern "C"指令僅指定編譯和連接規約,但不影響語義。例如在函數聲明中,指定了extern "C",仍然要遵守C++的類型檢測、參數轉換規則
extern "C"的真實目的是實現類C和C++的混合編程。在C++源文件中的語句前面加上extern "C",表明它按照類C的編譯和連接規約來編譯和連接,而不是C++的編譯的連接規約。這樣在類C的代碼中就可以調用C++的函數or變量等
C++是一個面向對象語言(雖不是純粹的面向對象語言),它支持函數的重載,重載這個特性給我們帶來了很大的便利。為了支持函數重載的這個特性,C++編譯器實際上將下面這些重載函數:
void print(int i);
void print(char c);
void print(float f);
void print(char* s);
編譯為:
_print_int
_print_char
_print_float
_pirnt_string
這樣的函數名,來唯一標識每個函數。C++中的變量,編譯也類似,如全局變量可能編譯g_xx,類變量編譯為c_xx等。連接是也是按照這種機制去查找相應的變量。
C語言中并沒有重載和類這些特性,故并不像C++那樣print(int i),會被編譯為_print_int,而是直接編譯為_print等。因此如果直接在C++中調用C的函數會失敗,因為連接是調用C中的print(3)時,它會去找_print_int(3)。因此extern "C"的作用就體現出來了
當我們C和C++混合編程時,有時候會用一種語言定義函數指針,而在應用中將函數指針指向另一中語言定義的函數。如果C和C++共享同一中編譯和連接、函數調用機制,這樣做是可以的。然而,這樣的通用機制,通常不然假定它存在,因此我們必須小心地確保函數以期望的方式調用。
而且當指定一個函數指針的編譯和連接方式時,函數的所有類型,包括函數名、函數引入的變量也按照指定的方式編譯和連接。如下例:
typedef int (*FT) (const void* ,const void*);//style of C++
extern "C"{
typedef int (*CFT) (const void*,const void*);//style of C
void qsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
}
void isort(void* p,size_t n,size_t sz,FT cmp);//style of C++
void xsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
//style of C
extern "C" void ysort(void* p,size_t n,size_t sz,FT cmp);
int compare(const void*,const void*);//style of C++
extern "C" ccomp(const void*,const void*);//style of C
void f(char* v,int sz)
{
//error,as qsort is style of C
//but compare is style of C++
qsort(v,sz,1,&compare);
qsort(v,sz,1,&ccomp);//ok
isort(v,sz,1,&compare);//ok
//error,as isort is style of C++
//but ccomp is style of C
isort(v,sz,1,&ccopm);
}
10 什么是內存泄漏?什么是野指針?什么是內存越界?如何避免?
10.1內存泄漏
概念:用動態內存分配函數動態開辟的空間,在使用完畢后未釋放,程序結束后,會導致一直占據該內存單元,直到程序結束,在現代操作系統中,一個應用程序使用的常規內存在程序終止時被釋放。這表示一個短暫運行的應用程序中的內存泄漏不會導致嚴重后果。但是在內存非常有限的系統中都可能導致非常嚴重的后果,shared_ptr來避免內存泄漏,但是要正確使用。
10.2野指針
“野指針”不是NULL指針,是指指向“垃圾”內存的指針。即指針指向的內容是不確定的。
產生的原因:
1)指針變量沒有初始化。因此,創建指針變量時,該變量要被置為NULL或者指向合法的內存單元。
2)指針p被free之后,沒有置為NULL,讓人誤以為p是個合法的指針。
3)指針跨越合法范圍操作。不要返回指向棧內存(非靜態局部變量)的指針或引用。
可能后果:
- 若操作系統將這部分已經釋放的內存重新分配給另外一個進程,而原來的程序重新引用現在的迷途指針,向其中寫入數據,則這部分程序內容將被破壞,而導致程序錯誤。這種類型的程序錯誤,通常會導致segment fault和一般的保護錯誤。
- 其他常見錯誤:返回一個基于棧分配的局部變量的地址時,一旦調用的函數返回,分配給這些變量的空間將回收,此時它們擁有的是垃圾值,如return &num,如果要使它的生命周期邊長,應該將其聲明為static
10.3 內存越界
存在一種情況就是調用棧溢出(stackoverflow),還有一種情況是緩沖區溢出,這兩種情況都會導致安全漏洞。
10.3.1緩沖區溢出
strcpy會一直復制直到碰到\0,很多平臺的棧變量是按照地址順序倒著分配的(高地址向低地址),所以destination溢出后會先修改先前定義的變量,這樣黑客就可以把is_administrator改為true,從而造成緩沖區溢出攻擊,當然數組越界也可以造成類似的效果,不過現在C++都提供了越界檢查的版本
// 緩沖區溢出攻擊
const int MAX_LENGTH = 16;
bool is_administrator = false;
char destination[MAX_LENGTH];
std::string source = read_string_from_client(); //內容存儲在緩沖區
strcpy(destination,source.c_str());
10.3.2棧溢出攻擊
棧溢出攻擊:在棧上分配length字節的空間,再往棧頂放上一個data。當Length十分大,會把data擠到棧空間之外,此時如果編譯器不做越界檢查的話,那么黑客只要用客戶端送特定的length和data,就能改寫服務器的任意內存(比如黑客可以修改服務器代碼的機器碼,注入一些JMP指令跳轉到黑客想執行的函數)
// 棧溢出攻擊
int length = read_int_from_client();
char buffer[length]; //棧空間分配
int data = read_int_from_client();
11 堆棧緩存的區別
1、棧使用的是一級緩存, 他們通常都是被調用時處于存儲空間中,調用完畢立即釋放;
2、堆是存放在二級緩存中,堆的首地址放在一級緩存緩存中,分配和釋放會產生系統調用,由用戶態進入內核態,所以速度會慢一些
12 STL 容器有哪些,常用的算法
13 如何理解智能指針,什么時候改變引用計數
14 share_ptr 與weak_ptr 的區別與聯系
15 C++構造函數是否可以拋出異常
構造函數可以拋出異常。但從邏輯上和風險控制上,構造函數中盡量不要拋出異常,既需要分配內存,又需要拋出異常時要特別注意防止內存泄露的情況發生。因為在構造函數中拋出異常,在概念上將被視為該對象沒有被成功構造,因此當前對象的析構函數就不會被調用,就會造成內存泄漏。同時,由于構造函數本身也是一個函數,在函數體內拋出異常將導致當前函數運行結束,并釋放已經構造的成員對象,包括其基類的成員,即執行直接基類和成員對象的析構函數
16 是否在析構函數拋出異常
1)如果析構函數拋出異常,則異常點之后的程序不會執行,如果析構函數在異常點之后執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源泄漏的問題。
2)通常異常發生時,c++的機制會調用已經構造對象的析構函數來釋放資源,此時若析構函數本身也拋出異常,則前一個異常尚未處理,又有新的異常,會造成程序崩潰的問題。
- 那么當無法保證在析構函數中不發生異常時, 其實還是有很好辦法來解決的。那就是把異常完全封裝在析構函數內部,決不讓異常拋出函數之外。這是一種非常簡單,也非常有效的方法。
17 volatile 的作用
18 構造函數和析構函數可以調用虛函數嗎
雖然可以對虛函數進行實調用,但程序員編寫虛函數的本意應該是實現動態聯編。在構造函數中調用虛函數,函數的入口地址是在編譯時靜態確定的,并未實現虛調用。但是為什么在構造函數中調用虛函數,實際上沒有發生動態聯編呢?
第一個原因,在概念上,構造函數的工作是為對象進行初始化。在構造函數完成之前,被構造的對象被認為“未完全生成”。當創建某個派生類的對象時,如果在它的基類的構造函數中調用虛函數,那么此時派生類的構造函數并未執行,所調用的函數可能操作還沒有被初始化的成員,將導致災難的發生。
第二個原因,即使想在構造函數中實現動態聯編,在實現上也會遇到困難。這涉及到對象虛指針(vptr)的建立問題。在Visual C++中,包含虛函數的類對象的虛指針被安排在對象的起始地址處,并且虛函數表(vtable)的地址是由構造函數寫入虛指針的。所以,一個類的構造函數在執行時,并不能保證該函數所能訪問到的虛指針就是當前被構造對象最后所擁有的虛指針,因為后面派生類的構造函數會對當前被構造對象的虛指針進行重寫,因此無法完成動態聯編
19 內存對齊的原則
1).數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以后每個數據成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數組,結構體等)的整數倍開始(比如int在32位機為4字節, 則要從4的整數倍地址開始存儲),基本類型不包括struct/class/uinon。
2).結構體作為成員:如果一個結構里有某些結構體成員,則結構體成員要從其內部"最寬基本類型成員"的整數倍地址開始存儲.(struct a里存有struct b,b里有char,int ,double等元素,那b應該從8的整數倍開始存儲.)。
3).收尾工作:結構體的總大小,也就是sizeof的結果,.必須是其內部最大成員的"最寬基本類型成員"的整數倍.不足的要補齊.(基本類型不包括struct/class/uinon)。
4).sizeof(union),以結構里面size最大元素為union的size,因為在某一時刻,union只有一個成員真正存儲于該地
20 內聯函數有什么優點?內聯函數和宏定義的區別。
1.內聯函數在運行時可調試,而宏定義不可以;
2.編譯器會對內聯函數的參數類型做安全檢查或自動類型轉換(同普通函數),而宏定義則不會;
3.內聯函數可以訪問類的成員變量,宏定義則不能;
4.在類中聲明同時定義的成員函數,自動轉化為內聯函數
內聯函數和普通函數相比可以加快程序運行的速度,因為不需要中斷調用,在編譯的時候內聯函數可以直接被鑲嵌到目標代碼中。
內聯函數要做參數類型檢查,這是內聯函數跟宏相比的優勢。
inline一般只用于如下情況:
(1)一個函數不斷被重復調用。
(2)函數只有簡單的幾行,且函數不包含for、while、switch語句,遞歸。
21 數組與指針的區別與聯系,函數指針,指針函數,指針數組,數組指針
22 STL set 和map 都是基于什么實現的
23 C++內存泄露及檢測工具
檢測內存泄漏的關鍵是要能截獲住對分配內存和釋放內存的函數的調用。截獲住這兩個函數,我們就能跟蹤每一 塊內存的生命周期,比如,每當成功的分配一塊內存后,就把它的指針加入一個全局的list中;每當釋放一塊內存,再把它的指針從list中刪除。這樣,當程序結束的時候,list中剩余的指針就是指向那些沒有被釋放的內存