(P46)面向对象版表达式计算器:让表达式计算器支持变量赋值 ,Calc类实现

1.让表达式计算器支持变量赋值

  • eg:a=5,解析成一个表达式树应该如下,其中a变量的值应该从Storage类中去获取在这里插入图片描述
    AssignNode节点的Calc方法,比如:x=5,=号节点的值,其实等于x节点,也等于5节点,首先取出右节点的值,然后调用左节点的Assign方法
    在这里插入图片描述

  • 类图如下:
    新增变量节点VariableNode,VariableNode继承至Node,在Node中新增Assign方法,用以变量赋值,IsValue判断变量是左值,其他节点都不是左值;
    新增赋值运算节点AssignNode,它是二元运算节点,其左节点是变量,右节点是数字节点,所以继承至二元运算节点BinaryNode;
    在这里插入图片描述

  • startUML,只画新增的
    在这里插入图片描述
    在这里插入图片描述
    P46\Node.h

#ifndef _NODE_H
#define _NODE_H

#include <vector>
#include <cassert>

class Storage;

//(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 bool IsLvalue() const
    {
        return false;
    }
    
    //只有变量节点有赋值的方法,其他节点都没有
    virtual void Assign(double)
    {
        assert(!"Assign called incorrectlly");//“XX”字符串是真的,取一个!为假的,默认情况下断言为假的
    }
    
    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;
};


class VariableNode: public Node
{
public:
    VariableNode(unsigned int id, Storage& storage)
        : id_(id), storage_(storage) {}
    double Calc() const;
    bool IsLvalue() const;
    void Assign(double val);
private:
    const unsigned int id_;
    Storage& storage_;//变量节点的值从Storage类中获取,Storage类中存储了某个变量的值,所以需要Storage类的一个引用
};

class AssignNode : public BinaryNode
{
public:
//构造函数初始化,要调用基类部分的构造函数
    AssignNode(Node* left, Node* right)
        : BinaryNode(left, right) 
        {
           assert(left->IsLvalue());//断言是一个左值节点,能够被赋值的 
        }
    //要实现Calc方法
    double Calc() const;
};


#endif/* _NODE_H */

P46\Node.cpp

#include "Node.h"
#include "Storage.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;
}


double VariableNode::Calc() const
{
    double x = 0.0;
    if (storage_.IsInit(id_))
    {
        x = storage_.GetValue(id_);
    }
    else
    {
        std::cout<<"Use of uninitialized variable"<<std::endl;
    }
    
    return x;
}

bool VariableNode::IsLvalue() const
{
    return true;
}

void VariableNode::Assign(double val)
{
    storage_.SetValue(id_, val)
}

double AssignNode::Calc() const
{
    //首先取出右节点的值
    double x = 0.0;
    x = right->Calc();
    left_->Assign(x);
}

P46\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;
    string GetSymbol() const;
    EToken Token() const;
private:
    void SkipWhite();
    const std:string buf_;
    unsigned int curPos_;
    EToken token_;//返回状态
    double number_;//返回数字
    std::string symbol_;
};
#endif/*_SCANNER_H_*/

P46\Scanner.cpp

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

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

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

string Scanner::GetSymbol() const
{
    return symbol_;
}

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_ASSIGN;
        ++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:
        //字母或者下划线打头的才是标识符
        if (isalpha(buf_[curPos_]) || buf_[curPos_] == '_')
        {
            token_ = TOKEN_IDENTIFIER;
            symbol_.erase();
            char ch = buf_[curPos_];
            do
            {
                symbol_ += ch;
                ++curPos_;
                ch = buf_[curPos_];
            } while (isalnum(ch) || ch == '_');
            
        }
        else
        {
            token_ = TOKEN_ERROR;
        }
        
        break;
    }
}

2.Calc类实现

  • 将SymbolTable类和Storage类正常应该放到Parser类中(直接放进去应该也行),现在组合到Calc类中,SymbolTable类,Storage类与Calc类的关系是组合关系。
    解析器Parser需要符号表,函数表,存储表,现在只支持符号表SymbolTable,存储表Storage
    在这里插入图片描述

  • startUML,关系就不画了,线条太多了,麻烦
    在这里插入图片描述
    Calc类中的FindSymbol,AddSymbol,GetStorage成员函数按照Parse类中解析的需要所写的接口,也就是说,先写Parse类中对标识符的解析处理,然后再写的这里的这仨接口
    P46\Calc.h

#ifndef _CALC_H_
#define _CALC_H_

#include "SymbolTable.h"
#include "Storage.h"
#include <string>

class Parser;

class Calc
{
    //将Parser类声明为友元,可以方便Parser类方便访问Calc类中的私有成员
    friend class Parser;
public:
    Calc() : storage_(symTbl_) {}
private:
    //因为这里不是引用,需要将其头文件include进来,而不能使用前向声明了
    //SymbolTable类,Storage类与Calc类的关系是组合关系,其生命周期由Calc类来管理
    //此外,SymbolTable对象要在Storage对象之前构造,SymbolTable对象构造完毕后传递给Storage对象
    SymbolTable symTbl_;
    Storage storage_;

