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

    Easy UI 准备工作(搭建) 1.在WebRoot 的目录下创建js 文件夹,在文件夹中倒入一下两个包 Jquery.easyui.min.js jquery.min.js 2.在WebRoot ...

  2. Android学习---SQLite数据库的增删改查和事务(transaction)调用

    上一篇文章中介绍了手工拼写sql语句进行数据库的CRUD操作,本文将介绍调用sqlite内置的方法实现CRUD操作,其实质也是通过拼写sql语句. 首先,创建一个新的android项目: 其次,查看代 ...

  3. C#实现简单的委托异步调用

    delegate void textAsy(); static void Main(string[] args) { textAsy t = texts; AsyncCallback callBack ...

  4. Oracle Cursor

    1.概念 游标:从字面来理解就是游动的光标.用数据库语言来描述,游标是映射在结果集中一行数据上的位置实体,有了游标,用户就可以访问结果集中的任意一行数据了.将游标放置到某行后,即可对该行数据进行操作, ...

  5. 百度自动发贴,登录很顺利的模拟实现,但发贴攻关失败,能力有限,追JS过程中颇为痛苦

    攻关失败,且短期内看不到希望,看不到方向,且越来越焦急,目前已知的是,用根据用户的鼠标事件以一定的规则结合其他数据,服务器以这些数据验证是否为真正的手动发贴. 不过闲暇时实现了百度贴吧的自动签到. 较 ...

  6. export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH

    如题,临时修改程序运行时动态库的搜索路径,平时经常会用到,记录之!

  7. 四元数(Quaternions)简介

    经常在代码中看到Quaternions,也知道它是用来表达三维空间的旋转的,但一直没有更深的理解.这两天终于花点时间看了看维基百科的介绍,算是多了解了点.做个记录吧! 本质上而言,四元数是一个数学概念 ...

  8. [linux] 默认权限修改(umask)

    1 文件默认权限 对于目录,默认权限=777-umask 对于文件,默认权限=666-umask(文件默认无执行权限) 默认权限修改: vim /etc/bashrc 71行是普通用户的更改,73是超 ...

  9. 一个login

    login 1.获取提交表单,保存到变量中.2.判断用户密码是否正确,利用Model类.3.验证用户是否激活.3.判断用户是否记住登录状态,是的话,将其用cookie和session分别保存.没有的话 ...

  10. 使用nginx为ArcGIS Server做反向代理

    1.下载nginx软件:官网地址http://nginx.org/ 2.修改conf文件夹下nginx.conf配置信息, 配置文件中以下内容: server { listen       80; s ...