(P43)面向对象版表达式计算器:更正设计缺陷

文章目录

1.更正设计缺陷

  • 算数表达式解析器的一个缺陷在于它的语法是右结合的。这意味着,8-2+1被解析为8-(2+1)
  • 可以通过更正语法
    可以设计成左结合的,把它设计成表达式+一个项;
    expresion:=expresion ‘+’ term,
    但这这会导致无限循环(Expr调用Expr,Expr又调用Expr。。。)
    前面的方法都是右递归,这里是左递归
  • 一种解决办法
    使用左结合的方法;
    SumNode有多个子代,主要处理+和-法运算;
    ProdectNode也有多个子代,主要处理*和/运算,这里没表示,后面看代码;
    在这里插入图片描述
  • 改进后的类图
    positives:处理节点的正负号,因为Node中的继承类NumberNode是不区分正负的,总是正的
    MultipleNode是一个抽象类,其派生类有2个,一个是SumNode(处理加减法),一个是ProductNode(处理乘除法)
    在这里插入图片描述
    使用StartUml
    在这里插入图片描述
    注意抽象类的画法
    在这里插入图片描述
  • eg:
    P43\Node.h
#ifndef _NODE_H
#define _NODE_H

#include <vector>

//(1)【采用】禁止对象拷贝的eg演示
//Noncopyable不能构造对象,因为构造对象没意义,仅仅用来继承
class Noncopyable
{
protected:
    Noncopyable() {};
    ~Noncopyable() {};
private:
    Noncopyable(const Noncopyable&);
    const Noncopyable& operator=(const Noncopyable&);
};

//用private继承的原因是:并没有继承Noncopyable类的接口,即:这不是接口继承,而是实现继承
//实现继承:仅仅利用基类的内部函数,仅仅能在派生类的内部使用,并不能成为派生类额接口
//Node变成了对象语义,因为:要构造Node,要先构造Noncopyable,而Noncopyable既不能拷贝构造,也不能赋值了
class Node : private Noncopyable
{
public:
    //每个节点都有一个计算的纯虚函数
    //类Node用于多态,派生类都要实现Calc
    //Calc声明为const的,因为Calc不会改变类的成员
    virtual double Calc() const = 0;
    //类Node是多态类,析构函数也要声明为虚析构函数,否则基类指针指向派生类对象,
    //通过基类指针释放对象的时候,是不会调用派生类的析构函数
    virtual ~Node() {};
};

//NumerNode要实现这个纯虚函数Calc,为具体类;若没实现,还是抽象类
class NumberNode : public Node
{
public:
    NumberNode(double number) : number_(number) {}
    double Calc() const;
private:
    const double number_;//加const的原因:因为number_初始化后就不会改变
};

//BinaryNode节点有2个子节点
//BinaryNode类没有实现Calc方法,BinaryNode类仍然是抽象类,只有它的派生类,加、减、乘、除节点才知道该如何计算
class BinaryNode : public Node
{
public:
    BinaryNode(Node* left, Node* right)
        : left_(left), right_(right) {}
    ~BinaryNode();//记得要释放left_和right_节点
protected:
    Node* const left_;//const的作用:指针不能改变(即指针不能指向其它的节点),而不是指针所指向的内容不能改变
    Node* const right_;
};

//与BinaryNode相比,它只有1个孩子
//UnaryNod也是抽象类,因为它没有实现Calc方法
class UnaryNode : public Node
{
public:
    UnaryNode(Node* child)
        : child_(child) {}
    ~UnaryNode();
protected:
    Node* child_;
}

// //加法运算节点AddNode
// class AddNode : public BinaryNode
// {
// public:
// //构造函数初始化,要调用基类部分的构造函数
//     AddNode(Node* left, Node* right)
//         : BinaryNode(left, right) {}
//     //要实现Calc方法,AddNode类是具体类
//     double Calc() const;
// };

// class SubNode : public BinaryNode
// {
// public:
//     SubNode(Node* left, Node* right)
//         : BinaryNode(left, right) {}
//     double Calc() const;
// };

// class MultiplyNode : public BinaryNode
// {
// public:
//     MultiplyNode(Node* left, Node* right)
//         : BinaryNode(left, right) {}
//     double Calc() const;
// };

// class DivideNode : public BinaryNode
// {
// public:
//     DivideNode(Node* left, Node* right)
//         : BinaryNode(left, right) {}
//     double Calc() const;
// };

class UminusNode : public UnaryNode
{
public:
    UminusNode(Node* child)
        : UnaryNode(child) {}
    double Calc() const;
};

