编译原理 | 实验四 | 逆波兰式
目录
一.问题分析
二.算法思想
1.关于分词器
2.关于逆波兰式分析器:
三.实现代码
1.头文件 & 类视图
2.预处理部分
3.逆波兰分析过程
4.计算,输出部分
四.总结
一.问题分析
将用中缀式表示的算术表达式转换为用逆波兰式(后缀式)表示的算术表达式,并计算用逆波兰式来表示的算术表达式的值.
如输入如下: 21+(( 42-2) *15+6 ) -18#
输出为:
原来表达式: 21+ (( 42-2 ) *15+6 ) - 18#
后缀表达式: 21&42&2&-15&*6&++18&-
计算结果: 609
二.算法思想
1.关于分词器
将输入串的所有非空白元素添加到一个vector <string> 数组里
在输入串(string)里设置两个(广义上的)指针,一前一后指向非空白元素.遇到空白字符自动向后跳
前 \ 后 |
数字 |
字符 |
数字 |
temp+=input[cur] |
temp+= input[cur] token.push_back(temp) temp.clear() |
字符 |
temp+=input[cur] token.push_back(temp) temp.clear() 比如(5+…) |
假设++,--操作是不存在的 Ep:碰到(3+5)*3 temp+=input[cur] token.push_back(curString) cur.clear() |
2.关于逆波兰式分析器:
或阅读该文:逆波兰分析
三.实现代码
1.头文件 & 类视图
using namespace std;
#include<iostream>
#include<string>
#include<stack>
#include<vector>
#include<algorithm>/*
逆波兰式不需要括号,当从中缀式转换为RPN时,遇到 ( 括号要暂存之后的运算符;遇到 ) 括号要将内部的运算符(在TempStack)挨个转移到finStack逆波兰计算的是表达式,可以写一个简化的分词器函数(只分析数字,运算符,空格),
将各个长度不一(如"55"长度为2,"+"长度为1)的元素分别进入一个 vector<string> token;
这个函数无需分析词性.分析的工作在核心函数Analysis()中对token[i][0]判别完成,
Analysis()同时还要调用isSym()来进行判别工作tempStack 总是将优先级高的运算符放在栈顶,压住优先级低的运算符.当转移到finStack时,优先级高的先转移
*/class RPN
{
public:RPN(string);~RPN();bool isLegal(); //检测输入串是否含有非法字符(不是运算符||操作数的字符)bool isNum(char); //经过分词程序后形成的vector<string> token. input:token[i][0] 每个元素的第一个字符void divideElement(); //分词器int Prio(char); //判断运算符的优先级,数字越小,优先级越弱void analysis(); //分析程序,将token->逆波兰式void Print(); //打印整个逆波兰式void Computing(); //根据逆波兰式计算解float getRes(); //返回最终值
private:stack<string>tempExp; //临时栈,记录临时存放的运算符stack<string>finExp; //最终栈,操作数依序放入即可vector<string>Result; //记录fin栈的结果vector<string>token; //接收分词后的字符串string input; //接收输入的字符串float res; //接收计算逆波兰式的最终结果};
2.预处理部分
包括判断是否数字,输入是否合法,将输入串分词为独立的元素,存入token 数组
/*isLegal(): 检测是否存在非法字符,即除了数字和运算符之外的其他所有字符input: 类数据成员 vector<string> Inputout: 存在非法字符,报错. (调用isLegal的函数会据此决定是否返回)
*/
bool RPN::isLegal()
{bool flag = true;for (size_t i = 0; i < input.size(); i++) //遍历输入串的每一个char{if ((input[i] >= '0') && (input[i] <= '9')) //判断数字{continue;}else //判断运算符{if (input[i] != '+' || input[i] != '-' || input[i] != '*' || input[i] != '/' ||input[i] != '(' || input[i] != ')' || input[i] != '#' || input[i] != ' '){flag = false;}}}return flag;;
}/*isSym(): 经过isLegal() 处理后,只剩下数字,运算符,空格return true: 是运算符return false: 是数字
*/
bool RPN::isNum(char ch)
{if (ch>='0' &&ch<='9'){return true;}else{return false;}
}/*divideElement(): 将输入串分为一个个不含空格的元素,存储在一个vector<string>里input: 成员变量:input output: 成员变量: vector<string> token*/
void RPN::divideElement()
{size_t next = 1; //一开始,指向input[1]string temp; //临时存放字符for (size_t cur = 0; cur < input.size(); cur++,next++) //cur从[0]->[n-2]{if (next == input.size() - 1) { next--; }//当next到达最后一个,cur到达倒数第二个时.让next回退 1,可以有效阻止range溢出if (input[cur] == ' ' || input[cur] == '\t') //跳过空格{cur++;next = cur + 1; //next总指向cur下一个}if (input[next] == ' '|| input[next] == ' \t'){next++;}if (isNum(input[cur]))//第一个是数字{if (isNum(input[next])) //第二个是数字{temp += input[cur]; //将前一位添加到tempif (cur == input.size() - 1) //当到达末尾时{token.push_back(temp);temp.clear();}}else //第二个是字符{temp += input[cur]; //前一个进入temptoken.push_back(temp); //token添加这个temptemp.clear(); //清空temp,待留下次使用}}else{temp += input[cur];token.push_back(temp);temp.clear();}}
}
3.逆波兰分析过程
包括优先级判断函数,以及核心的逆波兰分析程序
/*Prio(): 对每个字符进行判别input: token[i][0],即token每个元素的第一个字符return: 该字符的优先级
*/
int RPN::Prio(char ch)
{switch (ch){case '#':return 0;case'+':case'-':return 1;case'*':case'/':case'×':return 2;case'(':case')':return 3;default:break;}return 0;
}/*analysis(): 程序的核心部分,进行逆波兰式的转换input(): token向量,temp栈,fin栈return: finStack
*/
void RPN::analysis()
{tempExp.push("#"); //先将优先级最低的"#"放在栈底for (size_t i = 0; i < token.size(); i++) //遍历token{if (isNum(token[i][0])) //要么是数字{finExp.push(token[i]);}else //要么是运算符{if (token[i] == "(") //若x是'(',则直接压入temp{tempExp.push(token[i]);}else if (token[i] == ")") //若x是')',则将距离栈s1栈顶的最近的'('之间的运算符,逐个出栈,依次压入栈fin,{while (tempExp.top() != "("){finExp.push(tempExp.top());tempExp.pop();}tempExp.pop(); //此时抛弃 "("}else //此时不是( 与 ){if (tempExp.top()=="(") //此时temp栈顶是(,直接将扫描字符压入temp,因为()内是优先级最高的{tempExp.push(token[i]);}else //栈顶元素不为'('{if (Prio(token[i][0])>Prio(tempExp.top()[0])) //如果 token[i]的优先级 > temp栈顶的优先级,直接压入{tempExp.push(token[i]);}else{while (Prio(token[i][0]) <= Prio(tempExp.top()[0]) && tempExp.top()!="(") //否则,不断地将temp的栈顶转移到fin(不要括号),直到( temp的栈顶 的优先级 小于 token[i] 的优先级 或 栈顶为"("{finExp.push(tempExp.top());tempExp.pop();}tempExp.push(token[i]);}}}}}//检查tempExp是否为空,否则依次转移到finExpif (!tempExp.empty()){while (tempExp.top()!="#"){finExp.push(tempExp.top());tempExp.pop();}}
}
4.计算,输出部分
包括将栈中的元素取出到一个vector,逆波兰式计算值
/*Print: 打印结果input: finExp栈return: 一串cout结果
*/
void RPN::Print()
{//倒腾数据结构while (!finExp.empty()){Result.push_back(finExp.top());finExp.pop();}//想要逆转过来reverse(Result.begin(), Result.end());//逆序打印for (size_t i = 0; i <Result.size(); i++){cout << Result[i] << " ";}cout << endl;
}void RPN::Computing()
{while (Result.size()!=1) //这样写比较耗费性能,容器物理上的增删需要花费额外的时间与内存.但是使用指针式的写法需要更多时间与精力,遂作罢{for (size_t i = 0; i < Result.size(); i++){if (!isNum(Result[i][0])) //需要找到第一个运算符{float temp=0.0;switch (Result[i][0]){case '+':temp = stof(Result[i - 2]) + stof(Result[i - 1]);//新值放在原运算符的位置上break;case '-':temp = stof(Result[i - 2]) - stof(Result[i - 1]);break;case '/':temp = stof(Result[i - 2]) / stof(Result[i - 1]);break;case'*':case'×':temp = stof(Result[i - 2]) * stof(Result[i - 1]);break;default:break;}Result[i] = to_string(temp); //新值放在原运算符的位置上Result.erase(Result.begin() + i - 2); //删除前两个操作数Result.erase(Result.begin() + i - 2);i = 0; //每次执行完后重新从首元素开始}}}res=stof(Result[0]);
}float RPN::getRes()
{return this->res;
}
5.mian()部分
int main()
{string str="21+(( 42-2)*15+6 ) -18";cout << "表达式: 21+(( 42-2)*15+6 ) -18\n";RPN rpn(str);//输入串判别if (!rpn.isLegal()){cout << "含有非法字符,请重新输入\n";//此处可以有一个goto语句}//分词rpn.divideElement();//分析rpn.analysis();//打印结果rpn.Print();//计算值rpn.Computing();cout<<rpn.getRes();system("pause");return 0;
}
四.总结
其实最后的计算值部分应该用传址的方式写的,但那样需要更多的时间与精力,时间上不允许优化效率了.
编译原理 | 实验四 | 逆波兰式相关推荐
- 编译原理实验二-逆波兰式生成程序
一.实验目的和要求: 1. 掌握语法分析的基本思想,并用高级语言编写逆波兰式生成程序 2. 要求利用逆波兰式生成算法编写程序,将从键盘上输入的算术表达式 (中缀表达式)转化成逆波兰式 二.实验平台: ...
- 编译原理逆波兰式实验java_逆波兰式算法的编译原理实验过程.doc
逆波兰式算法的编译原理实验过程 实验目的 深入理解算符优先分析法 掌握FirstVt和LastVt集合的求法有算符优先关系表的求法 掌握利用算符优先分析法完成中缀表达式到逆波兰式的转化 实验内容及要求 ...
- 笔记-编译原理-实验四-语义分析与中间代码生成
实验四. 语义分析及中间代码生成 设计思想 根据对属性文法及语义分析.中间代码生成的学习,可以将实验二.三的两种语法分析器进行一定的改造,以达到进行语法分析的同时进行语义分析并生成中间代码.根据PL0 ...
- 编译原理-实验四-LR(0)语法分析程序的设计
一.实验目的 了解LR(0)语法分析算法的基本思想,掌握LR(0)语法分析程序的构造方法. 二.实验内容 根据LR(0)语法分析算法的基本思想,设计一个对给定文法进行LR(0)语法分析的程序,并用C. ...
- 编译原理 实验四 LR(0)分析法(LR0分析表的自动生成)
写在前面 由于代码较长,csdn对文章总长度有字数限制,想只看完整代码的请移步另一篇博客. https://blog.csdn.net/qq_46640863/article/details/1257 ...
- 编译原理实验四:验证Yacc的使用
所有实验的源代码:点此下载 实验目的: 熟悉语法分析器生成工具Yacc的使用,并学会在cygwin下使用bison工具编译Yacc文法说明文件.学习如何使用lex和yacc合作进行语法分析. 实验内容 ...
- 编译原理逆波兰式实验java_【实验三】—— 逆波兰式生成实验报告
实验三 逆波兰式生成实验报告 一.实验名称:逆波兰式生成 二.仪器.设备:计算机 三.参考资料:<编译原理教程>习题解析与上机指导(西安电子科技大 胡元义等) 四.实验目的:将非后缀式用来 ...
- 逆波兰式 java_Java 实现《编译原理》中间代码生成 -逆波兰式生成与计算 - 程序解析...
Java 实现<编译原理>中间代码生成 -逆波兰式生成与计算 - 程序解析 编译原理学习笔记 (一)逆波兰式是什么? 逆波兰式(Reverse Polish notation,RPN,或逆 ...
- 逆波兰式的产生及计算(C++/Java)---编译原理
逆波兰式的产生及计算(C++版及Java版本)-编译原理,数据结构 在现实生活中大家对于计算使用都是中缀表达式(nifix expression),如但是在计算机中表达式常常是以后缀表达式(postf ...
最新文章
- 在CentOS上搭建PHP服务器环境
- audio标签控制音量_HTML5中audio与video标签的使用
- 汇编入门学习笔记 (十二)—— int指令、port
- 【翻译】.NET 5 Preview 1 发布
- Ecipse快捷键的使用
- Python序列基本操作(二)
- OpenCV自动内存管理
- C#调用非托管Dll
- 聊聊 print 的前世今生
- @WebListener 注解方式实现监听(eclipse和idea)
- magento 客户表相关_第9章 相关性分析
- 音乐网站Spotify将融资4亿美元,估值84亿美元
- [译] 移动应用设计新趋势
- PyHook3 的下载与安装
- 固件编辑器android,定制 Android 固件
- JAVA实现战舰游戏
- 视频转GIF小工具,原生javascript + gif.js + canvas,自定义制作表情包
- 在windows上编译apr库apr-util库
- Maven 中跳过单元测试方法
- js里双重否定的作用
热门文章
- jzoj. 3518. 【NOIP2013模拟11.6A组】进化序列(evolve)
- JZOJ 3518. 【NOIP2013模拟11.6A组】进化序列(evolve)
- 十五、Events类
- div点击穿透,CSS属性pointer-events :none;实现护眼模式, 夜间模式遮罩
- 新版Vue项目配置项目名称-publicPath-前端_v1.0.2
- Hadoop单节点设置
- 盘点2018年云计算热点:云原生、全栈云,云大脑,谁能独占鳌头?
- vscode配置prettier格式化工具
- c语言浮点变量是什么意思,C语言中说的浮点型是什么意思呢 C语言的浮点数是什么...
- 一元享移动怎么样_中国移动终于认怂?29元享100G流量还不限速,网友:后悔携号转网了...