99精品伊人亚洲|最近国产中文炮友|九草在线视频支援|AV网站大全最新|美女黄片免费观看|国产精品资源视频|精彩无码视频一区|91大神在线后入|伊人终合在线播放|久草综合久久中文

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

關(guān)于C++的49個常用知識

Linux愛好者 ? 來源:拓跋阿秀 ? 作者:拓跋阿秀 ? 2021-03-11 16:42 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

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ū)別

指針是一個變量,存儲的是一個地址,引用跟原來的變量實質(zhì)上是同一個東西,是原變量的別名

指針可以有多級,引用只有一級

指針可以為空,引用不能為NULL且在定義時必須初始化

指針在初始化后可以改變指向,而引用在初始化之后不可再改變

sizeof指針得到的是本指針的大小,sizeof引用得到的是引用所指向變量的大小

當(dāng)把指針作為參數(shù)進(jìn)行傳遞時,也是將實參的一個拷貝傳遞給形參,兩者指向的地址相同,但不是同一個變量,在函數(shù)中改變這個變量的指向不影響實參,而引用卻可以。

引用只是別名,不占用具體存儲空間,只有聲明沒有定義;指針是具體變量,需要占用存儲空間。

引用在聲明時必須初始化為另一變量,一旦出現(xiàn)必須為typename refname &varname形式;指針聲明和定義可以分開,可以先只聲明指針變量而不初始化,等用到時再指向具體變量。

引用一旦初始化之后就不可以再改變(變量可以被引用為多次,但引用只能作為一個變量引用);指針變量可以重新指向別的變量。

不存在指向空值的引用,必須有具體實體;但是存在指向空值的指針。