//MultiplyNode没有实现Calc方法,还是一个抽象类
class MultipleNode : public Node
{
public:
    MultipleNode(Node*)
    {
        //添加第一个节点。第一个节点总是正的,比如-7-5+1,该-7就是第一个节点,在解析的时候它已经不是NumberNode
        //它应该是UminusNode,它里面已经能够处理负号了,应该把它当成是正的(+)-7
        AppendChild(node, true);
    }
    void AppendChild(Node* child, bool positive)
    {
        childs_.push_back(child);
        child_.push_back(positive);
    }
    ~MultipleNode();
private:
    //有很多子代,将其放入向量中
    std::vector<Node*> childs_;
    std::vector<bool> positive;//节点的正负性
};

class SumNode : public : MultipleNode
{
public:
    SumNode(Node* node)
        : MultipleNode(node) {}
    double Calc() const;
};

//乘积节点
class ProductNode : public : MultipleNode
{
public:
    SumNode(Node* node)
        : MultipleNode(node) {}
    double Calc() const;
};

#endif/* _NODE_H */

P43\Node.cpp

#include "Node.h"
#include <cmath.h>
#include <cassert>
#include <iostream>

//数字节点的计算方法就等于数字节点本身
double NumberNode::Calc() const
{
    return number_;
}
BinaryNode::~BinaryNode()
{
    delete left_;
    delete right_;
}

UnaryNode::~UnaryNode();
{
    delete child_;
}

// double AddNode::Calc() const
// {
//     //AddNode节点的值等于左计算节点得到的值+右计算节点得到的值
//     return left_->Calc() + right_->Calc();
// }

// double SubNode::Calc() const
// {
//     return left_->Calc() - right_->Calc();
// }

// double MultiplyNode::Calc() const
// {
//     return left_->Calc() * right_->Calc();
// }


// double AddNode::Calc() const
// {
//     double divisor = right_->Calc();
//     if (divisor != 0.0)
//         return left_->Calc() / divisor;
//     else
//     {
//         std::cout << "Error: Divisor by zero" <<std::endl;
//         return HUGE_VAL;
//     }
// }


double UnaryNode::Calc() const 
{
    //孩子节点前面加一个负号,对照改进类继承体系的图看更好理解
    return - child_->Calc();
}


MultipleNode::~MultipleNode()
{
    std::vector<Node*>::const_iterator it;
    for (it = children_.begin(); it != children_.end(); ++it)
    {
        //it实际上是Node*的指针的指针,它存放的类型是Node*,*it取出它里面存放的元素Node*
        delete *it;
    }
}

