一个面向对象的程序范例
面向对象编程的3个要素:数据抽象、继承及动态绑定。这个例程非常完整的展示了这3个要素。
程序要做的内容就是要将这个算术表达树输出,即得到:(-5)*(3+4)
#ifndef EXPRESSION_H_H
#define EXPRESSION_H_H
#include<iostream>
#include<string>
using namespace std;
//基类
class ExprNode
{
public:
ExprNode(): m_use(1) { } //ExprNode的子类在执行自己的构造函数时,会调用父类的构造函数
//在里这个父类的构造函数为子类其进行引用计数
virtual void print(ostream&) const = 0; //虚函数,在其子类中进行重新定义,运行时动态绑定
virtual ~ExprNode() { } //保证在删除由ExprNode*指针指向的对象时能够调用到正确的派生类析构造函数
virtual int Result() const = 0;
private:
friend ostream& operator << (ostream &os, const ExprNode &rhs) //友元函数重载输出操作符
{
rhs.print(os);
return os;
}
friend class Expr;
int m_use; //计数
};
//句柄类
class Expr
{
public:
Expr(int); //创建一个int对象
Expr(const string&, Expr); //创建一个UnaryNode对象
Expr(const string&, Expr, Expr); //创建一个BianryNode对象
Expr(const string&, Expr, Expr, Expr); //创建一个TernaryNode对象
//copy constroll
Expr(const Expr &t);
Expr& operator = (const Expr&);
~Expr() { if (--m_pENode->m_use == 0) delete m_pENode; }
int Result() const { return m_pENode->Result(); }
private:
friend ostream& operator<<(ostream &os, const Expr &rhs)
{
rhs.m_pENode->print(os);
return os;
}
// friend class ExprNode;
ExprNode *m_pENode;
};
//派生类:数节点
class IntCode :public ExprNode
{
friend class Expr;
IntCode(int k) : m_Ncount(k) { }
void print(ostream &os) const {os<<m_Ncount;}
int Result() const { return m_Ncount;}
private:
int m_Ncount; //数值
};
//派生类:单目运算符
class UnaryNode : public ExprNode
{
friend class Expr;
UnaryNode(const string& a, Expr b) : m_Ustr(a), m_pNode(b) { }
void print(ostream &os) const {os<<"("<<m_Ustr<<m_pNode<<")";}
int Result() const;
private:
string m_Ustr; //单目运算符
Expr m_pNode; //操作数
};
//派生类:双目运算符
class BianryNode : public ExprNode
{
friend class Expr;
BianryNode(const string &a, Expr b, Expr c) : m_Bstr(a), m_bLeft(b), m_bRight(c) { }
void print(ostream& os) const
{ os<<"("<<m_bLeft<<m_Bstr<<m_bRight<<")";}
int Result() const;
private:
string m_Bstr; //双目运算符
Expr m_bLeft; //左操作数
Expr m_bRight; //右操作数
};
//派生类:三目运算符
class TernaryNode :public ExprNode
{
TernaryNode(const string &a, Expr b, Expr c, Expr d):
m_Tstr(a), m_tLeft(b), m_tMiddle(c), m_tRight(d) { }
void print(ostream &os) const;
int Result() const;
private:
friend class Expr;
string m_Tstr;
Expr m_tLeft;
Expr m_tMiddle;
Expr m_tRight;
};
#endif
Expressions实现文件:
#include "Expressions.h"
//句柄类实现
Expr::Expr(int n)
{
m_pENode = new IntCode(n);
}
Expr::Expr(const string &op, Expr t)
{
m_pENode = new UnaryNode(op, t);
}
Expr::Expr(const string &op, Expr left, Expr right)
{
m_pENode = new BianryNode(op, left, right);
}
Expr::Expr(const string &op, Expr left, Expr middle, Expr right)
{
m_pENode = new TernaryNode(op, left, middle, right);
}
Expr::Expr(const Expr &t)
{
m_pENode = t.m_pENode;
++m_pENode->m_use;
}
Expr& Expr::operator = (const Expr &rhs)
{
++rhs.m_pENode->m_use;
if (--m_pENode->m_use == 0)
delete m_pENode;
m_pENode = rhs.m_pENode;
return *this;
}
//单目运算符
int UnaryNode::Result() const
{
if (m_Ustr == "-") return -m_pNode.Result();
throw "error, bad m_Ustr"+m_Ustr+"in UnaryNode";
}
//双目运算符
int BianryNode::Result() const
{
int val1 = m_bLeft.Result();
int val2 = m_bRight.Result();
if (m_Bstr == "-") return val1 - val2;
if (m_Bstr == "+") return val1 + val2;
if (m_Bstr == "*") return val1 * val2;
if (m_Bstr == "/" && val2 != 0) return val1 / val2;
throw "error, bad m_Bstr"+m_Bstr+"in BianryNode";
}
//三目运算符
void TernaryNode::print(ostream &os) const
{
os<<"("<<m_tLeft<<" ? "<<
m_tMiddle<<" : "<<m_tRight<<")";
}
int TernaryNode::Result() const
{
if (m_tLeft.Result())
{
return m_tMiddle.Result();
}
else
{
return m_tRight.Result();
}
}
TestDemo文件:
#include "stdafx.h"
#include "Expressions.h"
int main(int argc, char* argv[])
{
Expr t = Expr("*", Expr("-", 5), Expr("+", 3, 4));
cout<<t<<"="<<t.Result() <<endl;
t = Expr("*", t, t);
cout<<t<< "=" <<t.Result() <<endl;
return 0;
}
总结:
当我们分析出表达式是由节点和边组成,便可以设计数据抽象来对树进行建模,继承让我们抓住了各种节点和边之间的相似之处,而动态绑定帮助我们为各种类型的节点定义操作,让编译器来负责安排在运行时能够调用正确的函数。这样,数据抽象加上动态绑定可让我们集中精神考虑每个类型的行为和实现,而不必关心与其他对象的交互