8.用C/C++实现一个科学计算器———(超级详细完整,包含C/C++版本和Qt版本)

目录

1.需求分析

2.主要难点——逆波兰算法

     2.1 中缀表达式转换为后缀表达式

     2.2 后缀表达式的计算

3.编程实现

     3.1  C/C++版本

     3.2  Qt版本

4.参考资料

 


1.需求分析

编程实现一个科学计算器(类似于Windows自带的计算器),要求能够实现加减乘除混合运算,并且能够识别括号,优先级正确。

下面是本博客的Qt版本的计算器效果图

2.主要难点——逆波兰算法

     2.1 中缀表达式转换为后缀表达式

我们日常所用的数学表达式(如5+3)都是中缀表达式,中缀表达式是人容易理解的表达式。后缀表达式又叫做逆波兰表达式,对计算机来说,计算中缀表达式是很困难的,但是计算后缀表达式却非常容易,所以我们先把中缀表达式转化成后缀表达式来计算。下面的动态图和算法流程图可以很好的演示整个转换的过程:

                                                      图1   中缀表达式转后缀表达式的动态图    

    

                                                            图2   中缀表达式转后缀表达式的流程图

     2.2 后缀表达式的计算

后缀表达式的计算是比较简单的,基本思路就是遇到操作符就将操作数出栈并根据操作符进行计算,并将结果进栈,如果没有遇到操作符,就直接将操作数进栈。下图是具体的流程图,注意下面的代码使用'\0'作为表达式的终止符号(本人偷懒使用了别人的流程图)

                                                          图3   后缀表达式的计算---动态图  

                 

                                                             图4   后缀表达式的计算----流程图 

3.编程实现

     3.1  C/C++版本

在编程的时候,刚开始准备使用C语言,编程的过程中发现,在中缀转后缀表达式的时候需要一个存放字符型元素的栈,而后缀表达式的计算中又需要一个存放double型元素的栈,这样一来,就需要分别编写两个栈,很麻烦。下面的代码是直接用C++的类模板来实现的,程序比C语言简单很多,当然纯粹用C语言肯定也是没有任何问题的。

实际编程中还需要考虑正负号,因为‘+’和‘-’有时候不表示加减,为了解决这个问题,可以将负数-a看成是0-a,把正数+a看成0+a,这样一来,正负号的问题也解决了。

(1)类的声明    calculator.h

#ifndef CALCULATOR_H
#define CALCULATOR_H

enum MAXSIZE
{
    STACK_INIT_SIZE=20,//定义初始最大容量
    STACKINCREMENT=10,//栈满的时候,动态增加容量,每次增加10个元素空间
    MAXBUFFER=10,//最大缓冲区
    MAX_EXP_LEN=100//表达式最长为100
};

template<typename ElemType>
class Calculator
{
public:
    struct sqStack
    {
        ElemType *base;//指向栈顶
        ElemType *top;
        int stackSize;//当前栈的最大容量
    };
    Calculator();
    ~Calculator();
    void Push(ElemType e);
    bool Pop(ElemType &e);
    void clearStack();
    int StackLen();

    int Calculation(char Postfix[]);//后缀表达式的计算
    bool Infix2Postfix(char Infix[],char Postfix[]);//中缀表达式变为后缀表达式

private:
    sqStack s;

};

#endif // CALCULATOR_H

(2)类的实现  calculator.cpp

#include "calculator.h"
#include <stdio.h>

template<typename ElemType>
Calculator<ElemType>::Calculator()
{
    s.base=new ElemType[STACK_INIT_SIZE];//栈底指向申请空间的首地址
    if(s.base==NULL)//申请失败
        exit(0);
    s.top=s.base;//top总是指向有效元素的下一个空间(栈顶),top中没有数据
    s.stackSize=STACK_INIT_SIZE;
}

//销毁栈,将内存空间释放
template<typename ElemType>
Calculator<ElemType>::~Calculator()
{
    delete []s.base;
}

template<typename ElemType>
void Calculator<ElemType>::Push(ElemType e)
{
    if(s.top-s.base>=s.stackSize)
    {
        s.base=(ElemType *)realloc(s.base,(s.stackSize+STACKINCREMENT)*sizeof(ElemType));
        //        realloc是申请一个新的空间,并将旧的内容拷贝到新的空间,还会释放以前的空间
        if(s.base==NULL)
            exit(0);
        s.top=s.base+s.stackSize;//因为重新分配了空间,所以重新设置栈顶
        s.stackSize=s.stackSize+STACKINCREMENT;//当前栈的最大容量变大了
    }
    *(s.top)=e;
    s.top++;
}

template<typename ElemType>
bool Calculator<ElemType>::Pop(ElemType &e)
{
    if(s.top==s.base)
        return false;//空栈
    e=*(--(s.top));
    return true;
}

//清空栈,不改变物理空间
template<typename ElemType>
void Calculator<ElemType>::clearStack()
{
    s.top=s.base;
}

//计算栈的当前容量(存储的数据量或者元素个数)
template<typename ElemType>
int Calculator<ElemType>::StackLen()
{
    return s.top-s.base;
}

