基于逆波兰式的JAVA计算器
请看下方↓↓ 🤓
笔者用了四五天的时间完成了这个小Demo,可能有什么不完善或者解决方案Low的问题,欢迎大家在评论区反映,共同学习。
这个基于逆波兰式的计算器,是笔者最近 在看GUI 时的一个小想法,初衷只是想尝试下事件驱动编程,做个简单的+、-、*、/的简易版本,随着学习的深入,慢慢的想将这个Demo做的相对完善,那么就涉及到了()的 运算优先级结合问题和对任意长度的输入做出计算,第一个版本的时候通过设置运算数数组可以实现定长的数据运算,后来考虑到 这两点,笔者进行了深入的Google😂,了解到 波兰式和逆波兰式,本文中我们使用 逆波兰式。
那么什么是波兰式和逆波兰式呢,我们人类从小所接触的例如3+2,5*4这样的,运算符在操作数中间的称为中缀表达式,波兰式就是运算符在操作数前面的运算表达式,相反,逆波兰式就是运算符在操作数后面的运算表达式。比如上面的3+2使用逆波兰式表示为32+,5*4表示为54*;当然目前 还不知道为什么要用逆波兰式来解决运算表达式的解析,随着本文的深入,相信你可以 有所收获。
逆波兰式的优点:
1.当运算符在操作数后面时,使用一个运算栈,从左至右扫面表达式,如果是数字则入栈,如果是运算符则依次出栈两个元素进行运算,具体的运算取决于当前判断的运算符
2.使用逆波兰式可以忽略 () 。
本文的重点在于,中缀表达式->后缀表达式(逆波兰式),完成这个过程需要:
1.创建一个用于存放运算符的栈
2.存放原始表达式的 字符串Arraylist
3.存放逆波兰式的 字符串Arraylist
4.笔者上一篇文章 所提到的 StringTokenizer的字符串分割
中缀转后缀过程中,若是运算符,需要同栈顶运算符做优先级判断:
1.若栈空 直接入栈
2.若为左括号 直接入栈
3.若是 右括号,以此出栈,并输出到存储逆波兰式的arraylist中,直至当前栈顶为 ( , 将 ( 直接出栈,不存入arraylist。
4.若当前栈顶元素是 ( ,则直接入栈
5.否则,判断优先级 若当前运算符> 栈顶元素 直接入栈
6.若低于栈顶元素优先级 将当前栈顶元素输出至 存储逆波兰表达式的arraylist中,并将当前操作符 与 下一个栈顶继续进行判断。
得到了 逆波兰式后,我们需要计算 逆波兰表达式的Result,这个过程比较简单,使用一个运算栈,从左到右扫描逆波兰式,遇到数字Push,遇到运算符,依次POP 两个操作数,按照运算符 进行运算,将运算结果重新入栈,照此往复,直至 运算栈中只剩下一个元素,就是最终的结果。
好了,话不多说,贴代码。
1 package 逆波兰式; 2 3 import java.util.ArrayList; 4 import java.util.Scanner; 5 import java.util.Stack; 6 import java.util.StringTokenizer; 7 8 public class NBL { 9 10 // 操作符栈 11 private Stack<String> czf_stack = new Stack<>(); // 存放 运算符的栈 12 private ArrayList<String> ysbds_list = new ArrayList<>(); //存放 原始表达式的 arraylist 13 private ArrayList<String> nblbds_list = new ArrayList<>(); // 存放转换后的 逆波兰式 14 private static final int One = 1; // 15 private static final int Two = 3; // 16 private static final int Three = 5; //规定优先级 Three 最高 17 18 // 定义一个运算栈 19 private static Stack<String> ys_stack = new Stack<>(); 20 21 // 初始化 使用StringTokenizer分割 字符串 22 public NBL(String bdString) { 23 // TODO Auto-generated constructor stub 24 StringTokenizer stringTokenizer = new StringTokenizer(bdString, "+-*/()",true); 25 while(stringTokenizer.hasMoreTokens()){ 26 ysbds_list.add(stringTokenizer.nextToken()); 27 //System.out.println(stringTokenizer.nextToken()); 28 } 29 } 30 31 32 // 判断 是否是数字 33 public boolean isNum(String str){ 34 if(str.matches("[0-9]+")){ //这里使用正则表达式 验证是否是数字 35 //System.out.println("Y"); 36 return true; 37 }else{ 38 //System.out.println("N"); 39 return false; 40 } 41 } 42 43 // 判断 是否是操作符 44 public boolean isCzf(String str){ 45 if(str.matches("[\\+\\-\\*\\/\\(\\)]")){ 46 //System.out.println("Y"); 47 return true; 48 }else{ 49 //System.out.println("N"); 50 return false; 51 } 52 } 53 54 // 获取 优先级 55 public int getYxj(String str){ 56 57 switch(str){ 58 case "(":return Three; 59 case "*": 60 case "/":return Two; 61 case "+": 62 case "-":return One; 63 case ")":return 0; 64 65 default : return -1; 66 67 } 68 69 } 70 71 // 判断优先级 72 public boolean isYxj(String str1,String str2){ 73 return getYxj(str1) > getYxj(str2); 74 } 75 76 // ********* 当 当前操作元素为 操作符时********** 这里是 核心代码, 用于操作符栈的判断 77 public void stack_czf(String czf){ 78 79 //判断当前栈内是否为空 80 if(czf_stack.isEmpty()){ 81 czf_stack.push(czf); 82 return; 83 } 84 85 //判断是否为 ( 86 if("(".equals(czf)){ 87 czf_stack.push(czf); 88 return; 89 } 90 91 //判断是否为 ) 92 if(")".equals(czf)){ 93 String string = ""; 94 while(!"(".equals(string = czf_stack.pop())){ 95 nblbds_list.add(string); 96 } 97 return; 98 } 99 100 //如果 当前栈顶元素是 ( 直接入栈 101 if("(".equals(czf_stack.peek())){ 102 czf_stack.push(czf); 103 return; 104 } 105 106 // 判断 与 栈顶元素的优先级 , > 为true 107 if(isYxj(czf, czf_stack.peek())){ 108 czf_stack.push(czf); 109 return; 110 } 111 112 if(!isYxj(czf, czf_stack.peek())){ 113 nblbds_list.add(czf_stack.pop()); 114 stack_czf(czf); //这里调用函数 本身,并将本次的操作数传参 115 } 116 117 } 118 119 // 中缀 —> 后缀 120 public void zz_hz(){ 121 122 // 遍历原始表达式list 123 for(String str:ysbds_list){ 124 125 //System.out.println("-> " + str); 126 127 if(isNum(str)){ 128 nblbds_list.add(str); 129 }else if(isCzf(str)){ 130 //TODO 131 stack_czf(str); 132 }else{ 133 System.out.println("非法"); 134 } 135 136 } 137 138 // 遍历完原始表达式后 将操作符栈内元素 全部添加至 逆波兰表达式list 139 140 while(!czf_stack.isEmpty()){ 141 //System.out.println("即将 " + czf_stack.peek()); 142 nblbds_list.add(czf_stack.pop()); 143 } 144 145 } 146 147 // 具体计算方法 148 public int jsff(String s1,String s2,String s3){ 149 int a = Integer.parseInt(s2); 150 int b = Integer.parseInt(s1); 151 switch(s3){ 152 case "+":return a+b; 153 case "-":return a-b; 154 case "*":return a*b; 155 case "/":return a/b; 156 default : return 0; 157 } 158 } 159 160 // 计算 逆波兰式 161 public int js_nbl(){ 162 for(String str:nblbds_list){ 163 if(isNum(str)){ 164 ys_stack.push(str); 165 }else{ 166 ys_stack.push(String.valueOf(jsff(ys_stack.pop(), ys_stack.pop(), str))); 167 } 168 } 169 return Integer.parseInt(ys_stack.pop()); //最后 栈中元素 即为结果 170 } 171 172 // public void nbls_bc(){ 173 // for(String string:nblbds_list){ 174 // nbls_cc += string; 175 // } 176 // } 177 178 179 public static void main(String[] args) { 180 181 Scanner keyboard = new Scanner(System.in); 182 System.out.println("请输入"); 183 String input = keyboard.nextLine(); 184 NBL nbl = new NBL(input); 185 String nbls_cc = new String(); 186 int result = 0; 187 nbl.zz_hz(); 188 //nbl.nbls_bc(); 189 System.out.println("对应的逆波兰式为 :" + nbls_cc); 190 System.out.println("结果是:"); 191 result = nbl.js_nbl(); 192 System.out.println(result); 193 } 194 195 196 }
上面的代码,是从 传入字符串,分割字符串存储在 原始字符串的arraylist中,然后从中缀表达式转换成逆波兰式保存在 逆波兰式arraylist中,最后使用运算栈计算表达式结果。
下面的内容,是给计算器写了个GUI,毕竟笔者最初的目的只是学习下GUI )逃 ,这个没什么要说的,只是在做监听器的时候,稍微注意一点。
GUI代码:
1 package 逆波兰式; 2 3 import java.awt.BorderLayout; 4 import java.awt.CardLayout; 5 import java.awt.Container; 6 import java.awt.FlowLayout; 7 import java.awt.GridLayout; 8 import java.awt.event.WindowEvent; 9 import java.awt.event.WindowListener; 10 11 import javax.swing.JButton; 12 import javax.swing.JFrame; 13 import javax.swing.JLabel; 14 import javax.swing.JPanel; 15 16 public class jyjsq { 17 18 public static void main(String[] args) { 19 20 JFrame jFrame = new JFrame("计算器V1.0 By 昕越科技"); 21 Container container = jFrame.getContentPane(); 22 JPanel jp1 = new JPanel(); 23 JPanel jp2 = new JPanel(); 24 jp1.setLayout(new GridLayout(4, 4)); 25 jp2.setLayout(new FlowLayout()); 26 JLabel jLabel = new JLabel("0"); 27 container.add(jLabel, BorderLayout.NORTH); 28 29 JButton[] jButtons = new JButton[16]; 30 String[] jbutton_name = {"AC","(",")","+","7","8","9","-","4","5","6","*","1","2","3","/"}; 31 32 //创建监听器实例 33 jsq_jtq jtq = new jsq_jtq(jLabel); 34 35 for(int i=0;i<jButtons.length;i++){ 36 jButtons[i] = new JButton(jbutton_name[i]); 37 jButtons[i].addActionListener(jtq); 38 jp1.add(jButtons[i]); 39 } 40 JButton juButton_0 = new JButton("0"); 41 juButton_0.addActionListener(jtq); 42 jp2.add(juButton_0); 43 JButton jButton_dh = new JButton("="); 44 jButton_dh.addActionListener(jtq); 45 jp2.add(jButton_dh); 46 container.add(jLabel,BorderLayout.NORTH); 47 container.add(jp1, BorderLayout.CENTER); 48 container.add(jp2,BorderLayout.SOUTH); 49 jFrame.setBounds(800, 170, 260, 360); 50 jFrame.setVisible(true); 51 jFrame.setResizable(false); 52 jFrame.addWindowListener(new WindowListener() { 53 54 @Override 55 public void windowOpened(WindowEvent e) { 56 // TODO Auto-generated method stub 57 58 } 59 60 @Override 61 public void windowIconified(WindowEvent e) { 62 // TODO Auto-generated method stub 63 64 } 65 66 @Override 67 public void windowDeiconified(WindowEvent e) { 68 // TODO Auto-generated method stub 69 70 } 71 72 @Override 73 public void windowDeactivated(WindowEvent e) { 74 // TODO Auto-generated method stub 75 76 } 77 78 @Override 79 public void windowClosing(WindowEvent e) { 80 // TODO Auto-generated method stub 81 System.out.println("计算器已关闭"); 82 System.exit(0); 83 } 84 85 @Override 86 public void windowClosed(WindowEvent e) { 87 // TODO Auto-generated method stub 88 89 } 90 91 @Override 92 public void windowActivated(WindowEvent e) { 93 // TODO Auto-generated method stub 94 95 } 96 }); 97 98 } 99 100 }
最后是,监听器的代码:
1 package 逆波兰式; 2 3 import java.awt.event.ActionEvent; 4 import java.awt.event.ActionListener; 5 import java.awt.event.WindowListener; 6 7 import javax.swing.JLabel; 8 9 public class jsq_jtq implements ActionListener{ 10 11 private String bds_cc = ""; 12 private static JLabel JLabel_fuben; 13 14 public jsq_jtq(JLabel jLabel) { 15 // TODO Auto-generated constructor stub 16 JLabel_fuben = jLabel; 17 } 18 19 @Override 20 public void actionPerformed(ActionEvent e) { 21 // TODO Auto-generated method stub 22 String command = e.getActionCommand(); 23 if(command == "AC"){ 24 bds_cc = ""; 25 JLabel_fuben.setText("0"); 26 }else if(command == "="){ 27 //TODO 28 NBL nbl = new NBL(bds_cc); 29 int result = 0; 30 nbl.zz_hz(); 31 result = nbl.js_nbl(); 32 nbl = null; 33 System.out.println(result); 34 bds_cc = String.valueOf(result); 35 JLabel_fuben.setText(String.valueOf(result)); 36 System.out.println("-> -> :" + bds_cc); 37 }else{ 38 bds_cc += command; 39 System.out.println("-> :" + bds_cc); 40 JLabel_fuben.setText(bds_cc); 41 } 42 43 } 44 45 46 }
贴两张运行图吧:
在这里笔者必须要做个检讨,笔者的变量命名和类名以及代码规范真的是太烂了,提醒大家 类名 最好采用驼峰写法,首字母也要大写,变量名最好不要用拼音🤣(笔者英文实在是太烂了)。
好了,关于JAVA的东西,笔者要稍微放一放,开始进军Android了(虽然笔者JAVA仍然很水 (:逃 ),不过 学长给我的建议是 以需求为驱动力来学习,后期 用到什么 过来恶补什么,这样会学以致用,最近确实有所体会。
所以,未来笔者会更新一些Android方面的笔记,还是那句话,希望大家可以多批评建议。 (:逃