如何写一个计算器?

考虑这样一个问题,给定一个字符串,“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,它主要功能是顺序扫描字符串,返回字符并跳过其中的空白字符,如下
2015年就要结束了,

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 | term
term —> factor * term | factor/term | factor
factor—> 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,代码生成,将优化过对中间代码生成机器代码。

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

OneAPM Mobile Insight以真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客

转载于:https://www.cnblogs.com/oneapm/p/5091199.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/430990.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

由于在客户端检测到一个协议错误_HTTP协议,你了解多少?

HTTP简介HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。HTTP是一个属于应用层的面向对象的协议&am…

idea中没有j2ee_idea神器功能大全

IDEA 全称 IntelliJ IDEA,是java语言开发的集成环境,IntelliJ在业界被公认为最好的java开发工具之一,尤其在智能代码助手、代码自动提示、重构、J2EE支持、各类版本工具(git、svn、github等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面…

linux 固定ip_linux固定IP

在新安装的Linux系统命令行下,敲入:ifconfig,显示如下界面。上面这张图显示网卡没有启动,那么我们敲入代码:ifup eth0启动网卡。网卡启动后,我们可以看出,IP地址和网关等其他信息都已经出现。但是我们需要的…

php编译称opcode文件,PHP源码保护和性能加速

什么是Opcache?每一次执行 PHP 脚本的时候,该脚本都需要被编译成字节码,而 Opcache 可以对该字节码进行缓存,这样,下次请求同一个脚本的时候,该脚本就不需要重新编译,这极大节省了脚本的执行时间&#xff…

9553下载站java,java se development kit11最新版 64位

java se development kit11,简称java11,是一款专门进行java开发的编程软件,这款软件还拥有applet和组件的开发环境等操作,是程序员们进行java开发的飞铲不错软件,如果你喜欢这款软件,那就来下载基本介绍自从…

java sleep方法_一文搞懂 Java 线程中断!

在之前的一文《如何”优雅”地终止一个线程》详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程的方法吗?答案是肯定的,它就是我们今天要分享的——线程中断。下面的这断代码大家应该再熟悉不过了,线…

java 观察者模式_图解Java设计模式之观察者模式

图解Java设计模式之观察者模式天气预报项目需求天气预报设计方案 1 - 普通方案观察者模式(Observer)原理观察者模式解决天气预报需求观察者模式在JDK应用的源码分析天气预报项目需求1)气象站可以将每天测量到的湿度、温度、气压等等以公告的形…

怎么在同一页中分页_分库分表业界难题,跨库分页的几种常见方案

为什么需要研究跨库分页?互联网很多业务都有分页拉取数据的需求,例如:(1)微信消息过多时,拉取第N页消息;(2)京东下单过多时,拉取第N页订单;(3)浏览58同城,查看第N页帖子;…

content add tpl.php,phpcms后台批量上传添加图片文章方法详解(一)

注:以下所有代码中,红色部分为增加部分。一、在后台增加批量添加按钮打开“phpcms\modules\content\templates\content_list.tpl.php”文件搜索“$category[‘catname‘]));?>”在这句话的后天的添加:a echo"" href":;&q…

sap 供应商表_财务人员学习SAP的路线图

有许多网友在公众号给我们留言,咨询财务人员学习SAP的事情,如何才能快速掌握SAP,有没有捷径什么的。今天就给大家分享一下财务人员学习SAP的经验,希望能够为财务人员揭开SAP神秘的面纱,学习SAP少走弯路。刚接触SAP的财…

nodejs搭配phantomjs highcharts后台生成图表

简单分享一下,后台使用nodejs结合highcharts、phantomjs生成报表图片的方法。这主要应用在日报邮件。 主要参考以下资料: http://www.highcharts.com/component/content/article/2-news/52-serverside-generated-charts#phantom_usagehttps://bitbucket.…

vue 页面切换动画_Flutter Hero动画让你的APP页面切换充满动效 不一样的体验 不一样的细节处理...

优美的应用体验 来自于细节的处理,更源自于码农的自我要求与努力,当然也需要码农年轻灵活的思维。本文章实现的Demo效果,如下图所示:class HeroHomePage extends StatefulWidget { override _TestPageState createState() > …

自定义左右侧滑菜单

实现效果: 左右侧滑菜单,侧滑栏占主屏比为60%监听触控,自定义滑动动画,当侧边栏滑动超过50%松开触控将自动滑动到60%,未超过50%松开触控回归侧边栏隐藏为主屏设置蒙版效果,根据侧滑菜单的占屏比设置主屏蒙版…

ubuntu php7 memcache,linux上安装php7 memcache扩展

php7安装memcache扩展需要memcache php7的分支 否则安装会失败php7的memcache扩展安装,真的很让人心碎!下面则是php7的扩展memcache安装了。用之前的php版本安装是没有问题,但是用了php7安装 http://pecl.php.net/package/memcache 下的任一…

好文推荐系列--------(3)GruntJS 在线重载 提升生产率至新境界

好文原文地址:http://segmentfault.com/a/1190000000354555 本文将首先介绍grunt-markdown插件如何配合HTML模板使用,接着我将介绍如何使用grunt-watch插件将工作效率提升至新层次。如果你不熟悉GruntJS,请先阅读我关于GruntJS的文章。 Githu…

二叉树的创建_大多数人都不会手写创建并遍历二叉树,一航这里帮你终结了

创建二叉树、遍历二叉树、二叉树的最近公共祖先任何疑问、意见、建议请公众号留言或联系qq474356284先序、后序创建二叉树先中后层序遍历二叉树二叉树的最近公共祖先 输入格式:创建二叉树时的输入:如序列:{1 2 -1 -1 3 -1 -1}表示1结点有2,…

zookeeper 密码_阿里资深JAVA架构带你深度剖析dubbo和zookeeper关系

为什么要用dubbo?当网站规模达到了一定的量级的时候,普通的MVC框架已经不能满足我们的需求,于是分布式的服务框架和流动式的架构就凸显出来了。单一应用架构当网站流量很小时,只需一个应用,将所有功能都部署在一起&…

nw.js FrameLess Window下的窗口拖拽与窗口大小控制

nw.js FrameLess Window下的窗口拖拽与窗口大小控制 很多时候,我们觉得系统的Frame框很难看,于是想自定义。 自定义Frame的第一步是在package.config文件中将frame选项设置为false。 { "name": "1", "main": "index.…

linux 文件重命名_如何在 Linux 上重命名一组文件 | Linux 中国

要用单个命令重命名一组文件,请使用 rename 命令。它需要使用正则表达式,并且可以在开始前告诉你会有什么更改。-- Sandra Henry-stocker几十年来,Linux 用户一直使用 mv 命令重命名文件。它很简单,并且能做到你要做的。但有时你需…

oracle插入性能优化,Oracle-insert性能优化

看见朋友导入数据,花了很长时间都没完成!其实有很多快速的方法,整理下! 向表中插入数据有很多办法,但是方法不同,性能差别很看见朋友导入数据,,花了很长时间都没完成!其实有很多快速…