template<typename ElemType>
int Calculator<ElemType>::Calculation(char Postfix[])
{
    int i=0,j;
    char c;
    char str[MAXBUFFER];
    double a=0,b=0;

    for(j=0;Postfix[j]!='\0';j++)
    {
        //        c=Postfix[j];
        while ((Postfix[j]>=48)&&(Postfix[j]<=57)||Postfix[j]=='.') //输入的是数字
        {
            str[i]=Postfix[j];
//            printf("str[%d]=%c\n",i,c);
            i++;
            str[i]='\0';
            if(i>=10)
            {
                printf("出错,输入的数据长度过大!\n");
                return -1;
            }

            //            scanf("%c",&c);
            j++;
            if((Postfix[j]==' '))
            {
                //                str[i]='\0';
//                printf("str[%d]=%c\n",i,Postfix[j]);
                a=atof(str);
//                printf("%f \n",a);
                Push(a);
                i=0;
            }

        }

        switch (Postfix[j])
        {
        case '+':
            Pop(a);
            if(!Pop(b))//防止这是符号位(单目运算符)
            {
                 Push(a);
                 break;
            }
            Pop(b);
//            printf("%f+%f=%f\n",b,a,b+a);
            Push(b+a);
            break;
        case '-':
            Pop(a);
            if(!Pop(b))//
            {
                 Push(-a);
                 break;
            }
//            printf("%f-%f=%f\n",b,a,b-a);
            Push(b-a);
            break;
        case '*':
            Pop(a);
            Pop(b);
//            printf("%f*%f=%f\n",b,a,b*a);
            Push(b*a);
            break;
        case '/':
            Pop(a);
            if(a==0)
            {
                printf("除数不能为零 !\n");
                return -1;
            }
            Pop(b);
            Push(b/a);

            break;
        default:
            break;
        }
    }
    Pop(a);
    return a;

}

template<typename ElemType>
bool Calculator<ElemType>::Infix2Postfix(char Infix[],char Postfix[])
{
    Calculator<char> s;
    int i=0,j=0;
    char e;

    printf("中缀表达式为:");
    while (Infix[j]!='\0')
    {
        while(Infix[j]>='0' && Infix[j]<='9')
        {
            printf("%c",Infix[j]);
            Postfix[i++]=Infix[j];
            j++;
            if(Infix[j]<'0' || Infix[j]>'9')
            {
                Postfix[i++]=' ';
                printf(" ");
            }
        }

        switch (Infix[j])
        {

        case ')':
            s.Pop(e);
            while ('('!=e)
            {
                printf("%c ",e);
                Postfix[i++]=e;
                Postfix[i++]=' ';
                s.Pop(e);
            }
            break;

        case '+':
        case '-':
            if(0==s.StackLen())
                s.Push(Infix[j]);
            else
            {
                do
                {
                    s.Pop(e);
                    if('('==e)
                    {
                        s.Push(e);
                    }
                    else
                    {
                        printf("%c ",e);
                        Postfix[i++]=e;
                        Postfix[i++]=' ';
                    }
                }while (s.StackLen() && '('!=e);
                s.Push(Infix[j]);
            }
            break;

        case '*':
        case '/':
        case '(':
            s.Push(Infix[j]);
            break;

        case '\0':
            break;

        default:
            printf("\n输入格式错误!\n");
            return -1;
        }

        if('\0'==Infix[j])
            break;
        j++;
    }

    while (s.StackLen())
    {
        s.Pop(e);
        printf("%c ",e);
        Postfix[i++]=e;
        Postfix[i++]=' ';
    }
    Postfix[i]='\0';
    printf("\n");
    return true;
}

(3)测试程序 main.cpp

#include <iostream>
#include "calculator.cpp"
#include <stdio.h>
using namespace std;

int main()
{
    Calculator<double> cal;
    char Infix[MAX_EXP_LEN],Postfix[MAX_EXP_LEN];
    gets(Infix);
    double sum;

    cal.Infix2Postfix(Infix,Postfix);
    sum=cal.Calculation(Postfix);
    printf("最终计算结果为:%f\n\n",sum);
    return 0;
}

 (4)测试结果

下面计算8+(6-3)*(-5)+10/2:

     3.2  Qt版本

Qt实现的科学计算器有较友好的界面,效果图在本文的开头。

Qt的编程比较复杂一些,在这个程序里面不仅添加了界面,而且还增加了清除数据,后退一个数字等功能,用了四个文件来编写程序,基波思路跟上面的C++类似,由于程序太长,这里就不放代码了。我已经将完整的源代码上传,又需要的小伙伴可以前去下载(点击进入下载界面),没有积分的小伙伴可以私聊我。

4.参考资料

[1] 流程图来自https://blog.csdn.net/hackerain/article/details/7682891?locationNum=15

[2]中缀表达式转后缀表达式的动态图来自https://www.cnblogs.com/lulipro/p/7450886.html

猜你喜欢

转载自blog.csdn.net/wanzhen4330/article/details/81952851