日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > > C語言與CPP編程
[導(dǎo)讀]《逆襲進大廠》系列第二彈 C++ 進階篇直接發(fā)車了。


是本期 C++ 八股文問題目錄。

不逼逼了,《逆襲進大廠系列第二彈 C++ 進階篇直接發(fā)車了。

這篇總字數(shù)是 37814 個字,嗯,將近 4W 字。

50、static的用法和作用?

1.先來介紹它的第一條也是最重要的一條:隱藏。(static函數(shù),static變量均可)

當同時編譯多個文件時,所有未加static前綴的全局變量和函數(shù)都具有全局可見性。

2.static的第二個作用是保持變量內(nèi)容的持久。(static變量中的記憶功能和全局生存期)存儲在靜態(tài)數(shù)據(jù)區(qū)的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態(tài)存儲區(qū):全局變量和static變量,只不過和全局變量比起來,static可以控制變量的可見范圍,說到底static還是用來隱藏的。

3.static的第三個作用是默認初始化為0(static變量)

其實全局變量也具備這一屬性,因為全局變量也存儲在靜態(tài)數(shù)據(jù)區(qū)。在靜態(tài)數(shù)據(jù)區(qū),內(nèi)存中所有的字節(jié)默認值都是0x00,某些時候這一特點可以減少程序員的工作量。

4.static的第四個作用:C++中的類成員聲明static

1)  函數(shù)體內(nèi)static變量的作用范圍為該函數(shù)體,不同于auto變量,該變量的內(nèi)存只被分配一次,因此其值在下次調(diào)用時仍維持上次的值;

2)  在模塊內(nèi)的static全局變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問;

3)  在模塊內(nèi)的static函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用,這個函數(shù)的使用范圍被限制在聲明它的模塊內(nèi);

4)  在類中的static成員變量屬于整個類所擁有,對類的所有對象只有一份拷貝;

5)  在類中的static成員函數(shù)屬于整個類所擁有,這個函數(shù)不接收this指針,因而只能訪問類的static成員變量。

類內(nèi):

6)  static類對象必須要在類外進行初始化,static修飾的變量先于對象存在,所以static修飾的變量要在類外初始化;

7)  由于static修飾的類成員屬于類,不屬于對象,因此static類成員函數(shù)是沒有this指針的,this指針是指向本對象的指針。正因為沒有this指針,所以static類成員函數(shù)不能訪問非static的類成員,只能訪問 static修飾的類成員;

8)  static成員函數(shù)不能被virtual修飾,static成員不屬于任何對象或?qū)嵗约由蟰irtual沒有任何實際意義;靜態(tài)成員函數(shù)沒有this指針,虛函數(shù)的實現(xiàn)是為每一個對象分配一個vptr指針,而vptr是通過this指針調(diào)用的,所以不能為virtual;虛函數(shù)的調(diào)用關(guān)系,this->vptr->ctable->virtual function

51、靜態(tài)變量什么時候初始化

1)  初始化只有一次,但是可以多次賦值,在主程序之前,編譯器已經(jīng)為其分配好了內(nèi)存。

2)  靜態(tài)局部變量和全局變量一樣,數(shù)據(jù)都存放在全局區(qū)域,所以在主程序之前,編譯器已經(jīng)為其分配好了內(nèi)存,但在C和C++中靜態(tài)局部變量的初始化節(jié)點又有點不太一樣。在C中,初始化發(fā)生在代碼執(zhí)行之前,編譯階段分配好內(nèi)存之后,就會進行初始化,所以我們看到在C語言中無法使用變量對靜態(tài)局部變量進行初始化,在程序運行結(jié)束,變量所處的全局內(nèi)存會被全部回收。

3)  而在C++中,初始化時在執(zhí)行相關(guān)代碼時才會進行初始化,主要是由于C++引入對象后,要進行初始化必須執(zhí)行相應(yīng)構(gòu)造函數(shù)和析構(gòu)函數(shù),在構(gòu)造函數(shù)或析構(gòu)函數(shù)中經(jīng)常會需要進行某些程序中需要進行的特定操作,并非簡單地分配內(nèi)存。所以C++標準定為全局或靜態(tài)對象是有首次用到時才會進行構(gòu)造,并通過atexit()來管理。在程序結(jié)束,按照構(gòu)造順序反方向進行逐個析構(gòu)。所以在C++中是可以使用變量對靜態(tài)局部變量進行初始化的。

52、const關(guān)鍵字?

1)  阻止一個變量被改變,可以使用const關(guān)鍵字。在定義該const變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了;

2)  對指針來說,可以指定指針本身為const,也可以指定指針所指的數(shù)據(jù)為const,或二者同時指定為const;

3)  在一個函數(shù)聲明中,const可以修飾形參,表明它是一個輸入?yún)?shù),在函數(shù)內(nèi)部不能改變其值;

4)  對于類的成員函數(shù),若指定其為const類型,則表明其是一個常函數(shù),不能修改類的成員變量,類的常對象只能訪問類的常成員函數(shù);

5)  對于類的成員函數(shù),有時候必須指定其返回值為const類型,以使得其返回值不為“左值”。

6)  const成員函數(shù)可以訪問非const對象的非const數(shù)據(jù)成員、const數(shù)據(jù)成員,也可以訪問const對象內(nèi)的所有數(shù)據(jù)成員;

7)  非const成員函數(shù)可以訪問非const對象的非const數(shù)據(jù)成員、const數(shù)據(jù)成員,但不可以訪問const對象的任意數(shù)據(jù)成員;

8)  一個沒有明確聲明為const的成員函數(shù)被看作是將要修改對象中數(shù)據(jù)成員的函數(shù),而且編譯器不允許它為一個const對象所調(diào)用。因此const對象只能調(diào)用const成員函數(shù)。

9)  const類型變量可以通過類型轉(zhuǎn)換符const_cast將const類型轉(zhuǎn)換為非const類型;

10) const類型變量必須定義的時候進行初始化,因此也導(dǎo)致如果類的成員變量有const類型的變量,那么該變量必須在類的初始化列表中進行初始化;

11) 對于函數(shù)值傳遞的情況,因為參數(shù)傳遞是通過復(fù)制實參創(chuàng)建一個臨時變量傳遞進函數(shù)的,函數(shù)內(nèi)只能改變臨時變量,但無法改變實參。則這個時候無論加不加const對實參不會產(chǎn)生任何影響。但是在引用或指針傳遞函數(shù)調(diào)用中,因為傳進去的是一個引用或指針,這樣函數(shù)內(nèi)部可以改變引用或指針所指向的變量,這時const 才是實實在在地保護了實參所指向的變量。因為在編譯階段編譯器對調(diào)用函數(shù)的選擇是根據(jù)實參進行的,所以,只有引用傳遞和指針傳遞可以用是否加const來重載。一個擁有頂層const的形參無法和另一個沒有頂層const的形參區(qū)分開來。

53、指針和const的用法

1)  當const修飾指針時,由于const的位置不同,它的修飾對象會有所不同。

2)  int *const p2中const修飾p2的值,所以理解為p2的值不可以改變,即p2只能指向固定的一個變量地址,但可以通過*p2讀寫這個變量的值。頂層指針表示指針本身是一個常量

3)  int const *p1或者const int *p1兩種情況中const修飾*p1,所以理解為*p1的值不可以改變,即不可以給*p1賦值改變p1指向變量的值,但可以通過給p賦值不同的地址改變這個指針指向。

底層指針表示指針所指向的變量是一個常量。

54、形參與實參的區(qū)別?

1)  形參變量只有在被調(diào)用時才分配內(nèi)存單元,在調(diào)用結(jié)束時, 即刻釋放所分配的內(nèi)存單元。因此,形參只有在函數(shù)內(nèi)部有效。函數(shù)調(diào)用結(jié)束返回主調(diào)函數(shù)后則不能再使用該形參變量。

2)  實參可以是常量、變量、表達式、函數(shù)等, 無論實參是何種類型的量,在進行函數(shù)調(diào)用時,它們都必須具有確定的值, 以便把這些值傳送給形參。因此應(yīng)預(yù)先用賦值,輸入等辦法使實參獲得確定值,會產(chǎn)生一個臨時變量。

3)  實參和形參在數(shù)量上,類型上,順序上應(yīng)嚴格一致, 否則會發(fā)生“類型不匹配”的錯誤。

4)  函數(shù)調(diào)用中發(fā)生的數(shù)據(jù)傳送是單向的。即只能把實參的值傳送給形參,而不能把形參的值反向地傳送給實參。因此在函數(shù)調(diào)用過程中,形參的值發(fā)生改變,而實參中的值不會變化。

5)  當形參和實參不是指針類型時,在該函數(shù)運行時,形參和實參是不同的變量,他們在內(nèi)存中位于不同的位置,形參將實參的內(nèi)容復(fù)制一份,在該函數(shù)運行結(jié)束的時候形參被釋放,而實參內(nèi)容不會改變。

55、值傳遞、指針傳遞、引用傳遞的區(qū)別和效率

1)   值傳遞:有一個形參向函數(shù)所屬的棧拷貝數(shù)據(jù)的過程,如果值傳遞的對象是類對象   或是大的結(jié)構(gòu)體對象,將耗費一定的時間和空間。(傳值)

2)  指針傳遞:同樣有一個形參向函數(shù)所屬的??截悢?shù)據(jù)的過程,但拷貝的數(shù)據(jù)是一個固定為4字節(jié)的地址。(傳值,傳遞的是地址值)

3)  引用傳遞:同樣有上述的數(shù)據(jù)拷貝過程,但其是針對地址的,相當于為該數(shù)據(jù)所在的地址起了一個別名。(傳地址)

4)  效率上講,指針傳遞和引用傳遞比值傳遞效率高。一般主張使用引用傳遞,代碼邏輯上更加緊湊、清晰。

56、什么是類的繼承?

1) 類與類之間的關(guān)系

has-A包含關(guān)系,用以描述一個類由多個部件類構(gòu)成,實現(xiàn)has-A關(guān)系用類的成員屬性表示,即一個類的成員屬性是另一個已經(jīng)定義好的類;

use-A,一個類使用另一個類,通過類之間的成員函數(shù)相互聯(lián)系,定義友元或者通過傳遞參數(shù)的方式來實現(xiàn);

is-A,繼承關(guān)系,關(guān)系具有傳遞性;

2) 繼承的相關(guān)概念

所謂的繼承就是一個類繼承了另一個類的屬性和方法,這個新的類包含了上一個類的屬性和方法,被稱為子類或者派生類,被繼承的類稱為父類或者基類;

3) 繼承的特點

子類擁有父類的所有屬性和方法,子類可以擁有父類沒有的屬性和方法,子類對象可以當做父類對象使用;

4) 繼承中的訪問控制

public、protected、private

5) 繼承中的構(gòu)造和析構(gòu)函數(shù)

6) 繼承中的兼容性原則

57、什么是內(nèi)存池,如何實現(xiàn)

https://www.bilibili.com/video/BV1Kb411B7N8?p=25 C++內(nèi)存管理:P23-26

https://www.bilibili.com/video/BV1db411q7B8?p=12 C++STL P11

內(nèi)存池(Memory Pool) 是一種內(nèi)存分配方式。通常我們習(xí)慣直接使用new、malloc 等申請內(nèi)存,這樣做的缺點在于:由于所申請內(nèi)存塊的大小不定,當頻繁使用時會造成大量的內(nèi)存碎片并進而降低性能。內(nèi)存池則是在真正使用內(nèi)存之前,先申請分配一定數(shù)量的、大小相等(一般情況下)的內(nèi)存塊留作備用。當有新的內(nèi)存需求時,就從內(nèi)存池中分出一部分內(nèi)存塊, 若內(nèi)存塊不夠再繼續(xù)申請新的內(nèi)存。這樣做的一個顯著優(yōu)點是盡量避免了內(nèi)存碎片,使得內(nèi)存分配效率得到提升。

這里簡單描述一下《STL源碼剖析》中的內(nèi)存池實現(xiàn)機制:

allocate包裝malloc,deallocate包裝free

一般是一次20*2個的申請,先用一半,留著一半,為什么也沒個說法,侯捷在STL那邊書里說好像是C++委員會成員認為20是個比較好的數(shù)字,既不大也不小

  1. 首先客戶端會調(diào)用malloc()配置一定數(shù)量的區(qū)塊(固定大小的內(nèi)存塊,通常為8的倍數(shù)),假設(shè)40個32bytes的區(qū)塊,其中20個區(qū)塊(一半)給程序?qū)嶋H使用,1個區(qū)塊交出,另外19個處于維護狀態(tài)。剩余20個(一半)留給內(nèi)存池,此時一共有(20*32byte)

  2. 客戶端之后有有內(nèi)存需求,想申請(20*64bytes)的空間,這時內(nèi)存池只有(20*32bytes),就先將(10*64bytes)個區(qū)塊返回,1個區(qū)塊交出,另外9個處于維護狀態(tài),此時內(nèi)存池空空如也

  3. 接下來如果客戶端還有內(nèi)存需求,就必須再調(diào)用malloc()配置空間,此時新申請的區(qū)塊數(shù)量會增加一個隨著配置次數(shù)越來越大的附加量,同樣一半提供程序使用,另一半留給內(nèi)存池。申請內(nèi)存的時候用永遠是先看內(nèi)存池有無剩余,有的話就用上,然后掛在0-15號某一條鏈表上,要不然就重新申請。

  4. 如果整個堆的空間都不夠了,就會在原先已經(jīng)分配區(qū)塊中尋找能滿足當前需求的區(qū)塊數(shù)量,能滿足就返回,不能滿足就向客戶端報bad_alloc異常

