• 算术表达式树


    一个面向对象范例(算术表达式树)

    1.     问题描述

    用程序来表示算术表达式。例如表达式(-5*3+4)对应的树如下:

     

    该表达式树包括常数、一元运算符和二元运算符的节点。编写合适的函数来创建这样的树,然后打印该树的完整括号形式。

    2.     分析

    考虑定义一系列的类,用继承组织起来。这些类有一些共同的点:每个类都要存储一个值以及一些子节点。图中有三种节点:一种表示整数表达式,包含一个整数值,无子节点。另外两种分别表示一元表达式和二元表达式,包含一个操作符,分别有一个或两个子节点。各个节点之间是相互独立的。所以需要定义一个表示节点的基类。如下:

    Expr.h

    #ifndef   _EXPR_H
    
    #define   _EXPR_H  
    
    #include <iostream>
    
    #include <string>
    
    using namespace std;
    
    namespace Meditation
    
    {
    
        //公共基类,表示节点
    
     class Expr_node
    
     {
    
        friend ostream& operator<<(ostream &,const Expr_node &);
    
     public:
    
         virtual void print(ostream&) const =0;
    
         virtual ~Expr_node(){ }
    
     };
    
     
    
    //整数表达式节点
    
     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 print(ostream &o)const
    
         {
    
            o<<"("<<op<<*opnd<<")";
    
         }
    
     
    
     };
    
     
    
     //二元表达式节点
    
     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),left(b),right(c)
    
         { }
    
         void print(ostream &o)const
    
         {
    
            o<<"("<<*left<<op<<*right<<")";
    
         }
    
     };
    
    }
    
    #endif
    
    


     

    Expr.cpp

    #include "expr.h"
    
    using namespace Meditation;
    
    ostream& operator<<(ostream & o,const Expr_node & e)
    
    {
    
        e.print(o);
    
        return o;
    
    }
    
    


     

    上面的方法,有一个问题。用户处理的不是值而是指针,所以必须记住分配和释放对象。动态分配:

    Binary_node *t=new Binary_node(“*”,new Unary_node(“-”,new Int_node(5)),
    
                               new Binary_node(“+”,new new Int_node(3),
    
     new Int_node(4)));
    
    

    这里,我们必须记住删除这些节点,但是我们不再拥有指向内层new调用所构造出来的对象的指针了!如果通过Binary_node和Unary_node的析构函数删除它们的操作数,也不行,因为可能会多次删除对象,可能有不止一个Expr_node指向同一个下层的表达式树对象。

    3.     句柄类

    在Expr_node类族中,仅仅表示了图中的圆圈,而没有对箭头建模,正是因为这里把箭头简单的描述成指针,这些用户需要亲自操作指针。所以,类Expr应该是一种句柄类,表示一个边或者说该树结构根源一个边。由于用户关心的是树而不是树种的单个节点,可以用Expr来隐藏Expr_node的继承层次。在Expr中通过构造函数创建三种Expr_node,并且将这个对象的地址存储在创建中的Expr对象中。Expr类的用户就不会直接看到Expr_node 对象了。

    class Expr {
    
        friend class Expr_node;
    
        friend ostream& operator<<(ostream&, const Expr&);
    
        Expr_node* p;
    
    public:
    
        Expr(int);
    
        Expr(const String&, Expr);
    
        Expr(const String&, Expr, Expr);
    
        Expr(const String&, Expr, Expr, Expr);       
    
        Expr(const Expr& t) { p = t.p; ++p->use; }//由于构造函数为Expr_node分配了内存,需要实现复制构造函数和赋值操作符管理下层的Expr_node
    
        ~Expr() { if (--p->use == 0) delete p; }
    
        Expr& operator=(const Expr& t);
    
        int eval() const { return p->eval(); }//新添加的,为了求表达式的值
    
    };
    
    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 String& op, Expr left, Expr middle, Expr right)
    
    {
    
        p = new Ternary_node(op, left, middle, right);
    
    }
    
    


    当Expr类复制一个Expr_node时,该Expr将其引用计数加1,当引用者为0,删除底层的Expr_node。

    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_node,Expr和Expr_node类协同管理引用计数,当且仅当一个Expr_node的引用计数等于0,该节点才被删除。所以需要在Expr_node中加入引用计数,Expr类声明为友元,帮助管理计数。

    class Expr_node {
    
        friend class Expr;
    
            friend ostream& operator<<(ostream&, const Expr&);
    
        int use;
    
    protected:
    
        Expr_node(): use(1) { }
    
            virtual void print(ostream&) const = 0;
    
            virtual int eval() const = 0;
    
            virtual ~Expr_node() { }
    
    };
    
    


     

    更改每个派生自Expr_node的类,令其操作为私有,将Expr声明为友元,存储Expr。

    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 << "(" << left << op << right << ")"; }
    
    };
    
    


     

    现在用户可以自由地声明Expr的对象和临时对象,构造任意复杂的表达式,而不需要考虑内存管理。

    如:Expr t = Expr("*", Expr("-", 5), Expr("+", 3, 4));

     

    求表达式的值

    cout << t << " = " << t.eval() << endl;

    或者

    t = Expr("*", t, t);

        cout << t << " = " << t.eval() << endl;

    需要在Expr类中添加函数,把求值传递给其指向的Expr_node。

    int eval() const { return p->eval(); }

    在Expr_node中 添加一个虚函数。

    class Expr_node {

    protected:

            virtual int eval() const = 0;

           //其他和前面的相同

    };

    接着为每个派生自Expr_node的子类添加一个函数来实现求值运算。

    Int_node最简单,直接返回其值即可。

    int eval() const { return n; }  

    对于Unbry_node

    int
    
    Unary_node::eval() const
    
    {
    
        if (op == "-")
    
                    return -opnd.eval();//会调用Expr类的成员eval,然后再调用其指向的Expr_node的虚函数eval,调用到了实际类型的eval();
    
            throw "error, bad op " + op + " in UnaryNode";
    
    }
    
    


     

    对Binary_node:

    int
    
    Binary_node::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;
    
     
    
            throw "error, bad op " + op + " in BinaryNode";
    
    }
    
    


     

    现在可以对表达式进行求值。

     

    int main()
    
    {
    
        Expr t = Expr("*", Expr("-", 5), Expr("+", 3, 4));
    
        cout << t << " = " << t.eval() << endl;
    
        cout << "((-5)*(3+4)) = -35" << endl;
    
     
    
        t = Expr("*", t, t);
    
        cout << t << " = " << t.eval() << endl;
    
        cout << "(((-5)*(3+4))*((-5)*(3+4))) = 1225" << endl;
    
        return 0;
    
    }
    


  • 相关阅读:
    windows 临界区 InitializeCriticalSectionAndSpinCount以及InitializeCriticalSection的区别
    SRWLock 轻量级的读写锁
    QT 遍历获取Form上的控件
    mssql 查询作业执行情况,耗时 等
    C++ builder FMX 遍历窗口所有控件 并 动态消失
    delphi fmx 控件从天上掉下来
    Vue2入门必知必会
    人人开源&项目脚手架&微服务整合
    Spring Security应用到源码分析
    K8S系统学习笔记总览
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3295150.html
Copyright © 2020-2023  润新知