(P44)面向对象版表达式计算器:符号表SymbolTable的实现
文章目录
- 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的实现相关推荐
- 表达式计算器类的设计4(面向对象的表达式计算器7)
计算器的github下载地址:https://github.com/ljian1992/calculator 概述 把符号表和变量表中的内容保存到一个文件中,通过IO文件流,来把符号表和变量表存储到文 ...
- php:兄弟连之面向对象版图形计算器1
曾经看细说PHP的时候就想做这个,可是一直没什么时间,这次总算忙里偷闲搞了代码量比較多的project. 首先,文档结构,都在一个文件夹下就好了,我的就例如以下. 一開始,进入index.php文件. ...
- 算法(第四版)-xmind查找-符号表
- php面向对象编写计算器,使用面向对象的图形计算器
这个例子可能并不实用,但基本概括了面向对象的三个特征:继承性,封装性,多态性.本例的主要功能有:让用户可以选择不同类型的图形: 对所选的图形输入其相关属性: 根据输入的属性计算该图形的周长和面积. 效 ...
- php面向对象编写计算器,使用面向对象的图形计算器,面向对象图形计算器_PHP教程...
使用面向对象的图形计算器,面向对象图形计算器 这个例子可能并不实用,但基本概括了面向对象的三个特征:继承性,封装性,多态性.本例的主要功能有: 效果如下: 思路: 需要改进的地方: index.php ...
- 从零写一个编译器(七):语义分析之符号表的数据结构
项目的完整代码在 C2j-Compiler 前言 有关符号表的文件都在symboltable包里 前面我们通过完成一个LALR(1)有限状态自动机和一个reduce信息来构建了一个语法解析表,正式完成 ...
- java 线性计算器_java版科学计算器,支持表达式计算
<java版科学计算器,支持表达式计算>由会员分享,可在线阅读,更多相关<java版科学计算器,支持表达式计算(12页珍藏版)>请在人人文库网上搜索. 1.程序设计综合设计题目 ...
- 用面向对象思想实现计算器功能(Java版)
/*** 使用面向对象思想实现计算器的功能,要求实现加减乘除*/ import java.util.Scanner; public class TestCalculator {public stati ...
- 数学表达式: 从恐惧到单挑 (符号表)
这里列出常用符号表, 便于查阅. 有些符号还需要逐步加入. 表1. 符号表. 其中"文字"在两边加上$符号即变成左边的符号. 符号 文字 涵义 备注 x x x x 标量 小写字母 ...
- 编译原理——C++版桌面计算器
编译原理--C++版桌面计算器 系统描述 设计并实现一个桌面计算器,支持基本四则运算,关系运算,可编程运算 基本功能 (1)以命令行方式解析用户输入的表达式,计算并显示结果: (2)支持基本的整型和浮 ...
最新文章
- Re:CMM和RUP、XP的关系是什么?
- 算法工程师掌握了这个炼丹技巧的我开始突飞猛进
- 局域网聊天 一个十分热门的话题
- 你和高级工程师的差距在哪里?
- AJAX中get与post区别
- gettext实现多语言html中怎么处理,详解基于webpackgettext的前端多语言方案
- Java基础学习总结
- dll文件编辑器(Resource Hacker)下载
- JSP校园自行车租赁网站平台管理系统
- [视频相关2]网址解析接口
- excel拆分单元格内容_Excel中最神奇的一个快捷键!牛!!
- HTML结构及常用的标签
- 黑苹果无法连接wifi
- SpringBoot系列:Spring Boot集成定时任务Quartz,java百度云短信发送
- 树莓派3B配置无线路由器
- 大数据学习之javaAPI远程操作hadoop
- 认识计算机选题背景,与计算机专业相关的论文_计算机专业的毕业论文题目有哪些_大一我对计算机的认识3000字论文...
- 互斥锁深度理解与使用
- HTML保存变暗了怎么办,浮漂用久了漂尾变暗怎么办?试试这样做,没准还有救...
- win10非分页缓冲池占用过大的解决方法
热门文章
- 计算机卡住了怎样恢复,电脑频繁假死怎么办 电脑死机数据恢复
- Spark on Hive Hive on Spark,傻傻分不清楚
- exynos 4412 电源管理芯片PMIC 的配置及使用方法
- win10显示器亮度无法调节
- mod函数计算机,Excel中mod函数的使用方法
- 咚咚咚————【封装驱动】ADS1256驱动程序,分享交流自己编写的程序。
- 又看了一遍鲁迅的《祝福》
- 余承东 鸿蒙不是手机,余承东解密华为鸿蒙OS2.0:不是手机操作系统的简单替代-社会-文章-小虾米...
- 他发现了古老疟疾背后的元凶,也在质疑声中开创了致病生物的新时代
- iOS 中的常用设计模式总结