常量替换
数值的类型以常量表达
- 代码修改
// 设置成全局变量
const char number = '8';
// 替换掉 primary() 里的 case 语句
case number :
return t.value;
// 替换掉 Token_stream::get() 里的构造函数参数
// 就是这里 return Token{ number,val };
case '.':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
cin.putback(ch);
double val;
cin >> val;
return Token{ number,val };
}
输入输出提示符
const char quit = 'q'; // 表示程序结束,退出
const char print = ';'; // 表示输入结束,立刻打印结果
const string prompt = "> "; // 表示提示输入
const string result = "= "; // 表示计算结果
- 这里是
>
后面跟着空格,所以要用字符串类型;
使用函数
“ 程序中所使用的函数应该反映该程序的基本结构,而函数名则应有效地标识代码的逻辑独立部分。”
摘自 中文本 基础篇 145页
将 main() 中的计算部分独立成到函数 calculate() 之中
void calculate()
{
while (cin) {
cout << prompt;
Token t = ts.get();
while (t.kind == print) t = ts.get(); // eat ‘;’
if (t.kind == quit) return;
ts.putback(t);
cout << result << expression() << '\n';
}
}
int main()
try
{
calculate();
keep_window_open();
return 0;
}
catch (exception& e) {
cerr << e.what() << '\n';
keep_window_open("~~");
return 1;
}
catch (...) {
cerr << "exception \n";
keep_window_open("~~");
return 2;
}
-
main()
负责启动程序和错误处理 -
calculate();
负责计算 -
if (t.kind == quit) return;
这里的修改容易忘记
代码布局
- 没有功能上的修改,通过缩进和换行来提高代码可读性(一眼可以看完的高度和宽度):
switch (ch) {
case quit:
case print:
case '(':
case ')':
case '+':
case '-':
case '*':
case '/':
case '%':
case '=':
return Token(ch); // 字符表示自己
case '.': // 浮点数的开始
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
注释增加
“ 最好的注释就是代码本身。
代码说明了它做了什么,但是没有表达它做这些的目的是什么时需要注释。 ”
摘自 中文版 基础篇 147页
错误恢复
策略
面对错误用例 1+*2*3; 4+5;
- 采取 清除与当前表达式相关的所有单词 的策略;
- 清除直到遇见第一个分号为止的表达式,后面的
4+5;
仍旧可以继续计算;
面对错误用例 1@4z; 6+9;
- 采取上面相同策略的同时,采取 忽略非预期的字符 的策略;
具体到代码的4处修改
//修改[1]
void calculate()
{
while (cin)
try {
cout << prompt;
Token t = ts.get();
while (t.kind == print) t=ts.get(); // 先丢弃所有的打印字符
if (t.kind == quit) return;
ts.putback(t);
cout << result << expression() << '\n';
}
catch (exception& e) {
cerr << e.what() << '\n'; // 输出错误信息
clean_up_mess();
}
}
// 修改[2]
void clean_up_mess()
{
ts.ignore(print);
}
// 修改[3]
class Token_stream {
public:
void ignore(char c); // 忽略直到字符c为止的全部字符(最后字符c也被忽略掉)
};
// 修改[4]
void Token_stream::ignore(char c)
// c 表示Token的类型
{
// 先观察缓冲区
if (full && c==buffer.kind) {
full = false;
return;
}
full = false;
// 现在查找输入流
char ch = 0;
while (cin>>ch)
if (ch==c) return;
}
错误恢复机制用例展示
> 1+*2*3; 4+5;
primary expected
> = 9
> 1@4z; 6+9;
Bad token
> = 15
>
- 我们显式地使用了分号作为一次运算的结束符号,这本质就是一个用例的输入结束符号;
- 清除策略的代码实现也在于,清除遇到第一个分号为止的全部字符;
- 比较两个用例可以看见,第一个错误用例中全是预期的字符(我们提前设定好有具体含义的字符),第二个错误用例则存在非预期字符(比如@符号,未知的字母z等等);
- 在
while(cin)
的循环体里面放了try{} catch{}
,目的就是在遇到错误用例之后,后面的正常用例仍旧可以正常且继续地计算下去;
目前为止的全部代码
/*
本程序实现了一个简单的算术表达式计算器
从cin读入;输出到cout。
输入文法如下:
Statement:
Expression
Print
Quit
Print:
;
Quit:
q
Expression:
Term
Expression + Term
Expression – Term
Term:
Primary
Term * Primary
Term / Primary
Term % Primary
Primary:
Number
( Expression )
– Primary
+ Primary
Number:
floating-point-literal
Input comes from cin through the Token_stream called ts.
*/
#include "std_lib_facilities.h"
const char number = '8';
const char quit = 'q';
const char print = ';';
const string prompt = "> ";
const string result = "= ";
class Token { /* . to-do[1] . */
public:
char kind; // 记录token的类型
double value; // 对于浮点数而言:这是浮点数的值
Token(char ch)
: kind{ ch }, value{ 0 } {}
Token(char ch, double val)
: kind{ ch }, value{ val } {}
};
class Token_stream { /* . to-do[2] . */
public:
Token_stream(); // 创建一个Token流,从cin读取字符
Token get(); // 获取一个Token
void putback(Token t); // 放回一个Token
void ignore(char c); // 忽略直到字符c为止的全部字符(最后字符c也被忽略掉)
private:
bool full{ false }; // 缓冲区中是否有Token
Token buffer; // 存放着通过putback(t)操作放回的Token,这个地方称之为缓冲区
};
void Token_stream::putback(Token t) { /* . to-do[3] . */
buffer = t; // 拷贝t到缓冲区
full = true; // 表示缓冲区被占用
}
Token Token_stream::get() { /* . to-do[4] . */
if (full) {
full = false;
return buffer;
}
char ch;
cin >> ch; // >> 会跳过空白(空格、换行、制表符等)
switch (ch) {
case quit:
case print:
case '(':
case ')':
case '+':
case '-':
case '*':
case '/':
case '%':
case '=':
return Token(ch); // 字符表示自己
case '.': // 浮点数的开始
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': {
cin.putback(ch); // 将数字(或小数点)放回到 标准输入cin 中
double val;
cin >> val; // cin 自动读取一个 double
return Token{ number, val }; // 用'8'表示读取了一个浮点数
}
default:
error("Bad token");
}
}
void Token_stream::ignore(char c)
// c 表示Token的类型
{
// 先观察缓冲区
if (full && c == buffer.kind) {
full = false;
return;
}
full = false;
// 现在查找输入流
char ch = 0;
while (cin >> ch)
if (ch == c) return;
}
// to-do[5]
// 构造函数 Token_stream()
Token_stream::Token_stream()
:full{ false }, buffer{ 0 } // no Token in buffer
{
}
Token_stream ts; // provides get() and putback() // to-do[5]
double expression(); // declaration so that primary() can call expression()
double primary()
{
Token t = ts.get();
switch (t.kind) {
case '(': // 处理 ‘(’ expression ‘)’
{
double d = expression();
t = ts.get();
if (t.kind != ')') error("')' expected");
return d;
}
case number: // 使用 ‘8’ 来表示一个浮点数
return t.value; // 返回浮点数的值
case '-':
return -primary(); // 处理负数
case '+':
return primary();
default:
error("primary expected");
}
}
double term() { /* . to-do[7] . */
double left = primary();
Token t = ts.get(); // 从 token流 中获取下一个 token
while (true) {
switch (t.kind) {
case '*':
left *= primary();
t = ts.get();
break;
case '/':
{
double d = primary();
if (d == 0) error("divide by zero");
left /= d;
t = ts.get();
break;
}
case '%':
{
double d = primary();
if (d == 0) error("divide by zero");
left = fmod(left, d);
t = ts.get();
break;
}
default:
ts.putback(t); // put t back into the token stream
return left;
}
}
} // deal with * and /
double expression() { /* . to-do[6] . */
double left = term(); // 读入并计算一个 Term
Token t = ts.get(); // 获取下一个 Token
while (true) {
switch (t.kind) {
case '+':
left += term(); // 计算一个 Term 并且 相加
t = ts.get();
break;
case '-':
left -= term(); // 计算一个 Term 并且 相减
t = ts.get();
break;
default:
ts.putback(t); // 将 t 放回到 token 流
return left; // 再没有+ - 时返回计算结果
}
}
} // 处理 + 法 以及 - 法
void clean_up_mess()
{
ts.ignore(print);
}
void calculate()
{
while (cin)
try {
cout << prompt;
Token t = ts.get();
while (t.kind == print) t = ts.get(); // eat ‘;’
if (t.kind == quit) return;
ts.putback(t);
cout << result << expression() << '\n';
}
catch (exception& e) {
cerr << e.what() << '\n';
clean_up_mess();
}
}
int main()
try
{
calculate();
keep_window_open();
return 0;
}
catch (exception& e) {
cerr << e.what() << '\n';
keep_window_open("~~");
return 1;
}
catch (...) {
cerr << "exception \n";
keep_window_open("~~");
return 2;
}
基于文法的简单算术表达式计算器 系列索引
- [C++][第0篇] 系列索引 基于文法的算术表达式解释器
关键词 系列索引