一.实验目的
(1)能通过设计的按钮控制输入并实现算术表达式,表达式在文本框中显示,运算结果输出显示;保存和浏览历史运算记录。
(2)能够检验算术表达式的合法性。
(3)能够实现混合运算的求解,算术表达式中包括加、减、乘、除、括号等运算符。
(4)要求交互界面友好,程序健壮。
二.编程语言和开发环境
1.编程语言:JAVA
2.开发环境:IntelliJ IDEA
三.操作难点和解决步骤
1.操作难点
(1)大学以来第一次进行综合性实验,不知道从如何下手。
(2)Qt环境的开发(按键、文本框的设置以及按键在文本框内的响应)。
(3)算法如何由理论知识转化为代码。
(4)中缀表达式如何转成后缀表达式,以及后缀表达式如何计算
(5)历史记录如何保存。
2.解决办法(部分)
(1)对于这个问题可以去各大IT类网站或者b站学习往届学长学姐经验,虚心学习。
(2)在b站上寻找用基于C++的Qt环境开发并开始自学,结合网站上的各种建议,着手去做。
(3)首先搞懂代码的整个流程以及本质含义,在理解的基础上多去尝试编写代码,也可适当参考大神代码。
(4)和(5)目前待解决,目前主要开始学习Qt环境以及如何设置按键、文本框以及两者之间的响应情况。
四、退格以及清除功能的实现
(1)由上篇文章中对按键的设置,我们可以设计计算器的初始界面以及按键与文本的响应,具体效果如下所示:
综上,已基本实现按键与文本框的响应。计算器中我添加了Del(消除一位的功能)以及C(清除内容的功能),同时也添加了“=”来输出我们的结果。
(2)实现消除一位的代码如下:
void MainWindow::on_pushButton_17_clicked()//实现消去一位的功能
{
res=res.left(res.length()-1);
ui->lineEdit->setText(res);
}
(3) 实现清除内容的代码如下:
void MainWindow::on_pushButton_18_clicked()//实现清除功能
{
res="";
ui->lineEdit->setText(res);
}
(4)实现浏览历史记录
历史记录可使用栈结构来存储,在浏览时只需从栈底开始向上浏览。
先在widget.h中定义私有成员来存储历史记录。
private:
Ui::Widget *ui;
QString res;
QStack<QString> history_ans; //定义栈用来存储历史记录
QString mid_expression;
QString back_expression;
};
接下来在槽函数中编写程序:
void Widget::on_pushButton_10_clicked()
{
QString h_ans;
for(auto ans:history_ans)
{
h_ans+=ans;
h_ans+="\t"; //间隔输出历史记录
}
ui->lineEdit->setText(h_ans);
}
五、修改界面窗口属性
(1)通过以下代码可实现对窗口名的更改
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
w.setWindowTitle(QString("Calculator")); //修改这句即可,但是字母,汉字乱码
w.show();
return a.exec();
}
(2)修改文本框字体大小
在widget.ui中选中文本框,右击添加样式表
选择添加字体,进入后根据需求选择合适的字体以及大小
修改后的结果显示如下:
六、中缀表达式转后缀的操作
(1)基本规则
1.先将表达式从左到右进行遍历。
2.运算数,直接输出。
3.左括号,直接压入堆栈,(括号是最高优先级,无需比较)(入栈后优先级降到最低,确保其他符号正常入栈)。
4.右括号,(意味着括号已结束)不断弹出栈顶运算符并输出直到遇到左括号(弹出但不输出)。
5.运算符,将该运算符与栈顶运算符进行比较,
如果优先级高于栈顶运算符则压入堆栈(该部分运算还不能进行),
如果优先级低于等于栈顶运算符则将栈顶运算符弹出并输出,然后比较新的栈顶运算符.
(低于弹出意味着前面部分可以运算,先输出的一定是高优先级运算符,等于弹出是因为同等优先级,从左到右运算)
直到优先级大于栈顶运算符或者栈空,再将该运算符入栈.
6.如果对象处理完毕,则按顺序弹出并输出栈中所有运算符.
(2)基本想法
可创建一个符号栈和一个字符串队列,遍历各个字符信息,判断该字符是运算符、括号还是数值。
1)运算符
判断当前字符优先级是否小于等于栈顶字符优先级,此时栈顶元素中的左括号(,优先级最小
小于等于 ->将符号栈栈顶内容弹出且存入到字符串队列中,将当前字符存入到符号栈中
大于->将当前字符存入到符号栈中
2)括号
左括号->存入到符号栈中
右括号 ->依次将符号栈中的运算符弹出进入到字符串队列中直到在符号栈中弹出出现左括号停止弹栈 ,数值直接进入到字符串队列中
3)数值->直接输出
遍历结束后 判断符号栈中是否有元素
七、完整源代码
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Calculator extends JFrame implements ActionListener {
private String[] KEYS = { "7", "8", "9", "AC", "4", "5", "6", "-", "1", "2", "3", "+", "0", "e", "pi", "/", "sqrt",
"%", "x*x", "*", "(", ")", ".", "=" };
private JButton keys[] = new JButton[KEYS.length];
private JTextArea resultText = new JTextArea("0.0");// ⽂本域组件TextArea可容纳多⾏⽂本;⽂本框内容初始值设为0.0
private JTextArea History = new JTextArea();// 历史记录⽂本框初始值设为空
private JPanel jp1=new JPanel();
private JPanel jp2=new JPanel();
private JScrollPane gdt1=new JScrollPane(resultText);//给输⼊显⽰屏⽂本域新建⼀个垂直滚动滑条
private JScrollPane gdt2=new JScrollPane(History);//给历史记录⽂本域新建⼀个垂直滚动滑条
// private JScrollPane gdt3=new JScrollPane(History);//给历史记录⽂本域新建⼀个⽔平滚动滑条
private JLabel label = new JLabel("历史记录");
private String b = "";
// 构造⽅法
public Calculator() {
super("Caculator");//“超”关键字,表⽰调⽤⽗类的构造函数,
resultText.setBounds(20, 18, 255, 115);// 设置⽂本框⼤⼩
resultText.setAlignmentX(RIGHT_ALIGNMENT);// ⽂本框内容右对齐
resultText.setEditable(false);// ⽂本框不允许修改结果
History.setBounds(290, 40, 250,370);// 设置⽂本框⼤⼩
History.setAlignmentX(LEFT_ALIGNMENT);// ⽂本框内容右对齐
History.setEditable(false);// ⽂本框不允许修改结果
label.setBounds(300, 15, 100, 20);//设置标签位置及⼤⼩
jp2.setBounds(290,40,250,370);//设置⾯板窗⼝位置及⼤⼩
jp2.setLayout(new GridLayout());
jp1.setBounds(20,18,255,115);//设置⾯板窗⼝位置及⼤⼩
jp1.setLayout(new GridLayout());
resultText.setLineWrap(true);// 激活⾃动换⾏功能
resultText.setWrapStyleWord(true);// 激活断⾏不断字功能
resultText.setSelectedTextColor(Color.RED);
History.setLineWrap(true);//⾃动换⾏
History.setWrapStyleWord(true);
History.setSelectedTextColor(Color.blue);
gdt1.setViewportView(resultText);//使滚动条显⽰出来
gdt2.setViewportView(History);
gdt1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);//设置让垂直滚动条⼀直显⽰
gdt2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);//设置让垂直滚动条⼀直显⽰
gdt2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);//设置让⽔平滚动条⼀直显⽰
jp1.add(gdt1);//将滚动条添加⼊⾯板窗⼝中
jp2.add(gdt2);
this.add(jp1);//将⾯板添加到总窗体中
this.add(jp2);//将⾯板添加到总窗体中
this.setLayout(null);
this.add(label);// 新建“历史记录”标签
//this.add(resultText);// 新建⽂本框,该语句会添加进去⼀个新的JTextArea导致带有滚动条的⽂本⽆法显⽰或者发⽣覆盖
//this.add(History);// 新建历史记录⽂本框,该语句会添加进去⼀个新的JTextArea导致带有滚动条的⽂本⽆法显⽰
// 放置按钮
int x = 20, y = 150;
for (int i = 0; i < KEYS.length; i++)
{
keys[i] = new JButton();
keys[i].setText(KEYS[i]);
keys[i].setBounds(x, y, 60, 40);
if (x < 215) {
x += 65;
} else {
x = 20;
y += 45;
}
this.add(keys[i]);
}
for (int i = 0; i < KEYS.length; i++)// 每个按钮都注册事件监听器
{
keys[i].addActionListener(this);
}
this.setResizable(false);
this.setBounds(500, 200, 567, 480);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
}
// 事件处理
public void actionPerformed(ActionEvent e)
{
//History.setText(b);//使输⼊的表达式显⽰在历史记录⽂本框中
String label=e.getActionCommand();//获得事件源的标签
if(label=="=")//
{
resultText.setText(this.b);
History.setText(History.getText()+resultText.getText());
if(label=="=")//调⽤计算⽅法,得出最终结果
{
String s[]=houzhui(this.b);
String result=Result(s);
this.b=result+"";
//更新⽂本框,当前结果在字符串b中,并未删除,下⼀次输⼊接着此结果以实现连续运算
resultText.setText(this.b);
History.setText(History.getText()+"="+resultText.getText()+"\n");
}
}
else if(label=="AC")//清空按钮,消除显⽰屏⽂本框前⾯所有的输⼊和结果
{
this.b="";
resultText.setText("0");//更新⽂本域的显⽰,显⽰初始值;
}
else if(label=="sqrt")
{
String n=kfys(this.b);
resultText.setText("sqrt"+"("+this.b+")"+"="+n);//使运算表达式显⽰在输⼊界⾯
History.setText(History.getText()+"sqrt"+"("+this.b+")"+"=");//获取输⼊界⾯的运算表达式并使其显⽰在历史记录⽂本框
this.b=n;
}
else if(label=="x*x")
{
String m=pfys(this.b);
resultText.setText(this.b+"^2"+"="+m);//使运算表达式显⽰在输⼊界⾯
History.setText(History.getText()+this.b+"^2"+"=");//获取输⼊界⾯的运算表达式并使其显⽰在历史记录⽂本框
this.b=m;
}
else if(label=="e"||label=="pi")
{
if(label=="e")
{
String m=String.valueOf(2.71828);//将e的值以字符串的形式传给m
this.b=this.b+m;//保留显⽰m之前输⼊的运算符或数字字符继续下⼀步运算
resultText.setText(this.b);
// History.setText(History.getText()+this.b);
}
if(label=="pi")
{
String m=String.valueOf(3.14159265);
this.b=this.b+m;
resultText.setText(this.b);
// History.setText(History.getText()+this.b);
}
}
else
{
this.b=this.b+label;
resultText.setText(this.b);
// History.setText(History.getText()+this.b);
}
//History.setText(History.getText()+this.b);//使输⼊的表达式显⽰在历史记录⽂本框中
}
//将中缀表达式转换为后缀表达式
private String[] houzhui(String str) {
String s = "";// ⽤于承接多位数的字符串
char opStack[] = new char[100];// 静态栈,对⽤户输⼊的操作符进⾏处理,⽤于存储运算符
String postQueue[] = new String[100];// 后缀表达式字符串数组,为了将多位数存储为独⽴的字符串
int top = -1, j = 0;// 静态指针top,控制变量j
for (int i = 0; i < str.length(); i++)// 遍历中缀表达式
// indexof函数,返回字串⾸次出现的位置;charAt函数返回index位置处的字符;
{
if ("0123456789.".indexOf(str.charAt(i)) >= 0) // 遇到数字字符的情况
{
s = "";// 作为承接字符,每次开始时都要清空
for (; i < str.length() && "0123456789.".indexOf(str.charAt(i)) >= 0; i++) {
s = s + str.charAt(i);
}
i--;
postQueue[j] = s;// 数字字符直接加⼊后缀表达式
j++;
} else if ("(".indexOf(str.charAt(i)) >= 0) {// 遇到左括号
top++;
opStack[top] = str.charAt(i);// 左括号⼊栈
} else if (")".indexOf(str.charAt(i)) >= 0) {// 遇到右括号
for (;;)// 栈顶元素循环出栈,直到遇到左括号为⽌
{
if (opStack[top] != '(') {// 栈顶元素不是左括号
postQueue[j] = opStack[top] + "";// 栈顶元素出栈
j++;
top--;
} else { // 找到栈顶元素是左括号
top--;// 删除栈顶左括号
break;// 循环结束
}
}
}
if ("*%/".indexOf(str.charAt(i)) >= 0)// 遇到⾼优先级运算符
{
if (top == -1) {// 若栈为空则直接⼊栈
top++;
opStack[top] = str.charAt(i);
} else {// 栈不为空,把栈中弹出的元素⼊队,直到栈顶元素优先级⼩于x或者栈为空
if ("*%/".indexOf(opStack[top]) >= 0) {
// 栈顶元素也为⾼优先级运算符
postQueue[j] = opStack[top] + "";// 栈顶元素出栈进⼊后缀表达式
j++;
opStack[top] = str.charAt(i);// 当前运算符⼊栈
} else if ("(".indexOf(opStack[top]) >= 0) {// 栈顶元素为左括号,当前运算符⼊栈
top++;
opStack[top] = str.charAt(i);
} else if ("+-".indexOf(str.charAt(i)) >= 0) {// 遇到低优先级运算符
postQueue[j] = opStack[top] + "";// 栈顶元素出栈进⼊后最表达式
j++;
opStack[top] = str.charAt(i);// 当前元素⼊栈
}
}
} else if ("+-".indexOf(str.charAt(i)) >= 0) {
if (top == -1) {
top++;
opStack[top] = str.charAt(i);
} else {
if ("*%/".indexOf(opStack[top]) >= 0) {
// 栈顶元素也为⾼优先级运算符
postQueue[j] = opStack[top] + "";// 栈顶元素出栈进⼊后缀表达式
j++;
opStack[top] = str.charAt(i);// 当前运算符⼊栈
} else if ("(".indexOf(opStack[top]) >= 0) {// 栈顶元素为左括号,当前运算符⼊栈
top++;
opStack[top] = str.charAt(i);
} else if ("+-".indexOf(str.charAt(i)) >= 0) {// 遇到低优先级运算符
postQueue[j] = opStack[top] + "";// 栈顶元素出栈进⼊后最表达式
j++;
opStack[top] = str.charAt(i);// 当前元素⼊栈
}
}
}
}
for (; top != -1;) {// 遍历结束后将栈中剩余元素依次出栈进⼊后缀表达式
postQueue[j] = opStack[top] + "";
j++;
top--;
}
return postQueue;
}
//开⽅运算⽅法
public String kfys(String str) {
String result = "";
double a = Double.parseDouble(str), b = 0;
b = Math.sqrt(a);
result = String.valueOf(b);//将运算结果转换为string类型并赋给string类型的变量result
return result;
}
//平⽅运算⽅法
public String pfys(String str) {
String result = "";
double a = Double.parseDouble(str), b = 0;
b = Math.pow(a, 2);
result = String.valueOf(b);
return result;
}
// 计算后缀表达式,并返回最终结果
public String Result(String str[]) {
String Result[] = new String[100];// 顺序存储的栈,数据类型为字符串
int Top = -1;// 静态指针Top
for (int i = 0; str[i] != null; i++) {
if ("+-*%/".indexOf(str[i]) < 0) {
Top++;
Result[Top] = str[i];
}
if ("+-*%/".indexOf(str[i]) >= 0)// 遇到运算符字符,将栈顶两个元素出栈计算并将结果返回栈顶
{
double x, y, n;
x = Double.parseDouble(Result[Top]);// 顺序出栈两个数字字符串,并转换为double类型
Top--;
y = Double.parseDouble(Result[Top]);
Top--;
if ("-".indexOf(str[i]) >= 0) {
n = y - x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新⼊栈
}
if ("+".indexOf(str[i]) >= 0) {
n = y + x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新⼊栈
}
if ("*".indexOf(str[i]) >= 0) {
n = y * x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新⼊栈
}
if ("/".indexOf(str[i]) >= 0)
{
if (x == 0)// 被除数不允许为0
{
String s = "error!";
return s;
} else {
n = y / x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新⼊栈
}
}
if ("%".indexOf(str[i]) >= 0)
{
if (x == 0)// 被除数不允许为0
{
String s = "error!";
return s;
} else {
n = y % x;
Top++;
Result[Top] = String.valueOf(n);// 将运算结果重新⼊栈
}
}
}
}
return Result[Top];// 返回最终结果
}
// 主函数
public static void main(String[] args) {
Calculator a = new Calculator();
}
}