《STL源碼解析》侯捷 P68

allocator就是用來分配內(nèi)存的,最重要的兩個函數(shù)是allocate和deallocate,就是用來申請內(nèi)存和回收內(nèi)存的,外部(一般指容器)調(diào)用的時候只需要知道這些就夠了。內(nèi)部實現(xiàn),目前的所有編譯器都是直接調(diào)用的::operator new()和::operator delete(),說白了就是和直接使用new運算符的效果是一樣的,所以老師說它們都沒做任何特殊處理。

最開始GC2.9之前:

new和 operator new 的區(qū)別:new 是個運算符,編輯器會調(diào)用 operator new(0)

operator new()里面有調(diào)用malloc的操作,那同樣的 operator delete()里面有調(diào)用的free的操作

GC2.9的alloc的一個比較好的分配器的實現(xiàn)規(guī)則

維護一條0-15號的一共16條鏈表,其中0表示8 bytes ,1表示 16 bytes,2表示 24bytes。。。。而15 表示 16* 8 = 128bytes,如果在申請時并不是8的倍數(shù),那就找剛好能滿足內(nèi)存大小的那個位置。比如想申請 12,那就是找16了,想申請 20 ,那就找 24 了

但是現(xiàn)在GC4.9及其之后 也還有,變成_pool_alloc這個名字了,不再是默認的了,你需要自己去指定它可以自己指定,比如說vector

58、從匯編層去解釋一下引用

9: int x = 1; 00401048 mov     dword ptr [ebp-4],1 10: int &b = x; 0040104F lea     eax,[ebp-4] 00401052 mov     dword ptr [ebp-8],eax

x的地址為ebp-4,b的地址為ebp-8,因為棧內(nèi)的變量內(nèi)存是從高往低進行分配的,所以b的地址比x的低。

lea eax,[ebp-4] 這條語句將x的地址ebp-4放入eax寄存器

mov dword ptr [ebp-8],eax 這條語句將eax的值放入b的地址

ebp-8中上面兩條匯編的作用即:將x的地址存入變量b中,這不和將某個變量的地址存入指針變量是一樣的嗎?所以從匯編層次來看,的確引用是通過指針來實現(xiàn)的。

59、深拷貝與淺拷貝是怎么回事?

1)  淺復(fù)制 :只是拷貝了基本類型的數(shù)據(jù),而引用類型數(shù)據(jù),復(fù)制后也是會發(fā)生引用,我們把這種拷貝叫做“(淺復(fù)制)淺拷貝”,換句話說,淺復(fù)制僅僅是指向被復(fù)制的內(nèi)存地址,如果原地址中對象被改變了,那么淺復(fù)制出來的對象也會相應(yīng)改變。

深復(fù)制 :在計算機中開辟了一塊新的內(nèi)存地址用于存放復(fù)制的對象。

2)  在某些狀況下,類內(nèi)成員變量需要動態(tài)開辟堆內(nèi)存,如果實行位拷貝,也就是把對象里的值完全復(fù)制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經(jīng)申請了內(nèi)存,那A中的那個成員變量也指向同一塊內(nèi)存。這就出現(xiàn)了問題:當B把內(nèi)存釋放了(如:析構(gòu)),這時A內(nèi)的指針就是野指針了,出現(xiàn)運行錯誤。

60、C++模板是什么,你知道底層怎么實現(xiàn)的?

1)  編譯器并不是把函數(shù)模板處理成能夠處理任意類的函數(shù);編譯器從函數(shù)模板通過具體類型產(chǎn)生不同的函數(shù);編譯器會對函數(shù)模板進行兩次編譯:在聲明的地方對模板代碼本身進行編譯,在調(diào)用的地方對參數(shù)替換后的代碼進行編譯。

2)  這是因為函數(shù)模板要被實例化后才能成為真正的函數(shù),在使用函數(shù)模板的源文件中包含函數(shù)模板的頭文件,如果該頭文件中只有聲明,沒有定義,那編譯器無法實例化該模板,最終導(dǎo)致鏈接錯誤。

61、new和malloc的區(qū)別?

1、 new/delete是C++關(guān)鍵字,需要編譯器支持。malloc/free是庫函數(shù),需要頭文件支持;

2、 使用new操作符申請內(nèi)存分配時無須指定內(nèi)存塊的大小,編譯器會根據(jù)類型信息自行計算。而malloc則需要顯式地指出所需內(nèi)存的尺寸。

3、 new操作符內(nèi)存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉(zhuǎn)換,故new是符合類型安全性的操作符。而malloc內(nèi)存分配成功則是返回void * ,需要通過強制類型轉(zhuǎn)換將void*指針轉(zhuǎn)換成我們需要的類型。

4、 new內(nèi)存分配失敗時,會拋出bac_alloc異常。malloc分配內(nèi)存失敗時返回NULL。

5、 new會先調(diào)用operator new函數(shù),申請足夠的內(nèi)存(通常底層使用malloc實現(xiàn))。然后調(diào)用類型的構(gòu)造函數(shù),初始化成員變量,最后返回自定義類型指針。delete先調(diào)用析構(gòu)函數(shù),然后調(diào)用operator delete函數(shù)釋放內(nèi)存(通常底層使用free實現(xiàn))。malloc/free是庫函數(shù),只能動態(tài)的申請和釋放內(nèi)存,無法強制要求其做自定義類型對象構(gòu)造和析構(gòu)工作。

62、delete p、delete [] p、allocator都有什么作用?

1、 動態(tài)數(shù)組管理new一個數(shù)組時,[]中必須是一個整數(shù),但是不一定是常量整數(shù),普通數(shù)組必須是一個常量整數(shù);

2、 new動態(tài)數(shù)組返回的并不是數(shù)組類型,而是一個元素類型的指針;

3、 delete[]時,數(shù)組中的元素按逆序的順序進行銷毀;

4、 new在內(nèi)存分配上面有一些局限性,new的機制是將內(nèi)存分配和對象構(gòu)造組合在一起,同樣的,delete也是將對象析構(gòu)和內(nèi)存釋放組合在一起的。allocator將這兩部分分開進行,allocator申請一部分內(nèi)存,不進行初始化對象,只有當需要的時候才進行初始化操作。

63、new和delete的實現(xiàn)原理, delete是如何知道釋放內(nèi)存的大小的額?

1、 new簡單類型直接調(diào)用operator new分配內(nèi)存;

而對于復(fù)雜結(jié)構(gòu),先調(diào)用operator new分配內(nèi)存,然后在分配的內(nèi)存上調(diào)用構(gòu)造函數(shù);

對于簡單類型,new[]計算好大小后調(diào)用operator new;

對于復(fù)雜數(shù)據(jù)結(jié)構(gòu),new[]先調(diào)用operator new[]分配內(nèi)存,然后在p的前四個字節(jié)寫入數(shù)組大小n,然后調(diào)用n次構(gòu)造函數(shù),針對復(fù)雜類型,new[]會額外存儲數(shù)組大??;

①   new表達式調(diào)用一個名為operator new(operator new[])函數(shù),分配一塊足夠大的、原始的、未命名的內(nèi)存空間;

②   編譯器運行相應(yīng)的構(gòu)造函數(shù)以構(gòu)造這些對象,并為其傳入初始值;

③   對象被分配了空間并構(gòu)造完成,返回一個指向該對象的指針。

2、 delete簡單數(shù)據(jù)類型默認只是調(diào)用free函數(shù);復(fù)雜數(shù)據(jù)類型先調(diào)用析構(gòu)函數(shù)再調(diào)用operator delete;針對簡單類型,delete和delete[]等同。假設(shè)指針p指向new[]分配的內(nèi)存。因為要4字節(jié)存儲數(shù)組大小,實際分配的內(nèi)存地址為[p-4],系統(tǒng)記錄的也是這個地址。delete[]實際釋放的就是p-4指向的內(nèi)存。而delete會直接釋放p指向的內(nèi)存,這個內(nèi)存根本沒有被系統(tǒng)記錄,所以會崩潰。

3、 需要在 new [] 一個對象數(shù)組時,需要保存數(shù)組的維度,C++ 的做法是在分配數(shù)組空間時多分配了 4 個字節(jié)的大小,專門保存數(shù)組的大小,在 delete [] 時就可以取出這個保存的數(shù),就知道了需要調(diào)用析構(gòu)函數(shù)多少次了。

64、malloc申請的存儲空間能用delete釋放嗎

不能,malloc /free主要為了兼容C,new和delete 完全可以取代malloc /free的。

malloc /free的操作對象都是必須明確大小的,而且不能用在動態(tài)類上。

new 和delete會自動進行類型檢查和大小,malloc/free不能執(zhí)行構(gòu)造函數(shù)與析構(gòu)函數(shù),所以動態(tài)對象它是不行的。

當然從理論上說使用malloc申請的內(nèi)存是可以通過delete釋放的。不過一般不這樣寫的。而且也不能保證每個C++的運行時都能正常。

65、malloc與free的實現(xiàn)原理?

1、 在標準C庫中,提供了malloc/free函數(shù)分配釋放內(nèi)存,這兩個函數(shù)底層是由brk、mmap、,munmap這些系統(tǒng)調(diào)用實現(xiàn)的;

2、 brk是將數(shù)據(jù)段(.data)的最高地址指針_edata往高地址推,mmap是在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區(qū)域的地方)找一塊空閑的虛擬內(nèi)存。這兩種方式分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存。在第一次訪問已分配的虛擬地址空間的時候,發(fā)生缺頁中斷,操作系統(tǒng)負責(zé)分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系;

3、 malloc小于128k的內(nèi)存,使用brk分配內(nèi)存,將_edata往高地址推;malloc大于128k的內(nèi)存,使用mmap分配內(nèi)存,在堆和棧之間找一塊空閑內(nèi)存分配;brk分配的內(nèi)存需要等到高地址內(nèi)存釋放以后才能釋放,而mmap分配的內(nèi)存可以單獨釋放。當最高地址空間的空閑內(nèi)存超過128K(可由M_TRIM_THRESHOLD選項調(diào)節(jié))時,執(zhí)行內(nèi)存緊縮操作(trim)。在上一個步驟free的時候,發(fā)現(xiàn)最高地址空閑內(nèi)存超過128K,于是內(nèi)存緊縮。

4、 malloc是從堆里面申請內(nèi)存,也就是說函數(shù)返回的指針是指向堆里面的一塊內(nèi)存。操作系統(tǒng)中有一個記錄空閑內(nèi)存地址的鏈表。當操作系統(tǒng)收到程序的申請時,就會遍歷該鏈表,然后就尋找第一個空間大于所申請空間的堆結(jié)點,然后就將該結(jié)點從空閑結(jié)點鏈表中刪除,并將該結(jié)點的空間分配給程序。

66、malloc、realloc、calloc的區(qū)別

1)   malloc函數(shù)

void* malloc(unsigned int num_size); int *p = malloc(20*sizeof(int));申請20int類型的空間;

2)   calloc函數(shù)

void* calloc(size_t n,size_t size); int *p = calloc(20, sizeof(int));

省去了人為空間計算;malloc申請的空間的值是隨機初始化的,calloc申請的空間的值是初始化為0的;

3)   realloc函數(shù)

void realloc(void *p, size_t new_size);

給動態(tài)分配的空間分配額外的空間,用于擴充容量。

67、類成員初始化方式?構(gòu)造函數(shù)的執(zhí)行順序 ?為什么用成員初始化列表會快一些?

1)  賦值初始化,通過在函數(shù)體內(nèi)進行賦值初始化;列表初始化,在冒號后使用初始化列表進行初始化。

這兩種方式的主要區(qū)別在于:

對于在函數(shù)體中初始化,是在所有的數(shù)據(jù)成員被分配內(nèi)存空間后才進行的。

列表初始化是給數(shù)據(jù)成員分配內(nèi)存空間時就進行初始化,就是說分配一個數(shù)據(jù)成員只要冒號后有此數(shù)據(jù)成員的賦值表達式(此表達式必須是括號賦值表達式),那么分配了內(nèi)存空間后在進入函數(shù)體之前給數(shù)據(jù)成員賦值,就是說初始化這個數(shù)據(jù)成員此時函數(shù)體還未執(zhí)行。

2)  一個派生類構(gòu)造函數(shù)的執(zhí)行順序如下:

①   虛擬基類的構(gòu)造函數(shù)(多個虛擬基類則按照繼承的順序執(zhí)行構(gòu)造函數(shù))。

②   基類的構(gòu)造函數(shù)(多個普通基類也按照繼承的順序執(zhí)行構(gòu)造函數(shù))。

③   類類型的成員對象的構(gòu)造函數(shù)(按照初始化順序)

④   派生類自己的構(gòu)造函數(shù)。

