C++ primer Plus 第十五章 友元、異常和其他

15.1友元

類並非只有友元函數,還可以有友元類,友元類的所有方法都可以訪問原始類的私有成員和保護成員。

下面我們通過一個例子來講解一下友元類

定義TV類和Rmote類,分別作爲電視機和遙控器的類

由於遙控器可以控制電視機,即遙控器可以訪問電視機的成員,所以要將Rmote類聲明爲TV的友元類,但是如果只是將Rmote類的一個成員函數聲明爲友元函數,則需要知道Rmote類的聲明,所以TV類應該在Rmote類後聲明,但是在Rmote類的聲明中,有些成員函數會引用TV類的對象,這時候需要在Rmote類前對TV類進行名稱聲明(即聲明TV 是一個類),這種做法叫做前向引用聲明。

#include<iostream>
using namespace std;
class Tv;
class Remote
{
    int mode;
public:
    enum State{off,on};
    void onoff(Tv &t);
};
class Tv
{
    int state;
public:
    friend void Remote::onoff(Tv &t);//將Remote的onoff函數聲明爲Tv的友元函數
    enum State{off,on};
    void onoff(){state = (state == on)?off:on;}
};

如果我們希望電視機接受遙控器指令後,會返回信息給遙控器,遙控器會發出嗡嗡聲作爲迴應。

這時候可以將Tv和Rmote類互爲友元類,但是要注意如果Tv類聲明在前的話,對於Tv類中使用Rmote類對象的方法的定義,需要在Remote聲明之後。

例如

這種使用內聯函數的,即使用了Tv類的具體成員的內聯函數,要使用inline關鍵字 在Tv類聲明的後面定義。

 

15.3異常

 

15.3.1調用abort()

abort函數位於cstdlib頭文件裏,其典型實現是向標準錯誤流即cerr發送程序異常終止,然後終止程序。

也可以使用exit(),該函數會刷新文件緩衝區,但不顯示消息。

#include<iostream>
#include<cstdlib>
using namespace std;
double heam(double a,double b);
void fun()
{
    double a,b;
    cin>>a>>b;
    double result = heam(a,b);//除運算
    cout<<result<<endl;
}
double heam(double a,double b)
{
    if(b==0)
    {
        cout<<"error"<<endl;
        abort();//也可以用exit(0)
    }
}
int main()
{
    fun();
}

如果b==0的話說明發生錯誤了,執行abort,會直接結束程序,而不是結束函數。

 

15.3.3異常機制

對異常的處理分爲三個部分:

引發異常;

使用處理程序捕獲異常;

使用try塊;

 

try塊標識其中特定的異常可能被激活的代碼塊,它後面跟一個或多個catch塊

catch關鍵字表示捕獲異常,處理程序以關鍵字catch開頭,隨後是位於括號中的類型聲明,它指出了異常處理程序要響應的異常類型,然後是一個花括號括起來的代碼塊,指出要採取的措施。

throw 類似於return,它將終止函數的執行,並返回相應的值;但throw不是將控制權返回給調用程序,而是導致程序函數調用序列回退,一直到包含try塊的那個函數,並將返回值與catch的異常類型比較,以執行特定的代碼塊,這個過程叫做棧解退。

例如:

#include<iostream>
#include<cstdlib>
using namespace std;
double heam(double a,double b)
{
    if(b==0)
    {
        throw "Error!";
    }
}
void fun()
{
    double a,b,result;
    cin>>a>>b;
    try
    {
        result = heam(a,b);//除運算
    }
    catch(const char *s)//會將throw返回的那個字符串與s的類型匹配,所以執行這異常處理的代碼塊
    {
        cout<<s<<endl;
        abort();
    }
    cout<<result<<endl;
}

int main()
{
    fun();
}

15.3.4將對象用作異常的類型

 

我們可以手動定義異常的類,爲不同異常創建不同的類,以便於在多個catch裏找到應執行的代碼塊。

15.3.5異常規範

double harm(double a) throw(bad_thing);
double marm(double) throw();

其中throw()部分就是異常規範,它可能是出現在函數原型和函數定義中,可能包含類型列表。

這項功能不推薦使用,僅瞭解即可。

 

15.3.6棧解退

 

1.在try的語句塊內聲明的變量在外部是不可以訪問的,即使是在catch子句內也不可以訪問。 
 
2.棧解退(尋找異常處理(exception handling)代碼)