    unsigned int FindSymbol(const std::string& str) const;
    unsigned int AddSymbol(const std::string& str);
    Storage GetStorage()//不写成const Storage GetStorage() const的原因是:能够去更改storage_
    { 
        return storage_; 
    }
};

#endif /* _CALC_H_ */


P46\Calc.cpp

#include "Calc.h"

unsigned int Calc::FindSymbol(const std::string& str) const
{
    return symbol_.Find(str);
}

unsigned int Calc::AddSymbol(const std::string& str)
{
    return symbol_.Add(str);
}

P46\Parser.h

#ifndef _PARSER_H
#define _PARSER_H

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

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, Calc& calc);
    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_;
    Calc& calc_;
};



#endif /* _PARSER_H */

P46\Parser.cpp

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

#include <cassert>
#include <iostream>

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

}

//解析表达式:
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;
    }
    else if (token == TOKEN_ASSIGN) //项还能够解析等号,比如x=5,x是项,5是表达式;x=y=5,x是项,y=5是表达式
    {
        //Expr:=Term=Expr, :=这是一种BNF式的表达法
        scanner_.Accept();
        Node* nodeRight = Expr();
        if (node->IsLvalue())
        {
            node = new AssignNode(node, nodeRight);//这里是对创建的变量节点,进行的赋值操作
        }
        else
        {
            status_ = STATUS_ERROR;
            std::cout<<"The left-hand side of an assignment must be a variable"<<std::endl;//必须是左值节点
            //Todo 抛出异常
        }
        
    }


    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 if (token == TOKEN_IDENTIFIER)//解析到一个标识符
        {
            std::string symbol = scanner_.GetSymbol();
            unsigned int id = calc_.FindSymbol(symbol);
            scanner_.Accept();
            if (id == SymbolTable::IDNOTFOUND)
            {
                id = calc_.AddSymbol(symbol);
            }
            node = new VariableNode(id, calc_.GetStorage());//创建一个变量节点
        }
        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();
}

P46\main.cpp

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

int main(void)
{
    Calc calc;
    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, calc);  
        parser.Parse();//实际上计算表达式的值,就是计算这颗树的根节点的值
        std::cout<<paese.Calculate()<<std::endl;
    }while(status != STATUS_QUIT);
    return 0;
}

没变化的有:
P46\SymbolTable.h

#ifndef _SYMBOL_TABLE_H_
#define _SYMBOL_TABLE_H_
#include <map>
#include <string>

class SymbolTable
{
    //直接放在public上面是私有的
public:
    //枚举量
    enum { INNOTFOUND = 0xffffffff }
    SymbolTable() : curId_(0) {}
    unsigned int Add(const std::string& str);
    unsigned int Find(const std::string& str) const;
    void Clear();
    std::string GetSymbolName(unsigned int id) const;

private:
    std::map<std::string, unsigned int> dictionary_;
    unsigned int curId_;
};

#endif /* _SYMBOL_TABLE_H_ */

P46\SymbolTable.cpp

#include "SymbolTable.h"

unsigned int SymbolTable::Add(const std::string& str)
{
    dictionary_[str] = curId_;
    return curId_++;//返回值是当前符号的id,然后再++
}
    
unsigned int SymbolTable::Find(const std::string& str) const
{
    map<const std::string, unsigned int>::const_iterator it;
    it = dictionary_.find(str);
    if (it != dictionary_.end() )
        return it->second;
    return INNOTFOUND; 
}
    //不等于end,说明找到了
void SymbolTable::Clear()
{
    dictionary_.clear();
    curId_ = 0;
}

//函数对象function object,反函数functor
//目的:让一个类对象使用起来像一个函数,本质上是重载括号运算符
/*
IsEqualId ie;
is();此时ie使用起来像一个函数
*/
//这是STL6大组件
//迭代器pair有2个要素:fist是string,second是unsigned int
class IsEqualId
{
public:
    IsEqualId(unsigned int id) : id_(id) {}
    bool operator(const std::pair<std::string,unsigned int>& it) const
    {
        return it.second == id_;
    }
private:
    unsigned int id_;
};


std::string SymbolTable::GetSymbolName(unsigned int id) const
{
    //find_if(XX,XX,类)
    //将类传递进去,将类看成是一个函数
    map<const std::string, unsigned>::const_iterator it;
    it = find_if(dictionary_begin(), dictionary_end(), IsEqualId(id));

    return it->first;
}

P46\Storage.h

#ifndef _STORAGE_H
#define _STORAGE_H

#include <vector>
class SymbolTable;//这里是前向声明,需要在cpp文件中包含#include "SymbolTable.h"

