既然是表示式求值,自然需要在記憶體中儲存計算結果以及中間值。在《用c語言寫直譯器(一)》中提過:變數要求是若型別,而 c 語言中的
view plaincopy to clipboardprint?
// in basic_io.h
#define memery_size (26)
typedef enum variant_type;
typedef char string[128];
typedef struct ;
} variant;
extern variant memery[memery_size];
// in expression.h
typedef variant operand;
// in basic_io.h
#define memery_size (26)
typedef enum variant_type;
typedef char string[128];
typedef struct ;
} variant;
extern variant memery[memery_size];
// in expression.h
typedef variant operand;
程式自帶 a-z 26個可用變數,初始時都處於未賦值(ver_null)狀態。所有變數必須先賦值再使用,否則就會報錯!至於賦值語句的實現請參
見後面語法分析的章節。
操作符表示式中光有數值不行,還需要有操作符。在《一》中「表示式運算」一節已經給出了直譯器需要實現的所有操作符,包括「算術運算」、「
關係運算」和「邏輯運算」。下面給出程式中操作符的定義和宣告:
view plaincopy to clipboardprint?
// in expression.h
typedef enum operator_type;
typedef enum associativity;
typedef struct operator;
// in expression.c
static const operator operators = , // 左括號
, // 右括號
, // 加
, // 減
, // 乘
, // 除
, // 模
, // 冪
, // 正號
, // 負號
, // 階乘
/* 關係運算 */
, // 小於
, // 大於
, // 等於
, // 不等於
, // 不大於
, // 不小於
/* 邏輯運算 */
, // 且
, // 或
, // 非
/* 賦值 */
// basic 中賦值語句不屬於表示式!
, // 賦值
/* 最小優先順序 */
// 棧底
};
// in expression.h
typedef enum operator_type;
typedef enum associativity;
typedef struct operator;
// in expression.c
static const operator operators = , // 左括號
, // 右括號
, // 加
, // 減
, // 乘
, // 除
, // 模
, // 冪
, // 正號
, // 負號
, // 階乘
/* 關係運算 */
, // 小於
, // 大於
, // 等於
, // 不等於
, // 不大於
, // 不小於
/* 邏輯運算 */
, // 且
, // 或
, // 非
/* 賦值 */
// basic 中賦值語句不屬於表示式!
, // 賦值
/* 最小優先順序 */
// 棧底
};你也許會問為什麼需要 icp(incoming precedence)、isp(in-stack precedence) 兩個優先順序,現在不用著急,以後會詳細解釋!
字尾表示式
現在運算元(operand)和操作符(operator)都有了,乙個表示式就是由它們組合構成的,我們就統稱它們為標記(token)。在程式中定義
如下:view plaincopy to clipboardprint?
// in expression.h
typedef enum token_type;
typedef struct ;
} token;
typedef struct tlist token_list, *ptlist;
// in expression.h
typedef enum token_type;
typedef struct ;
} token;
typedef struct tlist token_list, *ptlist;
我們平時習慣將表示式符寫作:operand operator operand(比如1+1),這是乙個遞迴的定義,表示式本身也可作為運算元。像這種將操作
符放在兩個運算元之間的表示式稱為中綴表示式,中綴表示式的好處是可讀性強,運算元之間涇渭分明(尤其是手寫體中)。但它有自身的缺
陷:操作符的位置說明不了它在運算的先後問題。例如 1+2×3 中,雖然 + 的位置在 × 之前,但這並不表示先做加運算再做乘運算。為解
決這個問題,數學中給操作符分了等級,級別高的操作符先計算(乘號的級別比加號高),並用括號提高操作符優先順序。因此上例表示式的值
是 7 而不是 (1+2)*3=9。
但對於計算機來說,優先順序是乙個多餘的概念。就像上面提到的,中綴表示式中操作符的順序沒有提供運算先後關係的資訊,這就好比用4個
位元組的空間僅儲存1個位元組資料——太浪費了!索性將操作符按照運算的先後排序:先計算的排最前面。此時操作符就不適合再放中間了,可
以將它移到被運算元的後面:operand operand operator(比如 1 1 +)。上例中 1+2×3 就變化為 1 2 3 × +;(1+2)×3 變化成 1 2 + 3
×,這種將操作符符放到運算元後面的表示式稱為字尾表示式。同理還有將操作符符按照逆序放到運算元的前面的字首表示式。
無論是字首表示式還是字尾表示式,它們的優點都是用操作符的順序來代替優先順序,這樣就可以捨棄括號等概念,化繁為簡。
字尾表示式求值
請看下面的梯等式計算,比較中綴表示式和字尾表示式的求值過程。
8 × ( 2 + 3 ) 8 2 3 + ×
= 8 * 5 = 8 5 ×
= 40 = 40字尾表示式的求值方式:從頭開始乙個標記(token)乙個標記地往後掃瞄,碰到運算元時先放到乙個臨時的空間
裡;碰到操作符就從空間裡取出最後兩個運算元,做相應的運算,然後將結果再次放回空間中。到了最後,空間中就只剩下運算元即運算結果
!這個中綴表示式求值類似,只不過中綴表示式運算元取的是前後各乙個。下面的**是程式中字尾表示式求值的節選,其中只包含加法運算
,其他運算都是類似的。
view plaincopy to clipboardprint?
// in expression.c
variant eval ( const char expr )
// 如果是操作符...
switch ( p->token.ator.oper ) else else
if ( op2->token.var.type == var_double ) else
op1->token.type = var_string;
strcat ( s1, s2 );
strcpy ( op1->token.var.s, s1 );
}
free ( op2 );
break;
// ...
// 其他操作符方法類似
default:
// 無效操作符處理
break;
}
free ( p );
}
value = stack->token.var;
free ( stack );
// 最後乙個元素即表示式的值
return value;
}
表示式求值
程式的說明見清華大學出版社 資料結構 c語言版 include include define stack init size 40 define stackincrement 20 define ok 1 define false 0 typedef structs stack typedef st...
表示式求值
寫了乙個下午,各種糾結,各種問,終於搞明白了。但是自己還是想出來的一點東西的。很爽歪歪的,哈哈。先貼第一次的 include include include include include includeusing namespace std char data 7 7 int sign char ...
表示式求值
description 輸入中綴算術表示式s,s 中的運算元為非負整數,只含 和 運算,也可能含有括號 運算子的計算順序和實際四則運算的計算順序相同 輸出 表示式s 的值.注意除法運算只取整數部分,例如 1 2 0.input 輸入有多組資料.每組資料是乙個算術表示式s,s的長度不超過100.輸入的...