CPP
1挤巡、在main執(zhí)行之前和之后執(zhí)行的代碼可能是什么?
main函數(shù)執(zhí)行之前酷麦,主要就是初始化系統(tǒng)相關資源:
- 設置棧指針
- 初始化靜態(tài)static變量和global全局變量矿卑,即.data段的內容
- 將未初始化部分的全局變量賦初值:數(shù)值型short,int沃饶,long等為0母廷,bool為FALSE轻黑,指針為NULL等等,即.bss段的內容
- 全局對象初始化琴昆,在main之前調用構造函數(shù)氓鄙,這是可能會執(zhí)行前的一些代碼
將main函數(shù)的參數(shù)argc,argv等傳遞給main函數(shù)业舍,然后才真正運行main函數(shù)
main函數(shù)執(zhí)行之后:
- 全局對象的析構函數(shù)會在main函數(shù)之后執(zhí)行抖拦;
- 可以用 atexit 注冊一個函數(shù),它會在main 之后執(zhí)行;
2舷暮、結構體內存對齊問題态罪?
- 結構體內成員按照聲明順序存儲,第一個成員地址和整個結構體地址相同下面。
- 未特殊說明時复颈,按結構體中size最大的成員對齊(若有double成員,按8字節(jié)對齊沥割。)
3券膀、指針和引用的區(qū)別
- 指針是一個變量,存儲的是一個地址驯遇,引用跟原來的變量實質上是同一個東西,是原變量的別名
- 指針可以為空蓄髓,引用不能為NULL且在定義時必須初始化
- 指針在初始化后可以改變指向叉庐,而引用在初始化之后不可再改變
- sizeof指針得到的是本指針的大小,sizeof引用得到的是引用所指向變量的大小
- 當把指針作為參數(shù)進行傳遞時会喝,也是將實參的一個拷貝傳遞給形參陡叠,兩者指向的地址相同,但不是同一個變量肢执,在函數(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;
}
//運行結果為:
//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); //改變指針指向存皂,但是沒改變指針的所指的內容
cout << a << endl; // 10
cout << *b << endl; // 10
a = 10;
testREFF(a);
cout << a << endl; //12
}
4晌坤、堆和棧的區(qū)別
- 申請方式不同:棧由系統(tǒng)自動分配;堆是自己申請和釋放的旦袋。
- 申請大小限制不同:棧頂和棧底是之前預設好的骤菠,棧是向棧底擴展,大小固定疤孕,可以通過ulimit -a查看商乎,由ulimit -s修改;堆向高地址擴展祭阀,是不連續(xù)的內存區(qū)域鹉戚,大小可以靈活調整。
- 申請效率不同:棧由系統(tǒng)分配专控,速度快抹凳,不會有碎片;堆由程序員分配伦腐,速度慢赢底,且會有碎片。
5柏蘑、區(qū)別以下指針類型幸冻?
- int p[10]表示指針數(shù)組*,強調數(shù)組概念咳焚,是一個數(shù)組變量洽损,數(shù)組大小為10,數(shù)組內每個元素都是指向int類型的指針變量革半。
- int (p)[10]表示數(shù)組指針*碑定,強調是指針,只有一個變量又官,是指針類型不傅,不過指向的是一個int類型的數(shù)組,這個數(shù)組大小是10赏胚。
- int p(int)是函數(shù)聲明*访娶,函數(shù)名是p,參數(shù)是int類型的觉阅,返回值是int *類型的崖疤。
- int (p)(int)是函數(shù)指針*秘车,強調是指針,該指針指向的函數(shù)具有int類型參數(shù)劫哼,并且返回值是int類型的叮趴。
6、基類的虛函數(shù)表存放在內存的什么區(qū)权烧,虛表指針vptr的初始化時間
首先整理一下虛函數(shù)表的特征:
虛函數(shù)表是全局共享的元素眯亦,即全局僅有一個,在編譯時就構造完成
虛函數(shù)表類似一個數(shù)組般码,類對象中存儲vptr指針妻率,指向虛函數(shù)表,即虛函數(shù)表不是函數(shù)板祝,不是程序代碼宫静,不可能存儲在代碼段
虛函數(shù)表存儲虛函數(shù)的地址,即虛函數(shù)表的元素是指向類成員函數(shù)的指針,而類中虛函數(shù)的個數(shù)在編譯時期可以確定,即虛函數(shù)表的大小可以確定,即大小是在編譯時期確定的券时,不必動態(tài)分配內存空間存儲虛函數(shù)表孤里,所以不在堆中
根據(jù)以上特征,虛函數(shù)表類似于類中靜態(tài)成員變量.靜態(tài)成員變量也是全局共享橘洞,大小確定捌袜,因此最有可能存在全局數(shù)據(jù)區(qū),測試結果顯示:
虛函數(shù)表vtable在Linux/Unix中存放在可執(zhí)行文件的只讀數(shù)據(jù)段中(rodata)炸枣,這與微軟的編譯器將虛函數(shù)表存放在常量段存在一些差別
由于虛表指針vptr跟虛函數(shù)密不可分琢蛤,對于有虛函數(shù)或者繼承于擁有虛函數(shù)的基類,對該類進行實例化時抛虏,在構造函數(shù)執(zhí)行時會對虛表指針進行初始化,并且存在對象內存布局的最前面套才。
《虛函數(shù)表存放在哪里》:https://blog.csdn.net/u013270326/article/details/82830656
一般分為五個區(qū)域:棧區(qū)迂猴、堆區(qū)、函數(shù)區(qū)(存放函數(shù)體等二進制代碼)背伴、全局靜態(tài)區(qū)沸毁、常量區(qū)
C++中虛函數(shù)表位于只讀數(shù)據(jù)段(.rodata),也就是C++內存模型中的常量區(qū)傻寂;而虛函數(shù)則位于代碼段(.text)息尺,也就是C++內存模型中的代碼區(qū)。
7疾掰、new / delete 與 malloc / free的異同
相同點
都可用于內存的動態(tài)申請和釋放
不同點
前者是C++運算符搂誉,后者是C/C++語言標準庫函數(shù)
new自動計算要分配的空間大小,malloc需要手工計算
new是類型安全的静檬,malloc不是炭懊。例如:
ew調用名為operator new的標準庫函數(shù)分配足夠空間并調用相關對象的構造函數(shù)并级,delete對指針所指對象運行適當?shù)奈鰳嫼瘮?shù);然后通過調用名為operator delete的標準庫函數(shù)釋放該對象所用內存侮腹。后者均沒有相關調用
后者需要庫文件支持嘲碧,前者不用
new是封裝了malloc,直接free不會報錯父阻,但是這只是釋放內存愈涩,而不會析構對象
nt *p = new float[2]; //編譯錯誤
int *p = (int*)malloc(2 * sizeof(double));//編譯無錯誤
8、new和delete是如何實現(xiàn)的加矛?
new的實現(xiàn)過程是:首先調用名為operator new的標準庫函數(shù)履婉,分配足夠大的原始為類型化的內存,以保存指定類型的一個對象荒椭;接下來運行該類型的一個構造函數(shù)谐鼎,用指定初始化構造對象;最后返回指向新分配并構造后的的對象的指針
delete的實現(xiàn)過程:對指針指向的對象運行適當?shù)奈鰳嫼瘮?shù)趣惠;然后通過調用名為operator delete的標準庫函數(shù)釋放該對象所用內存
9狸棍、malloc和new的區(qū)別?
- malloc和free是標準庫函數(shù)味悄,支持覆蓋草戈;new和delete是運算符,并且支持重載侍瑟。
- malloc僅僅分配內存空間唐片,free僅僅回收空間,不具備調用構造函數(shù)和析構函數(shù)功能涨颜,用malloc分配空間存儲類的對象存在風險费韭;new和delete除了分配回收功能外,還會調用構造函數(shù)和析構函數(shù)庭瑰。
- malloc和free返回的是void類型指針(必須進行類型轉換)星持,new和delete返回的是具體類型指針。
delete和delete[]區(qū)別弹灭?
delete只會調用一次析構函數(shù)督暂。
delete[]會調用數(shù)組中每個元素的析構函數(shù)。
10穷吮、宏定義和函數(shù)有何區(qū)別逻翁?
- 宏在編譯時完成替換,之后被替換的文本參與編譯捡鱼,相當于直接插入了代碼八回,運行時不存在函數(shù)調用,執(zhí)行起來更快;函數(shù)調用在運行時需要跳轉到具體調用函數(shù)辽社。
- 宏定義屬于在結構中插入代碼伟墙,沒有返回值;函數(shù)調用具有返回值滴铅。
- 宏定義參數(shù)沒有類型戳葵,不進行類型檢查;函數(shù)參數(shù)具有類型汉匙,需要檢查類型拱烁。
- 宏定義不要在最后加分號。
11噩翠、宏定義和typedef區(qū)別阵赠?
- 宏主要用于定義常量及書寫復雜的內容乖篷;typedef主要用于定義類型別名辖所。
- 宏替換發(fā)生在編譯階段之前险胰,屬于文本插入替換;typedef是編譯的一部分屯援。
- 宏不檢查類型猛们;typedef會檢查數(shù)據(jù)類型。
- 宏不是語句狞洋,不用在最后加分號弯淘;typedef是語句,要加分號標識結束吉懊。
注意對指針的操作庐橙,typedef char * p_char 和 #define p_char char * 區(qū)別巨大。
12借嗽、變量聲明和定義區(qū)別态鳖?
聲明僅僅是把變量的聲明的位置及類型提供給編譯器,并不分配內存空間恶导;定義要在定義的地方為其分配存儲空間浆竭。
相同變量可以在多處聲明(外部變量extern),但只能在一處定義甲锡。
13、哪幾種情況必須用到初始化成員列表羽戒?
初始化一個const成員缤沦。
初始化一個reference成員。
調用一個基類的構造函數(shù)易稠,而該函數(shù)有一組參數(shù)缸废。
調用一個數(shù)據(jù)成員對象的構造函數(shù),而該函數(shù)有一組參數(shù)。
14企量、strlen和sizeof區(qū)別测萎?
sizeof是運算符,并不是函數(shù)届巩,結果在編譯時得到而非運行中獲得硅瞧;strlen是字符處理的庫函數(shù)。
sizeof參數(shù)可以是任何數(shù)據(jù)的類型或者數(shù)據(jù)(sizeof參數(shù)不退化)恕汇;strlen的參數(shù)只能是字符指針且結尾是'\0'的字符串腕唧。
因為sizeof值在編譯時確定,所以不能用來得到動態(tài)分配(運行時分配)存儲空間的大小瘾英。
int main(int argc, char const *argv[]){
const char* str = "name";
sizeof(str); // 取的是指針str的長度枣接,是8
strlen(str); // 取的是這個字符串的長度,不包含結尾的 \0缺谴。大小是4
return 0;
}
15但惶、常量指針和指針常量區(qū)別?
常量指針是一個指針湿蛔,讀成常量的指針膀曾,指向一個只讀變量。如int const *p或const int *p煌集。
指針常量是一個不能給改變指向的指針妓肢。指針是個常亮,不能中途改變指向苫纤,如int *const p碉钠。
16、a和&a有什么區(qū)別卷拘?
假設數(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)會認為是數(shù)組首地址加上整個數(shù)組的偏移(10個int型變量)惋增,值為數(shù)組a尾元素后一個元素的地址。
若(int *)p 改鲫,此時輸出 *p時诈皿,其值為a[0]的值林束,因為被轉為int *類型,解引用時按照int類型大小來讀取稽亏。
17壶冒、數(shù)組名和指針(這里為指向數(shù)組首元素的指針)區(qū)別?
- 二者均可通過增減偏移量來訪問數(shù)組中的元素截歉。
- 數(shù)組名不是真正意義上的指針胖腾,可以理解為常指針,所以數(shù)組名沒有自增怎披、自減等操作胸嘁。
- 當數(shù)組名當做形參傳遞給調用函數(shù)后,就失去了原有特性凉逛,退化成一般指針性宏,多了自增、自減操作状飞,但sizeof運算符不能再得到原數(shù)組的大小了毫胜。
18、野指針和懸空指針
都是是指向無效內存區(qū)域(這里的無效指的是"不安全不可控")的指針诬辈,訪問行為將會導致未定義行為酵使。
野指針,指的是沒有被初始化過的指針
int main(void) {
int * p;
std::cout<<*p<<std::endl;
return 0;
}
因此焙糟,為了防止出錯口渔,對于指針初始化時都是賦值為 nullptr,這樣在使用時編譯器就會直接報錯穿撮,產生非法內存訪問缺脉。
懸空指針,指針最初指向的內存已經被釋放了的一種指針悦穿。
int main(void) {
int * p = nullptr;
int* p2 = new int;
p = p2;
delete p2;
}
此時 p和p2就是懸空指針攻礼,指向的內存已經被釋放。繼續(xù)使用這兩個指針栗柒,行為不可預料礁扮。需要設置為p=p2=nullptr。此時再使用瞬沦,編譯器會直接報錯太伊。
避免野指針比較簡單,但懸空指針比較麻煩逛钻。c++引入了智能指針僚焦,C++智能指針的本質就是避免懸空指針的產生。
產生原因及解決辦法:
- 野指針:指針變量未及時初始化 => 定義指針變量及時初始化绣的,要么置空叠赐。
- 懸空指針:指針free或delete之后沒有及時置空 => 釋放操作后立即置空。
19屡江、迭代器失效的情況
以vector為例:
插入元素:
1芭概、尾后插入:size < capacity時,首迭代器不失效尾迭代失效(未重新分配空間)惩嘉,size == capacity時罢洲,所有迭代器均失效(需要重新分配空間)。
2文黎、中間插入:中間插入:size < capacity時惹苗,首迭代器不失效但插入元素之后所有迭代器失效,size == capacity時耸峭,所有迭代器均失效桩蓉。
刪除元素:
尾后刪除:只有尾迭代失效。
中間刪除:刪除位置之后所有迭代失效劳闹。
20院究、C和C++的區(qū)別
- C++中new和delete是對內存分配的運算符,取代了C中的malloc和free本涕。
- 標準C++中的字符串類取代了標準C函數(shù)庫頭文件中的字符數(shù)組處理函數(shù)(C中沒有字符串類型)业汰。
- C++中用來做控制態(tài)輸入輸出的iostream類庫替代了標準C中的stdio函數(shù)庫。
- C++中的try/catch/throw異常處理機制取代了標準C中的setjmp()和longjmp()函數(shù)菩颖。
- 在C++中样漆,允許有相同的函數(shù)名,不過它們的參數(shù)類型不能完全相同晦闰,這樣這些函數(shù)就可以相互區(qū)別開來放祟。而這在C語言中是不允許的。也就是C++可以重載鹅髓,C語言不允許舞竿。
- C++語言中,允許變量定義語句在程序中的任何地方窿冯,只要在是使用它之前就可以骗奖;而C語言中,必須要在函數(shù)開頭部分醒串。而且C++允許重復定義變量执桌,C語言也是做不到這一點的
- 在C++中,除了值和指針之外芜赌,新增了引用仰挣。引用型變量是其他變量的一個別名,我們可以認為他們只是名字不相同缠沈,其他都是相同的膘壶。
- C++相對與C增加了一些關鍵字错蝴,如:bool、using颓芭、dynamic_cast顷锰、namespace等等
21、C++與Java的區(qū)別
22亡问、C++中struct和class的區(qū)別
相同點
- 兩者都擁有成員函數(shù)官紫、公有和私有部分
- 任何可以使用class完成的工作,同樣可以使用struct完成
不同點
- 兩者中如果不對成員不指定公私有州藕,struct默認是公有的束世,class則默認是私有的
- class默認是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是沒有權限的設置的,且struct中只能是一些變量的集合體馅精,可以封裝數(shù)據(jù)卻不可以隱藏數(shù)據(jù)严嗜,而且成員不可以是函數(shù)
- C++中,struct增加了訪問權限洲敢,且可以和類一樣有成員函數(shù)漫玄,成員默認訪問說明符為public(為了與C兼容)
- struct作為類的一種特例是用來自定義數(shù)據(jù)結構的。一個結構標記聲明后压彭,在C中必須在結構標記前加上struct睦优,才能做結構類型名(除:typedef struct class{};);C++中結構體標記(結構體名)可以直接作為結構體類型名使用,此外結構體struct在C++中被當作類的一種特例
23壮不、define宏定義和const的區(qū)別
- 編譯階段: define是在編譯的預處理階段起作用汗盘,而const是在編譯、運行的時候起作用
- 安全性: define只做替換询一,不做類型檢查和計算隐孽,也不求解,容易產生錯誤健蕊,一般最好加上一個大括號包含住全部的內容菱阵,要不然很容易出錯.const常量有數(shù)據(jù)類型,編譯器可以對其進行類型安全檢查
- 內存占用: define只是將宏名稱進行替換缩功,在內存中會產生多分相同的備份杯聚。const在程序運行中只有一份備份撇贺,且可以執(zhí)行常量折疊,能將復雜的的表達式計算出結果放入常量表.
宏替換發(fā)生在編譯階段之前,屬于文本插入替換誓酒;const作用發(fā)生于編譯過程中。
宏不檢查類型;const會檢查數(shù)據(jù)類型。
宏定義的數(shù)據(jù)沒有分配內存空間槽卫,只是插入替換掉;const定義的變量只是值不能改變胰蝠,但要分配內存空間。
24震蒋、C++中const和static的作用
static不考慮類的情況
隱藏茸塞。所有不加static的全局變量和函數(shù)具有全局可見性,可以在其他文件中使用查剖,加了之后只能在該件所在的編譯模塊中使用
默認初始化為0钾虐,包括未初始化的全局靜態(tài)變量與局部靜態(tài)變量,都存在全局未初始化區(qū)
靜態(tài)變量在函數(shù)內定義笋庄,始終存在效扫,且只進行一次初始化,具有記憶性直砂,其作用范圍與局部變量相同菌仁,函數(shù)退出后仍然存在,但不能使用
考慮類的情況
static成員變量:只與類關聯(lián)静暂,不與類的對象關聯(lián)济丘。定義時要分配空間,不能在類聲明中初始化洽蛀,必須在類定義體外部初始化摹迷,初始化時不需要標示為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成員變量:不能在類定義外部初始化塔拳,只能通過構造函數(shù)初始化列表進行初始化,并且必須有構造函數(shù)峡竣;不同類對其const數(shù)據(jù)成員的值可以不同靠抑,所以不能在類中聲明時初始化
const成員函數(shù):const對象不可以調用非const成員函數(shù);非const對象都可以調用适掰;不可以改變非mutable(用該關鍵字聲明的變量可以在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
使用命名的強制類型轉換函數(shù)const_cast時,只能改變運算對象的底層const
C++ 頂層const與底層const總結
C++的頂層const和底層const淺析
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在 * 左側云稚,表示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關鍵字
override
當在父類中使用了虛函數(shù)時候呛伴,你可能需要在某個子類中對這個虛函數(shù)進行重寫,以下方法都可以:
class A
{
virtual void foo();
}
class B : public A
{
void foo(); //OK
virtual void foo(); // OK
void foo() override; //OK
}
如果不使用override谒所,當你手一抖热康,將foo()寫成了foo()會怎么樣呢?結果是編譯器并不會報錯劣领,因為它并不知道你的目的是重寫虛函數(shù)姐军,而是把它當成了新的函數(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
當不希望某個類被繼承油啤,或不希望某個虛函數(shù)被重寫典徘,可以在類名和虛函數(shù)后添加final關鍵字,添加final關鍵字后被繼承或重寫村砂,編譯器會報錯。例子如下:
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中已經被final了
};
class C : B // Error: B is final
{
};
28础废、拷貝初始化和直接初始化
當用于類類型對象時,初始化的拷貝形式和直接形式有所不同:直接初始化直接調用與實參匹配的構造函數(shù)罕模,拷貝初始化總是調用拷貝構造函數(shù)评腺。拷貝初始化首先使用指定構造函數(shù)創(chuàng)建一個臨時對象淑掌,然后用拷貝構造函數(shù)將那個臨時對象拷貝到正在創(chuàng)建的對象蒿讥。舉例如下
string str1("I am a string");//語句1 直接初始化
string str2(str1);//語句2 直接初始化,str1是已經存在的對象抛腕,直接調用構造函數(shù)對str2進行初始化
string str3 = "I am a string";//語句3 拷貝初始化芋绸,先為字符串”I am a string“創(chuàng)建臨時對象,再把臨時對象作為參數(shù)担敌,使用拷貝構造函數(shù)構造str3
string str4 = str1;//語句4 拷貝初始化摔敛,這里相當于隱式調用拷貝構造函數(shù),而不是調用賦值運算符函數(shù)
為了提高效率全封,允許編譯器跳過創(chuàng)建臨時對象這一步马昙,直接調用構造函數(shù)構造要創(chuàng)建的對象,這樣就完全等價于直接初始化了(語句1和語句3等價)刹悴。但是需要辨別兩種情況行楞。
當拷貝構造函數(shù)為private時:語句3和語句4在編譯時會報錯
使用explicit修飾構造函數(shù)時:如果構造函數(shù)存在隱式轉換,編譯時會報錯
C++的直接初始化與復制初始化的區(qū)別:https://blog.csdn.net/qq936836/article/details/83450218
29土匀、初始化和賦值的區(qū)別
對于簡單類型來說子房,初始化和賦值沒什么區(qū)別
對于類和復雜數(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; //拷貝初始化操作池颈,調用拷貝構造函數(shù)
A b;
b = a;//賦值操作尾序,對象a中,num1 = 1躯砰,num2 = 1每币;對象b中,num1 = 2琢歇,num2 = 2
return 0;
}
30兰怠、extern"C"的用法
為了能夠正確的在C++代碼中調用C語言的代碼:在程序中加上extern "C"后,相當于告訴編譯器這部分代碼是C語言寫的李茫,因此要按照C語言進行編譯揭保,而不是C++;
哪些情況下使用extern "C":
(1)C++代碼中調用C語言代碼魄宏;
(2)在C++中的頭文件中使用秸侣;
(3)在多個人協(xié)同開發(fā)時,可能有人擅長C語言宠互,而有人擅長C++味榛;
舉個例子,C++中調用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
綜上,總結出使用方法券册,在C語言的頭文件中频轿,對其外部函數(shù)只能指定為extern類型,C語言中不支持extern "C"聲明烁焙,在.c文件中包含了extern "C"時會出現(xiàn)編譯語法錯誤航邢。所以使用extern "C"全部都放在于cpp程序相關文件或其頭文件中。
總結出如下形式:
(1)C++調用C函數(shù):
//xx.h
extern int add(...)
//xx.c
int add(){
}
//xx.cpp
extern "C" {
#include "xx.h"
}
(2)C調用C++函數(shù)
//xx.h
extern "C"{
int add();
}
//xx.cpp
int add(){
}
//xx.c
extern int add();
31骄蝇、模板函數(shù)和模板類的特例化
引入原因
編寫單一的模板翠忠,它能適應多種類型的需求,使每種類型都具有相同的功能乞榨,但對于某種特定類型秽之,如果要實現(xiàn)其特有的功能,單一模板就無法做到吃既,這時就需要模板特例化
定義
對單一模板提供的一個特殊實例考榨,它將一個或多個模板參數(shù)綁定到特定的類型或值上
(1)模板函數(shù)特例化
必須為原函數(shù)模板的每個模板參數(shù)都提供實參,且使用關鍵字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);
}
本質
特例化的本質是實例化一個模板掀鹅,而非重載它散休。特例化不影響參數(shù)匹配。參數(shù)匹配都以最佳匹配為原則乐尊。例如戚丸,此處如果是compare(3,5),則調用普通的模板扔嵌,若為compare(“hi”,”haha”)則調用特例化版本(因為這個cosnt char*相對于T限府,更匹配實參類型),注意二者函數(shù)體的語句不一樣了痢缎,實現(xiàn)不同功能胁勺。
注意
模板及其特例化版本應該聲明在同一個頭文件中,且所有同名模板的聲明應該放在前面独旷,后面放特例化版本署穗。
(2)類模板特例化
原理類似函數(shù)模板,不過在類中嵌洼,我們可以對模板進行特例化案疲,也可以對類進行部分特例化。對類進行特例化時咱台,仍然用template<>表示是一個特例化版本络拌,例如:
template<>
class hash<sales_data>
{
size_t operator()(sales_data& s);
//里面所有T都換成特例化類型版本sales_data
//按照最佳匹配原則俭驮,若T != sales_data回溺,就用普通類模板,否則混萝,就使用含有特定功能的特例化版本遗遵。
};
類模板的部分特例化
不必為所有模板參數(shù)提供實參,可以指定一部分而非所有模板參數(shù)逸嘀,一個類模板的部分特例化本身仍是一個模板车要,使用它時還必須為其特例化版本中未指定的模板參數(shù)提供實參(特例化時類名一定要和原來的模板相同,只是參數(shù)類型不同崭倘,按最佳匹配原則翼岁,哪個最匹配,就用相應的模板)
特例化類中的部分成員
可以特例化類中的部分成員函數(shù)而不是整個類慈省,舉個例子:
template<typename T>
class Foo
{
void Bar();
void Barst(T a)();
};
template<>
void Foo<int>::Bar()
{
//進行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()功能不同
《類和函數(shù)模板特例化》:https://blog.csdn.net/wang664626482/article/details/52372789
32、C和C++的類型安全
什么是類型安全榆俺?
類型安全很大程度上可以等價于內存安全,類型安全的代碼不會試圖訪問自己沒被授權的內存區(qū)域≤罱“類型安全”常被用來形容編程語言陪捷,其根據(jù)在于該門編程語言是否提供保障類型安全的機制;有的時候也用“類型安全”形容某個程序诺擅,判別的標準在于該程序是否隱含類型錯誤市袖。類型安全的編程語言與類型安全的程序之間,沒有必然聯(lián)系掀虎。好的程序員可以使用類型不那么安全的語言寫出類型相當安全的程序凌盯,相反的,差一點兒的程序員可能使用類型相當安全的語言寫出類型不太安全的程序烹玉。絕對類型安全的編程語言暫時還沒有驰怎。
(1)C的類型安全
C只在局部上下文中表現(xiàn)出類型安全,比如試圖從一種結構體的指針轉換成另一種結構體的指針時二打,編譯器將會報告錯誤县忌,除非使用顯式類型轉換。然而继效,C中相當多的操作是不安全的症杏。以下是兩個十分常見的例子:
printf格式輸出
上述代碼中,使用%d控制整型數(shù)字的輸出瑞信,沒有問題厉颤,但是改成%f時,明顯輸出錯誤凡简,再改成%s時逼友,運行直接報segmentation fault錯誤
malloc函數(shù)的返回值
malloc是C中進行內存分配的函數(shù),它的返回類型是void即空類型指針秤涩,常常有這樣的用法char pStr=(char)malloc(100sizeof(char))帜乞,這里明顯做了顯式的類型轉換。類型匹配尚且沒有問題筐眷,但是一旦出現(xiàn)int* pInt=(int)malloc(100sizeof(char))就很可能帶來一些問題黎烈,而這樣的轉換C并不會提示錯誤。
(2)C++的類型安全
如果C++使用得當匀谣,它將遠比C更有類型安全性照棋。相比于C語言,C++提供了一些新的機制保障類型安全:
操作符new返回的指針類型嚴格與對象匹配武翎,而不是void*
C中很多以void*為參數(shù)的函數(shù)可以改寫為C++模板函數(shù)烈炭,而模板是支持類型檢查的;
引入const關鍵字代替#define constants后频,它是有類型梳庆、有作用域的暖途,而#define constants只是簡單的文本替換
一些#define宏可被改寫為inline函數(shù),結合函數(shù)的重載膏执,可在類型安全的前提下支持多種類型驻售,當然改寫為模板也能保證類型安全
C++提供了dynamic_cast關鍵字,使得轉換過程更加安全更米,因為dynamic_cast比static_cast涉及更多具體的類型檢查欺栗。
例1:使用void*進行類型轉換
例2:不同類型指針之間轉換
#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; // 類型向下轉換 強制轉換,由于類型仍然為Child1*征峦,不造成錯誤
cout<<pc1->i<<endl; //輸出:5
pp=&c2;
pc1=(Child1*)pp; //強制轉換迟几,且類型發(fā)生變化,將造成錯誤
cout<<pc1->i<<endl;// 輸出:1717986918
return 0;
}
上面兩個例子之所以引起類型不安全的問題栏笆,是因為程序員使用不得當类腮。第一個例子用到了空類型指針void,第二個例子則是在兩個類型指針之間進行強制轉換蛉加。因此蚜枢,想保證程序的類型安全性,應盡量避免使用空類型指針void针饥,盡量不對兩種類型指針做強制轉換厂抽。
33、為什么析構函數(shù)一般寫成虛函數(shù)
由于類的多態(tài)性丁眼,基類指針可以指向派生類的對象筷凤,如果刪除該基類的指針,就會調用該指針指向的派生類析構函數(shù)苞七,而派生類的析構函數(shù)又自動調用基類的析構函數(shù)藐守,這樣整個派生類的對象完全被釋放。如果析構函數(shù)不被聲明成虛函數(shù)莽鸭,則編譯器實施靜態(tài)綁定吗伤,在刪除基類指針時吃靠,只會調用基類的析構函數(shù)而不調用派生類析構函數(shù)硫眨,這樣就會造成派生類對象析構不完全,造成內存泄漏巢块。所以將析構函數(shù)聲明為虛函數(shù)是十分必要的礁阁。在實現(xiàn)多態(tài)時,當用基類操作派生類族奢,在析構時防止只析構基類而不析構派生類的狀況發(fā)生姥闭,要將基類的析構函數(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;
}
//運行結果:
//Parent construct function
//Son construct function
//Parent destructor function
將基類的析構函數(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;
}
//運行結果:
//Parent construct function
//Son construct function
//Son destructor function
//Parent destructor function
34越走、構造函數(shù)能否聲明為虛函數(shù)或者純虛函數(shù)棚品,析構函數(shù)呢靠欢?
析構函數(shù):
析構函數(shù)可以為虛函數(shù),并且一般情況下基類析構函數(shù)要定義為虛函數(shù)铜跑。
只有在基類析構函數(shù)定義為虛函數(shù)時门怪,調用操作符delete銷毀指向對象的基類指針時,才能準確調用派生類的析構函數(shù)(從該級向上按序調用虛函數(shù))锅纺,才能準確銷毀數(shù)據(jù)掷空。
析構函數(shù)可以是純虛函數(shù),含有純虛函數(shù)的類是抽象類囤锉,此時不能被實例化坦弟。但派生類中可以根據(jù)自身需求重新改寫基類中的純虛函數(shù)。
構造函數(shù):
構造函數(shù)不能定義為虛函數(shù)官地。在構造函數(shù)中可以調用虛函數(shù)酿傍,不過此時調用的是正在構造的類中的虛函數(shù),而不是子類的虛函數(shù)驱入,因為此時子類尚未構造好拧粪。
35、C++中的重載沧侥、重寫(覆蓋)和隱藏的區(qū)別
(1)重載(overload)
重載是指在同一范圍定義中的同名成員函數(shù)才存在重載關系可霎。主要特點是函數(shù)名相同,參數(shù)類型和數(shù)目有所不同宴杀,不能出現(xiàn)參數(shù)個數(shù)和類型均相同癣朗,僅僅依靠返回值不同來區(qū)分的函數(shù)。重載和函數(shù)成員是否是虛函數(shù)無關旺罢。舉個例子:
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ū)別:
重寫是父類和子類之間的垂直關系扁达,重載是不同函數(shù)之間的水平關系
重寫要求參數(shù)列表相同正卧,重載則要求參數(shù)列表不同,返回值不要求
重寫關系中跪解,調用方法根據(jù)對象類型決定炉旷,重載根據(jù)調用時實參表與形參表的對應關系來選擇函數(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); //調用的是B中的fun函數(shù)
b.A::fun(2); //調用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); //報錯捏顺,調用的是B中的fun函數(shù),參數(shù)類型不對
b.A::fun(2); //調用A中fun函數(shù)
return 0;
}
36纬黎、C++的多態(tài)如何實現(xiàn)
C++的多態(tài)性草丧,一言以蔽之就是:
在基類的函數(shù)前加上virtual關鍵字,在派生類中重寫該函數(shù)莹桅,運行時將會根據(jù)所指對象的實際類型來調用相應的函數(shù)昌执,如果對象類型是派生類,就調用派生類的函數(shù)诈泼,如果對象類型是基類懂拾,就調用基類的函數(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;
}
// 運行結果
// Son1::func()
// Base::func()
例子中铐达,Base為基類岖赋,其中的函數(shù)為虛函數(shù)。子類1繼承并重寫了基類的函數(shù)瓮孙,子類2繼承基類但沒有重寫基類的函數(shù)唐断,從結果分析子類體現(xiàn)了多態(tài)性,那么為什么會出現(xiàn)多態(tài)性杭抠,其底層的原理是什么脸甘?這里需要引出虛表和虛基表指針的概念。
虛表:虛函數(shù)表的縮寫偏灿,類中含有virtual關鍵字修飾的方法時丹诀,編譯器會自動生成虛表
虛表指針:在含有虛函數(shù)的類實例化對象時,對象地址的前四個字節(jié)存儲的指向虛表的指針
上圖中展示了虛表和虛表指針在基類對象和派生類對象中的模型翁垂,下面闡述實現(xiàn)多態(tài)的過程:
(1)編譯器在發(fā)現(xiàn)基類中有虛函數(shù)時铆遭,會自動為每個含有虛函數(shù)的類生成一份虛表,該表是一個一維數(shù)組沿猜,虛表里保存了虛函數(shù)的入口地址
(2)編譯器會在每個對象的前四個字節(jié)中保存一個虛表指針枚荣,即vptr,指向對象所屬類的虛表啼肩。在構造時橄妆,根據(jù)對象的類型去初始化虛指針vptr,從而讓vptr指向正確的虛表疟游,從而在調用虛函數(shù)時呼畸,能找到正確的函數(shù)
(3)所謂的合適時機痕支,在派生類定義對象時颁虐,程序運行會自動調用構造函數(shù),在構造函數(shù)中創(chuàng)建虛表并對虛表初始化卧须。在構造子類對象時另绩,會先調用父類的構造函數(shù)儒陨,此時,編譯器只“看到了”父類笋籽,并為父類對象初始化虛表指針蹦漠,令它指向父類的虛表;當調用子類的構造函數(shù)時车海,為子類對象初始化虛表指針笛园,令它指向子類的虛表
(4)當派生類對基類的虛函數(shù)沒有重寫時,派生類的虛表指針指向的是基類的虛表侍芝;當派生類對基類的虛函數(shù)重寫時研铆,派生類的虛表指針指向的是自身的虛表;當派生類中有自己的虛函數(shù)時州叠,在自己的虛表中將此虛函數(shù)地址添加在后面
這樣指向派生類的基類指針在運行時棵红,就可以根據(jù)派生類對虛函數(shù)重寫情況動態(tài)的進行調用,從而實現(xiàn)多態(tài)性咧栗。
《C++實現(xiàn)多態(tài)的原理》:https://blog.csdn.net/qq_37954088/article/details/79947898
37逆甜、C++有哪幾種的構造函數(shù)
C++中的構造函數(shù)可以分為4類:
默認構造函數(shù)
初始化構造函數(shù)(有參數(shù))
拷貝構造函數(shù)
移動構造函數(shù)(move和右值引用)
委托構造函數(shù)
轉換構造函數(shù)
舉個例子:
#include <iostream>
using namespace std;
class Student{
public:
Student(){//默認構造函數(shù),沒有參數(shù)
this->age = 20;
this->num = 1000;
};
Student(int a, int n):age(a), num(n){}; //初始化構造函數(shù)致板,有參數(shù)和參數(shù)列表
Student(const Student& s){//拷貝構造函數(shù)交煞,這里與編譯器生成的一致
this->age = s.age;
this->num = s.num;
};
Student(int r){ //轉換構造函數(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\n", s1.age, s1.num);
printf("s2 age:%d, num:%d\n", s2.age, s2.num);
printf("s3 age:%d, num:%d\n", s3.age, s3.num);
printf("s2 age:%d, num:%d\n", s4.age, s4.num);
return 0;
}
//運行結果
//s1 age:20, num:1000
//s2 age:18, num:1001
//s3 age:10, num:1002
//s2 age:10, num:1002
默認構造函數(shù)和初始化構造函數(shù)在定義類的對象斟或,完成對象的初始化工作
復制構造函數(shù)用于復制本類的對象
轉換構造函數(shù)用于將其他類型的變量错敢,隱式轉換為本類對象
《淺談C++中的幾種構造函數(shù)》:https://blog.csdn.net/zxc024000/article/details/51153743
38、淺拷貝和深拷貝的區(qū)別
淺拷貝
淺拷貝只是拷貝一個指針缕粹,并沒有新開辟一個地址稚茅,拷貝的指針和原來的指針指向同一塊地址,如果原來的指針所指向的資源釋放了平斩,那么再釋放淺拷貝的指針的資源就會出現(xiàn)錯誤亚享。
深拷貝
深拷貝不僅拷貝值,還開辟出一塊新的空間用來存放新的值绘面,即使原先的對象被析構掉欺税,釋放內存了也不會影響到深拷貝得到的值。在自己實現(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){//拷貝構造函數(shù)
//淺拷貝,當對象的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);// 復制對象
}
system("pause");
return 0;
}
//淺拷貝執(zhí)行結果:
//Student
//copy Student
//~Student 0x7fffed0c3ec0
//~Student 0x7fffed0c3ed0
//*** Error in `/tmp/815453382/a.out': double free or corruption (fasttop): 0x0000000001c82c20 ***
//深拷貝執(zhí)行結果:
//Student
//copy Student
//~Student 0x7fffebca9fb0
//~Student 0x7fffebca9fc0
從執(zhí)行結果可以看出歼秽,淺拷貝在對象的拷貝創(chuàng)建時存在風險,即被拷貝的對象析構釋放資源之后情组,拷貝對象析構時會再次釋放一個已經釋放的資源,深拷貝的結果是兩個對象之間沒有任何關系,各自成員地址不同蝗蛙。
《C++面試題之淺拷貝和深拷貝的區(qū)別》:https://blog.csdn.net/caoshangpa/article/details/79226270
39秽晚、內聯(lián)函數(shù)和宏定義的區(qū)別
內聯(lián)(inline)函數(shù)和普通函數(shù)相比可以加快程序運行的速度,因為不需要中斷調用,在編譯的時候內聯(lián)函數(shù)可以直接嵌入到目標代碼中。
內聯(lián)函數(shù)適用場景
使用宏定義的地方都可以使用inline函數(shù)
作為類成員接口函數(shù)來讀寫類的私有成員或者保護成員,會提高效率
為什么不能把所有的函數(shù)寫成內聯(lián)函數(shù)
內聯(lián)函數(shù)以代碼復雜為代價蕉陋,它以省去函數(shù)調用的開銷來提高執(zhí)行效率。所以一方面如果內聯(lián)函數(shù)體內代碼執(zhí)行時間相比函數(shù)調用開銷較大拨扶,則沒有太大的意義寺滚;另一方面每一處內聯(lián)函數(shù)的調用都要復制代碼,消耗更多的內存空間屈雄,因此以下情況不宜使用內聯(lián)函數(shù):
函數(shù)體內的代碼比較長村视,將導致內存消耗代價
函數(shù)體內有循環(huán),函數(shù)執(zhí)行時間要比函數(shù)調用開銷大
主要區(qū)別
內聯(lián)函數(shù)在編譯時展開酒奶,宏在預編譯時展開
內聯(lián)函數(shù)直接嵌入到目標代碼中蚁孔,宏是簡單的做文本替換
內聯(lián)函數(shù)有類型檢測、語法判斷等功能惋嚎,而宏沒有
內聯(lián)函數(shù)是函數(shù)杠氢,宏不是
宏定義時要注意書寫(參數(shù)要括起來)否則容易出現(xiàn)歧義,內聯(lián)函數(shù)不會產生歧義
內聯(lián)函數(shù)代碼是被放到符號表中另伍,使用時像宏一樣展開鼻百,沒有調用的開銷,效率很高摆尝;
《inline函數(shù)和宏定義區(qū)別 整理》:https://blog.csdn.net/wangliang888888/article/details/77990650
在使用時温艇,宏只做簡單字符串替換(編譯前)。而內聯(lián)函數(shù)可以進行參數(shù)類型檢查(編譯時)堕汞,且具有返回值勺爱。
內聯(lián)函數(shù)本身是函數(shù),強調函數(shù)特性讯检,具有重載等功能琐鲁。
內聯(lián)函數(shù)可以作為某個類的成員函數(shù),這樣可以使用類的保護成員和私有成員人灼,進而提升效率围段。而當一個表達式涉及到類保護成員或私有成員時,宏就不能實現(xiàn)了投放。
40奈泪、構造函數(shù)、析構函數(shù)、虛函數(shù)可否聲明為內聯(lián)函數(shù)
首先段磨,將這些函數(shù)聲明為內聯(lián)函數(shù)取逾,在語法上沒有錯誤耗绿。因為inline同register一樣苹支,只是個建議,編譯器并不一定真正的內聯(lián)误阻。
register關鍵字:這個關鍵字請求編譯器盡可能的將變量存在CPU內部寄存器中债蜜,而不是通過內存尋址訪問,以提高效率
舉個例子:
#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;
}
//輸出結果
//inline construct()
//inline virtual function
//inline destruct()
構造函數(shù)和析構函數(shù)聲明為內聯(lián)函數(shù)是沒有意義的
《Effective C++》中所闡述的是:將構造函數(shù)和析構函數(shù)聲明為inline是沒有什么意義的究反,即編譯器并不真正對聲明為inline的構造和析構函數(shù)進行內聯(lián)操作寻定,因為編譯器會在構造和析構函數(shù)中添加額外的操作(申請/釋放內存,構造/析構對象等)精耐,致使構造函數(shù)/析構函數(shù)并不像看上去的那么精簡狼速。其次,class中的函數(shù)默認是inline型的卦停,編譯器也只是有選擇性的inline向胡,將構造函數(shù)和析構函數(shù)聲明為內聯(lián)函數(shù)是沒有什么意義的。
將虛函數(shù)聲明為inline惊完,要分情況討論
有的人認為虛函數(shù)被聲明為inline僵芹,但是編譯器并沒有對其內聯(lián),他們給出的理由是inline是編譯期決定的小槐,而虛函數(shù)是運行期決定的拇派,即在不知道將要調用哪個函數(shù)的情況下,如何將函數(shù)內聯(lián)呢凿跳?
上述觀點看似正確件豌,其實不然,如果虛函數(shù)在編譯器就能夠決定將要調用哪個函數(shù)時控嗜,就能夠內聯(lián)苟径,那么什么情況下編譯器可以確定要調用哪個函數(shù)呢,答案是當用對象調用虛函數(shù)(此時不具有多態(tài)性)時躬审,就內聯(lián)展開
綜上棘街,當是指向派生類的指針(多態(tài)性)調用聲明為inline的虛函數(shù)時,不會內聯(lián)展開承边;當是對象本身調用虛函數(shù)時遭殉,會內聯(lián)展開,當然前提依然是函數(shù)并不復雜的情況下
《構造函數(shù)博助、析構函數(shù)险污、虛函數(shù)可否內聯(lián),有何意義》:https://www.cnblogs.com/helloweworld/archive/2013/06/14/3136705.html
41、auto蛔糯、decltype和decltype(auto)的用法
(1)auto
C++11新標準引入了auto類型說明符拯腮,用它就能讓編譯器替我們去分析表達式所屬的類型。和原來那些只對應某種特定的類型說明符(例如 int)不同蚁飒,
auto 讓編譯器通過初始值來進行類型推演动壤。從而獲得定義變量的類型,所以說 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
有的時候我們還會遇到這種情況,我們希望從表達式中推斷出要定義變量的類型爬早,但卻不想用表達式的值去初始化變量哼丈。還有可能是函數(shù)的返回類型為某表達式的值類型。在這些時候auto顯得就無力了筛严,所以C++11又引入了第二種類型說明符decltype醉旦,它的作用是選擇并返回操作數(shù)的數(shù)據(jù)類型。在此過程中桨啃,編譯器只是分析表達式并得到它的類型车胡,卻不進行實際的計算表達式的值。
int func() {return 0};
//普通類型
decltype(func()) sum = 5; // sum的類型是函數(shù)func()的返回值的類型int, 但是這時不會實際調用函數(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. 如果表達式是引用類型, 那么decltype的類型也是引用
const int i = 3, &j = i;
decltype(j) k = 5; // k的類型是 const int&
//2. 如果表達式是引用類型, 但是想要得到這個引用所指向的類型, 需要修改表達式:
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. 如果一個表達式的類型不是引用, 但是我們需要推斷出引用, 那么可以加上一對括號, 就變成了引用類型了
int i = 3;
decltype((i)) j = i; // 此時j的類型是int&類型, j和i綁定在了一起
(3)decltype(auto)
decltype(auto)是C++14新增的類型指示符优幸,可以用來聲明變量以及指示函數(shù)返回類型吨拍。在使用時,會將“=”號左邊的表達式替換掉auto网杆,再根據(jù)decltype的語法規(guī)則來確定類型羹饰。舉個例子:
int e = 4;
const int* f = &e; // f是底層const
decltype(auto) j = f;//j的類型是const int* 并且指向的是e
《auto和decltype的用法總結》: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訪問和繼承權限/public/protected/private的區(qū)別队秩?
public的變量和函數(shù)在類的內部外部都可以訪問。
protected的變量和函數(shù)只能在類的內部和其派生類中訪問昼浦。
private修飾的元素只能在類內訪問馍资。
(一)訪問權限
派生類可以繼承基類中除了構造/析構、賦值運算符重載函數(shù)之外的成員关噪,但是這些成員的訪問屬性在派生過程中也是可以調整的鸟蟹,三種派生方式的訪問權限如下表所示:注意外部訪問并不是真正的外部訪問,而是在通過派生類的對象對基類成員的訪問使兔。
派生類對基類成員的訪問形象有如下兩種:
內部訪問:由派生類中新增的成員函數(shù)對從基類繼承來的成員的訪問
外部訪問:在派生類外部建钥,通過派生類的對象對從基類繼承來的成員的訪問
(二)繼承權限
public繼承
公有繼承的特點是基類的公有成員和保護成員作為派生類的成員時,都保持原有的狀態(tài)虐沥,而基類的私有成員任然是私有的熊经,不能被這個派生類的子類所訪問
protected繼承
保護繼承的特點是基類的所有公有成員和保護成員都成為派生類的保護成員泽艘,并且只能被它的派生類成員函數(shù)或友元函數(shù)訪問,基類的私有成員仍然是私有的.
private繼承
私有繼承的特點是基類的所有公有成員和保護成員都成為派生類的私有成員镐依,并不被它的派生類的子類所訪問匹涮,基類的成員只能由自己派生類訪問,無法再往下繼承槐壳,訪問規(guī)則如下表
43然低、如何用代碼判斷大小端存儲
大端存儲:字數(shù)據(jù)的高字節(jié)存儲在低地址中
小端存儲:字數(shù)據(jù)的低字節(jié)存儲在低地址中
例如:32bit的數(shù)字0x12345678
所以在Socket編程中,往往需要將操作系統(tǒng)所用的小端存儲的IP地址轉換為大端存儲宏粤,這樣才能進行網絡傳輸
了解了大小端存儲的方式脚翘,如何在代碼中進行判斷呢灼卢?下面介紹兩種判斷方式:
方式一:使用強制類型轉換-這種法子不錯
#include <iostream>
using namespace std;
int main()
{
int a = 0x1234;
//由于int和char的長度不同绍哎,借助int型轉換成char型,只會留下低地址的部分
char c = (char)(a);
if (c == 0x12)
cout << "big endian" << endl;
else if(c == 0x34)
cout << "little endian" << endl;
}
方式二:巧用union聯(lián)合體
#include <iostream>
using namespace std;
//union聯(lián)合體的重疊式存儲鞋真,endian聯(lián)合體占用內存的空間為每個成員字節(jié)長度的最大值
union endian
{
int a;
char ch;
};
int main()
{
endian value;
value.a = 0x1234;
//a和ch共用4字節(jié)的內存空間
if (value.ch == 0x12)
cout << "big endian"<<endl;
else if (value.ch == 0x34)
cout << "little endian"<<endl;
}
《寫程序判斷系統(tǒng)是大端序還是小端序》:https://www.cnblogs.com/zhoudayang/p/5985563.html
44崇堰、volatile、mutable和explicit關鍵字的用法
(1)volatile
volatile 關鍵字是一種類型修飾符涩咖,用它聲明的類型變量表示可以被某些編譯器未知的因素更改海诲,比如:操作系統(tǒng)、硬件或者其它線程等檩互。遇到這個關鍵字聲明的變量特幔,編譯器對訪問該變量的代碼就不再進行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問闸昨。
當要求使用 volatile 聲明的變量的值的時候蚯斯,系統(tǒng)總是重新從它所在的內存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過數(shù)據(jù)饵较。
volatile定義變量的值是易變的拍嵌,每次用到這個變量的值的時候都要去重新讀取這個變量的值,而不是讀寄存器內的備份循诉。多線程中被幾個任務共享的變量需要定義為volatile類型横辆。
volatile 指針
volatile 指針和 const 修飾詞類似,const 有常量指針和指針常量的說法茄猫,volatile 也有相應的概念
修飾由指針指向的對象狈蚤、數(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類型進行修飾阿浓。
C++中一個有volatile標識符的類只能訪問它接口的子集他嚷,一個由類的實現(xiàn)者控制的子集。用戶只能用const_cast來獲得對類型接口的完全訪問。此外筋蓖,volatile向const一樣會從類傳遞到它的成員卸耘。
多線程下的volatile
有些變量是用volatile關鍵字聲明的。當兩個線程都要用到某一個變量且該變量的值會被改變時粘咖,應該用volatile聲明蚣抗,該關鍵字的作用是防止優(yōu)化編譯器把變量從內存裝入CPU寄存器中。如果變量被裝入寄存器瓮下,那么兩個線程有可能一個使用內存中的變量翰铡,一個使用寄存器中的變量,這會造成程序的錯誤執(zhí)行讽坏。volatile的意思是讓編譯器每次操作該變量時一定要從內存中真正取出锭魔,而不是使用已經存在寄存器中的值。
(2)mutable
mutable的中文意思是“可變的路呜,易變的”迷捧,跟constant(既C++中的const)是反義詞。在C++中胀葱,mutable也是為了突破const的限制而設置的漠秋。被mutable修飾的變量,將永遠處于可變的狀態(tài)抵屿,即使在一個const函數(shù)中庆锦。我們知道,如果類的成員函數(shù)不會改變對象的狀態(tài)轧葛,那么這個成員函數(shù)一般會聲明成const的搂抒。但是,有些時候朝群,我們需要在const函數(shù)里面修改一些跟類狀態(tài)無關的數(shù)據(jù)成員燕耿,那么這個函數(shù)就應該被mutable來修飾,并且放在函數(shù)后后面關鍵字位置姜胖。
(3)explicit
explicit關鍵字用來修飾類的構造函數(shù)誉帅,被修飾的構造函數(shù)的類,不能發(fā)生相應的隱式類型轉換右莱,只能以顯示的方式進行類型轉換蚜锨,注意以下幾點
explicit 關鍵字只能用于類內部的構造函數(shù)聲明上
explicit 關鍵字作用于單個參數(shù)的構造函數(shù)
被explicit修飾的構造函數(shù)的類,不能發(fā)生相應的隱式類型轉換
45慢蜓、什么情況下會調用拷貝構造函數(shù)
用類的一個實例化對象去初始化另一個對象的時候
函數(shù)的參數(shù)是類的對象時(非引用傳遞)
函數(shù)的返回值是函數(shù)體內局部對象的類的對象時 ,此時雖然發(fā)生(Named return Value優(yōu)化)NRV優(yōu)化亚再,但是由于返回方式是值傳遞,所以會在返回值的地方調用拷貝構造函數(shù)
另:第三種情況在Linux g++ 下則不會發(fā)生拷貝構造函數(shù)晨抡,不僅如此即使返回局部對象的引用氛悬,依然不會發(fā)生拷貝構造函數(shù)
總結就是:即使發(fā)生NRV優(yōu)化的情況下则剃,Linux+ g++的環(huán)境是不管值返回方式還是引用方式返回的方式都不會發(fā)生拷貝構造函數(shù),而Windows + VS2019在值返回的情況下發(fā)生拷貝構造函數(shù)如捅,引用返回方式則不發(fā)生拷貝構造函數(shù)棍现。
在c++編譯器發(fā)生NRV優(yōu)化,如果是引用返回的形式則不會調用拷貝構造函數(shù)镜遣,如果是值傳遞的方式依然會發(fā)生拷貝構造函數(shù)己肮。
在VS2019下進行下述實驗:
舉個例子:
class A
{
public:
A() {};
A(const A& a)
{
cout << "copy constructor is called" << endl;
};
~A() {};
};
void useClassA(A a) {}
A getClassA()//此時會發(fā)生拷貝構造函數(shù)的調用,雖然發(fā)生NRV優(yōu)化悲关,但是依然調用拷貝構造函數(shù)
{
A a;
return a;
}
//A& getClassA2()// VS2019下谎僻,此時編輯器會進行(Named return Value優(yōu)化)NRV優(yōu)化,不調用拷貝構造函數(shù) ,如果是引用傳遞的方式返回當前函數(shù)體內生成的對象時寓辱,并不發(fā)生拷貝構造函數(shù)的調用
//{
// A a;
// return a;
//}
int main()
{
A a1, a2,a3,a4;
A a2 = a1; //調用拷貝構造函數(shù),對應情況1
useClassA(a1);//調用拷貝構造函數(shù)艘绍,對應情況2
a3 = getClassA();//發(fā)生NRV優(yōu)化,但是值返回讶舰,依然會有拷貝構造函數(shù)的調用 情況3
a4 = getClassA2(a1);//發(fā)生NRV優(yōu)化鞍盗,且引用返回自身需了,不會調用
return 0;
}
情況1比較好理解
情況2的實現(xiàn)過程是跳昼,調用函數(shù)時先根據(jù)傳入的實參產生臨時對象,再用拷貝構造去初始化這個臨時對象肋乍,在函數(shù)中與形參對應鹅颊,函數(shù)調用結束后析構臨時對象
情況3在執(zhí)行return時,理論的執(zhí)行過程是:產生臨時對象墓造,調用拷貝構造函數(shù)把返回對象拷貝給臨時對象堪伍,函數(shù)執(zhí)行完先析構局部變量,再析構臨時對象觅闽, 依然會調用拷貝構造函數(shù)
《C++拷貝構造函數(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í)行結果: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;
}
//運行結果:alloc failed
(3)placement new
這種new允許在一塊已經分配成功的內存上重新構造對象或對象數(shù)組。placement new不用擔心內存分配失敗轴脐,因為它根本不分配內存调卑,它做的唯一一件事情就是調用對象的構造函數(shù)抡砂。定義如下:
void* operator new(size_t,void);
void operator delete(void,void*);
使用placement new需要注意兩點:
palcement new的主要用途就是反復使用一塊較大的動態(tài)分配的內存來構造不同類型的對象或者他們的數(shù)組
placement new構造起來的對象數(shù)組,要顯式的調用他們的析構函數(shù)來銷毀(析構函數(shù)并不釋放對象的內存)恬涧,千萬不要使用delete舀患,這是因為placement new構造起來的對象或數(shù)組大小并不一定等于原來分配的內存大小,使用delete會造成內存泄漏或者之后釋放內存時出現(xià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:不必擔心失敗聊浅,只要p所指對象的的空間足夠ADT創(chuàng)建即可
//delete q;//錯誤!不能在此處調用delete q;
q->ADT::~ADT();//顯示調用析構函數(shù)
delete[] p;
return 0;
}
//輸出結果:
//ADT construct i=10j=100
//ADT destruct
《【C++】幾種類型的new介紹》:http://www.reibang.com/p/9b57e769c3cb
47、C++中NULL和nullptr區(qū)別
算是為了與C語言進行兼容而定義的一個問題吧
NULL來自C語言现使,一般由宏定義實現(xiàn)低匙,而 nullptr 則是C++11的新增關鍵字。在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;
}
//輸出結果:int
那么在傳入NULL參數(shù)時贸人,會把NULL當做整數(shù)0來看间景,如果我們想調用參數(shù)是指針的函數(shù),該怎么辦呢?艺智。nullptr在C++11被引入用于解決這一問題倘要,nullptr可以明確區(qū)分整型和指針類型,能夠根據(jù)環(huá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 = {};
以上通過模板類和運算符重載的方式來對不同類型的指針進行實例化從而解決了(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;
}
//運行結果:
//語句1:char* p
//語句2:報錯捧杉,有多個匹配
//3:int p
在這種情況下存在對不同指針類型的函數(shù)重載,此時如果傳入nullptr指針則仍然存在無法區(qū)分應實際調用哪個函數(shù)埠胖,這種情況下必須顯示的指明參數(shù)類型糠溜。
《NULL和nullptr區(qū)別》:https://blog.csdn.net/qq_39380590/article/details/82563571
48、簡要說明C++的內存分區(qū)
C++中的內存分區(qū)直撤,分別是堆非竿、棧、自由存儲區(qū)谋竖、全局/靜態(tài)存儲區(qū)红柱、常量存儲區(qū)和代碼區(qū)承匣。如下圖所示
棧:在執(zhí)行函數(shù)時,函數(shù)內局部變量的存儲單元都可以在棧上創(chuàng)建锤悄,函數(shù)執(zhí)行結束時這些存儲單元自動被釋放韧骗。棧內存分配運算內置于處理器的指令集中,效率很高零聚,但是分配的內存容量有限
堆:就是那些由 new分配的內存塊袍暴,他們的釋放編譯器不去管,由我們的應用程序去控制隶症,一般一個new就要對應一個 delete政模。如果程序員沒有釋放掉,那么在程序結束后蚂会,操作系統(tǒng)會自動回收
自由存儲區(qū):就是那些由malloc等分配的內存塊淋样,它和堆是十分相似的,不過它是用free來結束自己的生命的
全局/靜態(tài)存儲區(qū):全局變量和靜態(tài)變量被分配到同一塊內存中胁住,在以前的C語言中趁猴,全局變量和靜態(tài)變量又分為初始化的和未初始化的,在C++里面沒有這個區(qū)分了彪见,它們共同占用同一塊內存區(qū)儡司,在該區(qū)定義的變量若沒有初始化,則會被自動初始化企巢,例如int型變量自動初始為0
常量存儲區(qū):這是一塊比較特殊的存儲區(qū)枫慷,這里面存放的是常量,不允許修改
代碼區(qū):存放函數(shù)體的二進制代碼
《C/C++內存管理詳解》:https://chenqx.github.io/2014/09/25/Cpp-Memory-Management/
49浪规、C++的異常處理的方法
在程序執(zhí)行過程中,由于程序員的疏忽或是系統(tǒng)資源緊張等因素都有可能導致異常探孝,任何程序都無法保證絕對的穩(wěn)定笋婿,常見的異常有:
數(shù)組下標越界
除法計算時除數(shù)為0
動態(tài)分配空間時空間不足
…
如果不及時對這些異常進行處理,程序多數(shù)情況下都會崩潰顿颅。
(1)try缸濒、throw和catch關鍵字
C++中的異常處理機制主要使用try、throw和catch三個關鍵字粱腻,其在程序中的用法如下:
#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;
}
//運行結果
//before dividing.
//catch (...)
//finished
代碼中庇配,對兩個數(shù)進行除法計算,其中除數(shù)為0绍些±袒牛可以看到以上三個關鍵字,程序的執(zhí)行流程是先執(zhí)行try包裹的語句塊柬批,如果執(zhí)行過程中沒有異常發(fā)生啸澡,則不會進入任何catch包裹的語句塊袖订。如果發(fā)生異常,則使用throw進行異常拋出嗅虏,再由catch進行捕獲洛姑,throw可以拋出各種數(shù)據(jù)類型的信息,代碼中使用的是數(shù)字皮服,也可以自定義異常class楞艾。
catch根據(jù)throw拋出的數(shù)據(jù)類型進行精確捕獲(不會出現(xiàn)類型轉換),如果匹配不到就直接報錯龄广,可以使用catch(…)的方式捕獲任何異常(不推薦)产徊。
當然,如果catch了異常蜀细,當前函數(shù)如果不進行處理舟铜,或者已經處理了想通知上一層的調用者,可以在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++標準異常類 exception
C++ 標準庫中有一些類代表異常番舆,這些類都是從 exception 類派生而來的,如下圖所示
圖片
bad_typeid:使用typeid運算符矾踱,如果其操作數(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;
}
//運行結果:bject is NULL
bad_cast:在用 dynamic_cast 進行從多態(tài)基類對象(或引用)到派生類的引用的強制類型轉換時禾怠,如果轉換是不安全的,則會拋出此異常
bad_alloc:在用 new 運算符進行動態(tài)內存分配時贝搁,如果沒有足夠的內存吗氏,則會引發(fā)此異常
out_of_range:用 vector 或 string的at 成員函數(shù)根據(jù)下標訪問元素時,如果下標越界雷逆,則會拋出此異常