1. 程式人生 > >《Effective C++》讀書筆記 條款34:區分介面繼承和實現繼承

《Effective C++》讀書筆記 條款34:區分介面繼承和實現繼承

第一次記錄讀書筆記,因為覺得純讀很沒有意思,於是突然決定開始寫部落格記錄一下,理解的也不深。菜鳥也希望能有進步,希望自己堅持,前面的條款在後來會補。 這一條款主要講純虛擬函式、虛擬函式和普通成員函式在public繼承時不同的地方。public繼承的概念包括兩個:函式的介面繼承和函式的實現繼承。類中的成員函式有三種類型,純虛擬函式,虛擬函式、普通成員函式,這三種類型在public繼承時的不同如下表 |純虛擬函式 |繼承函式介面 | |虛擬函式 |函式介面和預設實現| |普通成員函式 |介面和強制性實現 | 在進行類的設計時,如果希望派生類只繼承成員函式的介面(也就是宣告),則該成員函式宣告為純虛擬函式; 如果希望派生類同時成員函式的繼承介面和實現,但又希望能夠覆寫它們所繼承的實現,則該成員函式宣告為虛擬函式;如果希望同時繼承成員函式的介面和實現,並且不允許覆寫任何東西,則改成員函式宣告為普通成員函式即可。

對於這三種函式的不同可以通過例子來說明,首先說明一點,成員函式的介面總是會被繼承的(因為public繼承意味著is-a)

class shape
{
public:
	virtual void draw() const= 0; //pure virtual
	virtual void error(const std::string& msg);//impure virtual;
	int objectID()const;//non_virtual
//Private:
	//.......
};

class RectTangle :public shape
{
public:
	void draw();//對於具象的派生類,一定要重新宣告基類的純虛擬函式,否則該派生類也是抽象類
};
void RectTangle::draw()
{

}
class Circle :public shape
{
public:
	void draw();

};
void Circle::draw()
{

}

首先看基類shape,聲明瞭三個函式,draw,error,objectID,分別是pure virtual ,impure virtual,non_virtual。

shape::draw函式說明,形狀是畫出來的,但是不同的形狀有不同的畫法,所以不能提供預設的實現,這就是告訴所有的具象派生類,“你必須提供一個draw函式,但我不干涉你怎麼實現”。總的來說,基類設計純虛擬函式的目的就是告訴具象派生類一定會具有的功能,但功能的具體實現細則不一樣,所以不能給出統一的程式碼,這就像鳥都會叫,但是叫的方式不一樣,對於鳥這個基類,就可以設計一個表示叫的純虛擬函式,然後對於不同的鳥類,如烏鴉、喜鵲有不同的實現方式。這就是public繼承中的介面繼承。 關於純虛擬函式書中還提到了一點,我們可以給純虛擬函式提供一份實現程式碼,但呼叫它的唯一途徑是“呼叫時明確指出其class名稱”。舉例如下: *Shape ps1 = new Rectangle; ps1->Shape::draw();

shape::error,表示每個派生類都必須支援一個“當遇上錯誤時可呼叫”的函式,但每個類可自由處理錯誤,如果某個類不想對錯誤做出任何特殊的處理行為,他可以回退到基類提供的預設的錯誤處理行為。比如動物都要吃東西,基類動物可以提供一個虛擬函式eat,並提供預設的吃的方式,但是每一種動物吃的具體方式可能有差異,如直接吞的,嚼碎再吞的等等(舉例子好辛苦,總感覺哪裡怪怪的,(╥╯^╰╥),舉例能力太差了),如果沒有特別的吃的方式,就直接使用基類吃的方式。 書上提到用虛擬函式使基類繼承介面和預設的實現可能會帶來錯誤。用飛機舉例,如果將飛機飛行的方式寫成虛擬函式,並提供預設的程式碼,避免程式碼重複,但是卻可能出現問題,如果將來我有設計了一個派生類,這種飛機的飛行方式比較特別,而我又忘了覆寫飛行方式函式,那麼原則上來說生產的飛機可能就飛不起來了,造成故障,就像是你一直騎電瓶車,沒學過開車,讓你去開車,你用騎電拼車的方式怎麼能把車開走呢。那你肯呢個說把fly改成純虛擬函式不就好了,但是,如果大部分飛機飛行方式都一樣,這種設計方式會造成程式碼大量重複。那麼應該怎麼解決這個問題呢? 解決方式:1.是切斷“virtual函式的介面”和“預設實現”之間的連線。也是將fly(飛行函式)定義成純虛擬函式,將實現函式用一個而普通成員函式代替(protected),類似這種

class Airplane{
public:
	virtual void fly(const Airport& destination) = 0;
protected://注意是protected
	void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination)
{
	//預設行為
}

然後派生類在實現fly函式時可以呼叫基類的保護成員函式defaultFly,這樣既避免可程式碼重複,也避免了繼續設計派生類時忘記提供fly函式 2.將其宣告為純虛擬函式,併為該純虛擬函式提供一份實現程式碼是(預設的實現方式),在每個派生類中覆寫該純虛擬函式,如果不需要特殊的行為,則在該覆寫的函式中呼叫基類的純虛擬函式,注意上面說的純虛擬函式的呼叫方式

class Airplane
{
public:
	virtual void fly(const Airport& destination) = 0;
};
void Airplane::fly(const Airport& destination)
{
	//預設的行為
}

class ModelA :public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		Airplane::fly(destination);
	}
};

這樣既避免可程式碼重複,也避免了繼續設計派生類時忘記提供fly函式,而且減少了函式的命名,避免class名稱空間汙染

shape::objectID()函式,普通成員函式意味著在派生類中不想有不同的行為,派生類直接繼承介面和實現

請記住: 1.介面繼承和實現繼承不同。在public繼承下,派生類總是繼承基類的介面 2.pure virtual函式只具體指定介面繼承 3.impure virtual函式具體指定介面繼承及預設實現繼承 3.non_virtual 函式具體指定介面繼承以及強制性實現繼承