使用棧解決表示式求值(C語言)及問題總結
一、理論知識
表示式=(運算元)+(運算子)+(運算元)設 Exp = S1+OP+S2則稱OP+S1+S2為字首表示法S1+OP+S2為中綴表示法 S1+S2+OP為字尾表示法
例如:Exp = a x b + (c – d / e) x f,其字首式:+ xa b x – c / d e f;中綴式:a x b +c – d / e x f; 字尾式:a b x c d e / - f x+
特點:
1) 失去了括號,運算元之間的相對次序不變;
2) 運算子的相對次序不同;(中綴沒變)
3) 中綴式丟失了括弧資訊,致使運算的次序不確定。
字首式的運算規則特點:
1) 連續出現的兩個運算元和在它們之前且緊靠它們的運算子構成一個最小表示式;
2) 字首式唯一確定了運算順序。
字尾式的運算規則特點:
1) 運算子在式中出現的順序恰為表示式的運算順序;
2) 每個運算子和在它之前出現且緊靠它的兩個運算元構成一個最小表示式;
3) 先找運算子,再找運算元;
4) 運算元的順序不變。
以後綴式為例,字尾式計算方法:
1) 每個運算子的運算次序要由它之後的一個運算子來定;
2) 優先順序高的運算子領先於優先順序低的運算子。
從原表示式求得字尾式的規律
1) 設立運算子棧;
2) 設表示式的結束符為“#”,預設運算子棧的棧底為“#”;
3) 若當前字元是運算元,則直接傳送給字尾式;
4) 若當前運算子的優先順序高於棧頂運算子,則進棧;
5) 否則,退出棧頂運算子傳送給字尾式;
6) “(”對它之前後的運算子起隔離作用,“)”可視為自相應左括弧開始的表示式的結束符。
演算法求解過程:
1) 設定兩個棧,一個存運算元,棧名為OPND,一個存運算子,棧名為OPTR棧。
2) 首先置運算元棧為空,表示式起始符#為運算子棧的棧底元素;
3) 依次讀入表示式中每個字元,若是運算元則進OPND棧,若是運算子則和OPTR棧的棧頂運算子比較優先權;
4) 若棧頂運算子小於輸入運算子,輸入運算子進棧;
5) 若棧頂運算子等於輸入運算子(只有棧頂是“(”,輸入是“)”,或者棧頂是“#”,輸入是“#”兩種情況),分別去除一對括號或結束;
6) 若棧頂運算子大於輸入運算子,彈出棧頂運算子,從OPND中彈出兩個運算元與彈出運算子計算後再存入OPND棧,繼續第3步,直到表示式操作結束。
二、程式碼實現
對輸入表示式的要求:
1) 可以有空格,可以是多位數但是數字與數字之間不能有空格;
2) 只能輸入數字和 + - */ ( ) 運算子,括號要是英文的,否則會報錯;
3) 可以計算負數型別,但是輸入表示式中的負數前面只能是 + 運算子或作為表示式開頭,如- 5 + - 2 + -3或-5 + (-2) + (-3),2 + -5 * 3 – 6,負數前後最好不要加括號,2 + (-5) *3 –6結果為15,就出錯了。
程式碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h> // 申請記憶體
#include <ctype.h> // 內含isdigit()函式
#include <assert.h> // 斷言函式
#include <string.h> // 內含字串處理函式
#define STACK_INIT_SIZE 100 // 棧容量
#define STACK_INCREMENT 10 // 棧增量
typedef float DATATYPE;
typedef char SYMBOLTYPE;
typedef struct stack
{
int *base; // 基地址
int *top; // 棧頂指標
int stackSize; // 棧容量
}*Stack;
// 棧的初始化
Stack Init_Stack(Stack S)
{
S=(Stack)malloc(sizeof(Stack));
if(!S)
exit(0);
S->base = (int*)malloc(STACK_INIT_SIZE*sizeof(DATATYPE));
if(!S->base)
exit(0);
S->top = S->base;
S->stackSize = STACK_INIT_SIZE;
return S;
}
// 判棧空
int IsEmpty(Stack S)
{
if (S->top == S->base)
{
return 1;
} else
{
return 0;
}
}
// 判棧滿
int IsFull(Stack S)
{
if (S->top - S->base == S->stackSize)
{
return 1;
} else
{
return 0;
}
}
// 運算元壓棧
void Push(Stack S, DATATYPE e)
{
assert(S);
if (IsFull(S))
{
S->base = (int*)malloc((STACK_INIT_SIZE+STACK_INCREMENT)*sizeof(DATATYPE));
if (!S->base)
exit(0); // 儲存分配失敗
S->top = S->base + S->stackSize;
S->stackSize += STACK_INCREMENT;
}
*S->top++ = e;
}
// 運算子壓棧
void PushSymbol(Stack S, SYMBOLTYPE e)
{
assert(S);
if (IsFull(S))
{
S->base = (int*)malloc((STACK_INIT_SIZE+STACK_INCREMENT)*sizeof(DATATYPE));
if (!S->base)
exit(0); // 儲存分配失敗
S->top = S->base + S->stackSize;
S->stackSize += STACK_INCREMENT;
}
*S->top++ = e;
}
// 運算元彈棧
DATATYPE Pop(Stack S)
{
assert(S);
if (S->top == S->base)
return 0; // 空棧彈出0保證部分負數的正確運算
else
{
return *--S->top; // *--S->top就是*(--S->top)
}
}
// 運算子彈棧
SYMBOLTYPE PopSymbol(Stack S)
{
assert(S);
if (S->top == S->base)
return 0;
else
{
return *--S->top;
}
}
// 棧的銷燬
void DestroyStack(Stack S) {
free(S->base);
free(S);
}
// 運算子優先順序表
char Priority[7][7] =
{ // '+' '-' '*' '/' '(' ')' '#' 行row(左邊的)是棧頂運算子,列col(上邊的)是入棧運算子
{/*'+'*/'>','>','<','<','<','>','>'},
{/*'-'*/'>','>','<','<','<','>','>'},
{/*'*'*/'>','>','>','>','<','>','>'},
{/*'/'*/'>','>','>','>','<','>','>'},
{/*'('*/'<','<','<','<','<','=','0'},
{/*')'*/'>','>','>','>','0','>','>'},
{/*'#'*/'<','<','<','<','<','0','='}
};
// 確定運算子所在的行數或列數
int Operator(char c)
{
switch(c)
{
case '+': return 0;
case '-': return 1;
case '*': return 2;
case '/': return 3;
case '(': return 4;
case ')': return 5;
case '#': return 6;
default: return -1;
}
}
// 計算彈出的兩個運算元與彈出棧頂運算子的值
float Calculation(float a, char op, float b)
{
switch(op)
{
case '+': return a+b;
case '-': return a-b;
case '*': return a*b;
case '/': return a/b;
default: return -1;
}
}
// 表示式求值函式
float CalculatingExpression(char *s)
{
int i;
strcat(s, "#"); // 為表示式s串接"#"
Stack OPND=NULL;
OPND = Init_Stack(OPND); // 建立運算元棧
Stack OPTR=NULL;
OPTR = Init_Stack(OPTR); // 建立運算子棧
PushSymbol(OPTR, '#'); //"#"壓棧作為運算子棧的棧底元素
for (i=0; i<strlen(s); ++i)
{
while(s[i]==' ') // while迴圈跳過空格
++i;
if (isdigit(s[i])) // 判斷是否是數字
{
int j=i;
++i;
while(isdigit(s[i])) // 確定是幾位數
{
++i;
}
if (!isdigit(s[i])) // 將while迴圈裡因判斷是否是數字多加的i值減去
--i;
char str[10]="";
for (; j<=i; ++j) // 將字串陣列下標j到i的數字字元轉換為字串
{
char c[2] = {s[j]};
strcat(str, c);
}
float operand = atof(str); // 將字串轉換為浮點數
Push(OPND, operand); // 浮點數壓入運算元棧
}
else {
int row = Operator(*(OPTR->top-1)), col = Operator(s[i]); // 確定棧頂運算子的行數,入棧運算子的列數
switch(Priority[row][col]) // 確定優先順序
{
case '<': PushSymbol(OPTR, s[i]); break;
case '>': Push(OPND, Calculation(Pop(OPND), PopSymbol(OPTR), Pop(OPND))); --i; break;
//Push()引數裡右邊的Pop先執行;--i是為了下次繼續對當前入棧運算子s[i]進行判斷
case '=': PopSymbol(OPTR); break;
default: printf("輸入錯誤,請檢查數字之間是否有空格,表示式是否正確!\n");
DestroyStack(OPTR);
DestroyStack(OPND);
return -4294967296; // 執行到這一步,說明表示式錯誤,直接返回呼叫函式(主函式)
}
}
}
DestroyStack(OPTR);
return Pop(OPND); // 執行到這一步,說明表示式正確,彈出運算元棧的值即為運算結果
}
int main()
{
char s[100];
printf("請輸入要計算的表示式:\n");
gets(s);
printf("表示式 %s 的值為:\n", s);
printf("%1.2f", CalculatingExpression(s));
return 0;
}
執行結果:
3
三、程式實現過程當中遇到的問題總結:
1) 從鍵盤獲取一段字串,gets(s)函式與scanf("%s",字元陣列名或指標)相似,但不完全相同,使用scanf("%s, s)函式輸入字串時如果遇到空格會認為字串結束,空格後的字元將作為下一個輸入項處理,而gets()函式將接收輸入的整個字串直到遇到換行為止。相同點是都會自動在字串末尾加’\0’。scanf(“%s”, str);str前面不要加&,陣列名就是陣列的首地址。
2) C語言中沒有專門的函式,可以取字串指定起始和終止位置的子串的這樣一個函式,strcat()是串連線函式,strcpy()是串拷貝函式。如果已知起始和終止位置,可直接通過字元向字串的轉換完成,使用for迴圈(for迴圈通常是為了確定終止位置)結合strcat()也可達到取指定起始和終止位置的子串的目的。
3) 字串預設結束標誌是'\0',自己不加編譯器會自動加,字串處理函式(strcat、strlen、strlwr、strupr、puts、gets)操作字串時不要考慮'\0',不管’\0’是手動加的還是函式自動加的,當做透明的就好。
4) switch語句的case裡有了return就不用加break了,return是直接結束當前函式返回,break是直接結束當前迴圈返回,exit是直接結束當前程式返回。
5) 函式裡面有多個引數,且引數也是函式時,計算順序從右往左,即先確定右邊的引數,再確定左邊的引數。若兩個函式引數是一樣的,比如出棧,則右邊的引數是先出棧的值,左邊的是後出棧的值。
6) C中字元向字串的轉換:char str[3] = {c[1], '\0'};c[1]是字元,這種以字元方式初始化的結束字元要為’\0’;另外一種寫在雙引號裡的以字串方式初始化,編譯器會自動為字串末尾加’\0’,不管哪一種初始化方式都要留出加’\0’的空間。
7) 關於C陣列越界,C中編譯器不會檢查陣列是否越界。我們定義一個變數,實際上是向作業系統申請一段記憶體。這個記憶體塊是隨機的,它可以是目前空閒的任意一個記憶體段。由於這個不確定性,所以它後面的記憶體塊有兩種可能:如果恰好這段記憶體的後面的記憶體段(即越界地址)沒有被其他程式佔用,那麼我們對它進行使用是不會出錯的,但是它隨時可能被其他程式修改。同時因為這段記憶體不屬於本程式管理,那麼它也有可能被其他程式佔用,或者乾脆就是作業系統禁止訪問的區域,這時候就會導致錯誤的發生。
8) 字串向整型、長整型、浮點數的轉換函式atoi、atol、atof等,這些函式都只有一個引數,即要轉換為數字的字串,函式的返回值就是轉換所得的數值。strtod()、strtol()、strtoul()函式也能將字串轉換為數字,同時還能檢查溢位情況。strtod() 將字串轉換為雙精度浮點型值,並報告不能被轉換的所有剩餘數字; strtol() 將字串轉換為長整值,並報告不能被轉換的所有剩餘數字; strtoul() 將字串轉換為無符號長整型值,並報告不能被轉換的所有剩餘數字。
9) C中要求有返回值的函式,如果函式裡是if分支語句,要保證每個分支末尾都要有return。