第八章:一个面向对象程序范例
面向对象三个要素:数据抽象,继承以及动态绑定。
8-1 问题描述
程序内容:用来表示算术表达式的树。(-5)*(3+4)
我们希望通过调用合适的函数创建这样的树,然后打印该树完整的括号化形式。
#include
int main()
{
Expr t = Expr("*", Expr("-", 5), Expr("+", 3, 4));
cout << t <
t = Expr("*", t, t);
cout << t <
打印:
((-5)*(3+4)
(((-5)*(3+4))*((-5)*(3+4)))
8-2 面向对象的解决方案
分析:两种对象,节点和边, 每个节点包含一个操作数或者一个操作符。每个节点又有一个,零个,两个字节点。
我们可以用一个联合来容纳具体值,用一个List来表示子节点,以包含联合(union)和List的类来表示节点。这种表示方法需要设置一个专门的字段来表示节点的类型。
当需要用到一个类型字段的时候,请停下来,考虑如果定义一系列类,用继承组织起来,是否可以更有效的解决问题?
这些类一些共同点:每个类都要存储一个值以及一些子节点。
动态绑定帮助各个节点知晓他们的身份。
class Expr_node
{
friend ostream operator<<
(ostream&, const Expr_node&);
protected:
virtual void print(ostream&) const = 0;
virtual ~Expr_node() {}
};
为什么把print函数设为protected?
osteeam&
operator<
{
e.print(o);
return o;
}
class Int_node:public Expr_node {
friend class Expr;
int n;
Int_node(int k) :n(k) {}
void print(ostream& o) const { o << n; }
};
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 pring(ostream& o) const
{ o<
};
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(a0, left(b), right(c) {}
void print(ostream& o) const
{ o << "(" << *left << op <
};
不会奏效为什么?创建一元和二元表达式的构造函数期望获得指针,而不是对象。于是我们可以动态分配接点:
Binary_node*t = new Binary_node("*",
new Unary_node("-", new Int_node(5)),
new Binary_node("+",
new Int_node(3), new Int_node(t)));
我们必须记住要删除这些节点。不可能(为什么?)。我们不再拥有指向内层new调用所构造的对象的指针了。我们希望Binary_node 和Unary_node的析够函数删除他们的操作数,但是同样不行。如果析够函数删除了起操作数,可能会多次删除对象,因为可能不止一个Expr_node指向同一个下层的表达式对象。(这一段没有明白)。
8-3 句柄类
我们不仅把内存管理这类烦心的事推给了用户,而且用户也没有什么方便的办法来处理这些事情。
类Expr应当是一种句柄类,表示一个边。
class Expr {
friend ostream& operator << (ostream&, const Expr&);
Expr_node* p;
public:
Expr(int); //创建一个Int_node
Expr(const String&, Expr); //创建一个Unary_node;
Expr(const String&, Expr, Expr) //创建一个Binary_node
Expr(const Expr&);
Expr& operator=(const Expr&);
~Expr() {delete p;}
};
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_node包含一个引用计数。
class Expr_node {
friend ostream& operator<< (ostream&, const Expr&);
friend class Expr;
int use;
protected:
Expr_node(): use(1) {}
virtual void print(ostream& ) const = 0;
virtual ~Expr_node() {}
};
class Expr {
//和前面一样
public:
Expr(const EXpr& t) { p = t.p; ++p->use; }
Expr& operator=(const Expr& t);
};
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& t)
{
t.p->print(o);
return o;
}
最后需要更改每个派生自Expr_node的类,令其操作为私有,将Expr类声明为友元,存储Expr 而不是存储指向 Expr_node的指针。
class Binary_node: public Expr_node {
friend class Expr;
string op;
Expr left;
Expr right;
Binary_node(const String& a, Expr b, Expr c);
op(a), left(b), right(c) {}
void print(ostream& o) const {
o << "(" <
}
};
有了这些,最早哪个main可以工作了。
8-4扩展1:新操作
现在能力有限:只能创建和打印表达式。客户可能要求计算表达式的值。
int main()
{
Expr t = Expr("*", Expr("-", 5), Expr("+", 3, 4));
cout << t <
cout << t << "=" << t.eval() <
t = Expr("*", t, t);
cout << t <
cout <
}
((-5)*(3+4) =-35
(((-5)*(3+4))*((-5)*(3+4)))=1225
class Expr {
friend class Expr_node;
friend ostream& operator << (ostream&, const Expr&);
Expr_node* p;
public:
Expr(int); //创建一个Int_node
Expr(const String&, Expr); //创建一个Unary_node;
Expr(const String&, Expr, Expr) //创建一个Binary_node
Expr(const Expr& t) { p = t.p; ++p->use;};
Expr& operator=(const Expr& t);
~Expr() { if (--p->use == 0) delete p;}
int eval() const { return p->eval(); } //添加的
};
class Expr_node {
protected:
virtual int eval() const = 0;
//和前面一样
};
class Int_node:public Expr_node {
friend class Expr;
int n;
Int_node(int k) :n(k) {}
void print(ostream& o) const { o << n; }
int eval() const { return n: } //添加的
};
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<
int eval() const; //添加的
};
int
Unary_node::eval() const
{
if(op == "-")
return -opnd.eval();
throe "error, bad op "+op-" int UnaryNode";
}
可见增加一个新操作无须触及已有的操作的代码。
int Binary_node::eval () const
{
int op1 = left.eval();
int ope = right.eval();
if (op == "-") return op1-op2;
if(op == "+" ) return op1+op2;
if(op == "*") return op1*op2;
if(op =="/" &&op2 != 0) return op1/op2;
throw "error, bad op " + op + "int BinaryNode";
}
8-5 扩展2 :增加新的节点类型
class Ternary_node: public Expr_node {
friend class Expr;
String op;
Expr left;
Expr middle;
Expr right ;
Ternary_node(const String& a, Expr b, Expr c, Expr d);
op(a), left(b), middle(c), right(d) {}
void print (ostream& o) const;
int eval() const;
};
void
Ternary_node::print(ostream& o) const
{
o << "(" <
}
int Ternary_node::eval() const
{
if (left.eval())
return middle.eval();
else
return right.eval();
}
Expr::Expr
(const String& op, Expr left, Expr middle, Expr right)
{
p = new Ternary_node(op, left, middle, right);
}
有一位曾经做过C编译器的先生看到这个例子之后,感慨的说:“当时我们往C语言编译器里添加?:操作符的时候都快绝望了,前前后后花费了几个星期甚至是几个月----你居然只用了区区18行代码就做到了!”
8-6 反思
可以看到面向对象编程是如何简化程序的设计和更新过程的。解决方案的实质是要对希望模拟的下层系统中的对象进行建模。
继承让我们抓住各种节点类型间的相似之处,而动态绑定帮助我们为各种类型节点定义操作,让编译器来负责安排在运行时能够调用的正确的函数。这样数据抽象加上动态绑定让我们集中精力考虑每个类型的行为和实现, 而不必关心与其他对象的交互。
可以继续改进:1:还需要添加关系运算 2:希望添加赋值表达式。(什么是赋值表达式?)3:表达式数很有用,但是语句有时更强大(不理解)。
关键:这个程序可以非常优雅的进化。想想如果没有动态绑定,表示一个表达式树会是多么困难。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/409557/viewspace-891904/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/409557/viewspace-891904/