3)  方法一是在構(gòu)造函數(shù)當中做賦值的操作,而方法二是做純粹的初始化操作。我們都知道,C++的賦值操作是會產(chǎn)生臨時對象的。臨時對象的出現(xiàn)會降低程序的效率。

68、成員列表初始化?

1)  必須使用成員初始化的四種情況

①    當初始化一個引用成員時;

②    當初始化一個常量成員時;

③    當調(diào)用一個基類的構(gòu)造函數(shù),而它擁有一組參數(shù)時;

④    當調(diào)用一個成員類的構(gòu)造函數(shù),而它擁有一組參數(shù)時;

2)  成員初始化列表做了什么

①    編譯器會一一操作初始化列表,以適當?shù)捻樞蛟跇?gòu)造函數(shù)之內(nèi)安插初始化操作,并且在任何顯示用戶代碼之前;

②    list中的項目順序是由類中的成員聲明順序決定的,不是由初始化列表的順序決定的;

69、什么是內(nèi)存泄露,如何檢測與避免

內(nèi)存泄露

一般我們常說的內(nèi)存泄漏是指堆內(nèi)存的泄漏。堆內(nèi)存是指程序從堆中分配的,大小任意的(內(nèi)存塊的大小可以在程序運行期決定)內(nèi)存塊,使用完后必須顯式釋放的內(nèi)存。應(yīng)用程序般使用malloc,、realloc、 new等函數(shù)從堆中分配到塊內(nèi)存,使用完后,程序必須負責(zé)相應(yīng)的調(diào)用free或delete釋放該內(nèi)存塊,否則,這塊內(nèi)存就不能被再次使用,我們就說這塊內(nèi)存泄漏了

避免內(nèi)存泄露的幾種方式

  • 計數(shù)法:使用new或者malloc時,讓該數(shù)+1,delete或free時,該數(shù)-1,程序執(zhí)行完打印這個計數(shù),如果不為0則表示存在內(nèi)存泄露

  • 一定要將基類的析構(gòu)函數(shù)聲明為虛函數(shù)

  • 對象數(shù)組的釋放一定要用delete []

  • 有new就有delete,有malloc就有free,保證它們一定成對出現(xiàn)

檢測工具

  • Linux下可以使用Valgrind工具

  • Windows下可以使用CRT庫

70、對象復(fù)用的了解,零拷貝的了解

對象復(fù)用

對象復(fù)用其本質(zhì)是一種設(shè)計模式:Flyweight享元模式。

通過將對象存儲到“對象池”中實現(xiàn)對象的重復(fù)利用,這樣可以避免多次創(chuàng)建重復(fù)對象的開銷,節(jié)約系統(tǒng)資源。

零拷貝

零拷貝就是一種避免 CPU 將數(shù)據(jù)從一塊存儲拷貝到另外一塊存儲的技術(shù)。

零拷貝技術(shù)可以減少數(shù)據(jù)拷貝和共享總線操作的次數(shù)。

在C++中,vector的一個成員函數(shù)emplace_back()很好地體現(xiàn)了零拷貝技術(shù),它跟push_back()函數(shù)一樣可以將一個元素插入容器尾部,區(qū)別在于:使用push_back()函數(shù)需要調(diào)用拷貝構(gòu)造函數(shù)和轉(zhuǎn)移構(gòu)造函數(shù),而使用emplace_back()插入的元素原地構(gòu)造,不需要觸發(fā)拷貝構(gòu)造和轉(zhuǎn)移構(gòu)造,效率更高。舉個例子:

#include  #include  #include  using namespace std; struct Person { string name; int age; //初始構(gòu)造函數(shù) Person(string p_name, int p_age): name(std::move(p_name)), age(p_age)
    { cout << "I have been constructed" <<endl;
    } //拷貝構(gòu)造函數(shù) Person(const Person& other): name(std::move(other.name)), age(other.age)
    { cout << "I have been copy constructed" <<endl;
    } //轉(zhuǎn)移構(gòu)造函數(shù) Person(Person&& other): name(std::move(other.name)), age(other.age)
    { cout << "I have been moved"<<endl;
    }
}; int main() { vectore; cout << "emplace_back:" <<endl;
    e.emplace_back("Jane", 23); //不用構(gòu)造類對象 vectorp; cout << "push_back:"<<endl;
    p.push_back(Person("Mike",36)); return 0;
} //輸出結(jié)果: //emplace_back: //I have been constructed //push_back: //I have been constructed //I am being moved. 

71、解釋一下什么是trivial destructor

trivial destructor”一般是指用戶沒有自定義析構(gòu)函數(shù),而由系統(tǒng)生成的,這種析構(gòu)函數(shù)在《STL源碼解析》中成為“無關(guān)痛癢”的析構(gòu)函數(shù)。

反之,用戶自定義了析構(gòu)函數(shù),則稱之為“non-trivial destructor”,這種析構(gòu)函數(shù)如果申請了新的空間一定要顯式的釋放,否則會造成內(nèi)存泄露

對于trivial destructor,如果每次都進行調(diào)用,顯然對效率是一種傷害,如何進行判斷呢?《STL源碼解析》中給出的說明是:

首先利用value_type()獲取所指對象的型別,再利用__type_traits判斷該型別的析構(gòu)函數(shù)是否trivial,若是(__true_type),則什么也不做,若為(__false_type),則去調(diào)用destory()函數(shù)

也就是說,在實際的應(yīng)用當中,STL庫提供了相關(guān)的判斷方法__type_traits,感興趣的讀者可以自行查閱使用方式。除了trivial destructor,還有trivial construct、trivial copy construct等,如果能夠?qū)κ欠駎rivial進行區(qū)分,可以采用內(nèi)存處理函數(shù)memcpy()、malloc()等更加高效的完成相關(guān)操作,提升效率。

《C++中的 trivial destructor》:https://blog.csdn.net/wudishine/article/details/12307611

72、介紹面向?qū)ο蟮娜筇匦裕⑶遗e例說明

三大特性:繼承、封裝和多態(tài)

(1)繼承

讓某種類型對象獲得另一個類型對象的屬性和方法。

它可以使用現(xiàn)有類的所有功能,并在無需重新編寫原來的類的情況下對這些功能進行擴展

常見的繼承有三種方式:

  1. 實現(xiàn)繼承:指使用基類的屬性和方法而無需額外編碼的能力

  2. 接口繼承:指僅使用屬性和方法的名稱、但是子類必須提供實現(xiàn)的能力

  3. 可視繼承:指子窗體(類)使用基窗體(類)的外觀和實現(xiàn)代碼的能力(C++里好像不怎么用)

例如,將人定義為一個抽象類,擁有姓名、性別、年齡等公共屬性,吃飯、睡覺、走路等公共方法,在定義一個具體的人時,就可以繼承這個抽象類,既保留了公共屬性和方法,也可以在此基礎(chǔ)上擴展跳舞、唱歌等特有方法

(2)封裝

數(shù)據(jù)和代碼捆綁在一起,避免外界干擾和不確定性訪問。

封裝,也就是把客觀事物封裝成抽象的類,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏,例如:將公共的數(shù)據(jù)或方法使用public修飾,而不希望被訪問的數(shù)據(jù)或方法采用private修飾。

(3)多態(tài)

同一事物表現(xiàn)出不同事物的能力,即向不同對象發(fā)送同一消息,不同的對象在接收時會產(chǎn)生不同的行為(重載實現(xiàn)編譯時多態(tài),虛函數(shù)實現(xiàn)運行時多態(tài))。

多態(tài)性是允許你將父對象設(shè)置成為和一個或更多的他的子對象相等的技術(shù),賦值之后,父對象就可以根據(jù)當前賦值給它的子對象的特性以不同的方式運作。簡單一句話:允許將子類類型的指針賦值給父類類型的指針

實現(xiàn)多態(tài)有二種方式:覆蓋(override),重載(overload)。覆蓋:是指子類重新定義父類的虛函數(shù)的做法。重載:是指允許存在多個同名函數(shù),而這些函數(shù)的參數(shù)表不同(或許參數(shù)個數(shù)不同,或許參數(shù)類型不同,或許兩者都不同)。例如:基類是一個抽象對象——人,那教師、運動員也是人,而使用這個抽象對象既可以表示教師、也可以表示運動員。

《C++封裝繼承多態(tài)總結(jié)》:https://blog.csdn.net/IOT_SHUN/article/details/79674293

73、C++中類的數(shù)據(jù)成員和成員函數(shù)內(nèi)存分布情況

C++類是由結(jié)構(gòu)體發(fā)展得來的,所以他們的成員變量(C語言的結(jié)構(gòu)體只有成員變量)的內(nèi)存分配機制是一樣的。下面我們以類來說明問題,如果類的問題通了,結(jié)構(gòu)體也也就沒問題啦。類分為成員變量和成員函數(shù),我們先來討論成員變量。

一個類對象的地址就是類所包含的這一片內(nèi)存空間的首地址,這個首地址也就對應(yīng)具體某一個成員變量的地址。(在定義類對象的同時這些成員變量也就被定義了),舉個例子:

#include  using namespace std; class Person { public:
    Person()
    { this->age = 23;
    } void printAge() { cout << this->age <<endl;
    }
    ~Person(){} public: int age;
}; int main() {
    Person p; cout << "對象地址:"<< &p <<endl; cout << "age地址:"<< &(p.age) <<endl; cout << "對象大?。?<< sizeof(p) <<endl; cout << "age大?。?<< sizeof(p.age) <<endl; return 0;
} //輸出結(jié)果 //對象地址:0x7fffec0f15a8 //age地址:0x7fffec0f15a8 //對象大?。? //age大?。? 

從代碼運行結(jié)果來看,對象的大小和對象中數(shù)據(jù)成員的大小是一致的,也就是說,成員函數(shù)不占用對象的內(nèi)存。這是因為所有的函數(shù)都是存放在代碼區(qū)的,不管是全局函數(shù),還是成員函數(shù)。要是成員函數(shù)占用類的對象空間,那么將是多么可怕的事情:定義一次類對象就有成員函數(shù)占用一段空間。我們再來補充一下靜態(tài)成員函數(shù)的存放問題:靜態(tài)成員函數(shù)與一般成員函數(shù)的唯一區(qū)別就是沒有this指針,因此不能訪問非靜態(tài)數(shù)據(jù)成員,就像我前面提到的,所有函數(shù)都存放在代碼區(qū),靜態(tài)函數(shù)也不例外。所有有人一看到 static 這個單詞就主觀的認為是存放在全局數(shù)據(jù)區(qū),那是不對的。

《C++類對象成員變量和函數(shù)內(nèi)存分配的問題》:https://blog.csdn.net/z2664836046/article/details/78967313

74、成員初始化列表的概念,為什么用它會快一些?

成員初始化列表的概念

在類的構(gòu)造函數(shù)中,不在函數(shù)體內(nèi)對成員變量賦值,而是在構(gòu)造函數(shù)的花括號前面使用冒號和初始化列表賦值

效率

用初始化列表會快一些的原因是,對于類型,它少了一次調(diào)用構(gòu)造函數(shù)的過程,而在函數(shù)體中賦值則會多一次調(diào)用。而對于內(nèi)置數(shù)據(jù)類型則沒有差別。舉個例子:

#include  using namespace std; class A { public:
    A()
    { cout << "默認構(gòu)造函數(shù)A()" << endl;
    }
    A(int a)
    {
        value = a; cout << "A(int "<")" << endl;
    }
    A(const A& a)
    {
        value = a.value; cout << "拷貝構(gòu)造函數(shù)A(A& a):  "<endl;
    } int value;
}; class B { public:
    B() : a(1)
    {
        b = A(2);
    }
    A a;
    A b;
}; int main() {
    B b;
} //輸出結(jié)果: //A(int 1) //默認構(gòu)造函數(shù)A() //A(int 2) 

從代碼運行結(jié)果可以看出,在構(gòu)造函數(shù)體內(nèi)部初始化的對象b多了一次構(gòu)造函數(shù)的調(diào)用過程,而對象a則沒有。由于對象成員變量的初始化動作發(fā)生在進入構(gòu)造函數(shù)之前,對于內(nèi)置類型沒什么影響,但如果有些成員是類,那么在進入構(gòu)造函數(shù)之前,會先調(diào)用一次默認構(gòu)造函數(shù),進入構(gòu)造函數(shù)后所做的事其實是一次賦值操作(對象已存在),所以如果是在構(gòu)造函數(shù)體內(nèi)進行賦值的話,等于是一次默認構(gòu)造加一次賦值,而初始化列表只做一次賦值操作。

《為什么用成員初始化列表會快一些?》:https://blog.csdn.net/JackZhang_123/article/details/82590368

75、(超重要)構(gòu)造函數(shù)為什么不能為虛函數(shù)?析構(gòu)函數(shù)為什么要虛函數(shù)?

1、 從存儲空間角度,虛函數(shù)相應(yīng)一個指向vtable虛函數(shù)表的指針,這大家都知道,但是這個指向vtable的指針事實上是存儲在對象的內(nèi)存空間的。

問題出來了,假設(shè)構(gòu)造函數(shù)是虛的,就須要通過 vtable來調(diào)用,但是對象還沒有實例化,也就是內(nèi)存空間還沒有,怎么找vtable呢?所以構(gòu)造函數(shù)不能是虛函數(shù)。

