离散数学下有逻辑公式的计算。比如 a 与 b 或 c。表示为:a&b|c
此程序的操作符定义如下:
& -- 与
| -- 或
# -- 如果,则
$ -- 当且仅当
! -- 非
( -- 左括号
) -- 右括号
这个程序只定义了上面的这些操作符。但是你可以在理解了这个程序后自己非常简单地添加新的运算符。并且把程序改写成依靠配置文件的运算器。程序从命令行读入一个原始公式字符串。由 InputParser 来把其中的变量抽离出来。比如,a&b|c&b,可以抽离出:a, b, c。由 TrueValueGenerator 来根据这个抽离的结果生成一张真值表。对于 a, b,应该生成
{
{ 0, 0 }
{ 0, 1 }
{ 1, 0 }
{ 1, 1 }
}
对于公式的计算结果。由后面的类来完成。BackExpGenerator 来生成后则表达式。Computer 负责计算后则表达式。
这个程序一开始我也想用简单的双栈来解决,而不用后则表达式。但是,由于涉及到一元和二元操作符同时出现,还有括号。所以,用后则表达式应该是比较好的解决方法。
程序还必须提供一个操作符表,一个与操作符表相对应的优先级表和一个与操作符对应的函数指针表来指明相应操作符的运算法则。最后,在写这个程序的后期,为了使程序可扩充,单独又定义了一个一元操作符的表。注意,这里的操作符表与一元操作符表是两个表!操作符表定义了完整的操作符。包括上面列出的所有的操作符。而一元操作符表里目前只定义了一个“非”操作符。原本,我只是把非的运算放在了 Computer 类的计算方法里。但是,这样一来,就无法扩充其它的一元操作符了。(其实我也不知道除了“非”是不是还有其它的一元操作符)所以,我追加了一个一元操作符表和非的运算函数。并为它单独定义了一个一元操作符函数指针表。我把这五个表定义在了主函数里。由主函数负责组装上面提到的几个功能类。它们的定义如下:
char* szOptrTable = ")(&|#$!"; // operator table, ends with zero.
char* szUOptrTable = "!";
int prioTable[] = { 0, 1, 2, 2, 2, 2, 3 }; // corresponding priority table to operator table.
OptrFun optrFuns[] = { DoNothingB, DoNothingB, And, Or, IfThen, DIfThen, DoNothingB };
UnaryOptrFun uoptrFuns[] = { TurnOver };其中,函数指针表中的各个函数定义如下:(它们被置为全局函数。因为程序不大,所以置为全局函数的危害性并不高)
{
return (left && right) ? (true) : (false);
}
inline int Or(int left, int right)
{
return (left || right) ? (true) : (false);
}
inline int IfThen(int left, int right)
{
return (!left || right) ? (true) : (false);
}
inline int DIfThen(int left, int right)
{
return (left == right) ? (true) : (false);
}
inline int TurnOver(int value)
{
return !value;
}
int DoNothingB(int left, int right)
{
throw "Fuck";
}
注意,这个程序中的操作数和操作符只能是单个字符。如果含有两个以上的字符(比如在数学表达式中的 sin())那么其实现方法请看我的主页下载页面中的计算器中的实现。它是 C# 2.0 编写的。
在明白了上面的讲的程序的总体流程后,后面的章节会分别介绍 InputParser, TrueValueGenerator, BackExpGenerator, Computer 的关键代码。
