1. 程式人生 > >計算表示式的值c++逆波蘭式實現方法

計算表示式的值c++逆波蘭式實現方法

#include<stack>//棧容器的標頭檔案
#include<iostream>
#include<math.h> //數學 標頭檔案  次方函式
using namespace std;

int Precedence(char op)//運算子優先順序判斷
{
	switch(op)
	{
	case '+':
	case '-':
		return 1; //定義加減運算的優先順序為1
	case '*':
	case '/':
		return 2; //定義乘除運算的優先順序為2
	case '!':
	case '^':
		return 3;
	case '(':
	case '\0':
	default:
		return 0; //定義在棧中的左括號和棧底字元的優先順序為0
	}
}
void stringJudge(char *s)//非法字元判斷  其實 這樣寫 有點蠢 Java已經沒有這樣弄了
{
	int i=0;
	while(s[i]!=0)
	{
		switch(s[i])
		{
		case'+':
		case'-':
		case'*':
		case'/':
		case'0':
		case'1':
		case'2':
		case'3':
		case'4':
		case'5':
		case'6':
		case'7':
		case'8':
		case'9':
		case'.':
		case'(':
		case')':
		case'!':
		case'^':
			break;
		default:
			throw 3;
		}
		i++;
	}
	return;
}
void tonibolan(char *s1,char *s2)//中綴式轉換為逆波蘭式
{
	stack<char> s;//開一個字元棧
	s.push('\0');//棧底字元
	int i=0,j=0;//i記錄s1,j記錄s2
	char ch=s1[i]; //取首個
	if(ch==0)//判斷字串是否是空的
		throw 2;
	if(ch=='.')//判斷首字元是否為小數點
		throw 6;
	if(ch=='-')//負號首位補零
	{
		s2[j++]='0';  //就是在輸出字串中直接加了個0   注意 沒有 讓ch讀下個字元 那個符號還是要處理的
		s2[j++]=' ';  //空格為了隔開 數與數
	}
	stringJudge(s1); //判斷 非法字元
	while(ch!='\0')  //棧底字元 over
	{
		if(ch==' ') //空格 就 忽略
			ch=s1[++i]; //讀下一個
		else if(ch=='(') //左括號壓入棧中
		{
			s.push('('); //壓棧
			ch=s1[++i];
			if(ch=='-')//負號首位補零
			{
				s2[j++]='0';
				s2[j++]=' ';
			}
		}
		else if(ch==')') //右括號 把左括號之間的全部出來 依次 寫入 (這個地方改的 優先順序)
		{
			while(s.top()!='(') //迴圈至 左括號處  其實 有個bug  如果沒有 左括號的話   自己思考了
			{
				s2[j++]=s.top();  //出棧
				s.pop(); //pop()是刪除性出棧
			}
			s.pop(); //最後 刪除 這個 括號
			ch=s1[++i];
		}

		else if(ch=='+'||ch=='-'||ch=='*'||ch=='/'|| ch=='!' || ch=='^') //是運算子的話
		{
			char w=s.top();  //出第一個
			while(Precedence(w)>=Precedence(ch)) //比較優先順序
			{
				s2[j++]=w;  //棧裡的大的話 棧那個寫入
				s.pop(); //繼續出棧   這是一個迴圈
				w=s.top();
			}
			s.push(ch);  //壓入此字元
			ch=s1[++i];
			if(ch=='+'||ch=='-'||ch=='*'||ch=='/'|| ch=='!' || ch=='^') //這個表示 符號後面又是 符號 丟擲異常 Java中那個 廢棄了這個異常 有另一種方式判斷
				throw 4;
		}
		else
		{
			while((ch>='0' && ch<='9') || ch=='.') //是數字或者小數點
			{
				s2[j++]=ch;   //寫入輸出字串(字尾式)
				ch=s1[++i];
			}
			s2[j++]=' ';   //空格 隔開 數
		}
	}
	ch=s.top();
	s.pop();

	while(ch!='\0')  //將所有的依次取出 寫入 字尾式  '\0'是棧底字元
	{
		if(ch=='(')  //說明有多的( 拋異常
			throw 1;
		else
		{
			s2[j++]=ch;    //寫入
			ch=s.top();
			s.pop();
		}
	}
	s2[j++]='\0';  //加上 尾巴 字串尾
}
double nibolan(char *s) //這個函式 是 用來 運算 字尾式的
{
	stack<double> sd;  //double棧
	int i=0;  //迴圈變數 控制位置
	double num,num2; //臨時的運算的兩個數
	double n1,n2,n3; //同上
	while(s[i]!='\0') //迴圈 至尾
	{
		num=0;
		num2=1;
		if(s[i]>='0' && s[i]<='9') //如果是數字 整個if是 String 換為 double的作用 C++中自己寫 悲劇啊
		{
			while(s[i]>='0' && s[i]<='9')
			{ // 就是 乘10+新的數字
				num*=10;
				num+=s[i]-'0';
				i++;
			}
			if(s[i]=='.') //中間的小數點 只能有一個
			{
				i++;
				while(s[i]>='0' && s[i]<='9')
				{  //就是除10+新的
					num2/=10;
					num+=num2*(s[i]-'0');
					i++;
				}
			}

			if(s[i]=='.') //說明小數點多了 拋異常
				throw 6;
			sd.push(num);  //將 形成double壓入棧
		}
		else if(s[i]==' ')  //忽略 空格 分隔符
			i++;
		else if(s[i]=='+')  //以下都是一個意思
		{ //遇運算子 出棧 一般是兩個數 然後 運算 結果再 壓棧
			n1=sd.top();  //出棧
			sd.pop();  //刪除
			n2=sd.top(); //再出一個
			sd.pop(); //再刪除  其實c++的這兩個函式 很麻煩 Java的一個函式 即出也刪
			n3=n2+n1;  //運算
			sd.push(n3); //結果壓棧
			i++; //下一個 其實可以再分支的外面寫的  這樣不用 每個都寫 當時沒有注意到
		}
		else if(s[i]=='-') //減法
		{
			n1=sd.top();
			sd.pop();
			n2=sd.top();
			sd.pop();
			n3=n2-n1;  //注意是哪個減哪個
			sd.push(n3);
			i++;
		}
		else if(s[i]=='*')
		{
			n1=sd.top();
			sd.pop();
			n2=sd.top();
			sd.pop();
			n3=n2*n1;
			sd.push(n3);
			i++;
		}
		else if(s[i]=='/')
		{
			n1=sd.top();
			sd.pop();
			n2=sd.top();
			sd.pop();
			if(n1==0)  //除數為零 拋異常
				throw 5;
			n3=n2/n1;  //也注意是那個 除哪個
			sd.push(n3);
			i++;
		}
		else if(s[i]=='!')  //階乘 演算法很弱智
		{ //階乘是隻出一個
			n1=sd.top();
			sd.pop();
			n3=1;
			if(n1==0) //0!=1
				;
			else if(n1!=(unsigned int)n1) //double強制轉換無符號int,再與原數比相等  Java都自帶的方法 當年還有自己想
				throw 7; //就是 判讀 階乘前的數 是否為 正整數
			else
			{
				for(int jj=(int)n1;n1>0;n1--) //迴圈 一次減1 就是 階乘的人工 演算法
				{
					n3*=n1;  //資料大了 會超 double 很容易的 50!就會了 估計的
				}
			}
			sd.push(n3);
			i++;
		}
		else if(s[i]=='^')  //次方 用的math.h裡面的函式
		{
			n1=sd.top();
			sd.pop();
			n2=sd.top();
			sd.pop();
			if(n2<0 && n1!=(int)n1)  //這個是次方的數學要求 自己思考了 c++的次方 也要整數 悲劇
				throw 8;
			n3=pow(n2,n1);
			sd.push(n3);
			i++;
		}
	}
	if(sd.size()!=1)  //如果剩下的元素 大於1個 就說明 數 多了三 於是 拋異常了
		throw 4;
	return sd.top(); //最後的值 為 答案
}

