?? delp018.html
字號:
<html><!-- #BeginTemplate "/Templates/fwolf001.dwt" -->
<head>
<!-- #BeginEditable "doctitle" -->
<title>獨孤之所 - 一款設計精巧的表達式解析器</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<!-- #EndEditable -->
<style type="text/css">
<!--
body { font-family: "宋體"; font-size: 9pt}
td { font-family: "宋體"; font-size: 9pt}
a:hover { text-decoration: underline}
a { text-decoration: none}
input { font-family: "宋體"; font-size: 9pt}
select { font-family: "宋體"; font-size: 9pt}
-->
</style>
</head>
<body>
<!-- #BeginEditable "2%C7%F8" --> <!-- #EndEditable -->
<table width="584" border="0" align="center" cellspacing="1">
<tr>
<td width="582"> <font color="#FF3333">
<div align="center"><font size="3"><!-- #BeginEditable "%B1%EA%CC%E2" --> </font> <!-- #EndEditable --></div>
</font> </td>
</tr>
<tr>
<td width="582">
<div align="center"><font size="3"><!-- #BeginEditable "%D7%F7%D5%DF" --></font><!-- #EndEditable --></div>
</td>
</tr>
<tr>
<td width="582"><font size="3"><!-- #BeginEditable "%CE%C4%B1%BE%C7%F8" --><center><font color="#0000c0"><font color="#FF3333">一款設計精巧的表達式解析器</font><br>
</font>北京天心軟件公司<br>
陳建鍇
</center>
</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 開發MIS系統時,報表設計中經常會碰到表達式解釋器,完成用戶自定義的公式運算。這種程序的設計需要有比較高的技巧,以下介紹一款用DELPHI4.0開發的程序[程序重在算法,語言特性很少,用其它語言的人也能讀懂],只要按自已的要求稍加修改,即可做成組件或全局方法發部。它支持
"加[+]、減[-]、乘[*]、除[/]、商[$:兩整數相除,結果的整數部分]、模[%]、括號[()]"四則混合運算,支持"與[&]、或[|]、異或[^]、左移[<
]、右移[ >]和非[!]"邏輯運算功能,同時它們可以出現在同一個表達式中,它們的優先級依次為括號、非、與或異或左右移、乘除商模、加減。如式:12.45+3*16
>2*(3+6*(3+2)-1)=12.45+3*4*32,計算結果為:396.45。程序包括兩大部分功能:表達式拆解、因子計算,分別由兩個類TBdsProc和TPUSHPOP完成。具體如下:</font>
<pre><font size="3">
CDIKind=record
case id: Boolean of
True: (dval: Double);
False: (ival: Integer);
end;
CDKind:區別表達式中的整數和浮點數類型,
因為有些運算符不支持浮點數(如邏輯運算)。
ValKind = CDIKind;
TBdsProc = class
private
Fghpd : Integer;//識別并標記左右括號是否成對出現
function IsCalcFh(c: Char): boolean;
//判別一個字符是否運算符
function CopyRight(abds: String;start: Integer):
String;//截取字符串表達式
function BdsSs(var abds: String): ValKind;
//返回一個子表達式的值
function BdsYz(var abds: String): ValKind;
//表達式因子,如:15、(13+5)
function BdsItm(var abds: String): ValKind;
//讀取表達式中的一個因子
public
function CalcValue(const bds: String): ValKind;
//返回計算結果
end;
TPUSHPOP = class
private
ffh: array [0..2] of Char;//符號數組
value: array [0..3] of CDIKind;//值數組
flevel: Byte;//因子個數
fisfh: Boolean;//識別等待輸入值或運算符
fisnot: Boolean;//識別待計算數據項是否執行非運算
function Calcsj(av1,av2: CDIKind;fh: Char): CDIKind;
//執行兩個數值的四則運算
function Calclg(av1,av2: CDIKind; fh: Char): CDIKind;
//執行兩個數的邏輯運算
procedure Calccur;{當輸入數據項滿足四個數后
[依運算優先級層數求得,見下述算式解析原理]執行中間運算}
function IsLgFh(fh: Char): Boolean;
//一個符號是否邏輯運算符
function IsCcFH(fh: Char): Boolean;
// 一個符號乘除商模運算符
public
constructor Create;
procedure PushValue(avalue: CDIKind);//存入一個數據項
procedure PushFh(afh: Char);//存入一個符號
function CalcValue: CDIKind;//計算并返回值
end;
</font></pre>
<p><font size="3"><font color="#ffffff">----</font> 表達式解析基本原理:</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 1.表達式處理:</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 表達式的一個個數據項組成,中間由運算符連接,每個數據項為一個分析基本分析單元。表達式中如果包含有改變運算優先級別的括號運算,先計出括號中式子的值,再把該值當一個數據項處理,這一點在程序設計中只要運用遞歸功就能實現。</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 2.數據項計算處理</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> a >非運算:</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 它為單目運算符,級別最高,在存入符號時做標記,存入數據時即時計算并去除標記。</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> b >表達式運算:</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 設f1、f2、f3分別表示一二三級運算符,V1、V2、V3、V4分別表示順序四個數,則極端表達式模型為R=V1
f1 V2 f2 V3 f3 V4 …,計算時順序應為 R=…V4 f3 V3 f2 V2 f1 V1。為了簡化運算,把其中運算級別最高的邏輯運算在存入數據時先計算完成,
初始化時設V1=0,第一個運算符設為'+'。則公式化為: R=…V4 f2(f1) V3 f2(f1) V2 f1 V1。這樣,當V2與V3間的運算符級別為f2時,V4與V3間的運算符級別<
=f2,則:V2 =(V2與V3計算值),V3后的值和運算符前移;若V2與V3間的運算級別為f1,可先算V1與V2,V2以后的值和運算符前移。則計算后的表達式為:R=V3
f2(f2) V2 f1 V1剛好滿足循環取數條件。</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 3.實現:</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> 程序比較長(TBdsProc和TPUSHPOP的源代碼合計長度為400多行),完整代碼見附件,以下對一些重要實現方法做介紹:</font>
<p><font color="#ffffff"><font size="3">----</font></font> <font size="3"> < 1 >表達式拆解:由方法BdsSs和BdsYz完成表達式拆解和因子處理</font>
<pre><font size="3">
function TBdsProc.BdsSs(var abds: String): ValKind;
var
c: Char;
lpp: TPushPop;
begin
lpp := TPushPop.Create;//建立數據計算對象
while abds< >'' do
begin
c := abds[1];
if IsCalcFh(c) then//是否運算符
begin
lpp.PushFh(c);//保存運算符
abds := CopyRight(abds,2);
end
else
begin
if c=')' then
begin
Dec(Fghpd);//括號匹配
abds := CopyRight(abds,2);
if Fghpd < 0 then
Raise Exception.Create('括號不配對');
Result := lpp.CalcValue;
//返回括號中的子項值,進行下一步計算
lpp.Free;
Exit;
end
else
begin
if c='(' then
Inc(Fghpd);//做括號層數標識
lpp.PushValue(BdsYz(abds));//取下一項的值。
end;
end;
end;
if Fghpd< >0 then
Raise Exception.Create('括號不配對');
Result := lpp.CalcValue;//返回最終運算值
lpp.Free;
end;
function TBdsProc.BdsYZ(var abds: String): ValKind;
begin
if abds< >'' then
begin
if abds[1]='(' then
begin
abds := CopyRight(abds,2);
Result := BdsSs(abds);//遞歸調用,求括號中的值
end
else
Result := BdsItm(abds);{讀一個數據項,
如果包括函數定義,可以在該方法中定義接口或連接}
end;
end;
若表達式要支持函數功能,只要定義系統支持的函數,
在取行表達式因子BdsItm中加入連接函數定義即可,如下:
…..
else if (c< ='Z') and (c >='A') then
begin
bhs := 取出函數名及參數
Result := … //調用函數處理 (函數定義)
abds := 下一項
end
….
< 2 > 數據計算:主要包PushValue,PushFh,
Calccur分別完成存入數、符號、中間計算
procedure TPUSHPOP.PushValue(avalue: CDIKind);
begin
if fisfh=True then
Raise Exception.Create('缺少運算符');
if fisnot then//進行非運算
begin
if avalue.id then
Raise Exception.Create('浮點數不能做非運算');
avalue.ival := not avalue.ival;
fisnot := False;
end;
if IsLgFh(ffh[flevel]) then//運行邏輯運算
begin
value[flevel] := Calclg(value[flevel],
avalue,ffh[flevel]);
//與當前值做邏輯運算
end
else
begin
Inc(flevel);//存數位置指針加1
value[flevel] := avalue;//存入值
if flevel >2 then//數據個數達到4,進行中間運算
Calccur;
end;
fisfh := True;//輸入符號可見
end;
procedure TPUSHPOP.PushFh(afh: Char);
begin
if (fisfh=false) then//非運算是一級
begin
if (afh='!') and (not fisnot) then//標識非運算
begin
fisnot := True;
Exit;
end
else
Raise Exception.Create('運算符重復');
End
Else
begin
ffh[flevel] := afh;//存入運算符
fisfh := False; 輸入值可見
end;
end;
procedure TPUSHPOP.Calccur;
begin
if IsCcFh(ffh[1]) then//二級運算符
begin
value[1] := Calcsj(value[1],value[2],ffh[1]);
//計算2和3項的值
ffh[1] := ffh[2];//后序運符和值前移
value[2] := value[3];
end
else//一級運算符
begin
value[0] := Calcsj(value[0],value[1],ffh[0]);
//計算1和2項的值
value[1] := value[2];{ 后序運符和值前移,
2和3項的值不計算是為了保持第一個運算符為一級運算符}
value[2] := value[3];
ffh[0] := ffh[1];
ffh[1] := ffh[2];
end;
Dec(flevel);//存數位置指針減1
end;
</font></pre>
<!-- #EndEditable --></td>
</tr>
<tr>
<td width="582"><font size="3"><!-- #BeginEditable "%B1%B8%D7%A2" --></font><!-- #EndEditable --></td>
</tr>
</table>
<p align="center">
<script language="JavaScript1.1" src=http://ad.t2t2.com/textclick.asp?user=fwolf&style=4&bkcolor=no></script>
</p>
<p align="center"> </p>
</body>
<!-- #EndTemplate --></html>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -