计算器主要用到的知识比较简单,中缀表达式和后缀表达式,后缀表达式求值,以及栈这种数据结构,图形界面主要只用到了Qt的push button和label,计算器支持了两位数以上,小数和负数。
效果图:
源码:https://github.com/g1050/Calculator/tree/master
一、设计思路
二、什么是中缀和后缀表达式?
中缀表达式:9+(3-1)*3+10/2,这就是一个中缀表达式,顾名思义就是符号在中间的表达式,这是符合我们平时书写规范的表达式。但是对于计算机计算这种表达式就有点困难了,因为中间涉及括号和运算符号的优先级问题,因为如果计算机从左向右扫一遍的话不知道先计算哪些值,哪个和哪个值应该做何种运算,所以我们思考能不能先预处理一遍表达式,从而知道哪些值应该优先运算,所以后缀表达式应运而生。
后缀表达式:9 3 1 - 3 + 10 2 / + ,这是上式的后缀表达式。观察发现后缀表达式没有小括号,所以就解决了括号的问题,而运算符的优先级问题是通过栈这么一种结构来实现的。先不考虑是怎么转化过来的,我们先体会一下后缀表达式是怎么求值的,规则是这样的:从左到右扫描,1.如果是数字就进栈 2.如果是符号就弹出弹出栈顶两个元素(需要注意-和/要考虑两个操作符的顺序),运算后将结果在压入栈中。
以上就是后缀表达式求值的过程,下面看一下中缀怎么转为后缀表达式。
三、中缀转为后缀表达式
规则:
数字直接输出
符号----优先级+(1) -(1) *(2) /(2) ‘(’(0)
括号里表示优先级
优先级>栈顶就压栈,否则弹栈
左右括号特判
我们来举个例子:
需要注意的是最下面蓝色输出结果中,第一次输出的+是中缀表达式里面9后面的+,随后又将3后面的+入栈。
四、优点
这样先转后缀再对后缀求值的优点:
1.后缀表达式求值规则比较简单,中缀也不是不可以直接求值,两个栈一个操作数栈一个操作符栈,也可以解决,但是觉得后缀比较简单。
2.后缀表达式解决了括号和符号优先级问题。
3.中缀转后缀也不是很复杂。
五、多位数
目前程序存在的一个问题就是,多位数解析会出问题,比如:
9+(3-1)*3+10/2转后缀是9 3 1 - 3 + 10 2 / +,我们没办法区分是10十2二还是102一百零二,我的做法是每个数字解析后加一个#,所以结果就是这样的,9#3# 1# - 3# + 10# 2# / +
,这样每个完整数字后必跟一个#解决了解析出现多位数字粘在一起的情况。
六、负数
通过观察发现负数只有两种情况(可能考虑不周,还有遗漏的),一种类似-5+4…这种直接在首位是符号,另外一种是5+(-4)这样带括号的,我的处理方式是通过补一个0将单目负号变成双目减号,0-5+4… 和 5+(0-4),这样就和之前的处理一样了,应该还有更好的处理方式,欢迎补充!
七、小数
9+(3-1)3.141+10/2,小数问题也不难解决,比如这个3.141我们只要解析时判断数字后面是否接一个小数点,如果有,就把小数点后面按照3+110^(-1)+410 ^(-2)+110 ^(-3)这样解析就可以了,代码体现如下:
double cal::getValue()
{
int size = v.size();
stack <double> st;
for(int i = 0;i<size;i++){
if(isalnum(v[i])){
int cnt = -1;//标记是否有小数点后面几位
double res = v[i]-48;
while(i+1 < size && isalnum(v[i+1])){//处理两位数及两位数以上 后面必接一个#
res *= 10;
res += (v[++i]-48);
}
if(i+1 < size && v[i+1] == '.'){
i++;//跳过小数点开始扫描后面的数字
while(i+1 < size && isalnum(v[i+1])){
res += (v[++i]-48)*pow(10,cnt--);
}
}
/* cout << "element = " << res << endl; */
st.push(res);
i++;//跳过#
}else{
if(v[i] == '+'){
double top1 = st.top();
st.pop();
double top2 = st.top();
st.pop();
double res = top1 + top2;
/* cout << res << endl; */
st.push(res);
}else if(v[i] == '-'){
double top1 = st.top();
st.pop();
double top2 = st.top();
st.pop();
double res = top2 - top1;
st.push(res);
/* cout << res << endl; */
}
else if(v[i] == '*'){
double top1 = st.top();
st.pop();
double top2 = st.top();
st.pop();
double res = top1 * top2;
st.push(res);
/* cout << res << endl; */
}else if(v[i] == '/'){
double top1 = st.top();
st.pop();
double top2 = st.top();
st.pop();
/* cout << top1 << " " << top2 << endl; */
double res = top2 / top1;
st.push(res);
/* cout << res << endl; */
}
}
}
return st.top();
}
八、小结
1.我在这个程序中是自己仿照STL中的stack自己写了个stack类模板,也写了一些queue和stack之类的模板,主要是练手,用的时候直接包含自己写的stack.hpp,之所以写hpp文件是因为类模板在QT中.h .cpp 分开写编译不通过。
2.中缀转后缀表达式
3.后缀表达式求值
4.关于图形界面,其实前后端分离度高的话就这个计算器而言很容易实现,写一个类用来处理用户输入的字符串,提供合法性检测、转后缀,后缀求值等等方法,用Qt Designer画个界面,直接调用我们之前写好的类中的方法就可以了。
5.后面有时间可能会加乘方、三角、反三角和开方等等运算。