C++沉思录读书笔记(8章)-一个面向对象程序范例1

这个例子很好的展示了面向对象编程的三个要素:数据抽象、继承和动态绑定

问题:算术表达式的表达式树, 如(-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中添加个新构造函数就行了。用户完全不必知道底层的代码是怎么实现的。以后面对问题的时候要好好的借鉴这种思想!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值