下面是一个示例程序,基于文本中的一个示例,可以计算非常简单的算术表达式。
公共类Simple计算器{公共静态void main(String[] args){扫描器输入=新的扫描器(System.in);//操作结果int result = 0;//初始输入System.out。print(“输入要计算的表达式:”);字符串userInput = input.nextLine();//组成输入字符串的token [] tokens = userInput。分割(" ");//确定操作符开关(tokens[1].charAt(0)) {case '+': result = Integer.parseInt(tokens[0]) + Integer.parseInt(tokens[2]);打破;case '-': result = Integer.parseInt(tokens[0]) - Integer.parseInt(token [2]);打破;case '*': result = Integer.parseInt(tokens[0]) * Integer.parseInt(tokens[2]);打破;case '/': result = Integer.parseInt(tokens[0]) / Integer.parseInt(token [2]);} //显示结果System.out。println(令牌[0 ] + ' ' + 令牌[1 ] + ' ' + 令牌[2 ] + " = " + 结果);}}
该程序使用非常简单的算术表达式,例如
34 * 12
该程序使用Scanner类的nextLine方法读取用户键入的整行输入,从而立即获取整个表达式。接下来,程序使用String类的split方法将输入拆分为String对象数组。结果数组中中间的String包含操作符,因此可以使用简单的switch语句来处理操作符。
接下来,我们将把这个问题扩展到计算更复杂的算术表达式。首先,我们将看到如何处理如下表达式
20 - 3 * 6 + 2
最终,我们将扩展基本算法来处理括号,从而可以计算如下表达式
4 * (6 - 2 * (4 - 2))
现在我们准备看一下计算不带括号的算术语句的基本算法。该算法使用两个堆栈,一个值堆栈和一个操作符堆栈。
基本算法以简单的从左到右的顺序处理来自输入的令牌。我们遇到的任何数字令牌都会被压入值堆栈。遇到操作符令牌时,将其压入操作符堆栈。
在算法的不同阶段,我们将处理一个运算符。我们通过从操作符堆栈中取出操作符,并从值堆栈中取出两个数字来实现这一点。然后对这两个数赢博体育运算符来产生一个结果,然后将该数压回值堆栈的顶部。处理完成后,丢弃操作符令牌。
求值算法中最重要的逻辑告诉我们何时处理位于运算符堆栈上的运算符。这种逻辑部分是由所涉及的操作符的优先级驱动的:操作符+和-的优先级为1,而操作符*和/的优先级为2。以下是评估算法的关键元素。
下面是这个算法的实例。我们要计算的表达式是
2 + 3 * 4 - 6
在算法开始时,两个堆栈都是空的,令牌在输入中排列。
在处理了前三个令牌之后,我们得到了这个配置。
输入中的下一个操作符比操作符堆栈顶部的操作符具有更高的优先级,因此我们将下一个操作符压入。下一个数字令牌也被压入值堆栈。
输入序列中的下一个操作符的优先级低于操作符堆栈顶部的操作符。这导致我们处理并移除位于操作符堆栈顶部的操作符。
同样,输入操作符的优先级等于操作符堆栈顶部的操作符的优先级,因此我们处理并从操作符堆栈中删除该操作符。
此时,操作符堆栈为空,因此输入前面的操作符令牌被压入操作符堆栈。输入末尾的数字令牌被压入值堆栈。
一旦输入清空,我们处理操作符堆栈上剩余的赢博体育操作符。一旦赢博体育这些运算符都被处理完,值堆栈上唯一剩下的数字就是计算的结果。
上节课我展示了一个简单的堆栈类。我们将需要类似的类来实现今天的算法。
我们将遇到的一个复杂问题是,我们将需要两种不同类型的堆栈。对于值堆栈,我们需要一个可以存储数字的堆栈。对于操作符堆栈,我们需要一个可以存储字符的堆栈。为了使一个堆栈类适用于两种类型的数据,我们将堆栈类改为泛型类。在Java中,泛型类是这样一种类,我们用占位符符号替换至少一个成员变量的数据类型。这将允许类的用户通过指定用作占位符类型的不同类型来创建基本类的不同版本。
下面是堆栈类的工作方式。我们要做的第一件事是修改StackItem类来存储一个泛型值类型:
公共类StackItem<E>{公共E值;public StackItem<E> next;public StackItem(E x) {value = x;Next = null;}}
然后我们还将堆栈类转换为泛型。
public class Stack<E> {private StackItem<E> top;public Stack() {top = null;} public void push(E x) {StackItem<E> newItem = new StackItem<E>(x);newItem。Next = top;top = newItem;}公共布尔空(){返回顶部== null;}公共E peek(){返回top.value;}公共void pop() {top = top.next;}}
通过这种转换,我们可以使用相同的代码来创建双精度数栈和字符栈。
Stack<Double> values = new Stack<Double>();栈<字符>();
请注意,与ArrayList类一样,我们的泛型Stack类只能处理对象类型。因此,如果您想在Stack上放置双精度类型,则必须使用对象等效类型Double。同样,在原始char类型的位置,我们必须使用等效的对象类型Character。
一旦我们设置好了这两个栈,我们就可以使用方便的Java自动装箱/自动装箱特性,将这些类与更简单的基本类型一起使用。
values.push(2.5); operators.push('*');
为了将来参考,Java类库已经包含了一个可以使用的通用堆栈类,即Java .util. stack类。
上面概述的算法可以很容易地扩展到正确处理括号。所需要的只是一些额外的规则。
有了上面描述的Stack类,我们就有了实现表达式计算器所需的赢博体育部分。最后一步是构造一个可以完全实现算法的类。
public class Full计算器 {private Stack<Character> operatorStack;private Stack<Double>;私有布尔错误;public Full计算器() {operatorStack = new Stack<Character>();valueStack = new Stack<Double>();错误= false;}私人布尔isOperator (char ch){返回ch == '+' || ch == '-' || ch == '*' || ch = = ' / ';} private int getPrecedence(char ch) {if (ch == '+' || ch == '-'){返回1;} if (ch == '*' || ch == '/'){返回2;}返回0;} private void processOperator(char t) {double a, b;if (valueStack.empty()) {System.out。println(“表达式错误。”);Error = true;返回;} else {b = valueStack.peek();valueStack.pop ();} if (valueStack.empty()) {System.out。println(“表达式错误。”);Error = true;返回;} else {a = valueStack.peek();valueStack.pop ();} double r = 0;If (t == '+') {r = a + b;} else if (t == '-') {r = a - b;} else if (t == '*') {r = a * b;} else if(t == '/') {r = a / b;} else {System.out。println(“操作员错误。”);Error = true;} valueStack.push (r);} public void processInput(String input){//组成输入字符串的token [] token = input。分割(" ");//主循环-处理(int n = 0;N < tokens.length;n++){字符串nextToken = token [n];charch = nextToken.charAt(0);if (ch >= '0' && ch <= '9') {double value = double . parsedouble (nextToken);valueStack.push(价值);} else if (isOperator(ch)) {if (operatorStack.empty() || getPrecedence(ch) > getPrecedence(operatorStack.peek())) {operatorStack.push(ch);} else {while (!operatorStack.empty() && getPrecedence(ch) <= getPrecedence(operatorStack.peek())) {char toProcess = operatorStack.peek();operatorStack.pop ();processOperator (toProcess);} operatorStack.push (ch);}} else if (ch == '(') {operatorStack.push(ch);} else if (ch == ')') {while (!operatorStack.empty() && isOperator(operatorStack.peek())) {char toProcess = operatorStack.peek();operatorStack.pop ();processOperator (toProcess);} if (!operatorStack.empty() && operatorStack.peek() == '(') {operatorStack.pop();} else {System.out。println(“错误:圆括号不平衡”);Error = true;}}} //清空输入末尾的操作符堆栈while (!operatorStack.empty() && isOperator(operatorStack.peek())) {char toProcess = operatorStack.peek();operatorStack.pop ();processOperator (toProcess);} //如果没有看到错误,打印结果。if (error == false) {double result = valueStack.peek();valueStack.pop ();如果(!operatorStack.empty() || !valueStack.empty()) {System.out。println(“表达式错误。”);} else {System.out。println(“结果是” + result);}}}公共静态void main(String[] args){扫描器输入=新扫描器(System.in);//初始输入System.out。print(“输入要计算的表达式:”);字符串userInput = input.nextLine();Full计算器 calc = new Full计算器();calc.processInput (userInput);}}
这里的大部分工作都是由processInput方法完成的,该方法接受输入字符串,将其分解为单个标记,然后处理这些标记以计算表达式。