class Storage
{
public:
    Storage(SymbolTable& tbl);
    void Clear();
    bool IsInit(unsigned int id) const;
    void AddConstant(SymbolTable& tbl);
    double GetValue(unsigned int id) const;
    void SetValue(unsigned int id, double val);
    void AddValue(unsigned int id, double val);
private:
    std::vector<double> cells_;
    std::vector<bool> inits_;
};




#endif /* _STORAGE_H */

P46\Storage.cpp

#include "Storage.h"
#include "SymbolTable.h"
#include <cmath>
#include <cassert>

Storage::Storage(SymbolTable& tbl)
{
    //先添加常量到符号表中
    AddConstant(tbl);
}

void Storage::Clear()
{
    cells_.clear();
    inits_.clear();
}

bool Storage::IsInit(unsigned int id) const
{
    //id不能超过cells_元素中的最大值
    //下标就是id,第0变量是否初始化了,那么就是inits_[0]
    return id < cells.size() && inits_[id];
}

//添加2个常量
void Storage::AddConstant(SymbolTable& tbl)
{
    unsigned int id = tbl.Add("e");
    AddValue(id, exp(1.0));//exp(1.0):代表e的1一次方

    id = tbl.Add("pi");
    AddValue(id, 2.0*acos(0.0));//pi=2*acos(0),acos是反余弦函数
}

double Storage::GetValue(unsigned int id) const
{
    assert(id < cells_.size());
    return cells_[id];
}

void Storage::SetValue(unsigned int id, double val)
{
    assert(id <= cells_.size());
    if (id < cells_.size())//id已经存在重新赋值
    {
        cells_[id] = val;
        inits_[id] = true;
    }
    else if (id == cells_.size())//id超过size,也就是说id所对应的变量不存在
    {
        AddValue(id, value);
    }
    
}

void Storage::AddValue(unsigned int id, double val)
{
    //为啥不使用push_back?
    /*
    比如cells_空间里面有6个元素了,此时cells_.push_back(val);此时向量里面的空间并不等于7,有可能是12,
    会导致size为12,实际上并没存放12个变量或常量,我们希望向量cells_存放多少变量或常量,其空间就为多少,即size就为
    多少,所以不用
    */
   //先扩充一个容量
   cells_.resize(id+1);
   inits_.resize(id+1);
   cells_[id] = value;
   inits_[id] = true;
}
  • 测试:
    在这里插入图片描述
calculator.cpp 能够实现四则运算和乘方运算、赋值及报错的计算器 2018-12-4 -------------------------------------------------- 本程序的输入有三:指令、赋值语句和计算式 这三输入均允许在基本元素之间添加任意数目的空格 当出现错误时,本程序会显示Error并给出报错原因 ·指令 指令包含以下三个 exit 退出程序 printvar 输出所有已定义的变量及其值 emptyvar 清除所有变量 所有变量名不能与指令重名 ·赋值语句 基本格式为 变量名=数值 其中“数值”可以是一行计算式 ·计算式 所有非指令和赋值语句都会作为计算式进行计算 本程序可以对输入的一行计算式的值进行计算并输出答案 按照题目要求,若结果为整数,将输出整数;若结果为浮点数,将保留两位小数 计算式中可以包含变量、数字和运算符号 计算式直接以运算符开头会报错“出现连续的运算符”,但如果以+-直接开头会被认作数的正负符号 -------------------------------------------------- 输入的基本元素包括变量、数字和运算符号 ·变量变量名必须以字母或下划线开头,由字母、数字或下划线组成,不能与指令重名 ·数字 可以是整数或者小数,当然也包括用/表示的分数 暂不支持除十进制外其他进制的输入 支持.23这样的省略整数部分0的输入 支持32.这样的输入 但是.不会被认为是0而会报错 ·运算符号 包括 加号+ 减号- 乘号* 除号/ 括号( ) 乘方^ 其中除数不能为零 乘方的底数是负数时,指数需要是整数 零的零次幂没有意义 -------------------------------------------------- 请注意 请在输入时务必使用英文输入或选择半角字符! 当语句中出现多个错误时,只会提示最先发现的错误 与c/c++表达式计算不同的地方: 本程序输出时会将整数相除产生的浮点数自动进行型转换成浮点数,也会将浮点数运算得到的整数自动型转换成整数,也就是说1/2=0.50,2.0/1.0=2(c++的自动取整真的很蠢) c/c++为了避免与函数冲突,不允许a(b+c),2(3+1)这样的写法,而本程序会默认在括号前进行了乘法运算,即2(3+1)=2*(3+1)=8,a(b+c)=a*(b+c) cmath中的pow函数对于0的0次方会返回1,但是0的0次方是没有意义的,本程序会对0^0报错 鉴于c++支持+-+-+-1=-1,但是出现连+或连-时会报错的混乱情况,本程序允许在数字和变量前加一个+或-号用来变号,即2+-1 3*-x -3++2是支持的,但是多于一个的额外+-号将报错,即3+-+2 +-1是不支持的 --------------------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

喜欢打篮球的普通人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值