第一次作业
第一次作业
一、总体思路
利用Java,继承JFrame类实现图形用户页面框架及UI设计布局,继承了ActionListener接口实现按钮事件监听
用户按钮输入结束后获取等号前的字符串,采用双栈法将中缀表达式转化为后缀表达式后计算后缀表达式(单独写出牛顿迭代法计算开方运算)结果并输出在文本框中
二、UI设计
class Calculator1 extends JFrame implements ActionListener {
private JPanel jp_north = new JPanel();
//JPanel是java图形用户界面GUI工具包swing中的面板容器类,包含在javax.swing包中,可以进行嵌套,功能是对窗体中具有相同逻辑功能组件进行组合
private JTextField resultText = new JTextField("0"); //创建一个输入框
private JPanel jp_center = new JPanel();
private String input = "";
public Calculator1() throws HeadlessException{//异常HeadException在不支持键盘、显示器或鼠标的环境中调用依赖于键盘、显示器或鼠标的代码时引发
this.init();
this.addNorthComponent();
this.addCenterComponent();
}
//建立计算器界面布局
public void init() {
this.setTitle("计算器");//界面名称
this.setSize(800,800);//界面大小
this.setLayout(new BorderLayout());//建立布局
this.setResizable(false); //计算器界面被拉伸
this.setDefaultCloseOperation(EXIT_ON_CLOSE);//使用system exit方法退出应用程序
}
//向顶层容器添加组件
public void addNorthComponent()
{
this.resultText.setPreferredSize(new Dimension(700,150));
resultText.setHorizontalAlignment(JTextField.RIGHT);;//文本框内容右对齐
resultText.setEditable(false);//文本框不允许修改结果
resultText.setFont(new Font("隶书",Font.PLAIN,36));
jp_north.add(resultText);
this.add(jp_north,BorderLayout.NORTH);//把顶层界面添加到整体布局的上层
}
//向中间层容器添加组件
public void addCenterComponent()
{
String btn_next = "C()/789*456-123+√0.=";//存储中间按钮的所有字符
this.jp_center.setLayout(new GridLayout(5,4,3,3));//设置中间按钮的行列(5行4列的网格布局)
this.add(jp_center,BorderLayout.CENTER);//中层布局添加到整体布局的中层
this.setVisible(true);
String regex = "[\\+\\-\\*\\/\\√\\(\\)\\.\\=C]";
for(int i = 0;i < 20;i++)
{
String temp = btn_next.substring(i,i+1);
JButton btn = new JButton();
btn.setFont(new Font("宋体",Font.BOLD,30));
btn.addActionListener(this);//每个按钮都注册事件监听器
btn.setText(temp);
btn.setBackground(Color.PINK);
jp_center.add(btn);
if(temp.matches(regex)) {
btn.setFont(new Font("粗体",Font.BOLD,30));
btn.setForeground(Color.RED);
}
}
}
三、程序流程图
四、主体代码
package p1;
import java.util.Stack;
import java.util.Objects;
import java.util.Arrays;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//UI设计
class Calculator1 extends JFrame implements ActionListener {
private JPanel jp_north = new JPanel();
//JPanel是java图形用户界面GUI工具包swing中的面板容器类,包含在javax.swing包中,可以进行嵌套,功能是对窗体中具有相同逻辑功能组件进行组合
private JTextField resultText = new JTextField("0"); //创建一个输入框
private JPanel jp_center = new JPanel();
private String input = "";
public Calculator1() throws HeadlessException{
this.init();
this.addNorthComponent();
this.addCenterComponent();
}
//建立计算器界面布局
public void init() {
this.setTitle("计算器");//界面名称
this.setSize(800,800);//界面大小
this.setLayout(new BorderLayout());//建立布局
this.setResizable(false); //计算器界面被拉伸
this.setDefaultCloseOperation(EXIT_ON_CLOSE);//使用system exit方法退出应用程序
}
//向顶层容器添加组件
public void addNorthComponent()
{
this.resultText.setPreferredSize(new Dimension(700,150));
resultText.setHorizontalAlignment(JTextField.RIGHT);;//文本框内容右对齐
resultText.setEditable(false);//文本框不允许修改结果
resultText.setFont(new Font("隶书",Font.PLAIN,36));
jp_north.add(resultText);
this.add(jp_north,BorderLayout.NORTH);//把顶层界面添加到整体布局的上层
}
//向中间层容器添加组件
public void addCenterComponent()
{
String btn_next = "C()/789*456-123+√0.=";//存储中间按钮的所有字符
this.jp_center.setLayout(new GridLayout(5,4,3,3));//设置中间按钮的行列(5行4列的网格布局)
this.add(jp_center,BorderLayout.CENTER);//中层布局添加到整体布局的中层
this.setVisible(true);
String regex = "[\\+\\-\\*\\/\\√\\(\\)\\.\\=C]";
for(int i = 0;i < 20;i++)
{
String temp = btn_next.substring(i,i+1);
JButton btn = new JButton();
btn.setFont(new Font("宋体",Font.BOLD,30));
btn.addActionListener(this);//每个按钮都注册事件监听器
btn.setText(temp);
btn.setBackground(Color.PINK);
jp_center.add(btn);
if(temp.matches(regex)) {
btn.setFont(new Font("粗体",Font.BOLD,30));
btn.setForeground(Color.RED);
}
}
}
//牛顿迭代法计算开方
//牛顿迭代法算开方
public double sequareRoot (double n) {
if(n < 0) {
return Double.NaN;
}else {
double err = 1e-15;
double root = n;
while(Math.abs(n - root * root) > err) {
root = (n/root + root) / 2;
}
return root;
}
}
//用户按下等号按钮后获取存储用户输入的字符串input作为参数传入此方法,用户输入的运算表达式默认为中缀形式,
//此方法利用双栈法将其转化为计算机可以识别的后缀表达式
//中缀转后缀 3+(5*7)
private String[] houzhui(String infix) {
String s = "";
Stack<String> opStack = new Stack<String>();//第一个栈使操作符栈,对用户输入的操作符进行处理,用于存储运算符
Stack<String> postQueue = new Stack<String>();//第二个使后缀表达式栈,用于存储数字和处理后的操作符
System.out.println("中缀:"+infix.substring(0,infix.length()-1));
for(int i=0;i < infix.length();i++)
{
if("1234567890.".indexOf(infix.charAt(i))>=0){//遇到数字直接进栈
s="";//infix.charat()函数用于定位字符串infix 某个位置的值
for(;i<infix.length() && "0123456789.".indexOf(infix.charAt(i)) >=0;i++) {
s = s + infix.charAt(i);
//当前字符为数字时向后继续获取数字到栈中
}//当前i值的位置是非数字,为了避免跳过对其的处理,进行i--运算
i--;
//同一运算数的所有字符获取到字符串s后压入栈
postQueue.push(s);
//左括号直接压入操作符栈
}else if("(".indexOf(infix.charAt(i))>=0) {
opStack.push(String.valueOf(infix.charAt(i)));
//遇到右括号要将操作符栈的栈顶元素循环出栈直到遇到左括号,同时删除左括号
}else if(")".indexOf(infix.charAt(i))>=0) {
while ( !opStack.peek().equals("(")){
postQueue.push(opStack.pop());
}
opStack.pop();
// while (!opStack.empty())
// postQueue.push(opStack.pop());
}else if("+-*/√".indexOf(infix.charAt(i))>=0)//遇到加减乘除等两位预算操作符
{//先看操作符栈是否为空或者栈顶为左括号 若是如此 则直接入栈
if(opStack.empty() || "(".contains(opStack.peek())) {
opStack.push(String.valueOf(infix.charAt(i)));
}else {//如果不是 要比较栈顶操作符和当前操作符的优先级,栈顶为同级或高级时,让栈顶元素先进入后缀表达式栈,
//直到栈为空或者栈顶为低优先级时当前元素入栈 (*/的优先级要高于+-)
// stack().pop是获取栈顶元素并且弹出 stack().peek()是获取栈顶元素不弹出
//rule 定义了一个操作符的优先级规则 */√要优于+-的运算 即先入栈
boolean rule = ("√*/+-".contains(opStack.peek()) && "+-".indexOf(infix.charAt(i)) >=0)
|| ("√*/".contains(opStack.peek()) && "*/".indexOf(infix.charAt(i)) >=0);
while (!opStack.empty() && rule) {
postQueue.push(opStack.peek());
opStack.pop();
rule = ("√*/+-".contains(opStack.peek()) && "+-".indexOf(infix.charAt(i)) >=0)
|| ("√*/".contains(opStack.peek()) && "*/".indexOf(infix.charAt(i)) >=0);
}
//否则 栈为空或者当前操作符的优先级高于操作符栈顶的元素 则当前操作符入栈
opStack.push(String.valueOf(infix.charAt(i)));
}
}
}
//遍历完中缀表达式后 让此时的操作符栈的栈顶元素依次进入后缀表达式栈
while(!opStack.empty()) {
postQueue.push(opStack.pop());
}
//获取后缀表达式的大小 并使用循环让栈中元素倒序依次填入字符串型数组suffix
String[] suffix = new String[postQueue.size()];
for (int i=postQueue.size() - 1;i>=0;i--) {
suffix[i] = postQueue.pop();
}
//打印后缀表达式的数组
System.out.println("后缀:"+Arrays.toString(suffix.clone()));
return suffix;
}
//转化为后缀表达式,并返回最终结果
//上一个方法已经得到了后缀表达式且其存储在数组suffix中,计算时就是把后缀表达式的每一个字符从数组中一个一个取出
//看它是数字还是运算符,数字直接进入结果栈,运算符判断是单目还是双目运算符,双目运算符弹出结果栈顶两个数字,单目运算符弹出结果栈顶一个数字,计算后再次入栈
//运算符中,"-","√"为特殊字符,根号一个是除数为零时显示ERROR,一个是调用了牛顿迭代法运算;“-”这里可能是减号,也可能是负号即取反号
public String Result(String[] suffix)
{
//字符串栈Rsult,数字没有遇到运算符时进入结果栈顶等待出现运算符时弹出运算,运算后结果再次存储到结果栈
double label=0;
Stack<String> Result = new Stack<String>();
for(int i = 0;i < suffix.length; i++) {
//数字直接入栈 取字符串的首位即第0位 用indexof取查找在前面数字串的位置 若能返回一个位置值则该字符为数字
if ("1234567890.".indexOf(suffix[i].charAt(0))>=0)
Result.push(suffix[i]);
else
{//运算符的话 根号和减号单独拿出来算
double n = 0;
if (suffix[i].equals("√"))
{
double z = 0;
if(Double.parseDouble(Result.peek())>=0)
{
z = Double.parseDouble(Result.pop());
n = sequareRoot(z);
}
else label = 1;
}
else if (suffix[i].equals("-"))
{ //后面代码用了suffix[i+1],所以这里要确保i+1不超出后缀数组长度
if (i + 1 < suffix.length)
{ // 当结果栈顶只有一个元素或者当前运算符后也是运算符时,判断“-”为取反号
// 取反号则弹出栈顶一个元素进行取反运算
if (Result.size() == 1 || (Result.size() %2 == 0 && ("+-*/".contains(suffix[i + 1]))))
{
double q = 0;
q = Double.parseDouble(Result.pop());
n = -q;
}//否则 则判断为减号,弹出栈顶两个数相减
else
{
double a = 0, b = 0;
a = Double.parseDouble(Result.pop());
b = Double.parseDouble(Result.pop());
n = b - a;
}
}//i+1超出数组长度说明当前“-”为最后一个运算符
else
{
if (Result.size() == 1 )
{
double q = 0;
q = Double.parseDouble(Result.pop());
n = -q;
System.out.println(n);
}
else {
double a = 0, b = 0;
a = Double.parseDouble(Result.pop());
b = Double.parseDouble(Result.pop());
n = b - a;
}
}
}//判断为一般运算符 则弹出栈顶两个数字进行双目运算
else
{
double x, y = 0;
x = Double.parseDouble(Result.pop());
y = Double.parseDouble(Result.pop());
switch (suffix[i]) {
case "+":
n = x + y;
break;
case "*":
n = x * y;
break;
case "/":
if (x == 0) {//除数为零时 改变标签label的值使其显示输入错误
label = 1;
}
n = y / x;
break;
}
}
每一次计算的结果循环入栈 作为下一次计算的基数,最后栈顶只保留最终结果
Result.push(String.valueOf(n));
}
} //报错标志
if(label == 1)
return "ERROR!";
return (Result.peek());//返回栈顶的最终结果
}
//事件处理 UI布局时注册了监听器 这里使用监听器获取用户按动的按钮的字符
public void actionPerformed(ActionEvent e) {
String label = e.getActionCommand();
//每次获取的字符的接到input字符串中
input = input + label;
resultText.setText(input);
//清零输入框
if(Objects.equals(label, "C")) {
input = "";
resultText.setText("0");
}
//遇到等号即输入结束
if((Objects.equals(label, "="))){
if(input.isEmpty()) return ;
//调用houzhui方法将中缀表达式转化为后缀
String[] s = houzhui(input);
//调用Result方法结算最终结果并获取到result字符串中 打印在文本框中
String result = Result(s);
if(result.equals("ERROR!"))
resultText.setText(result);
else
resultText.setText(input + result);
}
}
public static void main(String[] args) {
Calculator1 calculator = new Calculator1();
calculator.setVisible(true);
}
}
五、测试用例