Qt实现计算器(支持负数和小数)

计算器主要用到的知识比较简单,中缀表达式和后缀表达式,后缀表达式求值,以及栈这种数据结构,图形界面主要只用到了Qt的push button和label,计算器支持了两位数以上,小数和负数。

效果图:
Qt实现计算器(支持负数和小数)_第1张图片
源码:https://github.com/g1050/Calculator/tree/master

一、设计思路

Qt实现计算器(支持负数和小数)_第2张图片

二、什么是中缀和后缀表达式?

中缀表达式:9+(3-1)*3+10/2,这就是一个中缀表达式,顾名思义就是符号在中间的表达式,这是符合我们平时书写规范的表达式。但是对于计算机计算这种表达式就有点困难了,因为中间涉及括号和运算符号的优先级问题,因为如果计算机从左向右扫一遍的话不知道先计算哪些值,哪个和哪个值应该做何种运算,所以我们思考能不能先预处理一遍表达式,从而知道哪些值应该优先运算,所以后缀表达式应运而生。
后缀表达式:9 3 1 - 3 + 10 2 / + ,这是上式的后缀表达式。观察发现后缀表达式没有小括号,所以就解决了括号的问题,而运算符的优先级问题是通过栈这么一种结构来实现的。先不考虑是怎么转化过来的,我们先体会一下后缀表达式是怎么求值的,规则是这样的:从左到右扫描,1.如果是数字就进栈 2.如果是符号就弹出弹出栈顶两个元素(需要注意-和/要考虑两个操作符的顺序),运算后将结果在压入栈中。
Qt实现计算器(支持负数和小数)_第3张图片
以上就是后缀表达式求值的过程,下面看一下中缀怎么转为后缀表达式。

三、中缀转为后缀表达式

规则:
数字直接输出
符号----优先级+(1) -(1) *(2) /(2) ‘(’(0)

括号里表示优先级

优先级>栈顶就压栈,否则弹栈
左右括号特判
我们来举个例子:
Qt实现计算器(支持负数和小数)_第4张图片
需要注意的是最下面蓝色输出结果中,第一次输出的+是中缀表达式里面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.后面有时间可能会加乘方、三角、反三角和开方等等运算。

你可能感兴趣的:(数据结构)