[C++][第4篇]具体用例 设置断点 调试代码

调试

使用 visual studio 2017(免费工具)

https://www.visualstudio.com/zh-hans/downloads/

设置断点

https://msdn.microsoft.com/zh-cn/library/5557y8b4.aspx

  • 设置断点的时候


    设置断点
  • 调试的过程中


    调试

我使用的用例

[用例1]
实际输入       1-2-3;
预期输出       =-4
期待的BUG      =2
BUG产生的原因       1- (2-3);  错误的优先级

[用例2]
1+2*3;
=7
想看一下 乘法 *  的 优先级 是否高于 加法+

[用例3]
1.1 *(2+3);
=5.5
想看一下 括号 是否改变了优先级

具体的数据如何在代码之间流淌(函数先后调用关系)

  • 我一开始最困惑的就是一点“计算到底是怎么开始的?”以1-2-3; 作为实际的用例,这里期待的正确输出应该是满足从左到右运算优先顺序的= -4
  • 在这个具体的例子里面,函数的调用关系和语句的执行顺序大概是下面这样的
1-2-3;

调用函数 main()  
  执行 
  Token t = ts.get();
调用函数 Token_stream::get()
  得到 
  t = |'8'| 1 |

回到函数 main()  
  执行 
  ts.putback(t);
  状态 
    buffer = t;
    full = true; 
  执行
  val = expression();

调用函数expression()
执行
double left = term();

调用函数 term()
执行
double left = primary();

调用函数 primary()
执行
Token t = ts.get();
由于这时候 buffer 是满的,
所以直接会从 buffer 里面取出存着数字 1 的Token对象,
故而返回double类型 1

并且依次返回给
term() 
expression()

回到 expression()
执行
Token t = ts.get();

计算由此就彻底开始了

自己实现过程中的BUG总结

primary() 只处理一次

Primary:
  Number
  "(" Expression ")" 
  • 我本来以为primary()也是放在while(true){}里面
  • 这说明这时候要面对的已经是3.14 或者 (3.14)这样的整体了,只需要处理一次;

expression() 要面对1+2+3+4+5+6; 所以需要while(true){}

Expression:
  Term
  Expression "+" Term 
  Expression "–" Term

term() 要面对1*2*3*4*5*6;所以也需要while(true){}

Term:
  Primary
  Term "*" Primary 
  Term "/" Primary 
  Term "%" Primary 
  • 无论是3.14还是1 2 3 4 5 6 都是表示一个右值;
    右值是一个可以被放到赋值号 = 右边的值;

所有的switch语句我都没有加break

  • 一是不熟悉switch的语法
  • 二是完全把switch当成if语句来用了

缓存区使用之后要设置标识符为true

void Token_stream::putback(Token t) {
    buffer = t;
    isfull = true;
}

1 + 2 * 3; 的运算优先级到底是如何保证的?

  • main() 使用ts.get()率先获得了存着 数值1 的Token;
  • 计算的入口是expression(), 该函数处理+ - 运算,意味着 + 号 会作为本层(这个函数)中的switch语句的判断条件在函数调用进入下一层之前就被使用掉,造成的结果就是当函数返回时要做一个left+=term()的计算;
  • 从term() 进入下一层计算term 也有自己的left,这时候term得到了 数值2,紧接着,term并不急着返回数值2,而是看看数值2之后是否跟着term可以处理的运算,也就是* /运算,term利用自己的ts.get语句发现2之后有一个乘法,于是开始进入自己的循环,着手*乘法的处理,课件对于标题里的表达式来说,乘法的入口是在 数值2 :
    • 有一些使用栈的表达式计算算法(可以参考我之前的博客 点这里),面对标题里的表达式,会先处理成1 2 3 + * 的后缀形式,然后不断把元素和运算符弹出弹栈来计算,这种实现乘法的入口其实是在 数值3;
  • 回到term这里,在处理好2*3并且成功得到6之后,term继续读取Token,马上遇到了标识运算结束的符号,分号,于是利用ts.putback()将分号放回到全局变量ts之中,并且返回了运算结果6;
  • 递归调用返回到expression,会得到 left+= 6,最后算出正确答案,并返回;
  • 对于上面这个看似复杂的过程,其实可以总结成, 加减号下不去,乘除号回不来;

