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

    var str="1件*20桶*30包*123.45公斤"; var res=str.match(/\d+(\.\d+)?/g); alert(res);

  2. MongoDB学习:(一)MongoDB安装

    MongoDB学习:(一)MongoDB安装 MongoDB介绍:     直接百科了: MongoDB安装: 1:下载安装: MongoDB安装:https://www.mongodb.com/do ...

  3. JavaWeb 学习005-4个页面,5条sql语句(添加、查看、修改、删除)

    今天完成了的事: = 1.班级模块,list页面的添加班级信息操作,中文乱码问题 尚待解决 这俩天做得工作是: 首先搭建好main页面:是由top.left.right三个部分构成的,拼接到一起. 流 ...

  4. JavaScript基础认知

    此文只适用于初学者,大神们就不要看了,嘿嘿~ 一.定义变量 关键字 var,由此关键字定义变量,例如:var a =21:就把21这个数定义给了变量a 二.基本数据类型 1.Number类型 表示数字 ...

  5. git 常见命令

    查看.添加.提交.删除.找回,重置修改文件 git help <command> # 显示command的help git show # 显示某次提交的内容 git show $id gi ...

  6. Struts 2学习笔记——拦截器相关

    一.添加国际化支持 默认的struts-deault.xml文件中已经定义了国际化拦截器,内容如下 <!-定义国际化拦截器--> <interceptor name="i1 ...

  7. shell 环境初始化顺序

    登陆shell 的执行顺序 /etc/profile /etc/profile.d/file /etc/bashrc .bashrc .bash_profile 非登录shell 的执行顺序, 例如: ...

  8. [Notes] AWS Automation using script and AWS CLI

    (c) 2014 Amazon Web Services, Inc. and its afflialtes, All rights reserved. The content in this file ...

  9. android Activity类中的finish()、onDestory()和System.exit(0) 三者的区别

    android Activity类中的finish().onDestory()和System.exit(0) 三者的区别 Activity.finish() Call this when your a ...

  10. js处理匿名函数

    首先js 有DOM0 和DOM2级事件 DOM 0级事件处理一般是直接把一个函数分配给一个事件处理程序,既可以在元素中直接分配一个事件处理程序 一个元素可以绑定多个事件 DOM0: <div i ...