棧解退會沿着嵌套函數的調用鏈不斷查找,知道找到了已拋出的異常匹配的catch子句。如果在最後還是沒有找到對應的catch子句的話,則退出主函數後查找過程終止,程序調用標準函數庫的terminate()函數,終止該程序的執行

1.當一個exception被拋出的時候,控制權會從函數調用中釋放出來,並需找一個可以處理的catch子句
2.對於一個拋出異常的try區段,程序會先檢查與該try區段關聯的catch子句,如果找到了匹配的catch子句,就使用這個catch子句處理這個異常。
3.沒有找到匹配的catch子句,如果這個try區段嵌套在其他try區段中,則繼續檢查與外層try匹配的catch子句。如果仍然沒有找到匹配的catch子句,則退出當前這個主調函數,並在調用了剛剛退出的這個函數的其他函數中尋找。

3. catch子句的查找: 
 
1.catch子句是按照出現的順序進行匹配的(以例2來說,異常先會匹配catch(exception e)子句,然後在匹配 catch (Exception e)子句,一步一步的棧展開)。在尋找catch子句的過程中,拋出的異常可以進行類型轉換,但是比較嚴格:
2.允許從非常量轉換到常量的類型轉換(權限縮小)
3.允許從派生類到基類的轉換。
4.允許數組被轉換成爲指向數組(元素)類型的指針,函數被轉換成指向該函數類型的指針(降級問題)
5.標準算術類型的轉換(比如:把bool型和char型轉換成int型)和類類型轉換(使用類的類型轉換運算符和轉換構造函數)。


15.5.7其他異常特性

1.如果拋出的異常一直沒有函數捕獲(catch),則會一直上傳到c++運行系統那裏,導致整個程序的終止

2.一般在異常拋出後資源可以正常被釋放,但注意如果在類的構造函數中拋出異常,系統是不會調用它的析構函數的,處理方法是:如果在構造函數中要拋出異常,則在拋出前要記得刪除申請的資源。

3.異常處理僅僅通過類型而不是通過值來匹配的,所以catch塊的參數可以沒有參數名稱,只需要參數類型。

4.函數原型中的異常說明要與實現中的異常說明一致,否則容易引起異常衝突。

5.應該在throw語句後寫上異常對象時,throw先通過Copy構造函數構造一個新對象,再把該新對象傳遞給 catch.  

注:那麼當異常拋出後新對象如何釋放?

異常處理機制保證:異常拋出的新對象並非創建在函數棧上,而是創建在專用的異常棧上,因此它纔可以跨接多個函數而傳遞到上層,否則在棧清空的過程中就會被銷燬。所有從try到throw語句之間構造起來的對象的析構函數將被自動調用。但如果一直上溯到main函數後還沒有找到匹配的catch塊,那麼系統調用terminate()終止整個程序,這種情況下不能保證所有局部對象會被正確地銷燬。
6.catch塊的參數推薦採用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對象的多態性。另外,派生類的異常撲獲要放到父類異常撲獲的前面,否則,派生類的異常無法被撲獲。

7.編寫異常說明時,要確保派生類成員函數的異常說明和基類成員函數的異常說明一致,即派生類改寫的虛函數的異常說明至少要和對應的基類虛函數的異常說明相同,甚至更加嚴格,更特殊。
 

15.4RTTI以及類型轉換運算符

RTTI是運行階段類型識別的簡稱。

RTTI包含dynamic_cast、typeid和type_info

類型轉換運算符包含 dynamic_case const_cast static_cast  reinterpret_cast.

1.dynamic_cast運算符:最常用的RTTI組件

Superb *pm=dynamic_cast<Superb *>(pg);

若pg的類型能夠被安全地轉化爲Superb*,如果可以,返回對象的地址,否則,返回一個空指針。

dynamic_cast<Type*>(pt);正確,返回Type*,否則,返回0。

2.typeid使得能夠確定兩個對象是否爲同種類型,可接受兩個參數:類名和結果爲對象的表達式。返回值爲type_info對象的引用。

typeid(Magnificent)==typeid(*pg);

pg爲空指針會拋出異常bad_typeid。type_info含有name()函數,返回類名。

3.const_cast<type-name> (expression) 轉變爲const或者volatile類型,typename與expression類型相同

4.static_cast<type-name>(expression) type-name轉化爲expression或者expression轉化爲type-name時均可使用。枚舉/整型互轉,double/int互轉。

5.reinterpret_cast<type-name>(expression)

函數指針不可轉化爲數據指針,指針不能轉爲整型或者浮點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章