程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++沉思錄讀書筆記(8章)-一個面向對象程序范例

C++沉思錄讀書筆記(8章)-一個面向對象程序范例

編輯:C++入門知識

 

這個例子很好的展示了面向對象編程的三個要素:數據抽象、繼承和動態綁定。

問題:算術表達式的表達式樹, 如(-5)*(3+4)對應的表達式樹為:

\

 

我們希望通過調用合適的函數來創建這樣的樹,然後打印該樹完整的括號化形式。例如:

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

cout << t << endl;

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

 

面向對象的解決方案:

一 節點如何表示?

仔細觀察上圖,根據子節點的個數可以抽象出三種節點,分別有0,1,2個子節點的節點。如果讓我來設計的話我很可能會這樣描述節點:

 

class Node 

private: 

       string value;    //節點值 

       int num;         //子節點個數 

       Node* son[2]; //儲存子節點指針 

public: 

       Node(); 

       Node(string&, int);                             //0 個子節點 

       Node(string&, int, Node*);                 //1 個子節點 

       Node(string&, int, Node*, Node*);      //2 個子節點 

       ~Node(); 

}; 

 

這種表示方法需要設置專門的字段類指示子節點的個數。然而書中用繼承解決了這個問題,讓人拍案叫絕!

(1)考慮三種節點類的共同點與不同點:都具有節點值,都需要打印節點,但節點值類型不同,打印節點方式不同,子節點個數不同。

(2)考慮三種節點之間的關系: 每一種節點都與其他節點相互獨立,一個沒有子節點的節點不是“一種”有一個子節點的節點,反之亦然。

(3)綜合上面兩點,定義一個抽象基類,動態綁定在這裡就有用武之地了。

二 幾種節點類的具體定義:

 

#include <iostream> 

#include <string> 

using namespace std; 

class Expr_node 

    friend ostream& operator<<(ostream&, const Expr_node&); 

protected: 

    virtual void print(ostream&) const = 0;//動態綁定 

    virtual ~Expr_node(){} 

}; 

 

ostream& operator<<(ostream& o, const Expr_node& e) 

    e.print(o); 

    return o; 

 

class Int_node: public Expr_node                //0 個子節點 

private: 

    int n;   

    friend ostream& operator<<(ostream&, const Expr_node&);                                 

public: 

    Int_node(int k) : n(k) {} 

    void print(ostream &o) const {o << n;} 

}; 

 

class Unary_node:public Expr_node     //1 個子節點 

private: 

    string op; 

    Expr_node *opnd; 

    friend ostream& operator<<(ostream&, const Expr_node&); 

public: 

    Unary_node(const string & a, Expr_node *b) : op(a), opnd(b) {} 

    void print(ostream &o) const {o << "(" << op << *opnd << ")";} 

}; 

 

class Binary_node:public Expr_node    //2 個子節點 

private: 

    string op; 

    Expr_node *left; 

    Expr_node *right; 

    friend ostream& operator<<(ostream&, const Expr_node&); 

public: 

    Binary_node(const string &a, Expr_node *b, Expr_node *c):op(a), left(b), right(c) {} 

    void print(ostream &o) const {o << "(" << *left << op << *right<< ")";} 

}; 

 

void main() 

    Binary_node * t = new Binary_node("*", 

        new Unary_node("-", new Int_node(5)), 

        new Binary_node("+",new Int_node(3), new Int_node(4))); 

    cout << *t; 

    //節點未刪除 

 

三 類定義的缺陷

上面方案已經基本上解決問題了,但是創建表達式樹的方式還有待改進。按照上面的方案,我們只能這樣定義表達式:

Binary_node *t = new Binary_node("*",

                                                   new Unary_node("-", new Int_node(5)),

                                                   new Binary_node("+",new Int_node(3),new Int_node(4)));

cout << *t << endl;

這個改進離我們理想的表達方式還有差距,並且我們不再擁有指向內層new的對象的指針,因此上述代碼的情形會造成內存洩露,如果我們通過定義好析構函數來解決這個問題,則又可能會多次刪除對象,因為理想情況下可能有多個Expr_node指向同一個下層表達式對象,這種形式把內存管理的事情都交給了用戶。

 

四 用句柄類改進。

既然用戶關心的只是樹,而不是樹中的單個節點,就可以用Expr來隱藏Expr_node的繼承層次。這裡又回到了前面討論的句柄類的內容,用戶可見的只有Expr了,內存管理的事情就完全由Expr掌控!改進後代碼如下:

 

#include <iostream> 

#include <string> 

using namespace std; 

 

class Expr_node 

    friend class Expr;  //友元類可以被繼承 

    int use;                  //引用計數 

public: 

    virtual void print(ostream&) const = 0; 

public: 

    Expr_node():use(1) {} 

    virtual ~Expr_node() {} 

}; 

 

class Expr                     //句柄類 

    friend ostream& operator<<(ostream &o, const Expr &e); 

private: 

    Expr_node *p;       //指向基類的指針 

public: 

    Expr(int n); 

    Expr(const string &op, Expr t); 

    Expr(const string &op, Expr left, Expr right); 

    Expr(const Expr &t); 

    Expr& operator=(const Expr&); 

    ~Expr() 

    {  

        if(--p->use == 0) 

            delete p; 

    } 

}; 

 

class Int_node: public Expr_node 

private: 

    int n; 

public: 

    Int_node(int k):n(k) {} 

    void print(ostream &o) const 

    { 

        o << n; 

    } 

}; 

 

 

class Unary_node: public Expr_node 

private: 

    //friend class Expr; 

    string op; 

    Expr opnd; 

public: 

    Unary_node(const string &a, Expr b):op(a), opnd(b) {} 

    void print(ostream &o) const 

    { 

        o << "(" << op << opnd << ")"; 

    } 

}; 

 

class Binary_node: public Expr_node 

private: 

    //friend class Expr; 

    string op; 

    Expr left; 

    Expr right; 

public: 

    Binary_node(const string &a, Expr b, Expr c):op(a), left(b), right(c) {} 

    void print(ostream &o) const 

    { 

        o << "(" << left << op << right << ")"; 

    } 

}; 

 

Expr::Expr(int n) { p = new Int_node(n); } 

Expr::Expr(const string& op, Expr t) { p = new Unary_node(op,t); } 

Expr::Expr(const string &op, Expr left, Expr right) { p = new Binary_node(op, left, right); } 

Expr::Expr(const Expr& t) { p = t.p; ++p->use; } 

 

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

    rhs.p->use++; 

    if(--p->use == 0) 

        delete p; 

    p = rhs.p; 

    return *this; 

 

ostream& operator<<(ostream &o, const Expr &e) 

    e.p->print(o); 

    return o; 

 

void main() 

    Expr t = Expr("*", 

        Expr("-", Expr(5)), 

        Expr("+", Expr(3), Expr(4))); 

    cout << t << endl; 

 

 

五 總結

       這個例子很好的展示了面向對象的三個要素,這樣設計出的類具有很好的擴展性,比如再增加有多個子節點的節點,只要添加個新類,然後在Expr中添加個新構造函數就行了。用戶完全不必知道底層的代碼是怎麼實現的。以後面對問題的時候要好好的借鑒這種思想!

 

作者 yucan1001

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved