Java实现四则运算,使用堆栈,检查语法
突然发闲想试一试自己实现算术的四则运算,支持加减乘除和括号、正负号;支持语法检查;思路很常规,利用两个堆栈,一个压操作符,一个压操作数,念头冒出来之后,立马动手;然后本以为很容易的一个实现,却存在各种各样的坑,正常逻辑花了1个小时,填坑缺填了5个小时,不多说,上代码;
能够检测的语法错误:缺少操作数、缺少操作符、缺失括号、不合法的数值;
支持运算程度:全部使用浮点数float;支持任意位置的空格、制表符、回车;多重括号;
视为语法错误的约束:空括号、多重正负号(非加减号)、除数为0;
编码能力有限,望各路大神海涵;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack; public class FunctionStack { private Map<String, Integer> optLevel;
private boolean nextIsOpt = false;
private boolean debug = false;
private int debug_len = 3; public FunctionStack setDebugLen(int len) {
this.debug_len = len;
return this;
} public FunctionStack setDebug(boolean debug) {
this.debug = debug;
return this;
} private void println(Object obj) {
if (debug) {
System.out.println(obj);
}
} public FunctionStack() {
optLevel = new HashMap<String, Integer>();
optLevel.put("(", 1);
optLevel.put("+", 2);
optLevel.put("-", 2);
optLevel.put("*", 3);
optLevel.put("/", 3);
optLevel.put(")", 4);
} public static void main(String[] args) { FunctionStack fs = new FunctionStack();
fs.setDebug(true).setDebugLen(5);
String fun = " 1 + 2 * 3 / 4 ";
try {
float res = fs.execute(fun);
System.out.println("结果为:" + res);
} catch (FunctionStackException e) {
System.out.println(e.getMessage());
}
System.out.println("结束.");
} /**
*
* @param fun
* @return
* @throws FunctionStackException
*/
public float execute(String fun) throws FunctionStackException {
this.clear();
// TODO Auto-generated method stub
if (fun == null || fun.trim().length() == 0) {
throw new FunctionStackException("表达式不能为空;");
}
// 创建操作符堆栈和操作数堆栈
Stack<String> opt = new Stack<String>();
Stack<Float> num = new Stack<Float>();
// 扫描整个表达式
// point记录上一个扫描点
int point = 0;
for (int i = 0; i < fun.length(); i++) {
String scanOpt = fun.charAt(i) + "";
if (scanOpt.equals("(")) {
// 发现左括号,压入栈
// 检查是否为空的括号
int right = fun.indexOf(')', i);
if (right < 0) {
// 如果没有找到右括号
throw new FunctionStackException(fun
+ "\n语法错误:"
+ "缺少右括号与之对应:"
+ fun.substring(Math.max(point - debug_len, 0),
Math.min(i + debug_len, fun.length())));
} else {
// 找到右括号,检查是否为空括号
if (fun.substring(i + 1, right).trim().length() == 0) {
throw new FunctionStackException(fun
+ "\n语法错误:"
+ "括号中内容不可为空:"
+ fun.substring(Math.max(point - debug_len, 0),
Math.min(i + debug_len, fun.length())));
}
}
// 检查语法
String num_before_opt = fun.substring(point, i).trim();
if (num_before_opt.length() != 0) {
throw new FunctionStackException(fun
+ "\n语法错误:"
+ "括号前缺少操作符:"
+ fun.substring(Math.max(point - debug_len, 0),
Math.min(i + debug_len, fun.length())));
}
// 将左括号压入栈
println("↓压栈:" + fun.substring(i, i + 1));
opt.push(fun.substring(i, i + 1));
// 记录扫描点
point = i + 1;
} else if (scanOpt.equals(")")) {
// 发现右括号,取出栈,直到取出左括号
println("---计算括号开始:");
// 将括号前的数值取出
pushFloatStack(fun, num, point, i);
// 记录下一个是操作符
nextIsOpt = true;
// 检查前一个操作符是否为空
if (opt.empty()) {
throw new FunctionStackException(fun
+ "\n语法错误:"
+ "缺少左括号与之对应:"
+ fun.substring(Math.max(point - debug_len, 0),
Math.min(i + debug_len, fun.length())));
}
// 取出前一个操作符
String optpop = opt.pop();
println("↑↑出栈:" + optpop);
// 取出栈,直到取出左括号
while (!optpop.equals("(")) {
// 若取出的操作符不是左括号,执行运算;
calculator(optpop, num);
if (opt.empty()) {
throw new FunctionStackException(fun
+ "\n语法错误:"
+ "缺少左括号与之对应:"
+ fun.substring(Math.max(point - debug_len, 0),
Math.min(i + debug_len, fun.length())));
}
optpop = opt.pop();
println("↑↑出栈:" + optpop);
} // 记录扫描点
point = i + 1;
println("---计算括号结束:");
} else if (optLevel.get(scanOpt) != null) {
if (scanOpt.equals("-") || scanOpt.equals("+")) {
// 如果是减号,可能是一个负号
if (fun.substring(point, i).trim().replaceAll("-", "")
.replaceAll("\\+", "").trim().length() == 0) {
// 如果减号前没有操作数,视此减号为负号,point不移动,-也不压入堆栈
continue;
}
}
// 发现非括号的操作符,查看栈顶操作符优先级,选择计算or压栈
// 获取操作数,并检查语法
pushFloatStack(fun, num, point, i);
// 比较优先级,将栈顶优先级高的先计算
if (!opt.empty()) {
// 获取栈顶操作符,不取出
String optpop = opt.peek();
// 取栈计算,直到栈顶操作符优先级小于scanOpt
while (optLevel.get(optpop) >= optLevel.get(scanOpt)) {
calculator(optpop, num);
optpop = opt.pop();
println("↑↑出栈:" + optpop);
if (opt.empty()) {
// 如果操作符取空了则退出
break;
}
optpop = opt.peek();
}
}
// 压入操作符
println("↓压栈:" + scanOpt);
opt.push(scanOpt);
// 记录扫描点
point = i + 1;
} else if (scanOpt.equals("=")) {
// 发现=号,取栈计算总结果,并提前结束循环
// 获取操作数,并检查语法
pushFloatStack(fun, num, point, i);
// 取栈计算直到结束
while (!opt.empty()) {
String optpop = opt.pop();
println("↑↑出栈:" + optpop);
calculator(optpop, num);
}
return getResult(num);
} } // 表达式结束
// 获取操作数,并检查语法
pushFloatStack(fun, num, point, fun.length());
// 取栈计算直到结束
while (!opt.empty()) {
String optpop = opt.pop();
println("↑↑出栈:" + optpop);
calculator(optpop, num);
}
return getResult(num);
} private void clear() {
// TODO Auto-generated method stub
nextIsOpt = false;
} private float getResult(Stack<Float> num) {
// TODO Auto-generated method stub
Float res = num.pop();
if (num.empty()) {
return res;
} else {
throw new FunctionStackException("计算错误,堆栈中还有数据;");
}
} /**
* 将操作符i前的操作数解析,并压入堆栈
*
* @param fun
* @param num
* @param point
* 操作数起点
* @param i
* 操作符位置,即操作数终点
*/
private void pushFloatStack(String fun, Stack<Float> num, int point, int i) {
String num_before_opt = fun.substring(point, i).trim();
if (num_before_opt.length() == 0) {
// 没有操作数
if (nextIsOpt) { // 应该没有操作数,(即此处本应只有操作符,没有操作数)
nextIsOpt = false;
return;
} else {
// 应该有操作数
throw new FunctionStackException(fun
+ "\n语法错误:"
+ "缺少操作数:"
+ fun.substring(Math.max(point - debug_len, 0),
Math.min(i + debug_len, fun.length())));
}
} else {
// 有操作数
if (nextIsOpt) {
// 应该没有操作数
throw new FunctionStackException(fun
+ "\n语法错误:"
+ "缺少操作符:"
+ fun.substring(Math.max(point - debug_len, 0),
Math.min(i + debug_len, fun.length())));
} else {
// 应该有操作数
try {
// 去除操作数中间的空格、回车、制表符
num_before_opt = num_before_opt.replaceAll(" ", "");
num_before_opt = num_before_opt.replaceAll("\t", "");
num_before_opt = num_before_opt.replaceAll("\n", "");
Float scannum = Float.parseFloat(num_before_opt);
println("↓压栈:" + scannum);
num.push(scannum);
} catch (NumberFormatException e) {
throw new FunctionStackException(fun + "\n语法错误:"
+ "无法识别的数值:" + num_before_opt);
}
}
}
} /**
*
* @param optpop
* 运算符
*/
private void calculator(String optpop, Stack<Float> num) {
// TODO Auto-generated method stub
Float pop2 = num.pop();
println("↑↑出栈:" + pop2);
Float pop1 = num.pop();
println("↑↑出栈:" + pop1);
println("--------计算 " + pop1 + optpop + pop2);
if (optpop.equals("+")) {
println("↓压栈:" + (pop1 + pop2));
num.push(pop1 + pop2);
} else if (optpop.equals("-")) {
println("↓压栈:" + (pop1 - pop2));
num.push(pop1 - pop2);
} else if (optpop.equals("*")) {
println("↓压栈:" + (pop1 * pop2));
num.push(pop1 * pop2);
} else if (optpop.equals("/")) {
if (pop2 == 0) {
throw new FunctionStackException("语法错误:" + "除数不可以为零:" + pop2);
}
println("↓压栈:" + (pop1 / pop2));
num.push(pop1 / pop2);
} else if (optpop.equals("(")) {
throw new FunctionStackException("语法错误:" + "缺少右括号与之对应:" + optpop);
} else {
throw new FunctionStackException("语法错误:" + "错误的操作符:" + optpop);
} } }
主代码
public class FunctionStackException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public FunctionStackException(String message) {
super(message);
}
}
自定义异常
Java实现四则运算,使用堆栈,检查语法的更多相关文章
- [转] Java程序员学C#基本语法两个小时搞定(对比学习)
Java程序员学C#基本语法两个小时搞定(对比学习) 对于学习一门新的语言,关键是学习新语言和以前掌握的语言的区别,但是也不要让以前语言的东西,固定了自己的思维模式,多看一下新的语言的编程思想. ...
- 使用TCMalloc的堆栈检查
在前一篇译文<TCMalloc:线程缓冲的Malloc>详细讲解了TCMalloc的工作原理和特点,今天翻译<heap-checking using tcmalloc>,了解T ...
- Java和C#在面向对象上语法的区别
做了几年了开发一直没有总结什么,回到了家乡的小城做了一名培训班的教员,教授软件开发的知识.细小的知识从头细细嚼来,别有一番滋味.或是以前遗漏的太多,或是确实没有系统的学习过,教学生的过程中自己也对教材 ...
- Java正則表達式语法
Java正則表達式语法 字符 说明 \ 将下一字符标记为特殊字符.文本.反向引用或八进制转义符.比如,"n"匹配字符"n"."\n"匹配换行 ...
- java中堆和堆栈的区别
java中堆和堆栈的区别(一) 1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆. 2. 栈的优势是,存取 ...
- 使用 java 实现一个简单的 markdown 语法解析器
1. 什么是 markdown Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用.看到这里请不要被「标记」.「语言」所迷惑,Markdown 的 ...
- java中存储机制堆栈。
一.java的六种存储地址及解释 1) 寄存器(register):这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配.你不 ...
- Java多线程——查看线程堆栈信息
Java多线程——查看线程堆栈信息 摘要:本文主要介绍了查看线程堆栈信息的方法. 使用Thread类的getAllStackTraces()方法 方法定义 可以看到getAllStackTraces( ...
- Java入门 - 语言基础 - 03.基础语法
原文地址:http://www.work100.net/training/java-basic-syntax.html 更多教程:光束云 - 免费课程 基础语法 序号 文内章节 视频 1 第一个Jav ...
随机推荐
- xml/map转换器,递归设计思路
xml/map转换器 图片:http://pan.baidu.com/s/1nuKJD13 应用场景,为什么要把xml转map?我直接用jdom,dom4j操作不行吗? 如果你了解模板引擎(像velo ...
- iOS错误总结(三)
1.如果tableView设置为分组的样式(默认是有cell之间的分割线,可以设置颜色),默认有组以及组尾的高度 需要手动在组头组尾的代理方法中进行组高的设置(如果想设置为0,最好写0.01) 2.组 ...
- 安装好grunt,cmd 提示"grunt不是内部或外部命令" 怎么办?
Grunt和所有grunt插件都是基于nodejs来运行的,因此,必须安装node.js. (一) 去官网http://nodejs.org/ 下载安装包 node-v6.9.2.msi,直接点击安装 ...
- RHEL6.7 x64双节点安装Oracle 11g r2 RAC
基础环境 使用两台HP DL580服务器作为RAC节点,存储使用IBM V7000.具体环境如下: 设备 用途 IP地址 磁盘空间 HP DL580 RAC节点01 RAC01-pub:116.1.1 ...
- JavaScript基础认知
此文只适用于初学者,大神们就不要看了,嘿嘿~ 一.定义变量 关键字 var,由此关键字定义变量,例如:var a =21:就把21这个数定义给了变量a 二.基本数据类型 1.Number类型 表示数字 ...
- Eclipse中调试Android技巧
Android eclipse中程序调试 一:断点调试 用eclipse开发android程序的时,跟VS一样是可以断点单步调试的. 步骤如下. 1 设置断点:在编码窗体的左边框上用鼠标双击,或者右键 ...
- 浅谈rem、em、px
1.px:像素(Pixel) px是相对长度单位,他是相对于显示器屏幕分辨率而言的 优点:比较稳定.精确 缺点:在浏览器 中放大或者缩小浏览页面,会出现页面混乱的情况. 如下例子: .buttonPX ...
- JSP内置对象有哪些呢?
内置对象 request request 对象是 javax.servlet.httpServletRequest类型的对象. 该对象代表了客户端的请求信息,主要用于接受通过HTTP协议传送到服务器的 ...
- 用 eval() 转换 Json 对象时,为什么要加括号?
var dataObj=eval("("+data+")");//转换为json对象 为什么 eval 这里,data 要用 "(".& ...
- sqlserver2012 表分区
无论是新建数据库,还是现有的问题,都可以执行表分区的操作. 1.在数据库中点鼠标右键点击属性,在选择页,选中文件栏,在数据库文件列表中,可以看到现有的数据库文件逻辑名称.文件类型.初始大小.保存位置等 ...