[C++][第7篇]变量功能

使用Variable

// 新增[1]
class Variable {
public:
    string name;
    double value;
    Variable(string n, double v) :name{ n }, value{ v } { }
};

// 新增[2]
vector<Variable> var_table;

// 新增[3]
double get_value(string s)
// 返回 名字为 s 的变量的 值
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == s) return var_table[i].value;
    error("get: undefined variable ", s);
}

// 新增[4]
void set_value(string s, double d)
// 赋值操作 设置命名为 s 的变量 的值 为 d
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == s) {
            var_table[i].value = d;
            return;
        }
    error("set: undefined variable ", s);
}

顶层设计

变量初始化(声明) 与 变量赋值 的严格区分

  • C++里面的声明与赋值
// 变量声明
double x = 3.14; 
double y {3.14}; 

// 变量赋值
x = 5.67; 
y = 5.67;
  • 我们选择更短的关键词 let 来代替 double 的功能
// 声明变量 x 
let x = 3.14;

修改顶层设计的文法

Calculation:
        Statement
        Print
        Quit
        Calculation Statement
    
    Statement:
        Declaration
        Expression
    
    Declaration:
        "let" Name "=" Expression
  • 可见,从 Expression 开始就可以无缝衔接之前的文法;

新增文法对应函数 statement()

// 新增[5]
double statement()
{
    Token t = ts.get();
    switch (t.kind) {
    case let:
        return declaration();
    default:
        ts.putback(t);
        return expression();
    }
}

将函数 calculate() 中的 expression() 修改成 statement()

// 修改
void calculate()
{
    while (cin) 
        cout << result << statement() << '\n';     // 修改成statement()
    }
    catch (exception& e) {  
    }
}

判断变量是否已经声明 与 新增变量

// 新增[6]
bool is_declared(string var)
//  变量 var 在 var_table 了吗?
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == var) return true;
    return false;
}

// 新增[7]
double define_name(string var, double val)
// 将(var, val) 加入到 var_table 表中
{
    if (is_declared(var)) error(var, " declared twice");
    var_table.push_back(Variable{var, val});
    return val;
}

declaration()

/*
      Declaration:
        "let" Name "=" Expression
*/

double declaration()
// 假设已经看到了 "let"
// 处理: name = expression
// 声明一个变量 "name",使用初始值 "expression"
{
    Token t = ts.get();
    if (t.kind != name) error("name expected in declaration");
    string var_name = t.name;

    Token t2 = ts.get();
    if (t2.kind != '=') error("= missing in declaration of ", var_name);

    double d = expression();
    define_name(var_name, d);
    return d;
}

引入name

使用常量

const char name = 'a'; // name token
const char let = 'L'; // declaration token
const string declkey = "let"; // declaration keyword

修改 Token 定义以存储字符串

// 修改
class Token {   /* . to-do[1] . */
public:
    char kind;      // 记录token的类型
    double value;   // 对于浮点数而言:这是浮点数的 
    
    string name;      // 变量 names: name 自身
    Token(char ch) : kind{ ch }, value{ 0 } {}
    Token(char ch, double val) : kind{ ch }, value{ val } {}
    Token(char ch, string n) : kind{ ch }, name{ n } {}
};

定义 name

  • 以字母开头的字母或者数字串
// 合法的name
a
aa
abc
assssssssssssssssssssssssssssd
ab1024

// 不合法的name
123abc
¥a
@sdd
x+y/2;   // 一个表达式而非一个变量名 

修改Token_stream::get() 来识别 name

Token Token_stream::get() {   /* . to-do[4] . */
    // 不变
    switch (ch) {
    // 不变
    default:
        if (isalpha(ch)) {    // 字母开头
            string s;
            s += ch;
            // 遇到字母或者数字就不断添加到字符串 s
            while (cin.get(ch) && (isalpha(ch) || isdigit(ch))) s += ch;
            cin.putback(ch);

            if (s == declkey) return Token(let); // 关键词 "let"
            return Token{ name, s};
        }
        error("Bad token");
    }

}
  • cin.get() 不会跳过空格

修改primary 使变量加入计算

修改文法

 Primary:
        Number
        Name
        ( Expression )
        - Primary
        + Primary
  • 这里新增的NameDeclaration 里面的 相呼应

修改代码

double primary()
{
    Token t = ts.get();
    switch (t.kind) {
    case '(':           // handle '(' expression ')'
        {
            double d = expression();
            t = ts.get();
            if (t.kind != ')') error("')' expected");
            return d;
        }
    case number:    
        return t.value;    // return the number's value
    case name:
        return get_value(t.name);   // 返回 变量的值
    case '-':
        return - primary();
    case '+':
        return primary();
    default:
        error("primary expected");
    }
}
  • 新增一个 case 仅此而已
  case name:
        return get_value(t.name);   // 返回 变量的值

全部功能已经实现 用例展示

程序使用 与 辅助检验

  • 如何使用我们自己写的计算器
1. 编译运行源代码