int main()
{
	char s1[100]; //最大長度是 固定的 你也可  自己看著辦
	char s2[100];
	cout<<"支援的符號(半形模式):0123456789 ( ) .(小數點) + - * / !(階乘) ^(次方)"<<endl; //這個裡面你可以溫馨一下
	cout<<endl;
	while(1)
	{
		cout<<"請輸入計算式:"; //需要溫馨一下的地方
		gets(s1);  //字串形式 輸入
		try
		{
			tonibolan(s1,s2);  //轉字尾式
			cout<<nibolan(s2)<<endl;  //計算
			cout<<endl; //打醬油
		}
		catch(int key)//錯誤處理……
		{ //當年用了 比較蠢的方式 其實可以直接拋字串 不用 在這裡寫了
			cout<<endl;
			if(key==1)
				cout<<"輸入的計算式括號有誤,請重新輸入"<<endl;
			else if(key==2)
				cout<<"輸入的計算式是空的,請重新輸入"<<endl;
			else if(key==3)
				cout<<"輸入的式子含有非法字元,請重新輸入"<<endl;
			else if(key==4)
				cout<<"輸入的式子格式不正確,請重新輸入"<<endl;
			else if(key==5)
				cout<<"0不能作除數,請檢查式子"<<endl;
			else if(key==6)
				cout<<"小數點出現錯誤,請檢查式子"<<endl;
			else if(key==7)
				cout<<"階乘計算的內容只能為正整數,請檢查式子"<<endl;
			else if(key==8)
				cout<<"負數的小數次方無意義,請檢查式子"<<endl;
			cout<<endl;
		}
	}
	system("pause");
	return 0;
}