博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何写一个计算器
阅读量:6257 次
发布时间:2019-06-22

本文共 4071 字,大约阅读时间需要 13 分钟。

考虑这样一个问题,给定一个字符串,“1+1+(3+4)-2*3+8/2”,如何将它转化为如下形式,

“1+1=2”“3+4=7”“2+7=9”“2*3=6”“9-6=3”“8/2=4”“3+4=7”

换句话说,就是如何将字符串按照四则运算计算出来,如何写一个计算器。

拿java来举例,并且为了简单,我们只考虑个位数。这个过程大概分为这几个步骤,首先需要扫描字符串去除空白字符,其次将各个字符转换成对应的操作符或操作数,然后按照四则运算规则逐次计算并输出。

好,我们首先构造一个scanner,它主要功能是顺序扫描字符串,返回字符并跳过其中的空白字符,如下

public class Scanner {    public Scanner(String source){       this.source = source.toCharArray();    }    private char[] source;    private int index = 0;    private static char END = '\n';    public char getNext(){        char result;        do{            if (index >= source.length){                return END;            }            result = source[index];            index += 1;        }while (Character.isWhitespace(result));        return result;    }}

在进行下一步之前,让我们思考一下这个算式的规律,算式中存在两种对象,一种是数字,一种是操作符,由于存在运算的优先级,我们分成三种对象,并用下面的形式来说明,

expr —> term + expr | term - expr | termterm —> factor * term | factor/term | factorfactor—> digit |(expr)

‘—>’的意思是’由...组成’,’|’ 代表’或关系’,expr代表加减法运算式,term代表乘除法运算式,factor代表操作的最小元素,最后一句的意思就是factor由数字或者带括号的expr组成。这三个定义式是递归的,它可以代表任意深度的算式。让我们用树的形式来观察一下,

如何写一个计算器
有了这三种抽象对象我们可以写出对应方法了,我们在parser类里定义三个函数,来代表三种对象的产生过程,并且定义char类型变量head代表正在被扫描的字符。

public class Parser {    private Scanner scanner;    public Parser(Scanner scanner){        this.scanner = scanner;    }    private char head;    public void parse(){        if (Scanner.END != (head = scanner.getNext())){            expr();        }        if (head != Scanner.END){            throw new RuntimeException("syntax error at "+head);        }    }    public int expr(){        int result = term();        int tempResult;        char operate;        while ((operate = head) == '+' || operate == '-') {            head = scanner.getNext();            tempResult = term();            switch (operate) {                case '+':                    System.out.println(result + "+" + tempResult + "=" + (result + tempResult));                    result += tempResult;                    break;                case '-':                    System.out.println(result + "-" + tempResult + "=" + (result - tempResult));                    result -= tempResult;            }        }        return result;    }    public int term(){        int result = factor();        int tempResult;        char operate ;        while ((operate=head) == '*' ||operate == '/') {            head = scanner.getNext();            tempResult = factor();            switch (operate) {                case '*':                    System.out.println(result + "*" + tempResult + "=" + (result * tempResult));                    result *= tempResult;                    break;                case '/':                    System.out.println(result + "/" + tempResult + "=" + (result / tempResult));                    result /= tempResult;            }        }        return result;    }    public int factor(){        int factor;        if (Character.isDigit(head)){            factor = head - 48; //字符变量-48可以转换成对应的字面数字            head = scanner.getNext();        } else {            match('(');            factor = expr();            match(')');        }        return factor;    }//match方法用来断言head是什么字符,如果为真,将读取下一个字符赋值给head    public boolean match(char symbol){        if (symbol == head){            head = scanner.getNext();            return true;        }        throw new  RuntimeException("syntax error at "+head);    }    public static void main(String... args){        Scanner scanner = new Scanner("1+1+(3+4)-2*3+8/2");        Parser parser = new Parser(scanner);        parser.parse();    }}

如果回过头来重新考虑这件事情,你会发现我们这个小程序的本质是将一段文本转化成可以执行的程序,正如我们的编译器一样。而实际上编译器要复杂的多,它的基本工作过程可以分为几个步骤:

  1. 词法分析(scanning),读入源程序字符流,将字符转换成有意义的词素(lexeme)的序列,并生成对应的词法单元(token)
  2. 语法分析(parsing),主要目的是生成词法单元的语法结构,一般会使用树形结构来表示,称为语法树。
  3. 语义分析(semantic analysis),使用语法树检查源程序是否和语言定义的语义一致。其中一个重要部分是类型检查。
  4. 生成中间代码,语义分析完成后,编译器会将语法树生成为一种接近机器语言的中间代码。我们程序最后产生的一系列小的表达式与之类似。
  5. 代码优化,编译器会尝试改进中间代码,用以生成更高效的机器代码。
  6. 代码生成,将优化过对中间代码生成机器代码。

在这些过程中,递归的方法起到了非常重要的作用,有一句话说明了编译器的本质,编译器就是让你的源程序变成可执行程序的另一个程序。你会发现这个定义本身就是递归的。透过这些编译原理,可以让我们更加深入的理解编程语言,甚至发明一种编程语言。

转载于:https://blog.51cto.com/abiton/2115404

你可能感兴趣的文章
把一个select查询结果插入到一个表(可选指定字段和值实例)
查看>>
使用windbg抓取崩溃文件和分析的过程
查看>>
ViewHolder模式超简洁写法
查看>>
项目管理学习笔记之三.绩效分析
查看>>
php十行代码将xml转成数组
查看>>
centos 7 执行 groupinstall报错
查看>>
Web开发入门
查看>>
Flex开发小结(1)如何使用AdvancedDataGrid
查看>>
AFNetworking 下载文件断点续传操作
查看>>
Jar mismatch! Fix your dependencies
查看>>
哀悼日, 网页变灰的实现
查看>>
php:检测用户当前浏览器是否为IE浏览器
查看>>
linux命令备份
查看>>
10个你可能不知道的JavaScript小技巧
查看>>
【ASP】文件上传
查看>>
集合类(数据结构图、集合图、集合之间的比较)
查看>>
hibernate _关联级别策略介绍
查看>>
来了!阿里开源分布式事务解决方案 Fescar
查看>>
挑战Kafka!Redis5.0重量级特性Stream尝鲜
查看>>
荣耀畅玩7C挑战红米5 Plus,千元手机档的王者对决
查看>>