程序员面试宝典
第一章 類
1.1 為什么構(gòu)造函數(shù)不可以是虛函數(shù)
①從存儲空間角度
? ? 虛函數(shù)對應(yīng)一個vtable(虛函數(shù)表),這大家都知道,可是這個vtable其實(shí)是存儲在對象的內(nèi)存空間的。問題出來了,如果構(gòu)造函數(shù)是虛的,就需要通過 vtable來調(diào)用,可是對象還沒有實(shí)例化,也就是內(nèi)存空間還沒有,無法找到vtable,所以構(gòu)造函數(shù)不能是虛函數(shù)。
②從使用角度
? ? ? ? 虛函數(shù)主要用于在信息不全的情況下,能使重載的函數(shù)得到對應(yīng)的調(diào)用。構(gòu)造函數(shù)本身就是要初始化實(shí)例,那使用虛函數(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ù)。
③構(gòu)造函數(shù)不需要是虛函數(shù),也不允許是虛函數(shù),因?yàn)閯?chuàng)建一個對象時我們總是要明確指定對象的類型,盡管我們可能通過實(shí)驗(yàn)室的基類的指針或引用去訪問它。但析構(gòu)卻不一定,我們往往通過基類的指針來銷毀對象。這時候如果析構(gòu)函數(shù)不是虛函數(shù),就不能正確識別對象類型從而不能正確調(diào)用析構(gòu)函數(shù)。
④從實(shí)現(xiàn)上看,vbtl在構(gòu)造函數(shù)調(diào)用后才建立,因而構(gòu)造函數(shù)不可能成為虛函數(shù) ?
? 從實(shí)際含義上看,在調(diào)用構(gòu)造函數(shù)時還不能確定對象的真實(shí)類型(因?yàn)樽宇悤{(diào)父類的構(gòu)造函數(shù));而且構(gòu)造函數(shù)的作用是提供初始化,在對象生命期只執(zhí)行一次,不是對象的動態(tài)行為,也沒有太大的必要成為虛函數(shù)
⑤當(dāng)一個構(gòu)造函數(shù)被調(diào)用時,它做的首要的事情之一是初始化它的V P T R。因此,它只能知道它是“當(dāng)前”類的,而完全忽視這個對象后面是否還有繼承者。當(dāng)編譯器為這個構(gòu)造函數(shù)產(chǎn)生代碼時,它是為這個類的構(gòu)造函數(shù)產(chǎn)生代碼- -既不是為基類,也不是為它的派生類(因?yàn)轭惒恢勒l繼承它)。
? ? ? ? 所以它使用的V P T R必須是對于這個類的V TA B L E。而且,只要它是最后的構(gòu)造函數(shù)調(diào)用,那么在這個對象的生命期內(nèi), V P T R將保持被初始化為指向這個V TA B L E, 但如果接著還有一個更晚派生的構(gòu)造函數(shù)被調(diào)用,這個構(gòu)造函數(shù)又將設(shè)置V P T R指向它的 V TA B L E,等.直到最后的構(gòu)造函數(shù)結(jié)束。V P T R的狀態(tài)是由被最后調(diào)用的構(gòu)造函數(shù)確定的。這就是為什么構(gòu)造函數(shù)調(diào)用是從基類到更加派生 類順序的另一個理由。
? ? ? ? 但是,當(dāng)這一系列構(gòu)造函數(shù)調(diào)用正發(fā)生時,每個構(gòu)造函數(shù)都已經(jīng)設(shè)置V P T R指向它自己的 V TA B L E。如果函數(shù)調(diào)用使用虛機(jī)制,它將只產(chǎn)生通過它自己的V TA B L E的調(diào)用,而不是最后的V TA B L E(所有構(gòu)造函數(shù)被調(diào)用后才會有最后的V TA B L E)。
1.2、為什么析構(gòu)函數(shù)可以是虛函數(shù)
? ? ? 編譯器總是根據(jù)類型來調(diào)用類成員函數(shù)。但是一個派生類的指針可以安全地轉(zhuǎn)化為一個基類的指針。這樣刪除一個基類的指針的時候,C++不管這個指針指向一個基類對象還是一個派生類的對象,調(diào)用的都是基類的析構(gòu)函數(shù)而不是派生類的。如果你依賴于派生類的析構(gòu)函數(shù)的代碼來釋放資源,而沒有重載析構(gòu)函數(shù),那么會有資源泄漏。
? ? ? 所以建議的方式是將析構(gòu)函數(shù)聲明為虛函數(shù)。如果你使用MFC,并且以CObject或其派生類為基類,那么MFC已經(jīng)為你做了這件事情;CObject的析構(gòu)函數(shù)是虛函數(shù)。一個函數(shù)一旦聲明為虛函數(shù),那么不管你是否加上virtual 修飾符,它在所有派生類中都成為虛函數(shù)。但是由于理解明確起見,建議的方式還是加上virtual 修飾符。
? ? ? C++不把虛析構(gòu)函數(shù)直接作為默認(rèn)值的原因是虛函數(shù)表的開銷以及和C語言的類型的兼容性。有虛函數(shù)的對象總是在開始的位置包含一個隱含的虛函數(shù)表指針成員。如果是對于MFC類CPoint和CSize這樣的小型類,增加一個指針就增加了很多內(nèi)存占用,而且使得其內(nèi)存表示和基類POINT和SIZE不一致。
2.虛函數(shù)表
? ? ?請移步:https://www.cnblogs.com/LUO77/p/5771237.html
3.動態(tài)綁定與靜態(tài)綁定
- 靜態(tài)綁定發(fā)生在編譯期,動態(tài)綁定發(fā)生在運(yùn)行期;
- 對象的動態(tài)類型可以更改,但是靜態(tài)類型無法更改;
- 要想實(shí)現(xiàn)動態(tài),必須使用動態(tài)綁定;
- 在繼承體系中只有虛函數(shù)使用的是動態(tài)綁定,其他的全部是靜態(tài)綁定;
- 靜態(tài)多態(tài)是指通過模板技術(shù)或者函數(shù)重載技術(shù)實(shí)現(xiàn)的多態(tài),其在編譯器確定行為。動態(tài)多態(tài)是指通過虛函數(shù)技術(shù)實(shí)現(xiàn)在運(yùn)行期動態(tài)綁定的技術(shù)
動態(tài)綁定:有一個基類,兩個派生類,基類有一個virtual函數(shù),兩個派生類都覆蓋了這個虛函數(shù)。現(xiàn)在有一個基類的指針或者引用,當(dāng)該基類指針或者引用指向不同的派生類對象時,調(diào)用該虛函數(shù),那么最終調(diào)用的是該被指向?qū)ο髮?yīng)的派生類自己實(shí)現(xiàn)的虛函數(shù)。
4.虛函數(shù)表是針對類的還是針對對象的?同一個類的兩個對象的虛函數(shù)表是怎么維護(hù)的?
編譯器為每一個類維護(hù)一個虛函數(shù)表(本質(zhì)是一個函數(shù)指針數(shù)組,數(shù)組里面存放了一系列函數(shù)地址 ),每個對象的首地址保存著該虛函數(shù)表的指針,同一個類的不同對象實(shí)際上指向同一張?zhí)摵瘮?shù)表。調(diào)用形式:*(this指針+調(diào)整量)[虛函數(shù)在vftable內(nèi)的偏移]()
在類內(nèi)部添加一個虛擬函數(shù)表指針,該指針指向一個虛擬函數(shù)表,該虛擬函數(shù)表包含了所有的虛擬函數(shù)的入口地址,每個類的虛擬函數(shù)表都不一樣,在運(yùn)行階段可以循此脈絡(luò)找到自己的函數(shù)入口。純虛函數(shù)相當(dāng)于占位符, 先在虛函數(shù)表中占一個位置由派生類實(shí)現(xiàn)后再把真正的函數(shù)指針填進(jìn)去。除此之外和普通的虛函數(shù)沒什么區(qū)別。
在單繼承形式下,子類的完全獲得父類的虛函數(shù)表和數(shù)據(jù)。子類如果重寫了父類的虛函數(shù)(如fun),就會把虛函數(shù)表原本fun對應(yīng)的記錄(內(nèi)容MyClass::fun)覆蓋為新的函數(shù)地址(內(nèi)容MyClassA::fun),否則繼續(xù)保持原本的函數(shù)地址記錄。
使用這種方式,就可以實(shí)現(xiàn)多態(tài)的特性。假設(shè)我們使用如下語句:
MyClass*pc= new MyClassA;
pc->fun();??
因?yàn)樘摵瘮?shù)表內(nèi)的函數(shù)地址已經(jīng)被子類重寫的fun函數(shù)地址覆蓋了,因此該處調(diào)用的函數(shù)正是MyClassA::fun,而不是基類的MyClass::fun。
如果使用MyClassA對象直接訪問fun,則不會出發(fā)多態(tài)機(jī)制,因?yàn)檫@個函數(shù)調(diào)用在編譯時期是可以確定的,編譯器只需要直接調(diào)用MyClassA::fun即可。
注:對象不包含虛函數(shù)表,只有虛指針,類才包含虛函數(shù)表,派生類會生成一個兼容基類的虛函數(shù)表
詳情可以參考:http://www.cnblogs.com/fanzhidongyzby/archive/2013/01/14/2859064.html
第二章 其他
2.?extern關(guān)鍵字的作用
? ? ?2.1 概述
? ? ? ?extern置于變量或函數(shù)前,用于標(biāo)示變量或函數(shù)的定義在別的文件中,提示編譯器遇到此變量和函數(shù)時在其他模塊中尋找其定義。它只要有兩個作用:
- 當(dāng)它與“C”一起連用的時候,如:extern "C" void fun(int a,int b);則告訴編譯器在編譯fun這個函數(shù)時候按著C的規(guī)矩去翻譯,而不是C++的(這與C++的重載有關(guān),C++語言支持函數(shù)重載,C語言不支持函數(shù)重載,函數(shù)被C++編譯器編譯后在庫中的名字與C語言的不同) #ifdef __cplusplus extern "C" { #endif/*...*/#ifdef __cplusplus } #endif
- 當(dāng)extern不與“C”在一起修飾變量或函數(shù)時,如:extern int g_Int;它的作用就是聲明函數(shù)或全局變量的作用范圍的關(guān)鍵字,其聲明的函數(shù)和變量可以在本模塊或其他模塊中使用。記住它是一個聲明不是定義!也就是說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數(shù)時,它只要包含A模塊的頭文件即可,在編譯階段,模塊B雖然找不到該函數(shù)或變量,但它不會報錯,它會在連接時從模塊A生成的目標(biāo)代碼中找到此函數(shù)。
? ?2.2 .為什么不將全局變量的定義放在頭文件中
? ? ? ?首先要說明什么是全局變量,c語言中全局變量一般是指定義在函數(shù)體外的變量。全局變量按可訪問性可分為外部變量和內(nèi)部變量。
? ? ? 內(nèi)部變量是指?使用了static關(guān)鍵字修飾的全局變量,它的可訪問范圍(作用域)被限定在本源文件所在的鏈接文件模塊中,不能被其它文件模塊引用。反之沒有被static關(guān)鍵字修飾的全局變量則是外部變量,其它文件模塊可以通過extern關(guān)鍵字引用該全局變量并訪問。
要說明的是全局變量?無論是內(nèi)部變量還是外部變量,的存儲類別都是靜態(tài)的,也就是放到靜態(tài)內(nèi)存區(qū)域中,它編譯鏈接階段就已經(jīng)分配好了固定的內(nèi)存。
搞清楚上面的內(nèi)容,就很容易得出若把全局變量放在頭文件會有哪些問題;
一 對內(nèi)部變量來說,每個include該頭文件的文件模塊中都會單獨(dú)為這個內(nèi)部變量分配靜態(tài)內(nèi)存空間,這個空間是相對獨(dú)立的,是一種空間浪費(fèi),同時還失去了全局變量訪問一致性的特點(diǎn),實(shí)在沒有什么意義。如果這個頭文件只被一個模塊使用,對于這個文件模塊來說應(yīng)該沒啥問題。
二 對外部變量來講,這個頭文件被多個文件模塊include的情況下,鏈接過程會報錯,因?yàn)榉枦_突,所有include這個頭文件的模塊都會有這個全局符號。在這個頭文件僅僅只被一個模塊include的時候可以正常使用。
經(jīng)上分析得出要避免全局變量定義在頭文件中,因?yàn)楫?dāng)這個頭文件被多方include的時候會產(chǎn)生一些不必要的麻煩,就這么多。
全局變量作用域范圍較廣,被錯誤修改后排查定位問題比較困難,若非必要盡少使用。
下面說一下比較好的方式就是全局變量只定義在實(shí)現(xiàn)文件(.c,.m)中,對內(nèi)部變量沒啥說的它只在文件模塊內(nèi)部使用,對外部變量可以在該模塊頭文件中使用extern關(guān)鍵字修飾一下,這樣其它文件模塊只要直接include該頭文件就可以使用模塊中的外部變量了。
2.2.static關(guān)鍵字的作用
- ? 修飾局部變量
? ??static修飾局部變量時,使得被修飾的變量成為靜態(tài)變量,存儲在靜態(tài)區(qū)。存儲在靜態(tài)區(qū)的數(shù)據(jù)生命周期與程序相同,在main函數(shù)之前初始化,在程序退出時銷毀。(無論是局部靜態(tài)還是全局靜態(tài))
- ? 修飾全局變量
? ? 全局變量本來就存儲在靜態(tài)區(qū),因此static并不能改變其存儲位置。但是,static限制了其鏈接屬性。被static修飾的全局變量只能被該包含該定義的文件訪問(即改變了作用域)。
- ? 修飾函數(shù)
? ? ?static修飾函數(shù)使得函數(shù)只能在包含該函數(shù)定義的文件中被調(diào)用。對于靜態(tài)函數(shù),聲明和定義需要放在同一個文件夾中。
- ? 修飾成員變量
? ? ??用static修飾類的數(shù)據(jù)成員使其成為類的全局變量,會被類的所有對象共享,包括派生類的對象,所有的對象都只維持同一個實(shí)例。?因此,static成員必須在類外進(jìn)行初始化(初始化格式:int base::var=10;),而不能在構(gòu)造函數(shù)內(nèi)進(jìn)行初始化,不過也可以用const修飾static數(shù)據(jù)成員在類內(nèi)初始化。
- ? 修飾成員函數(shù)
? ? ?用static修飾成員函數(shù),使這個類只存在這一份函數(shù),所有對象共享該函數(shù),不含this指針,因而只能訪問類的static成員變量。靜態(tài)成員是可以獨(dú)立訪問的,也就是說,無須創(chuàng)建任何對象實(shí)例就可以訪問。例如可以封裝某些算法,比如數(shù)學(xué)函數(shù),如ln,sin,tan等等,這些函數(shù)本就沒必要屬于任何一個對象,所以從類上調(diào)用感覺更好,比如定義一個數(shù)學(xué)函數(shù)類Math,調(diào)用Math::sin(3.14);還可以實(shí)現(xiàn)某些特殊的設(shè)計模式:如Singleton;
- 最重要的特性:隱藏
? ? 當(dāng)同時編譯多個文件時,所有未加static前綴的全局變量和函數(shù)都具有全局可見性,其它的源文件也能訪問。利用這一特性可以在不同的文件中定義同名函數(shù)和同名變量,而不必?fù)?dān)心命名沖突。static可以用作函數(shù)和變量的前綴,對于函數(shù)來講,static的作用僅限于隱藏。
不可以同時用const和static修飾成員函數(shù)。
? ?C++編譯器在實(shí)現(xiàn)const的成員函數(shù)的時候?yàn)榱舜_保該函數(shù)不能修改類的實(shí)例的狀態(tài),會在函數(shù)中添加一個隱式的參數(shù)const this*。但當(dāng)一個成員為static的時候,該函數(shù)是沒有this指針的。也就是說此時const的用法和static是沖突的。我們也可以這樣理解:兩者的語意是矛盾的。static的作用是表示該函數(shù)只作用在類型的靜態(tài)變量上,與類的實(shí)例沒有關(guān)系;而const的作用是確保函數(shù)不能修改類的實(shí)例的狀態(tài),與類型的靜態(tài)變量沒有關(guān)系。因此不能同時用它們。
2.3.const的作用
- 定義變量為只讀變量,不可修改
- 修飾函數(shù)的參數(shù)和返回值(后者應(yīng)用比較少,一般為值傳遞)
- const成員函數(shù)(只需要在成員函數(shù)參數(shù)列表后加上關(guān)鍵字const,如char?get()?const;)可以訪問const成員變量和非const成員變量,但不能修改任何變量。在聲明一個成員函數(shù)時,若該成員函數(shù)并不對數(shù)據(jù)成員進(jìn)行修改操作,應(yīng)盡可能將該成員函數(shù)聲明為const成員函數(shù)。
-
const對象只能訪問const成員函數(shù),而非const對象可以訪問任意的成員函數(shù),包括const成員函數(shù).即對于class A,有const A a;那么a只能訪問A的const成員函數(shù)。而對于:A b;b可以訪問任何成員函數(shù)。
使用const關(guān)鍵字修飾的變量,一定要對變量進(jìn)行初始化
2.4.指針與引用的區(qū)別
- 指針只是一個變量,只不過這個變量存儲的是一個地址;而引用跟原來的變量實(shí)質(zhì)上是同一個東西,只不過是原變量的一個別名而已,不占用內(nèi)存空間。
- 引用必須在定義的時候初始化,而且初始化后就不能再改變;而指針不必在定義的時候初始化,初始化后可以改變。
- 指針可以為空,但引用不能為空(這就意味著我們拿到一個引用的時候,是不需要判斷引用是否為空的,而拿到一個指針的時候,我們則需要判斷它是否為空。這點(diǎn)經(jīng)常在判斷函數(shù)參數(shù)是否有效的時候使用。)
- “sizeof 引用" = 指向變量的大小 , "sizeof 指針"= 指針本身的大小
- 指針可以有多級,而引用只能是一級
2.5.new與malloc的區(qū)別
- malloc與free是C++/C語言的標(biāo)準(zhǔn)庫函數(shù),new/delete是C++的運(yùn)算符。它們都可用于申請動態(tài)內(nèi)存和釋放內(nèi)存。
- 對于非內(nèi)部數(shù)據(jù)類型的對象而言,光用malloc/free無法滿足動態(tài)對象的要求。對象在創(chuàng)建的同時要自動執(zhí)行構(gòu)造函數(shù),對象在消亡之前要自動執(zhí)行析構(gòu)函數(shù)。
- new可以認(rèn)為是malloc加構(gòu)造函數(shù)的執(zhí)行。new出來的指針是直接帶類型信息的。而malloc返回的都是void指針。
2.6.智能指針怎么實(shí)現(xiàn)?什么時候改變引用計數(shù)?
- 構(gòu)造函數(shù)中計數(shù)初始化為1;
- 拷貝構(gòu)造函數(shù)中計數(shù)值加1;
- 賦值運(yùn)算符中,左邊的對象引用計數(shù)減一,右邊的對象引用計數(shù)加一;
- 析構(gòu)函數(shù)中引用計數(shù)減一;
- 在賦值運(yùn)算符和析構(gòu)函數(shù)中,如果減一后為0,則調(diào)用delete釋放對象。
2.7.內(nèi)聯(lián)函數(shù),宏定義和普通函數(shù)的區(qū)別
- 內(nèi)聯(lián)函數(shù)要做參數(shù)類型檢查,這是內(nèi)聯(lián)函數(shù)跟宏相比的優(yōu)勢
- 宏定義是在預(yù)編譯的時候把所有的宏名用宏體來替換,簡單的說就是字符串替換,?內(nèi)聯(lián)函數(shù)則是在編譯的時候進(jìn)行代碼插入,編譯器會在每處調(diào)用內(nèi)聯(lián)函數(shù)的地方直接把內(nèi)聯(lián)函數(shù)的內(nèi)容展開,這樣可以省去函數(shù)的調(diào)用的壓棧出棧的開銷,提高效率。
- 內(nèi)聯(lián)函數(shù)是指嵌入代碼,就是在調(diào)用函數(shù)的地方不是跳轉(zhuǎn),而是把代碼直接寫到那里去。對于短小簡單的代碼來說,內(nèi)聯(lián)函數(shù)可以帶來一定的效率提升,而且和C時代的宏函數(shù)相比,內(nèi)聯(lián)函數(shù)?更安全可靠。可是這個是以增加空間消耗為代價的
- const與#define的區(qū)別:宏在預(yù)處理階段替換,const在編譯階段替換;宏沒有類型,不做安全檢查,const有類型,在編譯階段進(jìn)行安全檢查
2.8. C++內(nèi)存管理
棧:? 存放函數(shù)參數(shù)以及局部變量 , 在出作用域時 , 將自動被釋放 . 棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中 , 效率 很 高 , 但分配的內(nèi)存容量有限 .
堆?:new 分配的內(nèi)存塊 ( 包括數(shù)組 , 類實(shí)例等 ), 需 delete 手動釋放 . 如果未釋放 , 在整個程序結(jié)束后 ,OS 會幫你回收掉 .
自由存儲區(qū):?malloc 分配的內(nèi)存塊 , 需 free 手動釋放 . 它和堆有些相似 .
全局/靜態(tài)區(qū):?保存自動全局變量和static變量(包括static全局和局部變量)。靜態(tài)區(qū)的內(nèi)容在整個程序的生命周期內(nèi)都存在,有編譯器在編譯的時候分配(數(shù)據(jù)段(存儲全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù))和代碼段(可執(zhí)行的代碼/只讀常量))。
常量存儲區(qū):?常量 (const) 存于此處 , 此存儲區(qū)不可修改 .
棧與堆的區(qū)別:?? ? ? ?
管理方式不同:?棧是編譯器自動管理的,堆需手動釋放
空間大小不同:?在32位OS下,堆內(nèi)存可達(dá)到4GB的的空間,而棧就小得可憐.(VC6中,棧默認(rèn)大小是1M,當(dāng)然,你可以修改它)
能否產(chǎn)生碎片不同:對于棧來說,進(jìn)棧/出棧都有著嚴(yán)格的順序(先進(jìn)后出),不會產(chǎn)生碎片;而堆頻繁的new/delete,會造成內(nèi)存空間的不連續(xù),容易產(chǎn)生碎片.
生長方向不同:棧向下生長,以降序分配內(nèi)存地址;堆向上生長,以升序分配內(nèi)在地址.
分配方式不同:堆動態(tài)分配,無靜態(tài)分配;棧分為靜態(tài)分配和動態(tài)分配,比如局部變量的分配,就是動態(tài)分配(alloca函數(shù)),函數(shù)參數(shù)的分配就是動態(tài)分配(我想的…).
分配效率不同:棧是系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計算機(jī)會在底層對棧提供支持,進(jìn)棧/出棧都有專門的指令,這就決定了棧的效率比較高.堆則不然,它由C/C++函數(shù)庫提供,機(jī)制復(fù)雜,堆的效率要比棧低得多.
可以看出,棧的效率要比堆高很多,所以,推薦大家盡量用棧.不過,雖然棧有如此多的好處,但遠(yuǎn)沒有堆使用靈活.
第四章 實(shí)戰(zhàn)
4.1 .手寫strcpy,memcpy,strcat,strcmp等函數(shù)
https://blog.csdn.net/gao1440156051/article/details/51496782
https://blog.csdn.net/wilsonboliu/article/details/7919773
16.i++是否為原子操作?
不是。操作系統(tǒng)原子操作是不可分割的,在執(zhí)行完畢不會被任何其它任務(wù)或事件中斷,分為兩種情況(兩種都應(yīng)該滿足)
?(1) 在單線程中, 能夠在單條指令中完成的操作都可以認(rèn)為是" 原子操作",因?yàn)橹袛嘀荒馨l(fā)生于指令之間。
?(2) 在多線程中,不能被其它進(jìn)程(線程)打斷的操作就叫原子操作。
i++分為三個階段:
內(nèi)存到寄存器
寄存器自增
寫回內(nèi)存
這三個階段中間都可以被中斷分離開.
17.有關(guān)數(shù)組,指針,函數(shù)的三者結(jié)合問題
數(shù)組指針和指針數(shù)組的區(qū)別:https://blog.csdn.net/men_wen/article/details/52694069
右左法則的說明:http://www.cnblogs.com/zhangjing0502/archive/2012/06/08/2542059.html
指針常量和常量指針:https://www.zhihu.com/question/19829354
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://blog.csdn.net/xingjiarong/article/details/47282563
注意:所謂指向常量的指針或引用(即常量引用、常量指針),不過是指針或引用“自以為是”罷了,它們覺得自己指向了常量,所以自覺地不去改變所指對象的值,但這些對象卻可以通過其他途徑改變。
const int *a; ?等價于int const *a; ? ?const在前面所以內(nèi)容不可以改變,但是指針指向可以改變。也就是常量指針
int *const a; ?表示的是指針指向不可改變,但是指針?biāo)娣诺膬?nèi)容可以改變,也即是指針常量
補(bǔ)充:
18.C++中類與結(jié)構(gòu)體的區(qū)別?
- 最本質(zhì)的一個區(qū)別就是默認(rèn)的訪問控制:?struct作為數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)體,它默認(rèn)的數(shù)據(jù)訪問控制是public的,而class作為對象的實(shí)現(xiàn)體,它默認(rèn)的成員變量訪問控制是private的。
- “class”這個關(guān)鍵字還用于定義模板參數(shù),就像“typename”。但關(guān)鍵字“struct”不用于定義模板參數(shù)。
19.析構(gòu)函數(shù)的作用?
析構(gòu)函數(shù)是用來釋放所定義的對象中使用的指針,默認(rèn)的析構(gòu)函數(shù)不用顯示調(diào)用,自建的析構(gòu)函數(shù)要在程序末尾調(diào)用。
如果你的類里面只用到的基本類型,如int char double等,系統(tǒng)的默認(rèn)析構(gòu)函數(shù)其實(shí)什么都沒有做
但如果你使用了其他的類如vector,string等,系統(tǒng)的默認(rèn)析構(gòu)函數(shù)就會調(diào)用這些類對象的析構(gòu)函數(shù)
如果是自己寫析構(gòu)函數(shù)的話,如果你的類里面分配了系統(tǒng)資源,如new了內(nèi)存空間,打開了文件等,那么在你的析構(gòu)函數(shù)中就必須釋放相應(yīng)的內(nèi)存空間和關(guān)閉相關(guān)的文件;這樣系統(tǒng)就會自動調(diào)用你的析構(gòu)函數(shù)釋放資源,避免內(nèi)存泄漏
例如:
class A
{
private:
char *data;
public:
A()
{
data = new char[ 10];
}
~A()
{
delete[] data;
}
};
- 1
A? a;
a?中將?new?10個?char
當(dāng)?a?這個變量消亡的時候,將自動執(zhí)行?~A(),釋放空間
對象消亡時,自動被調(diào)用,用來釋放對象占用的空間,避免內(nèi)存泄漏
20.虛函數(shù)的作用?
虛函數(shù)可以讓成員函數(shù)操作一般化,用基類的指針指向不同的派生類的對象時,基類指針調(diào)用其虛成員函數(shù),則會調(diào)用其真正指向?qū)ο蟮某蓡T函數(shù),而不是基類中定義的成員函數(shù)(只要派生類改寫了該成員函數(shù))。若不是虛函數(shù),則不管基類指針指向的哪個派生類對象,調(diào)用時都會調(diào)用基類中定義的那個函數(shù)。虛函數(shù)是C++多態(tài)的一種表現(xiàn),可以進(jìn)行靈活的動態(tài)綁定。? ?? 重點(diǎn)可參考:https://www.cnblogs.com/wangxiaobao/p/5850949.html
? ? ? ? ? ? ? ? ? ? ?http://www.cnblogs.com/fanzhidongyzby/archive/2013/01/14/2859064.html
21.操作系統(tǒng)和編譯器如何區(qū)分全局變量和局部變量?
? ? ? ?操作系統(tǒng)只管調(diào)度進(jìn)程,編譯器通過內(nèi)存分配的位置來知道的,全局變量分配在全局?jǐn)?shù)據(jù)段并且在程序開始運(yùn)行的時候被加載。局部變量則分配在棧里面 。
22. Makefile文件的作用?
? ?makefile關(guān)系到了整個工程的編譯規(guī)則。一個工程中的源文件不計數(shù),其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規(guī)則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進(jìn)行更復(fù)雜的功能操作,因?yàn)閙akefile就像一個Shell腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。
23.結(jié)構(gòu)體和聯(lián)合體的區(qū)別?
? ? ? 結(jié)構(gòu)和聯(lián)合都是由多個不同的數(shù)據(jù)類型成員組成,?但在任何同一時刻,?聯(lián)合中只存放了一個被選中的成員(所有成員共用一塊地址空間),?而結(jié)構(gòu)的所有成員都存在(不同成員的存放地址不同)。?
? ? ?對于聯(lián)合的不同成員賦值,?將會對其它成員重寫,?原來成員的值就不存在了,?而對于結(jié)構(gòu)的不同成員賦值是互不影響的。
24.列表初始化問題?
? ? ??使用初始化列表主要是基于性能問題,對于內(nèi)置類型,如int, float等,使用初始化列表和在構(gòu)造函數(shù)體內(nèi)初始化差別不是很大;但是對于類類型來說,最好使用初始化列表。這樣就可以直接調(diào)用拷貝構(gòu)造函數(shù)初始化,省去了一次調(diào)用默認(rèn)構(gòu)造函數(shù)的過程。
struct Test1
{
Test1() // 無參構(gòu)造函數(shù)
{
cout << "Construct Test1" << endl ;
}
Test1( const Test1& t1) // 拷貝構(gòu)造函數(shù)
{
cout << "Copy constructor for Test1" << endl ;
this->a = t1.a ;
}
Test1& operator = ( const Test1& t1) // 賦值運(yùn)算符
{
cout << "assignment for Test1" << endl ;
this->a = t1.a ;
return * this;
}
int a ;
};
struct Test2 //普通初始化
{
Test1 test1 ;
Test2(Test1 &t1)
{
test1 = t1 ;
}
};
- 1
struct Test2 //2.列表初始化
{
Test1 test1 ;
Test2(Test1 &t1):test1(t1){}
}
- 1
Test1 t1 ; //調(diào)用
Test2 t2(t1) ;
- 1
普通初始化:
列表初始化:
下列情況一定要使用初始化成員列表
- 常量成員,因?yàn)槌A恐荒艹跏蓟荒苜x值,所以必須放在初始化列表里面
- 引用類型,引用必須在定義的時候初始化,并且不能重新賦值,所以也要寫在初始化列表里面
- 需要初始化的數(shù)據(jù)成員是對象的情況
? 參考地址:https://www.cnblogs.com/weizhixiang/p/6374430.html?
25. 重載與重寫的區(qū)別?
? ? 從定義上來說:重載:是指允許存在多個同名函數(shù),而這些函數(shù)的參數(shù)表不同(或許參數(shù)個數(shù)不同,或許參數(shù)類型不同,或許兩者都不同)。重寫:是指子類重新定義父類虛函數(shù)的方法。
? ?從實(shí)現(xiàn)原理上來說:重載:編譯器根據(jù)函數(shù)不同的參數(shù)表,對同名函數(shù)的名稱做修飾,然后這些同名函數(shù)就成了不同的函數(shù)。重寫:當(dāng)子類重新定義了父類的虛函數(shù)后,父類指針根據(jù)賦給它的不同的子類指針,動態(tài)的調(diào)用屬于子類的該函數(shù),這樣的函數(shù)調(diào)用在編譯期間是無法確定的(調(diào)用的子類的虛函數(shù)的地址無法給出)。
? ?補(bǔ)充:“隱藏”是指派生類的函數(shù)屏蔽了與其同名的基類函數(shù)。規(guī)則如下:?
(1)如果派生類的函數(shù)與基類的函數(shù)同名,但是參數(shù)不同。此時,不論有無virtual關(guān)鍵字,基類的函數(shù)將被隱藏(注意別與重載混淆)。?
(2)如果派生類的函數(shù)與基類的函數(shù)同名,并且參數(shù)也相同,但是基類函數(shù)沒有virtual 關(guān)鍵字。此時,基類的函數(shù)被隱藏(注意別與覆蓋混淆)。
26.類型安全以及C++中的類型轉(zhuǎn)換?
? ? ?類型安全很大程度上可以等價于內(nèi)存安全,類型安全的代碼不會試圖訪問自己沒被授權(quán)的內(nèi)存區(qū)域。C只在局部上下文中表現(xiàn)出類型安全,比如試圖從一種結(jié)構(gòu)體的指針轉(zhuǎn)換成另一種結(jié)構(gòu)體的指針時,編譯器將會報告錯誤,除非使用顯式類型轉(zhuǎn)換。然而,C中相當(dāng)多的操作是不安全的。
?詳情可以移步:https://blog.csdn.net/chengonghao/article/details/50974022
? ?四種類型轉(zhuǎn)換:
- static_cast <T*> (content)? 靜態(tài)轉(zhuǎn)換.在編譯期間處理,可以實(shí)現(xiàn)C++中內(nèi)置基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換。如果涉及到類的話,static_cast只能在有相互聯(lián)系的類型中進(jìn)行相互轉(zhuǎn)換,不一定包含虛函數(shù)。
- dynamic_cast<T*>(content) 動態(tài)類型轉(zhuǎn)換;也是向下安全轉(zhuǎn)型;是在運(yùn)行的時候執(zhí)行;基類中一定要有虛函數(shù),否則編譯不通過。在類層次間進(jìn)行上行轉(zhuǎn)換時(如派生類指針轉(zhuǎn)為基類指針),dynamic_cast和static_cast的效果是一樣的。在進(jìn)行下行轉(zhuǎn)換時(如基類指針轉(zhuǎn)為派生類指針),dynamic_cast具有類型檢查的功能,比static_cast更安全。
- const_cast<T*>(content) 去常轉(zhuǎn)換;編譯時執(zhí)行;
- reinterpret_cast<T*>(content) 重解釋類型轉(zhuǎn)換;
詳情可以移步:https://blog.csdn.net/u010025211/article/details/48626687
? ? ? ? ? ? ? ? ? ? ? ? https://blog.csdn.net/xtzmm1215/article/details/46475565
? ? ? ? ? ? ? ? ? ? ? ??https://blog.csdn.net/xingkongfenqi/article/details/49148885
27.內(nèi)存對齊的原則以及作用?
- 結(jié)構(gòu)體內(nèi)的成員按自身長度自對齊(32位機(jī)器上,如char=1,short=2,int=4,double=8),所謂自對齊是指該成員的起始地址必須是它自身長度的整數(shù)倍。如int只能以0,4,8這類地址開始。
- 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體的有效對齊值的整數(shù)倍(默認(rèn)以結(jié)構(gòu)體中最長的成員長度為有效值的整數(shù)倍,當(dāng)用#pragrma pack(n)指定時,以n和結(jié)構(gòu)體中最長的成員的長度中較小者為其值)。即sizeof的值,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補(bǔ)齊。
?例如:
class A
{
char c;
int a;
char d;
};
cout << sizeof(A) << endl;
class B
{
char c;
char d;
int a;
};
cout << sizeof(B) << endl;
- 1
sizeof(A)=12,sizeof(B)=8;
因?yàn)樽筮吺?+(3)+4+1+(3)=12,而右邊是1+1+(2)+4=8。括號中為補(bǔ)齊的字節(jié)。
內(nèi)存對齊的作用:
1、平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
2、性能原因:經(jīng)過內(nèi)存對齊后,CPU的內(nèi)存訪問速度大大提升。
詳情可以移步:https://blog.csdn.net/chy19911123/article/details/48894579
28.關(guān)鍵字registr,typdef的作用?
register關(guān)鍵字的作用:
請求CPU盡可能讓變量的值保存在CPU內(nèi)部的寄存器中,減去CPU從內(nèi)存中抓取數(shù)據(jù)的時間,提高程序運(yùn)行效率。
使用register關(guān)鍵字應(yīng)注意什么?
1.只有局部變量才可以被聲明用register修飾
(register不能修飾全局變量和函數(shù)的原因:全局變量可能被多個進(jìn)程訪問,而用register修飾的變量,只能被當(dāng)前進(jìn)程訪問)
2.不能用取地址獲取用register修飾的變量的地址(原因:變量保存在寄存器中,而取地址獲取的地址的是內(nèi)存的地址)
3. 用register修飾的變量一定要是CPU所接受的數(shù)據(jù)類型
typedef關(guān)鍵字的作用:
給數(shù)據(jù)類型定義一個新名字,
1.? 提高了移植性
2.? 簡化復(fù)雜的類型聲明,提高編碼效率
3.? 解釋數(shù)據(jù)類型的作用
29.什么情況下需要將析構(gòu)函數(shù)定義為虛函數(shù)?
? ? 當(dāng)基類指針指向派生類的對象(多態(tài)性)時。如果定義為虛函數(shù),則就會先調(diào)用該指針指向的派生類析構(gòu)函數(shù),然后派生類的析構(gòu)函數(shù)再又自動調(diào)用基類的析構(gòu)函數(shù),這樣整個派生類的對象完全被釋放。如果析構(gòu)函數(shù)不被聲明成虛函數(shù),則編譯器實(shí)施靜態(tài)綁定,在刪除基類指針時,只會調(diào)用基類的析構(gòu)函數(shù)而不調(diào)用派生類析構(gòu)函數(shù),這樣就會造成派生類對象析構(gòu)不完全。所以,將析構(gòu)函數(shù)聲明為虛函數(shù)是十分必要的。
? 詳情可以移步:https://blog.csdn.net/jiadebin890724/article/details/7951461
30.有關(guān)純虛函數(shù)的理解?
? ? ?純虛函數(shù)是為你的程序制定一種標(biāo)準(zhǔn),純虛函數(shù)只是一個接口,是個函數(shù)的聲明而已,它要留到子類里去實(shí)現(xiàn)。
class A{
protected:
void foo(); //普通類函數(shù)
virtual void foo1(); //虛函數(shù)
virtual void foo2() = 0; //純虛函數(shù)
}
- 1
? ? 帶純虛函數(shù)的類抽象類,這種類不能直接生成對象,而只有被繼承,并重寫其虛函數(shù)后,才能使用。
? ? 虛函數(shù)是為了繼承接口和默認(rèn)行為
? ? 純虛函數(shù)只是繼承接口,行為必須重新定義
? ?(在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常? 理。所以引入了純虛函數(shù)的概念)
? ? 詳情可以參考:https://blog.csdn.net/ybhjx/article/details/51788396
30.基類指針指向派生類,派生類指針指向基類?
? ? 基類指針可以指向派生類對象,從而實(shí)現(xiàn)多態(tài),例如:
#include <iostream>
using namespace std;
class Shape {
public:
virtual double area() const = 0; //純虛函數(shù)
};
class Square : public Shape {
double size;
public:
Square( double s) {
size = s;
}
virtual double area() const {
return size * size;
}
};
class Circle : public Shape {
double radius;
public:
Circle( double r) {
radius = r;
}
virtual double area() const {
return 3.14159 * radius * radius;
}
};
int main()
{
Shape* array[ 2]; //定義基類指針數(shù)組
Square Sq(2.0);
Circle Cir(1.0);
array[ 0] = &Sq;
array[ 1] =&Cir;
for ( int i = 0; i < 2; i++) /
{
cout << array[i]->area() << endl;
}
return 0;
}
- 1
? ? ?上面的不同對象Sq,Cir(來自繼承同一基類的不同派生類)接受同一消息(求面積,來自基類的成員函數(shù)area()),但是卻根據(jù)自身情況調(diào)用不同的面積公式(執(zhí)行了不同的行為,它是通過虛函數(shù)實(shí)現(xiàn)的)。我們可以理解為,繼承同一基類的不同派生對象,對來自基類的同一消息執(zhí)行了不同的行為,這就是多態(tài),它是通過繼承和虛函數(shù)實(shí)現(xiàn)的。而接受同一消息的實(shí)現(xiàn)就是基于基類指針。?
? ? ?但是要注意的是,這個指針只能用來調(diào)用基類的成員函數(shù)。
? ? ?如果試圖通過基類指針調(diào)用派生類才有的成員函數(shù),則編譯器會報錯。
? ? ?為了避免這種錯誤,必須將基類指針強(qiáng)制轉(zhuǎn)化為派生類指針。然后派生類指針可以用來調(diào)用派生類的功能。這稱為向下強(qiáng)制類型轉(zhuǎn)換,這是一種潛在的危險操作。
? ? ?派生類指針不可以指向基類對象,例如:
有個people類是基類,成員有姓名和身份證號,有個派生類學(xué)生student,添加了成員學(xué)號,現(xiàn)在如果你說的這個情況成立student的指針----pt讓他指向people成員t,則t只有兩個成員變量,而*pt有3個,現(xiàn)在pt->學(xué)號這個變量在pt下是可以使用的,但它指向的實(shí)體卻沒有這個變量,所以出錯,于是C++直接就避免了這樣的隱式轉(zhuǎn)換。 所以根據(jù)上述信息我們可以知道: 進(jìn)行上行轉(zhuǎn)換(把派生類的指針或引用轉(zhuǎn)換成基類表示)是安全的;進(jìn)行下行轉(zhuǎn)換(把基類指針或引用轉(zhuǎn)換成派生類表示)是不安全的。? ? 參考鏈接:https://blog.csdn.net/flyingbird_sxf/article/details/41358737
? ? ? ? ? ? ? ? ? ? ?https://www.cnblogs.com/rednodel/p/5800142.html
? ?31.?繼承機(jī)制中引用和指針之間如何轉(zhuǎn)換??
? ? 基類——>派生類:用dynamic_cast轉(zhuǎn)換(顯示轉(zhuǎn)換),首先檢查基類指針(引用)是否真正指向一個派生類對象,然后再做相應(yīng)處理,對指針進(jìn)行dynamic_cast,成功返回派生類對象,失敗返回空指針,對引用進(jìn)行dynamic_cast,成功返回派生類對象,失敗拋出一個異常。 不允許隱式轉(zhuǎn)換。
? ? 派生類——>基類:可以用dynamic_cast或者直接進(jìn)行類型轉(zhuǎn)換(直接賦值)。
? ??32.c語言和c++有什么區(qū)別??
? ? C語言是結(jié)構(gòu)化的編程語言,它是面向過程的,而C++是面向?qū)ο蟮摹?
? ??封裝:將數(shù)據(jù)和函數(shù)等集合在一個單元中(即類)。被封裝的類通常稱為抽象數(shù)據(jù)類型。封裝的意義在于保護(hù)或者防止代碼(數(shù)據(jù))被我們無意中破壞。?
? ??繼承:繼承主要實(shí)現(xiàn)重用代碼,節(jié)省開發(fā)時間。它可以使用現(xiàn)有類的所有功能,并在無需重新編寫原來的類的情況下對這些功能進(jìn)行擴(kuò)展。?
? ??多態(tài):同一操作作用于不同的對象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果。在運(yùn)行時,可以通過指向派生類的基類指針,來調(diào)用實(shí)現(xiàn)派生類中的方法。有編譯時多態(tài)和運(yùn)行時多態(tài)。
???33.C++中的公有,私有,保護(hù)的問題??
| 類 | 類對象 | 公有繼承派生類 | 私有繼承派生類 | 保護(hù)繼承派生類 | 公有繼承派生類對象 | 私有繼承派生類對象 | 保護(hù)繼承派生類對象 | |
| 公有成員 | √ | √ | √ | √ | √ | √ | X | X |
| 私有成員 | √ | X | X | X | X | X | X | X |
| 保護(hù)成員 | √ | X | √ | √ | √ | X | X | X |
? ?√:代表可以訪問,X代表不能訪問。? ? ?
?參考鏈接:https://zhidao.baidu.com/question/551075894.html
34.如何實(shí)現(xiàn)類對象只能靜態(tài)分配或動態(tài)分配?
?C++中建立類的對象有兩種方式:
(1)靜態(tài)建立,例如 A a;
? ? ?靜態(tài)建立一個類對象,就是由編譯器為對象在棧空間中分配內(nèi)存。使用這種方法,是直接調(diào)用類的構(gòu)造函數(shù)。
(2)動態(tài)建立,例如 A* p = new A();
? ? ?動態(tài)建立一個類對象,就是使用new運(yùn)算符為對象在堆空間中分配內(nèi)存。這個過程分為兩步:第一步執(zhí)行operator new( )函數(shù),在堆空間中搜索一塊內(nèi)存并進(jìn)行分配;第二步調(diào)用類的構(gòu)造函數(shù)構(gòu)造對象。這種方法是間接調(diào)用類的構(gòu)造函數(shù)。
只能動態(tài)分配:??
? ? ? 其實(shí),編譯器在為類對象分配棧空間時,會先檢查類的析構(gòu)函數(shù)的訪問性(其實(shí)不光是析構(gòu)函數(shù),只要是非靜態(tài)的函數(shù),編譯器都會進(jìn)行檢查)。如果類的析構(gòu)函數(shù)在類外部無法訪問,則編譯器拒絕在棧空間上為類對象分配內(nèi)存。 因此,可以將析構(gòu)函數(shù)設(shè)為private,這樣就無法在棧上建立類對象了。但是為了子類可以繼承,最好設(shè)置成protected。
只能靜態(tài)分配:
? ??只有使用new運(yùn)算符,對象才會被建立在堆上。因此只要限制new運(yùn)算符就可以實(shí)現(xiàn)類對象只能建立在棧上。可以將new運(yùn)算符設(shè)為私有。
35.explicit關(guān)鍵字的作用?
C++中, 一個參數(shù)的?構(gòu)造函數(shù)(或者除了第一個參數(shù)外其余參數(shù)都有默認(rèn)值的多參構(gòu)造函數(shù)), 承擔(dān)了兩個角色。 1 是個?構(gòu)造器?,2 是個默認(rèn)且隱含的類型轉(zhuǎn)換操作符。
所以, 有時候在我們寫下如 AAA = XXX, 這樣的代碼, 且恰好XXX的類型正好是AAA單參數(shù)構(gòu)造器的參數(shù)類型, 這時候?編譯器就自動調(diào)用這個構(gòu)造器, 創(chuàng)建一個AAA的對象。
這樣看起來好象很酷, 很方便。 但在某些情況下(見下面權(quán)威的例子), 卻違背了我們(程序員)的本意。 這時候就要在這個構(gòu)造器前面加上explicit修飾, 指定這個構(gòu)造器只能被明確的調(diào)用/使用, 不能作為類型轉(zhuǎn)換操作符被隱含的使用。
class Test1
{
public:
Test1( int n)
{
num=n;
} //普通構(gòu)造函數(shù)
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
} //explicit(顯式)構(gòu)造函數(shù)
private:
int num;
};
int main()
{
Test1 t1= 12; //隱式調(diào)用其構(gòu)造函數(shù),成功
Test2 t2= 12; //編譯錯誤,不能隱式調(diào)用其構(gòu)造函數(shù)
Test2 t2(12); //顯式調(diào)用成功
return 0;
}
- 1
Test1的?構(gòu)造函數(shù)帶一個int型的參數(shù),代碼23行會隱式轉(zhuǎn)換成調(diào)用Test1的這個構(gòu)造函數(shù)。而Test2的構(gòu)造函數(shù)被聲明為explicit(顯式),這表示不能通過隱式轉(zhuǎn)換來調(diào)用這個構(gòu)造函數(shù),因此代碼24行會出現(xiàn)編譯錯誤。
普通構(gòu)造函數(shù)能夠被?隱式調(diào)用。而explicit構(gòu)造函數(shù)只能被顯式調(diào)用。
36.內(nèi)存溢出,內(nèi)存泄漏的原因?
? ? 內(nèi)存溢出是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用。原因可能如下:
- ?內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù)
- ?代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實(shí)體
- ?遞歸調(diào)用太深,導(dǎo)致堆棧溢出等
- ?內(nèi)存泄漏最終導(dǎo)致內(nèi)存溢出
? ??內(nèi)存泄漏是指向系統(tǒng)申請分配內(nèi)存進(jìn)行使用(new),但是用完后不歸還(delete),導(dǎo)致占用有效內(nèi)存。常見的幾種情況:
? ?(1)?在類的構(gòu)造函數(shù)和析構(gòu)函數(shù)中沒有匹配的調(diào)用new和delete函數(shù)
? ? ????兩種情況下會出現(xiàn)這種內(nèi)存泄露:一是在堆里創(chuàng)建了對象占用了內(nèi)存,但是沒有顯示地釋放對象占用的內(nèi)存;二是在類的構(gòu)造函數(shù)中動態(tài)的分配了內(nèi)存,但是在析構(gòu) 函數(shù)中沒有釋放內(nèi)存或者沒有正確的釋放內(nèi)存
? ?(2)?在釋放對象數(shù)組時在delete中沒有使用方括號
? ? ??方括號是告訴編譯器這個指針指向的是一個對象數(shù)組,同時也告訴編譯器正確的對象地址值病調(diào)用對象的析構(gòu)函數(shù),如果沒有方括號,那么這個指針就被默認(rèn)為只指向一個對象,對象數(shù)組中的其他對象的析構(gòu)函數(shù)就不會被調(diào)用,結(jié)果造成了內(nèi)存泄露。
? ? (3)沒有將基類的析構(gòu)函數(shù)定義為虛函數(shù)
? ? ???當(dāng)基類指針指向子類對象時,如果基類的析構(gòu)函數(shù)不是virtual,那么子類的析構(gòu)函數(shù)將不會被調(diào)用,子類的資源沒有正確是釋放,因此造成內(nèi)存泄露
? ? ?參考鏈接: https://blog.csdn.net/hyqwmxsh/article/details/52813307? ??
? ? ?緩沖區(qū)溢出(棧溢出)
? ? ?程序?yàn)榱伺R時存取數(shù)據(jù)的需要,一般會分配一些內(nèi)存空間稱為緩沖區(qū)。如果向緩沖區(qū)中寫入緩沖區(qū)無法容納的數(shù)據(jù),機(jī)會造成緩沖區(qū)以外的存儲單元被改寫,稱為緩沖區(qū)溢出。而棧溢出是緩沖區(qū)溢出的一種,原理也是相同的。分為上溢出和下溢出。其中,上溢出是指棧滿而又向其增加新的數(shù)據(jù),導(dǎo)致數(shù)據(jù)溢出;下溢出是指空棧而又進(jìn)行刪除操作等,導(dǎo)致空間溢出。
37.auto_ptr類與shared_ptr類?
??? ??從c++11開始, auto_ptr已經(jīng)被標(biāo)記為棄用, 常見的替代品為shared_ptr。shared_ptr的不同之處在于引用計數(shù), 在復(fù)制(或賦值)時不會像auto_ptr那樣直接轉(zhuǎn)移所有權(quán)。?兩者都是模板類,卻可以像指針一樣去使用。只是在指針上面的一層封裝。
?? ???auto_ptr實(shí)際也是一種類, 擁有自己的析構(gòu)函數(shù), 生命周期結(jié)束時能自動釋放資源,正因?yàn)槟茏詣俞尫刨Y源, 特別適合在單個函數(shù)內(nèi)代替new/delete的調(diào)用, 不用自己調(diào)用delete,也不用擔(dān)心意外退出造成內(nèi)存的泄漏。
? ?atuo_ptr的缺陷:
- ?auto_ptr不能共享所有權(quán),即不要讓兩個auto_ptr指向同一個對象(因?yàn)樗捎玫氖寝D(zhuǎn)移語義的拷貝,原指針會變?yōu)镹ULL)。
- ?auto_ptr不能管理對象數(shù)組(因?yàn)樗鼉?nèi)部的析構(gòu)函數(shù)調(diào)用的是delete而不是delete[])。
- ?auto_ptr不能作為容器對象,STL容器中的元素經(jīng)常要支持拷貝,賦值等操作,在這過程中auto_ptr會傳遞所有權(quán)。
? ?詳情原因可以參考:https://blog.csdn.net/uestclr/article/details/51316001
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??https://blog.csdn.net/kezunhai/article/details/38514823
? ? ?
? ? ?shared_ptr 使用引用計數(shù)的方式來實(shí)現(xiàn)對指針資源的管理。同一個指針資源,可以被多個 shared_ptr 對象所擁有,直到最后一個 shared_ptr 對象析構(gòu)時才釋放所管理的對象資源。
? ? ? 可以說,shared_ptr 是最智能的智能指針,因?yàn)槠涮攸c(diǎn)最接近原始的指針。不僅能夠自由的賦值和拷貝,而且可以安全的用在標(biāo)準(zhǔn)容器中。
38.???有4種情況,編譯器必須為未聲明的constructor的classes合成一個default constructor:
l? “帶有默認(rèn)構(gòu)造函數(shù)”的成員對象
l? “帶有默認(rèn)構(gòu)造函數(shù)”的基類
l? “帶有虛函數(shù)”的類
l? “帶有虛擬基類”的類
被合成的構(gòu)造函數(shù)只能滿足編譯器(而非程序員)的需要。在合成默認(rèn)的構(gòu)造函數(shù)中,只有基類的子對象和成員對象會被初始化,其他非靜態(tài)的數(shù)據(jù)成員(如整數(shù),指針等)都不會被初始化。
所以并不是任何的類如果沒有定義默認(rèn)的構(gòu)造函數(shù),都會被合成一個出來。
39.???虛基類
在C++中,如果在多條繼承路徑上有一個公共的基類,那么在這些路徑中的某幾條路徑的匯合處,這個公共的基類就會產(chǎn)生多個實(shí)例(從而造成二義性).如果想使這個公共的基類只產(chǎn)生一個實(shí)例,則可將這個基類說明為虛基類. 這要求在從base類派生新類時,使用關(guān)鍵字virtual將base類說明為虛基類.
用例子說明吧。
class base{protected:int b};
clase base1:public base{..};
clase base2:public base{..};
clase derived:public base1,public base2 {..};
derived d;
d.b //錯誤.
d.base::b //錯誤. 因?yàn)椴恢怯胐.base1::b還是d.base2::b
=================================================
class base{protected:int b..};
clase base1:virtual public base{..}; //說明base為base1虛基類
clase base2:virtual public base{..}; //說明base為base2虛基類
clase derived:public base1,public base2 {..};
derived d;
d.b //對.
d.base::b //對. 因?yàn)閐.base::b和d.base1::b還是d.base2::b都是引用同一虛基類成員b,具有相同的值.
40.??模板的特例化
引入原因:編寫單一的模板,它能適應(yīng)大眾化,使每種類型都具有相同的功能,但對于某種特定類型,如果要實(shí)現(xiàn)其特有的功能,單一模板就無法做到,這時就需要模板特例化。?
定義:是對單一模板提供的一個特殊實(shí)例,它將一個或多個模板參數(shù)綁定到特定的類型或值上。
函數(shù)模板特例化:必須為原函數(shù)模板的每個模板參數(shù)都提供實(shí)參,且使用關(guān)鍵字template后跟一個空尖括號對<>,表明將原模板的所有模板參數(shù)提供實(shí)參。
1.???template?<typename?T>??
2.???void?fun(T?a)??
3.?? {??
4.?? ????cout?<<?"The?main?template?fun():?"?<<?a?<<?endl;??
5.?? }??
6.?? ??
7.???template?<>???//?對int型特例化??
8.???void?fun(int?a)??
9.?? {??
10. ????cout?<<?"Specialized?template?for?int?type:?"?<<?a?<<?endl;??
11. }??
12. ??
13.?int?main()??
14. {??
15. ????fun<char>('a');??
16. ????fun<int>(10);??
17. ????fun<float>(9.15);??
18. ????return?0;??
19. }??
對于除int型外的其他數(shù)據(jù)類型,都會調(diào)用通用版本的函數(shù)模板fun(T a);對于int型,則會調(diào)用特例化版本的fun(int a)。注意,一個特例化版本的本質(zhì)是一個實(shí)例,而非函數(shù)的重載。因此,特例化不影響函數(shù)匹配。
?
類模板的特例化:
1.???template?<typename?T>??
2.???class?Test{??
3.???public:??
4.?? ????void?print(){??
5.?? ????????cout?<<?"General?template?object"?<<?endl;??
6.?? ????}??
7.?? };??
8.?? ??
9.???template<>???//?對int型特例化??
10.?class?Test<int>{??
11.?public:??
12. ????void?print(){??
13. ????????cout?<<?"Specialized?template?object"?<<?endl;??
14. ????}??
15. };??
另外,與函數(shù)模板不同,類模板的特例化不必為所有模板參數(shù)提供實(shí)參。我們可以只指定一部分而非所有模板參數(shù),這種叫做類模板的偏特化?或部分特例化(partial specialization)。例如,C++標(biāo)準(zhǔn)庫中的類vector的定義:
[cpp]?view plain?copy
1.???template?<typename?T,?typename?Allocator>??
2.???class?vector??
3.?? {??
4.?? ????/*......*/??
5.?? };??
6.?? ??
7.?? //?部分特例化??
8.???template?<typename?Allocator>??
9.???class?vector<bool,?Allocator>??
10. {??
11. ????/*......*/??
12. };??
在vector這個例子中,一個參數(shù)被綁定到bool類型,而另一個參數(shù)仍未綁定需要由用戶指定。注意,一個類模板的部分特例化版本仍然是一個模板,因?yàn)槭褂盟鼤r用戶還必須為那些在特例化版本中未指定的模板參數(shù)提供實(shí)參。?
第二部分:嵌入式
總結(jié)
- 上一篇: MSB8036 The Windows
- 下一篇: 一种抑制undershoot/overs