物件導向有三個要素:
資料抽象,繼承以及動態綁定
問題描述:
1、一個運算式樹狀架構包括代表常量、一元運算子、二元運算子的結點。
2、我們希望通過調用合適的函數來建立這棵樹,然後列印這棵樹的完整括弧化形式。
物件導向的解決方案:
我們可以用一個聯合(UNION)來容納具體的值,用一個List來表示子結點,以包含聯合(UNION)和List的類來表示結點。這些類有一個共同點:每個類都要儲存一個值以及一些子結點,當然也有不少不同點,比如它們儲存的值的種類,子結點的數目,繼承可以捕捉這些共同點,而動態綁定協助各個結點知道它的身份。
進一步分析,我們發現這棵樹有三種結點:
1、整數運算式,無子結點
2、包含一個一元運算式,有一個子結點。
3、包含一個二元運算式,有兩個子結點。
我們需要一個類來表示結點的"概念",但這個類並不表示具體的結點。我們將這個公用基類命名為Expr_node:
class Expr_node
{
//友元函數,重載<<操作符用於輸出
friend ostream operator<<(ostream&, const Expr_node&)
{
}
protected:
virtual void print(ostream& ) const = 0;
virtual ~Expr_node() {};
}
因為所要建立的對象都派生至Expr_node,所以提供了虛解構函式。動態綁定只用於成員函數,所以我們定義了一個虛函數print。我們希望使用者使用輸出操作符,而不是Print函數,那麼把Print設為Protected,把operator<<設為友元。
從函數Print的聲明可以看出,它是一個純虛函數,這就使得Expr_node成為抽象基類。這就體現了我們的意圖:不存在所謂的Expr_node對象。Expr_node類的存在只是為了獲得公用介面。
輸出操作符:
ostream& oprator << (ostream & o, const Expr_node e)
{
e.print(o);
return o;
}
下面定義具體的類:
1、整數,無子結點的結點類
class Int_node:public Expr_node{
friend class Expr;
int n;
Int_node(int k) : n(k) {}
void print(ostream & o) const { o << n; }
}
2、一元結點類
class Unary_node: public Expr_node{
//friend class Expr;
string op;
Expr_node * opnd;
Unary_node(const string & a, Expr_node * b):Op(a), opnd(b) {}
void print(ostream & o) const
{o << "(" << op << *opnd << ")";}
}
3、二元結點類
class Binary_node: public Expr_node{
//friend class Expr;
string op;
Expr_node * left;
Expr_node * right;
Binary_node(const string & a, Expr_node * b,Expr_node * c):Op(a), opnd(b),opnd(c) {}
void print(ostream & o) const
{o << "(" << *left << op << *right << ")";}
}
建立一元和二元運算式的建構函式期望獲得指標,於是我們可以動態分配結點:
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的對象的指標了。下一節介紹如何new 和 delete對象。