C 沉思錄 Chap8 乙個物件導向程式範例

2022-07-03 17:36:12 字數 4780 閱讀 999

物件導向程式設計的三要素:資料抽象、繼承以及動態繫結。

這裡討論乙個算術表示式樹問題,如(-5)*(3+4)對應的表示式樹為:

我們希望通過呼叫合適的函式來建立這樣的樹,然後列印該樹完整的括號化形式。例如:

expr t = expr("

*", expr("

-",5), expr("

+", 3, 4

));cout

<< t << endl;

輸出結果為:((-5)*(3+4))

此外我們不想為這些表示式的表示形式操心,也不想關心它們記憶體分配和**的事宜。

從上面圖我們可以看出,圖中有兩種物件節點和箭頭。每個節點包含乙個值——乙個運算元或者乙個操作符——並且每個結點又具有零個、乙個或者兩個子節點。

這些節點既是相同的又是不同的,我們該如何抽象節點的資料結構呢?

這些節點首先有乙個共同的特點:每個類都要儲存乙個值以及一些子節點。同時也可以很容易看出這些節點的一些不同點,比如它們儲存的值的種類,子節點的數目。

在設計類的時候繼承使得我們可以不中這些類的共同點,而動態繫結又可以幫助各個節點確定身份(從而才去不同的操作)。

仔細分析我們可以把節點分為三種型別:一種表示整數表示式,包含乙個整數值,無子節點。另外兩種分別表示一元表示式和二元表示式(包含乙個操作符,分別有乙個或者兩個子節點)。因為這些節點的不同,我們在列印的時候也要採取不同的操作,這時候就可以使用動態繫結了:我們可以定義乙個virtual 函式來指明應當如何列印各種節點。

注意到這三種節點都成為節點,我們可以先定義乙個共同的基類:

class

expr_node

};

由於expr_node 類是乙個虛基類,不能例項化物件,只有從其中派生類來例項化物件。expr_node 類的存在只是為了獲得公共的介面。

接著我們先來定義輸出操作符,它要呼叫「對應」的print函式:

ostream & operator

<< ( ostream & o, const expr_node &e)

按照前面對三種節點的分析,可以很快定義出第一種節點:

class int_node : public

expr_node

void print(ostream & o) const

};

對於一元表示式或者二元表示式,因為其中包含有子節點,這時候我們並不知道子節點的型別會是什麼,因此不能按值儲存子節點,必須儲存指標。

class unary_node : public

expr_node

void print(ostream & o) const };

class binary_node : public

expr_node

void print( ostream & o) const

};

從現有的定義來看,如果我們要建立下面的表示式樹:

expr t = expr( "*", expr("-",5), expr("+",3,4) );

則需要像下面一樣來實現:(建立一元和二元表示式樹的建構函式期望獲得指標,而不是物件)

binary_node *t = new binary_node("

*", new unary_node("

-",new int_node(5), new binary_node("

+", new int_node(3), new int_node(4

) );

這個改進離我們理想的表達方式還有差距,並且我們不再擁有指向內層new的物件的指標,因此上述**的情形會造成記憶體洩露,如果我們通過定義好析構函式來解決這個問題,則又可能會多次刪除物件,因為理想情況下可能有多個expr_node指向同乙個下層表示式物件,這種形式把記憶體管理的事情都交給了使用者。

既然使用者關心的只是樹,而不是樹中的單個節點,就可以用expr來隱藏expr_node的繼承層次。這裡又回到了前面討論的控制代碼類的內容,使用者可見的只有expr。既然使用者要乘船的是expr 而不是expr_node, 我們就希望expr的建構函式能代表所有3種expr_node。每個expr建構函式豆漿建立expr_node的派生類的乙個合適物件,並且將這個物件的位址儲存在正在建立的expr物件中。expr 類的使用者不會直接看到expr_node 物件。

class

expr

};

expr :: expr(int

n)expr :: expr(

const

string&op, expr t)

expr :: expr(

const

string &op, expr left, expr right)

