目录
一、实验内容
1、学习图形界面的设计,利用 MFC 应用程序(Java swing 或 QT 框架,或 C#)创建基于对话框的应用程序,添加按钮、编辑框等控件。
2、能通过设计的按钮控件输入并实现简单算术运算,要求表达式在编辑框中显示,能将运算结果,输出在编辑框内显示;并保存历史的表达式运算记录。
3、能够正确实现算术表达式求解,是指算术表达式中包括加、减、乘、除、括号等运算符,能求解包含括号的四则混合运算,并且能够识别括号,优先级正确。
4、实现三角函数的运算、对数运算、指数运算、进制转换等。
二、项目目标
1、能通过设计的按钮控件输入并实现简单算术运算,要求表达式在编辑框中显示,能将运算结 果,输出在编辑框内显示
2、能够实现混合运算的求解,算术表达式中包括加、减、乘、除、括号等运算符;并且能够识别括号,优先级正确。
3、能够保存历史的表达式运算记录以及三角函数的运算、对数运算、指数运算、进制转换等科学计数。
三、编程语言和开发环境
本次开发采用C++与QT结合的形式
四、实验步骤
首先用QT建立一个如下图所示的UI界面,我是想做一个如图所示的科学计算器,其中下面的白色框input存储的是数字或者字,上面的白色框formula存储的是中缀表达式,按下等于号后则由中缀表达式转为后缀表达式。
其次是在代码里运行各个按键的属性,考虑到分母不为0,后括号之前不能没有前括号的情况以及若是四个0则直接清楚只留下一个0,考虑各个部分的优先级。
最后是界面跳转、历史记录特效等的整合。
五、实验核心
整个项目的核心点在于如何用双栈法求解中缀表达式转后缀表达式。
中缀表达式是一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间(例:a+b),中缀表达式是传统表达式也是人们常用的算术表示方法。而后缀表达式又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)(例:ab+)。
中缀转后缀规则:利用双栈法,让操作符在一个栈,操作数在另一个栈。若后面一个符号比前面一个符号的优先级高,则后面一个符号入栈,若后面一个符号比前面一个符号的优先级低,则前面一个符号出栈。
算法思路:建立一个栈S。从左到右读表达式,如果读到操作数就将它压入栈S中,如果读到n元运算符(即需要参数个数为n的运算符)则取出由栈顶向下的n项按操作数运算,再将运算的结果代替原栈顶的n项,压入栈S中。如果后缀表达式未读完,则重复上面过程,最后输出栈顶的数值则为结束。
典型案例
后缀表达式:abcd/-e*+
- 扫描操作数abcd,并依次入栈
- 扫描到运算符 / ,d出栈,c出栈,计算c/d,再重新压入栈
- 扫描到运算符 - ,c/d出栈,b出栈,计算b-c/d,再重新压入栈
4、扫描操作数e,入栈
5、扫描到运算符 * ,e出栈,b-c/d 出栈,计算 (b-c/d)*e,再重新压入栈
6、扫描到运算符 + ,(b-c/d)*e 出栈,a出栈,计算 a+(b-c/d)*e ,再重新压入栈
此时栈顶的值就是后缀表达式abcd/-e*+的值。
分离操作数和数代码
QQueue<QString> myCal::getMid(const QString& s) //分离操作数和运算符
{
QQueue<QString> Q; //分离后的操作数与运算符队列
QString val=""; //操作数
for(int i=0;i<s.length();i++)
{
if( (s[i]=='.') || ( (s[i]>='0') && (s[i]<='9') )) //判断小数点和数字
val += s[i];
else if(s[i]== '(' || s[i]== ')' || s[i]== '*' || s[i]== '/' || s[i] == '^' || s[i] == '%' )
{
if(!val.isEmpty()){
Q.enqueue(val); //加入数字
val.clear(); //清空数字,等待下一个操作数
}
Q.enqueue(s[i]); //加入运算符
}
else if(s[i]== '+' || s[i]== '-') //+,-号有两种情况,运算符或者正负号
{
if(i==0) val+= s[i]; //表达式第一个字符则是正负号
else if(s[i-1]=='(' || s[i-1]=='+' || s[i-1]=='-' || s[i-1]=='*' || s[i-1]=='/'|| s[i-1] == '^' || s[i-1] == '%')
val+= s[i]; //括号或者运算符开头也是正负号
else //其他情况则是加减运算符
{
if(!val.isEmpty())
{
Q.enqueue(val); //加入数字
val.clear(); //清空数字
}
Q.enqueue(s[i]); //加入运算符
}
}
}
if(!val.isEmpty()) //遍历完成,加入最后一个操作数
{
Q.enqueue(val);
val.clear();
}
return Q;
}
中缀转后缀代码
QQueue<QString> myCal::getPost(QQueue<QString>& s) //将中缀队列转换为后缀队列
{
QStack<QString> st;
QQueue<QString> Q;
bool cheak;
QString fuhao;
while(!s.isEmpty())
{
fuhao = s.dequeue(); //取出一个字符
fuhao.toDouble(&cheak); //转换为字符串
if(cheak==true) //字符为数字
st.push(fuhao);
else if(fuhao=="+"||fuhao=="-") //加减运算符
{
while(!st.isEmpty() &&(st.top()!="(")) //直到栈空或遇到左括号
Q.enqueue(st.pop()); //由于优先级,先将其他运算符加入队列
st.push(fuhao); //此次运算符入栈
}
else if(fuhao=="*"||fuhao=="/"|| fuhao == "^" || fuhao == "%")
{
while(!st.isEmpty() && (st.top()!="(") && (st.top()!="+") && (st.top()!="-"))
Q.enqueue(st.pop());//之前的同优先级运算符入队列
st.push(fuhao); //此次运算符入栈
}
else if(fuhao == "(") //左括号入栈
st.push(fuhao);
else if(fuhao ==")") //右括号
{
while(!st.isEmpty() && (st.top()!="("))
Q.enqueue(st.pop());//括号里运算符入队列
if(st.top()=="(") //去掉左括号
st.pop();
}
}
while(!st.isEmpty()&& (st.top()!="("))//遍历完成,加入最后一个运算符
Q.enqueue(st.pop());
return Q;
}
后缀表达式求值
QString myCal::calExp(QQueue<QString>& s)//计算后缀表达式
{
QStack<QString> st;
QString fuhao,l,r,result;
bool check;
while(!s.isEmpty())
{
fuhao = s.dequeue(); //取出一个字符串
fuhao.toDouble(&check);
if(check==true) //数字则入栈等待计算
st.push(fuhao);
else //运算符则进行计算
{
if(st.size()<2) return "wrong";//操作数不足两个
r= st.pop(); //取出两个数字进行计算
l= st.pop();
result = calTwoOper(l,fuhao,r );//得到计算结果
if(result==NULL) return result; //除数为零
st.push(result); //加入计算结果,等待后续计算
}
}
if(st.size()==1) //计算完成,结果唯一
return resultCorrect(st.pop());
else return "wrong";
}