參考代碼:

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)ワ堭^里吃飯,只管點菜(發(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)行實例化時,在構(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的異同

相同點

都可用于內(nèi)存的動態(tài)申請和釋放

不同點

前者是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是如何實現(xiàn)的?

new的實現(xiàn)過程是:首先調(diào)用名為operator new的標(biāo)準(zhǔn)庫函數(shù),分配足夠大的原始為類型化的內(nèi)存,以保存指定類型的一個對象;接下來運(yùn)行該類型的一個構(gòu)造函數(shù),用指定初始化構(gòu)造對象;最后返回指向新分配并構(gòu)造后的的對象的指針

delete的實現(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é)尾是‘’的字符串。

因為sizeof值在編譯時確定,所以不能用來得到動態(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]的值,因為被轉(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語言也是做不到這一點的

在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++中利用指針實現(xiàn)的“偽數(shù)組”,Java引入了真正的數(shù)組,同時將容易造成麻煩的指針從語言中去掉,這將有利于防止在C++程序中常見的因為數(shù)組操作越界等指針操作而對系統(tǒng)數(shù)據(jù)進(jìn)行非法讀寫帶來的不安全問題

C++也可以在其他系統(tǒng)運(yùn)行,但是需要不同的編碼(這一點不如Java,只編寫一次代碼,到處運(yùn)行),例如對一個數(shù)字,在windows下是大端存儲,在unix中則為小端存儲。Java程序一般都是生成字節(jié)碼,在JVM里面運(yùn)行得到結(jié)果

Java用接口(Interface)技術(shù)取代C++程序中的多繼承性。接口與多繼承有同樣的功能,但是省卻了多繼承在實現(xiàn)和維護(hù)上的復(fù)雜性

垃圾回收

C++用析構(gòu)函數(shù)回收垃圾,寫C和C++程序時一定要注意內(nèi)存的申請和釋放

Java語言不使用指針,內(nèi)存的分配和回收都是自動進(jìn)行的,程序員無須考慮內(nèi)存碎片的問題

應(yīng)用場景

Java在桌面程序上不如C++實用,C++可以直接編譯成exe文件,指針是c++的優(yōu)勢,可以直接對內(nèi)存的操作,但同時具有危險性 。(操作內(nèi)存的確是一項非常危險的事情,一旦指針指向的位置發(fā)生錯誤,或者誤刪除了內(nèi)存中某個地址單元存放的重要數(shù)據(jù),后果是可想而知的)

Java在Web 應(yīng)用上具有C++ 無可比擬的優(yōu)勢,具有豐富多樣的框架

對于底層程序的編程以及控制方面的編程,C++很靈活,因為有句柄的存在

《C++和java的區(qū)別和聯(lián)系》:

https://www.cnblogs.com/tanrong/p/8503202.html

22、C++中struct和class的區(qū)別

相同點

兩者都擁有成員函數(shù)、公有和私有部分

任何可以使用class完成的工作,同樣可以使用struct完成

不同點

兩者中如果不對成員不指定公私有,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能繼承,能實現(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類型的實參,例如

// 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é)果是編譯器并不會報錯,因為它并不知道你的目的是重寫虛函數(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)用與實參匹配的構(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)多種類型的需求,使每種類型都具有相同的功能,但對于某種特定類型,如果要實現(xiàn)其特有的功能,單一模板就無法做到,這時就需要模板特例化

定義

對單一模板提供的一個特殊實例,它將一個或多個模板參數(shù)綁定到特定的類型或值上

(1)模板函數(shù)特例化

必須為原函數(shù)模板的每個模板參數(shù)都提供實參,且使用關(guān)鍵字template后跟一個空尖括號對《》,表明將原模板的所有模板參數(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; } //模板特例化,滿足針對字符串特定的比較,要提供所有實參,這里只有一個T template《》 int compare(const char* const &v1,const char* const &v2) { return strcmp(p1,p2); }

本質(zhì)

特例化的本質(zhì)是實例化一個模板,而非重載它。特例化不影響參數(shù)匹配。參數(shù)匹配都以最佳匹配為原則。例如,此處如果是compare(3,5),則調(diào)用普通的模板,若為compare(“hi”,”haha”)則調(diào)用特例化版本(因為這個cosnt char*相對于T,更匹配實參類型),注意二者函數(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ù)類型不同,按最佳匹配原則,哪個最匹配,就用相應(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)安全的程序,相反的,差一點兒的程序員可能使用類型相當(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)換過程更加安全,因為dynamic_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; }

上面兩個例子之所以引起類型不安全的問題,是因為程序員使用不得當(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ù),則編譯器實施靜態(tài)綁定,在刪除基類指針時,只會調(diào)用基類的析構(gòu)函數(shù)而不調(diào)用派生類析構(gòu)函數(shù),這樣就會造成派生類對象析構(gòu)不完全,造成內(nèi)存泄漏。所以將析構(gòu)函數(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ù)的類是抽象類,此時不能被實例化。但派生類中可以根據(jù)自身需求重新改寫基類中的純虛函數(shù)。

構(gòu)造函數(shù):

構(gòu)造函數(shù)不能定義為虛函數(shù)。在構(gòu)造函數(shù)中可以調(diào)用虛函數(shù),不過此時調(diào)用的是正在構(gòu)造的類中的虛函數(shù),而不是子類的虛函數(shù),因為此時子類尚未構(gòu)造好。

35、C++中的重載、重寫(覆蓋)和隱藏的區(qū)別

(1)重載(overload)

重載是指在同一范圍定義中的同名成員函數(shù)才存在重載關(guā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)用時實參表與形參表的對應(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)如何實現(xiàn)

C++的多態(tài)性,一言以蔽之就是:

在基類的函數(shù)前加上virtual關(guān)鍵字,在派生類中重寫該函數(shù),運(yùn)行時將會根據(jù)所指對象的實際類型來調(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ù)的類實例化對象時,對象地址的前四個字節(jié)存儲的指向虛表的指針

上圖中展示了虛表和虛表指針在基類對象和派生類對象中的模型,下面闡述實現(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)用,從而實現(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)存了也不會影響到深拷貝得到的值。在自己實現(xiàn)拷貝賦值的時候,如果有指針變量的話是需要自己實現(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)行的速度,因為不需要中斷調(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ù)成員或私有成員時,宏就不能實現(xiàn)了。

40、構(gòu)造函數(shù)、析構(gòu)函數(shù)、虛函數(shù)可否聲明為內(nèi)聯(lián)函數(shù)

首先,將這些函數(shù)聲明為內(nèi)聯(lián)函數(shù),在語法上沒有錯誤。因為inline同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)操作,因為編譯器會在構(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)呢?

上述觀點看似正確,其實不然,如果虛函數(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)行實際的計算表達(dá)式的值。

int func() {return 0}; //普通類型 decltype(func()) sum = 5; // sum的類型是函數(shù)func()的返回值的類型int, 但是這時不會實際調(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)限如下表所示:注意外部訪問并不是真正的外部訪問,而是在通過派生類的對象對基類成員的訪問。

e06c593a-7e2c-11eb-8b86-12bb97331649.png

派生類對基類成員的訪問形象有如下兩種:

內(nèi)部訪問:由派生類中新增的成員函數(shù)對從基類繼承來的成員的訪問

外部訪問:在派生類外部,通過派生類的對象對從基類繼承來的成員的訪問

(二)繼承權(quán)限

public繼承

公有繼承的特點是基類的公有成員和保護(hù)成員作為派生類的成員時,都保持原有的狀態(tài),而基類的私有成員任然是私有的,不能被這個派生類的子類所訪問

protected繼承

保護(hù)繼承的特點是基類的所有公有成員和保護(hù)成員都成為派生類的保護(hù)成員,并且只能被它的派生類成員函數(shù)或友元函數(shù)訪問,基類的私有成員仍然是私有的。

private繼承

私有繼承的特點是基類的所有公有成員和保護(hù)成員都成為派生類的私有成員,并不被它的派生類的子類所訪問,基類的成員只能由自己派生類訪問,無法再往下繼承,訪問規(guī)則如下表

e0c4b576-7e2c-11eb-8b86-12bb97331649.png

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ò)傳輸

小端模式中的存儲方式為:

e1246872-7e2c-11eb-8b86-12bb97331649.png

大端模式中的存儲方式為:

e1564626-7e2c-11eb-8b86-12bb97331649.png

了解了大小端存儲的方式,如何在代碼中進(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)識符的類只能訪問它接口的子集,一個由類的實現(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)換,注意以下幾點:

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ù)體內(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)行下述實驗:

