单词实现
为了从输入读取包括数字和运算符在内的表达式,我们需要采用“分词”的方法,首先需要引入“单词”。
单词(token)是表示可以看作一个单元的一个字符序列。在这里,每个单词需要记录其类型和值,即将单词表示为(kind, value)对,kind表示单词是一个数、运算符还是括号,若单词为一个数,value就表示其值。以下我们自定义Token类型,来储存这样的数据:
class Token{
public:
char kind;
double value;
};
可以这样定义:
Token t1;
t1.kind = '+';
Token t2;
t2.kind = '8';
t2.value = 3.14;
除此之外,还有一种更加方便的方法,即利用构造函数来定义:
class Token{
public:
char kind;
double value;
Token(char ch);
Token(char ch, double val);
};
Token::Token(char ch)
:kind(ch), value(0)
{
}
Token::Token(char ch, double val)
:kind(ch), value(val)
{
}
Token t1('+');
Token t2('8', 3.14);
Token(char ch)和Token(char ch, double val)为构造函数。这里需要注意的几点是:
1. 构造函数名必须与类型名一致;
2. 只有在构造函数的初始化中才可以使用以冒号开始的特殊初始化语法;
3. 当我们定义了构造函数后,再使用Token t2; t2.kind = '8'; t2.value = 3.14;来定义就会出现错误。因为在Token类型中我们定义的两个构造函数都是有参数的,因此在定义时要么是只有kind值,要么是既有kind又有value,不能是没有参数的,故Token t2;是不对的;
文法实现
根据我们计算时运用的规则,设计出如下文法:
Expression:
Term
Expression + Term
Expression - Term
Term;
Primary
Term * Primary
Term / Primary
Term % Primary
Primary:
Number
( Experssion )
- Primary
+ Primary
Number:
floating-point-literal
以上文法显示出下列规则:
1. 一个Expression必须是一个Term或者以Term结尾;
2. 一个Term必须是一个Primary或者以Primary结尾;
3. 一个Primary必须以( 或者Number开头;
举例:45 + 11.5 * 7
45为一个Number,进而为一个Primary、Term、Expression,其后跟一个+,因此需要找到一个Term,以实现Expression + Term;
11.5为一个Number,进而为一个Primary、Term,其后跟一个*,因此需要找到一个Primary,以实现Term * Primary;
7为一个Number,进而为一个Primary。
故由文法可判断出45 + 11.5 * 7为一个表达式,且根据判断的顺序,可以实现正确的运算顺序(我们在读取完45 + 后需要先处理Term:11.5 * 7,即先计算了11.5 * 7)。
下面将以上文法转换为代码:
double primary(){
Token t = get_token();
switch(t.kind){
case '(':
{
double d = expression();
t = get_token();
if(t.kind != ')'){
perror("')' expected");
}
return d;
}
case '8':
return t.value;
default:
perror("primary expected");
}
}
double term(){
double left = primary();
Token t = get_token();
while(true){
switch(t.kind){
case '*':
left *= primary();
t = get_token();
break;
case '/':
{
double d = primary();
if(d == 0) {
perror("divide by zero");
}
left /= d;
t = get_token();
break;
}
default:
return left;
}
}
}
double expression(){
double left = term();
Token t = get_token();
while(true){
switch(t.kind){
case '+':
left += term();
t = get_token();
break;
case '-':
left -= term();
t = get_token();
break;
default:
return left;
}
}
}
int main()
try{
while(cin){
cout << "=" << expression() << endl;
}
keep_window_open();
}
/*异常处理*/
catch(exception& e){
cerr << e.what() << endl;
keep_window_open();
return 1;
}
catch(...){
cerr << "exception" << endl;
keep_window_open();
return 2;
}
get_token()是从cin流读入一个单词,但这里有两个问题:
1. 暂时没有用的单词被读入后便丢失了,没有被很好的保存起来,以便未来使用;
2. 程序必须在读入下一个单词之后才能输