現在使用expr 為 expr_node 分配記憶體,我i類避免不必要的複製,我們依然維護乙個引用計數,但這裡引用計數包含在expr_node 裡面,expr 類和 expr_node 類系統管理引用計數,因此expr 需要作為expr_node的友元出現。

class

expr_node

virtual

void print( ostream &) const = 0

;

virtual ~expr_node()

};

當expr 類「複製」乙個expr_node 時,該expr 將其引用計數增1,當引用為0的時候刪除底層的expr_node:

expr 類需要增加複製建構函式,析構函式和賦值函式:

class

expr

~expr()

expr & operator = (const expr &t);

};expr & expr :: operator = (const expr &rhs)

針對expr 我們還需要定義輸出操作符:

ostream & operator

<< (ostream & o, const expr &t)

最後需要更改每個派生自expr_node的類,令其操作為私有,將expr 類宣告為友元,儲存expr而不是expr_node的指標,例如:

class binary_node : public

expr_node

;

如果我們需要對表示式求值,在現在的構架下也很容易實現,可以在 expr 類中增加 eval 成員方法,eval 可以將實際的求值工作委託為做出expr 的結點來完成。

class

expr

//新新增的

};這樣 expr_node 類就需要新增另乙個純虛函式:

class

expr_node

;

針對expr_node 派生的每乙個類新增乙個函式來實現求值運算。(這裡就不單獨列出)

將全部**列出:

/*

既然使用者關心的只是樹,而不是樹中的單個節點,就可以用expr來隱藏expr_node的繼承層次。

這裡又回到了前面討論的控制代碼類的內容,使用者可見的只有expr了,記憶體管理的事情就完全由expr掌控!

改進後**如下:

*/#include

#include

using

namespace

std;

class

expr_node

virtual ~expr_node() {}};

class expr //

控制代碼類;

~expr()};

class int_node: public

expr_node

void print(ostream &o) const

int eval() const };

class unary_node: public

expr_node

void print(ostream &o) const

int eval() const};

class binary_node: public

expr_node

void print(ostream &o) const

int eval() const};

expr::expr(

int n)

expr::expr(

const

string& op, expr t)

expr::expr(

const

string &op, expr left, expr right)

expr::expr(

const expr& t)

expr& expr::operator=(const expr&rhs)

ostream& operator

<

void

main()

/*這個例子很好的展示了物件導向的三個要素,這樣設計出的類具有很好的擴充套件性,比如再增加有多個子節點的節點,

只要新增個新類,然後在expr中新增個新建構函式就行了。使用者完全不必知道底層的**是怎麼實現的。以後面對

問題的時候要好好的借鑑這種思想!

*/

執行結果:

c:\windows\system32\cmd.exe /c  chap8_expr2.exe

((-5)*(3+4))

-35hit any key to close this window...

《C 沉思錄》 第八章 乙個物件導向程式範例

node.h ifndef node h define node h include expr.h include include using namespace std class expr node virtual expr node virtual void print ostream con...

沉思錄 乙個墮落上進者的自我救贖

人生可以歸結為一種簡單的選擇 不是忙著活,就是忙著死。肖申克的救贖 古人云 吾當三日而自省吾身。意思就是說每個人都需要自我反思。首先我們要相信群眾的眼睛是雪亮的,乙個人好與不好並不是自己說了算,而是他人說的和與昨天的自我對比。lz畢業差不多快一年了,遙想剛剛畢業時的豪情壯志雖仍記憶猶新,但早已拋之九...

沉思錄一 如何維護乙個複雜的網路應用

寫下這個文章的時候,剛從乙個複雜的linux服務端網路應用專案中脫出,除去身心的疲憊不堪後,不得不反思標題中的問題,如何破局?這樣在下次面對相似問題時,可能就多幾分倖存的機率。最想知道我是在什麼地方死的,這樣我就可以避開死亡了。這個是查理芒格多次講過的話。但是很多人被困難問題擊敗後,都沒有找到自己是...