1. 程式人生 > 實用技巧 >《C++ Primer Plus》 第十五章

《C++ Primer Plus》 第十五章

第十五章:友元、異常和其他

15.1 友元

類並非只能擁有友元, 也可以將類作為友元。 友元類的所有方法都可以訪問原始類的私有成員和保護成員。 也可以更嚴格的限制, 只將特定的成員函式作為另一個類的友元。 那些函式、成員函式或類為友元是由類定義的, 而不能從外部強加友情。

友元類

例:遙控器(Remote)和電視機(TV) 應將Remote類作為TV類的一個友元
friend class Remote; // 在TV中的任意部分無關緊要
這樣Remote類就可以訪問TV類的私有成員了

友元成員函式

在Tv中宣告:
friend void
Remote::set_chan(Tv & t, int c); // 由Tv類決定誰是它的友元
提前宣告Remote類:
class Remote;
class Tv;
當Tv類中存在Remote友元函式時, 應當看見Remote類宣告和友元函式的類宣告。 解決方法:類宣告中只放方法宣告, 具體定義放在後面。 行內函數使用inline關鍵字: 行內函數的連結是內部的, 這意味著函式定義必須在使用函式的檔案中 如果放在實現檔案中, 不應當使用inline關鍵字, 這樣函式的連結是外部的。 friend class Remote已經指出Remote是一個類, 無需向前宣告。

其他友元關係

相互之間的友元

共同的友元

當函式需要訪問兩個類的私有資料時, 它可以是一個類的友元, 同時是另一個類的友元。有時同時將函式看作兩個類的友元更合理。 例:Probe類和Analyzer類

15.2 巢狀類

在C++中, 可以將類宣告放在另一個類中, 在另一個類中宣告的類被稱為巢狀類, 它通過提供新的型別類作用域來避免名稱混亂。 包含類的成員函式可以建立和使用被巢狀類的物件。 僅當宣告位於公有部分, 才能在包含類的外面使用巢狀類, 而且必須使用作用域解析符。 巢狀和包含並不相同。 包含意味著類物件作為另一個類的成員, 而巢狀不建立類成員, 只是定義了一種型別, 該型別僅在包含巢狀類宣告的類中才有效。
結構是一種其成員在預設情況下公有的類。 C++11 可以使用nullptr表示NULL 如果想在方法檔案中使用嵌套了, 應使用兩次作用域解析符:
Queue::Node::Node(const item & i) : item(i), next(0);

巢狀類和訪問許可權

1.巢狀類的宣告決定了巢狀類的作用域, 即決定了程式的哪些部分可以建立這種類的物件。 2.和其他類一樣, 巢狀類的公有、保護、私有部分控制了對類成員的訪問。

作用域

如果巢狀類是在另一個類中的私有部分宣告的, 則之有後者知道它。對於程式的其他部分,巢狀類都是不可見的(包括派生類)。 如果巢狀類是在另一個類中的保護部分宣告的, 則他對於後者來說是可見的, 但是對於外部世界是不可見的, 但是對於派生類是可見的。 如果巢狀類是在另一個類中的公有部分宣告的, 則所有部分都可以使用它。 外部程式使用時必須包含類限定符。 巢狀結構和列舉的作用域與此相同。 第十七章將更全面地介紹公有列舉。(提供可供客戶程式設計師使用的類常數)

訪問控制

對巢狀類訪問權的控制規則與常規類相同。 公有、保護、私有、友元

模板中的巢狀

定義類模板時, 不會因為包含嵌套出現問題。 舉例:Queue

15.3 異常

呼叫abort()

返回錯誤碼

異常機制

try {}
catch (異常型別) {}
throw 異常型別的變數/物件;
捕獲所有異常:
catch (...) {}

將物件作為異常型別

異常規範和C++11

棧解退

try塊中的函式呼叫了引發異常的函式, 程式將從引發異常的函式跳轉的try塊和處理程式的函式, 涉及到棧解退。 棧解退將會釋放throw到達try塊前棧中所有的函式和物件(呼叫解構函式)。

其他異常特性

throw 引發的異常是臨時拷貝。 捕獲父類的引用, 可以將子類的也一起捕獲, 所以父類應該放在最後。

exception類

異常、類和繼承 異常何時會迷失方向 未捕獲異常 terminate()-->abort() set_terminate()修改terminate呼叫的函式 意外異常 unexpected()-->terminated()-->abort() set_unexpected()修改unexpected()呼叫的函式 意外異常較為複雜。。如果unexpected()呼叫的函式引發了一個新的異常... ... P640

有關異常的注意事項