使用文法设计了乘除优先级 > 加减优先级的运算规则,在实际代码编写过程中,将expression作为计算的入口保证了在函数递归调用返回的时候,运算会先遇到乘除,最后遇到加减;

1+2-3;揭示了term() ts.putback()的双重作用

 default: 
            ts.putback(t);     // put t back into the token stream
            return left;
        }
  • putback 语句有一个显而易见的作用,就是在读到分号 ; 的时候,表示自己无法处理,返回给全局变量,用以最终结束计算;
  • 然而,term函数中的putback有第二个重要作用,那就是putback(+) 以及 putback(-),本质就是putback(除了* / 以外全都不能处理的符号) ;
  • 事实上,与上方的例子相同,数值2同样是被term获取的,并且term也获取到了2之后的运算符,一个负号,这时候就出现了与上方的用例相反的情况,term是无法处理负号的,而我们都知道expression才可以处理负号,因此需要“把负号交还给expression”,但是term又如何知道谁是expression以及怎么返回,term只是一个函数而已,函数自己怎么能知道谁调用了自己;
  • 幸运的是,程序知道,程序知道谁调用了谁,栈知道怎么返回,栈知道这时候返回的东西会交给expression的,因此当term执行了return left之后,term拿到的数值2就被返回给expression了,那么负号怎么办,这可是接下来重要的运算符啊!
  • 搞了一个大动作, ts.putback(t); 还给了全局变量 ts,不用怀疑,这个负号一定会保存在ts的buffer里面去;
  • 很好,这时候烫手山芋数值2 回到了expression,但是数值2 首先要面对是自己作为term的返回值参与的left+=term,也就是left += 2,这时候expression里面的left 是1,所以会先进行1+2的运算,left变成3;
  • 至于保存在ts 缓冲区buffer里面的那个负号,可别忘了+= 运算之后的, t = ts.get();
 while(true) {    
        switch(t.kind) {
        case '+':
            left += term();    // 计算一个 Term 并且 相加
            t = ts.get();
            break;
  ........
}
  • 这时候, t = ts.get();本质就是从缓冲区里面拿到的负号,并成功进入while的循环,下一个要计算的是left -= term了;
  • 于是,我们继续跟踪数据,实际上, term 再一次拿到了数值3,但是这次term又拿到了之后的分号,也是只能把数值3老老实实地返回给expression,把分号还给全局的ts;
  • 这时候,终于好了,3 -= 3,也就是3 - 3 嘛,答案是0,又遇到了分号,输出答案;

term 里面的 ts.putback(t); 本质上putback的就是term自己无法处理的任意字符,更具体地说是,优先级低于自己本层的运算符以及结束运算符(这里不考虑无效输入),同时,expression的入口身份,意味着expression里面的 ts.putback(t);本质上只能putback结束符,因为没有比它优先级更低的运算符了。

两个用例的区别与联系

  • 全局变量 ts 的缓冲区,仅在main函数执行后,存储过一次 数值类型的Token;
  • 计算从入口expression 开始之初就从缓冲区取回了数值,此后的计算,缓冲区要么存着某个函数无法处理的运算符、要么存着标识计算结束的分号;
  • 计算的过程中,数值 本质上并不以Token的形式传递,而是以函数的return与调用传递,即以自己的本身的double类型值进行传递;

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

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

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,138评论 0 13
  • 一、Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计...
    子非鱼_t_阅读 4,172评论 1 44
  • (来自最底层的声音,总需要人述说) 武士拿着刀,面对生活 面对陌生的异乡,——呐喊 来吧!风, 来吧!雨, 来吧!...
    肖先生肖军阅读 311评论 0 0
  • 这是我当年的一场精彩比赛,献给大家,望它陪伴大家喝个好茶! 这段时间陆续看到了很多朋友关于振作茶的买家秀,在此感谢...
    小窗阅读 546评论 0 2
  • 这个周末,全家一起出去玩了两天,住在海边的五星级酒店,丫丫很开心,这就够了。这个世界上,只有这样一个人,她开...
    cf5e1326a4da阅读 322评论 0 0