用于大型程序的工具
- 與僅需幾個程序員就能開發(fā)完成的系統(tǒng)相比,大規(guī)模編程對程序設(shè)計語言的要求更高,大規(guī)模應(yīng)用程序的特殊要求包括:
- [x] 在獨立開發(fā)的子系統(tǒng)之間協(xié)同處理錯誤的能力
- [x] 使用各種庫(可能包含獨立開發(fā)的庫)進行協(xié)同開發(fā)的能力
- [x] 對比復雜的應(yīng)用概念建模的能力
異常處理
- 異常處理機制允許程序中獨立開發(fā)的部分能夠在運行時就出現(xiàn)的問題進行通信并作出相應(yīng)的處理,異常使得我們能夠?qū)栴}的檢測與解決過程分離開來,程序的一部分檢測問題的出現(xiàn),然后使得我們能夠?qū)栴}的檢測與解決過程分離開來,程序的一部分檢測問題的出現(xiàn),然后解決該問題的任務(wù)傳遞給程序的另一部分,檢測環(huán)節(jié)無需知道問題處理模塊的所有細節(jié),反之亦然
拋出異常
- 在C++語言中,我們通過拋出
throwing一條表達式來引發(fā)raised一個異常,被拋出的表達式的類型以及當前的調(diào)用鏈共同決定了哪段處理代碼將被用來處理該異常
- 當執(zhí)行一個throw時,跟在throw后面的語句將不再被執(zhí)行,相反,程序的控制權(quán)從throw轉(zhuǎn)移到與之匹配的catch模塊,該catch可能是同一個函數(shù)中的局部catch,也可能位于直接或間接調(diào)用了發(fā)生異常的函數(shù)的另一個函數(shù)中
- 控制權(quán)從一處轉(zhuǎn)移到另一處,這有兩個重要的含義:
- [x] 沿著調(diào)用鏈的函數(shù)可能會提早退出
- [x] 一旦程序開始執(zhí)行異常處理代碼,則沿著調(diào)用鏈創(chuàng)建的對象將被銷毀
- 因為跟在throw后面的語句將不再被執(zhí)行,所以throw語句的用法有些類似于return,它通常作為條件語句的而一部分或者作為某個函數(shù)的最后(或者唯一)一條語句
- 一個異常如果沒有被捕獲,則它將終止當前的程序
- 析構(gòu)函數(shù)總是會被執(zhí)行的,但是函數(shù)中負責釋放資源的代碼卻是可能被跳過,這一特點對于我們?nèi)绾巫柚钩绦蚪Y(jié)構(gòu)有重要影響,如果我們使用類來控制資源的分配,就能確保無論函數(shù)正常結(jié)束還是遭遇異常,資源都能被正確的釋放
- 由于棧展開可能使用析構(gòu)函數(shù),析構(gòu)函數(shù)不應(yīng)該拋出不屬于它自身處理的異常,即是,如果析構(gòu)函數(shù)需要執(zhí)行某個可能拋出異常的操作,則該操作應(yīng)該被放置在一個try語句塊當中,并且在析構(gòu)函數(shù)內(nèi)部得到處理
- 在實際的編成過程中,因為析構(gòu)函數(shù)僅僅是釋放資源,所以它不太可能拋出異常,所有標準庫類型都能確保它們的析構(gòu)函數(shù)不會引發(fā)異常
- 在棧展開的過程張,運行類類型的局部對象的析構(gòu)函數(shù)。因為這些析構(gòu)函數(shù)是自動執(zhí)行的,所以它們不應(yīng)該拋出異常,一旦在棧展開的過程中析構(gòu)函數(shù)拋出了異常,并且析構(gòu)函數(shù)自身沒能捕獲到該異常,則程序?qū)⒈唤K止
捕獲異常
-
catch子句中的異常聲明看起來像是只包含一個形參的函數(shù)形參列表,如果catch無需訪問拋出的表達式的話,則我們可以忽略捕獲形參的名字
- 通常情況下,如果catch接受的異常與某個繼承體系有關(guān),則最好將該catch的參數(shù)定義成引用類型
noexcept異常聲明
- 在C++11的新標準中,我呢可以通過moexcept說明指定某個函數(shù)不會拋出異常,其形式是關(guān)鍵字noexcept緊跟在函數(shù)?形參列表后面,用以標識該函數(shù)不會拋出異常
void recoup(int) noexcept; //不會拋出異常
void alloc(int); //可能拋出異常
- 上述的recoup做了
不拋出說明
- 對于一個函數(shù)來說,noexcept說明要么出現(xiàn)在該函數(shù)的所有聲明語句和定義語句中,要么一次也不出現(xiàn)
- except可以用在兩個情況下
- [x] 確認我們的函數(shù)不會拋出異常
- [x] 我們根本不知道該如何處理異常
- 通常情況下,編譯器不能也不必在編譯時驗證異常說明
- noexcept說明符接受一個可選的實參,該實參必須能轉(zhuǎn)換為bool類型:如果實參是true,則函數(shù)不會拋出異常;如果實參是false,則函數(shù)可能拋出異常
- noexcept有兩層含義:當跟在函數(shù)參數(shù)列表后面時它是異常說明符;而當作為noexcept異常說明的bool實參出現(xiàn)時,它是一個運算符
命名空間
- 當應(yīng)用程序用到多個供應(yīng)商提供的庫時,不可避免的會發(fā)生某些名字相互沖突的情況,多個庫名字放置在全局命名空間中將引發(fā)
命名空間污染
- 傳統(tǒng)上,程序員通過將其定義的全局實體名字設(shè)置的很長4來避免命名空間污染問題,這樣的名字中通常包含名字所屬庫的前綴部分
- 命名空間為防止名字沖突提供了更加可控的機制,命名空間分割了全局命名空間,其中每個命名空間是一個作用域,通過在某個命名空間中定義庫的名字,庫的作者(以及用戶)可以避免全局名字固有的限制
命名空間的定義
- 一個命名空間的定義包含兩部分:首先是關(guān)鍵字
namespace,隨后是命名空間的名字,在命名空間名字后面是一系列由花括號括起來的聲明和定義
- 只要能出現(xiàn)在全局作用域中的聲明就能置于命名空間內(nèi),主要包括:類、變量(及其初始化操作)、函數(shù)(及其定義)、模板和其他命名空間
namespace cplussplus_primer {
class Sales_data{/*...*/};
Sales_data operator+(const Sales_data&,
const Sales_data&);
class Query{/*...*/};
class Query_base{/*...*/};
}
- 命名空間結(jié)束后無須分號
- 命名空間的名字也必須在定義它的作用域內(nèi)保持唯一,命名空間既可以定義在全局作用域內(nèi),也可以定義在其它命名空間中,但是不能定義在函數(shù)或類的內(nèi)部
- 當出現(xiàn)一個命名空間的定義,如果之前沒有該命名空間的定義,則會創(chuàng)建一個命名空間,否則,定義會打開已經(jīng)存在的命名空間定義并為其添加一些新成員的聲明
- 命名空間的定義可以不連續(xù)的特性使得我們可以將幾個獨立的接口和實現(xiàn)文件組成一個命名空間,此時,命名空間的組織方式類似于我們管理自定義類及函數(shù)的方式
- [x] 命名空間的一部分成員的作用是定義類,以及聲明作為類接口的函數(shù)及對象,則這些成員應(yīng)該置于頭文件中,這些頭文件將被包含在使用了這些成員的文件中
- [x] 命名空間成員的定義部分則置于另外的源文件中
- 在程序中某些實體只能定義一次:如非內(nèi)聯(lián)函數(shù)、靜態(tài)數(shù)據(jù)成員、變量等,命名空間中定義的名字也需要滿足這一要求,我們可以通過上面的方式組織命名空間并達到目的
- 這種接口和實現(xiàn)分離的機制確保我們所需的函數(shù)和其他名字只定義一次,而只要是用到這些實體的地方都能看到對于實體名字的聲明
- 定義多個類型不相關(guān)的命名空間應(yīng)該使用單獨的文件分別表示每個類型(或關(guān)聯(lián)類型構(gòu)成的集合)
- 全局作用域中定義的名字(即所在類、函數(shù)及命名空間之外定義的名字)也就是定義在
全局命名空間中。全局命名空間以隱式的方式聲明,并且在所以程序中都存在,全局作用域中定義的名字被隱式地添加到全局命名空間中
未命名的命名空間
-
未命名的命名空間是指關(guān)鍵字namespace后緊跟花括號括起來的一系列聲明語句,未命名的命名空間中定義的變量擁有靜態(tài)生命周期:它們在第一次使用前創(chuàng)建,并且知道程序結(jié)束才銷毀
- 和其他命名空間不同,未命名的命名空間僅在特定的文件內(nèi)部有效,其作用范圍不會橫跨多個不同的文件
多重繼承與虛繼承
-
多重繼承是指從多個直接基類中產(chǎn)生派生類的能力,多重繼承的派生類繼承了所有父類的屬性
- 盡管概念上非常簡單,但是多個基類相互交織產(chǎn)生的細節(jié)可能會帶來錯綜復雜的設(shè)計問題與實現(xiàn)問題
多重繼承
class Bear : public ZooAnimal{/*...*/};
class Panda : public Bear , public Endangered{/*...*/};
- 每個基類包含一個可選的訪問說明符,如果訪問說明符被忽略掉了,則關(guān)鍵字class對應(yīng)的默認訪問說明符是private,關(guān)鍵字struct對應(yīng)的是public
- 和只有一個基類的繼承一樣,多重繼承的派生列表也只能包含已經(jīng)被定義過的類,而且這類不能是final的,對于派生類能夠繼承的基類個數(shù),C++沒有特殊規(guī)定;但是在某個給定的派生類列表中,同一個基類只能出現(xiàn)一次
- 多重繼承的派生類從每個基類中繼承狀態(tài)
- 派生類構(gòu)造函數(shù)初始化所有基類
- [x] 構(gòu)造一個派生類的對象將同時構(gòu)造并初始化它的所有基類子對象,與從一個基類進行的派生一樣,多重繼承的派生類的構(gòu)造函數(shù)初始值也只能初始化它的直接基類
- [x] 派生類的構(gòu)造函數(shù)初始值列表將實參分別傳遞給每個直接基類,其中基類的構(gòu)造順序與派生列表中基類的出現(xiàn)順序保持一致,而與派生類構(gòu)造函數(shù)初始值列表中基類的順序無關(guān)
- [x] 在C++11新標準中,允許派生類從它的一個或幾個基類中繼承構(gòu)造函數(shù),但是`如果從多個基類中繼承了相同的構(gòu)造函數(shù),則程序產(chǎn)生錯誤`
- [x] 派生類的析構(gòu)函數(shù)只負責清除派生類本身分配的資源,派生類的成員以及基類都是自動銷毀的
- [x] 與只有一個基類的繼承一樣,多重繼承的派生類如果定義了自己的拷貝/賦值構(gòu)造函數(shù)和賦值運算符,則必須在完整的對象上執(zhí)行拷貝、移動或賦值操作,只有當派生類使用的是合成版本的拷貝、移動或賦值成員時,才會自動對基類部分執(zhí)行這些操作
- [x] 在合成的拷貝控制成員中,每個基類分別使用自己的對應(yīng)成員隱式的完成構(gòu)造、賦值或銷毀等工作
類型轉(zhuǎn)換與多個基類
- 在只有一個基類的情況下,派生類的指針或引用能自動轉(zhuǎn)換成一個可訪問基類的指針或引用,多個基類的情況與之類似
- 基于指針類型或引用類型查找
— [x] 與只有一個基類的繼承一樣,對象、指針和引用的靜態(tài)類型決定了我們能夠使用哪些成員
多重繼承下的類作用域
- 在只有一個基類的情況下,派生類的作用域嵌套在直接基類和間接基類的作用域中,查找過程沿著繼承體系自底部向上進行,直到找到所需的名字,派生類的名字將隱藏基類的同名成員
- 在多重繼承的情況下,相同的查找過程在所有直接基類中同時進行,如果名字在多個基類中都被找到,則對該名字的使用將具有二義性
- 當一個類擁有多個基類時,有可能出現(xiàn)派生類從兩個或更多基類中繼承了同名成員的情況,此時,不加前綴限定符直接使用該名字將引發(fā)二義性
虛繼承
- 盡管在派生列表中同一個基類只能出現(xiàn)一次,但實際上派生類可以多次繼承同一個類,派生類可以通過它的兩個直接基類分別繼承同一個間接基類,也可以直接繼承某個基類,然后通過另一個基類再一次間接繼承該類
- 在C++語言中存在虛繼承的機制,虛繼承的目的時令某個類做出證明,承諾愿意共享它的基類,其中,共享的基類子對象成為虛基類,在這種機制下,不論虛基類在繼承體系中出現(xiàn)了多少次,在派生類中都只包含唯一一個共享的虛基類子對象
- 虛派生只影響從指定了虛基類的派生類中進一步派生出的類,它不會影響派生類本身
//關(guān)鍵字public和virtual的順序隨意
class Raccoon : public virtual ZooAnimal{/*...*/};
class Bear : virtual public ZooAnimal{/*...*/};
- virtual說明符表明了一種愿望,即在后續(xù)的派生類當中共享虛基類的同一份實例,至于什么樣的類能夠作為虛基類并沒有特殊規(guī)定
構(gòu)造函數(shù)與虛繼承
- 在虛派生中,虛基類時由最低層的派生類初始化的
- 虛繼承的對象的構(gòu)造方式
- [x] 含有虛基類的對象的構(gòu)造順序與一般的順序稍有區(qū)別:首先使用提供給最低層派生類構(gòu)造函數(shù)的初始值初始化該對象的虛基類子部分,接下來按照直接基類在派生列表中出現(xiàn)的次序依次對其進行初始化
- [x] 虛基類總是先于非虛基類構(gòu)造,與它們在繼承體系中的次序和位置無關(guān)
- 構(gòu)造函數(shù)與析構(gòu)函數(shù)的次序
- [x] 一個類可以有多個虛基類,這些虛基類的子對象按照它們在派生列表中出現(xiàn)的順序從左向右依次構(gòu)造