舉個例子:

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的實現(xiàn)過程是,調(diào)用函數(shù)時先根據(jù)傳入的實參產(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)存分配失敗,因為它根本不分配內(nèi)存,它做的唯一一件事情就是調(diào)用對象的構(gòu)造函數(shù)。定義如下:

void* operator new(size_t,void*); void operator delete(void*,void*);

使用placement new需要注意兩點:

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,這是因為placement 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語言,一般由宏定義實現(xiàn),而 nullptr 則是C++11的新增關(guān)鍵字。在C語言中,NULL被定義為(void*)0,而在C++語言中,NULL則被定義為整數(shù)0。編譯器一般對其實際定義如下:

#ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif

在C++中指針必須有明確的類型定義。但是將NULL定義為0帶來的另一個問題是無法與整數(shù)的0區(qū)分。因為C++中允許有函數(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的一種實現(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)行實例化從而解決了(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)實際調(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 類派生而來的,如下圖所示

e1d09764-7e2c-11eb-8b86-12bb97331649.jpg

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

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 程序
    +關(guān)注

    關(guān)注

    117

    文章

    3826

    瀏覽量

    82961
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2119

    瀏覽量

    75268

原文標(biāo)題:《逆襲進(jìn)大廠》之 C++ 篇 49 問 49 答(絕對的干貨)

文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    主流的 MCU 開發(fā)語言為什么是 C 而不是 C++?

    在單片機(jī)的地界兒里,C語言穩(wěn)坐中軍帳,C++想分杯羹?難嘍。咱電子工程師天天跟那針尖大的內(nèi)存空間較勁,C++那些花里胡哨的玩意兒,在這兒真玩不轉(zhuǎn)。先說內(nèi)存這道坎兒。您當(dāng)stm32f4的256kRAM
    的頭像 發(fā)表于 05-21 10:33 ?436次閱讀
    主流的 MCU 開發(fā)語言為什么是 <b class='flag-5'>C</b> 而不是 <b class='flag-5'>C++</b>?

    C++學(xué)到什么程度可以找工作?

    C++開發(fā)的工作不僅需要深厚的編程功底,還要具備解決實際問題的能力,以及良好的溝通能力和團(tuán)隊協(xié)作精神。此外,持續(xù)學(xué)習(xí)和更新自己的知識體系也是保持競爭力的關(guān)鍵。
    發(fā)表于 03-13 10:19

    基于OpenHarmony標(biāo)準(zhǔn)系統(tǒng)的C++公共基礎(chǔ)類庫案例:ThreadPoll

    。每個線程每秒打印1段字符串,10秒后停止。2、基礎(chǔ)知識C++公共基礎(chǔ)類庫為標(biāo)準(zhǔn)系統(tǒng)提供了一些常用C++開發(fā)工具類,包括:文件、路徑、字符串相關(guān)操作的能力增強(qiáng)接口
    的頭像 發(fā)表于 02-10 18:09 ?360次閱讀
    基于OpenHarmony標(biāo)準(zhǔn)系統(tǒng)的<b class='flag-5'>C++</b>公共基礎(chǔ)類庫案例:ThreadPoll

    Spire.XLS for C++組件說明

    Spire.XLS for C++ 是一款專業(yè)的 C++ Excel 組件,可以用在各種 C++ 框架和應(yīng)用程序中。Spire.XLS for C++ 提供了一
    的頭像 發(fā)表于 01-14 09:40 ?617次閱讀
    Spire.XLS for <b class='flag-5'>C++</b>組件說明

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

    電子發(fā)燒友網(wǎng)站提供《EE-112:模擬C++中的類實現(xiàn).pdf》資料免費(fèi)下載
    發(fā)表于 01-03 15:15 ?0次下載
    EE-112:模擬<b class='flag-5'>C++</b>中的類實現(xiàn)

    同樣是函數(shù),在CC++中有什么區(qū)別

    同樣是函數(shù),在 CC++ 中有什么區(qū)別? 第一返回值。 C語言的函數(shù)可以不寫返回值類型,編譯器會默認(rèn)為返回 int。 但是 C++
    的頭像 發(fā)表于 11-29 10:25 ?906次閱讀

    C++新手容易犯的十編程錯誤

    簡單的總結(jié)一下?C++ 新手容易犯的一些編程錯誤,給新人們提供一參考。 1 有些關(guān)鍵字在 cpp 文件中多寫了 對于 C++ 類,一些關(guān)鍵字只要寫在 .h 中就好,cpp 中就不用再加上了,比如
    的頭像 發(fā)表于 11-15 12:42 ?1021次閱讀

    C7000 C/C++優(yōu)化指南用戶手冊

    電子發(fā)燒友網(wǎng)站提供《C7000 C/C++優(yōu)化指南用戶手冊.pdf》資料免費(fèi)下載
    發(fā)表于 11-09 15:00 ?0次下載
    <b class='flag-5'>C</b>7000 <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>優(yōu)化指南用戶手冊

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

    電子發(fā)燒友網(wǎng)站提供《TMS320C6000優(yōu)化C/C++編譯器v8.3.x.pdf》資料免費(fèi)下載
    發(fā)表于 11-01 09:35 ?1次下載
    TMS320<b class='flag-5'>C</b>6000優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編譯器v8.3.x

    C語言和C++中結(jié)構(gòu)體的區(qū)別

    同樣是結(jié)構(gòu)體,看看在C語言和C++中有什么區(qū)別?
    的頭像 發(fā)表于 10-30 15:11 ?765次閱讀

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

    電子發(fā)燒友網(wǎng)站提供《C7000優(yōu)化C/C++編譯器.pdf》資料免費(fèi)下載
    發(fā)表于 10-30 09:45 ?0次下載
    <b class='flag-5'>C</b>7000優(yōu)化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編譯器

    ostream在c++中的用法

    ostream 是 C++ 標(biāo)準(zhǔn)庫中一非常重要的類,它位于 頭文件中(實際上,更常見的是通過包含 頭文件來間接包含 ,因為 包含了 和 )。 ostream 類及其派生類(如 std::cout
    的頭像 發(fā)表于 09-20 15:11 ?1932次閱讀

    OpenVINO2024 C++推理使用技巧

    很多人都使用OpenVINO新版的C++ 或者Python的SDK,都覺得非常好用,OpenVINO2022之后的版本C++ SDK做了大量的優(yōu)化與整理,已經(jīng)是非常貼近開發(fā)的使用習(xí)慣與推理方式。與OpenCV的Mat對象對接方式更是幾乎無縫對接,非常的方便好用。
    的頭像 發(fā)表于 07-26 09:20 ?1563次閱讀

    C++語言基礎(chǔ)知識

    電子發(fā)燒友網(wǎng)站提供《C++語言基礎(chǔ)知識.pdf》資料免費(fèi)下載
    發(fā)表于 07-19 10:58 ?10次下載

    C++中實現(xiàn)類似instanceof的方法

    C++有多態(tài)與繼承,但是很多人開始學(xué)習(xí)C++,有時候會面臨一常見問題,就是如何向下轉(zhuǎn)型,特別是不知道具體類型的時候,這個時候就希望C++ 可以向Java或者Python中有insta
    的頭像 發(fā)表于 07-18 10:16 ?936次閱讀
    <b class='flag-5'>C++</b>中實現(xiàn)類似instanceof的方法