2、 從使用角度,虛函數(shù)主要用于在信息不全的情況下,能使重載的函數(shù)得到相應(yīng)的調(diào)用。

構(gòu)造函數(shù)本身就是要初始化實例,那使用虛函數(shù)也沒有實際意義呀。

所以構(gòu)造函數(shù)沒有必要是虛函數(shù)。虛函數(shù)的作用在于通過父類的指針或者引用來調(diào)用它的時候可以變成調(diào)用子類的那個成員函數(shù)。而構(gòu)造函數(shù)是在創(chuàng)建對象時自己主動調(diào)用的,不可能通過父類的指針或者引用去調(diào)用,因此也就規(guī)定構(gòu)造函數(shù)不能是虛函數(shù)。

3、構(gòu)造函數(shù)不須要是虛函數(shù),也不同意是虛函數(shù),由于創(chuàng)建一個對象時我們總是要明白指定對象的類型,雖然我們可能通過實驗室的基類的指針或引用去訪問它但析構(gòu)卻不一定,我們往往通過基類的指針來銷毀對象。這時候假設(shè)析構(gòu)函數(shù)不是虛函數(shù),就不能正確識別對象類型從而不能正確調(diào)用析構(gòu)函數(shù)。

4、從實現(xiàn)上看,vbtl在構(gòu)造函數(shù)調(diào)用后才建立,因而構(gòu)造函數(shù)不可能成為虛函數(shù)從實際含義上看,在調(diào)用構(gòu)造函數(shù)時還不能確定對象的真實類型(由于子類會調(diào)父類的構(gòu)造函數(shù));并且構(gòu)造函數(shù)的作用是提供初始化,在對象生命期僅僅運行一次,不是對象的動態(tài)行為,也沒有必要成為虛函數(shù)。

5、當一個構(gòu)造函數(shù)被調(diào)用時,它做的首要的事情之中的一個是初始化它的VPTR。

因此,它僅僅能知道它是“當前”類的,而全然忽視這個對象后面是否還有繼承者。當編譯器為這個構(gòu)造函數(shù)產(chǎn)生代碼時,它是為這個類的構(gòu)造函數(shù)產(chǎn)生代碼——既不是為基類,也不是為它的派生類(由于類不知道誰繼承它)。所以它使用的VPTR必須是對于這個類的VTABLE。

并且,僅僅要它是最后的構(gòu)造函數(shù)調(diào)用,那么在這個對象的生命期內(nèi),VPTR將保持被初始化為指向這個VTABLE, 但假設(shè)接著另一個更晚派生的構(gòu)造函數(shù)被調(diào)用,這個構(gòu)造函數(shù)又將設(shè)置VPTR指向它的 VTABLE,等.直到最后的構(gòu)造函數(shù)結(jié)束。

VPTR的狀態(tài)是由被最后調(diào)用的構(gòu)造函數(shù)確定的。這就是為什么構(gòu)造函數(shù)調(diào)用是從基類到更加派生類順序的還有一個理由??墒?,當這一系列構(gòu)造函數(shù)調(diào)用正發(fā)生時,每一個構(gòu)造函數(shù)都已經(jīng)設(shè)置VPTR指向它自己的VTABLE。假設(shè)函數(shù)調(diào)用使用虛機制,它將僅僅產(chǎn)生通過它自己的VTABLE的調(diào)用,而不是最后的VTABLE(全部構(gòu)造函數(shù)被調(diào)用后才會有最后的VTABLE)。

因為構(gòu)造函數(shù)本來就是為了明確初始化對象成員才產(chǎn)生的,然而virtual function主要是為了再不完全了解細節(jié)的情況下也能正確處理對象。另外,virtual函數(shù)是在不同類型的對象產(chǎn)生不同的動作,現(xiàn)在對象還沒有產(chǎn)生,如何使用virtual函數(shù)來完成你想完成的動作。

直接的講,C++中基類采用virtual虛析構(gòu)函數(shù)是為了防止內(nèi)存泄漏。

具體地說,如果派生類中申請了內(nèi)存空間,并在其析構(gòu)函數(shù)中對這些內(nèi)存空間進行釋放。假設(shè)基類中采用的是非虛析構(gòu)函數(shù),當刪除基類指針指向的派生類對象時就不會觸發(fā)動態(tài)綁定,因而只會調(diào)用基類的析構(gòu)函數(shù),而不會調(diào)用派生類的析構(gòu)函數(shù)。那么在這種情況下,派生類中申請的空間就得不到釋放從而產(chǎn)生內(nèi)存泄漏。

所以,為了防止這種情況的發(fā)生,C++中基類的析構(gòu)函數(shù)應(yīng)采用virtual虛析構(gòu)函數(shù)。

76、析構(gòu)函數(shù)的作用,如何起作用?

1)  構(gòu)造函數(shù)只是起初始化值的作用,但實例化一個對象的時候,可以通過實例去傳遞參數(shù),從主函數(shù)傳遞到其他的函數(shù)里面,這樣就使其他的函數(shù)里面有值了。

規(guī)則,只要你一實例化對象,系統(tǒng)自動回調(diào)用一個構(gòu)造函數(shù)就是你不寫,編譯器也自動調(diào)用一次。

2)  析構(gòu)函數(shù)與構(gòu)造函數(shù)的作用相反,用于撤銷對象的一些特殊任務(wù)處理,可以是釋放對象分配的內(nèi)存空間;特點:析構(gòu)函數(shù)與構(gòu)造函數(shù)同名,但該函數(shù)前面加~。

析構(gòu)函數(shù)沒有參數(shù),也沒有返回值,而且不能重載,在一個類中只能有一個析構(gòu)函數(shù)。當撤銷對象時,編譯器也會自動調(diào)用析構(gòu)函數(shù)。

每一個類必須有一個析構(gòu)函數(shù),用戶可以自定義析構(gòu)函數(shù),也可以是編譯器自動生成默認的析構(gòu)函數(shù)。一般析構(gòu)函數(shù)定義為類的公有成員。

77、構(gòu)造函數(shù)和析構(gòu)函數(shù)可以調(diào)用虛函數(shù)嗎,為什么

1) 在C++中,提倡不在構(gòu)造函數(shù)和析構(gòu)函數(shù)中調(diào)用虛函數(shù);

2) 構(gòu)造函數(shù)和析構(gòu)函數(shù)調(diào)用虛函數(shù)時都不使用動態(tài)聯(lián)編,如果在構(gòu)造函數(shù)或析構(gòu)函數(shù)中調(diào)用虛函數(shù),則運行的是為構(gòu)造函數(shù)或析構(gòu)函數(shù)自身類型定義的版本;

3) 因為父類對象會在子類之前進行構(gòu)造,此時子類部分的數(shù)據(jù)成員還未初始化,因此調(diào)用子類的虛函數(shù)時不安全的,故而C++不會進行動態(tài)聯(lián)編;

4) 析構(gòu)函數(shù)是用來銷毀一個對象的,在銷毀一個對象時,先調(diào)用子類的析構(gòu)函數(shù),然后再調(diào)用基類的析構(gòu)函數(shù)。所以在調(diào)用基類的析構(gòu)函數(shù)時,派生類對象的數(shù)據(jù)成員已經(jīng)銷毀,這個時候再調(diào)用子類的虛函數(shù)沒有任何意義。

78、構(gòu)造函數(shù)、析構(gòu)函數(shù)的執(zhí)行順序?構(gòu)造函數(shù)和拷貝構(gòu)造的內(nèi)部都干了啥?

1)     構(gòu)造函數(shù)順序

①   基類構(gòu)造函數(shù)。如果有多個基類,則構(gòu)造函數(shù)的調(diào)用順序是某類在類派生表中出現(xiàn)的順序,而不是它們在成員初始化表中的順序。

②   成員類對象構(gòu)造函數(shù)。如果有多個成員類對象則構(gòu)造函數(shù)的調(diào)用順序是對象在類中被聲明的順序,而不是它們出現(xiàn)在成員初始化表中的順序。

③   派生類構(gòu)造函數(shù)。

2)     析構(gòu)函數(shù)順序

①   調(diào)用派生類的析構(gòu)函數(shù);

②   調(diào)用成員類對象的析構(gòu)函數(shù);

③   調(diào)用基類的析構(gòu)函數(shù)。

79、虛析構(gòu)函數(shù)的作用,父類的析構(gòu)函數(shù)是否要設(shè)置為虛函數(shù)?

1)  C++中基類采用virtual虛析構(gòu)函數(shù)是為了防止內(nèi)存泄漏。

具體地說,如果派生類中申請了內(nèi)存空間,并在其析構(gòu)函數(shù)中對這些內(nèi)存空間進行釋放。

假設(shè)基類中采用的是非虛析構(gòu)函數(shù),當刪除基類指針指向的派生類對象時就不會觸發(fā)動態(tài)綁定,因而只會調(diào)用基類的析構(gòu)函數(shù),而不會調(diào)用派生類的析構(gòu)函數(shù)。

那么在這種情況下,派生類中申請的空間就得不到釋放從而產(chǎn)生內(nèi)存泄漏。

所以,為了防止這種情況的發(fā)生,C++中基類的析構(gòu)函數(shù)應(yīng)采用virtual虛析構(gòu)函數(shù)。

2)  純虛析構(gòu)函數(shù)一定得定義,因為每一個派生類析構(gòu)函數(shù)會被編譯器加以擴張,以靜態(tài)調(diào)用的方式調(diào)用其每一個虛基類以及上一層基類的析構(gòu)函數(shù)。

因此,缺乏任何一個基類析構(gòu)函數(shù)的定義,就會導(dǎo)致鏈接失敗,最好不要把虛析構(gòu)函數(shù)定義為純虛析構(gòu)函數(shù)。

80、構(gòu)造函數(shù)析構(gòu)函數(shù)可否拋出異常

1)   C++只會析構(gòu)已經(jīng)完成的對象,對象只有在其構(gòu)造函數(shù)執(zhí)行完畢才算是完全構(gòu)造妥當。在構(gòu)造函數(shù)中發(fā)生異常,控制權(quán)轉(zhuǎn)出構(gòu)造函數(shù)之外。

因此,在對象b的構(gòu)造函數(shù)中發(fā)生異常,對象b的析構(gòu)函數(shù)不會被調(diào)用。因此會造成內(nèi)存泄漏。

2)  用auto_ptr對象來取代指針類成員,便對構(gòu)造函數(shù)做了強化,免除了拋出異常時發(fā)生資源泄漏的危機,不再需要在析構(gòu)函數(shù)中手動釋放資源;

3)  如果控制權(quán)基于異常的因素離開析構(gòu)函數(shù),而此時正有另一個異常處于作用狀態(tài),C++會調(diào)用terminate函數(shù)讓程序結(jié)束;

4)  如果異常從析構(gòu)函數(shù)拋出,而且沒有在當?shù)剡M行捕捉,那個析構(gòu)函數(shù)便是執(zhí)行不全的。如果析構(gòu)函數(shù)執(zhí)行不全,就是沒有完成他應(yīng)該執(zhí)行的每一件事情。

81、構(gòu)造函數(shù)一般不定義為虛函數(shù)的原因

(1)創(chuàng)建一個對象時需要確定對象的類型,而虛函數(shù)是在運行時動態(tài)確定其類型的。在構(gòu)造一個對象時,由于對象還未創(chuàng)建成功,編譯器無法知道對象的實際類型

(2)虛函數(shù)的調(diào)用需要虛函數(shù)表指針vptr,而該指針存放在對象的內(nèi)存空間中,若構(gòu)造函數(shù)聲明為虛函數(shù),那么由于對象還未創(chuàng)建,還沒有內(nèi)存空間,更沒有虛函數(shù)表vtable地址用來調(diào)用虛構(gòu)造函數(shù)了

(3)虛函數(shù)的作用在于通過父類的指針或者引用調(diào)用它的時候能夠變成調(diào)用子類的那個成員函數(shù)。而構(gòu)造函數(shù)是在創(chuàng)建對象時自動調(diào)用的,不可能通過父類或者引用去調(diào)用,因此就規(guī)定構(gòu)造函數(shù)不能是虛函數(shù)

(4)析構(gòu)函數(shù)一般都要聲明為虛函數(shù),這個應(yīng)該是老生常談了,這里不再贅述

《為什么C++不能有虛構(gòu)造函數(shù),卻可以有虛析構(gòu)函數(shù)》:https://dwz.cn/lnfW9H6m

82、類什么時候會析構(gòu)?

1)  對象生命周期結(jié)束,被銷毀時;

2)  delete指向?qū)ο蟮闹羔槙r,或delete指向?qū)ο蟮幕愵愋椭羔?,而其基類虛?gòu)函數(shù)是虛函數(shù)時;

3)  對象i是對象o的成員,o的析構(gòu)函數(shù)被調(diào)用時,對象i的析構(gòu)函數(shù)也被調(diào)用。

83、構(gòu)造函數(shù)或者析構(gòu)函數(shù)中可以調(diào)用虛函數(shù)嗎

簡要結(jié)論:

  • 從語法上講,調(diào)用完全沒有問題。

  • 但是從效果上看,往往不能達到需要的目的。

