1、简单计算器的实现
实验内容:
- 学习GUI图形界面的设计,Python Tinker或 Java Swing/Awt 或C++ QT框架,创建交互友好的应用程序;
- 能通过界面按钮控件输入并实现算术表达式,输入的表达式即时在控件中显示,按下“=”按钮能实现运算,并将运算结果输出在控件中显示;要求能保存和浏览历史表达式的运算记录。
- 算术表达式求解,是指算术表达式中包括加、减、乘、除、括号等运算符,能求解包含括号的四则混合运算;并且能够检验表达式的合法性。
- 选做:①实现三角函数的运算、对数运算、指数运算、进制转换等;
②设计函数,分别求两个一元多项式的乘积与和。
图形界面首先开始设计一个计算器的界面,定义计算器所需要的按钮并赋给其按钮名称,使得使用计算器时简单明了,清晰可见,操作轻松。
给各个按钮进行算法,利用鼠标点击事件,当鼠标点击某个按钮时就开始执行那个按钮所定义的算法,生成数据并显示。
除数字的输入为直接进行输入外,其他运算例如+、一、*、/、开平方等算法要求计算时通过内部较为复杂的运算将结果显示在显示屏上。
至于其中的进制转换功能的实现则是直接调用Java中进制转换的函数, 例如要将十进制转换成二进制则可直接调用函数Integer.to Binary String(inti) 来实现, 充分显示了Java语言的简洁性和Java语言功能的强大性。
当然了在输入数据时还要进行异常捕捉, 防止越界的现象的发生, 这也是Java语言健壮性的体现。
功能模块设计:
1.能实现正常的四则运算,且具有一定的自动纠错功能。
2.输入的运算式子能在显示框内正常显示。
3.能带括号正常运算
思路分为两个方面:
第一是计算器界面怎么做,需要什么元素、控件。第二是对运算的实现。
1、首先来分析一下计算器的基本界面,完成四则运算,基本上就是0~9十个按钮、一个小数点、一个等号、四个加减乘除按钮,还有一个显示文本的Text。大概就长这个样子:
这个布局首先是一个上下结构,上面是一个text文本框,下面是各种按钮,所以我们可以用一个VBox把text放上面,把下面的其他组件放下面。而下面是一个5*5的矩阵,矩阵里放着22个按钮(Button)。所以下面的按钮需要一个矩阵来装,这就要用到GridLayout,大主要是按照按照网格状进行布局。
2、接下来我们来分析一下运算怎么搞,大家可以先想想平时我们算四则运算需要什么,他们出现的先后顺序。没错,我们需要知道第一个数,然后一个操作,然后第二个数,最后只要点击了等号就会出现结果。
3、因为是鼠标点击按钮输入数字,我们就需要对四则运算加监听器。
监听器就是监听某个对象的状态变化的技术。监听器包括事件源,监听器,注册监听器以及响应行为四个部分。
事件源即被监听的对象。
监听器是监听事件源对象的状态变化。
注册监听器即将监听器与事件源关联起来。
响应行为即监听到事件源所触发的动作。
监听器按照不同的划分方式可以分为多种
1,按照监听的对象不同可分为HttpRequest域,HttpSession域和ServletContext域。
2,按照监听的状态可分为监听域对象的创建与销毁,属性状态的变化。(这两点可归纳为监听三大域对象的创建与销毁及其属性的状态变化)
4、基于在显示框显示操作记录,以及运算的实现,需要用到堆栈的算法结构,所以基于堆栈的原理
1.一个名为TOP的指针 用于跟踪堆栈中的顶部元素。
2.初始化堆栈时,我们将其值设置为 -1,以便我们可以通过比较来检查堆栈是否为空 TOP == -1。
3.在推送一个元素时,我们增加TOP的值 并将新元素放置在TOP指向的位置 。
4.在弹出一个元素时,我们返回TOP指向的元素 并减少它的值。
5.在推送之前,我们检查堆栈是否已满。
6.在弹出之前,我们检查堆栈是否已经为空。
而我们这里直接调用JAVA库里的方法即可
5.部分使用截图:
接下来,这是我们的实行代码:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.StringTokenizer;
import javax.swing.JButton;
import javax.swing.Jframe;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Main extends Jframe implements ActionListener {
ArrayList list;
String[] KEYS = { "(", ")", "^", "7", "8", "9", "4", "5",
"6", "1", "2", "3", "0", ".", "π" };
String[] CLEAR = { "AC", "C" };
String[] SYMBOL = { "/", "*", "-", "+" ,"="};
JButton[] keys = new JButton[KEYS.length];
JButton[] clear = new JButton[CLEAR.length];
JButton[] symbol = new JButton[SYMBOL.length];
public JTextField resultText = new JTextField("0");
boolean vbegin = true; // 控制输入,true为重新输入,false为接着输入
boolean equals_flag = true; // true为未输入=,false表示输入=
boolean isContinueInput = true; // true为正确,可以继续输入,false错误,输入锁定
int MAXLEN = 100;
double PI = 3.141592657;
public Main() {
init();
this.setTitle("Calculator");
this.setLocation(900, 420); //计算器出现位置
this.pack();
}
private void init() {
resultText.setHorizontalAlignment(JTextField.RIGHT);
resultText.setEditable(false);
resultText.setBackground(Color.white);
list = new ArrayList<>();
initLayout();//界面设置
initActionEvent();//添加组件事件处理,button响应
}
public void initLayout() {
JPanel calckeysPanel = new JPanel();
calckeysPanel.setLayout(new GridLayout(5, 3, 3, 3));
for (int i = 0; i < KEYS.length; i++) {
keys[i] = new JButton(KEYS[i]);
calckeysPanel.add(keys[i]);
keys[i].setForeground(Color.black);
}
for (int i = 0; i < SYMBOL.length; i++) {
symbol[i] = new JButton(SYMBOL[i]);
symbol[i].setForeground(Color.red);
}
for (int i = 0; i < CLEAR.length; i++) {
clear[i] = new JButton(CLEAR[i]);
clear[i].setForeground(Color.red);
}
JPanel text = new JPanel();
text.setLayout(new BorderLayout());
text.add("Center", resultText);
JPanel panel1 = new JPanel();
panel1.setLayout(new GridBagLayout());
GridBagConstraints gbc= new GridBagConstraints();//定义一个GridBagConstraints
gbc.fill = GridBagConstraints.BOTH;
gbc.insets = new Insets(3, 3, 3, 3);
gbc.gridx = 0;
gbc.gridy = 0;
//gbc.gridwidth = 1;
//gbc.gridheight = 1;
panel1.add(symbol[0], gbc);// “/号”
gbc.gridx = 1;
gbc.gridy = 0;
//gbc.gridwidth = 1;
//gbc.gridheight = 1;
panel1.add(clear[0], gbc);// "AC"
gbc.gridx = 0;
gbc.gridy = 1;
//gbc.gridwidth = 1;
//gbc.gridheight = 1;
panel1.add(symbol[1], gbc);//"*"
gbc.gridx = 1;
gbc.gridy = 1;
gbc.gridwidth = 1;
gbc.gridheight = 2;
panel1.add(clear[1], gbc);//“backspace”
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 1;
gbc.gridheight = 1;
panel1.add(symbol[2], gbc);//“-”
gbc.gridx = 0;
gbc.gridy = 3;
gbc.ipady = 33;
panel1.add(symbol[3], gbc);//"+"
gbc.gridx = 1;
gbc.gridy = 3;
//gbc.ipadx = 10;
//gbc.ipady = 33;
panel1.add(symbol[4], gbc);//"="
getContentPane().setLayout(new BorderLayout(20, 3));
getContentPane().add("Center", calckeysPanel);
getContentPane().add("East", panel1);
getContentPane().add("North", text);
}
public void initActionEvent() {
for (int i = 0; i < KEYS.length; i++) {
keys[i].addActionListener(this);
}
for (int i = 0; i < CLEAR.length; i++) {
clear[i].addActionListener(this);
}
for (int i = 0; i < SYMBOL.length; i++) {
symbol[i].addActionListener(this);
}
}
//组建发生操作时调用
public void actionPerformed(ActionEvent e) {
String label = e.getActionCommand();
if (label.equals(CLEAR[1])) {
handleBackspace();
} else if (label.equals(CLEAR[0])) {
list.clear();
resultText.setText("0");
vbegin = true;
equals_flag = true;
} else {
handle(label);
}
}
private void handleBackspace() {
String text = resultText.getText();
list.add(text);
int i = text.length();
if (i > 0) {
text = text.substring(0, i - 1);
list.remove(list.size() - 1); // 移除栈顶的那个元素
if (text.length() == 0) {
list.clear();
resultText.setText("0");
vbegin = true;
equals_flag = true;
} else {
resultText.setText(text);
}
}
}
public void handle(String key) {
String text = resultText.getText();
if (!equals_flag) { //&& "π0123456789.()+-*/^".indexOf(key) != -1
list.add(text);
vbegin = false;// ?????????????
}
if (!list.isEmpty()) {
TipChecker(list.get(list.size() - 1), key);
} else {
TipChecker("#", key);
}
if (isContinueInput && "π0123456789.()+-*/^".contains(key)) {
list.add(key);
}
// 若输入正确,则将输入信息显示到显示器上
if (isContinueInput && "π0123456789.()+-*/^".contains(key)) {
if (!equals_flag && ("+-*/^".contains(key))) {
vbegin = false;
equals_flag = true;
printText(key);
} else if (!equals_flag
&& ("π0123456789.()".contains(key))) {
vbegin = true;
equals_flag = true;
printText(key);
} else {
printText(key);
}
} else if (isContinueInput && equals_flag && key.equals("=")) {
isContinueInput = false;// 表明不可以继续输入
equals_flag = false;// 表明已经输入=
vbegin = true;// 重新输入标志设置true
process(resultText.getText()); // 整个程序的核心,计算表达式的值并显示
list.clear();
}
isContinueInput = true;
}
private void printText(String key) {
if (vbegin) {
resultText.setText(key);// 清屏后输出
// firstDigit = false;
} else {
resultText.setText(resultText.getText() + key);
}
vbegin = false;
}
private void TipChecker(String tipcommand1, String tipcommand2) {
// Tipcode1表示错误类型,Tipcode2表示名词解释类型
int Tipcode1 = 0;
// 表示命令类型
int tiptype1 = 0, tiptype2 = 0;
// 括号数
// “+-*/ ^”不能作为第一位
if (tipcommand1.compareTo("#") == 0
&& (tipcommand2.compareTo("/") == 0
|| tipcommand2.compareTo("*") == 0
|| tipcommand2.compareTo("+") == 0
|| tipcommand2.compareTo(")") == 0 || tipcommand2
.compareTo("^") == 0)) {
Tipcode1 = -1;
}
// 定义存储字符串中最后一位的类型
else if (tipcommand1.compareTo("#") != 0) {
if (tipcommand1.compareTo("(") == 0) {
tiptype1 = 1;
} else if (tipcommand1.compareTo(")") == 0) {
tiptype1 = 2;
} else if (tipcommand1.compareTo(".") == 0) {
tiptype1 = 3;
} else if ("0123456789".contains(tipcommand1)) {
tiptype1 = 4;
} else if ("+-*/".contains(tipcommand1)) {
tiptype1 = 5;
} else if ("^".contains(tipcommand1)) {
tiptype1 = 6;
}else if ("π".contains(tipcommand1)){
tiptype1 = 7;
}
// 定义欲输入的按键类型
if (tipcommand2.compareTo("(") == 0) {
tiptype2 = 1;
} else if (tipcommand2.compareTo(")") == 0) {
tiptype2 = 2;
} else if (tipcommand2.compareTo(".") == 0) {
tiptype2 = 3;
} else if ("0123456789".contains(tipcommand2)) {
tiptype2 = 4;
} else if ("+-*/".contains(tipcommand2)) {
tiptype2 = 5;
} else if ("^".contains(tipcommand2)) {
tiptype2 = 6;
}else if ("π".contains(tipcommand2)){
tiptype2 = 7;
}
switch (tiptype1) {
case 1:
// 左括号后面直接接右括号,“+*/”(负号“-”不算),或者" ^"
if (tiptype2 == 2
|| (tiptype2 == 5 && tipcommand2.compareTo("-") != 0)
|| tiptype2 == 6)
Tipcode1 = 1;
break;
case 2:
// 右括号后面接左括号,数字,“+-*/^...π”
if (tiptype2 == 1 || tiptype2 == 3 || tiptype2 == 4|| tiptype2 == 7)
Tipcode1 = 2;
break;
case 3:
// “.”后面接左括号,π
if (tiptype2 == 1 || tiptype2 == 7)
Tipcode1 = 3;
// 连续输入两个“.”
if (tiptype2 == 3)
Tipcode1 = 8;
break;
case 4:
// 数字后面直接接左括号和π
if (tiptype2 == 1 || tiptype2 == 7)
Tipcode1 = 4;
break;
case 5:
// “+-*/”后面直接接右括号,“+-*/ ^”
if (tiptype2 == 2 || tiptype2 == 5 || tiptype2 == 6)
Tipcode1 = 5;
break;
case 6:
// “ ^”后面直接接右括号,“+-*/ ^π”
if (tiptype2 == 2 || tiptype2 == 5 || tiptype2 == 6 || tiptype2 == 7)
Tipcode1 = 6;
break;
case 7:
//"π"之后只能为"+-*/^)"不能为"π(.0123456789"
if (tiptype2 == 1 || tiptype2 == 3 || tiptype2 == 4 || tiptype2 == 7){
Tipcode1 = 7;
}
break;
}
}
// 检测小数点的重复性,Tipconde1=0,表明满足前面的规则
if (Tipcode1 == 0 && tipcommand2.compareTo(".") == 0) {
int tip_point = 0;
for (String s : list) {
// 若之前出现一个小数点点,则小数点计数加1
if (s.equals(".")) {
tip_point++;
}
// 若出现以下几个运算符之一,小数点计数清零
if (s.equals("^") || s.equals("/")
|| s.equals("*") || s.equals("-")
|| s.equals("+") || s.equals("(")
|| s.equals(")")) {
tip_point = 0;
}
}
tip_point++;
// 若小数点计数大于1,表明小数点重复了
if (tip_point > 1) {
Tipcode1 = 8;
}
}
// 检测右括号是否匹配
if (Tipcode1 == 0 && tipcommand2.compareTo(")") == 0) {
int tip_right_bracket = 0;
for (String s : list) {
// 如果出现一个左括号,则计数加1
if (s.equals("(")) {
tip_right_bracket++;
}
// 如果出现一个右括号,则计数减1
if (s.equals(")")) {
tip_right_bracket--;
}
}
// 如果右括号计数=0,表明没有响应的左括号与当前右括号匹配
if (tip_right_bracket == 0) {
Tipcode1 = 10;
}
}
// 检查输入=的合法性
if (Tipcode1 == 0 && tipcommand2.compareTo("=") == 0) {
// 括号匹配数
int tip_bracket = 0;
for (String s : list) {
if (s.equals("(")) {
tip_bracket++;
}
if (s.equals(")")) {
tip_bracket--;
}
}
// 若大于0,表明左括号还有未匹配的
if (tip_bracket > 0) {
Tipcode1 = 9;
} else if (tip_bracket == 0) {
// 若前一个字符是以下之一,表明=号不合法
if ("+-*/".contains(tipcommand1)) {
Tipcode1 = 5;
}
}
}
if (Tipcode1 != 0) {
isContinueInput = false;// 表明不可以继续输入
}
}
public void process(String str) {
int weightPlus = 0, topOp = 0, topNum = 0, flag = 1;
// weightPlus为同一()下的基本优先级,weightTemp临时记录优先级的变化
// topOp为weight[],operator[]的计数器;topNum为number[]的计数器
// flag为正负数的计数器,1为正数,-1为负数
int[] weight; // 保存operator栈中运算符的优先级,以topOp计数
double[] number; // 保存数字,以topNum计数
char ch; // operator[]保存运算符,以topOp计数
char ch_gai;
char[] operator;
String num;// 记录数字,str以+-*/() ! ^分段,+-*/() ^字符之间的字符串即为数字
weight = new int[MAXLEN];
number = new double[MAXLEN];
operator = new char[MAXLEN];
String expression = str.replace("π",String.valueOf(PI));//将字符串中的π用PI
// 建议用split代替字符串分割
StringTokenizer expToken = new StringTokenizer(expression, "+-*/()^");
int i = 0;
while (i < expression.length()) {
ch = expression.charAt(i);
// 判断正负数
if (i == 0) {
if (ch == '-')
flag = -1;
} else if (expression.charAt(i - 1) == '(' && ch == '-')
flag = -1;
// 取得数字,并将正负符号转移给数字,E是科学计数
if (ch <= '9' && ch >= '0' || ch == '.' || ch == 'E') {
num = expToken.nextToken();//分割后的StringTokenizer中的下一个索引数据
ch_gai = ch;
// 取得整个数字
while (i < expression.length()
&& (ch_gai <= '9' && ch_gai >= '0' || ch_gai == '.' || ch_gai == 'E')) {
ch_gai = expression.charAt(i++);
}
// 将指针退回之前的位置,即每个数字的末尾位置
if (i >= expression.length())
i -= 1;
else {
i -= 2;
}
if (num.compareTo(".") == 0)
number[topNum++] = 0;
// 将正负符号转移给数字
else {
number[topNum++] = Double.parseDouble(num) * flag;
flag = 1;
}
}
// 计算运算符的优先级
if (ch == '(')
weightPlus += 4;
if (ch == ')')
weightPlus -= 4;
if (ch == '-' && flag == 1 || ch == '+' || ch == '*' || ch == '/'
|| ch == '^') {
int weightTemp = switch (ch) {
// +-的优先级最低,为1
case '+', '-' -> 1 + weightPlus;
// x/的优先级稍高,为2
case '*', '/' -> 2 + weightPlus;
default -> 4 + weightPlus;
};
// 如果当前优先级大于堆栈顶部元素,则直接入栈
if (topOp == 0 || weight[topOp - 1] < weightTemp) {
weight[topOp] = weightTemp;
operator[topOp] = ch;
topOp++;
// 否则将堆栈中运算符逐个取出,直到当前堆栈顶部运算符的优先级小于当前运算符
}
else {
while (topOp > 0 && weight[topOp - 1] >= weightTemp) {
switch (operator[topOp - 1]) {
// 取出数字数组的相应元素进行运算
case '+' -> number[topNum - 2] += number[topNum - 1];
case '-' -> number[topNum - 2] -= number[topNum - 1];
case '*' -> number[topNum - 2] *= number[topNum - 1];
// 判断除数为0的情况
case '/' -> {
if (number[topNum - 1] == 0) {
// showError(1, str_old);
return;
}
number[topNum - 2] /= number[topNum - 1];
}
case '^' -> number[topNum - 2] = Math.pow(number[topNum - 2],
number[topNum - 1]);
// 计算时进行角度弧度的判断及转换
}
// 继续取堆栈的下一个元素进行判断
topNum--;
topOp--;
}
// 将运算符入堆栈
weight[topOp] = weightTemp;
operator[topOp] = ch;
topOp++;
}
}
i++;
}
// 依次取出堆栈的运算符进行运算
while (topOp > 0) {
// +-x直接将数组的后两位数取出运算
switch (operator[topOp - 1]) {
case '+' -> number[topNum - 2] += number[topNum - 1];
case '-' -> number[topNum - 2] -= number[topNum - 1];
case '*' -> number[topNum - 2] *= number[topNum - 1];
// 涉及到除法时要考虑除数不能为零的情况
case '/' -> {
if (number[topNum - 1] == 0) {
// showError(1, str_old);
return;
}
number[topNum - 2] /= number[topNum - 1];
}
case '^' -> number[topNum - 2] = Math.pow(number[topNum - 2],
number[topNum - 1]);
}
// 取堆栈下一个元素计算
topNum--;
topOp--;
}
// 如果是数字太大,提示错误信息
if (number[0] > 7.3E306) {
// showError(3, str_old);
return;
}
// 输出最终结果
resultText.setText(String.valueOf(FP(number[0])));
}
public double FP(double n) {
// NumberFormat format=NumberFormat.getInstance(); //创建一个格式化类f
// format.setMaximumFractionDigits(18); //设置小数位的格式
DecimalFormat format = new DecimalFormat("0.#############");
return Double.parseDouble(format.format(n));
}
public static void main(String[] args) {
Main calculator = new Main();
calculator.setVisible(true);
calculator.setDefaultCloseOperation(Jframe.EXIT_ON_CLOSE);
}
}



