目錄
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