《Effective C++》的解釋是:
派生類對象構(gòu)造期間進入基類的構(gòu)造函數(shù)時,對象類型變成了基類類型,而不是派生類類型。同樣,進入基類析構(gòu)函數(shù)時,對象也是基類類型。

舉個例子:

#include using namespace std; class Base { public:
    Base()
    {
       Function();
    } virtual void Function() { cout << "Base::Fuction" << endl;
    }
    ~Base()
    {
        Function();
    }
}; class A : public Base
{ public:
    A()
    {
      Function();
    } virtual void Function() { cout << "A::Function" << endl;
    }
    ~A()
    {
        Function();
    }
}; int main() {
    Base* a = new Base; delete a; cout << "-------------------------" <<endl;
    Base* b = new A;//語句1 delete b;
} //輸出結(jié)果 //Base::Fuction //Base::Fuction //------------------------- //Base::Fuction //A::Function //Base::Fuction 

語句1講道理應(yīng)該體現(xiàn)多態(tài)性,執(zhí)行類A中的構(gòu)造和析構(gòu)函數(shù),從實驗結(jié)果來看,語句1并沒有體現(xiàn),執(zhí)行流程是先構(gòu)造基類,所以先調(diào)用基類的構(gòu)造函數(shù),構(gòu)造完成再執(zhí)行A自己的構(gòu)造函數(shù),析構(gòu)時也是調(diào)用基類的析構(gòu)函數(shù),也就是說構(gòu)造和析構(gòu)中調(diào)用虛函數(shù)并不能達到目的,應(yīng)該避免

《構(gòu)造函數(shù)或者析構(gòu)函數(shù)中調(diào)用虛函數(shù)會怎么樣?》:https://dwz.cn/TaJTJONX

84、智能指針的原理、常用的智能指針及實現(xiàn)

原理

智能指針是一個類,用來存儲指向動態(tài)分配對象的指針,負責(zé)自動釋放動態(tài)分配的對象,防止堆內(nèi)存泄漏。動態(tài)分配的資源,交給一個類對象去管理,當類對象聲明周期結(jié)束時,自動調(diào)用析構(gòu)函數(shù)釋放資源

常用的智能指針

(1) shared_ptr

實現(xiàn)原理:采用引用計數(shù)器的方法,允許多個智能指針指向同一個對象,每當多一個指針指向該對象時,指向該對象的所有智能指針內(nèi)部的引用計數(shù)加1,每當減少一個智能指針指向?qū)ο髸r,引用計數(shù)會減1,當計數(shù)為0的時候會自動的釋放動態(tài)分配的資源。

  • 智能指針將一個計數(shù)器與類指向的對象相關(guān)聯(lián),引用計數(shù)器跟蹤共有多少個類對象共享同一指針

  • 每次創(chuàng)建類的新對象時,初始化指針并將引用計數(shù)置為1

  • 當對象作為另一對象的副本而創(chuàng)建時,拷貝構(gòu)造函數(shù)拷貝指針并增加與之相應(yīng)的引用計數(shù)

  • 對一個對象進行賦值時,賦值操作符減少左操作數(shù)所指對象的引用計數(shù)(如果引用計數(shù)為減至0,則刪除對象),并增加右操作數(shù)所指對象的引用計數(shù)

  • 調(diào)用析構(gòu)函數(shù)時,構(gòu)造函數(shù)減少引用計數(shù)(如果引用計數(shù)減至0,則刪除基礎(chǔ)對象)

(2) unique_ptr

unique_ptr采用的是獨享所有權(quán)語義,一個非空的unique_ptr總是擁有它所指向的資源。轉(zhuǎn)移一個unique_ptr將會把所有權(quán)全部從源指針轉(zhuǎn)移給目標指針,源指針被置空;所以unique_ptr不支持普通的拷貝和賦值操作,不能用在STL標準容器中;局部變量的返回值除外(因為編譯器知道要返回的對象將要被銷毀);如果你拷貝一個unique_ptr,那么拷貝結(jié)束后,這兩個unique_ptr都會指向相同的資源,造成在結(jié)束時對同一內(nèi)存指針多次釋放而導(dǎo)致程序崩潰。

(3) weak_ptr

weak_ptr:弱引用。引用計數(shù)有一個問題就是互相引用形成環(huán)(環(huán)形引用),這樣兩個指針指向的內(nèi)存都無法釋放。需要使用weak_ptr打破環(huán)形引用。weak_ptr是一個弱引用,它是為了配合shared_ptr而引入的一種智能指針,它指向一個由shared_ptr管理的對象而不影響所指對象的生命周期,也就是說,它只引用,不計數(shù)。如果一塊內(nèi)存被shared_ptr和weak_ptr同時引用,當所有shared_ptr析構(gòu)了之后,不管還有沒有weak_ptr引用該內(nèi)存,內(nèi)存也會被釋放。所以weak_ptr不保證它指向的內(nèi)存一定是有效的,在使用之前使用函數(shù)lock()檢查weak_ptr是否為空指針。

(4) auto_ptr

主要是為了解決“有異常拋出時發(fā)生內(nèi)存泄漏”的問題 。因為發(fā)生異常而無法正常釋放內(nèi)存。

auto_ptr有拷貝語義,拷貝后源對象變得無效,這可能引發(fā)很嚴重的問題;而unique_ptr則無拷貝語義,但提供了移動語義,這樣的錯誤不再可能發(fā)生,因為很明顯必須使用std::move()進行轉(zhuǎn)移。

auto_ptr不支持拷貝和賦值操作,不能用在STL標準容器中。STL容器中的元素經(jīng)常要支持拷貝、賦值操作,在這過程中auto_ptr會傳遞所有權(quán),所以不能在STL中使用。

智能指針shared_ptr代碼實現(xiàn):

template<typename T> class SharedPtr { public:
    SharedPtr(T* ptr = NULL):_ptr(ptr), _pcount(new int(1))
    {}

    SharedPtr(const SharedPtr& s):_ptr(s._ptr), _pcount(s._pcount){
        *(_pcount)++;
    }

    SharedPtr& operator=(const SharedPtr& s){ if (this != &s)
        { if (--(*(this->_pcount)) == 0)
            { delete this->_ptr; delete this->_pcount;
            }
            _ptr = s._ptr;
            _pcount = s._pcount;
            *(_pcount)++;
        } return *this;
    }
    T& operator*()
    { return *(this->_ptr);
    }
    T* operator->()
    { return this->_ptr;
    }
    ~SharedPtr()
    {
        --(*(this->_pcount)); if (this->_pcount == 0)
        { delete _ptr;
            _ptr = NULL; delete _pcount;
            _pcount = NULL;
        }
    } private:
    T* _ptr; int* _pcount;//指向引用計數(shù)的指針 };

《智能指針的原理及實現(xiàn)》:https://blog.csdn.net/lizhentao0707/article/details/81156384

85、構(gòu)造函數(shù)的幾種關(guān)鍵字

default

default關(guān)鍵字可以顯式要求編譯器生成合成構(gòu)造函數(shù),防止在調(diào)用時相關(guān)構(gòu)造函數(shù)類型沒有定義而報錯

#include  using namespace std; class CString { public:
    CString() = default; //語句1 //構(gòu)造函數(shù) CString(const char* pstr) : _str(pstr){} void* operator new() = delete;//這樣不允許使用new關(guān)鍵字 //析構(gòu)函數(shù) ~CString(){} public: string _str;
}; int main() { auto a = new CString(); //語句2 cout << "Hello World" <<endl; return 0;
} //運行結(jié)果 //Hello World 

如果沒有加語句1,語句2會報錯,表示找不到參數(shù)為空的構(gòu)造函數(shù),將其設(shè)置為default可以解決這個問題

delete

delete關(guān)鍵字可以刪除構(gòu)造函數(shù)、賦值運算符函數(shù)等,這樣在使用的時候會得到友善的提示

#include  using namespace std; class CString { public: void* operator new() = delete;//這樣不允許使用new關(guān)鍵字 //析構(gòu)函數(shù) ~CString(){}
}; int main() { auto a = new CString(); //語句1 cout << "Hello World" <<endl; return 0;
}

在執(zhí)行語句1時,會提示new方法已經(jīng)被刪除,如果將new設(shè)置為私有方法,則會報慘不忍睹的錯誤,因此使用delete關(guān)鍵字可以更加人性化的刪除一些默認方法

=0

將虛函數(shù)定義為純虛函數(shù)(純虛函數(shù)無需定義,= 0只能出現(xiàn)在類內(nèi)部虛函數(shù)的聲明語句處;當然,也可以為純虛函數(shù)提供定義,不過函數(shù)體必須定義在類的外部)

《C++構(gòu)造函數(shù)的default和delete》:https://blog.csdn.net/u010591680/article/details/71101737

86、C++的四種強制轉(zhuǎn)換reinterpret_cast/const_cast/static_cast /dynamic_cast

reinterpret_cast

reinterpret_cast(expression)

type-id 必須是一個指針、引用、算術(shù)類型、函數(shù)指針或者成員指針。它可以用于類型之間進行強制轉(zhuǎn)換。

const_cast

const_cast(expression)

該運算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外, type_id和expression的類型是一樣的。用法如下:

  • 常量指針被轉(zhuǎn)化成非常量的指針,并且仍然指向原來的對象

  • 常量引用被轉(zhuǎn)換成非常量的引用,并且仍然指向原來的對象

  • const_cast一般用于修改底指針。如const char *p形式

static_cast

static_cast < type-id > (expression)

該運算符把expression轉(zhuǎn)換為type-id類型,但沒有運行時類型檢查來保證轉(zhuǎn)換的安全性。它主要有如下幾種用法:

  • 用于類層次結(jié)構(gòu)中基類(父類)和派生類(子類)之間指針或引用引用的轉(zhuǎn)換

  • 進行上行轉(zhuǎn)換(把派生類的指針或引用轉(zhuǎn)換成基類表示)是安全的

  • 進行下行轉(zhuǎn)換(把基類指針或引用轉(zhuǎn)換成派生類表示)時,由于沒有動態(tài)類型檢查,所以是不安全的

  • 用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如把int轉(zhuǎn)換成char,把int轉(zhuǎn)換成enum。這種轉(zhuǎn)換的安全性也要開發(fā)人員來保證。

  • 把空指針轉(zhuǎn)換成目標類型的空指針

  • 把任何類型的表達式轉(zhuǎn)換成void類型

注意:static_cast不能轉(zhuǎn)換掉expression的const、volatile、或者__unaligned屬性。

dynamic_cast

有類型檢查,基類向派生類轉(zhuǎn)換比較安全,但是派生類向基類轉(zhuǎn)換則不太安全

dynamic_cast(expression)

該運算符把expression轉(zhuǎn)換成type-id類型的對象。type-id 必須是類的指針、類的引用或者void*

如果 type-id 是類指針類型,那么expression也必須是一個指針,如果 type-id 是一個引用,那么 expression 也必須是一個引用

dynamic_cast運算符可以在執(zhí)行期決定真正的類型,也就是說expression必須是多態(tài)類型。如果下行轉(zhuǎn)換是安全的(也就說,如果基類指針或者引用確實指向一個派生類對象)這個運算符會傳回適當轉(zhuǎn)型過的指針。如果 如果下行轉(zhuǎn)換不安全,這個運算符會傳回空指針(也就是說,基類指針或者引用沒有指向一個派生類對象)

dynamic_cast主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換

在類層次間進行上行轉(zhuǎn)換時,dynamic_cast和static_cast的效果是一樣的

在進行下行轉(zhuǎn)換時,dynamic_cast具有類型檢查的功能,比static_cast更安全

舉個例子:

#include  using namespace std; class Base { public:
    Base() :b(1) {} virtual void fun() {}; int b;
}; class Son : public Base
{ public:
    Son() :d(2) {} int d;
}; int main() { int n = 97; //reinterpret_cast int *p = &n; //以下兩者效果相同 char *c = reinterpret_cast<char*> (p); char *c2 =  (char*)(p); cout << "reinterpret_cast輸出:"<< *c2 << endl; //const_cast const int *p2 = &n; int *p3 = const_cast<int*>(p2);
    *p3 = 100; cout << "const_cast輸出:" << *p3 << endl;

    Base* b1 = new Son;
    Base* b2 = new Base; //static_cast Son* s1 = static_cast(b1); //同類型轉(zhuǎn)換 Son* s2 = static_cast(b2); //下行轉(zhuǎn)換,不安全 cout << "static_cast輸出:"<< endl; cout << s1->d << endl; cout << s2->d << endl; //下行轉(zhuǎn)換,原先父對象沒有d成員,輸出垃圾值 //dynamic_cast Son* s3 = dynamic_cast(b1); //同類型轉(zhuǎn)換 Son* s4 = dynamic_cast(b2); //下行轉(zhuǎn)換,安全 cout << "dynamic_cast輸出:" << endl; cout << s3->d << endl; if(s4 == nullptr) cout << "s4指針為nullptr" << endl; else cout << s4->d << endl; return 0;
} //輸出結(jié)果 //reinterpret_cast輸出:a //const_cast輸出:100 //static_cast輸出: //2 //-33686019 //dynamic_cast輸出: //2 //s4指針為nullptr 

