短学期本来已逃脱计算器的厄运,因为一直纠结不清我计算器的架构,不知如何下手;但学了MFC的底层实现机制和框架后发现计算器还是比较好实现的,于是总结下做计算器的架构和关键算法。
先声明:数据类型是CString,并用vector进行操作,算法为逆波兰。
我用MFC做的计算器架构比较简单,界面上除括号外的所有按钮都是直接在编辑框中显示,成为一个算术表达式,“=”作为一个响应函数,建立CMyCalculator的实例,再把算术表达式作为参数调用计算函数,得到结果更新显示在界面上。
void CTestDlg::OnBUTTONEqual() //"="的响应函数
{
CMyCalculator myCalculator(m_sTest);
if(myCalculator.IsExpression()) //检查式子的有效性
{
myCalculator.Change(); //算术表达式->逆波兰表达式
m_sTest.Format("%lf",myCalculator.Calculate()); //计算逆波兰表达式得到结果
UpdateData(false);
}
else
{
MessageBox("Wrong Expresion! please input again!");
}
m_sTest="";
}
封装计算功能的CMyCalculator类架构是
class CMyCalculator
{
public:
CMyCalculator(CString m_stest); //传入算术表达式
~CMyCalculator(); //析构释放vector占用的内存
double Calculate(); //计算后缀表达式(逆波兰表达式)
void Change(); //算数表达式转换为后缀表达式
bool IsExpression(); //判断算术表达式是否正确
bool IsOprand(char ); //判断运算符
bool CheckBracket(); //判断括号的正确性
int GetPriority(CString); //获得运算符的优先级
bool IsPriority(CString,CString); //判断运算符之间的优先级大小
private:
CString m_string; //算术表达式
vector <CString> m_vOperator; //运算符栈,压入运算符(包括括号)
vector <CString> m_vOprand; //操作数栈,压入操作数与运算符(不包括括号)
vector <double> m_vResult; //计算后缀表达式
};
接下来是计算器的核心步骤,逆波兰算法的实现和计算:
算术表达式->逆波兰表达式
//1.将运算符(含括号)压入m_vOperator,把操作数(含运算符)压入m_vOprand;
//2.遇'('直接压入运算符栈,遇')'把中间的运算符弹出到操作数栈;
//3.当前元素<栈顶元素时,栈顶元素的运算符压入运算符栈,当前元素再入操作数栈。
void CMyCalculator::Change()
{
m_vOperator.push_back("#");
for(int i=0;i<m_string.GetLength();i++)
{
//若为运算符或括号
if(IsOprand(m_string.GetAt(i))||m_string.GetAt(i)=='('||m_string.GetAt(i)==')')
{
// 若为 '('
if(m_string.GetAt(i)=='(')
{
m_vOperator.push_back(m_string.GetAt(i));
}
//若为')'
if(m_string.GetAt(i)==')')
{
while(m_vOperator.back()!='(')
{
m_vOprand.push_back(m_vOperator.back());
m_vOperator.pop_back();
}
m_vOperator.pop_back();
}
//若为运算符
if(m_string.GetAt(i)!='('&&m_string.GetAt(i)!=')')
{
if(IsPriority(m_string.GetAt(i),m_vOperator.back())) //若当前元素优先级>栈顶元素则入栈
{
m_vOperator.push_back(m_string.GetAt(i));
}
else //若当前元素优先级<=栈顶元素
{
m_vOprand.push_back(m_vOperator.back()); //栈顶元素压入操作数栈
m_vOperator.pop_back(); //删除栈顶元素
m_vOperator.push_back(m_string.GetAt(i)); //当前元素压入运算符栈
}
}
}
//若为数字
else
{
CString str;
while((m_string.GetAt(i)>=48&&m_string.GetAt(i<=57||m_string.GetAt(i)=='.')
&&i<m_string.GetLength()-1) //当为数字或小数点时累加成一个数字的字符串再压入操作数栈
{
str+=m_string.GetAt(i);
i++;
}
if(i==m_string.GetLength()-1) // 防止访问下表越界,而专为最后一位进行判断
{
str+=m_string.GetAt(i);
i++;
}
m_vOprand.push_back(str);
i--; //for循环中会i++,所以这里得先减1
}
}
//将运算符栈中剩余的运算符压入操作数栈
while(m_vOperator.back()!="#")
{
m_vOprand.push_back(m_vOperator.back());
m_vOperator.pop_back();
}
}
计算逆波兰表达式(后缀表达式)
//将操作数栈中的后缀表达式依次取出,从左至右扫描
//若遇操作数压入m_vResult栈中
//若遇运算符(只含计算的运算符),则把栈顶俩元素弹出进行运算,把结果压入栈中
//最后在栈中保存的就只有一个元素的栈,把m_vResult[0]返回即可
注意:我的计算器是用vector来操作,入栈,出栈只是形象化的比喻,还有vector的内存需要析构,不然会不断耗尽内存的,其次不能简单的析构,需要调用swap来把vector占用的内存给交换出来再析构,vector的clear只是清除数据,不能释放内存。