1、在main執(zhí)行之前和之后執(zhí)行的代碼可能是什么?
main函數(shù)執(zhí)行之前,主要就是初始化系統(tǒng)相關(guān)資源:
設(shè)置棧指針
初始化靜態(tài)static變量和global全局變量,即.data段的內(nèi)容
將未初始化部分的全局變量賦初值:數(shù)值型short,int,long等為0,bool為FALSE,指針為NULL等等,即.bss段的內(nèi)容
全局對象初始化,在main之前調(diào)用構(gòu)造函數(shù),這是可能會執(zhí)行前的一些代碼
將main函數(shù)的參數(shù)argc,argv等傳遞給main函數(shù),然后才真正運(yùn)行main函數(shù)
main函數(shù)執(zhí)行之后:
全局對象的析構(gòu)函數(shù)會在main函數(shù)之后執(zhí)行;
可以用 atexit 注冊一個函數(shù),它會在main 之后執(zhí)行;
2、結(jié)構(gòu)體內(nèi)存對齊問題?
結(jié)構(gòu)體內(nèi)成員按照聲明順序存儲,第一個成員地址和整個結(jié)構(gòu)體地址相同。
未特殊說明時,按結(jié)構(gòu)體中size最大的成員對齊(若有double成員,按8字節(jié)對齊。)
3、指針和引用的區(qū)別
指針是一個變量,存儲的是一個地址,引用跟原來的變量實(shí)質(zhì)上是同一個東西,是原變量的別名
指針可以有多級,引用只有一級
指針可以為空,引用不能為NULL且在定義時必須初始化
指針在初始化后可以改變指向,而引用在初始化之后不可再改變
sizeof指針得到的是本指針的大小,sizeof引用得到的是引用所指向變量的大小
當(dāng)把指針作為參數(shù)進(jìn)行傳遞時,也是將實(shí)參的一個拷貝傳遞給形參,兩者指向的地址相同,但不是同一個變量,在函數(shù)中改變這個變量的指向不影響實(shí)參,而引用卻可以。
引用只是別名,不占用具體存儲空間,只有聲明沒有定義;指針是具體變量,需要占用存儲空間。
引用在聲明時必須初始化為另一變量,一旦出現(xiàn)必須為typename refname &varname形式;指針聲明和定義可以分開,可以先只聲明指針變量而不初始化,等用到時再指向具體變量。
引用一旦初始化之后就不可以再改變(變量可以被引用為多次,但引用只能作為一個變量引用);指針變量可以重新指向別的變量。
不存在指向空值的引用,必須有具體實(shí)體;但是存在指向空值的指針。
參考代碼:
void test(int *p) { int a=1; p=&a; cout《《p《《“ ”《《*p《《endl; } int main(void) { int *p=NULL; test(p); if(p==NULL) cout《《“指針p為NULL”《《endl; return 0; } //運(yùn)行結(jié)果為: //0x22ff44 1 //指針p為NULL void testPTR(int* p) { int a = 12; p = &a; } void testREFF(int& p) { int a = 12; p = a; } void main() { int a = 10; int* b = &a; testPTR(b);//改變指針指向,但是沒改變指針的所指的內(nèi)容 cout 《《 a 《《 endl;// 10 cout 《《 *b 《《 endl;// 10 a = 10; testREFF(a); cout 《《 a 《《 endl;//12 }
4、堆和棧的區(qū)別
申請方式不同:棧由系統(tǒng)自動分配;堆是自己申請和釋放的。
申請大小限制不同:棧頂和棧底是之前預(yù)設(shè)好的,棧是向棧底擴(kuò)展,大小固定,可以通過ulimit -a查看,由ulimit -s修改;堆向高地址擴(kuò)展,是不連續(xù)的內(nèi)存區(qū)域,大小可以靈活調(diào)整。
申請效率不同:棧由系統(tǒng)分配,速度快,不會有碎片;堆由程序員分配,速度慢,且會有碎片。
形象的比喻
棧就像我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。
堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。
《C++中堆(heap)和棧(stack)的區(qū)別》:
https://blog.csdn.net/qq_34175893/article/details/83502412
5、區(qū)別以下指針類型?
int *p[10] int (*p)[10] int *p(int) int (*p)(int)
int *p[10]表示指針數(shù)組,強(qiáng)調(diào)數(shù)組概念,是一個數(shù)組變量,數(shù)組大小為10,數(shù)組內(nèi)每個元素都是指向int類型的指針變量。
int (*p)[10]表示數(shù)組指針,強(qiáng)調(diào)是指針,只有一個變量,是指針類型,不過指向的是一個int類型的數(shù)組,這個數(shù)組大小是10。
int *p(int)是函數(shù)聲明,函數(shù)名是p,參數(shù)是int類型的,返回值是int *類型的。
int (*p)(int)是函數(shù)指針,強(qiáng)調(diào)是指針,該指針指向的函數(shù)具有int類型參數(shù),并且返回值是int類型的。
6、基類的虛函數(shù)表存放在內(nèi)存的什么區(qū),虛表指針vptr的初始化時間
首先整理一下虛函數(shù)表的特征:
虛函數(shù)表是全局共享的元素,即全局僅有一個,在編譯時就構(gòu)造完成
虛函數(shù)表類似一個數(shù)組,類對象中存儲vptr指針,指向虛函數(shù)表,即虛函數(shù)表不是函數(shù),不是程序代碼,不可能存儲在代碼段
虛函數(shù)表存儲虛函數(shù)的地址,即虛函數(shù)表的元素是指向類成員函數(shù)的指針,而類中虛函數(shù)的個數(shù)在編譯時期可以確定,即虛函數(shù)表的大小可以確定,即大小是在編譯時期確定的,不必動態(tài)分配內(nèi)存空間存儲虛函數(shù)表,所以不在堆中
根據(jù)以上特征,虛函數(shù)表類似于類中靜態(tài)成員變量。靜態(tài)成員變量也是全局共享,大小確定,因此最有可能存在全局?jǐn)?shù)據(jù)區(qū),測試結(jié)果顯示:
虛函數(shù)表vtable在Linux/Unix中存放在可執(zhí)行文件的只讀數(shù)據(jù)段中(rodata),這與微軟的編譯器將虛函數(shù)表存放在常量段存在一些差別
由于虛表指針vptr跟虛函數(shù)密不可分,對于有虛函數(shù)或者繼承于擁有虛函數(shù)的基類,對該類進(jìn)行實(shí)例化時,在構(gòu)造函數(shù)執(zhí)行時會對虛表指針進(jìn)行初始化,并且存在對象內(nèi)存布局的最前面。
一般分為五個區(qū)域:棧區(qū)、堆區(qū)、函數(shù)區(qū)(存放函數(shù)體等二進(jìn)制代碼)、全局靜態(tài)區(qū)、常量區(qū)
C++中虛函數(shù)表位于只讀數(shù)據(jù)段(.rodata),也就是C++內(nèi)存模型中的常量區(qū);而虛函數(shù)則位于代碼段(.text),也就是C++內(nèi)存模型中的代碼區(qū)。
7、new / delete 與 malloc / free的異同
相同點(diǎn)
都可用于內(nèi)存的動態(tài)申請和釋放
不同點(diǎn)
前者是C++運(yùn)算符,后者是C/C++語言標(biāo)準(zhǔn)庫函數(shù)
new自動計算要分配的空間大小,malloc需要手工計算
new是類型安全的,malloc不是。例如:
int *p = new float[2]; //編譯錯誤 int *p = (int*)malloc(2 * sizeof(double));//編譯無錯誤
new調(diào)用名為operator new的標(biāo)準(zhǔn)庫函數(shù)分配足夠空間并調(diào)用相關(guān)對象的構(gòu)造函數(shù),delete對指針?biāo)笇ο筮\(yùn)行適當(dāng)?shù)奈鰳?gòu)函數(shù);然后通過調(diào)用名為operator delete的標(biāo)準(zhǔn)庫函數(shù)釋放該對象所用內(nèi)存。后者均沒有相關(guān)調(diào)用
后者需要庫文件支持,前者不用
new是封裝了malloc,直接free不會報錯,但是這只是釋放內(nèi)存,而不會析構(gòu)對象
8、new和delete是如何實(shí)現(xiàn)的?
new的實(shí)現(xiàn)過程是:首先調(diào)用名為operator new的標(biāo)準(zhǔn)庫函數(shù),分配足夠大的原始為類型化的內(nèi)存,以保存指定類型的一個對象;接下來運(yùn)行該類型的一個構(gòu)造函數(shù),用指定初始化構(gòu)造對象;最后返回指向新分配并構(gòu)造后的的對象的指針
delete的實(shí)現(xiàn)過程:對指針指向的對象運(yùn)行適當(dāng)?shù)奈鰳?gòu)函數(shù);然后通過調(diào)用名為operator delete的標(biāo)準(zhǔn)庫函數(shù)釋放該對象所用內(nèi)存
9、malloc和new的區(qū)別?
malloc和free是標(biāo)準(zhǔn)庫函數(shù),支持覆蓋;new和delete是運(yùn)算符,并且支持重載。
malloc僅僅分配內(nèi)存空間,free僅僅回收空間,不具備調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)功能,用malloc分配空間存儲類的對象存在風(fēng)險;new和delete除了分配回收功能外,還會調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
malloc和free返回的是void類型指針(必須進(jìn)行類型轉(zhuǎn)換),new和delete返回的是具體類型指針。
delete和delete[]區(qū)別?
delete只會調(diào)用一次析構(gòu)函數(shù)。
delete[]會調(diào)用數(shù)組中每個元素的析構(gòu)函數(shù)。
10、宏定義和函數(shù)有何區(qū)別?
宏在編譯時完成替換,之后被替換的文本參與編譯,相當(dāng)于直接插入了代碼,運(yùn)行時不存在函數(shù)調(diào)用,執(zhí)行起來更快;函數(shù)調(diào)用在運(yùn)行時需要跳轉(zhuǎn)到具體調(diào)用函數(shù)。
宏定義屬于在結(jié)構(gòu)中插入代碼,沒有返回值;函數(shù)調(diào)用具有返回值。
宏定義參數(shù)沒有類型,不進(jìn)行類型檢查;函數(shù)參數(shù)具有類型,需要檢查類型。
宏定義不要在最后加分號。
11、宏定義和typedef區(qū)別?
宏主要用于定義常量及書寫復(fù)雜的內(nèi)容;typedef主要用于定義類型別名。
宏替換發(fā)生在編譯階段之前,屬于文本插入替換;typedef是編譯的一部分。
宏不檢查類型;typedef會檢查數(shù)據(jù)類型。
宏不是語句,不在在最后加分號;typedef是語句,要加分號標(biāo)識結(jié)束。
注意對指針的操作,typedef char * p_char和#define p_char char *區(qū)別巨大。
12、變量聲明和定義區(qū)別?
聲明僅僅是把變量的聲明的位置及類型提供給編譯器,并不分配內(nèi)存空間;定義要在定義的地方為其分配存儲空間。
相同變量可以在多處聲明(外部變量extern),但只能在一處定義。
13、哪幾種情況必須用到初始化成員列表?
初始化一個const成員。
初始化一個reference成員。
調(diào)用一個基類的構(gòu)造函數(shù),而該函數(shù)有一組參數(shù)。
調(diào)用一個數(shù)據(jù)成員對象的構(gòu)造函數(shù),而該函數(shù)有一組參數(shù)。
14、strlen和sizeof區(qū)別?
sizeof是運(yùn)算符,并不是函數(shù),結(jié)果在編譯時得到而非運(yùn)行中獲得;strlen是字符處理的庫函數(shù)。
sizeof參數(shù)可以是任何數(shù)據(jù)的類型或者數(shù)據(jù)(sizeof參數(shù)不退化);strlen的參數(shù)只能是字符指針且結(jié)尾是‘’的字符串。
因?yàn)閟izeof值在編譯時確定,所以不能用來得到動態(tài)分配(運(yùn)行時分配)存儲空間的大小。
int main(int argc, char const *argv[]){ const char* str = “name”; sizeof(str); // 取的是指針str的長度,是8 strlen(str); // 取的是這個字符串的長度,不包含結(jié)尾的 。大小是4 return 0; }
15、常量指針和指針常量區(qū)別?
常量指針是一個指針,讀成常量的指針,指向一個只讀變量。如int const *p或const int *p。
指針常量是一個不能給改變指向的指針。指針是個常亮,不能中途改變指向,如int *const p。
16、a和&a有什么區(qū)別?
假設(shè)數(shù)組int a[10]; int (*p)[10] = &a;
a是數(shù)組名,是數(shù)組首元素地址,+1表示地址值加上一個int類型的大小,如果a的值是0x00000001,加1操作后變?yōu)?x00000005。*(a + 1) = a[1]。
&a是數(shù)組的指針,其類型為int (*)[10](就是前面提到的數(shù)組指針),其加1時,系統(tǒng)會認(rèn)為是數(shù)組首地址加上整個數(shù)組的偏移(10個int型變量),值為數(shù)組a尾元素后一個元素的地址。
若(int *)p ,此時輸出 *p時,其值為a[0]的值,因?yàn)楸晦D(zhuǎn)為int *類型,解引用時按照int類型大小來讀取。
17、數(shù)組名和指針(這里為指向數(shù)組首元素的指針)區(qū)別?
二者均可通過增減偏移量來訪問數(shù)組中的元素。
數(shù)組名不是真正意義上的指針,可以理解為常指針,所以數(shù)組名沒有自增、自減等操作。
當(dāng)數(shù)組名當(dāng)做形參傳遞給調(diào)用函數(shù)后,就失去了原有特性,退化成一般指針,多了自增、自減操作,但sizeof運(yùn)算符不能再得到原數(shù)組的大小了。
18、野指針和懸空指針
都是是指向無效內(nèi)存區(qū)域(這里的無效指的是“不安全不可控”)的指針,訪問行為將會導(dǎo)致未定義行為。
野指針,指的是沒有被初始化過的指針
int main(void) { int * p; std::cout《《*p《《std::endl; return 0; }
因此,為了防止出錯,對于指針初始化時都是賦值為 nullptr,這樣在使用時編譯器就會直接報錯,產(chǎn)生非法內(nèi)存訪問。
懸空指針,指針最初指向的內(nèi)存已經(jīng)被釋放了的一種指針。
int main(void) { int * p = nullptr; int* p2 = new int; p = p2; delete p2; }
此時 p和p2就是懸空指針,指向的內(nèi)存已經(jīng)被釋放。繼續(xù)使用這兩個指針,行為不可預(yù)料。需要設(shè)置為p=p2=nullptr。此時再使用,編譯器會直接保錯。
避免野指針比較簡單,但懸空指針比較麻煩。c++引入了智能指針,C++智能指針的本質(zhì)就是避免懸空指針的產(chǎn)生。
產(chǎn)生原因及解決辦法:
野指針:指針變量未及時初始化 =》 定義指針變量及時初始化,要么置空。
懸空指針:指針free或delete之后沒有及時置空 =》 釋放操作后立即置空。
19、迭代器失效的情況
以vector為例:
插入元素:
1、尾后插入:size 《 capacity時,首迭代器不失效尾迭代失效(未重新分配空間),size == capacity時,所有迭代器均失效(需要重新分配空間)。
2、中間插入:中間插入:size 《 capacity時,首迭代器不失效但插入元素之后所有迭代器失效,size == capacity時,所有迭代器均失效。
刪除元素:
尾后刪除:只有尾迭代失效。
中間刪除:刪除位置之后所有迭代失效。
20、C和C++的區(qū)別
C++中new和delete是對內(nèi)存分配的運(yùn)算符,取代了C中的malloc和free。
標(biāo)準(zhǔn)C++中的字符串類取代了標(biāo)準(zhǔn)C函數(shù)庫頭文件中的字符數(shù)組處理函數(shù)(C中沒有字符串類型)。
C++中用來做控制態(tài)輸入輸出的iostream類庫替代了標(biāo)準(zhǔn)C中的stdio函數(shù)庫。
C++中的try/catch/throw異常處理機(jī)制取代了標(biāo)準(zhǔn)C中的setjmp()和longjmp()函數(shù)。
在C++中,允許有相同的函數(shù)名,不過它們的參數(shù)類型不能完全相同,這樣這些函數(shù)就可以相互區(qū)別開來。而這在C語言中是不允許的。也就是C++可以重載,C語言不允許。
C++語言中,允許變量定義語句在程序中的任何地方,只要在是使用它之前就可以;而C語言中,必須要在函數(shù)開頭部分。而且C++允許重復(fù)定義變量,C語言也是做不到這一點(diǎn)的
在C++中,除了值和指針之外,新增了引用。引用型變量是其他變量的一個別名,我們可以認(rèn)為他們只是名字不相同,其他都是相同的。
C++相對與C增加了一些關(guān)鍵字,如:bool、using、dynamic_cast、namespace等等
《C語言與C++有什么區(qū)別?》
https://www.cnblogs.com/ITziyuan/p/9487760.html
21、C++與Java的區(qū)別
語言特性
Java語言給開發(fā)人員提供了更為簡潔的語法;完全面向?qū)ο?,由于JVM可以安裝到任何的操作系統(tǒng)上,所以說它的可移植性強(qiáng)
Java語言中沒有指針的概念,引入了真正的數(shù)組。不同于C++中利用指針實(shí)現(xiàn)的“偽數(shù)組”,Java引入了真正的數(shù)組,同時將容易造成麻煩的指針從語言中去掉,這將有利于防止在C++程序中常見的因?yàn)閿?shù)組操作越界等指針操作而對系統(tǒng)數(shù)據(jù)進(jìn)行非法讀寫帶來的不安全問題
C++也可以在其他系統(tǒng)運(yùn)行,但是需要不同的編碼(這一點(diǎn)不如Java,只編寫一次代碼,到處運(yùn)行),例如對一個數(shù)字,在windows下是大端存儲,在unix中則為小端存儲。Java程序一般都是生成字節(jié)碼,在JVM里面運(yùn)行得到結(jié)果
Java用接口(Interface)技術(shù)取代C++程序中的多繼承性。接口與多繼承有同樣的功能,但是省卻了多繼承在實(shí)現(xiàn)和維護(hù)上的復(fù)雜性
垃圾回收
C++用析構(gòu)函數(shù)回收垃圾,寫C和C++程序時一定要注意內(nèi)存的申請和釋放
Java語言不使用指針,內(nèi)存的分配和回收都是自動進(jìn)行的,程序員無須考慮內(nèi)存碎片的問題
應(yīng)用場景
Java在桌面程序上不如C++實(shí)用,C++可以直接編譯成exe文件,指針是c++的優(yōu)勢,可以直接對內(nèi)存的操作,但同時具有危險性 。(操作內(nèi)存的確是一項非常危險的事情,一旦指針指向的位置發(fā)生錯誤,或者誤刪除了內(nèi)存中某個地址單元存放的重要數(shù)據(jù),后果是可想而知的)
Java在Web 應(yīng)用上具有C++ 無可比擬的優(yōu)勢,具有豐富多樣的框架
對于底層程序的編程以及控制方面的編程,C++很靈活,因?yàn)橛芯浔拇嬖?/p>
《C++和java的區(qū)別和聯(lián)系》:
https://www.cnblogs.com/tanrong/p/8503202.html
22、C++中struct和class的區(qū)別
相同點(diǎn)
兩者都擁有成員函數(shù)、公有和私有部分
任何可以使用class完成的工作,同樣可以使用struct完成
不同點(diǎn)
兩者中如果不對成員不指定公私有,struct默認(rèn)是公有的,class則默認(rèn)是私有的
class默認(rèn)是private繼承,而struct模式是public繼承
class可以作為模板類型,struct不行
引申:C++和C的struct區(qū)別
C語言中:struct是用戶自定義數(shù)據(jù)類型(UDT);C++中struct是抽象數(shù)據(jù)類型(ADT),支持成員函數(shù)的定義,(C++中的struct能繼承,能實(shí)現(xiàn)多態(tài))
C中struct是沒有權(quán)限的設(shè)置的,且struct中只能是一些變量的集合體,可以封裝數(shù)據(jù)卻不可以隱藏數(shù)據(jù),而且成員不可以是函數(shù)
C++中,struct增加了訪問權(quán)限,且可以和類一樣有成員函數(shù),成員默認(rèn)訪問說明符為public(為了與C兼容)
struct作為類的一種特例是用來自定義數(shù)據(jù)結(jié)構(gòu)的。一個結(jié)構(gòu)標(biāo)記聲明后,在C中必須在結(jié)構(gòu)標(biāo)記前加上struct,才能做結(jié)構(gòu)類型名(除:typedef struct class{};);C++中結(jié)構(gòu)體標(biāo)記(結(jié)構(gòu)體名)可以直接作為結(jié)構(gòu)體類型名使用,此外結(jié)構(gòu)體struct在C++中被當(dāng)作類的一種特例
《struct結(jié)構(gòu)在C和C++中的區(qū)別》:
https://blog.csdn.net/mm_hh/article/details/70456240
23、define宏定義和const的區(qū)別
編譯階段
define是在編譯的預(yù)處理階段起作用,而const是在編譯、運(yùn)行的時候起作用
安全性
define只做替換,不做類型檢查和計算,也不求解,容易產(chǎn)生錯誤,一般最好加上一個大括號包含住全部的內(nèi)容,要不然很容易出錯
const常量有數(shù)據(jù)類型,編譯器可以對其進(jìn)行類型安全檢查
內(nèi)存占用
define只是將宏名稱進(jìn)行替換,在內(nèi)存中會產(chǎn)生多分相同的備份。const在程序運(yùn)行中只有一份備份,且可以執(zhí)行常量折疊,能將復(fù)雜的的表達(dá)式計算出結(jié)果放入常量表
宏替換發(fā)生在編譯階段之前,屬于文本插入替換;const作用發(fā)生于編譯過程中。
宏不檢查類型;const會檢查數(shù)據(jù)類型。
宏定義的數(shù)據(jù)沒有分配內(nèi)存空間,只是插入替換掉;const定義的變量只是值不能改變,但要分配內(nèi)存空間。
24、C++中const和static的作用
static
不考慮類的情況
隱藏。所有不加static的全局變量和函數(shù)具有全局可見性,可以在其他文件中使用,加了之后只能在該文件所在的編譯模塊中使用
默認(rèn)初始化為0,包括未初始化的全局靜態(tài)變量與局部靜態(tài)變量,都存在全局未初始化區(qū)
靜態(tài)變量在函數(shù)內(nèi)定義,始終存在,且只進(jìn)行一次初始化,具有記憶性,其作用范圍與局部變量相同,函數(shù)退出后仍然存在,但不能使用
考慮類的情況
static成員變量:只與類關(guān)聯(lián),不與類的對象關(guān)聯(lián)。定義時要分配空間,不能在類聲明中初始化,必須在類定義體外部初始化,初始化時不需要標(biāo)示為static;可以被非static成員函數(shù)任意訪問。
static成員函數(shù):不具有this指針,無法訪問類對象的非static成員變量和非static成員函數(shù);不能被聲明為const、虛函數(shù)和volatile;可以被非static成員函數(shù)任意訪問
const
不考慮類的情況
const常量在定義時必須初始化,之后無法更改
const形參可以接收const和非const類型的實(shí)參,例如
// i 可以是 int 型或者 const int 型 void fun(const int& i){ //。.. }
考慮類的情況
const成員變量:不能在類定義外部初始化,只能通過構(gòu)造函數(shù)初始化列表進(jìn)行初始化,并且必須有構(gòu)造函數(shù);不同類對其const數(shù)據(jù)成員的值可以不同,所以不能在類中聲明時初始化
const成員函數(shù):const對象不可以調(diào)用非const成員函數(shù);非const對象都可以調(diào)用;不可以改變非mutable(用該關(guān)鍵字聲明的變量可以在const成員函數(shù)中被修改)數(shù)據(jù)的值
25、C++的頂層const和底層const
概念區(qū)分
頂層const:指的是const修飾的變量本身是一個常量,無法修改,指的是指針,就是 * 號的右邊
底層const:指的是const修飾的變量所指向的對象是一個常量,指的是所指變量,就是 * 號的左邊
舉個例子
int a = 10; int* const b1 = &a; //頂層const,b1本身是一個常量 const int* b2 = &a; //底層const,b2本身可變,所指的對象是常量 const int b3 = 20; //頂層const,b3是常量不可變 const int* const b4 = &a; //前一個const為底層,后一個為頂層,b4不可變 const int& b5 = a; //用于聲明引用變量,都是底層const
區(qū)分作用
執(zhí)行對象拷貝時有限制,常量的底層const不能賦值給非常量的底層const
使用命名的強(qiáng)制類型轉(zhuǎn)換函數(shù)const_cast時,只能改變運(yùn)算對象的底層const
《C++ 頂層const與底層const總結(jié)》:
https://www.jianshu.com/p/fbbcf11100f6
《C++的頂層const和底層const淺析》:
https://blog.csdn.net/qq_37059483/article/details/78811231
const int a; int const a; const int *a; int *const a;
int const a和const int a均表示定義常量類型a。
const int *a,其中a為指向int型變量的指針,const在 * 左側(cè),表示a指向不可變常量。(看成const (*a),對引用加const)
int *const a,依舊是指針類型,表示a為指向整型數(shù)據(jù)的常指針。(看成const(a),對指針const)
26、類的對象存儲空間?
非靜態(tài)成員的數(shù)據(jù)類型大小之和。
編譯器加入的額外成員變量(如指向虛函數(shù)表的指針)。
為了邊緣對齊優(yōu)化加入的padding。
27、final和override關(guān)鍵字
override
當(dāng)在父類中使用了虛函數(shù)時候,你可能需要在某個子類中對這個虛函數(shù)進(jìn)行重寫,以下方法都可以:
class A { virtual void foo(); } class B : public A { void foo(); //OK virtual void foo(); // OK void foo() override; //OK }
如果不使用override,當(dāng)你手一抖,將foo()寫成了foo()會怎么樣呢?結(jié)果是編譯器并不會報錯,因?yàn)樗⒉恢滥愕哪康氖侵貙懱摵瘮?shù),而是把它當(dāng)成了新的函數(shù)。如果這個虛函數(shù)很重要的話,那就會對整個程序不利。所以,override的作用就出來了,它指定了子類的這個虛函數(shù)是重寫的父類的,如果你名字不小心打錯了的話,編譯器是不會編譯通過的:
class A { virtual void foo(); }; class B : public A { virtual void f00(); //OK,這個函數(shù)是B新增的,不是繼承的 virtual void f0o() override; //Error, 加了override之后,這個函數(shù)一定是繼承自A的,A找不到就報錯 };
final
當(dāng)不希望某個類被繼承,或不希望某個虛函數(shù)被重寫,可以在類名和虛函數(shù)后添加final關(guān)鍵字,添加final關(guān)鍵字后被繼承或重寫,編譯器會報錯。例子如下:
class Base { virtual void foo(); }; class A : public Base { void foo() final; // foo 被override并且是最后一個override,在其子類中不可以重寫 }; class B final : A // 指明B是不可以被繼承的 { void foo() override; // Error: 在A中已經(jīng)被final了 }; class C : B // Error: B is final { };
《C++:override和final》:
https://www.cnblogs.com/whlook/p/6501918.html
28、拷貝初始化和直接初始化
當(dāng)用于類類型對象時,初始化的拷貝形式和直接形式有所不同:直接初始化直接調(diào)用與實(shí)參匹配的構(gòu)造函數(shù),拷貝初始化總是調(diào)用拷貝構(gòu)造函數(shù)??截惓跏蓟紫仁褂弥付?gòu)造函數(shù)創(chuàng)建一個臨時對象,然后用拷貝構(gòu)造函數(shù)將那個臨時對象拷貝到正在創(chuàng)建的對象。舉例如下
string str1(“I am a string”);//語句1 直接初始化 string str2(str1);//語句2 直接初始化,str1是已經(jīng)存在的對象,直接調(diào)用構(gòu)造函數(shù)對str2進(jìn)行初始化 string str3 = “I am a string”;//語句3 拷貝初始化,先為字符串”I am a string“創(chuàng)建臨時對象,再把臨時對象作為參數(shù),使用拷貝構(gòu)造函數(shù)構(gòu)造str3 string str4 = str1;//語句4 拷貝初始化,這里相當(dāng)于隱式調(diào)用拷貝構(gòu)造函數(shù),而不是調(diào)用賦值運(yùn)算符函數(shù)
為了提高效率,允許編譯器跳過創(chuàng)建臨時對象這一步,直接調(diào)用構(gòu)造函數(shù)構(gòu)造要創(chuàng)建的對象,這樣就完全等價于直接初始化了(語句1和語句3等價)。但是需要辨別兩種情況。
當(dāng)拷貝構(gòu)造函數(shù)為private時:語句3和語句4在編譯時會報錯
使用explicit修飾構(gòu)造函數(shù)時:如果構(gòu)造函數(shù)存在隱式轉(zhuǎn)換,編譯時會報錯
C++的直接初始化與復(fù)制初始化的區(qū)別:
https://blog.csdn.net/qq936836/article/details/83450218
29、初始化和賦值的區(qū)別
對于簡單類型來說,初始化和賦值沒什么區(qū)別
對于類和復(fù)雜數(shù)據(jù)類型來說,這兩者的區(qū)別就大了,舉例如下:
class A{ public: int num1; int num2; public: A(int a=0, int b=0):num1(a),num2(b){}; A(const A& a){}; //重載 = 號操作符函數(shù) A& operator=(const A& a){ num1 = a.num1 + 1; num2 = a.num2 + 1; return *this; }; }; int main(){ A a(1,1); A a1 = a; //拷貝初始化操作,調(diào)用拷貝構(gòu)造函數(shù) A b; b = a;//賦值操作,對象a中,num1 = 1,num2 = 1;對象b中,num1 = 2,num2 = 2 return 0; }
30、extern“C”的用法
為了能夠正確的在C++代碼中調(diào)用C語言的代碼:在程序中加上extern “C”后,相當(dāng)于告訴編譯器這部分代碼是C語言寫的,因此要按照C語言進(jìn)行編譯,而不是C++;
哪些情況下使用extern “C”:
(1)C++代碼中調(diào)用C語言代碼;
(2)在C++中的頭文件中使用;
(3)在多個人協(xié)同開發(fā)時,可能有人擅長C語言,而有人擅長C++;
舉個例子,C++中調(diào)用C代碼:
#ifndef __MY_HANDLE_H__ #define __MY_HANDLE_H__ extern “C”{ typedef unsigned int result_t; typedef void* my_handle_t; my_handle_t create_handle(const char* name); result_t operate_on_handle(my_handle_t handle); void close_handle(my_handle_t handle); }
參考的blog中有一篇google code上的文章,專門寫extern “C”的,有興趣的讀者不妨去看看
《extern “C”的功能和用法研究》:
https://blog.csdn.net/sss_369/article/details/84060561
綜上,總結(jié)出使用方法,在C語言的頭文件中,對其外部函數(shù)只能指定為extern類型,C語言中不支持extern “C”聲明,在.c文件中包含了extern “C”時會出現(xiàn)編譯語法錯誤。所以使用extern “C”全部都放在于cpp程序相關(guān)文件或其頭文件中。
總結(jié)出如下形式:
(1)C++調(diào)用C函數(shù):
//xx.h extern int add(。..) //xx.c int add(){ } //xx.cpp extern “C” { #include “xx.h” }
(2)C調(diào)用C++函數(shù)
//xx.h extern “C”{ int add(); } //xx.cpp int add(){ } //xx.c extern int add();
31、模板函數(shù)和模板類的特例化
引入原因
編寫單一的模板,它能適應(yīng)多種類型的需求,使每種類型都具有相同的功能,但對于某種特定類型,如果要實(shí)現(xiàn)其特有的功能,單一模板就無法做到,這時就需要模板特例化
定義
對單一模板提供的一個特殊實(shí)例,它將一個或多個模板參數(shù)綁定到特定的類型或值上
(1)模板函數(shù)特例化
必須為原函數(shù)模板的每個模板參數(shù)都提供實(shí)參,且使用關(guān)鍵字template后跟一個空尖括號對《》,表明將原模板的所有模板參數(shù)提供實(shí)參,舉例如下:
template《typename T》 //模板函數(shù) int compare(const T &v1,const T &v2) { if(v1 》 v2) return -1; if(v2 》 v1) return 1; return 0; } //模板特例化,滿足針對字符串特定的比較,要提供所有實(shí)參,這里只有一個T template《》 int compare(const char* const &v1,const char* const &v2) { return strcmp(p1,p2); }
本質(zhì)
特例化的本質(zhì)是實(shí)例化一個模板,而非重載它。特例化不影響參數(shù)匹配。參數(shù)匹配都以最佳匹配為原則。例如,此處如果是compare(3,5),則調(diào)用普通的模板,若為compare(“hi”,”haha”)則調(diào)用特例化版本(因?yàn)檫@個cosnt char*相對于T,更匹配實(shí)參類型),注意二者函數(shù)體的語句不一樣了,實(shí)現(xiàn)不同功能。
注意
模板及其特例化版本應(yīng)該聲明在同一個頭文件中,且所有同名模板的聲明應(yīng)該放在前面,后面放特例化版本。
(2)類模板特例化
原理類似函數(shù)模板,不過在類中,我們可以對模板進(jìn)行特例化,也可以對類進(jìn)行部分特例化。對類進(jìn)行特例化時,仍然用template《》表示是一個特例化版本,例如:
template《》 class hash《sales_data》 { size_t operator()(sales_data& s); //里面所有T都換成特例化類型版本sales_data //按照最佳匹配原則,若T != sales_data,就用普通類模板,否則,就使用含有特定功能的特例化版本。 };
類模板的部分特例化
不必為所有模板參數(shù)提供實(shí)參,可以指定一部分而非所有模板參數(shù),一個類模板的部分特例化本身仍是一個模板,使用它時還必須為其特例化版本中未指定的模板參數(shù)提供實(shí)參(特例化時類名一定要和原來的模板相同,只是參數(shù)類型不同,按最佳匹配原則,哪個最匹配,就用相應(yīng)的模板)
特例化類中的部分成員
可以特例化類中的部分成員函數(shù)而不是整個類,舉個例子:
template《typename T》 class Foo { void Bar(); void Barst(T a)(); }; template《》 void Foo《int》::Bar() { //進(jìn)行int類型的特例化處理 cout 《《 “我是int型特例化” 《《 endl; } Foo《string》 fs; Foo《int》 fi;//使用特例化 fs.Bar();//使用的是普通模板,即Foo《string》::Bar() fi.Bar();//特例化版本,執(zhí)行Foo《int》::Bar() //Foo《string》::Bar()和Foo《int》::Bar()功能不同
32、C和C++的類型安全
什么是類型安全?
類型安全很大程度上可以等價于內(nèi)存安全,類型安全的代碼不會試圖訪問自己沒被授權(quán)的內(nèi)存區(qū)域?!邦愋桶踩背1挥脕硇稳菥幊陶Z言,其根據(jù)在于該門編程語言是否提供保障類型安全的機(jī)制;有的時候也用“類型安全”形容某個程序,判別的標(biāo)準(zhǔn)在于該程序是否隱含類型錯誤。類型安全的編程語言與類型安全的程序之間,沒有必然聯(lián)系。好的程序員可以使用類型不那么安全的語言寫出類型相當(dāng)安全的程序,相反的,差一點(diǎn)兒的程序員可能使用類型相當(dāng)安全的語言寫出類型不太安全的程序。絕對類型安全的編程語言暫時還沒有。
(1)C的類型安全
C只在局部上下文中表現(xiàn)出類型安全,比如試圖從一種結(jié)構(gòu)體的指針轉(zhuǎn)換成另一種結(jié)構(gòu)體的指針時,編譯器將會報告錯誤,除非使用顯式類型轉(zhuǎn)換。然而,C中相當(dāng)多的操作是不安全的。以下是兩個十分常見的例子:
printf格式輸出
上述代碼中,使用%d控制整型數(shù)字的輸出,沒有問題,但是改成%f時,明顯輸出錯誤,再改成%s時,運(yùn)行直接報segmentation fault錯誤
malloc函數(shù)的返回值
malloc是C中進(jìn)行內(nèi)存分配的函數(shù),它的返回類型是void*即空類型指針,常常有這樣的用法char* pStr=(char*)malloc(100*sizeof(char)),這里明顯做了顯式的類型轉(zhuǎn)換。類型匹配尚且沒有問題,但是一旦出現(xiàn)int* pInt=(int*)malloc(100*sizeof(char))就很可能帶來一些問題,而這樣的轉(zhuǎn)換C并不會提示錯誤。
(2)C++的類型安全
如果C++使用得當(dāng),它將遠(yuǎn)比C更有類型安全性。相比于C語言,C++提供了一些新的機(jī)制保障類型安全:
操作符new返回的指針類型嚴(yán)格與對象匹配,而不是void*
C中很多以void*為參數(shù)的函數(shù)可以改寫為C++模板函數(shù),而模板是支持類型檢查的;
引入const關(guān)鍵字代替#define constants,它是有類型、有作用域的,而#define constants只是簡單的文本替換
一些#define宏可被改寫為inline函數(shù),結(jié)合函數(shù)的重載,可在類型安全的前提下支持多種類型,當(dāng)然改寫為模板也能保證類型安全
C++提供了dynamic_cast關(guān)鍵字,使得轉(zhuǎn)換過程更加安全,因?yàn)閐ynamic_cast比static_cast涉及更多具體的類型檢查。
例1:使用void*進(jìn)行類型轉(zhuǎn)換
例2:不同類型指針之間轉(zhuǎn)換
#include《iostream》 using namespace std; class Parent{}; class Child1 : public Parent { public: int i; Child1(int e):i(e){} }; class Child2 : public Parent { public: double d; Child2(double e):d(e){} }; int main() { Child1 c1(5); Child2 c2(4.1); Parent* pp; Child1* pc1; pp=&c1; pc1=(Child1*)pp; // 類型向下轉(zhuǎn)換 強(qiáng)制轉(zhuǎn)換,由于類型仍然為Child1*,不造成錯誤 cout《《pc1-》i《《endl; //輸出:5 pp=&c2; pc1=(Child1*)pp; //強(qiáng)制轉(zhuǎn)換,且類型發(fā)生變化,將造成錯誤 cout《《pc1-》i《《endl;// 輸出:1717986918 return 0; }
上面兩個例子之所以引起類型不安全的問題,是因?yàn)槌绦騿T使用不得當(dāng)。第一個例子用到了空類型指針void*,第二個例子則是在兩個類型指針之間進(jìn)行強(qiáng)制轉(zhuǎn)換。因此,想保證程序的類型安全性,應(yīng)盡量避免使用空類型指針void*,盡量不對兩種類型指針做強(qiáng)制轉(zhuǎn)換。
33、為什么析構(gòu)函數(shù)一般寫成虛函數(shù)
由于類的多態(tài)性,基類指針可以指向派生類的對象,如果刪除該基類的指針,就會調(diào)用該指針指向的派生類析構(gòu)函數(shù),而派生類的析構(gòu)函數(shù)又自動調(diào)用基類的析構(gòu)函數(shù),這樣整個派生類的對象完全被釋放。如果析構(gòu)函數(shù)不被聲明成虛函數(shù),則編譯器實(shí)施靜態(tài)綁定,在刪除基類指針時,只會調(diào)用基類的析構(gòu)函數(shù)而不調(diào)用派生類析構(gòu)函數(shù),這樣就會造成派生類對象析構(gòu)不完全,造成內(nèi)存泄漏。所以將析構(gòu)函數(shù)聲明為虛函數(shù)是十分必要的。在實(shí)現(xiàn)多態(tài)時,當(dāng)用基類操作派生類,在析構(gòu)時防止只析構(gòu)基類而不析構(gòu)派生類的狀況發(fā)生,要將基類的析構(gòu)函數(shù)聲明為虛函數(shù)。舉個例子:
#include 《iostream》 using namespace std; class Parent{ public: Parent(){ cout 《《 “Parent construct function” 《《 endl; }; ~Parent(){ cout 《《 “Parent destructor function” 《《endl; } }; class Son : public Parent{ public: Son(){ cout 《《 “Son construct function” 《《 endl; }; ~Son(){ cout 《《 “Son destructor function” 《《endl; } }; int main() { Parent* p = new Son(); delete p; p = NULL; return 0; } //運(yùn)行結(jié)果: //Parent construct function //Son construct function //Parent destructor function
將基類的析構(gòu)函數(shù)聲明為虛函數(shù):
#include 《iostream》 using namespace std; class Parent{ public: Parent(){ cout 《《 “Parent construct function” 《《 endl; }; virtual ~Parent(){ cout 《《 “Parent destructor function” 《《endl; } }; class Son : public Parent{ public: Son(){ cout 《《 “Son construct function” 《《 endl; }; ~Son(){ cout 《《 “Son destructor function” 《《endl; } }; int main() { Parent* p = new Son(); delete p; p = NULL; return 0; } //運(yùn)行結(jié)果: //Parent construct function //Son construct function //Son destructor function //Parent destructor function
34、構(gòu)造函數(shù)能否聲明為虛函數(shù)或者純虛函數(shù),析構(gòu)函數(shù)呢?
析構(gòu)函數(shù):
析構(gòu)函數(shù)可以為虛函數(shù),并且一般情況下基類析構(gòu)函數(shù)要定義為虛函數(shù)。
只有在基類析構(gòu)函數(shù)定義為虛函數(shù)時,調(diào)用操作符delete銷毀指向?qū)ο蟮幕愔羔槙r,才能準(zhǔn)確調(diào)用派生類的析構(gòu)函數(shù)(從該級向上按序調(diào)用虛函數(shù)),才能準(zhǔn)確銷毀數(shù)據(jù)。
析構(gòu)函數(shù)可以是純虛函數(shù),含有純虛函數(shù)的類是抽象類,此時不能被實(shí)例化。但派生類中可以根據(jù)自身需求重新改寫基類中的純虛函數(shù)。
構(gòu)造函數(shù):
構(gòu)造函數(shù)不能定義為虛函數(shù)。在構(gòu)造函數(shù)中可以調(diào)用虛函數(shù),不過此時調(diào)用的是正在構(gòu)造的類中的虛函數(shù),而不是子類的虛函數(shù),因?yàn)榇藭r子類尚未構(gòu)造好。
35、C++中的重載、重寫(覆蓋)和隱藏的區(qū)別
(1)重載(overload)
重載是指在同一范圍定義中的同名成員函數(shù)才存在重載關(guān)系。主要特點(diǎn)是函數(shù)名相同,參數(shù)類型和數(shù)目有所不同,不能出現(xiàn)參數(shù)個數(shù)和類型均相同,僅僅依靠返回值不同來區(qū)分的函數(shù)。重載和函數(shù)成員是否是虛函數(shù)無關(guān)。舉個例子:
class A{ 。.. virtual int fun(); void fun(int); void fun(double, double); static int fun(char); 。.. }
(2)重寫(覆蓋)(override)
重寫指的是在派生類中覆蓋基類中的同名函數(shù),重寫就是重寫函數(shù)體,要求基類函數(shù)必須是虛函數(shù)且:
與基類的虛函數(shù)有相同的參數(shù)個數(shù)
與基類的虛函數(shù)有相同的參數(shù)類型
與基類的虛函數(shù)有相同的返回值類型
舉個例子:
//父類 class A{ public: virtual int fun(int a){} } //子類 class B : public A{ public: //重寫,一般加override可以確保是重寫父類的函數(shù) virtual int fun(int a) override{} }
重載與重寫的區(qū)別:
重寫是父類和子類之間的垂直關(guān)系,重載是不同函數(shù)之間的水平關(guān)系
重寫要求參數(shù)列表相同,重載則要求參數(shù)列表不同,返回值不要求
重寫關(guān)系中,調(diào)用方法根據(jù)對象類型決定,重載根據(jù)調(diào)用時實(shí)參表與形參表的對應(yīng)關(guān)系來選擇函數(shù)體
(3)隱藏(hide)
隱藏指的是某些情況下,派生類中的函數(shù)屏蔽了基類中的同名函數(shù),包括以下情況:
兩個函數(shù)參數(shù)相同,但是基類函數(shù)不是虛函數(shù)。和重寫的區(qū)別在于基類函數(shù)是否是虛函數(shù)。舉個例子:
//父類 class A{ public: void fun(int a){ cout 《《 “A中的fun函數(shù)” 《《 endl; } }; //子類 class B : public A{ public: //隱藏父類的fun函數(shù) void fun(int a){ cout 《《 “B中的fun函數(shù)” 《《 endl; } }; int main(){ B b; b.fun(2); //調(diào)用的是B中的fun函數(shù) b.A::fun(2); //調(diào)用A中fun函數(shù) return 0; }
兩個函數(shù)參數(shù)不同,無論基類函數(shù)是不是虛函數(shù),都會被隱藏。和重載的區(qū)別在于兩個函數(shù)不在同一個類中。舉個例子:
//父類 class A{ public: virtual void fun(int a){ cout 《《 “A中的fun函數(shù)” 《《 endl; } }; //子類 class B : public A{ public: //隱藏父類的fun函數(shù) virtual void fun(char* a){ cout 《《 “A中的fun函數(shù)” 《《 endl; } }; int main(){ B b; b.fun(2); //報錯,調(diào)用的是B中的fun函數(shù),參數(shù)類型不對 b.A::fun(2); //調(diào)用A中fun函數(shù) return 0; }
36、C++的多態(tài)如何實(shí)現(xiàn)
C++的多態(tài)性,一言以蔽之就是:
在基類的函數(shù)前加上virtual關(guān)鍵字,在派生類中重寫該函數(shù),運(yùn)行時將會根據(jù)所指對象的實(shí)際類型來調(diào)用相應(yīng)的函數(shù),如果對象類型是派生類,就調(diào)用派生類的函數(shù),如果對象類型是基類,就調(diào)用基類的函數(shù)。
舉個例子:
#include 《iostream》 using namespace std; class Base{ public: virtual void fun(){ cout 《《 “ Base::func()” 《《endl; } }; class Son1 : public Base{ public: virtual void fun() override{ cout 《《 “ Son1::func()” 《《endl; } }; class Son2 : public Base{ }; int main() { Base* base = new Son1; base-》fun(); base = new Son2; base-》fun(); delete base; base = NULL; return 0; } // 運(yùn)行結(jié)果 // Son1::func() // Base::func()
例子中,Base為基類,其中的函數(shù)為虛函數(shù)。子類1繼承并重寫了基類的函數(shù),子類2繼承基類但沒有重寫基類的函數(shù),從結(jié)果分析子類體現(xiàn)了多態(tài)性,那么為什么會出現(xiàn)多態(tài)性,其底層的原理是什么?這里需要引出虛表和虛基表指針的概念。
虛表:虛函數(shù)表的縮寫,類中含有virtual關(guān)鍵字修飾的方法時,編譯器會自動生成虛表
虛表指針:在含有虛函數(shù)的類實(shí)例化對象時,對象地址的前四個字節(jié)存儲的指向虛表的指針
上圖中展示了虛表和虛表指針在基類對象和派生類對象中的模型,下面闡述實(shí)現(xiàn)多態(tài)的過程:
(1)編譯器在發(fā)現(xiàn)基類中有虛函數(shù)時,會自動為每個含有虛函數(shù)的類生成一份虛表,該表是一個一維數(shù)組,虛表里保存了虛函數(shù)的入口地址
(2)編譯器會在每個對象的前四個字節(jié)中保存一個虛表指針,即vptr,指向?qū)ο笏鶎兕惖奶摫?。在?gòu)造時,根據(jù)對象的類型去初始化虛指針vptr,從而讓vptr指向正確的虛表,從而在調(diào)用虛函數(shù)時,能找到正確的函數(shù)
(3)所謂的合適時機(jī),在派生類定義對象時,程序運(yùn)行會自動調(diào)用構(gòu)造函數(shù),在構(gòu)造函數(shù)中創(chuàng)建虛表并對虛表初始化。在構(gòu)造子類對象時,會先調(diào)用父類的構(gòu)造函數(shù),此時,編譯器只“看到了”父類,并為父類對象初始化虛表指針,令它指向父類的虛表;當(dāng)調(diào)用子類的構(gòu)造函數(shù)時,為子類對象初始化虛表指針,令它指向子類的虛表
(4)當(dāng)派生類對基類的虛函數(shù)沒有重寫時,派生類的虛表指針指向的是基類的虛表;當(dāng)派生類對基類的虛函數(shù)重寫時,派生類的虛表指針指向的是自身的虛表;當(dāng)派生類中有自己的虛函數(shù)時,在自己的虛表中將此虛函數(shù)地址添加在后面
這樣指向派生類的基類指針在運(yùn)行時,就可以根據(jù)派生類對虛函數(shù)重寫情況動態(tài)的進(jìn)行調(diào)用,從而實(shí)現(xiàn)多態(tài)性。
37、C++有哪幾種的構(gòu)造函數(shù)
C++中的構(gòu)造函數(shù)可以分為4類:
默認(rèn)構(gòu)造函數(shù)
初始化構(gòu)造函數(shù)(有參數(shù))
拷貝構(gòu)造函數(shù)
移動構(gòu)造函數(shù)(move和右值引用)
委托構(gòu)造函數(shù)
轉(zhuǎn)換構(gòu)造函數(shù)
舉個例子:
#include 《iostream》 using namespace std; class Student{ public: Student(){//默認(rèn)構(gòu)造函數(shù),沒有參數(shù) this-》age = 20; this-》num = 1000; }; Student(int a, int n):age(a), num(n){}; //初始化構(gòu)造函數(shù),有參數(shù)和參數(shù)列表 Student(const Student& s){//拷貝構(gòu)造函數(shù),這里與編譯器生成的一致 this-》age = s.age; this-》num = s.num; }; Student(int r){ //轉(zhuǎn)換構(gòu)造函數(shù),形參是其他類型變量,且只有一個形參 this-》age = r; this-》num = 1002; }; ~Student(){} public: int age; int num; }; int main(){ Student s1; Student s2(18,1001); int a = 10; Student s3(a); Student s4(s3); printf(“s1 age:%d, num:%d ”, s1.age, s1.num); printf(“s2 age:%d, num:%d ”, s2.age, s2.num); printf(“s3 age:%d, num:%d ”, s3.age, s3.num); printf(“s2 age:%d, num:%d ”, s4.age, s4.num); return 0; } //運(yùn)行結(jié)果 //s1 age:20, num:1000 //s2 age:18, num:1001 //s3 age:10, num:1002 //s2 age:10, num:1002
默認(rèn)構(gòu)造函數(shù)和初始化構(gòu)造函數(shù)在定義類的對象,完成對象的初始化工作
復(fù)制構(gòu)造函數(shù)用于復(fù)制本類的對象
轉(zhuǎn)換構(gòu)造函數(shù)用于將其他類型的變量,隱式轉(zhuǎn)換為本類對象
《淺談C++中的幾種構(gòu)造函數(shù)》:
https://blog.csdn.net/zxc024000/article/details/51153743
38、淺拷貝和深拷貝的區(qū)別
淺拷貝
淺拷貝只是拷貝一個指針,并沒有新開辟一個地址,拷貝的指針和原來的指針指向同一塊地址,如果原來的指針?biāo)赶虻馁Y源釋放了,那么再釋放淺拷貝的指針的資源就會出現(xiàn)錯誤。
深拷貝
深拷貝不僅拷貝值,還開辟出一塊新的空間用來存放新的值,即使原先的對象被析構(gòu)掉,釋放內(nèi)存了也不會影響到深拷貝得到的值。在自己實(shí)現(xiàn)拷貝賦值的時候,如果有指針變量的話是需要自己實(shí)現(xiàn)深拷貝的。
#include 《iostream》 #include 《string.h》 using namespace std; class Student { private: int num; char *name; public: Student(){ name = new char(20); cout 《《 “Student” 《《 endl; }; ~Student(){ cout 《《 “~Student ” 《《 &name 《《 endl; delete name; name = NULL; }; Student(const Student &s){//拷貝構(gòu)造函數(shù) //淺拷貝,當(dāng)對象的name和傳入對象的name指向相同的地址 name = s.name; //深拷貝 //name = new char(20); //memcpy(name, s.name, strlen(s.name)); cout 《《 “copy Student” 《《 endl; }; }; int main() { {// 花括號讓s1和s2變成局部對象,方便測試 Student s1; Student s2(s1);// 復(fù)制對象 } system(“pause”); return 0; } //淺拷貝執(zhí)行結(jié)果: //Student //copy Student //~Student 0x7fffed0c3ec0 //~Student 0x7fffed0c3ed0 //*** Error in `/tmp/815453382/a.out‘: double free or corruption (fasttop): 0x0000000001c82c20 *** //深拷貝執(zhí)行結(jié)果: //Student //copy Student //~Student 0x7fffebca9fb0 //~Student 0x7fffebca9fc0
從執(zhí)行結(jié)果可以看出,淺拷貝在對象的拷貝創(chuàng)建時存在風(fēng)險,即被拷貝的對象析構(gòu)釋放資源之后,拷貝對象析構(gòu)時會再次釋放一個已經(jīng)釋放的資源,深拷貝的結(jié)果是兩個對象之間沒有任何關(guān)系,各自成員地址不同。
《C++面試題之淺拷貝和深拷貝的區(qū)別》:
https://blog.csdn.net/caoshangpa/article/details/79226270
39、內(nèi)聯(lián)函數(shù)和宏定義的區(qū)別
內(nèi)聯(lián)(inline)函數(shù)和普通函數(shù)相比可以加快程序運(yùn)行的速度,因?yàn)椴恍枰袛嗾{(diào)用,在編譯的時候內(nèi)聯(lián)函數(shù)可以直接嵌入到目標(biāo)代碼中。
內(nèi)聯(lián)函數(shù)適用場景
使用宏定義的地方都可以使用inline函數(shù)
作為類成員接口函數(shù)來讀寫類的私有成員或者保護(hù)成員,會提高效率
為什么不能把所有的函數(shù)寫成內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)以代碼復(fù)雜為代價,它以省去函數(shù)調(diào)用的開銷來提高執(zhí)行效率。所以一方面如果內(nèi)聯(lián)函數(shù)體內(nèi)代碼執(zhí)行時間相比函數(shù)調(diào)用開銷較大,則沒有太大的意義;另一方面每一處內(nèi)聯(lián)函數(shù)的調(diào)用都要復(fù)制代碼,消耗更多的內(nèi)存空間,因此以下情況不宜使用內(nèi)聯(lián)函數(shù):
函數(shù)體內(nèi)的代碼比較長,將導(dǎo)致內(nèi)存消耗代價
函數(shù)體內(nèi)有循環(huán),函數(shù)執(zhí)行時間要比函數(shù)調(diào)用開銷大
主要區(qū)別
內(nèi)聯(lián)函數(shù)在編譯時展開,宏在預(yù)編譯時展開
內(nèi)聯(lián)函數(shù)直接嵌入到目標(biāo)代碼中,宏是簡單的做文本替換
內(nèi)聯(lián)函數(shù)有類型檢測、語法判斷等功能,而宏沒有
內(nèi)聯(lián)函數(shù)是函數(shù),宏不是
宏定義時要注意書寫(參數(shù)要括起來)否則容易出現(xiàn)歧義,內(nèi)聯(lián)函數(shù)不會產(chǎn)生歧義
內(nèi)聯(lián)函數(shù)代碼是被放到符號表中,使用時像宏一樣展開,沒有調(diào)用的開銷,效率很高;
在使用時,宏只做簡單字符串替換(編譯前)。而內(nèi)聯(lián)函數(shù)可以進(jìn)行參數(shù)類型檢查(編譯時),且具有返回值。
內(nèi)聯(lián)函數(shù)本身是函數(shù),強(qiáng)調(diào)函數(shù)特性,具有重載等功能。
內(nèi)聯(lián)函數(shù)可以作為某個類的成員函數(shù),這樣可以使用類的保護(hù)成員和私有成員,進(jìn)而提升效率。而當(dāng)一個表達(dá)式涉及到類保護(hù)成員或私有成員時,宏就不能實(shí)現(xiàn)了。
40、構(gòu)造函數(shù)、析構(gòu)函數(shù)、虛函數(shù)可否聲明為內(nèi)聯(lián)函數(shù)
首先,將這些函數(shù)聲明為內(nèi)聯(lián)函數(shù),在語法上沒有錯誤。因?yàn)閕nline同register一樣,只是個建議,編譯器并不一定真正的內(nèi)聯(lián)。
register關(guān)鍵字:這個關(guān)鍵字請求編譯器盡可能的將變量存在CPU內(nèi)部寄存器中,而不是通過內(nèi)存尋址訪問,以提高效率
舉個例子:
#include 《iostream》 using namespace std; class A { public: inline A() { cout 《《 “inline construct()” 《《endl; } inline ~A() { cout 《《 “inline destruct()” 《《endl; } inline virtual void virtualFun() { cout 《《 “inline virtual function” 《《endl; } }; int main() { A a; a.virtualFun(); return 0; } //輸出結(jié)果 //inline construct() //inline virtual function //inline destruct()
構(gòu)造函數(shù)和析構(gòu)函數(shù)聲明為內(nèi)聯(lián)函數(shù)是沒有意義的
《Effective C++》中所闡述的是:將構(gòu)造函數(shù)和析構(gòu)函數(shù)聲明為inline是沒有什么意義的,即編譯器并不真正對聲明為inline的構(gòu)造和析構(gòu)函數(shù)進(jìn)行內(nèi)聯(lián)操作,因?yàn)榫幾g器會在構(gòu)造和析構(gòu)函數(shù)中添加額外的操作(申請/釋放內(nèi)存,構(gòu)造/析構(gòu)對象等),致使構(gòu)造函數(shù)/析構(gòu)函數(shù)并不像看上去的那么精簡。其次,class中的函數(shù)默認(rèn)是inline型的,編譯器也只是有選擇性的inline,將構(gòu)造函數(shù)和析構(gòu)函數(shù)聲明為內(nèi)聯(lián)函數(shù)是沒有什么意義的。
將虛函數(shù)聲明為inline,要分情況討論
有的人認(rèn)為虛函數(shù)被聲明為inline,但是編譯器并沒有對其內(nèi)聯(lián),他們給出的理由是inline是編譯期決定的,而虛函數(shù)是運(yùn)行期決定的,即在不知道將要調(diào)用哪個函數(shù)的情況下,如何將函數(shù)內(nèi)聯(lián)呢?
上述觀點(diǎn)看似正確,其實(shí)不然,如果虛函數(shù)在編譯器就能夠決定將要調(diào)用哪個函數(shù)時,就能夠內(nèi)聯(lián),那么什么情況下編譯器可以確定要調(diào)用哪個函數(shù)呢,答案是當(dāng)用對象調(diào)用虛函數(shù)(此時不具有多態(tài)性)時,就內(nèi)聯(lián)展開
綜上,當(dāng)是指向派生類的指針(多態(tài)性)調(diào)用聲明為inline的虛函數(shù)時,不會內(nèi)聯(lián)展開;當(dāng)是對象本身調(diào)用虛函數(shù)時,會內(nèi)聯(lián)展開,當(dāng)然前提依然是函數(shù)并不復(fù)雜的情況下
《構(gòu)造函數(shù)、析構(gòu)函數(shù)、虛函數(shù)可否內(nèi)聯(lián),有何意義》:
https://www.cnblogs.com/helloweworld/archive/2013/06/14/3136705.html
41、auto、decltype和decltype(auto)的用法
(1)auto
C++11新標(biāo)準(zhǔn)引入了auto類型說明符,用它就能讓編譯器替我們?nèi)シ治霰磉_(dá)式所屬的類型。和原來那些只對應(yīng)某種特定的類型說明符(例如 int)不同,
auto 讓編譯器通過初始值來進(jìn)行類型推演。從而獲得定義變量的類型,所以說 auto 定義的變量必須有初始值。舉個例子:
//普通;類型 int a = 1, b = 3; auto c = a + b;// c為int型 //const類型 const int i = 5; auto j = i; // 變量i是頂層const, 會被忽略, 所以j的類型是int auto k = &i; // 變量i是一個常量, 對常量取地址是一種底層const, 所以b的類型是const int* const auto l = i; //如果希望推斷出的類型是頂層const的, 那么就需要在auto前面加上cosnt //引用和指針類型 int x = 2; int& y = x; auto z = y; //z是int型不是int& 型 auto& p1 = y; //p1是int&型 auto p2 = &x; //p2是指針類型int*
(2)decltype
有的時候我們還會遇到這種情況,我們希望從表達(dá)式中推斷出要定義變量的類型,但卻不想用表達(dá)式的值去初始化變量。還有可能是函數(shù)的返回類型為某表達(dá)式的值類型。在這些時候auto顯得就無力了,所以C++11又引入了第二種類型說明符decltype,它的作用是選擇并返回操作數(shù)的數(shù)據(jù)類型。在此過程中,編譯器只是分析表達(dá)式并得到它的類型,卻不進(jìn)行實(shí)際的計算表達(dá)式的值。
int func() {return 0}; //普通類型 decltype(func()) sum = 5; // sum的類型是函數(shù)func()的返回值的類型int, 但是這時不會實(shí)際調(diào)用函數(shù)func() int a = 0; decltype(a) b = 4; // a的類型是int, 所以b的類型也是int //不論是頂層const還是底層const, decltype都會保留 const int c = 3; decltype(c) d = c; // d的類型和c是一樣的, 都是頂層const int e = 4; const int* f = &e; // f是底層const decltype(f) g = f; // g也是底層const //引用與指針類型 //1. 如果表達(dá)式是引用類型, 那么decltype的類型也是引用 const int i = 3, &j = i; decltype(j) k = 5; // k的類型是 const int& //2. 如果表達(dá)式是引用類型, 但是想要得到這個引用所指向的類型, 需要修改表達(dá)式: int i = 3, &r = i; decltype(r + 0) t = 5; // 此時是int類型 //3. 對指針的解引用操作返回的是引用類型 int i = 3, j = 6, *p = &i; decltype(*p) c = j; // c是int&類型, c和j綁定在一起 //4. 如果一個表達(dá)式的類型不是引用, 但是我們需要推斷出引用, 那么可以加上一對括號, 就變成了引用類型了 int i = 3; decltype((i)) j = i; // 此時j的類型是int&類型, j和i綁定在了一起
(3)decltype(auto)
decltype(auto)是C++14新增的類型指示符,可以用來聲明變量以及指示函數(shù)返回類型。在使用時,會將“=”號左邊的表達(dá)式替換掉auto,再根據(jù)decltype的語法規(guī)則來確定類型。舉個例子:
int e = 4; const int* f = &e; // f是底層const decltype(auto) j = f;//j的類型是const int* 并且指向的是e
《auto和decltype的用法總結(jié)》:
https://www.cnblogs.com/XiangfeiAi/p/4451904.html
《C++11新特性中auto 和 decltype 區(qū)別和聯(lián)系》:
https://www.jb51.net/article/103666.htm
42、public,protected和private訪問和繼承權(quán)限/public/protected/private的區(qū)別?
public的變量和函數(shù)在類的內(nèi)部外部都可以訪問。
protected的變量和函數(shù)只能在類的內(nèi)部和其派生類中訪問。
private修飾的元素只能在類內(nèi)訪問。
(一)訪問權(quán)限
派生類可以繼承基類中除了構(gòu)造/析構(gòu)、賦值運(yùn)算符重載函數(shù)之外的成員,但是這些成員的訪問屬性在派生過程中也是可以調(diào)整的,三種派生方式的訪問權(quán)限如下表所示:注意外部訪問并不是真正的外部訪問,而是在通過派生類的對象對基類成員的訪問。
派生類對基類成員的訪問形象有如下兩種:
內(nèi)部訪問:由派生類中新增的成員函數(shù)對從基類繼承來的成員的訪問
外部訪問:在派生類外部,通過派生類的對象對從基類繼承來的成員的訪問
(二)繼承權(quán)限
public繼承
公有繼承的特點(diǎn)是基類的公有成員和保護(hù)成員作為派生類的成員時,都保持原有的狀態(tài),而基類的私有成員任然是私有的,不能被這個派生類的子類所訪問
protected繼承
保護(hù)繼承的特點(diǎn)是基類的所有公有成員和保護(hù)成員都成為派生類的保護(hù)成員,并且只能被它的派生類成員函數(shù)或友元函數(shù)訪問,基類的私有成員仍然是私有的。
private繼承
私有繼承的特點(diǎn)是基類的所有公有成員和保護(hù)成員都成為派生類的私有成員,并不被它的派生類的子類所訪問,基類的成員只能由自己派生類訪問,無法再往下繼承,訪問規(guī)則如下表
43、如何用代碼判斷大小端存儲
大端存儲:字?jǐn)?shù)據(jù)的高字節(jié)存儲在低地址中
小端存儲:字?jǐn)?shù)據(jù)的低字節(jié)存儲在低地址中
例如:32bit的數(shù)字0x12345678
所以在Socket編程中,往往需要將操作系統(tǒng)所用的小端存儲的IP地址轉(zhuǎn)換為大端存儲,這樣才能進(jìn)行網(wǎng)絡(luò)傳輸
小端模式中的存儲方式為:
大端模式中的存儲方式為:
了解了大小端存儲的方式,如何在代碼中進(jìn)行判斷呢?下面介紹兩種判斷方式:
方式一:使用強(qiáng)制類型轉(zhuǎn)換-這種法子不錯
#include 《iostream》 using namespace std; int main() { int a = 0x1234; //由于int和char的長度不同,借助int型轉(zhuǎn)換成char型,只會留下低地址的部分 char c = (char)(a); if (c == 0x12) cout 《《 “big endian” 《《 endl; else if(c == 0x34) cout 《《 “l(fā)ittle endian” 《《 endl; }
方式二:巧用union聯(lián)合體
#include 《iostream》 using namespace std; //union聯(lián)合體的重疊式存儲,endian聯(lián)合體占用內(nèi)存的空間為每個成員字節(jié)長度的最大值 union endian { int a; char ch; }; int main() { endian value; value.a = 0x1234; //a和ch共用4字節(jié)的內(nèi)存空間 if (value.ch == 0x12) cout 《《 “big endian”《《endl; else if (value.ch == 0x34) cout 《《 “l(fā)ittle endian”《《endl; }
《寫程序判斷系統(tǒng)是大端序還是小端序》:
https://www.cnblogs.com/zhoudayang/p/5985563.html
44、volatile、mutable和explicit關(guān)鍵字的用法
(1)volatile
volatile 關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。遇到這個關(guān)鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問。
當(dāng)要求使用 volatile 聲明的變量的值的時候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過數(shù)據(jù)。
volatile定義變量的值是易變的,每次用到這個變量的值的時候都要去重新讀取這個變量的值,而不是讀寄存器內(nèi)的備份。多線程中被幾個任務(wù)共享的變量需要定義為volatile類型。
volatile 指針
volatile 指針和 const 修飾詞類似,const 有常量指針和指針常量的說法,volatile 也有相應(yīng)的概念
修飾由指針指向的對象、數(shù)據(jù)是 const 或 volatile 的:
const char* cpch; volatile char* vpch;
指針自身的值——一個代表地址的整數(shù)變量,是 const 或 volatile 的:
char* const pchc; char* volatile pchv;
注意:
可以把一個非volatile int賦給volatile int,但是不能把非volatile對象賦給一個volatile對象。
除了基本類型外,對用戶定義類型也可以用volatile類型進(jìn)行修飾。
C++中一個有volatile標(biāo)識符的類只能訪問它接口的子集,一個由類的實(shí)現(xiàn)者控制的子集。用戶只能用const_cast來獲得對類型接口的完全訪問。此外,volatile向const一樣會從類傳遞到它的成員。
多線程下的volatile
有些變量是用volatile關(guān)鍵字聲明的。當(dāng)兩個線程都要用到某一個變量且該變量的值會被改變時,應(yīng)該用volatile聲明,該關(guān)鍵字的作用是防止優(yōu)化編譯器把變量從內(nèi)存裝入CPU寄存器中。如果變量被裝入寄存器,那么兩個線程有可能一個使用內(nèi)存中的變量,一個使用寄存器中的變量,這會造成程序的錯誤執(zhí)行。volatile的意思是讓編譯器每次操作該變量時一定要從內(nèi)存中真正取出,而不是使用已經(jīng)存在寄存器中的值。
(2)mutable
mutable的中文意思是“可變的,易變的”,跟constant(既C++中的const)是反義詞。在C++中,mutable也是為了突破const的限制而設(shè)置的。被mutable修飾的變量,將永遠(yuǎn)處于可變的狀態(tài),即使在一個const函數(shù)中。我們知道,如果類的成員函數(shù)不會改變對象的狀態(tài),那么這個成員函數(shù)一般會聲明成const的。但是,有些時候,我們需要在const函數(shù)里面修改一些跟類狀態(tài)無關(guān)的數(shù)據(jù)成員,那么這個函數(shù)就應(yīng)該被mutable來修飾,并且放在函數(shù)后后面關(guān)鍵字位置。
(3)explicit
explicit關(guān)鍵字用來修飾類的構(gòu)造函數(shù),被修飾的構(gòu)造函數(shù)的類,不能發(fā)生相應(yīng)的隱式類型轉(zhuǎn)換,只能以顯示的方式進(jìn)行類型轉(zhuǎn)換,注意以下幾點(diǎn):
explicit 關(guān)鍵字只能用于類內(nèi)部的構(gòu)造函數(shù)聲明上
explicit 關(guān)鍵字作用于單個參數(shù)的構(gòu)造函數(shù)
被explicit修飾的構(gòu)造函數(shù)的類,不能發(fā)生相應(yīng)的隱式類型轉(zhuǎn)換
45、什么情況下會調(diào)用拷貝構(gòu)造函數(shù)
用類的一個實(shí)例化對象去初始化另一個對象的時候
函數(shù)的參數(shù)是類的對象時(非引用傳遞)
函數(shù)的返回值是函數(shù)體內(nèi)局部對象的類的對象時 ,此時雖然發(fā)生(Named return Value優(yōu)化)NRV優(yōu)化,但是由于返回方式是值傳遞,所以會在返回值的地方調(diào)用拷貝構(gòu)造函數(shù)
另:第三種情況在Linux g++ 下則不會發(fā)生拷貝構(gòu)造函數(shù),不僅如此即使返回局部對象的引用,依然不會發(fā)生拷貝構(gòu)造函數(shù)
總結(jié)就是:即使發(fā)生NRV優(yōu)化的情況下,Linux+ g++的環(huán)境是不管值返回方式還是引用方式返回的方式都不會發(fā)生拷貝構(gòu)造函數(shù),而Windows + VS2019在值返回的情況下發(fā)生拷貝構(gòu)造函數(shù),引用返回方式則不發(fā)生拷貝構(gòu)造函數(shù)。
在c++編譯器發(fā)生NRV優(yōu)化,如果是引用返回的形式則不會調(diào)用拷貝構(gòu)造函數(shù),如果是值傳遞的方式依然會發(fā)生拷貝構(gòu)造函數(shù)。
在VS2019下進(jìn)行下述實(shí)驗(yàn):
舉個例子:
class A { public: A() {}; A(const A& a) { cout 《《 “copy constructor is called” 《《 endl; }; ~A() {}; }; void useClassA(A a) {} A getClassA()//此時會發(fā)生拷貝構(gòu)造函數(shù)的調(diào)用,雖然發(fā)生NRV優(yōu)化,但是依然調(diào)用拷貝構(gòu)造函數(shù) { A a; return a; } //A& getClassA2()// VS2019下,此時編輯器會進(jìn)行(Named return Value優(yōu)化)NRV優(yōu)化,不調(diào)用拷貝構(gòu)造函數(shù) ,如果是引用傳遞的方式返回當(dāng)前函數(shù)體內(nèi)生成的對象時,并不發(fā)生拷貝構(gòu)造函數(shù)的調(diào)用 //{ // A a; // return a; //} int main() { A a1, a2,a3,a4; A a2 = a1; //調(diào)用拷貝構(gòu)造函數(shù),對應(yīng)情況1 useClassA(a1);//調(diào)用拷貝構(gòu)造函數(shù),對應(yīng)情況2 a3 = getClassA();//發(fā)生NRV優(yōu)化,但是值返回,依然會有拷貝構(gòu)造函數(shù)的調(diào)用 情況3 a4 = getClassA2(a1);//發(fā)生NRV優(yōu)化,且引用返回自身,不會調(diào)用 return 0; }
情況1比較好理解
情況2的實(shí)現(xiàn)過程是,調(diào)用函數(shù)時先根據(jù)傳入的實(shí)參產(chǎn)生臨時對象,再用拷貝構(gòu)造去初始化這個臨時對象,在函數(shù)中與形參對應(yīng),函數(shù)調(diào)用結(jié)束后析構(gòu)臨時對象
情況3在執(zhí)行return時,理論的執(zhí)行過程是:產(chǎn)生臨時對象,調(diào)用拷貝構(gòu)造函數(shù)把返回對象拷貝給臨時對象,函數(shù)執(zhí)行完先析構(gòu)局部變量,再析構(gòu)臨時對象, 依然會調(diào)用拷貝構(gòu)造函數(shù)
《C++拷貝構(gòu)造函數(shù)詳解》:
https://www.cnblogs.com/alantu2018/p/8459250.html
46、C++中有幾種類型的new
在C++中,new有三種典型的使用方法:plain new,nothrow new和placement new
(1)plain new
言下之意就是普通的new,就是我們常用的new,在C++中定義如下:
void* operator new(std::size_t) throw(std::bad_alloc); void operator delete(void *) throw();
因此plain new在空間分配失敗的情況下,拋出異常std::bad_alloc而不是返回NULL,因此通過判斷返回值是否為NULL是徒勞的,舉個例子:
#include 《iostream》 #include 《string》 using namespace std; int main() { try { char *p = new char[10e11]; delete p; } catch (const std::bad_alloc &ex) { cout 《《 ex.what() 《《 endl; } return 0; } //執(zhí)行結(jié)果:bad allocation
(2)nothrow new
nothrow new在空間分配失敗的情況下是不拋出異常,而是返回NULL,定義如下:
void * operator new(std::size_t,const std::nothrow_t&) throw(); void operator delete(void*) throw();
舉個例子:
#include 《iostream》 #include 《string》 using namespace std; int main() { char *p = new(nothrow) char[10e11]; if (p == NULL) { cout 《《 “alloc failed” 《《 endl; } delete p; return 0; } //運(yùn)行結(jié)果:alloc failed
(3)placement new
這種new允許在一塊已經(jīng)分配成功的內(nèi)存上重新構(gòu)造對象或?qū)ο髷?shù)組。placement new不用擔(dān)心內(nèi)存分配失敗,因?yàn)樗静环峙鋬?nèi)存,它做的唯一一件事情就是調(diào)用對象的構(gòu)造函數(shù)。定義如下:
void* operator new(size_t,void*); void operator delete(void*,void*);
使用placement new需要注意兩點(diǎn):
palcement new的主要用途就是反復(fù)使用一塊較大的動態(tài)分配的內(nèi)存來構(gòu)造不同類型的對象或者他們的數(shù)組
placement new構(gòu)造起來的對象數(shù)組,要顯式的調(diào)用他們的析構(gòu)函數(shù)來銷毀(析構(gòu)函數(shù)并不釋放對象的內(nèi)存),千萬不要使用delete,這是因?yàn)閜lacement new構(gòu)造起來的對象或數(shù)組大小并不一定等于原來分配的內(nèi)存大小,使用delete會造成內(nèi)存泄漏或者之后釋放內(nèi)存時出現(xiàn)運(yùn)行時錯誤。
舉個例子:
#include 《iostream》 #include 《string》 using namespace std; class ADT{ int i; int j; public: ADT(){ i = 10; j = 100; cout 《《 “ADT construct i=” 《《 i 《《 “j=”《《j 《《endl; } ~ADT(){ cout 《《 “ADT destruct” 《《 endl; } }; int main() { char *p = new(nothrow) char[sizeof ADT + 1]; if (p == NULL) { cout 《《 “alloc failed” 《《 endl; } ADT *q = new(p) ADT; //placement new:不必?fù)?dān)心失敗,只要p所指對象的的空間足夠ADT創(chuàng)建即可 //delete q;//錯誤!不能在此處調(diào)用delete q; q-》ADT::~ADT();//顯示調(diào)用析構(gòu)函數(shù) delete[] p; return 0; } //輸出結(jié)果: //ADT construct i=10j=100 //ADT destruct
47、C++中NULL和nullptr區(qū)別
算是為了與C語言進(jìn)行兼容而定義的一個問題吧
NULL來自C語言,一般由宏定義實(shí)現(xiàn),而 nullptr 則是C++11的新增關(guān)鍵字。在C語言中,NULL被定義為(void*)0,而在C++語言中,NULL則被定義為整數(shù)0。編譯器一般對其實(shí)際定義如下:
#ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif
在C++中指針必須有明確的類型定義。但是將NULL定義為0帶來的另一個問題是無法與整數(shù)的0區(qū)分。因?yàn)镃++中允許有函數(shù)重載,所以可以試想如下函數(shù)定義情況:
#include 《iostream》 using namespace std; void fun(char* p) { cout 《《 “char*” 《《 endl; } void fun(int p) { cout 《《 “int” 《《 endl; } int main() { fun(NULL); return 0; } //輸出結(jié)果:int
那么在傳入NULL參數(shù)時,會把NULL當(dāng)做整數(shù)0來看,如果我們想調(diào)用參數(shù)是指針的函數(shù),該怎么辦呢?。nullptr在C++11被引入用于解決這一問題,nullptr可以明確區(qū)分整型和指針類型,能夠根據(jù)環(huán)境自動轉(zhuǎn)換成相應(yīng)的指針類型,但不會被轉(zhuǎn)換為任何整型,所以不會造成參數(shù)傳遞錯誤。
nullptr的一種實(shí)現(xiàn)方式如下:
const class nullptr_t{ public: template《class T》 inline operator T*() const{ return 0; } template《class C, class T》 inline operator T C::*() const { return 0; } private: void operator&() const; } nullptr = {};
以上通過模板類和運(yùn)算符重載的方式來對不同類型的指針進(jìn)行實(shí)例化從而解決了(void*)指針帶來參數(shù)類型不明的問題,另外由于nullptr是明確的指針類型,所以不會與整形變量相混淆。但nullptr仍然存在一定問題,例如:
#include 《iostream》 using namespace std; void fun(char* p) { cout《《 “char* p” 《《endl; } void fun(int* p) { cout《《 “int* p” 《《endl; } void fun(int p) { cout《《 “int p” 《《endl; } int main() { fun((char*)nullptr);//語句1 fun(nullptr);//語句2 fun(NULL);//語句3 return 0; } //運(yùn)行結(jié)果: //語句1:char* p //語句2:報錯,有多個匹配 //3:int p
在這種情況下存在對不同指針類型的函數(shù)重載,此時如果傳入nullptr指針則仍然存在無法區(qū)分應(yīng)實(shí)際調(diào)用哪個函數(shù),這種情況下必須顯示的指明參數(shù)類型。
48、簡要說明C++的內(nèi)存分區(qū)
C++中的內(nèi)存分區(qū),分別是堆、棧、自由存儲區(qū)、全局/靜態(tài)存儲區(qū)、常量存儲區(qū)和代碼區(qū)。如下圖所示
棧:在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限
堆:就是那些由 new分配的內(nèi)存塊,他們的釋放編譯器不去管,由我們的應(yīng)用程序去控制,一般一個new就要對應(yīng)一個 delete。如果程序員沒有釋放掉,那么在程序結(jié)束后,操作系統(tǒng)會自動回收
自由存儲區(qū):就是那些由malloc等分配的內(nèi)存塊,它和堆是十分相似的,不過它是用free來結(jié)束自己的生命的
全局/靜態(tài)存儲區(qū):全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中,在以前的C語言中,全局變量和靜態(tài)變量又分為初始化的和未初始化的,在C++里面沒有這個區(qū)分了,它們共同占用同一塊內(nèi)存區(qū),在該區(qū)定義的變量若沒有初始化,則會被自動初始化,例如int型變量自動初始為0
常量存儲區(qū):這是一塊比較特殊的存儲區(qū),這里面存放的是常量,不允許修改
代碼區(qū):存放函數(shù)體的二進(jìn)制代碼
49、C++的異常處理的方法
在程序執(zhí)行過程中,由于程序員的疏忽或是系統(tǒng)資源緊張等因素都有可能導(dǎo)致異常,任何程序都無法保證絕對的穩(wěn)定,常見的異常有:
數(shù)組下標(biāo)越界
除法計算時除數(shù)為0
動態(tài)分配空間時空間不足
…
如果不及時對這些異常進(jìn)行處理,程序多數(shù)情況下都會崩潰。
(1)try、throw和catch關(guān)鍵字
C++中的異常處理機(jī)制主要使用try、throw和catch三個關(guān)鍵字,其在程序中的用法如下:
#include 《iostream》 using namespace std; int main() { double m = 1, n = 0; try { cout 《《 “before dividing.” 《《 endl; if (n == 0) throw - 1; //拋出int型異常 else if (m == 0) throw - 1.0; //拋出 double 型異常 else cout 《《 m / n 《《 endl; cout 《《 “after dividing.” 《《 endl; } catch (double d) { cout 《《 “catch (double)” 《《 d 《《 endl; } catch (。..) { cout 《《 “catch (。..)” 《《 endl; } cout 《《 “finished” 《《 endl; return 0; } //運(yùn)行結(jié)果 //before dividing. //catch (。..) //finished
代碼中,對兩個數(shù)進(jìn)行除法計算,其中除數(shù)為0。可以看到以上三個關(guān)鍵字,程序的執(zhí)行流程是先執(zhí)行try包裹的語句塊,如果執(zhí)行過程中沒有異常發(fā)生,則不會進(jìn)入任何catch包裹的語句塊。如果發(fā)生異常,則使用throw進(jìn)行異常拋出,再由catch進(jìn)行捕獲,throw可以拋出各種數(shù)據(jù)類型的信息,代碼中使用的是數(shù)字,也可以自定義異常class。
catch根據(jù)throw拋出的數(shù)據(jù)類型進(jìn)行精確捕獲(不會出現(xiàn)類型轉(zhuǎn)換),如果匹配不到就直接報錯,可以使用catch(…)的方式捕獲任何異常(不推薦)。
當(dāng)然,如果catch了異常,當(dāng)前函數(shù)如果不進(jìn)行處理,或者已經(jīng)處理了想通知上一層的調(diào)用者,可以在catch里面再throw異常。
(2)函數(shù)的異常聲明列表
有時候,程序員在定義函數(shù)的時候知道函數(shù)可能發(fā)生的異常,可以在函數(shù)聲明和定義時,指出所能拋出異常的列表,寫法如下:
int fun() throw(int,double,A,B,C){。..};
這種寫法表名函數(shù)可能會拋出int,double型或者A、B、C三種類型的異常,如果throw中為空,表明不會拋出任何異常,如果沒有throw則可能拋出任何異常
(3)C++標(biāo)準(zhǔn)異常類 exception
C++ 標(biāo)準(zhǔn)庫中有一些類代表異常,這些類都是從 exception 類派生而來的,如下圖所示
bad_typeid:使用typeid運(yùn)算符,如果其操作數(shù)是一個多態(tài)類的指針,而該指針的值為 NULL,則會拋出此異常,例如:
#include 《iostream》 #include 《typeinfo》 using namespace std; class A{ public: virtual ~A(); }; using namespace std; int main() { A* a = NULL; try { cout 《《 typeid(*a).name() 《《 endl; // Error condition } catch (bad_typeid){ cout 《《 “Object is NULL” 《《 endl; } return 0; } //運(yùn)行結(jié)果:bject is NULL
bad_cast:在用 dynamic_cast 進(jìn)行從多態(tài)基類對象(或引用)到派生類的引用的強(qiáng)制類型轉(zhuǎn)換時,如果轉(zhuǎn)換是不安全的,則會拋出此異常
bad_alloc:在用 new 運(yùn)算符進(jìn)行動態(tài)內(nèi)存分配時,如果沒有足夠的內(nèi)存,則會引發(fā)此異常
out_of_range:用 vector 或 string的at 成員函數(shù)根據(jù)下標(biāo)訪問元素時,如果下標(biāo)越界,則會拋出此異常
原文標(biāo)題:《逆襲進(jìn)大廠》之 C++ 篇 49 問 49 答(絕對的干貨)
文章出處:【微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
責(zé)任編輯:haq
-
程序
+關(guān)注
關(guān)注
117文章
3826瀏覽量
82968 -
C++
+關(guān)注
關(guān)注
22文章
2119瀏覽量
75294
原文標(biāo)題:《逆襲進(jìn)大廠》之 C++ 篇 49 問 49 答(絕對的干貨)
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
主流的 MCU 開發(fā)語言為什么是 C 而不是 C++?

C++學(xué)到什么程度可以找工作?
基于OpenHarmony標(biāo)準(zhǔn)系統(tǒng)的C++公共基礎(chǔ)類庫案例:ThreadPoll

Spire.XLS for C++組件說明

EE-112:模擬C++中的類實(shí)現(xiàn)

同樣是函數(shù),在C和C++中有什么區(qū)別
C++新手容易犯的十個編程錯誤
C7000 C/C++優(yōu)化指南用戶手冊

TMS320C6000優(yōu)化C/C++編譯器v8.3.x

C7000優(yōu)化C/C++編譯器

ostream在c++中的用法
OpenVINO2024 C++推理使用技巧
C++中實(shí)現(xiàn)類似instanceof的方法

評論