從輸出結(jié)果可以看出,在進行下行轉(zhuǎn)換時,dynamic_cast安全的,如果下行轉(zhuǎn)換不安全的話其會返回空指針,這樣在進行操作的時候可以預(yù)先判斷。而使用static_cast下行轉(zhuǎn)換存在不安全的情況也可以轉(zhuǎn)換成功,但是直接使用轉(zhuǎn)換后的對象進行操作容易造成錯誤。

87、C++函數(shù)調(diào)用的壓棧過程

從代碼入手,解釋這個過程:

#include  using namespace std; int f(int n) { cout << n << endl; return n;
} void func(int param1, int param2) { int var1 = param1; int var2 = param2; printf("var1=%d,var2=%d", f(var1), f(var2));
} int main(int argc, char* argv[]) {
    func(1, 2); return 0;
} //輸出結(jié)果 //2 //1 //var1=1,var2=2 

當函數(shù)從入口函數(shù)main函數(shù)開始執(zhí)行時,編譯器會將我們操作系統(tǒng)的運行狀態(tài),main函數(shù)的返回地址、main的參數(shù)、mian函數(shù)中的變量、進行依次壓棧;

當main函數(shù)開始調(diào)用func()函數(shù)時,編譯器此時會將main函數(shù)的運行狀態(tài)進行壓棧,再將func()函數(shù)的返回地址、func()函數(shù)的參數(shù)從右到左、func()定義變量依次壓棧;

當func()調(diào)用f()的時候,編譯器此時會將func()函數(shù)的運行狀態(tài)進行壓棧,再將的返回地址、f()函數(shù)的參數(shù)從右到左、f()定義變量依次壓棧

從代碼的輸出結(jié)果可以看出,函數(shù)f(var1)、f(var2)依次入棧,而后先執(zhí)行f(var2),再執(zhí)行f(var1),最后打印整個字符串,將棧中的變量依次彈出,最后主函數(shù)返回。

《C/C++函數(shù)調(diào)用過程分析》:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601204.html

《C/C++函數(shù)調(diào)用的壓棧模型》:https://blog.csdn.net/m0_37717595/article/details/80368411

88、說說移動構(gòu)造函數(shù)

1)  我們用對象a初始化對象b,后對象a我們就不在使用了,但是對象a的空間還在呀(在析構(gòu)之前),既然拷貝構(gòu)造函數(shù),實際上就是把a對象的內(nèi)容復(fù)制一份到b中,那么為什么我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構(gòu)造的成本。這就是移動構(gòu)造函數(shù)設(shè)計的初衷;

2)  拷貝構(gòu)造函數(shù)中,對于指針,我們一定要采用深層復(fù)制,而移動構(gòu)造函數(shù)中,對于指針,我們采用淺層復(fù)制。淺層復(fù)制之所以危險,是因為兩個指針共同指向一片內(nèi)存空間,若第一個指針將其釋放,另一個指針的指向就不合法了。

所以我們只要避免第一個指針釋放空間就可以了。避免的方法就是將第一個指針(比如a->value)置為NULL,這樣在調(diào)用析構(gòu)函數(shù)的時候,由于有判斷是否為NULL的語句,所以析構(gòu)a的時候并不會回收a->value指向的空間;

3)  移動構(gòu)造函數(shù)的參數(shù)和拷貝構(gòu)造函數(shù)不同,拷貝構(gòu)造函數(shù)的參數(shù)是一個左值引用,但是移動構(gòu)造函數(shù)的初值是一個右值引用。意味著,移動構(gòu)造函數(shù)的參數(shù)是一個右值或者將亡值的引用。也就是說,只用用一個右值,或者將亡值初始化另一個對象的時候,才會調(diào)用移動構(gòu)造函數(shù)。而那個move語句,就是將一個左值變成一個將亡值。

89、C++中將臨時變量作為返回值時的處理過程

首先需要明白一件事情,臨時變量,在函數(shù)調(diào)用過程中是被壓到程序進程的棧中的,當函數(shù)退出時,臨時變量出棧,即臨時變量已經(jīng)被銷毀,臨時變量占用的內(nèi)存空間沒有被清空,但是可以被分配給其他變量,所以有可能在函數(shù)退出時,該內(nèi)存已經(jīng)被修改了,對于臨時變量來說已經(jīng)是沒有意義的值了

C語言里規(guī)定:16bit程序中,返回值保存在ax寄存器中,32bit程序中,返回值保持在eax寄存器中,如果是64bit返回值,edx寄存器保存高32bit,eax寄存器保存低32bit

由此可見,函數(shù)調(diào)用結(jié)束后,返回值被臨時存儲到寄存器中,并沒有放到堆或棧中,也就是說與內(nèi)存沒有關(guān)系了。當退出函數(shù)的時候,臨時變量可能被銷毀,但是返回值卻被放到寄存器中與臨時變量的生命周期沒有關(guān)系

如果我們需要返回值,一般使用賦值語句就可以了

《【C++】臨時變量不能作為函數(shù)的返回值?》:https://www.wandouip.com/t5i204349/

(棧上的內(nèi)存分配、拷貝過程)

90、關(guān)于this指針你知道什么?全說出來

  • this指針是類的指針,指向?qū)ο蟮氖椎刂贰?/span>

  • this指針只能在成員函數(shù)中使用,在全局函數(shù)、靜態(tài)成員函數(shù)中都不能用this。

  • this指針只有在成員函數(shù)中才有定義,且存儲位置會因編譯器不同有不同存儲位置。

this指針的用處

一個對象的this指針并不是對象本身的一部分,不會影響sizeof(對象)的結(jié)果。this作用域是在類內(nèi)部,當在類的非靜態(tài)成員函數(shù)中訪問類的非靜態(tài)成員的時候(全局函數(shù),靜態(tài)函數(shù)中不能使用this指針),編譯器會自動將對象本身的地址作為一個隱含參數(shù)傳遞給函數(shù)。也就是說,即使你沒有寫上this指針,編譯器在編譯的時候也是加上this的,它作為非靜態(tài)成員函數(shù)的隱含形參,對各成員的訪問均通過this進行

this指針的使用

一種情況就是,在類的非靜態(tài)成員函數(shù)中返回類對象本身的時候,直接使用 return *this;

另外一種情況是當形參數(shù)與成員變量名相同時用于區(qū)分,如this->n = n (不能寫成n = n)

類的this指針有以下特點

(1)this只能在成員函數(shù)中使用,全局函數(shù)、靜態(tài)函數(shù)都不能使用this。實際上,成員函數(shù)默認第一個參數(shù)T * const this

如:

class A{ public: int func(int p){}
};

其中,func的原型在編譯器看來應(yīng)該是:

int func(A * const this,int p);

(2)由此可見,this在成員函數(shù)的開始前構(gòu)造,在成員函數(shù)的結(jié)束后清除。這個生命周期同任何一個函數(shù)的參數(shù)是一樣的,沒有任何區(qū)別。當調(diào)用一個類的成員函數(shù)時,編譯器將類的指針作為函數(shù)的this參數(shù)傳遞進去。如:

A a;
a.func(10); //此處,編譯器將會編譯成: A::func(&a,10);

看起來和靜態(tài)函數(shù)沒差別,對嗎?不過,區(qū)別還是有的。編譯器通常會對this指針做一些優(yōu)化,因此,this指針的傳遞效率比較高,例如VC通常是通過ecx(計數(shù)寄存器)傳遞this參數(shù)的。

91、幾個this指針的易混問題

A. this指針是什么時候創(chuàng)建的?

this在成員函數(shù)的開始執(zhí)行前構(gòu)造,在成員的執(zhí)行結(jié)束后清除。

但是如果class或者struct里面沒有方法的話,它們是沒有構(gòu)造函數(shù)的,只能當做C的struct使用。采用TYPE xx的方式定義的話,在棧里分配內(nèi)存,這時候this指針的值就是這塊內(nèi)存的地址。采用new的方式創(chuàng)建對象的話,在堆里分配內(nèi)存,new操作符通過eax(累加寄存器)返回分配的地址,然后設(shè)置給指針變量。之后去調(diào)用構(gòu)造函數(shù)(如果有構(gòu)造函數(shù)的話),這時將這個內(nèi)存塊的地址傳給ecx,之后構(gòu)造函數(shù)里面怎么處理請看上面的回答

B. this指針存放在何處?堆、棧、全局變量,還是其他?

this指針會因編譯器不同而有不同的放置位置。可能是棧,也可能是寄存器,甚至全局變量。在匯編級別里面,一個值只會以3種形式出現(xiàn):立即數(shù)、寄存器值和內(nèi)存變量值。不是存放在寄存器就是存放在內(nèi)存中,它們并不是和高級語言變量對應(yīng)的。

C. this指針是如何傳遞類中的函數(shù)的?綁定?還是在函數(shù)參數(shù)的首參數(shù)就是this指針?那么,this指針又是如何找到“類實例后函數(shù)的”?

大多數(shù)編譯器通過ecx(寄數(shù)寄存器)寄存器傳遞this指針。事實上,這也是一個潛規(guī)則。一般來說,不同編譯器都會遵從一致的傳參規(guī)則,否則不同編譯器產(chǎn)生的obj就無法匹配了。

在call之前,編譯器會把對應(yīng)的對象地址放到eax中。this是通過函數(shù)參數(shù)的首參來傳遞的。this指針在調(diào)用之前生成,至于“類實例后函數(shù)”,沒有這個說法。類在實例化時,只分配類中的變量空間,并沒有為函數(shù)分配空間。自從類的函數(shù)定義完成后,它就在那兒,不會跑的

D. this指針是如何訪問類中的變量的?

如果不是類,而是結(jié)構(gòu)體的話,那么,如何通過結(jié)構(gòu)指針來訪問結(jié)構(gòu)中的變量呢?如果你明白這一點的話,就很容易理解這個問題了。

在C++中,類和結(jié)構(gòu)是只有一個區(qū)別的:類的成員默認是private,而結(jié)構(gòu)是public。

this是類的指針,如果換成結(jié)構(gòu)體,那this就是結(jié)構(gòu)的指針了。

E.我們只有獲得一個對象后,才能通過對象使用this指針。如果我們知道一個對象this指針的位置,可以直接使用嗎?

this指針只有在成員函數(shù)中才有定義。因此,你獲得一個對象后,也不能通過對象使用this指針。所以,我們無法知道一個對象的this指針的位置(只有在成員函數(shù)里才有this指針的位置)。當然,在成員函數(shù)里,你是可以知道this指針的位置的(可以通過&this獲得),也可以直接使用它。

F.每個類編譯后,是否創(chuàng)建一個類中函數(shù)表保存函數(shù)指針,以便用來調(diào)用函數(shù)?

普通的類函數(shù)(不論是成員函數(shù),還是靜態(tài)函數(shù))都不會創(chuàng)建一個函數(shù)表來保存函數(shù)指針。只有虛函數(shù)才會被放到函數(shù)表中。但是,即使是虛函數(shù),如果編譯期就能明確知道調(diào)用的是哪個函數(shù),編譯器就不會通過函數(shù)表中的指針來間接調(diào)用,而是會直接調(diào)用該函數(shù)。正是由于this指針的存在,用來指向不同的對象,從而確保不同對象之間調(diào)用相同的函數(shù)可以互不干擾

《C++中this指針的用法詳解》http://blog.chinaunix.net/uid-21411227-id-1826942.html

92、構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)和賦值操作符的區(qū)別

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

對象不存在,沒用別的對象初始化,在創(chuàng)建一個新的對象時調(diào)用構(gòu)造函數(shù)

拷貝構(gòu)造函數(shù)

對象不存在,但是使用別的已經(jīng)存在的對象來進行初始化

賦值運算符

對象存在,用別的對象給它賦值,這屬于重載“=”號運算符的范疇,“=”號兩側(cè)的對象都是已存在的

舉個例子:

#include  using namespace std; class A { public:
    A()
    { cout << "我是構(gòu)造函數(shù)" << endl;
    }
    A(const A& a)
    { cout << "我是拷貝構(gòu)造函數(shù)" << endl;
    }
    A& operator = (A& a)
    { cout << "我是賦值操作符" << endl; return *this;
    }
    ~A() {};
}; int main() {
    A a1; //調(diào)用構(gòu)造函數(shù) A a2 = a1; //調(diào)用拷貝構(gòu)造函數(shù) a2 = a1; //調(diào)用賦值操作符 return 0;
} //輸出結(jié)果 //我是構(gòu)造函數(shù) //我是拷貝構(gòu)造函數(shù) //我是賦值操作符 

93、拷貝構(gòu)造函數(shù)和賦值運算符重載的區(qū)別?

  • 拷貝構(gòu)造函數(shù)是函數(shù),賦值運算符是運算符重載。

  • 拷貝構(gòu)造函數(shù)會生成新的類對象,賦值運算符不能。

  • 拷貝構(gòu)造函數(shù)是直接構(gòu)造一個新的類對象,所以在初始化對象前不需要檢查源對象和新建對象是否相同;賦值運算符需要上述操作并提供兩套不同的復(fù)制策略,另外賦值運算符中如果原來的對象有內(nèi)存分配則需要先把內(nèi)存釋放掉。

  • 形參傳遞是調(diào)用拷貝構(gòu)造函數(shù)(調(diào)用的被賦值對象的拷貝構(gòu)造函數(shù)),但并不是所有出現(xiàn)"="的地方都是使用賦值運算符,如下:

    Student s;
    Student s1 = s;    // 調(diào)用拷貝構(gòu)造函數(shù)
    Student s2;
    s2 = s;    // 賦值運算符操作