2. 命令行窗口跳出来了

3. 在输入提示符 > 后面输入表达式,并且以分号作为一次输入的结束

4. 可以在一行之内进行多次计算比如 
    1+1;2-2;3*3;4/4;

5. 输入表达式不需要给使用的数字、变量以及运算符之间显式地用空格隔开,变量声明例外

6. 声明变量的语法是 
      let 变量名 = 变量值; 
   最后的分号不能省略

7. 使用变量就像使用普通的浮点数值一样,浮点数值可以放在哪里,变量就可以放在哪里


使用在线的计算工具检验用例的计算结果 

1. 在 octave-online 里面要计算一个表达式,只需要输入这个表达式就可以了

2. 在下面的闪动的光标 》后面敲入 1+1 就会给出 ans = 2

3. ans 就是默认记录运算结果的变量

4. 旁边的橙色区域会显示处现在有的变量列表 Vars

加 减 乘 除 取余 括号 负数 功能

加 减 乘 除 取余 括号 负数 功能
> 1;
= 1
> 1+1;
= 2
> 1+2+3;
= 6
> 1-2-3;
= -4
> 1+2*3;
= 7
> 1+2*3-4/5+6%2;
= 6.2
> (1);
= 1
> (1+2)*3;
= 9
> ((1+1)*1-(1+1)*1*1*1+(((1+1))));
= 2
> (1+2)*-3+-4*5-(7*8);
= -85

加减乘除取余检验用例结果

octave-online 加 减 乘 除 取余 负数 括号

变量功能

变量功能
> let x = 1.11;
= 1.11
> x;
= 1.11
> let y = 2.22;
= 2.22
> y;
= 2.22
> x+x+x+x+x+x+x;
= 7.77
> x+y;
= 3.33
> let z = x+y;
= 3.33
> z;
= 3.33
> x+y+z;
= 6.66
> 1+x*(y-z)/2;
= 0.38395
>

变量功能检验用例结果

变量功能检验用例结果

至此,全部完整源码


/*
    Simple calculator

    This program implements a basic expression calculator.
    Input from cin; output to cout.

    The grammar for input is:

    Calculation:
        Statement
        Print
        Quit
        Calculation Statement
    
    Statement:
        Declaration
        Expression
    
    Declaration:
        "let" Name "=" Expression

    Print:
        ;

    Quit:
        q 

    Expression:
        Term
        Expression + Term
        Expression - Term
    Term:
        Primary
        Term * Primary
        Term / Primary
        Term % Primary
    Primary:
        Number
        Name
        ( 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 = "= ";
const char name = 'a'; // name token
const char let = 'L'; // declaration token
const string declkey = "let"; // declaration keyword

class Token {   /* . to-do[1] . */
public:
    char kind;      // 记录token的类型
    double value;   // 对于浮点数而言:这是浮点数的值
    string name;      // 变量 names: name 自身
    Token(char ch) : kind{ ch }, value{ 0 } {}
    Token(char ch, double val) : kind{ ch }, value{ val } {}
    Token(char ch, string n) : kind{ ch }, name{ n } {}
};

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:
        if (isalpha(ch)) {
            string s;
            s += ch;
            while (cin.get(ch) && (isalpha(ch) || isdigit(ch))) s += ch;
            cin.putback(ch);
            if (s == declkey) return Token(let); // keyword "let"
            return Token{ name, s };
        }
        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]

class Variable {
public:
    string name;
    double value;
    Variable(string n, double v) :name{ n }, value{ v } { }
};

vector<Variable> var_table;

double get_value(string s)
// 返回 名字为 s 的变量的 值
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == s) return var_table[i].value;
    error("get: undefined variable ", s);
}

void set_value(string s, double d)
// 赋值操作 设置命名为 s 的变量 的值 为 d
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == s) {
            var_table[i].value = d;
            return;
        }
    error("set: undefined variable ", s);
}

bool is_declared(string var)
//  变量 var 在 var_table 了吗?
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == var) return true;
    return false;
}


double define_name(string var, double val)
// 将(var, val) 加入到 var_table 表中
{
    if (is_declared(var)) error(var, " declared twice");
    var_table.push_back(Variable{ var, val });
    return val;
}



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 name:
        return get_value(t.name); // 返回变量的值
    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;       // 再没有+ - 时返回计算结果
        }
    }

}   // 处理 + 法 以及 - 法


double declaration()
// 处理: name = expression
// 声明一个变量 "name",使用初始值 "expression"
{
    Token t = ts.get();
    if (t.kind != name) error("name expected in declaration");
    string var_name = t.name;

    Token t2 = ts.get();
    if (t2.kind != '=') error("= missing in declaration of ", var_name);

    double d = expression();
    define_name(var_name, d);
    return d;
}


double statement()
{
    Token t = ts.get();
    switch (t.kind) {
    case let:
        return declaration();
    default:
        ts.putback(t);
        return expression();
    }
}


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 << statement() << '\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;
}

基于文法的简单算术表达式计算器 系列索引

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容