//把它的子节点计算一下,然后把它+起来
double SumNode::Calc() const
{
    double result = 0.0;
    std::vector<Node*>::const_iterator childIt = childs_.begin();
    std::vector<bool>::const_iterator positiveIt = positives_.begin();

    for (; childIt != childs_.end(); ++childIt, ++positive)
    {
        //实际上childs_和positives_的元素个数是一样的,childs_没有遍历到结尾,那么positives_也没有遍历到结尾
        assert(positiveIt != positives_.begin();
        double val = (*childIt)->Calc();//*childIt的类型是Node*
        if (*positiveIt)
            result + =val;
        else
            result -= val;
    }
    //childs_遍历到结尾,那么positives_也遍历到结尾
    assert(positiveIt == positives_.end());
    return result;
}

//把它的子节点计算一下,然后把它*起来
double ProductNode::Calc() const
{
    double result = 1.0;
    std::vector<Node*>::const_iterator childIt = childs_.begin();
    std::vector<bool>::const_iterator positiveIt = positives_.begin();

    for (; childIt != childs_.end(); ++childIt, ++positive)
    {
        //实际上childs_和positives_的元素个数是一样的,childs_没有遍历到结尾,那么positives_也没有遍历到结尾
        assert(positiveIt != positives_.begin();
        double val = (*childIt)->Calc();//*childIt的类型是Node*
        if (*positiveIt)
            result * =val;
        else if (val != 0.0)
            result /= val;
        else
        {
            std::cout<<"Division by zero"<<std::endl;
            return HUGE_VAL;
        }
    }
    //childs_遍历到结尾,那么positives_也遍历到结尾
    assert(positiveIt == positives_.end());
    return result;
}

P43\Parser.h

#ifndef _PARSER_H
#define _PARSER_H

//使用前向声明而不是包含Scanner的头文件的原因是,如果在cpp文件中多次包含了这样的头文件,使得生成的可执行文件增大
class Scanner;
class Node;//解决在 Node* Expr();中没有定义Node

enum STATUS
{
    STATUS_OK;
    STATUS_ERROR;
    STATUS_QUIT;
};


//Parser类:根据扫描结果,进行扫描,递归下降解析,直到生成一颗树
//Parser类与Scanner类之间的关系是什么?
//依赖关系:一个类作为另一个类成员函数的参数,或者内部的局部变量调用了某个类的静态成员函数
//而Scanner作为Parser类的成员,而且是引用: Scanner& scanner_;
//说明:Scanner类与Parser类具有一定的固定关系,在Parser类的生命周期内都要对scanner_的引用固定关系
//所以把他们看成关联关系
class Parser
{
public:
    Parser(Scanner& scanner);
    void Parse();
    Node* Expr();
    Node* Term();
    Node* Factor();
    double Calculate() const;
private:
    Scanner& scanner_;//这里是引用,即使Parser类销毁了,Scanner类也不不一定销毁,Parser类不负责Scanner类的生命周期
                    //若这里不是引用,Parser对象销毁,Scanner对象也跟着销毁,这就是组合关系了
                    //这里也可以用引用,但是会拷贝一个Scanner类对象拷贝到Parser类内部,组合的方式效率低一些,没必要用了
    Node* tree_;
    STATUS status_;
};



#endif /* _PARSER_H */

P43\Parser.cpp

#include "Parser.h"
#include "Scanner.h"//因为会使用到Scanner的一些接口进行扫描
#include "Node.h"

#include <cassert>
#include <iostream>

//引用的初始化只能才初始化列表中进行初始化
Parser::Parser(Scanner& scanner) : scanner_(scanner), tree_(0)
{

}

//解析表达式:
Node* Parser::Expr()
{
    Node* node = Term();
    EToken token = scanner_.Token();
    // if (token == TOKEN_PLUS)//扫描到+
    // {
    //     scanner_.Accept();//accept+号,扫描下一个字符,看看是不是一个Expression
    //     Node* nodeRight = Expr();
    //     node = new AddNode(node, nodeRight);//返回的是加法节点,(左节点,右节点),Expression is Term + Expression
    // }
    // else if (token == TOKEN_MINUS)
    // {
    //     scanner_.Accept();
    //     Node* nodeRight = Expr();
    //     node = new SubNode(node, nodeRight);//Expression is Term - Expression
    // }
    if (token == TOKEN_PLUS || token == TOKEN_MINUS)
    {
        //此时的表达式,Expr := Term{ ( '+'| '-' ) Term}
        MultipleNode* multipleNode = New SumNode(node);
        //子节点有多个,所以用循环
        do
        {
            scanner_.Accept();
            Node* nextNode = Term();
            multipleNode->AppendChild(nextNode, (token == TOKEN_PLUS));
            token = scanner_.Token();//更新当前扫描的状态,即:看下一个字符是加法还是减法,直到遇到其他负号,就不是SumNode的子代了

        }while(token == TOKEN_PLUS || token == TOKEN_MINUS)
        node = multipleNode;
    }
  

    return node;//Expression is Term
}

//解析项
Node* Parser::Term()
{
    Node* node = Factor();
    EToken token = scanner_.Token();
    // if (token == TOKEN_MULTIPLY)
    // {
    //     scanner_.Accept();
    //     Node* nodeRight = Term();
    //     node = new MultiplyNode(node, nodeRight);//Term is Factory * Term
    // }
    // else if (token == TOKEN_DIVIDE)
    // {
    //     scanner_.Accept();
    //     Node* nodeRight = Term();
    //     node = new DivideNode(node, nodeRight);//Term is Factory / Term
    // }

    if (token == TOKEN_MULTIPLY || token == TOKEN_DIVIDE)
    {
        //此时的表达式,Expr := Factor{ ( '*'| '/' ) Factor}
        MultipleNode* multipleNode = New SumNode(node);
        //子节点有多个,所以用循环
        do
        {
            scanner_.Accept();
            Node* nextNode = Factor();
            multipleNode->AppendChild(nextNode, (token == TOKEN_MULTIPLY));
            token = scanner_.Token();//更新当前扫描的状态,即:看下一个字符是加法还是减法,直到遇到其他负号,就不是SumNode的子代了

        }while(token == TOKEN_MULTIPLY || token == TOKEN_DIVIDE)
        node = multipleNode;
    }
    return node;//Expression is Factory
}

//解析因式
Node* Parser::Factor()
{
    //or (Expression)
    Node* node = 0;
    EToken token = scanner_.Token();
    if (token == TOKEN_LPARENTHESIS) 
    {
        scanner_.Accept(); //accept '('
        node = Expr();//先解析表达式,右边应该有个右括号
        if (scanner_ == TOKEN_RPARENTHESIS)
        {
            scanner_.Accept(); //accept ')'
        }
        else
        {
            status = STATUS_ERROR;
            //to do:抛出异常
            std::cout<<"missing parenthesis"<<std::endl;
            node = 0;
        }
        else if (token == STATUS_NUMBER)
        {
            node = new NumberNode(scanner_.Number());//新建一个数字节点
            scanner_.Accept();
        }
        else if(token == STATUS_MINUS)
        {
            scanner_.Accept();//接受一个负号,目的是指针偏移到下一个字符,让下一次的解析做准备
            node = new UminusNode(Factor());//传递一个子节点进去,这个子节点就是因式
        }
        else
        {   
            status = STATUS_ERROR;
            //to do:抛出异常
            std::cout<<"Not a valid expression"<<std::endl;
            node = 0;
        }
        
        return node;
    }
}

void Parser::Parse()
{
    tree_ = Expr();//解析完后,将整个表达式赋值给tree_
}

//注意:带const的成员函数与不带const的成员函数可以构成重载
double Parser::Calculate() const
{
    assert(tree_ != 0);//0,NULL都行
    //求表达式的值,实际上就是求其根节点的值
    return tree_->Calc();
}

P43\Scanner.h

#ifndef _SCANNER_H_
#define _SCANNER_H_
#include <string>

enum EToken
{
    TOKEN_END;
    TOKEN_ERROR;
    TOKEN_NUMBER;
    TOKEN_PLUS;
    TOKEN_MINUS;
    TOKEN_MULTIPLY;
    TOKEN_DIVIDE;
    TOKEN_LPARENTHESIS;
    TOKEN_RPARENTHESIS;
    TOKEN_IDENTIFIER;
    TOKEN_ASSIGN;//eg:a=5
};

//Scanner类:只负责扫描,并且登记当前的状态
class Scanner
{
public:
    Scanner(const std::string& buf);
    void Accept();
    double Number() const;
    EToken Token() const;
private:
    void SkipWhite();
    const std:string buf_;
    unsigned int curPos_;
    EToken token_;//返回状态
    double number_;//返回数字
};






#endif/*_SCANNER_H_*/

P43\Scanner.cpp

#include "Scanner.h"
#include <cctype>

Scanner::Scan(const std::string& buf) : buf_(buf), curPos_(0)
{
    Accept();//一个字符一个字符的扫描
}

double Scanner::Number() const
{
    return number_;
}

EToken Scanner::Token() const
{
    return token_;
}

//忽略空白字符
void Scanner::SkipWhite()
{
    while (isspace(buf_[curPos_]))
        ++curPos_;
}

void Scanner::Accept()
{
    SkipWhite();//首先忽略空白字符
    switch (buf_[curPos_])
    {
    case '+':
        token_ = TOKEN_ADD;
        ++curPos_;
        break;
    case '-':
        token_ = TOKEN_MINUS;
        ++curPos_;
        break;
    case '*':
        token_ = TOKEN_MULTIPLY;
        ++curPos_;
        break;
    case '/':
        token_ = TOKEN_DIVIDE;
        ++curPos_;
        break;
    case '(':
        token_ = TOKEN_LPARENTHESIS;
        ++curPos_;
        break;
    case ')':
        token_ = TOKEN_RPARENTHESIS;
        ++curPos_;
        break;
    case '0': case '1': case '2' : case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
    case '.':
        token_ = TOKEN_NUMBER;
        char* p;//实际上这里的指针并没有指向
        //buf_是一个字符串,buf_[curPos_]是一个字符,相当于得到了内部字符串的字符
        //这里的指针p,指针变量p本身发送改变,也就是说它指向了其他地方,改变了指针的指向
        number_ = strtod(&buf_[curPos_], &p);//返回第一个不是数字的位置
        //将地址p转换为数字curPos_,用以更新curPos_
        curPos_ = p - &buf[0];// &buf[0]是字符串的首地址
        break;
    case '\0' : case '\n' : case '\r' : case EOF://认为表达式结束了
        token_ = TOKEN_END;
        break;
    default:
        token_ = TOKEN_ERROR;
        break;
    }
}

P43\main.cpp

#include <iostream>
#include <string>
#include "Scanner.h"
#include "Parser.h"

int main(void)
{
    STATUS status= STATUS_OK;
    do
    {
        std::cout<<">";
        std::string buffer;
        std::getline(std::cin, buffer);//输入一行表达式放到buf当中
        // std::cout<<buffer<<std::endl;
        Scanner scanner(buffer);
        Parser parser(scanner);  
        parser.Parse();//实际上计算表达式的值,就是计算这颗树的根节点的值
        std::cout<<parser.Calculate()<<std::endl;
    }while(status != STATUS_QUIT);
    return 0;
}
  • 测试:
    在这里插入图片描述
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜欢打篮球的普通人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值