硬核 | C++ 基礎(chǔ)大全
簡(jiǎn)述智能指針
智能指針其作用是管理一個(gè)指針,避免咋們程序員申請(qǐng)的空間在函數(shù)結(jié)束時(shí)忘記釋放,造成內(nèi)存泄漏這種情況滴發(fā)生。
然后使用智能指針可以很大程度上的避免這個(gè)問題,因?yàn)橹悄苤羔樉褪且粋€(gè)類,當(dāng)超出了類的作用域是,類會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),析構(gòu)函數(shù)會(huì)自動(dòng)釋放資源。所以智能指針的作用原理就是在函數(shù)結(jié)束時(shí)自動(dòng)釋放內(nèi)存空間,不需要手動(dòng)釋放內(nèi)存空間。
智能指針的常見接口
T*?get();
T&?operator*();
T*?operator->();
T&?operator=(const?T&?val);
T*?release();
void?reset?(T*?ptr?=?nullptr);
T 是模板參數(shù), 也就是傳入的類型;
get() 用來獲取 auto_ptr 封裝在內(nèi)部的指針, 也就是獲取原生指針;
operator*() 重載* , operator->() 重載了->, operator=()重載了=;
realease() 將 auto_ptr 封裝在內(nèi)部的指針置為 nullptr, 但并不會(huì)破壞指針?biāo)赶虻膬?nèi)容, 函數(shù)返回的是內(nèi)部指針置空之前的值;
直接釋放封裝的內(nèi)部指針?biāo)赶虻膬?nèi)存, 如果指定了 ptr 的值, 則將內(nèi)部指針初始化為該值 (否則將其設(shè)置為nullptr;
CPP智能指針有哪幾種?
auto_ptr
unique_ptr
shared_ptr
weak_ptr
簡(jiǎn)述auto_ptr
auto_ptr(C++98 的方案,C11 已拋棄)采用所有權(quán)模式。
auto_ptr
auto_ptr
p2?=?p1;?//auto_ptr?不會(huì)報(bào)錯(cuò).
此時(shí)不會(huì)報(bào)錯(cuò),p2 剝奪了 p1 的所有權(quán),但是當(dāng)程序運(yùn)行時(shí)訪問 p1 將會(huì)報(bào)錯(cuò)。所以 auto_ptr 的缺點(diǎn)是:存在潛在的內(nèi)存崩潰問題!
簡(jiǎn)述unique_ptr
unique_ptr 實(shí)現(xiàn)獨(dú)占式擁有或嚴(yán)格擁有概念,保證同一時(shí)間內(nèi)只有一個(gè)智能指針可以指向該對(duì)象。它對(duì)于避免資源泄露特別有用。
采用所有權(quán)模式,還是上面那個(gè)例子
unique_ptr
unique_ptr
p4?=?p3;//此時(shí)會(huì)報(bào)錯(cuò)
編譯器認(rèn)為 p4=p3 非法,避免了 p3 不再指向有效數(shù)據(jù)的問題。
因此,unique_ptr 比 auto_ptr 更安全。
簡(jiǎn)述shared_ptr
shared_ptr 實(shí)現(xiàn)共享式擁有概念,多個(gè)智能指針可以指向相同對(duì)象,該對(duì)象和其相關(guān)資源會(huì)在“最后一個(gè)引用被銷毀”時(shí)候釋放。從名字 share 就可以看出了資源可以被多個(gè)指針共享,它使用計(jì)數(shù)機(jī)制來表明資源被幾個(gè)指針共享。
可以通過成員函數(shù) use_count() 來查看資源的所有者個(gè)數(shù),除了可以通過 new 來構(gòu)造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr 來構(gòu)造。當(dāng)我們調(diào)用 release() 時(shí),當(dāng)前指針會(huì)釋放資源所有權(quán),計(jì)數(shù)減一。當(dāng)計(jì)數(shù)等于 0 時(shí),資源會(huì)被釋放。
shared_ptr 是為了解決 auto_ptr 在對(duì)象所有權(quán)上的局限性 (auto_ptr 是獨(dú)占的),在使用引用計(jì)數(shù)的機(jī)制上提供了可以共享所有權(quán)的智能指針。
簡(jiǎn)述weak_ptr
weak_ptr 是一種不控制對(duì)象生命周期的智能指針,它指向一個(gè) ?shared_ptr 管理的對(duì)象。進(jìn)行該對(duì)象的內(nèi)存管理的是那個(gè)強(qiáng)引用的 shared_ptr。
weak_ptr 只是提供了對(duì)管理對(duì)象的一個(gè)訪問手段。weak_ptr ?設(shè)計(jì)的目的是為配合 ?shared_ptr 而引入的一種智能指針來協(xié)助 shared_ptr 工作,它只可以從一個(gè) shared_ptr 或另一個(gè) weak_ptr 對(duì)象構(gòu)造,,它的構(gòu)造和析構(gòu)不會(huì)引起引用記數(shù)的增加或減少。
weak_ptr 是用來解決 shared_ptr 相互引用時(shí)的死鎖問題,如果說兩個(gè) shared_ptr 相互引用,那么這兩個(gè)指針的引用計(jì)數(shù)永遠(yuǎn)不可能下降為0,也就是資源永遠(yuǎn)不會(huì)釋放。它是對(duì)對(duì)象的一種弱引用,不會(huì)增加對(duì)象的引用計(jì)數(shù),和 shared_ptr 之間可以相互轉(zhuǎn)化,shared_ptr 可以直接賦值給它,它可以通過調(diào)用 lock 函數(shù)來獲得shared_ptr。
當(dāng)兩個(gè)智能指針都是 shared_ptr 類型的時(shí)候,析構(gòu)時(shí)兩個(gè)資源引用計(jì)數(shù)會(huì)減一,但是兩者引用計(jì)數(shù)還是為 1,導(dǎo)致跳出函數(shù)時(shí)資源沒有被釋放(的析構(gòu)函數(shù)沒有被調(diào)用),解決辦法:把其中一個(gè)改為weak_ptr就可以。
C++ 中內(nèi)存分配情況
棧:由編譯器管理分配和回收,存放局部變量和函數(shù)參數(shù)。
堆:由程序員管理,需要手動(dòng) new malloc delete free 進(jìn)行分配和回收,空間較大,但可能會(huì)出現(xiàn)內(nèi)存泄漏和空閑碎片的情況。
全局/靜態(tài)存儲(chǔ)區(qū):分為初始化和未初始化兩個(gè)相鄰區(qū)域,存儲(chǔ)初始化和未初始化的全局變量和靜態(tài)變量。
常量存儲(chǔ)區(qū):存儲(chǔ)常量,一般不允許修改。
代碼區(qū):存放程序的二進(jìn)制代碼。
C++ 中的指針參數(shù)傳遞
指針參數(shù)傳遞本質(zhì)上是值傳遞,它所傳遞的是一個(gè)地址值。值傳遞過程中,被調(diào)函數(shù)的形式參數(shù)作為被調(diào)函數(shù)的局部變量處理,會(huì)在棧中開辟內(nèi)存空間以存放由主調(diào)函數(shù)傳遞進(jìn)來的實(shí)參值,從而形成了實(shí)參的一個(gè)副本(替身)。值傳遞的特點(diǎn)是,被調(diào)函數(shù)對(duì)形式參數(shù)的任何操作都是作為局部變量進(jìn)行的,不會(huì)影響主調(diào)函數(shù)的實(shí)參變量的值(形參指針變了,實(shí)參指針不會(huì)變)。
引用參數(shù)傳遞過程中,被調(diào)函數(shù)的形式參數(shù)也作為局部變量在棧中開辟了內(nèi)存空間,但是這時(shí)存放的是由主調(diào)函數(shù)放進(jìn)來的實(shí)參變量的地址。被調(diào)函數(shù)對(duì)形參(本體)的任何操作都被處理成間接尋址,即通過棧中存放的地址訪問主調(diào)函數(shù)中的實(shí)參變量(根據(jù)別名找到主調(diào)函數(shù)中的本體)。因此,被調(diào)函數(shù)對(duì)形參的任何操作都會(huì)影響主調(diào)函數(shù)中的實(shí)參變量。
簡(jiǎn)述C++ 中的引用參數(shù)傳遞
引用傳遞和指針傳遞是不同的,雖然他們都是在被調(diào)函數(shù)棧空間上的一個(gè)局部變量,但是任何對(duì)于引用參數(shù)的處理都會(huì)通過一個(gè)間接尋址的方式操作到主調(diào)函數(shù)中的相關(guān)變量。而對(duì)于指針傳遞的參數(shù),如果改變被調(diào)函數(shù)中的指針地址,它將應(yīng)用不到主調(diào)函數(shù)的相關(guān)變量。如果想通過指針參數(shù)傳遞來改變主調(diào)函數(shù)中的相關(guān)變量(地址),那就得使用指向指針的指針或者指針引用。
從編譯的角度來講,程序在編譯時(shí)分別將指針和引用添加到符號(hào)表上,符號(hào)表中記錄的是變量名及變量所對(duì)應(yīng)地址。指針變量在符號(hào)表上對(duì)應(yīng)的地址值為指針變量的地址值,而引用在符號(hào)表上對(duì)應(yīng)的地址值為引用對(duì)象的地址值(與實(shí)參名字不同,地址相同)。符號(hào)表生成之后就不會(huì)再改,因此指針可以改變其指向的對(duì)象(指針變量中的值可以改),而引用對(duì)象則不能修改。
簡(jiǎn)述C++ 中 const 關(guān)鍵詞
const 修飾基本類型數(shù)據(jù)類型:基本數(shù)據(jù)類型,修飾符 const 可以用在類型說明符前,也可以用在類型說明符后,其結(jié)果是一樣的。在使用這些常量的時(shí)候,只要不改變這些常量的值即可。
const 修飾指針變量和引用變量:如果 const 位于小星星的左側(cè),則 const 就是用來修飾指針?biāo)赶虻淖兞浚粗羔樦赶驗(yàn)槌A浚蝗绻?const 位于小星星的右側(cè),則 const 就是修飾指針本身,即指針本身是常量。
const 應(yīng)用到函數(shù)中:作為參數(shù)的 const 修飾符:調(diào)用函數(shù)的時(shí)候,用相應(yīng)的變量初始化 const 常量,則在函數(shù)體中,按照 const 所修飾的部分進(jìn)行常量化,保護(hù)了原對(duì)象的屬性。[注意]:參數(shù) const 通常用于參數(shù)為指針或引用的情況; 作為函數(shù)返回值的 const 修飾符:聲明了返回值后,const 按照"修飾原則"進(jìn)行修飾,起到相應(yīng)的保護(hù)作用。
const 在類中的用法:const 成員變量,只在某個(gè)對(duì)象生命周期內(nèi)是常量,而對(duì)于整個(gè)類而言是可以改變的。因?yàn)轭惪梢詣?chuàng)建多個(gè)對(duì)象,不同的對(duì)象其 const 數(shù)據(jù)成員值可以不同。所以不能在類的聲明中初始化 const 數(shù)據(jù)成員,因?yàn)轭惖膶?duì)象在沒有創(chuàng)建時(shí)候,編譯器不知道 const 數(shù)據(jù)成員的值是什么。const 數(shù)據(jù)成員的初始化只能在類的構(gòu)造函數(shù)的初始化列表中進(jìn)行。const 成員函數(shù):const 成員函數(shù)的主要目的是防止成員函數(shù)修改對(duì)象的內(nèi)容。要注意,const 關(guān)鍵字和 static 關(guān)鍵字對(duì)于成員函數(shù)來說是不能同時(shí)使用的,因?yàn)?static 關(guān)鍵字修飾靜態(tài)成員函數(shù)不含有 this 指針,即不能實(shí)例化,const 成員函數(shù)又必須具體到某一個(gè)函數(shù)。
const 修飾類對(duì)象,定義常量對(duì)象:常量對(duì)象只能調(diào)用常量函數(shù),別的成員函數(shù)都不能調(diào)用。
**補(bǔ)充:**const 成員函數(shù)中如果實(shí)在想修改某個(gè)變量,可以使用 mutable 進(jìn)行修飾。成員變量中如果想建立在整個(gè)類中都恒定的常量,應(yīng)該用類中的枚舉常量來實(shí)現(xiàn)或者 static const。
C ++ 中的 const類成員函數(shù)(用法和意義)
常量對(duì)象可以調(diào)用類中的 const 成員函數(shù),但不能調(diào)用非 const 成員函數(shù);(原因:對(duì)象調(diào)用成員函數(shù)時(shí),在形參列表的最前面加一個(gè)形參 this,但這是隱式的。this 指針是默認(rèn)指向調(diào)用函數(shù)的當(dāng)前對(duì)象的,所以,很自然,this 是一個(gè)常量指針 test * const,因?yàn)椴豢梢孕薷?this 指針代表的地址。但當(dāng)成員函數(shù)的參數(shù)列表(即小括號(hào))后加了 const 關(guān)鍵字(void print() const;),此成員函數(shù)為常量成員函數(shù),此時(shí)它的隱式this形參為 const test * const,即不可以通過 this 指針來改變指向?qū)ο蟮闹怠?/p>
非常量對(duì)象可以調(diào)用類中的 const 成員函數(shù),也可以調(diào)用非 const 成員函數(shù)。
簡(jiǎn)述static關(guān)鍵詞
作用一:修飾局部變量:一般情況下,對(duì)于局部變量在程序中是存放在棧區(qū)的,并且局部的生命周期在包含語句塊執(zhí)行結(jié)束時(shí)便結(jié)束了。但是如果用 static 關(guān)鍵字修飾的話,該變量便會(huì)存放在靜態(tài)數(shù)據(jù)區(qū),其生命周期會(huì)一直延續(xù)到整個(gè)程序執(zhí)行結(jié)束。但是要注意的是,雖然用 static ?對(duì)局部變量進(jìn)行修飾之后,其生命周期以及存儲(chǔ)空間發(fā)生了變化,但其作用域并沒有改變,作用域還是限制在其語句塊。
作用二:修飾全部變量:對(duì)于一個(gè)全局變量,它既可以在本文件中被訪問到,也可以在同一個(gè)工程中其它源文件被訪問(添加 extern進(jìn)行聲明即可)。用 static 對(duì)全局變量進(jìn)行修飾改變了其作用域范圍,由原來的整個(gè)工程可見變成了本文件可見。
作用三:修飾函數(shù):用 static 修飾函數(shù),情況和修飾全局變量類似,也是改變了函數(shù)的作用域。
作用四:修飾類:如果 C++ 中對(duì)類中的某個(gè)函數(shù)用 static ?修飾,則表示該函數(shù)屬于一個(gè)類而不是屬于此類的任何特定對(duì)象;如果對(duì)類中的某個(gè)變量進(jìn)行 static 修飾,則表示該變量以及所有的對(duì)象所有,存儲(chǔ)空間中只存在一個(gè)副本,可以通過;類和對(duì)象去調(diào)用。
(補(bǔ)充:靜態(tài)非常量數(shù)據(jù)成員,其只能在類外定義和初始化,在類內(nèi)僅是聲明而已。)
作用五:類成員/類函數(shù)聲明 static
函數(shù)體內(nèi) static 變量的作用范圍為該函數(shù)體,不同于 auto 變量,該變量的內(nèi)存只被分配一次,因此其值在下次調(diào)用時(shí)仍維持上次的值;
在模塊內(nèi)的 static 全局變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問;
在模塊內(nèi)的 static 函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用,這個(gè)函數(shù)的使用范圍被限制在聲明它的模塊內(nèi);
在類中的 static 成員變量屬于整個(gè)類所擁有,對(duì)類的所有對(duì)象只有一份拷貝;
在類中的 static 成員函數(shù)屬于整個(gè)類所擁有,這個(gè)函數(shù)不接收 this 指針,因而只能訪問類的 static 成員變量。
static 類對(duì)象必須要在類外進(jìn)行初始化,static 修飾的變量先于對(duì)象存在,所以 static 修飾的變量要在類外初始化;
由于 static 修飾的類成員屬于類,不屬于對(duì)象,因此 static 類成員函數(shù)是沒有 this 指針,this 指針是指向本對(duì)象的指針,正因?yàn)闆]有 this 指針,所以 static 類成員函數(shù)不能訪問非 static 的類成員,只能訪問 static修飾的類成員;
static 成員函數(shù)不能被 virtual 修飾,static 成員不屬于任何對(duì)象或?qū)嵗约由?virtual 沒有任何實(shí)際意義;靜態(tài)成員函數(shù)沒有 this 指針,虛函數(shù)的實(shí)現(xiàn)是為每一個(gè)對(duì)象分配一個(gè) vptr 指針,而 vptr 是通過 this 指針調(diào)用的,所以不能為 virtual;虛函數(shù)的調(diào)用關(guān)系,this->vptr->ctable->virtual function。
C 和 C++ 區(qū)別
首先,C 和 C++ 在基本語句上沒有過大的區(qū)別。
C++ 有新增的語法和關(guān)鍵字,語法的區(qū)別有頭文件的不同和命名空間的不同,C++ 允許我們自己定義自己的空間,C 中不可以。關(guān)鍵字方面比如 C++ 與 C 動(dòng)態(tài)管理內(nèi)存的方式不同,C++ 中在 malloc 和 free 的基礎(chǔ)上增加了 new ?和 delete,而且 C++ 中在指針的基礎(chǔ)上增加了引用的概念,關(guān)鍵字例如 C++中還增加了 auto,explicit 體現(xiàn)顯示和隱式轉(zhuǎn)換上的概念要求,還有 dynamic_cast 增加類型安全方面的內(nèi)容。
函數(shù)方面 C++ 中有重載和虛函數(shù)的概念:C++ 支持函數(shù)重載而 C 不支持,是因?yàn)?C++ 函數(shù)的名字修飾與 C 不同,C++ 函數(shù)名字的修飾會(huì)將參數(shù)加在后面,例如,int func(int,double)經(jīng)過名字修飾之后會(huì)變成_func_int_double,而 C 中則會(huì)變成 _func,所以 C++ 中會(huì)支持不同參數(shù)調(diào)用不同函數(shù)。
C++ 還有虛函數(shù)概念,用以實(shí)現(xiàn)多態(tài)。
類方面,C 的 struct 和 C++ 的類也有很大不同:C++ 中的 struct 不僅可以有成員變量還可以成員函數(shù),而且對(duì)于 ?struct 增加了權(quán)限訪問的概念,struct 的默認(rèn)成員訪問權(quán)限和默認(rèn)繼承權(quán)限都是 public,C++ 中除了 struct ?還有 ?class 表示類,struct ?和 class 還有一點(diǎn)不同在于 class 的默認(rèn)成員訪問權(quán)限和默認(rèn)繼承權(quán)限都是 private。
C++ 中增加了模板還重用代碼,提供了更加強(qiáng)大的 STL 標(biāo)準(zhǔn)庫。
最后補(bǔ)充一點(diǎn)就是 C 是一種結(jié)構(gòu)化的語言,重點(diǎn)在于算法和數(shù)據(jù)結(jié)構(gòu)。C 程序的設(shè)計(jì)首先考慮的是如何通過一個(gè)代碼,一個(gè)過程對(duì)輸入進(jìn)行運(yùn)算處理輸出。而 C++ 首先考慮的是如何構(gòu)造一個(gè)對(duì)象模型,讓這個(gè)模型能夠契合與之對(duì)應(yīng)的問題領(lǐng)域,這樣就能通過獲取對(duì)象的狀態(tài)信息得到輸出。
C 的 struct 更適合看成是一個(gè)數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)體,而 C++ 的 class 更適合看成是一個(gè)對(duì)象的實(shí)現(xiàn)體。
C++ 和 Java 區(qū)別
**指針:**Java 語言讓程序員沒法找到指針來直接訪問內(nèi)存,沒有指針的概念,并有內(nèi)存的自動(dòng)管理功能,從而有效的防止了 C++ 語言中的指針操作失誤的影響。但并非 Java 中沒有指針,Java 虛擬機(jī)內(nèi)部中還是用了指針,保證了 Java ?程序的安全。
**多重繼承:**C++ 支持多重繼承但 Java 不支持,但支持一個(gè)類繼承多個(gè)接口,實(shí)現(xiàn) C++ 中多重繼承的功能,又避免了 C++ 的多重繼承帶來的不便。
**數(shù)據(jù)類型和類:**Java 是完全面向?qū)ο蟮恼Z言,所有的函數(shù)和變量必須是類的一部分。除了基本數(shù)據(jù)類型之外,其余的都作為類對(duì)象,對(duì)象將數(shù)據(jù)和方法結(jié)合起來,把它們封裝在類中,這樣每個(gè)對(duì)象都可以實(shí)現(xiàn)自己的特點(diǎn)和行為。Java 中取消了 C++ 中的 struct 和 ?union 。
**自動(dòng)內(nèi)存管理:**Java 程序中所有對(duì)象都是用 new 操作符建立在內(nèi)存堆棧上,Java 自動(dòng)進(jìn)行無用內(nèi)存回收操作,不需要程序員進(jìn)行手動(dòng)刪除。而 C++ 中必須由程序員釋放內(nèi)存資源,增加了程序設(shè)計(jì)者的負(fù)擔(dān)。Java 中當(dāng)一個(gè)對(duì)象不再被用到時(shí), 無用內(nèi)存回收器將給他們加上標(biāo)簽。Java 里無用內(nèi)存回收程序是以線程方式在后臺(tái)運(yùn)行的,利用空閑時(shí)間工作來刪除。
Java 不支持操作符重載。操作符重載被認(rèn)為是 C++ 的突出特性。
Java 不支持預(yù)處理功能。C++ 在編譯過程中都有一個(gè)預(yù)編譯階段,Java 沒有預(yù)處理器,但它提供了 import 與 C++ 預(yù)處理器具有類似功能。
**類型轉(zhuǎn)換:**C++ 中有數(shù)據(jù)類型隱含轉(zhuǎn)換的機(jī)制,Java 中需要限時(shí)強(qiáng)制類型轉(zhuǎn)換。
**字符串:**C++中字符串是以 Null 終止符代表字符串的結(jié)束,而 Java 的字符串 是用類對(duì)象(string 和 stringBuffer)來實(shí)現(xiàn)的。
Java 中不提供 goto 語句,雖然指定 goto 作為關(guān)鍵字,但不支持它的使用,使程序簡(jiǎn)潔易讀。
Java 的異常機(jī)制用于捕獲例外事件,增強(qiáng)系統(tǒng)容錯(cuò)能力。
說一下 C++ 里是怎么定義常量的?常量存放在內(nèi)存的哪個(gè)位置?
對(duì)于局部常量,存放在棧區(qū);
對(duì)于全局常量,編譯期一般不分配內(nèi)存,放在符號(hào)表中以提高訪問效率;
字面值常量,比如字符串,放在常量區(qū)。
C++ 中重載和重寫,重定義的區(qū)別
重載
翻譯自 overload,是指同一可訪問區(qū)內(nèi)被聲明的幾個(gè)具有不同參數(shù)列表的同名函數(shù),依賴于 C++函數(shù)名字的修飾會(huì)將參數(shù)加在后面,可以是參數(shù)類型,個(gè)數(shù),順序的不同。根據(jù)參數(shù)列表決定調(diào)用哪個(gè)函數(shù),重載不關(guān)心函數(shù)的返回類型。
重寫
翻譯自 override,派生類中重新定義父類中除了函數(shù)體外完全相同的虛函數(shù),注意被重寫的函數(shù)不能是 static 的,一定要是虛函數(shù),且其他一定要完全相同。要注意,重寫和被重寫的函數(shù)是在不同的類當(dāng)中的,重寫函數(shù)的訪問修飾符是可以不同的,盡管 virtual 中是 private 的,派生類中重寫可以改為 public。
重定義(隱藏)
派生類重新定義父類中相同名字的非 virtual 函數(shù),參數(shù)列表
和返回類型都可以不同,即父類中除了定義成 virtual 且完全相同的同名函數(shù)才
不會(huì)被派生類中的同名函數(shù)所隱藏(重定義)。
介紹 C++ 的構(gòu)造函數(shù)
類的對(duì)象被創(chuàng)建時(shí),編譯系統(tǒng)為對(duì)象分配內(nèi)存空間,并自動(dòng)調(diào)用構(gòu)造函數(shù),由構(gòu)造函數(shù)完成成員的初始化工作。
即構(gòu)造函數(shù)的作用:初始化對(duì)象的數(shù)據(jù)成員。
無參數(shù)構(gòu)造函數(shù):?即默認(rèn)構(gòu)造函數(shù),如果沒有明確寫出無參數(shù)構(gòu)造函數(shù),編譯器會(huì)自動(dòng)生成默認(rèn)的無參數(shù)構(gòu)造函數(shù),函數(shù)為空,什么也不做,如果不想使用自動(dòng)生成的無參構(gòu)造函數(shù),必需要自己顯示寫出一個(gè)無參構(gòu)造函數(shù)。
一般構(gòu)造函數(shù):?也稱重載構(gòu)造函數(shù),一般構(gòu)造函數(shù)可以有各種參數(shù)形式,一個(gè)類可以有多個(gè)一般構(gòu)造函數(shù),前提是參數(shù)的個(gè)數(shù)或者類型不同,創(chuàng)建對(duì)象時(shí)根據(jù)傳入?yún)?shù)不同調(diào)用不同的構(gòu)造函數(shù)。
拷貝構(gòu)造函數(shù):?拷貝構(gòu)造函數(shù)的函數(shù)參數(shù)為對(duì)象本身的引用,用于根據(jù)一個(gè)已存在的對(duì)象復(fù)制出一個(gè)新的該類的對(duì)象,一般在函數(shù)中會(huì)將已存在的對(duì)象的數(shù)據(jù)成員的值一一復(fù)制到新創(chuàng)建的對(duì)象中。如果沒有顯示的寫拷貝構(gòu)造函數(shù),則系統(tǒng)會(huì)默認(rèn)創(chuàng)建一個(gè)拷貝構(gòu)造函數(shù),但當(dāng)類中有指針成員時(shí),最好不要使用編譯器提供的默認(rèn)的拷貝構(gòu)造函數(shù),最好自己定義并且在函數(shù)中執(zhí)行深拷貝。
類型轉(zhuǎn)換構(gòu)造函數(shù):?根據(jù)一個(gè)指定類型的對(duì)象創(chuàng)建一個(gè)本類的對(duì)象,也可以算是一般構(gòu)造函數(shù)的一種,這里提出來,是想說有的時(shí)候不允許默認(rèn)轉(zhuǎn)換的話,要記得將其聲明為 explict 的,來阻止一些隱式轉(zhuǎn)換的發(fā)生。
賦值運(yùn)算符的重載?:注意,這個(gè)類似拷貝構(gòu)造函數(shù),將=右邊的本類對(duì)象的值復(fù)制給=左邊的對(duì)象,它不屬于構(gòu)造函數(shù),=左右兩邊的對(duì)象必需已經(jīng)被創(chuàng)建。如果沒有顯示的寫賦值運(yùn)算符的重載,系統(tǒng)也會(huì)生成默認(rèn)的賦值運(yùn)算符,做一些基本的拷貝工作。
這里區(qū)分
A?a1,?A?a2;?a1?=?a2;//調(diào)用賦值運(yùn)算符
A?a3?=?a1;//調(diào)用拷貝構(gòu)造函數(shù),因?yàn)檫M(jìn)行的是初始化工作,a3?并未存在
簡(jiǎn)述C++ 的四種強(qiáng)制轉(zhuǎn)換
C++ 的四種強(qiáng)制轉(zhuǎn)換包括:static_cast, dynamic_cast, const_cast, reinterpret_cast
static_cast:明確指出類型轉(zhuǎn)換,一般建議將隱式轉(zhuǎn)換都替換成顯示轉(zhuǎn)換,因?yàn)闆]有動(dòng)態(tài)類型檢查,上行轉(zhuǎn)換(派生類->基類)安全,下行轉(zhuǎn)換(基類->派生類) 不安全,所以主要執(zhí)行非多態(tài)的轉(zhuǎn)換操作;
dynamic_cast:專門用于派生類之間的轉(zhuǎn)換,type-id 必須是類指針,類引用或 void*,對(duì)于下行轉(zhuǎn)換是安全的,當(dāng)類型不一致時(shí),轉(zhuǎn)換過來的是空指針,而static_cast,當(dāng)類型不一致時(shí),轉(zhuǎn)換過來的事錯(cuò)誤意義的指針,可能造成非法訪問等問題。
const_cast:專門用于 const 屬性的轉(zhuǎn)換,去除 const 性質(zhì),或增加 const 性質(zhì), 是四個(gè)轉(zhuǎn)換符中唯一一個(gè)可以操作常量的轉(zhuǎn)換符。
reinterpret_cast:不到萬不得已,不要使用這個(gè)轉(zhuǎn)換符,高危操作。使用特點(diǎn):從底層對(duì)數(shù)據(jù)進(jìn)行重新解釋,依賴具體的平臺(tái),可移植性差;可以將整形轉(zhuǎn) 換為指針,也可以把指針轉(zhuǎn)換為數(shù)組;可以在指針和引用之間進(jìn)行肆無忌憚的轉(zhuǎn)換。
簡(jiǎn)述指針和引用的區(qū)別
指針和引用都是一種內(nèi)存地址的概念,區(qū)別呢,指針是一個(gè)實(shí)體,引用只是一個(gè)別名。
在程序編譯的時(shí)候,將指針和引用添加到符號(hào)表中。
指針?biāo)赶蛞粔K內(nèi)存,指針的內(nèi)容是所指向的內(nèi)存的地址,在編譯的時(shí)候,則是將“指針變量名-指針變量的地址”添加到符號(hào)表中,所以說,指針包含的內(nèi)容是可以改變的,允許拷貝和賦值,有 const 和非 const 區(qū)別,甚至可以為空,sizeof 指針得到的是指針類型的大小。
而對(duì)于引用來說,它只是一塊內(nèi)存的別名,在添加到符號(hào)表的時(shí)候,是將"引用變量名-引用對(duì)象的地址"添加到符號(hào)表中,符號(hào)表一經(jīng)完成不能改變,所以引用必須而且只能在定義時(shí)被綁定到一塊內(nèi)存上,后續(xù)不能更改,也不能為空,也沒有 const 和非 const 區(qū)別。
sizeof 引用得到代表對(duì)象的大小。而 sizeof 指針得到的是指針本身的大小。另外在參數(shù)傳遞中,指針需要被解引用后才可以對(duì)對(duì)象進(jìn)行操作,而直接對(duì)引用進(jìn)行的修改會(huì)直接作用到引用對(duì)象上。
作為參數(shù)時(shí)也不同,傳指針的實(shí)質(zhì)是傳值,傳遞的值是指針的地址;傳引用的實(shí)質(zhì)是傳地址,傳遞的是變量的地址。
野(wild)指針與懸空(dangling)指針有什么區(qū)別?如何避免?
野指針(wild pointer):就是沒有被初始化過的指針。用?gcc -Wall?編譯, 會(huì)出現(xiàn)?used uninitialized警告。
懸空指針:是指針最初指向的內(nèi)存已經(jīng)被釋放了的一種指針。
無論是野指針還是懸空指針,都是指向無效內(nèi)存區(qū)域(這里的無效指的是"不安全不可控")的指針。訪問"不安全可控"(invalid)的內(nèi)存區(qū)域?qū)?dǎo)致"Undefined Behavior"。
如何避免使用野指針?在平時(shí)的編碼中,養(yǎng)成在定義指針后且在使用之前完成初始化的習(xí)慣或者使用智能指針。
說一下 const 修飾指針如何區(qū)分?
下面都是合法的聲明,但是含義大不同:
const int * p1; ?//指向整形常量?的指針,它指向的值不能修改
int * const p2; ?//指向整形的常量指針?,它不能在指向別的變量,但指向(變量)的值可以修改。
const int *const p3; //指向整形常量?的?常量指針?。它既不能再指向別的常量,指向的值也不能修改。
理解這些聲明的技巧在于,查看關(guān)鍵字const右邊來確定什么被聲明為常量 ,如果該關(guān)鍵字的右邊是類型,則值是常量;如果關(guān)鍵字的右邊是指針變量,則指針本身是常量。
簡(jiǎn)單說一下函數(shù)指針
從定義和用途兩方面來說一下自己的理解:
首先是定義:函數(shù)指針是指向函數(shù)的指針變量。函數(shù)指針本身首先是一個(gè)指針變量,該指針變量指向一個(gè)具體的函數(shù)。這正如用指針變量可指向整型變量、字符型、數(shù)組一樣,這里是指向函數(shù)。
在編譯時(shí),每一個(gè)函數(shù)都有一個(gè)入口地址,該入口地址就是函數(shù)指針?biāo)赶虻牡刂贰S辛酥赶蚝瘮?shù)的指針變量后,可用該指針變量調(diào)用函數(shù),就如同用指針變量可引用其他類型變量一樣,在這些概念上是大體一致的。
其次是用途:調(diào)用函數(shù)和做函數(shù)的參數(shù),比如回調(diào)函數(shù)。
示例:
char?*?fun(char?*?p)??{…}??//?函數(shù)fun
char?*?(*pf)(char?*?p);????//?函數(shù)指針pf
pf?=?fun;?????????????????//?函數(shù)指針pf指向函數(shù)fun
pf(p);????????????????????//?通過函數(shù)指針pf調(diào)用函數(shù)fun
堆和棧區(qū)別
棧
由編譯器進(jìn)行管理,在需要時(shí)由編譯器自動(dòng)分配空間,在不需要時(shí)候自動(dòng)回收空間,一般保存的是局部變量和函數(shù)參數(shù)等。
連續(xù)的內(nèi)存空間,在函數(shù)調(diào)用的時(shí)候,首先入棧的主函數(shù)的下一條可執(zhí)行指令的地址,然后是函數(shù)的各個(gè)參數(shù)。
大多數(shù)編譯器中,參數(shù)是從右向左入棧(原因在于采用這種順序,是為了讓程序員在使用C/C++的“函數(shù)參數(shù)長(zhǎng)度可變”這個(gè)特性時(shí)更方便。如果是從左向右壓棧,第一個(gè)參數(shù)(即描述可變參數(shù)表各變量類型的那個(gè)參數(shù))將被放在棧底,由于可變參的函數(shù)第一步就需要解析可變參數(shù)表的各參數(shù)類型,即第一步就需要得到上述參數(shù),因此,將它放在棧底是很不方便的。)本次函數(shù)調(diào)用結(jié)束時(shí),局部變量先出棧,然后是參數(shù),最后是棧頂指針最開始存放的地址,程序由該點(diǎn)繼續(xù)運(yùn)行,不會(huì)產(chǎn)生碎片。
棧是高地址向低地址擴(kuò)展,棧低高地址,空間較小。
堆
由程序員管理,需要手動(dòng) new malloc delete free 進(jìn)行分配和回收,如果不進(jìn)行回收的話,會(huì)造成內(nèi)存泄漏的問題。
不連續(xù)的空間,實(shí)際上系統(tǒng)中有一個(gè)空閑鏈表,當(dāng)有程序申請(qǐng)的時(shí)候,系統(tǒng)遍歷空閑鏈表找到第一個(gè)大于等于申請(qǐng)大小的空間分配給程序,一般在分配程序的時(shí)候,也會(huì)空間頭部寫入內(nèi)存大小,方便 delete 回收空間大小。當(dāng)然如果有剩余的,也會(huì)將剩余的插入到空閑鏈表中,這也是產(chǎn)生內(nèi)存碎片的原因。
堆是低地址向高地址擴(kuò)展,空間交大,較為靈活。
函數(shù)傳遞參數(shù)的幾種方式
值傳遞:?形參是實(shí)參的拷貝,函數(shù)內(nèi)部對(duì)形參的操作并不會(huì)影響到外部的實(shí)參。
指針傳遞:?也是值傳遞的一種方式,形參是指向?qū)崊⒌刂返闹羔槪?dāng)對(duì)形參的指向操作時(shí),就相當(dāng)于對(duì)實(shí)參本身進(jìn)行操作。
引用傳遞:?實(shí)際上就是把引用對(duì)象的地址放在了開辟的棧空間中,函數(shù)內(nèi)部對(duì)形參的任何操作可以直接映射到外部的實(shí)參上面。
new / delete ,malloc / free 區(qū)別
都可以用來在堆上分配和回收空間。new /delete 是操作符,malloc/free 是庫函數(shù)。
執(zhí)行 new 實(shí)際上執(zhí)行兩個(gè)過程:1.分配未初始化的內(nèi)存空間(malloc);2.使用對(duì)象的構(gòu)造函數(shù)對(duì)空間進(jìn)行初始化;返回空間的首地址。如果在第一步分配空間中出現(xiàn)問題,則拋出 std::bad_alloc 異常,或被某個(gè)設(shè)定的異常處理函數(shù)捕獲處理;如果在第二步構(gòu)造對(duì)象時(shí)出現(xiàn)異常,則自動(dòng)調(diào)用 delete 釋放內(nèi)存。
執(zhí)行 delete 實(shí)際上也有兩個(gè)過程:1. 使用析構(gòu)函數(shù)對(duì)對(duì)象進(jìn)行析構(gòu);2.回收內(nèi)存空間(free)。
以上也可以看出 new 和 malloc 的區(qū)別,new 得到的是經(jīng)過初始化的空間,而 malloc 得到的是未初始化的空間。所以 new 是 new 一個(gè)類型,而 malloc 則是malloc 一個(gè)字節(jié)長(zhǎng)度的空間。delete 和 free 同理,delete 不僅釋放空間還析構(gòu)對(duì)象,delete 一個(gè)類型,free 一個(gè)字節(jié)長(zhǎng)度的空間。
為什么有了 malloc/free 還需要 new/delete??因?yàn)閷?duì)于非內(nèi)部數(shù)據(jù)類型而言,光用 malloc/free 無法滿足動(dòng)態(tài)對(duì)象的要求。對(duì)象在創(chuàng)建的同時(shí)需要自動(dòng)執(zhí)行構(gòu)造函數(shù),對(duì)象在消亡以前要自動(dòng)執(zhí)行析構(gòu)函數(shù)。由于 mallo/free 是庫函數(shù)而不是運(yùn)算符,不在編譯器控制權(quán)限之內(nèi),不能夠把執(zhí)行的構(gòu)造函數(shù)和析構(gòu)函數(shù)的任務(wù)強(qiáng)加于 malloc/free,所以有了 new/delete 操作符。
volatile 和 extern 關(guān)鍵字
volatile 三個(gè)特性
易變性:在匯編層面反映出來,就是兩條語句,下一條語句不會(huì)直接使用上一條語句對(duì)應(yīng)的 volatile 變量的寄存器內(nèi)容,而是重新從內(nèi)存中讀取。
不可優(yōu)化性:volatile 告訴編譯器,不要對(duì)我這個(gè)變量進(jìn)行各種激進(jìn)的優(yōu)化,甚至將變量直接消除,保證程序員寫在代碼中的指令,一定會(huì)被執(zhí)行。
順序性:能夠保證 volatile 變量之間的順序性,編譯器不會(huì)進(jìn)行亂序優(yōu)化。
extern
在 C 語言中,修飾符 extern 用在變量或者函數(shù)的聲明前,用來說明 “此變量/函數(shù)是在別處定義的,要在此處引用”。
注意 extern 聲明的位置對(duì)其作用域也有關(guān)系,如果是在 main 函數(shù)中進(jìn)行聲明的,則只能在 main 函數(shù)中調(diào)用,在其它函數(shù)中不能調(diào)用。其實(shí)要調(diào)用其它文件中的函數(shù)和變量,只需把該文件用 #include 包含進(jìn)來即可,為啥要用 extern?因?yàn)橛?extern 會(huì)加速程序的編譯過程,這樣能節(jié)省時(shí)間。
在 C++ 中 extern 還有另外一種作用,用于指示 C 或者 C++函數(shù)的調(diào)用規(guī)范。比如在 C++ 中調(diào)用 C 庫函數(shù),就需要在 C++ 程序中用 extern “C” 聲明要引用的函數(shù)。這是給鏈接器用的,告訴鏈接器在鏈接的時(shí)候用C 函數(shù)規(guī)范來鏈接。主要原因是 C++ 和 C 程序編譯完成后在目標(biāo)代碼中命名規(guī)則不同,用此來解決名字匹配的問題。
define 和 const 區(qū)別
對(duì)于 define 來說,?宏定義實(shí)際上是在預(yù)編譯階段進(jìn)行處理,沒有類型,也就沒有類型檢查,僅僅做的是遇到宏定義進(jìn)行字符串的展開,遇到多少次就展開多少次,而且這個(gè)簡(jiǎn)單的展開過程中,很容易出現(xiàn)邊界效應(yīng),達(dá)不到預(yù)期的效果。因?yàn)?define 宏定義僅僅是展開,因此運(yùn)行時(shí)系統(tǒng)并不為宏定義分配內(nèi)存,但是從匯編 的角度來講,define 卻以立即數(shù)的方式保留了多份數(shù)據(jù)的拷貝。
對(duì)于 const 來說,?const 是在編譯期間進(jìn)行處理的,const 有類型,也有類型檢查,程序運(yùn)行時(shí)系統(tǒng)會(huì)為 const 常量分配內(nèi)存,而且從匯編的角度講,const 常量在出現(xiàn)的地方保留的是真正數(shù)據(jù)的內(nèi)存地址,只保留了一份數(shù)據(jù)的拷貝,省去了不必要的內(nèi)存空間。而且,有時(shí)編譯器不會(huì)為普通的 const 常量分配內(nèi)存,而是直接將 const 常量添加到符號(hào)表中,省去了讀取和寫入內(nèi)存的操作,效率更高。
計(jì)算下面幾個(gè)類的大小
class?A{};?sizeof(A)?=?1;?//空類在實(shí)例化時(shí)得到一個(gè)獨(dú)一無二的地址,所以為?1.
class?A{virtual?Fun(){}?};?sizeof(A)?=?4(32bit)/8(64bit)?//當(dāng)?C++?類中有虛函數(shù)的時(shí)候,會(huì)有一個(gè)指向虛函數(shù)表的指針(vptr)
class?A{static?int?a;?};?sizeof(A)?=?1;
class?A{int?a;?};?sizeof(A)?=?4;
class?A{static?int?a;?int?b;?};?sizeof(A)?=?4;
面向?qū)ο蟮娜筇匦裕⑴e例說明
C++ 面向?qū)ο蟮娜筇卣魇牵悍庋b、繼承、多態(tài)。
所謂封裝
就是把客觀事物封裝成抽象的類,并且類可以把自己的數(shù)據(jù)和方法只讓信任的類或者對(duì)象操作,對(duì)不可信的進(jìn)行信息隱藏。一個(gè)類就是一個(gè)封裝了數(shù)據(jù)以及操作這些數(shù)據(jù)的代碼的邏輯實(shí)體。在一個(gè)對(duì)象內(nèi)部,某些代碼或某些數(shù)據(jù)可以是私有的,不能被外界訪問。通過這種方式,對(duì)象對(duì)內(nèi)部數(shù)據(jù)提供了不同級(jí)別的保護(hù),以防止程序中無關(guān)的部分意外的改變或錯(cuò)誤的使用了對(duì)象的私有部分。
所謂繼承
是指可以讓某個(gè)類型的對(duì)象獲得另一個(gè)類型的對(duì)象的屬性的方法。它支持按級(jí)分類的概念。繼承是指這樣一種能力:它可以使用現(xiàn)有類的所有功能,并在無需重新編寫原來的類的情況下對(duì)這些功能進(jìn)行擴(kuò)展。通過繼承創(chuàng)建的新類稱為“子類”或者“派生類”,被繼承的類稱為“基類”、“父類”或“超類”。繼承的過程,就是從一般到特殊的過程。要實(shí)現(xiàn)繼承,可以通過“繼承”和“組合”來實(shí)現(xiàn)。
繼承概念的實(shí)現(xiàn)方式有兩類:
實(shí)現(xiàn)繼承:實(shí)現(xiàn)繼承是指直接使用基類的屬性和方法而無需額外編碼的能力。
接口繼承:接口繼承是指僅使用屬性和方法的名稱、但是子類必需提供實(shí)現(xiàn)的能力。
所謂多態(tài)
就是向不同的對(duì)象發(fā)送同一個(gè)消息,不同對(duì)象在接收時(shí)會(huì)產(chǎn)生不同的行為(即方法)。即一個(gè)接口,可以實(shí)現(xiàn)多種方法。
多態(tài)與非多態(tài)的實(shí)質(zhì)區(qū)別就是函數(shù)地址是早綁定還是晚綁定的。如果函數(shù)的調(diào)用,在編譯器編譯期間就可以確定函數(shù)的調(diào)用地址,并產(chǎn)生代碼,則是靜態(tài)的,即地址早綁定。而如果函數(shù)調(diào)用的地址不能在編譯器期間確定,需要在運(yùn)行時(shí)才確定,這就屬于晚綁定。
多態(tài)的實(shí)現(xiàn)
多態(tài)其實(shí)一般就是指繼承加虛函數(shù)實(shí)現(xiàn)的多態(tài),對(duì)于重載來說,實(shí)際上基于的原理是,編譯器為函數(shù)生成符號(hào)表時(shí)的不同規(guī)則,重載只是一種語言特性,與多態(tài)無關(guān),與面向?qū)ο笠矡o關(guān),但這又是 C++中增加的新規(guī)則,所以也算屬于 C++,所以如果非要說重載算是多態(tài)的一種,那就可以說:多態(tài)可以分為靜態(tài)多態(tài)和動(dòng)態(tài)多態(tài)。
靜態(tài)多態(tài)其實(shí)就是重載,因?yàn)殪o態(tài)多態(tài)是指在編譯時(shí)期就決定了調(diào)用哪個(gè)函數(shù),根據(jù)參數(shù)列表來決定;
動(dòng)態(tài)多態(tài)是指通過子類重寫父類的虛函數(shù)來實(shí)現(xiàn)的,因?yàn)槭窃谶\(yùn)行期間決定調(diào)用的函數(shù),所以稱為動(dòng)態(tài)多態(tài),
一般情況下我們不區(qū)分這兩個(gè)時(shí)所說的多態(tài)就是指動(dòng)態(tài)多態(tài)。
動(dòng)態(tài)多態(tài)的實(shí)現(xiàn)與虛函數(shù)表,虛函數(shù)指針相關(guān)。
擴(kuò)展:?子類是否要重寫父類的虛函數(shù)?子類繼承父類時(shí), 父類的純虛函數(shù)必須重寫,否則子類也是一個(gè)虛類不可實(shí)例化。定義純虛函數(shù)是為了實(shí)現(xiàn)一個(gè)接口,起到一個(gè)規(guī)范的作用,規(guī)范繼承這個(gè)類的程序員必須實(shí)現(xiàn)這個(gè)函數(shù)。
虛函數(shù)相關(guān)(虛函數(shù)表,虛函數(shù)指針),虛函數(shù)的實(shí)現(xiàn)原理
首先我們來說一下,C++中多態(tài)的表象,在基類的函數(shù)前加上 virtual 關(guān)鍵字,在派生類中重寫該函數(shù),運(yùn)行時(shí)將會(huì)根據(jù)對(duì)象的實(shí)際類型來調(diào)用相應(yīng)的函數(shù)。如果對(duì)象類型是派生類,就調(diào)用派生類的函數(shù),如果是基類,就調(diào)用基類的函數(shù)。
實(shí)際上,當(dāng)一個(gè)類中包含虛函數(shù)時(shí),編譯器會(huì)為該類生成一個(gè)虛函數(shù)表,保存該類中虛函數(shù)的地址,同樣,派生類繼承基類,派生類中自然一定有虛函數(shù),所以編譯器也會(huì)為派生類生成自己的虛函數(shù)表。當(dāng)我們定義一個(gè)派生類對(duì)象時(shí),編譯器檢測(cè)該類型有虛函數(shù),所以為這個(gè)派生類對(duì)象生成一個(gè)虛函數(shù)指針,指向該類型的虛函數(shù)表,這個(gè)虛函數(shù)指針的初始化是在構(gòu)造函數(shù)中完成的。
后續(xù)如果有一個(gè)基類類型的指針,指向派生類,那么當(dāng)調(diào)用虛函數(shù)時(shí),就會(huì)根據(jù)所指真正對(duì)象的虛函數(shù)表指針去尋找虛函數(shù)的地址,也就可以調(diào)用派生類的虛函數(shù)表中的虛函數(shù)以此實(shí)現(xiàn)多態(tài)。
補(bǔ)充:如果基類中沒有定義成 virtual,那么進(jìn)行 Base B; Derived D; Base *p = D; p->function(); 這種情況下調(diào)用的則是 Base 中的 function()。因?yàn)榛惡团缮愔卸紱]有虛函數(shù)的定義,那么編譯器就會(huì)認(rèn)為不用留給動(dòng)態(tài)多態(tài)的機(jī)會(huì),就事先進(jìn)行函數(shù)地址的綁定(早綁定),詳述過程就是,定義了一個(gè)派生類對(duì)象,首先要構(gòu)造基類的空間,然后構(gòu)造派生類的自身內(nèi)容,形成一個(gè)派生類對(duì)象,那么在進(jìn)行類型轉(zhuǎn)換時(shí),直接截取基類的部分的內(nèi)存,編譯器認(rèn)為類型就是基類,那么(函數(shù)符號(hào)表[不同于虛函數(shù)表的另一個(gè)表]中)綁定的函數(shù)地址也就是基類中函數(shù)的地址,所以執(zhí)行的是基類的函數(shù)。
編譯器處理虛函數(shù)表應(yīng)該如何處理
對(duì)于派生類來說,編譯器建立虛函數(shù)表的過程其實(shí)一共是三個(gè)步驟:
拷貝基類的虛函數(shù)表,如果是多繼承,就拷貝每個(gè)有虛函數(shù)基類的虛函數(shù)表
當(dāng)然還有一個(gè)基類的虛函數(shù)表和派生類自身的虛函數(shù)表共用了一個(gè)虛函數(shù)表,也稱為某個(gè)基類為派生類的主基類
查看派生類中是否有重寫基類中的虛函數(shù), 如果有,就替換成已經(jīng)重寫的虛函數(shù)地址;查看派生類是否有自身的虛函數(shù),如果有,就追加自身的虛函數(shù)到自身的虛函數(shù)表中。
析構(gòu)函數(shù)一般寫成虛函數(shù)的原因
直觀的講:是為了降低內(nèi)存泄漏的可能性。舉例來說就是,一個(gè)基類的指針指向一個(gè)派生類的對(duì)象,在使用完畢準(zhǔn)備銷毀時(shí),如果基類的析構(gòu)函數(shù)沒有定義成虛函數(shù),那 么編譯器根據(jù)指針類型就會(huì)認(rèn)為當(dāng)前對(duì)象的類型是基類,調(diào)用基類的析構(gòu)函數(shù) (該對(duì)象的析構(gòu)函數(shù)的函數(shù)地址早就被綁定為基類的析構(gòu)函數(shù)),僅執(zhí)行基類的析構(gòu),派生類的自身內(nèi)容將無法被析構(gòu),造成內(nèi)存泄漏。
如果基類的析構(gòu)函數(shù)定義成虛函數(shù),那么編譯器就可以根據(jù)實(shí)際對(duì)象,執(zhí)行派生類的析構(gòu)函數(shù),再執(zhí)行基類的析構(gòu)函數(shù),成功釋放內(nèi)存。
構(gòu)造函數(shù)為什么一般不定義為虛函數(shù)
虛函數(shù)調(diào)用只需要知道“部分的”信息,即只需要知道函數(shù)接口,而不需要知道對(duì)象的具體類型。但是,我們要?jiǎng)?chuàng)建一個(gè)對(duì)象的話,是需要知道對(duì)象的完整信息的。特別是,需要知道要?jiǎng)?chuàng)建對(duì)象的確切類型,因此,構(gòu)造函數(shù)不應(yīng)該被定義成虛函數(shù);
而且從目前編譯器實(shí)現(xiàn)虛函數(shù)進(jìn)行多態(tài)的方式來看,虛函數(shù)的調(diào)用是通過實(shí)例化之后對(duì)象的虛函數(shù)表指針來找到虛函數(shù)的地址進(jìn)行調(diào)用的,如果說構(gòu)造函數(shù)是虛的,那么虛函數(shù)表指針則是不存在的,無法找到對(duì)應(yīng)的虛函數(shù)表來調(diào)用虛函數(shù),那么這個(gè)調(diào)用實(shí)際上也是違反了先實(shí)例化后調(diào)用的準(zhǔn)則。
構(gòu)造函數(shù)或析構(gòu)函數(shù)中調(diào)用虛函數(shù)會(huì)怎樣
實(shí)際上是不應(yīng)該在構(gòu)造函數(shù)或析構(gòu)函數(shù)中調(diào)用虛函數(shù)的,因?yàn)檫@樣的調(diào)用其實(shí)并不會(huì)帶來所想要的效果。
舉例來說就是,有一個(gè)動(dòng)物的基類,基類中定義了一個(gè)動(dòng)物本身行為的虛函數(shù) action_type(),在基類的構(gòu)造函數(shù)中調(diào)用了這個(gè)虛函數(shù)。
派生類中重寫了這個(gè)虛函數(shù),我們期望著根據(jù)對(duì)象的真實(shí)類型不同,而調(diào)用各自實(shí)現(xiàn)的虛函數(shù),但實(shí)際上當(dāng)我們創(chuàng)建一個(gè)派生類對(duì)象時(shí),首先會(huì)創(chuàng)建派生類的基類部分,執(zhí)行基類的構(gòu)造函數(shù),此時(shí),派生類的自身部分還沒有被初始化,對(duì)于這種還沒有初始化的東西,C++選擇當(dāng)它們還不存在作為一種安全的方法。
也就是說構(gòu)造派生類的基類部分是,編譯器會(huì)認(rèn)為這就是一個(gè)基類類型的對(duì)象,然后調(diào)用基類類型中的虛函數(shù)實(shí)現(xiàn),并沒有按照我們想要的方式進(jìn)行。即對(duì)象在派生類構(gòu)造函數(shù)執(zhí)行前并不會(huì)成為一個(gè)派生類對(duì)象。
在析構(gòu)函數(shù)中也是同理,派生類執(zhí)行了析構(gòu)函數(shù)后,派生類的自身成員呈現(xiàn)未定義的狀態(tài),那么在執(zhí)行基類的析構(gòu)函數(shù)中是不可能調(diào)用到派生類重寫的方法的。所以說,我們不應(yīng)該在構(gòu)在函數(shù)或析構(gòu)函數(shù)中調(diào)用虛函數(shù),就算調(diào)用一般也不會(huì)達(dá)到我們想要的結(jié)果。
析構(gòu)函數(shù)的作用,如何起作用?
構(gòu)造函數(shù)只是起初始化值的作用,但實(shí)例化一個(gè)對(duì)象的時(shí)候,可以通過實(shí)例去傳遞參數(shù),從主函數(shù)傳遞到其他的函數(shù)里面,這樣就使其他的函數(shù)里面有值了。規(guī)則,只要你一實(shí)例化對(duì)象,系統(tǒng)自動(dòng)回調(diào)用一個(gè)構(gòu)造函數(shù),就是你不寫,編譯器也自動(dòng)調(diào)用一次。
析構(gòu)函數(shù)與構(gòu)造函數(shù)的作用相反,用于撤銷對(duì)象的一些特殊任務(wù)處理,可以是釋放對(duì)象分配的內(nèi)存空間;特點(diǎn):析構(gòu)函數(shù)與構(gòu)造函數(shù)同名,但該函數(shù)前面加~。
析構(gòu)函數(shù)沒有參數(shù),也沒有返回值,而且不能重載,在一個(gè)類中只能有一個(gè)析構(gòu)函數(shù)。當(dāng)撤銷對(duì)象時(shí),編譯器也會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)。每一個(gè)類必須有一個(gè)析構(gòu)函數(shù),用戶可以自定義析構(gòu)函數(shù),也可以是編譯器自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)。一般析構(gòu)函數(shù)定義為類的公有成員。
構(gòu)造函數(shù)的執(zhí)行順序?析構(gòu)函數(shù)的執(zhí)行順序?
構(gòu)造函數(shù)順序
基類構(gòu)造函數(shù)。如果有多個(gè)基類,則構(gòu)造函數(shù)的調(diào)用順序是某類在類派生表中出現(xiàn)的順序,而不是它們?cè)诔蓡T初始化表中的順序。
成員類對(duì)象構(gòu)造函數(shù)。如果有多個(gè)成員類對(duì)象則構(gòu)造函數(shù)的調(diào)用順序是對(duì)象在類中被聲明的順序,而不是它們出現(xiàn)在成員初始化表中的順序。
派生類構(gòu)造函數(shù)。
析構(gòu)函數(shù)順序
調(diào)用派生類的析構(gòu)函數(shù);
調(diào)用成員類對(duì)象的析構(gòu)函數(shù);
調(diào)用基類的析構(gòu)函數(shù)。
純虛函數(shù) (應(yīng)用于接口繼承和實(shí)現(xiàn)繼承)
實(shí)際上,純虛函數(shù)的出現(xiàn)就是為了讓繼承可以出現(xiàn)多種情況:
有時(shí)我們希望派生類只繼承成員函數(shù)的接口
有時(shí)我們又希望派生類既繼承成員函數(shù)的接口,又繼承成員函數(shù)的實(shí)現(xiàn),而且可以在派生類中可以重寫成員函數(shù)以實(shí)現(xiàn)多態(tài)
有的時(shí)候我們又希望派生類在繼承成員函數(shù)接口和實(shí)現(xiàn)的情況下,不能重寫缺省的實(shí)現(xiàn)。
其實(shí),聲明一個(gè)純虛函數(shù)的目的就是為了讓派生類只繼承函數(shù)的接口,而且派生類中必需提供一個(gè)這個(gè)純虛函數(shù)的實(shí)現(xiàn),否則含有純虛函數(shù)的類將是抽象類,不能進(jìn)行實(shí)例化。
對(duì)于純虛函數(shù)來說,我們其實(shí)是可以給它提供實(shí)現(xiàn)代碼的,但是由于抽象類不能實(shí)例化,調(diào)用這個(gè)實(shí)現(xiàn)的唯一方式是在派生類對(duì)象中指出其 class 名稱來調(diào)用。
靜態(tài)綁定和動(dòng)態(tài)綁定的介紹
說起靜態(tài)綁定和動(dòng)態(tài)綁定,我們首先要知道靜態(tài)類型和動(dòng)態(tài)類型,靜態(tài)類型就是它在程序中被聲明時(shí)所采用的類型,在編譯期間確定。動(dòng)態(tài)類型則是指“目前所指對(duì)象的實(shí)際類型”,在運(yùn)行期間確定。
靜態(tài)綁定,又名早綁定,綁定的是靜態(tài)類型,所對(duì)應(yīng)的函數(shù)或?qū)傩砸蕾囉趯?duì)象的靜態(tài)類型,發(fā)生在編譯期間。
動(dòng)態(tài)綁定,又名晚綁定,綁定的是動(dòng)態(tài)類型,所對(duì)應(yīng)的函數(shù)或?qū)傩砸蕾囉趧?dòng)態(tài)類型,發(fā)生在運(yùn)行期間。
比如說,virtual 函數(shù)是動(dòng)態(tài)綁定的,非虛函數(shù)是靜態(tài)綁定的,缺省參數(shù)值也是靜態(tài)綁定的。這里呢,就需要注意,我們不應(yīng)該重新定義繼承而來的缺省參數(shù),因?yàn)榧词刮覀冎囟x了,也不會(huì)起到效果。因?yàn)橐粋€(gè)基類的指針指向一個(gè)派生類對(duì)象,在派生類的對(duì)象中針對(duì)虛函數(shù)的參數(shù)缺省值進(jìn)行了重定義, 但是缺省參數(shù)值是靜態(tài)綁定的,靜態(tài)綁定綁定的是靜態(tài)類型相關(guān)的內(nèi)容,所以會(huì)出現(xiàn)一種派生類的虛函數(shù)實(shí)現(xiàn)方式結(jié)合了基類的缺省參數(shù)值的調(diào)用效果,這個(gè)與所期望的效果不同。
深拷貝和淺拷貝的區(qū)別(舉例說明深拷貝的安全性)
當(dāng)出現(xiàn)類的等號(hào)賦值時(shí),會(huì)調(diào)用拷貝函數(shù),在未定義顯示拷貝構(gòu)造函數(shù)的情況下, 系統(tǒng)會(huì)調(diào)用默認(rèn)的拷貝函數(shù)-即淺拷貝,它能夠完成成員的一一復(fù)制。當(dāng)數(shù)據(jù)成員中沒有指針時(shí),淺拷貝是可行的。
但當(dāng)數(shù)據(jù)成員中有指針時(shí),如果采用簡(jiǎn)單的淺拷貝,則兩類中的兩個(gè)指針指向同一個(gè)地址,當(dāng)對(duì)象快要結(jié)束時(shí),會(huì)調(diào)用兩次析構(gòu)函數(shù),而導(dǎo)致指野指針的問題。
所以,這時(shí)必需采用深拷貝。深拷貝與淺拷貝之間的區(qū)別就在于深拷貝會(huì)在堆內(nèi)存中另外申請(qǐng)空間來存儲(chǔ)數(shù)據(jù),從而也就解決來野指針的問題。簡(jiǎn)而言之,當(dāng)數(shù)據(jù)成員中有指針時(shí),必需要用深拷貝更加安全。
什么情況下會(huì)調(diào)用拷貝構(gòu)造函數(shù)(三種情況)
類的對(duì)象需要拷貝時(shí),拷貝構(gòu)造函數(shù)將會(huì)被調(diào)用,以下的情況都會(huì)調(diào)用拷貝構(gòu)造函數(shù):
一個(gè)對(duì)象以值傳遞的方式傳入函數(shù)體,需要拷貝構(gòu)造函數(shù)創(chuàng)建一個(gè)臨時(shí)對(duì)象壓入到棧空間中。
一個(gè)對(duì)象以值傳遞的方式從函數(shù)返回,需要執(zhí)行拷貝構(gòu)造函數(shù)創(chuàng)建一個(gè)臨時(shí)對(duì)象作為返回值。
一個(gè)對(duì)象需要通過另外一個(gè)對(duì)象進(jìn)行初始化。
為什么拷貝構(gòu)造函數(shù)必需時(shí)引用傳遞,不能是值傳遞?
為了防止遞歸調(diào)用。當(dāng)一個(gè)對(duì)象需要以值方式進(jìn)行傳遞時(shí),編譯器會(huì)生成代碼調(diào)用它的拷貝構(gòu)造函數(shù)生成一個(gè)副本,如果類 A 的拷貝構(gòu)造函數(shù)的參數(shù)不是引用傳遞,而是采用值傳遞,那么就又需要為了創(chuàng)建傳遞給拷貝構(gòu)造函數(shù)的參數(shù)的臨時(shí)對(duì)象,而又一次調(diào)用類 A 的拷貝構(gòu)造函數(shù),這就是一個(gè)無限遞歸。
結(jié)構(gòu)體內(nèi)存對(duì)齊方式和為什么要進(jìn)行內(nèi)存對(duì)齊?
首先我們來說一下結(jié)構(gòu)體中內(nèi)存對(duì)齊的規(guī)則:
對(duì)于結(jié)構(gòu)體中的各個(gè)成員,第一個(gè)成員位于偏移為 0 的位置,以后的每個(gè)數(shù)據(jù)成員的偏移量必須是 min(#pragma pack() 制定的數(shù),數(shù)據(jù)成員本身長(zhǎng)度) 的倍數(shù)。
在所有的數(shù)據(jù)成員完成各自對(duì)齊之后,結(jié)構(gòu)體或聯(lián)合體本身也要進(jìn)行對(duì)齊,整體長(zhǎng)度是 min(#pragma pack()制定的數(shù),長(zhǎng)度最長(zhǎng)的數(shù)據(jù)成員的長(zhǎng)度) 的倍數(shù)。
那么內(nèi)存對(duì)齊的作用是什么呢?
經(jīng)過內(nèi)存對(duì)齊之后,CPU 的內(nèi)存訪問速度大大提升。因?yàn)?CPU 把內(nèi)存當(dāng)成是一塊一塊的,塊的大小可以是 2,4,8,16 個(gè)字節(jié),因此 CPU 在讀取內(nèi)存的時(shí)候是一塊一塊進(jìn)行讀取的,塊的大小稱為內(nèi)存讀取粒度。比如說 CPU 要讀取一個(gè) 4 個(gè)字節(jié)的數(shù)據(jù)到寄存器中(假設(shè)內(nèi)存讀取粒度是 4),如果數(shù)據(jù)是從 0 字節(jié)開始的,那么直接將 0-3 四個(gè)字節(jié)完全讀取到寄存器中進(jìn)行處理即可。
如果數(shù)據(jù)是從 1 字節(jié)開始的,就首先要將前 4 個(gè)字節(jié)讀取到寄存器,并再次讀取 4-7 個(gè)字節(jié)數(shù)據(jù)進(jìn)入寄存器,接著把 0 字節(jié),5,6,7 字節(jié)的數(shù)據(jù)剔除,最后合并 1,2,3,4 字節(jié)的數(shù)據(jù)進(jìn)入寄存器,所以說,當(dāng)內(nèi)存沒有對(duì)齊時(shí),寄存器進(jìn)行了很多額外的操作,大大降低了 CPU 的性能。
另外,還有一個(gè)就是,有的 CPU 遇到未進(jìn)行內(nèi)存對(duì)齊的處理直接拒絕處理,不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù),某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。所以內(nèi)存對(duì)齊還有利于平臺(tái)移植。
內(nèi)存泄漏的定義,如何檢測(cè)與避免?
定義:內(nèi)存泄漏簡(jiǎn)單的說就是申請(qǐng)了一塊內(nèi)存空間,使用完畢后沒有釋放掉。它的一般表現(xiàn)方式是程序運(yùn)行時(shí)間越長(zhǎng),占用內(nèi)存越多,最終用盡全部?jī)?nèi)存,整個(gè)系統(tǒng)崩潰。由程序申請(qǐng)的一塊內(nèi)存,且沒有任何一個(gè)指針指向它,那么這塊內(nèi)存就泄漏了。
如何檢測(cè)內(nèi)存泄漏
首先可以通過觀察猜測(cè)是否可能發(fā)生內(nèi)存泄漏,Linux 中使用 swap 命令觀察還有多少可用的交換空間,在一兩分鐘內(nèi)鍵入該命令三到四次,看看可用的交換區(qū)是否在減少。
還可以使用 其他一些 /usr/bin/stat 工具如 netstat、vmstat 等。如發(fā)現(xiàn)波段有內(nèi)存被分配且從不釋放,一個(gè)可能的解釋就是有個(gè)進(jìn)程出現(xiàn)了內(nèi)存泄漏。
當(dāng)然也有用于內(nèi)存調(diào)試,內(nèi)存泄漏檢測(cè)以及性能分析的軟件開發(fā)工具 valgrind 這樣的工具來進(jìn)行內(nèi)存泄漏的檢測(cè)。
說一下 define、const、typedef、inline 使用方法?
1、const 與 #define 的區(qū)別
const 定義的常量是變量帶類型,而 #define 定義的只是個(gè)常數(shù)不帶類型;
define 只在預(yù)處理階段起作用,簡(jiǎn)單的文本替換,而 const 在編譯、鏈接過程中起作用;
define 只是簡(jiǎn)單的字符串替換沒有類型檢查。而const是有數(shù)據(jù)類型的,是要進(jìn)行判斷的,可以避免一些低級(jí)錯(cuò)誤;
define 預(yù)處理后,占用代碼段空間,const 占用數(shù)據(jù)段空間;
const 不能重定義,而 define 可以通過 #undef 取消某個(gè)符號(hào)的定義,進(jìn)行重定義;
define 獨(dú)特功能,比如可以用來防止文件重復(fù)引用。
2、#define 和別名 typedef 的區(qū)別
執(zhí)行時(shí)間不同,typedef 在編譯階段有效,typedef 有類型檢查的功能;#define 是宏定義,發(fā)生在預(yù)處理階段,不進(jìn)行類型檢查;
功能差異,typedef 用來定義類型的別名,定義與平臺(tái)無關(guān)的數(shù)據(jù)類型,與 struct 的結(jié)合使用等。
#define 不只是可以為類型取別名,還可以定義常量、變量、編譯開關(guān)等。
作用域不同,#define 沒有作用域的限制,只要是之前預(yù)定義過的宏,在以后的程序中都可以使用。
而 typedef 有自己的作用域。
3、define 與 inline 的區(qū)別
#define是關(guān)鍵字,inline是函數(shù);
宏定義在預(yù)處理階段進(jìn)行文本替換,inline 函數(shù)在編譯階段進(jìn)行替換;
inline 函數(shù)有類型檢查,相比宏定義比較安全;
預(yù)處理,編譯,匯編,鏈接程序的區(qū)別
一段高級(jí)語言代碼經(jīng)過四個(gè)階段的處理形成可執(zhí)行的目標(biāo)二進(jìn)制代碼。
預(yù)處理器→編譯器→匯編器→鏈接器:最難理解的是編譯與匯編的區(qū)別。
這里采用《深入理解計(jì)算機(jī)系統(tǒng)》的說法。
預(yù)處理階段:?寫好的高級(jí)語言的程序文本比如 hello.c,預(yù)處理器根據(jù) #開頭的命令,修改原始的程序,如#include
編譯階段:?編譯器將 hello.i 文件翻譯成文本文件 *hello.s,這個(gè)是匯編語言程序。高級(jí)語言是源程序。所以注意概念之間的區(qū)別。匯編語言程序是干嘛的?每條語句都以標(biāo)準(zhǔn)的文本格式確切描述一條低級(jí)機(jī)器語言指令。*不同的高級(jí)語言翻譯的匯編語言相同。
匯編階段:?匯編器將 hello.s 翻譯成機(jī)器語言指令。把這些指令打包成可重定位目標(biāo)程序,即 .o文件。hello.o是一個(gè)二進(jìn)制文件,它的字節(jié)碼是機(jī)器語言指令,不再是字符。前面兩個(gè)階段都還有字符。
鏈接階段:?比如 hello 程序調(diào)用 printf 程序,它是每個(gè) C 編譯器都會(huì)提供的標(biāo)準(zhǔn)庫 C 的函數(shù)。這個(gè)函數(shù)存在于一個(gè)名叫 printf.o 的單獨(dú)編譯好的目標(biāo)文件中,這個(gè)文件將以某種方式合并到 hello.o 中。鏈接器就負(fù)責(zé)這種合并。得到的是可執(zhí)行目標(biāo)文件。
說一下 fork,wait,exec 函數(shù)
父進(jìn)程產(chǎn)生子進(jìn)程使用 fork 拷貝出來一個(gè)父進(jìn)程的副本,此時(shí)只拷貝了父進(jìn)程的頁表,兩個(gè)進(jìn)程都讀同一塊內(nèi)存。
當(dāng)有進(jìn)程寫的時(shí)候使用寫實(shí)拷貝機(jī)制分配內(nèi)存,exec 函數(shù)可以加載一個(gè) elf 文件去替換父進(jìn)程,從此父進(jìn)程和子進(jìn)程就可以運(yùn)行不同的程序了。
fork 從父進(jìn)程返回子進(jìn)程的 pid,從子進(jìn)程返回 0,調(diào)用了 wait 的父進(jìn)程將會(huì)發(fā)生阻塞,直到有子進(jìn)程狀態(tài)改變,執(zhí)行成功返回 0,錯(cuò)誤返回 -1。
exec 執(zhí)行成功則子進(jìn)程從新的程序開始運(yùn)行,無返回值,執(zhí)行失敗返回 -1。
動(dòng)態(tài)編譯與靜態(tài)編譯
靜態(tài)編譯,編譯器在編譯可執(zhí)行文件時(shí),把需要用到的對(duì)應(yīng)動(dòng)態(tài)鏈接庫中的部分提取出來,連接到可執(zhí)行文件中去,使可執(zhí)行文件在運(yùn)行時(shí)不需要依賴于動(dòng)態(tài)鏈接庫;
動(dòng)態(tài)編譯,可執(zhí)行文件需要附帶一個(gè)動(dòng)態(tài)鏈接庫,在執(zhí)行時(shí),需要調(diào)用其對(duì)應(yīng)動(dòng)態(tài)鏈接庫的命令。所以其優(yōu)點(diǎn)一方面是縮小了執(zhí)行文件本身的體積,另一方面是加快了編譯速度,節(jié)省了系統(tǒng)資源。缺點(diǎn)是哪怕是很簡(jiǎn)單的程序,只用到了鏈接庫的一兩條命令,也需要附帶一個(gè)相對(duì)龐大的鏈接庫;二是如果其他計(jì)算機(jī)上沒有安裝對(duì)應(yīng)的運(yùn)行庫,則用動(dòng)態(tài)編譯的可執(zhí)行文件就不能運(yùn)行。
動(dòng)態(tài)鏈接和靜態(tài)鏈接區(qū)別
靜態(tài)連接庫就是把 (lib) 文件中用到的函數(shù)代碼直接鏈接進(jìn)目標(biāo)程序,程序運(yùn)行的時(shí)候不再需要其它的庫文件;動(dòng)態(tài)鏈接就是把調(diào)用的函數(shù)所在文件模塊(DLL)和調(diào)用函數(shù)在文件中的位置等信息鏈接進(jìn)目標(biāo)程序,程序運(yùn)行的時(shí)候再從 DLL 中尋找相應(yīng)函數(shù)代碼,因此需要相應(yīng) DLL 文件的支持。
靜態(tài)鏈接庫與動(dòng)態(tài)鏈接庫都是共享代碼的方式,如果采用靜態(tài)鏈接庫,則無論你愿不愿意,lib 中的指令都全部被直接包含在最終生成的 EXE 文件中了。但是若使用 DLL,該 DLL 不必被包含在最終 EXE 文件中,EXE 文件執(zhí)行時(shí)可以“動(dòng)態(tài)”地引用和卸載這個(gè)與 EXE 獨(dú)立的 DLL 文件。
靜態(tài)鏈接庫和動(dòng)態(tài)鏈接庫的另外一個(gè)區(qū)別在于靜態(tài)鏈接庫中不能再包含其他的動(dòng)態(tài)鏈接庫或者靜態(tài)庫,而在動(dòng)態(tài)鏈接庫中還可以再包含其他的動(dòng)態(tài)或靜態(tài)鏈接庫。
動(dòng)態(tài)庫就是在需要調(diào)用其中的函數(shù)時(shí),根據(jù)函數(shù)映射表找到該函數(shù)然后調(diào)入堆棧執(zhí)行。如果在當(dāng)前工程中有多處對(duì)dll文件中同一個(gè)函數(shù)的調(diào)用,那么執(zhí)行時(shí),這個(gè)函數(shù)只會(huì)留下一份拷貝。但如果有多處對(duì) lib 文件中同一個(gè)函數(shù)的調(diào)用,那么執(zhí)行時(shí)該函數(shù)將在當(dāng)前程序的執(zhí)行空間里留下多份拷貝,而且是一處調(diào)用就產(chǎn)生一份拷貝。
動(dòng)態(tài)聯(lián)編與靜態(tài)聯(lián)編
在 C++ 中,聯(lián)編是指一個(gè)計(jì)算機(jī)程序的不同部分彼此關(guān)聯(lián)的過程。按照聯(lián)編所進(jìn)行的階段不同,可以分為靜態(tài)聯(lián)編和動(dòng)態(tài)聯(lián)編;
靜態(tài)聯(lián)編是指聯(lián)編工作在編譯階段完成的,這種聯(lián)編過程是在程序運(yùn)行之前完成的,又稱為早期聯(lián)編。要實(shí)現(xiàn)靜態(tài)聯(lián)編,在編譯階段就必須確定程序中的操作調(diào)用(如函數(shù)調(diào)用)與執(zhí)行該操作代碼間的關(guān)系,確定這種關(guān)系稱為束定,在編譯時(shí)的束定稱為靜態(tài)束定。靜態(tài)聯(lián)編對(duì)函數(shù)的選擇是基于指向?qū)ο蟮闹羔樆蛘咭玫念愋汀F鋬?yōu)點(diǎn)是效率高,但靈活性差。
動(dòng)態(tài)聯(lián)編是指聯(lián)編在程序運(yùn)行時(shí)動(dòng)態(tài)地進(jìn)行,根據(jù)當(dāng)時(shí)的情況來確定調(diào)用哪個(gè)同名函數(shù),實(shí)際上是在運(yùn)行時(shí)虛函數(shù)的實(shí)現(xiàn)。這種聯(lián)編又稱為晚期聯(lián)編,或動(dòng)態(tài)束定。動(dòng)態(tài)聯(lián)編對(duì)成員函數(shù)的選擇是基于對(duì)象的類型,針對(duì)不同的對(duì)象類型將做出不同的編譯結(jié)果。
C++中一般情況下的聯(lián)編是靜態(tài)聯(lián)編,但是當(dāng)涉及到多態(tài)性和虛函數(shù)時(shí)應(yīng)該使用動(dòng)態(tài)聯(lián)編。動(dòng)態(tài)聯(lián)編的優(yōu)點(diǎn)是靈活性強(qiáng),但效率低。動(dòng)態(tài)聯(lián)編規(guī)定,只能通過指向基類的指針或基類對(duì)象的引用來調(diào)用虛函數(shù),其格式為:指向基類的指針變量名->虛函數(shù)名(實(shí)參表)或基類對(duì)象的引用名.虛函數(shù)名(實(shí)參表)
實(shí)現(xiàn)動(dòng)態(tài)聯(lián)編三個(gè)條件:
必須把動(dòng)態(tài)聯(lián)編的行為定義為類的虛函數(shù);
類之間應(yīng)滿足子類型關(guān)系,通常表現(xiàn)為一個(gè)類從另一個(gè)類公有派生而來;
必須先使用基類指針指向子類型的對(duì)象,然后直接或間接使用基類指針調(diào)用虛函數(shù);
什么是類的繼承?
類與類之間的關(guān)系
has-A 包含關(guān)系,用以描述一個(gè)類由多個(gè)部件類構(gòu)成,實(shí)現(xiàn) has-A 關(guān)系用類的成員屬性表示,即一個(gè)類的成員屬性是另一個(gè)已經(jīng)定義好的類;
use-A,一個(gè)類使用另一個(gè)類,通過類之間的成員函數(shù)相互聯(lián)系,定義友元或者通過傳遞參數(shù)的方式來實(shí)現(xiàn);
is-A,繼承關(guān)系,關(guān)系具有傳遞性;
繼承的相關(guān)概念
所謂的繼承就是一個(gè)類繼承了另一個(gè)類的屬性和方法,這個(gè)新的類包含了上一個(gè)類的屬性和方法,被稱為子類或者派生類,被繼承的類稱為父類或者基類;
繼承的特點(diǎn)
子類擁有父類的所有屬性和方法,子類可以擁有父類沒有的屬性和方法,子類對(duì)象可以當(dāng)做父類對(duì)象使用;
繼承中的訪問控制
public、protected、private
繼承中的構(gòu)造和析構(gòu)函數(shù)
繼承中的兼容性原則
什么是組合?
一個(gè)類里面的數(shù)據(jù)成員是另一個(gè)類的對(duì)象,即內(nèi)嵌其他類的對(duì)象作為自己的成員;創(chuàng)建組合類的對(duì)象:首先創(chuàng)建各個(gè)內(nèi)嵌對(duì)象,難點(diǎn)在于構(gòu)造函數(shù)的設(shè)計(jì)。創(chuàng)建對(duì)象時(shí)既要對(duì)基本類型的成員進(jìn)行初始化,又要對(duì)內(nèi)嵌對(duì)象進(jìn)行初始化。
創(chuàng)建組合類對(duì)象,構(gòu)造函數(shù)的執(zhí)行順序:先調(diào)用內(nèi)嵌對(duì)象的構(gòu)造函數(shù),然后按照內(nèi)嵌對(duì)象成員在組合類中的定義順序,與組合類構(gòu)造函數(shù)的初始化列表順序無關(guān)。然后執(zhí)行組合類構(gòu)造函數(shù)的函數(shù)體,析構(gòu)函數(shù)調(diào)用順序相反。
構(gòu)造函數(shù)析構(gòu)函數(shù)可否拋出異常
C++ 只會(huì)析構(gòu)已經(jīng)完成的對(duì)象,對(duì)象只有在其構(gòu)造函數(shù)執(zhí)行完畢才算是完全構(gòu)造妥當(dāng)。在構(gòu)造函數(shù)中發(fā)生異常,控制權(quán)轉(zhuǎn)出構(gòu)造函數(shù)之外。因此,在對(duì)象 b 的構(gòu)造函數(shù)中發(fā)生異常,對(duì)象b的析構(gòu)函數(shù)不會(huì)被調(diào)用。因此會(huì)造成內(nèi)存泄漏。
用 auto_ptr 對(duì)象來取代指針類成員,便對(duì)構(gòu)造函數(shù)做了強(qiáng)化,免除了拋出異常時(shí)發(fā)生資源泄漏的危機(jī),不再需要在析構(gòu)函數(shù)中手動(dòng)釋放資源;
如果控制權(quán)基于異常的因素離開析構(gòu)函數(shù),而此時(shí)正有另一個(gè)異常處于作用狀態(tài),C++ 會(huì)調(diào)用 terminate 函數(shù)讓程序結(jié)束;
如果異常從析構(gòu)函數(shù)拋出,而且沒有在當(dāng)?shù)剡M(jìn)行捕捉,那個(gè)析構(gòu)函數(shù)便是執(zhí)行不全的。如果析構(gòu)函數(shù)執(zhí)行不全,就是沒有完成他應(yīng)該執(zhí)行的每一件事情。
類如何實(shí)現(xiàn)只能靜態(tài)分配和只能動(dòng)態(tài)分配
前者是把 new、delete 運(yùn)算符重載為 private 屬性。
后者是把構(gòu)造、析構(gòu)函數(shù)設(shè)為 protected 屬性,再用子類來動(dòng)態(tài)創(chuàng)建
建立類的對(duì)象有兩種方式:
靜態(tài)建立,靜態(tài)建立一個(gè)類對(duì)象,就是由編譯器為對(duì)象在棧空間中分配內(nèi)存;
動(dòng)態(tài)建立,A *p = new A(); 動(dòng)態(tài)建立一個(gè)類對(duì)象,就是使用 new 運(yùn)算符為對(duì)象在堆空間中分配內(nèi)存。這個(gè)過程分為兩步,第一步執(zhí)行 operator new() 函數(shù),在堆中搜索一塊內(nèi)存并進(jìn)行分配;第二步調(diào)用類構(gòu)造函數(shù)構(gòu)造對(duì)象;
只有使用 new 運(yùn)算符,對(duì)象才會(huì)被建立在堆上,因此只要限制 new 運(yùn)算符就可以實(shí)現(xiàn)類對(duì)象只能建立在棧上。可以將 new 運(yùn)算符設(shè)為私有。
何時(shí)需要成員初始化列表?過程是什么?
當(dāng)初始化一個(gè)引用成員變量時(shí);
初始化一個(gè) const 成員變量時(shí);
當(dāng)調(diào)用一個(gè)基類的構(gòu)造函數(shù),而構(gòu)造函數(shù)擁有一組參數(shù)時(shí);
當(dāng)調(diào)用一個(gè)成員類的構(gòu)造函數(shù),而他擁有一組參數(shù);
編譯器會(huì)一一操作初始化列表,以適當(dāng)順序在構(gòu)造函數(shù)之內(nèi)安插初始化操作,并且在任何顯示用戶代碼前。list中的項(xiàng)目順序是由類中的成員聲明順序決定的,不是初始化列表中的排列順序決定的。
程序員定義的析構(gòu)函數(shù)被擴(kuò)展的過程?
析構(gòu)函數(shù)函數(shù)體被執(zhí)行;
如果 class 擁有成員類對(duì)象,而后者擁有析構(gòu)函數(shù),那么它們會(huì)以其聲明順序的相反順序被調(diào)用;
如果對(duì)象有一個(gè) vptr,現(xiàn)在被重新定義
如果有任何直接的上一層非虛基類擁有析構(gòu)函數(shù),則它們會(huì)以聲明順序被調(diào)用;
如果任何虛基類擁有析構(gòu)函數(shù)
構(gòu)造函數(shù)的執(zhí)行算法?
在派生類構(gòu)造函數(shù)中,所有的虛基類及上一層基類的構(gòu)造函數(shù)調(diào)用;
對(duì)象的 vptr 被初始化;
如果有成員初始化列表,將在構(gòu)造函數(shù)體內(nèi)擴(kuò)展開來,這必須在 vptr 被設(shè)定之后才做;
執(zhí)行程序員所提供的代碼;
構(gòu)造函數(shù)的擴(kuò)展過程?
記錄在成員初始化列表中的數(shù)據(jù)成員初始化操作會(huì)被放在構(gòu)造函數(shù)的函數(shù)體內(nèi),并與成員的聲明順序?yàn)轫樞颍?/p>
如果一個(gè)成員并沒有出現(xiàn)在成員初始化列表中,但它有一個(gè)默認(rèn)構(gòu)造函數(shù),那么默認(rèn)構(gòu)造函數(shù)必須被調(diào)用;
如果 class 有虛表,那么它必須被設(shè)定初值;
所有上一層的基類構(gòu)造函數(shù)必須被調(diào)用;
所有虛基類的構(gòu)造函數(shù)必須被調(diào)用。
哪些函數(shù)不能是虛函數(shù)
構(gòu)造函數(shù),構(gòu)造函數(shù)初始化對(duì)象,派生類必須知道基類函數(shù)干了什么,才能進(jìn)行構(gòu)造;當(dāng)有虛函數(shù)時(shí),每一個(gè)類有一個(gè)虛表,每一個(gè)對(duì)象有一個(gè)虛表指針,虛表指針在構(gòu)造函數(shù)中初始化;
內(nèi)聯(lián)函數(shù),內(nèi)聯(lián)函數(shù)表示在編譯階段進(jìn)行函數(shù)體的替換操作,而虛函數(shù)意味著在運(yùn)行期間進(jìn)行類型確定,所以內(nèi)聯(lián)函數(shù)不能是虛函數(shù);
靜態(tài)函數(shù),靜態(tài)函數(shù)不屬于對(duì)象屬于類,靜態(tài)成員函數(shù)沒有this指針,因此靜態(tài)函數(shù)設(shè)置為虛函數(shù)沒有任何意義。
友元函數(shù),友元函數(shù)不屬于類的成員函數(shù),不能被繼承。對(duì)于沒有繼承特性的函數(shù)沒有虛函數(shù)的說法。
普通函數(shù),普通函數(shù)不屬于類的成員函數(shù),不具有繼承特性,因此普通函數(shù)沒有虛函數(shù)。
C++ 面向?qū)ο缶幊?/p>
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。