具体要求
要求:
- 能通过设计的按钮控件输入并实现简单算术运算,要求表达式在编辑框中显示,能将运算结果,输出在编辑框内显示;
- 能够实现混合运算的求解,算术表达式中包括加、减、 乘、除、括号等运算符;并且能够识别括号,优先级正确。
- 并保存历史的表达式运算记录。
技术准备:图形界面的开发;
难点:中缀表达式转换为后缀表达式,后缀表达式的计算。
目前考虑使用MFC进行界面设计展示。
MFC编程时用静态编译,这样编译出来的EXE文件可以独立于环境运行。
实现方法
方法一:利用堆栈和队列实现,先将中缀表达式转换为后缀表达式,
算法思想如下:
从左向右依次读取算术表达式的元素X,分以下情况进行不同的处理:
①如果X是操作数,直接入队
②如果X是运算符,再分以下情况:
a)如果栈为空,直接入栈。
b)如果X==”(“,直接入栈。
c)如果X==”)“,则将栈里的元素逐个出栈,并入队到后缀表达式队列中,直到第一个配对的”"("出栈。(注:“(“和”)”都不入队)
d)如果是其他操作符(±*/) ,则和栈顶元素进行比较优先级。如果栈顶元素的优先级大于等于X,则出栈并把栈中弹出的元素入队,直到栈顶元素的优先级小于X或者栈为空。弹出完这些元素后,才将遇到的操作符压入到栈中。
(3)最后将栈中剩余的操作符全部入队。
方法二:双栈算符优先级法
双栈算符优先级法为了实现表达式求值,需要设置两个栈:
·一个是运算符栈op,用于寄存运算符;
·另一个成为操作数栈Opnd,用于寄存运算数和运算结果。
算法思想如下:
求值的处理过程是自左至右扫描表达式的每一个字符:
1、当扫描到的是运算数,则将其压入栈OPND
2、当扫描到的是运算符时:
如这个运算符比OP栈顶运算符的优先级 高,则入栈;
如这个运算符比OP栈顶运算符优先级低,则从OPND栈中弹出两个运算符,从栈OP中弹出栈顶运算符进行运算,并将运算结果压入栈OPND。
3、继续处理当前字符,直到遇到结束符为止。
方法三:用二叉树实现
将后缀表达式转换为二叉树:
需要数据结构;
二叉树节点栈nodeStack,用于存放所有临时节点和最后根节点。
算法思想如下:
首先准备一个二叉树节点栈s.
①从左开始向右遍历后缀表达式的元素。
②新建一个树节点p,值为当前元素的值,如果取到的元素是操作数,直接把p入栈s,如果是运算符,从栈中弹出2个节点,把第一个弹出的节点作为p的右子树,第二个弹出的节点作为p的左子树,然后把p入栈。
当遍历完后缀表达式时,树的根节点就保存在栈里了。
我选用方法二也就是双栈来进行计算器的设计与实现
·我选用vs中的MFC工具来实现计算器的核心算法以及界面设计,设计语言为:c++,在开始之前,需要先学习一下MFC工具的使用方法,例如:如何添加一个按钮,文本框,以及按钮和文本框之间如何实现相关联系等。
·了解堆栈的作用,设计运算符与操作符的出入栈。
按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。
·计算器基本功能:
实现简单的四则运算,包括优先级判断,在出现括号时,仍可以正确判断优先级得出正确结果。同时,可以实现多个操作数与操作符同时运算。
附加功能:
①添加历史记录显示框,将每次计算之后的结果以及输入在运算框的数据进行保存与显示。
②在运算显示框中增加输入错误提示,来提醒操作者表达式输入不规范。
例如:当我们输入的左右括号数目不匹配时,它会提示括号数目不匹配,请重新输入;当我们连续输入运算符或者小数点时也会提示错误
信息。
框图展示:
设计流程图
代码实现部分
计算器的代码实现部分我主要分为三部分进行实现。
·第一部分:
主要在MFC界面下完成计算机界面的相关响应函数实现。对每个操作按钮进行具体功能实现。将界面上的数字按钮以及操作符按钮与输入显示框和历史记录显示框进行联系。
对其中具有代表性的按钮进行举例展示:
//运算符加号
void CcalculatorDlg::OnBnClickedButton16add()
{
// TODO: 在此添加控件通知处理程序代码
CString a,Str;
GetDlgItemText(IDC_EDIT1display, a);
SetDlgItemText(IDC_EDIT1display, _T(a + "+"));
Str = a;
SetDlgItemText(IDC_EDIT1history, _T(Str + "+" + "\n" ));
}
//数字1
void CcalculatorDlg::OnBnClickedButton5num1()
{
// TODO: 在此添加控件通知处理程序代码
CString a,Str;
GetDlgItemText(IDC_EDIT1display, a);
SetDlgItemText(IDC_EDIT1display, _T(a + "1"));
Str = a;
SetDlgItemText(IDC_EDIT1history, _T(Str + "1" + "\n"));
}
//清屏
void CcalculatorDlg::OnBnClickedButton1ac()
{
// TODO: 在此添加控件通知处理程序代码
SetDlgItemText(IDC_EDIT1display, _T(""));
}
//退格
void CcalculatorDlg::OnBnClickedButton2bs()
{
// TODO: 在此添加控件通知处理程序代码
CString temp, num;
GetDlgItemText(IDC_EDIT1display, num);
temp = num.Left(num.GetLength() - 1);
SetDlgItemText(IDC_EDIT1display, temp);
}
//利用此命令GetDlgItemText(IDC_EDIT1display, a)获取输入显示框的输入字符,再利用SetDlgItemText(IDC_EDIT1display, _T(a + "+"))命令将获取的字符按照想要的方式进行显示,然后利用此命令SetDlgItemText(IDC_EDIT1history, _T(Str + "+" + "\n" ))都历史记录显示框的内容进行设置
·第二部分
操作符优先级的判断,利用比较简单的switch语句进行每个运算符的优先级比较,当符合其中某一个case情况时,进行相应操作。如果均不符合,将会执行default后的相关操作。
//设置运算符的优先级
int Lv(char m)
{
switch (m)
{
case '+':
return 1; break;
case '-':
return 1; break;
case '*':
return 2; break;
case '/':
return 2; break;
case '(':
return 0; break;
case ')':
return 0; break;
case '!':
return -1; break;
default:break;
}
}
相同等级的运算符返回相同的数字,当不满足以上情况时,执行default后的语句。
·第三部分:
这一部分对表达式的检测主要利用循环语句挨个进行。根据操作符的优先级比较情况进行操作数与操作符的出入栈,然后对出栈的操作数进行相应运算,具体运算仍然利用switch语句进行加减乘除等相关操作。
//在栈顶添加元素
template<class T>
stack<T>& stack<T>::Add(const T& x)
{
Node<T>* p = new Node<T>;
p->data = x;
p->link = top;
top = p;
return *this;
}
//在栈顶删除元素
template<class T>
stack<T>& stack<T>::Delete(T& x)
{
x = top->data;
Node<T>* p = top;
top = top->link;
delete p;
return *this;
}
以上为在栈顶添加和删除元素的具体实现
switch (f)
{
case '+':t = b + a; break;
case '-':t = b - a; break;
case '*':t = b * a; break;
case '/':t = b / a; break;
case '%':
if (a<1 && a>-1)
{
//MessageBox("取整后除数为0!");
SetDlgItemText(IDC_EDIT1display, _T("取整后除数为0!"));
return;
}
t = (int)b % (int)a;
break;
default:break;
}
以上为对表达式的具体运算,将运算结果存至变量t中,在计算之前,遍历每个字符时仍然使用for循环。
界面展示
以上为关于计算器实现的全部记录。