[C++][第6篇]代码清理

常量替换

数值的类型以常量表达

  • 代码修改
// 设置成全局变量
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;
}

错误恢复机制用例展示

[C++][第6篇]代码清理_第1张图片
错误恢复机制用例展示
> 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篇] 系列索引 基于文法的算术表达式解释器
    关键词 系列索引

你可能感兴趣的:([C++][第6篇]代码清理)