文章目录

  • 1.符号表SymbolTable的实现

1.符号表SymbolTable的实现

  • 接下来的任务是让表达式支持变量与函数

  • eg:a=100,a是符号

  • eg:a+5+log(0),a是变量

  • 这里变量名与函数都算是符号,所以要有一个表存储这些符号SymbolTable

    用map来表示,map<符号,符号所对应的id(从0开始)>
    Add:往符号表中增加一个符号,返回值是该符号的id;
    Find:根据某个符号,查找某个符号的id;
    Clear:清除;
    GetSymbolName:根据id查找符号名称;

  • startUML增加SymbolTable类

  • eg:
    P44\SymbolTable.h

#ifndef _SYMBOL_TABLE_H_
#define _SYMBOL_TABLE_H_
#include <map>
#include <string>class SymbolTable
{//枚举量enum { INNOTFOUND = 0xffffffff }
public: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_ */

P44\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;
}

下面的都没变化
P44\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_*/

P44\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;}
}

P44\Parser.h

#ifndef _PARSER_H
#define _PARSER_H//使用前向声明而不是包含Scanner的头文件的原因是,如果在cpp文件中多次包含了这样的头文件,使得生成的可执行文件增大
class Scanner;
class Node;//解决在 Node* Expr();中没有定义Nodeenum 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 */

P44\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();
}

P44\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,它里面已经能够处理负号了,应该把它当成是正的(+)-7AppendChild(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 */

P44\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;elseresult -= 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;
}

P44\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<<paese.Calculate()<<std::endl;}while(status != STATUS_QUIT);return 0;
}

(P44)面向对象版表达式计算器:符号表SymbolTable的实现相关推荐

  1. 表达式计算器类的设计4(面向对象的表达式计算器7)

    计算器的github下载地址:https://github.com/ljian1992/calculator 概述 把符号表和变量表中的内容保存到一个文件中,通过IO文件流,来把符号表和变量表存储到文 ...

  2. php:兄弟连之面向对象版图形计算器1

    曾经看细说PHP的时候就想做这个,可是一直没什么时间,这次总算忙里偷闲搞了代码量比較多的project. 首先,文档结构,都在一个文件夹下就好了,我的就例如以下. 一開始,进入index.php文件. ...

  3. 算法(第四版)-xmind查找-符号表

  4. php面向对象编写计算器,使用面向对象的图形计算器

    这个例子可能并不实用,但基本概括了面向对象的三个特征:继承性,封装性,多态性.本例的主要功能有:让用户可以选择不同类型的图形: 对所选的图形输入其相关属性: 根据输入的属性计算该图形的周长和面积. 效 ...

  5. php面向对象编写计算器,使用面向对象的图形计算器,面向对象图形计算器_PHP教程...

    使用面向对象的图形计算器,面向对象图形计算器 这个例子可能并不实用,但基本概括了面向对象的三个特征:继承性,封装性,多态性.本例的主要功能有: 效果如下: 思路: 需要改进的地方: index.php ...

  6. 从零写一个编译器(七):语义分析之符号表的数据结构

    项目的完整代码在 C2j-Compiler 前言 有关符号表的文件都在symboltable包里 前面我们通过完成一个LALR(1)有限状态自动机和一个reduce信息来构建了一个语法解析表,正式完成 ...

  7. java 线性计算器_java版科学计算器,支持表达式计算

    <java版科学计算器,支持表达式计算>由会员分享,可在线阅读,更多相关<java版科学计算器,支持表达式计算(12页珍藏版)>请在人人文库网上搜索. 1.程序设计综合设计题目 ...

  8. 用面向对象思想实现计算器功能(Java版)

    /*** 使用面向对象思想实现计算器的功能,要求实现加减乘除*/ import java.util.Scanner; public class TestCalculator {public stati ...

  9. 数学表达式: 从恐惧到单挑 (符号表)

    这里列出常用符号表, 便于查阅. 有些符号还需要逐步加入. 表1. 符号表. 其中"文字"在两边加上$符号即变成左边的符号. 符号 文字 涵义 备注 x x x x 标量 小写字母 ...

  10. 编译原理——C++版桌面计算器

    编译原理--C++版桌面计算器 系统描述 设计并实现一个桌面计算器,支持基本四则运算,关系运算,可编程运算 基本功能 (1)以命令行方式解析用户输入的表达式,计算并显示结果: (2)支持基本的整型和浮 ...

最新文章

  1. Re:CMM和RUP、XP的关系是什么?
  2. 算法工程师掌握了这个炼丹技巧的我开始突飞猛进
  3. 局域网聊天 一个十分热门的话题
  4. 你和高级工程师的差距在哪里?
  5. AJAX中get与post区别
  6. gettext实现多语言html中怎么处理,详解基于webpackgettext的前端多语言方案
  7. Java基础学习总结
  8. dll文件编辑器(Resource Hacker)下载
  9. JSP校园自行车租赁网站平台管理系统
  10. [视频相关2]网址解析接口
  11. excel拆分单元格内容_Excel中最神奇的一个快捷键!牛!!
  12. HTML结构及常用的标签
  13. 黑苹果无法连接wifi
  14. SpringBoot系列:Spring Boot集成定时任务Quartz,java百度云短信发送
  15. 树莓派3B配置无线路由器
  16. 大数据学习之javaAPI远程操作hadoop
  17. 认识计算机选题背景,与计算机专业相关的论文_计算机专业的毕业论文题目有哪些_大一我对计算机的认识3000字论文...
  18. 互斥锁深度理解与使用
  19. HTML保存变暗了怎么办,浮漂用久了漂尾变暗怎么办?试试这样做,没准还有救...
  20. win10非分页缓冲池占用过大的解决方法

热门文章

  1. 计算机卡住了怎样恢复,电脑频繁假死怎么办 电脑死机数据恢复
  2. Spark on Hive Hive on Spark,傻傻分不清楚
  3. exynos 4412 电源管理芯片PMIC 的配置及使用方法
  4. win10显示器亮度无法调节
  5. mod函数计算机,Excel中mod函数的使用方法
  6. 咚咚咚————【封装驱动】ADS1256驱动程序,分享交流自己编写的程序。
  7. 又看了一遍鲁迅的《祝福》
  8. 余承东 鸿蒙不是手机,余承东解密华为鸿蒙OS2.0:不是手机操作系统的简单替代-社会-文章-小虾米...
  9. 他发现了古老疟疾背后的元凶,也在质疑声中开创了致病生物的新时代
  10. iOS 中的常用设计模式总结