突然发闲想试一试自己实现算术的四则运算,支持加减乘除和括号、正负号;支持语法检查;思路很常规,利用两个堆栈,一个压操作符,一个压操作数,念头冒出来之后,立马动手;然后本以为很容易的一个实现,却存在各种各样的坑,正常逻辑花了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实现四则运算,使用堆栈,检查语法的更多相关文章

  1. [转] Java程序员学C#基本语法两个小时搞定(对比学习)

    Java程序员学C#基本语法两个小时搞定(对比学习)   对于学习一门新的语言,关键是学习新语言和以前掌握的语言的区别,但是也不要让以前语言的东西,固定了自己的思维模式,多看一下新的语言的编程思想. ...

  2. 使用TCMalloc的堆栈检查

    在前一篇译文<TCMalloc:线程缓冲的Malloc>详细讲解了TCMalloc的工作原理和特点,今天翻译<heap-checking using tcmalloc>,了解T ...

  3. Java和C#在面向对象上语法的区别

    做了几年了开发一直没有总结什么,回到了家乡的小城做了一名培训班的教员,教授软件开发的知识.细小的知识从头细细嚼来,别有一番滋味.或是以前遗漏的太多,或是确实没有系统的学习过,教学生的过程中自己也对教材 ...

  4. Java正則表達式语法

    Java正則表達式语法 字符 说明 \ 将下一字符标记为特殊字符.文本.反向引用或八进制转义符.比如,"n"匹配字符"n"."\n"匹配换行 ...

  5. java中堆和堆栈的区别

    java中堆和堆栈的区别(一) 1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆. 2. 栈的优势是,存取 ...

  6. 使用 java 实现一个简单的 markdown 语法解析器

    1. 什么是 markdown Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用.看到这里请不要被「标记」.「语言」所迷惑,Markdown 的 ...

  7. java中存储机制堆栈。

    一.java的六种存储地址及解释 1) 寄存器(register):这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配.你不 ...

  8. Java多线程——查看线程堆栈信息

    Java多线程——查看线程堆栈信息 摘要:本文主要介绍了查看线程堆栈信息的方法. 使用Thread类的getAllStackTraces()方法 方法定义 可以看到getAllStackTraces( ...

  9. Java入门 - 语言基础 - 03.基础语法

    原文地址:http://www.work100.net/training/java-basic-syntax.html 更多教程:光束云 - 免费课程 基础语法 序号 文内章节 视频 1 第一个Jav ...

随机推荐

  1. Content is not allowed in prolog ---UTF-8 无bom

  2. Android Studio开发基础之自定义View组件

    一般情况下,不直接使用View和ViewGroup类,而是使用使用其子类.例如要显示一张图片可以用View类的子类ImageView,开发自定义View组件可分为两个主要步骤: 一.创建一个继承自an ...

  3. java 零碎知识点

    1. 字符串有整型的相互转换 1 2 String a = String.valueOf(2);   //integer to numeric string  int i = Integer.pars ...

  4. EXISTS语句

    通常在我写EXISTS语句时,我会写成IF EXISTS(SELECT TOP(1) 1 FROM XXX),也没细细考究过为什么要这么写,只是隐约认为这样写没有啥问题,那今天就深究下吧! 首先准备测 ...

  5. Android学习---ListView的点击事件,simpleAdapter和arrayadapter,SimpleCursoAdapter的原理和使用

    如题,本文将介绍 listview的点击事件,simpleAdapter和arrayadapter的原理和使用. 1.ListView的注册点击事件 //注册点击事件 personListView.s ...

  6. Windows XP SP3 VC6环境下成功编译openssl-0.9.8zh

    1.下载openssl-0.9.8zh解压到f:\openssl-0.9.8zh 下载nasm-2.12.03rc1解压到D:\develop\nasm-2.12.03rc1并把添加到系统环境变量PA ...

  7. 使用selenium控制滚动条(非整屏body)

    方法原理:     (1)使用jQuery CSS 操作 - scrollTop() 方法,设置 <div> 元素中滚动条的垂直偏移,语法:$(selector).scrollTop(of ...

  8. Nginx-->基础-->安装-->001:安装总结

    root@ubuntu:/data/src/nginx# ./configure --help --help print this message --prefix=PATH set installa ...

  9. 【Debian】非法关机后无法联网 connect: network is unreachable

    某一天,突然发现无法ssh登录虚拟机内的debian系统,一直认为是ssh的问题,然后无意间ping了ping百度,发现原来是debian系统没有联网....囧 首先,是虚拟机的网络设置检查. 打开V ...

  10. sublime插件 TortioseSVN

    TortioseSVN 可以安装在sublime中,实现svn文件的增加.删除.更新.提交等功能(TortioseSVN用在window系统中,linux安装svn) 安装: 首先在sublime中搜 ...