《C++沉思录》——面向对象
2016-06-04 18:33
423 查看
面向对象——数据抽象、继承、动态绑定(规模大、方便修改),下面是面向对象和句柄类综合应用案例:
(2)如果你说我们可以在Unary_node和Binary_Node的析构函数中去处理内存的释放,那么如果析构函数删除了操作数,可能会多次删除对象,因为可能不止一个Expr_Node指向同一个下层的表达式对象。
所以行不通,很麻烦。
问题描述:
算术表达式树,(-5)*(3+4)样例展示说明:
void main() { Expr expression = Expr("*", Expr("-", "5"), Expr("+", "3", "4")); cout << expression << endl; expression = Expr("*", expression, expression); cout << expression << endl; }
输出: ((-5)*(3 + 4)) ((-5)*(3 + 4))*((-5)*(3 + 4))
分析:
树的结构分为三种结点:(1)整数表达式,无子节点;(2)一元表达式,有一个操作符和一个子节点;(3)二元表达式,有一个操作符和两个子节点。第一种思路:
因为我们不知道子节点类型,所以构造不同类型的节点时我们绝不能按值,而是传入父类指针。class Expr_node { //main中的输出示例采用<<输出操作符来打印 friend ostream& operator<<(ostream&, const Expr_node&); protected: //输出不同类型的节点,需要使用动态绑定来决定打印方式 virtual void printNode(ostream&) const = 0; virtual ~Expr_node() { }; }; ostream& operator<<(ostream& out, const Expr_node& node) { node.printNode(out); return out; } class Int_Node :public Expr_node { friend class Expr; private: int n; Int_Node(int k) : n(k) { } void printNode(ostream& out) const { out << n; } }; class Unary_node :public Expr_node { friend class Expr; private: string op; Expr_node* opnd; //因为我们不知道子节点类型,所以构造不同类型的节点时我们绝不能按值,而是传入父类指针。 Unary_node(const string& a, Expr_node* b) :op(a), opnd(b) { } void printNode(ostream& out) const { out << "(" << op.c_str() << opnd << ")"; } }; class Binary_Node :public Expr_node { friend class Expr; private: string op; Expr_node* left; //因为我们不知道子节点类型,所以构造不同类型的节点时我们绝不能按值,而是传入父类指针。 Expr_node* right; Binary_Node(const string& a, Expr_node* b, Expr_node* c) :op(a), left(b), right(c) { } void printNode(ostream& out) const { out << "(" << left << op.c_str() << right << ")"; } };(1)我们先不说题目要求的调用和创建的树的形式,就按上面的方法来动态分配创建节点:
//一层一层创建,以便内存分配和回收 Int_Node* iNode1 = new Int_Node(5); Int_Node* iNode2 = new Int_Node(3); Int_Node* iNode3 = new Int_Node(4); Unary_node* expr1 = new Unary_node("-", iNode1); Binary_Node* expr2 = new Binary_Node("+", iNode2, iNode3); Binary_Node* t = new Binary_Node("*", expr1, expr2); ... delete ...假设我们也可以有耐心有超强记忆力能伤处创建的每个对象。(毕竟这是愚蠢和有难度的 )
(2)如果你说我们可以在Unary_node和Binary_Node的析构函数中去处理内存的释放,那么如果析构函数删除了操作数,可能会多次删除对象,因为可能不止一个Expr_Node指向同一个下层的表达式对象。
所以行不通,很麻烦。
第二种思路:
引入句柄类Expr类,所有的Expr_Node的继承层次都包含在Expr,而用户不再能看到Expr_Node对象。#include<iostream> using namespace std; class Expr_node { friend class Expr; //Expr需要帮助Expr_node管理引用计数,两者协同管理引用计数,仅当引用计数为0时才删除该节点 public: //引用计数初始为1 Expr_node() :useCount(1) { } //输出不同类型的节点,需要使用动态绑定来决定打印方式 virtual void printNode(ostream&) const = 0; virtual ~Expr_node() { }; virtual int eval() const = 0; private: //引入引用计数,避免对下层Expr_node的复制(反正也不改变它,没必要复制) //引用计数————指明同时有多少个Expr指向同一个Expr_node int useCount; }; class Expr { friend std::ostream& operator<<(std::ostream& out, const Expr&); public: Expr(int); //创建Int_Node节点 Expr(const string&, Expr); //创建Unary_Node节点 Expr(const string&, Expr, Expr); //创建Binary_Node节点 Expr(const string&, Expr, Expr, Expr); //创建Ternary_Node节点 Expr(const Expr&); Expr& operator=(const Expr&); ~Expr(); int eval() const { return pNode->eval(); } private: Expr_node* pNode; //构造函数将创建合适类型的Expr_node的地址存储在pNode中,析构函数负责释放构造函数动态分配的节点 }; class Int_Node :public Expr_node //整数表达式 { friend class Expr; private: int n; Int_Node(int k) : n(k) { } void printNode(ostream& out) const { out << n; } int eval() const { return n; } }; class Unary_node :public Expr_node //一元表达式 { friend class Expr; private: string op; Expr opnd; //因为我们不知道子节点类型,所以构造不同类型的节点时我们绝不能按值,而是传入父类指针。 Unary_node(const string& a, Expr b) :op(a), opnd(b) { } void printNode(ostream& out) const { out << "(" << op.c_str() << opnd << ")"; } int eval() const { if (op == "-") return -opnd.eval(); } }; class Binary_Node :public Expr_node //二元表达式 { friend class Expr; private: string op; Expr left; //因为我们不知道子节点类型,所以构造不同类型的节点时我们绝不能按值,而是传入父类指针。 Expr right; Binary_Node(const string& a, Expr b, Expr c) :op(a), left(b), right(c) { } void printNode(ostream& out) const { out << "(" << left << op.c_str() << right << ")"; } int eval() const { int op1 = left.eval(); int op2 = right.eval(); if (op == "-") { return op1 - op2; } if (op == "+") { return op1 + op2; } if (op == "*") { return op1 * op2; } if (op == "/" && op2 != 0) { return op1 / op2; } } }; class Ternary_Node :public Expr_node // 三元表达式 { friend class Expr; private: 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 printNode(ostream& out) const { out << "(" << left << " ? " << middle << " : " << right << ")"; } int eval() const { if (left.eval()) return middle.eval(); else return right.eval(); } }; //针对Expr的输出操作符 std::ostream& operator<<(std::ostream& out, const Expr& t) { t.pNode->printNode(out); return out; } Expr::Expr(int n) { pNode = new Int_Node(n); } Expr::Expr(const string& op, Expr n) { pNode = new Unary_node(op, n); } Expr::Expr(const string& op, Expr a, Expr b) { pNode = new Binary_Node(op, a, b); } Expr::Expr(const string& op, Expr left, Expr middle, Expr right) { pNode = new Ternary_Node(op, left, middle, right); } Expr::Expr(const Expr& t) //拷贝构造,两者指向是同一个Expr_node { pNode = t.pNode; ++pNode->useCount; } Expr& Expr::operator=(const Expr& t) //赋值运算符,需要递增右边、递减左边 { t.pNode->useCount++; if (--pNode->useCount == 0) delete pNode; pNode = t.pNode; return *this; } Expr::~Expr() { if (--pNode->useCount == 0) { delete pNode; } } void main() { Expr expression = Expr("*", Expr("-", 5), Expr("+", 3, 4)); cout << expression << "=" << expression.eval() << endl; expression = Expr("*", expression, expression); cout << expression << "=" << expression.eval() << endl; }
相关文章推荐
- Lua编程示例(二):面向对象、metatable对表进行扩展
- C#中面向对象编程机制之多态学习笔记
- 浅谈Lua的面向对象特性
- Lua面向对象之类和继承浅析
- JavaScript面向对象的两种书写方法以及差别
- 浅谈c# 面向对象之类与对象
- C#面向对象特征的具体实现及作用详解
- C# 面向对象的基本原则
- 浅谈对c# 面向对象的理解
- Ruby面向对象编程详解
- C# 面向对象三大特性:封装、继承、多态
- php学习 面向对象 课件第1/2页
- PHP程序61条面向对象分析设计的经验小结
- 收集学习asp.net比较完整的面向对象开发流程
- javascript 面向对象编程 万物皆对象
- 不错的JavaScript面向对象的简单入门介绍第1/2页
- [推荐]javascript 面向对象技术基础教程
- javascript 面向对象的JavaScript类
- JavaScript中的面向对象介绍
- javascript实现面向对象类的功能书写技巧