注:類中有指針變量時要重寫析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)和賦值運算符

94、智能指針的作用;

1)  C++11中引入了智能指針的概念,方便管理堆內(nèi)存。使用普通指針,容易造成堆內(nèi)存泄露(忘記釋放),二次釋放,程序發(fā)生異常時內(nèi)存泄露等問題等,使用智能指針能更好的管理堆內(nèi)存。

2)  智能指針在C++11版本之后提供,包含在頭文件中,shared_ptr、unique_ptr、weak_ptr。shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數(shù),每一個shared_ptr的拷貝都指向相同的內(nèi)存。每使用他一次,內(nèi)部的引用計數(shù)加1,每析構(gòu)一次,內(nèi)部的引用計數(shù)減1,減為0時,自動刪除所指向的堆內(nèi)存。shared_ptr內(nèi)部的引用計數(shù)是線程安全的,但是對象的讀取需要加鎖。

3)  初始化。智能指針是個模板類,可以指定類型,傳入指針通過構(gòu)造函數(shù)初始化。也可以使用make_shared函數(shù)初始化。不能將指針直接賦值給一個智能指針,一個是類,一個是指針。例如std::shared_ptrp4 = new int(1);的寫法是錯誤的

拷貝和賦值??截愂沟脤ο蟮囊糜嫈?shù)增加1,賦值使得原對象引用計數(shù)減1,當計數(shù)為0時,自動釋放內(nèi)存。后來指向的對象引用計數(shù)加1,指向后來的對象

4)  unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現(xiàn))。相比與原始指針unique_ptr用于其RAII的特性,使得在出現(xiàn)異常的情況下,動態(tài)資源能得到釋放。unique_ptr指針本身的生命周期:從unique_ptr指針創(chuàng)建時開始,直到離開作用域。離開作用域時,若其指向?qū)ο?,則將其所指對象銷毀(默認使用delete操作符,用戶可指定其他操作)。unique_ptr指針與其所指對象的關(guān)系:在智能指針生命周期內(nèi),可以改變智能指針所指對象,如創(chuàng)建智能指針時通過構(gòu)造函數(shù)指定、通過reset方法重新指定、通過release方法釋放所有權(quán)、通過移動語義轉(zhuǎn)移所有權(quán)。

5)  智能指針類將一個計數(shù)器與類指向的對象相關(guān)聯(lián),引用計數(shù)跟蹤該類有多少個對象共享同一指針。每次創(chuàng)建類的新對象時,初始化指針并將引用計數(shù)置為1;當對象作為另一對象的副本而創(chuàng)建時,拷貝構(gòu)造函數(shù)拷貝指針并增加與之相應(yīng)的引用計數(shù);對一個對象進行賦值時,賦值操作符減少左操作數(shù)所指對象的引用計數(shù)(如果引用計數(shù)為減至0,則刪除對象),并增加右操作數(shù)所指對象的引用計數(shù);調(diào)用析構(gòu)函數(shù)時,構(gòu)造函數(shù)減少引用計數(shù)(如果引用計數(shù)減至0,則刪除基礎(chǔ)對象)。

6)  weak_ptr 是一種不控制對象生命周期的智能指針, 它指向一個 shared_ptr 管理的對象. 進行該對象的內(nèi)存管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理對象的一個訪問手段。weak_ptr 設(shè)計的目的是為配合 shared_ptr 而引入的一種智能指針來協(xié)助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構(gòu)造, 它的構(gòu)造和析構(gòu)不會引起引用記數(shù)的增加或減少.

95、說說你了解的auto_ptr作用

1)  auto_ptr的出現(xiàn),主要是為了解決“有異常拋出時發(fā)生內(nèi)存泄漏”的問題;拋出異常,將導(dǎo)致指針p所指向的空間得不到釋放而導(dǎo)致內(nèi)存泄漏;

2)  auto_ptr構(gòu)造時取得某個對象的控制權(quán),在析構(gòu)時釋放該對象。我們實際上是創(chuàng)建一個auto_ptr類型的局部對象,該局部對象析構(gòu)時,會將自身所擁有的指針空間釋放,所以不會有內(nèi)存泄漏;

3)  auto_ptr的構(gòu)造函數(shù)是explicit,阻止了一般指針隱式轉(zhuǎn)換為 auto_ptr的構(gòu)造,所以不能直接將一般類型的指針賦值給auto_ptr類型的對象,必須用auto_ptr的構(gòu)造函數(shù)創(chuàng)建對象;

4)  由于auto_ptr對象析構(gòu)時會刪除它所擁有的指針,所以使用時避免多個auto_ptr對象管理同一個指針;

5)  Auto_ptr內(nèi)部實現(xiàn),析構(gòu)函數(shù)中刪除對象用的是delete而不是delete[],所以auto_ptr不能管理數(shù)組;

6)  auto_ptr支持所擁有的指針類型之間的隱式類型轉(zhuǎn)換。

7)  可以通過*和->運算符對auto_ptr所有用的指針進行提領(lǐng)操作;

8)  T* get(),獲得auto_ptr所擁有的指針;T* release(),釋放auto_ptr的所有權(quán),并將所有用的指針返回。

96、智能指針的循環(huán)引用

循環(huán)引用是指使用多個智能指針share_ptr時,出現(xiàn)了指針之間相互指向,從而形成環(huán)的情況,有點類似于死鎖的情況,這種情況下,智能指針往往不能正常調(diào)用對象的析構(gòu)函數(shù),從而造成內(nèi)存泄漏。舉個例子:

#include  using namespace std; template <typename T> class Node { public:
    Node(const T& value)
        :_pPre(NULL)
        , _pNext(NULL)
        , _value(value)
    { cout << "Node()" << endl;
    }
    ~Node()
    { cout << "~Node()" << endl; cout << "this:" << this << endl;
    } shared_ptr _pPre; shared_ptr _pNext;
    T _value;
}; void Funtest() { shared_ptrint>> sp1(new Node<int>(1)); shared_ptrint>> sp2(new Node<int>(2)); cout << "sp1.use_count:" << sp1.use_count() << endl; cout << "sp2.use_count:" << sp2.use_count() << endl;

    sp1->_pNext = sp2; //sp1的引用+1 sp2->_pPre = sp1; //sp2的引用+1 cout << "sp1.use_count:" << sp1.use_count() << endl; cout << "sp2.use_count:" << sp2.use_count() << endl;
} int main() {
    Funtest();
    system("pause"); return 0;
} //輸出結(jié)果 //Node() //Node() //sp1.use_count:1 //sp2.use_count:1 //sp1.use_count:2 //sp2.use_count:2 

從上面shared_ptr的實現(xiàn)中我們知道了只有當引用計數(shù)減減之后等于0,析構(gòu)時才會釋放對象,而上述情況造成了一個僵局,那就是析構(gòu)對象時先析構(gòu)sp2,可是由于sp2的空間sp1還在使用中,所以sp2.use_count減減之后為1,不釋放,sp1也是相同的道理,由于sp1的空間sp2還在使用中,所以sp1.use_count減減之后為1,也不釋放。sp1等著sp2先釋放,sp2等著sp1先釋放,二者互不相讓,導(dǎo)致最終都沒能釋放,內(nèi)存泄漏。

在實際編程過程中,應(yīng)該盡量避免出現(xiàn)智能指針之間相互指向的情況,如果不可避免,可以使用弱指針—weak_ptr,它不增加引用計數(shù),只要出了作用域就會自動析構(gòu)。

《C++ 智能指針(及循環(huán)引用問題)》:https://blog.csdn.net/m0_37968340/article/details/76737395

97、什么是虛擬繼承

由于C++支持多繼承,除了public、protected和private三種繼承方式外,還支持虛擬(virtual)繼承,舉個例子:

#include  using namespace std; class A{} class B : virtual public A{}; class C : virtual public A{}; class D : public B, public C{}; int main() { cout << "sizeof(A):" << sizeof A <<endl; // 1,空對象,只有一個占位 cout << "sizeof(B):" << sizeof B <<endl; // 4,一個bptr指針,省去占位,不需要對齊 cout << "sizeof(C):" << sizeof C <<endl; // 4,一個bptr指針,省去占位,不需要對齊 cout << "sizeof(D):" << sizeof D <<endl; // 8,兩個bptr,省去占位,不需要對齊 }

上述代碼所體現(xiàn)的關(guān)系是,B和C虛擬繼承A,D又公有繼承B和C,這種方式是一種菱形繼承或者鉆石繼承,可以用如下圖來表示

虛擬繼承的情況下,無論基類被繼承多少次,只會存在一個實體。虛擬繼承基類的子類中,子類會增加某種形式的指針,或者指向虛基類子對象,或者指向一個相關(guān)的表格;表格中存放的不是虛基類子對象的地址,就是其偏移量,此類指針被稱為bptr,如上圖所示。如果既存在vptr又存在bptr,某些編譯器會將其優(yōu)化,合并為一個指針

98、如何獲得結(jié)構(gòu)成員相對于結(jié)構(gòu)開頭的字節(jié)偏移量

使用 offsetof() 函數(shù)

舉個例子:

#include  #include  using namespace std; struct S { int x; char y; int z; double a;
}; int main() { cout << offsetof(S, x) << endl; // 0 cout << offsetof(S, y) << endl; // 4 cout << offsetof(S, z) << endl; // 8 cout << offsetof(S, a) << endl; // 12 return 0;
}

在VS2019 + win下 并不是這樣的 cout << offsetof(S, x) << endl; // 0 cout << offsetof(S, y) << endl; // 4 cout << offsetof(S, z) << endl; // 8 cout << offsetof(S, a) << endl; // 16 這里是 16的位置,因為 double是8字節(jié),需要找一個8的倍數(shù)對齊, 當然了,如果加上 #pragma pack(4)指定 4字節(jié)對齊就可以了 #pragma pack(4) struct S { int x; char y; int z; double a;
}; void test02() { cout << offsetof(S, x) << endl; // 0 cout << offsetof(S, y) << endl; // 4 cout << offsetof(S, z) << endl; // 8 cout << offsetof(S, a) << endl; // 12

S結(jié)構(gòu)體中各個數(shù)據(jù)成員的內(nèi)存空間劃分如下所示,需要注意內(nèi)存對齊

99、靜態(tài)類型和動態(tài)類型以及靜態(tài)綁定和動態(tài)綁定的總結(jié)

  • 靜態(tài)類型:對象在聲明時采用的類型,在編譯期既已確定;

  • 動態(tài)類型:通常是指一個指針或引用目前所指對象的類型,是在運行期決定的;

  • 靜態(tài)綁定:綁定的是靜態(tài)類型,所對應(yīng)的函數(shù)或?qū)傩砸蕾囉趯ο蟮撵o態(tài)類型,發(fā)生在編譯期;

  • 動態(tài)綁定:綁定的是動態(tài)類型,所對應(yīng)的函數(shù)或?qū)傩砸蕾囉趯ο蟮膭討B(tài)類型,發(fā)生在運行期;

從上面的定義也可以看出,非虛函數(shù)一般都是靜態(tài)綁定,而虛函數(shù)都是動態(tài)綁定(如此才可實現(xiàn)多態(tài)性)。
舉個例子:

#include  using namespace std; class A { public: /*virtual*/ void func() { std::cout << "A::func()\n"; }
}; class B : public A
{ public: void func() { std::cout << "B::func()\n"; }
}; class C : public A
{ public: void func() { std::cout << "C::func()\n"; }
}; int main() {
    C* pc = new C(); //pc的靜態(tài)類型是它聲明的類型C*,動態(tài)類型也是C*; B* pb = new B(); //pb的靜態(tài)類型和動態(tài)類型也都是B*; A* pa = pc; //pa的靜態(tài)類型是它聲明的類型A*,動態(tài)類型是pa所指向的對象pc的類型C*; pa = pb; //pa的動態(tài)類型可以更改,現(xiàn)在它的動態(tài)類型是B*,但其靜態(tài)類型仍是聲明時候的A*; C *pnull = NULL; //pnull的靜態(tài)類型是它聲明的類型C*,沒有動態(tài)類型,因為它指向了NULL; pa->func(); //A::func() pa的靜態(tài)類型永遠都是A*,不管其指向的是哪個子類,都是直接調(diào)用A::func(); pc->func(); //C::func() pc的動、靜態(tài)類型都是C*,因此調(diào)用C::func(); pnull->func(); //C::func() 不用奇怪為什么空指針也可以調(diào)用函數(shù),因為這在編譯期就確定了,和指針空不空沒關(guān)系; return 0;
}

如果將A類中的virtual注釋去掉,則運行結(jié)果是:

pa->func(); //B::func() 因為有了virtual虛函數(shù)特性,pa的動態(tài)類型指向B*,因此先在B中查找,找到后直接調(diào)用; pc->func(); //C::func() pc的動、靜態(tài)類型都是C*,因此也是先在C中查找; pnull->func(); //空指針異常,因為是func是virtual函數(shù),因此對func的調(diào)用只能等到運行期才能確定,然后才發(fā)現(xiàn)pnull是空指針; 

