面向对象有三个要素:
数据抽象,继承以及动态绑定
问题描述:
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对象。