缺點: 使用異常增加程式程式碼, 降低程式執行速度, 異常規範不適用於模板, 異常和動態記憶體分配並不總能協同工作。 動態記憶體分配和異常 棧解退時, 棧中的自動變數會被釋放, 但是動態分配的記憶體將不會被釋放。--記憶體洩漏。 可以在引發異常的函式中捕獲該異常, 然後再catch塊中包含一些清理程式碼, 然後重新引發該異常:
double *ar = new double[n];
try
{
if (oh_no)
throw exception();
}
catch (exception &ex)
{
delete [] arr;
throw;
}
但是, 這件共增加疏忽和產生其他錯誤的機會。另一種解決辦法:第十六章將討論智慧指標模板。

15.4 RTTI

RTTI是執行階段型別識別(Runtime Type Identification)的簡稱。 這是新新增到C++中的特性之一, 很多老實現先不支援。 RTTI旨在為程式再執行階段確定物件的型別提供一種標準的方式。 建立一種RTTI語言標準將使得未來的各種廠商的庫可以彼此相容。

RTTI用途

C++有三個支援RTTI的元素。 1.dynamic_cast運算子將使用一個指向基類的指標來生成一個指向派生類的指標;否則, 該運算子返回0——空指標。 2.typeid運算子返回一個指出物件型別的值。 3.type_info結構儲存了有關特定型別的資訊。 只能將RTTI用於包含虛擬函式的類層次結構, 原因在於只有對於這種類層次結構, 才應該將派生類物件的地址賦給基類指標。

dynamic_cast運算子

它不能回答指標指向的是那類物件, 但能回答是否可以安全地將物件的地址賦給特定型別的指標。 語法:
dynamic_cast<Type *>(pt);
Superb * pm = dynamic_cast<Superb *>(pg);

P646-注意:即使編譯器支援RTTI, 在預設情況下, 他也可能關閉該特性。如果該特性被關閉, 程式可能仍通過編譯, 但將出現執行階段錯誤。 在這種情況下, 您應檢視文件或選單選項。 應儘可能使用虛擬函式, 而只在必要時使用RTTI。 也可以將dynamic_cast用於引用, 用法稍微不同:沒有與空指標對應的引用值, 因此無法使用特殊的引用之來指示失敗。當請求不正確時, dynamic_cast將引發bad_cast異常, 從exception派生而來, 標頭檔案:<typeinfo>:
#include <typeinfo>
...
try
{
Surperb & rs = dynamic_cast<Surper &>(rg);
...
}
catch (bad_cast &)
{
...
}; // 此處應該沒有分號P646

typeid運算子和type_info類

typeid運算子能夠確定兩個物件是否為同類型, 他與sizeof有些像, 可以接受兩種引數: 類名、結果為物件的表示式。 返回值:返回一個對type_info物件的引用, type_info在標頭檔案<typeinfo>中。 type_info過載了 == 和 != 運算子, 以便可以使用這些運算子來對型別進行比較。 typeid(Magnificent) == typeid(*pg) 如果pg是一個空指標, 則引發bad_typeid異常, 從exception中派生而來。標頭檔案:<typeinfo> type_info隨廠商而異, 包含name()成員, 該函式返回一個隨實現而異的字串:通常是類的名稱。

誤用RTTI的例子

提示:如果發現在擴充套件的if else語句系列中使用了typeid, 則應考慮是否應該使用虛擬函式和dynamic_cast。

15.5 型別轉換運算子

C語言中型別轉換過於鬆散, 允許進行無意義的型別轉換。 C++採取更嚴格地限制允許轉換的型別, 並新增四個型別轉換運算子, 使轉換過程更規範:
dynamic_cast
const_cast
static_cast
reinterpret_cast
dynamic_cast運算子通用語法
dynamic_cast < type-name > (expression)

const_cast運算子執行只有一種用途的型別轉換, 即改變值為const或volatile, 語法與dynamic_cast相同:
const_cast < type-name > (expression)

如果型別的其他部分被修改, 上述型別轉換將出錯。也就是說, 除了const或volatile特徵, 可以不同外, type-name和expression的型別必須相同。 使用通用轉換也可以, 但是_const_cast更安全 例:必須將指向該空間的地址的const都去掉, 否則依然無法修改該空間的值。 static_cast運算子語法和前面相同:
static_cast < type-name > (expression)
僅當type-name可以被隱式轉換為expression時或expression可以被隱式轉換位type-name時才是合法的。 可以進行基類和派生類的向上向下轉換。 可以對列舉和整型進行轉換。 其他int/double/float.... reinterpret_cast運算子用於危險的型別轉換。語法相同:
reinterpret_cast < type-name > (expression)

例:
struct dat {short a; short b;};
long value = 0xA224B118;
dat * pd = reinterpret_cast< dat * > (&value);
cout << hex << pd->a;

通常這樣的轉換依賴於底層程式設計技術, 是不可移植的。 reinterpret_cast並不支援所有的型別轉換, 例如:指標型別可以轉換位足以儲存指標表示的整型, 但不能見指標轉換為更小的整型或浮點型。不能將函式指標轉換為資料指標。 C++普通型別轉換也受到限制。