在上面的例子中,

  • 如果基類A中的func不是virtual函數(shù),那么不論pa、pb、pc指向哪個子類對象,對func的調(diào)用都是在定義pa、pb、pc時的靜態(tài)類型決定,早已在編譯期確定了。

  • 同樣的空指針也能夠直接調(diào)用no-virtual函數(shù)而不報錯(這也說明一定要做空指針檢查?。。虼遂o態(tài)綁定不能實現(xiàn)多態(tài);

  • 如果func是虛函數(shù),那所有的調(diào)用都要等到運行時根據(jù)其指向?qū)ο蟮念愋筒拍艽_定,比起靜態(tài)綁定自然是要有性能損失的,但是卻能實現(xiàn)多態(tài)特性;

    本文代碼里都是針對指針的情況來分析的,對于引用的情況也同樣適用。

至此總結(jié)一下靜態(tài)綁定和動態(tài)綁定的區(qū)別:

  • 靜態(tài)綁定發(fā)生在編譯期,動態(tài)綁定發(fā)生在運行期;

  • 對象的動態(tài)類型可以更改,但是靜態(tài)類型無法更改;

  • 要想實現(xiàn)動態(tài),必須使用動態(tài)綁定;

  • 在繼承體系中只有虛函數(shù)使用的是動態(tài)綁定,其他的全部是靜態(tài)綁定;

    建議:

絕對不要重新定義繼承而來的非虛(non-virtual)函數(shù)(《Effective C++ 第三版》條款36),因為這樣導(dǎo)致函數(shù)調(diào)用由對象聲明時的靜態(tài)類型確定了,而和對象本身脫離了關(guān)系,沒有多態(tài),也這將給程序留下不可預(yù)知的隱患和莫名其妙的BUG;另外,在動態(tài)綁定也即在virtual函數(shù)中,要注意默認參數(shù)的使用。當缺省參數(shù)和virtual函數(shù)一起使用的時候一定要謹慎,不然出了問題怕是很難排查。
看下面的代碼:

#include  using namespace std; class E { public: virtual void func(int i = 0) { std::cout << "E::func()\t" << i << "\n";
    }
}; class F : public E
{ public: virtual void func(int i = 1) { std::cout << "F::func()\t" << i << "\n";
    }
}; void test2() {
    F* pf = new F();
    E* pe = pf;
    pf->func(); //F::func() 1  正常,就該如此; pe->func(); //F::func() 0  哇哦,這是什么情況,調(diào)用了子類的函數(shù),卻使用了基類中參數(shù)的默認值! } int main() {
    test2(); return 0;
}

《C++中的靜態(tài)綁定和動態(tài)綁定》:https://www.cnblogs.com/lizhenghn/p/3657717.html

100、C++ 11有哪些新特性?

  • nullptr替代 NULL

  • 引入了 auto 和 decltype 這兩個關(guān)鍵字實現(xiàn)了類型推導(dǎo)

  • 基于范圍的 for 循環(huán)for(auto& i : res){}

  • 類和結(jié)構(gòu)體的中初始化列表

  • Lambda 表達式(匿名函數(shù))

  • std::forward_list(單向鏈表)

  • 右值引用和move語義

101、引用是否能實現(xiàn)動態(tài)綁定,為什么可以實現(xiàn)?

可以。

引用在創(chuàng)建的時候必須初始化,在訪問虛函數(shù)時,編譯器會根據(jù)其所綁定的對象類型決定要調(diào)用哪個函數(shù)。注意只能調(diào)用虛函數(shù)。

舉個例子:

#include  using namespace std; class Base { public: virtual void fun() { cout << "base :: fun()" << endl;
    }
}; class Son : public Base
{ public: virtual void fun() { cout << "son :: fun()" << endl;
    } void func() { cout << "son :: not virtual function" <<endl;
    }
}; int main() {
    Son s;
    Base& b = s; // 基類類型引用綁定已經(jīng)存在的Son對象,引用必須初始化 s.fun(); //son::fun() b.fun(); //son :: fun() return 0;
}

需要說明的是虛函數(shù)才具有動態(tài)綁定,上面代碼中,Son類中還有一個非虛函數(shù)func(),這在b對象中是無法調(diào)用的,如果使用基類指針來指向子類也是一樣的。

102、全局變量和局部變量有什么區(qū)別?

生命周期不同:全局變量隨主程序創(chuàng)建和創(chuàng)建,隨主程序銷毀而銷毀;局部變量在局部函數(shù)內(nèi)部,甚至局部循環(huán)體等內(nèi)部存在,退出就不存在;

使用方式不同:通過聲明后全局變量在程序的各個部分都可以用到;局部變量分配在堆棧區(qū),只能在局部使用。

操作系統(tǒng)和編譯器通過內(nèi)存分配的位置可以區(qū)分兩者,全局變量分配在全局數(shù)據(jù)段并且在程序開始運行的時候被加載,局部變量則分配在堆棧里面 。

《C++經(jīng)典面試題》:https://www.cnblogs.com/yjd_hycf_space/p/7495640.html

103、指針加減計算要注意什么?

指針加減本質(zhì)是對其所指地址的移動,移動的步長跟指針的類型是有關(guān)系的,因此在涉及到指針加減運算需要十分小心,加多或者減多都會導(dǎo)致指針指向一塊未知的內(nèi)存地址,如果再進行操作就會很危險。

舉個例子:

#include  using namespace std; int main() { int *a, *b, c;
    a = (int*)0x500;
    b = (int*)0x520;
    c = b - a; printf("%d\n", c); // 8 a += 0x020;
    c = b - a; printf("%d\n", c); // -24 return 0;
}

首先變量a和b都是以16進制的形式初始化,將它們轉(zhuǎn)成10進制分別是1280(5*16\^2=1280)和1312(5*16\^2+2*16=1312), 那么它們的差值為32,也就是說a和b所指向的地址之間間隔32個位,但是考慮到是int類型占4位,所以c的值為32/4=8

a自增16進制0x20之后,其實際地址變?yōu)?280 + 2*16*4 = 1408,(因為一個int占4位,所以要乘4),這樣它們的差值就變成了1312 - 1280 = -96,所以c的值就變成了-96/4 = -24

遇到指針的計算,需要明確的是指針每移動一位,它實際跨越的內(nèi)存間隔是指針類型的長度,建議都轉(zhuǎn)成10進制計算,計算結(jié)果除以類型長度取得結(jié)果

104、 怎樣判斷兩個浮點數(shù)是否相等?

對兩個浮點數(shù)判斷大小和是否相等不能直接用==來判斷,會出錯!明明相等的兩個數(shù)比較反而是不相等!對于兩個浮點數(shù)比較只能通過相減并與預(yù)先設(shè)定的精度比較,記得要取絕對值!浮點數(shù)與0的比較也應(yīng)該注意。與浮點數(shù)的表示方式有關(guān)。

105、方法調(diào)用的原理(棧、匯編)

1)  機器用棧來傳遞過程參數(shù)、存儲返回信息、保存寄存器用于以后恢復(fù),以及本地存儲。而為單個過程分配的那部分棧稱為幀棧;幀棧可以認為是程序棧的一段,它有兩個端點,一個標識起始地址,一個標識著結(jié)束地址,兩個指針結(jié)束地址指針esp,開始地址指針ebp;

2)  由一系列棧幀構(gòu)成,這些棧幀對應(yīng)一個過程,而且每一個棧指針+4的位置存儲函數(shù)返回地址;每一個棧幀都建立在調(diào)用者的下方,當被調(diào)用者執(zhí)行完畢時,這一段棧幀會被釋放。由于棧幀是向地址遞減的方向延伸,因此如果我們將棧指針減去一定的值,就相當于給棧幀分配了一定空間的內(nèi)存。如果將棧指針加上一定的值,也就是向上移動,那么就相當于壓縮了棧幀的長度,也就是說內(nèi)存被釋放了。

3)  過程實現(xiàn)

①   備份原來的幀指針,調(diào)整當前的棧幀指針到棧指針位置;

②   建立起來的棧幀就是為被調(diào)用者準備的,當被調(diào)用者使用棧幀時,需要給臨時變量分配預(yù)留內(nèi)存;

③   使用建立好的棧幀,比如讀取和寫入,一般使用mov,push以及pop指令等等。

④   恢復(fù)被調(diào)用者寄存器當中的值,這一過程其實是從棧幀中將備份的值再恢復(fù)到寄存器,不過此時這些值可能已經(jīng)不在棧頂了

⑤   恢復(fù)被調(diào)用者寄存器當中的值,這一過程其實是從棧幀中將備份的值再恢復(fù)到寄存器,不過此時這些值可能已經(jīng)不在棧頂了。

⑥   釋放被調(diào)用者的棧幀,釋放就意味著將棧指針加大,而具體的做法一般是直接將棧指針指向幀指針,因此會采用類似下面的匯編代碼處理。

⑦   恢復(fù)調(diào)用者的棧幀,恢復(fù)其實就是調(diào)整棧幀兩端,使得當前棧幀的區(qū)域又回到了原始的位置。

⑧   彈出返回地址,跳出當前過程,繼續(xù)執(zhí)行調(diào)用者的代碼。

4)  過程調(diào)用和返回指令

①   call指令

②   leave指令

③   ret指令

106、C++中的指針參數(shù)傳遞和引用參數(shù)傳遞有什么區(qū)別?底層原理你知道嗎?

1) 指針參數(shù)傳遞本質(zhì)上是值傳遞,它所傳遞的是一個地址值。

值傳遞過程中,被調(diào)函數(shù)的形式參數(shù)作為被調(diào)函數(shù)的局部變量處理,會在棧中開辟內(nèi)存空間以存放由主調(diào)函數(shù)傳遞進來的實參值,從而形成了實參的一個副本(替身)。

值傳遞的特點是,被調(diào)函數(shù)對形式參數(shù)的任何操作都是作為局部變量進行的,不會影響主調(diào)函數(shù)的實參變量的值(形參指針變了,實參指針不會變)。

2) 引用參數(shù)傳遞過程中,被調(diào)函數(shù)的形式參數(shù)也作為局部變量在棧中開辟了內(nèi)存空間,但是這時存放的是由主調(diào)函數(shù)放進來的實參變量的地址。

被調(diào)函數(shù)對形參(本體)的任何操作都被處理成間接尋址,即通過棧中存放的地址訪問主調(diào)函數(shù)中的實參變量(根據(jù)別名找到主調(diào)函數(shù)中的本體)。

因此,被調(diào)函數(shù)對形參的任何操作都會影響主調(diào)函數(shù)中的實參變量。

3) 引用傳遞和指針傳遞是不同的,雖然他們都是在被調(diào)函數(shù)??臻g上的一個局部變量,但是任何對于引用參數(shù)的處理都會通過一個間接尋址的方式操作到主調(diào)函數(shù)中的相關(guān)變量。

而對于指針傳遞的參數(shù),如果改變被調(diào)函數(shù)中的指針地址,它將應(yīng)用不到主調(diào)函數(shù)的相關(guān)變量。如果想通過指針參數(shù)傳遞來改變主調(diào)函數(shù)中的相關(guān)變量(地址),那就得使用指向指針的指針或者指針引用。

4) 從編譯的角度來講,程序在編譯時分別將指針和引用添加到符號表上,符號表中記錄的是變量名及變量所對應(yīng)地址。

指針變量在符號表上對應(yīng)的地址值為指針變量的地址值,而引用在符號表上對應(yīng)的地址值為引用對象的地址值(與實參名字不同,地址相同)。

符號表生成之后就不會再改,因此指針可以改變其指向的對象(指針變量中的值可以改),而引用對象則不能修改。

107、類如何實現(xiàn)只能靜態(tài)分配和只能動態(tài)分配

1)  前者是把new、delete運算符重載為private屬性。后者是把構(gòu)造、析構(gòu)函數(shù)設(shè)為protected屬性,再用子類來動態(tài)創(chuàng)建

2)  建立類的對象有兩種方式:

①   靜態(tài)建立,靜態(tài)建立一個類對象,就是由編譯器為對象在??臻g中分配內(nèi)存;

②   動態(tài)建立,A *p = new A();動態(tài)建立一個類對象,就是使用new運算符為對象在堆空間中分配內(nèi)存。這個過程分為兩步,第一步執(zhí)行operator new()函數(shù),在堆中搜索一塊內(nèi)存并進行分配;第二步調(diào)用類構(gòu)造函數(shù)構(gòu)造對象;

3)  只有使用new運算符,對象才會被建立在堆上,因此只要限制new運算符就可以實現(xiàn)類對象只能建立在棧上,可以將new運算符設(shè)為私有。

108、如果想將某個類用作基類,為什么該類必須定義而非聲明?

派生類中包含并且可以使用它從基類繼承而來的成員,為了使用這些成員,派生類必須知道他們是什么。

結(jié)語

好家伙,終于完了。你要是能看到這里,那真是個狠人。

整理這些東西真心不容易!眼睛快看瞎了求個三連吧!感謝大家!

就醬,拜了個拜!下期再見!





免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