2016012056+小学四则运算练习软件项目报告
2016012056+小学四则运算练习软件项目报告
写在前面:第一次看到作业要求时,觉得很难,一开始有种抗拒的心态。自己一直以来学习前端,对后端只是基本的了解而已。写的过程遇到了很多问题,虽然项目中还有许多不完善的地方,但通过此次作业,一个一个解决问题。经过这些天的“补课”,让我加深了后端的学习,虽然辛苦,但是收获了很多(所以说欠的债总是要还的)。
项目克隆地址:https://git.coding.net/chenxin1998/calculate2.git
一.需求分析
小学生计算能力需要通过练习大量的运算题目得到锻炼,因此很多老师让家长每天给学生出相应的练习题。为了方便老师的教学任务,同时减轻家长的负担,现在需要一个可以出四则混合运算的小型软件。
1.通过程序接受一个数字,产生N道加减乘除练习题。
2.判断传入参数是否合法
3.每个数字在0-100之间,运算符3-5个
4.运算过程不能出现负数和非整数
5.计算出练习题结果
6.所有信息输出到txt文件
二.功能设计
基础功能:
- 程序可接收一个输入参数n,然后随机产生n道加减乘除练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
- 为了让小学生得到充分锻炼,每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3/5+2=2.6,2-5+10=7等算式。
- 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
- 当程序接收的参数为4时,以下为输出文件示例。
扩展功能:
- 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号必须大于2个,且不得超过运算符的个数。
- 扩展程序功能支持真分数的出题与运算,例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6。
三.设计实现:
1.在实现过程中,为了更好地实现模块化,我分了三个类,分别是:
CreatProblem.java(随机创建一个运算过程不含负数和小数的四则算式);RandomArithmetic.java(主程序类操作,但操作隐藏);WriteResult.java(文件写入)
2.用到的重要函数:
产生四则运算:CreateProblem函数,其中Random随机产生3-5个运算符,且带括号的四则运算式子。若式子不符合要求,则用递归到合法为止。
计算结果:getResult函数,在四则运算式生成后,计算每一部分的结果,且排除带有小数和负数的式子。
启动程序:RandomAtithmetic函数,接收参数n,并判断其合法性。
3.用两个Character栈存储数字和符号,然后用两个栈分别用作转换的缓冲栈和计算结果的缓冲栈
4.一般Character里面的编码是ascii,但我没有那样做,就是一种新的编码,用ascii0-99表达0-99,而符号和数字冲突,所以符号是100-105
优点:比用string省空间
缺点:虽然有括号识别但随机生成算式没有括号(这是需要继续完善的地方)
四.测试运行:
进入src文件下,输入javac -encoding utf-8 RandomArithmetic.java 编译出相应的class文件,再输入java RandomArithmetic 10进行测试:
注意:一开始进行测试的时候,在命令行会出现“找不到或无法加载主类”的情况。最后通过查阅博客,发现是由于JRE版本问题。这是由于我使用高版本的JDK编译的Java class文件试图在较低版本的JVM上运行,所报的错误。 因为,高版本的JDK生成的class文件使用的格式,可能与低版本的JDK的.class文件格式不同。这样,低版本的JVM无法解释执行这个.class文件。于是,问题就变成了“如何解决JDK和JRE版本不一致”,最后只是通过从新安装JDK和JRE,然后重配环境解决。
里面还有一些原理没有弄懂,待以后开发。
总结几个知识点吧~~
JDK、JRE有什么区别:
Jre 是java runtime environment的缩写, 是java程序的运行环境。既然是运行,当然要包含jvm,也就是大家熟悉的虚拟机啦, 还有所有java类库的class文件,都在lib目录下打包成了jar。大家可以自己验证。至于在windows上的虚拟机是哪个文件呢? Java\jdk\ jre\bin\client里面是不是有一个jvm.dll呢?那就是虚拟机。
Jdk 是java development kit,是java的开发工具包,里面包含了各种类库和工具。当然也包括了另外一个Jre--------Java\jre. 那么为什么要包括另外一个Jre呢?而且jdk\jre\bin同时有client和server两个文件夹下都包含一个jvm.dll。 说明是有两个虚拟机的。
Java\jdk\bin这个bin下有各种java程序需要用到的命令,与Java\jdk\jre\bin或者Java\jre\bin的bin目录最明显的区别就是Java\jdk\bin下才有javac.exe,这一点很好理解,因为 jre只是一个运行环境而已。与开发无关,正因为如此,具备开发功能的jdk自己的jre下才会同时有client性质的jvm和server性质的 jvm, 而仅仅作为运行环境的jre下只需要client性质的jvm.dll就够了。
我们用的java命令并不是 Java\jdk\bin目录下的而是Java\jdk\jre\bin目录下的。不信可以做一个实验,大家可以把Java\jdk\bin目录下的java.exe剪切到别的地方再运行 java程序,发现了什么?一切OK!
如果java为了提供给大多数人使用,他们是不需要jdk做开发的,只需要jre能让java程序跑起来就可以了,那么每个客户还需要手动去设置环境变量多麻烦啊?
所以安装jre的时候安装程序自动帮你把jre的java.exe添加到了系统变量中,因此去C:\Windows\system64下面去看看吧,发现有一个java.exe。
五.核心代码:
(1)JAVA用栈处理四则运算:
一共分两步:
1.中缀表达式转后缀表达式
从左到右遍历中缀表达式的每一个数字和运算符。
如果数字就输出(即存入后缀表达式);
如果是右括号,则弹出左括号之前的运算符;
如果优先级低于栈顶运算符,则弹出栈顶运算符,并将当前运算符进栈。
遍历结束后,将栈则剩余运算符弹出。
从左到右遍历后缀表达式,遇到数字就进栈,遇到符号,就将栈顶的两个数字出栈运算,运算结果进栈,直到获得最终结果。
//创建若干个四则运算中缀表达式 ////////////////////////////////////// ///注意,为减小内存使用,ascii0-100表示数字0-99,100-103分别表示加减乘除,()为104,105 //////////////////////////////////////// import java.util.Random; import java.util.*;// import java.lang.*; public class CreateProblem { private int numOfNum=3;//存储该表达式有几个运算数,默认3个 private Stack<Character> infix= new Stack<Character>();//中缀表达式 private Stack<Character> suffix=new Stack<Character>();//后缀表达式 private Stack<Character> stackOfOperation=new Stack<Character>();//计算后缀表达式所用栈 private Stack<Integer> stackOfNum=new Stack<Integer>();//计算结果所用栈 private int max=100;//运算数最大值,默认100
(2)如何解决四则运算表达式中不能出现负数和小数的问题
int getResult()//如果结果是-1,说明随机运算不满足要求,重新创建新的运算式 { //根据后缀表达式计算并且检查算式是否合法,是否有小数或者负数在运算中出现,如果出现,重新创建新的表达式并相应转换 int i=0; while(i<suffix.size()) { int temp,temp0; char c=suffix.get(i); if(isDigit(c)) stackOfNum.push((int) c);//将所有字符数字转换为整数型并入栈 else if(c==100||c==101||c==102||c==103) { switch(c) { case 100: temp=stackOfNum.pop(); temp0=stackOfNum.pop(); stackOfNum.push(temp0+temp); break; case 101: temp=stackOfNum.pop(); temp0=stackOfNum.pop(); if(temp0-temp<0) return -1;//不满足运算中不得出现负数的要求,因此结束程序 else stackOfNum.push(temp0-temp); break; case 102: temp=stackOfNum.pop(); temp0=stackOfNum.pop(); stackOfNum.push(temp*temp0); break; case 103: temp=stackOfNum.pop(); temp0=stackOfNum.pop(); if(temp0%temp==0) stackOfNum.push(temp0/temp); else return -1;//不满足运算中不得出现小数的要求,因此结束程序 break; } } i++; } if(!stackOfNum.empty()) return stackOfNum.pop(); else return -1; }
(3)一开始在写文件的时候出现了陷入循环无法出来的情况,错误代码如下:
public static void write(CreateProblem problem) throws IOException { int i=0; File file=new File("result.txt"); try (PrintWriter output=new PrintWriter(file); ) { while(i<problem.showInfix().size()) { String str=new String(""); char ch=problem.showInfix().get(i); switch(ch) { case 100:str+"+";break; case 101:str+"-";break; case 102:str+"*";break; case 103:str+"/";break; case 104:str+"(";break; case 105:str+")";break; default:str+((int)ch);break; } i++; } output.print('='); output.print(problem.getResult()); output.print('\n'); } file.close(); output.close(); } public static void write(String s) throws IOException { File file=new File("result.txt"); try (PrintWriter output=new PrintWriter(file); ) { output.println(s); } file.close(); output.close(); } }
修改后的代码如下:
import java.io.*; //写文件 public class WriteResult { public static void w1(String str) { try { File f=new File("result.txt"); FileWriter writer=new FileWriter(f,true); PrintWriter pw=new PrintWriter(writer); pw.append(str); pw.println(""); pw.flush(); pw.close(); } catch (IOException e) { e.printStackTrace(); } } public static void w2(CreateProblem problem) { int i=0; String s=new String(); while(i<problem.showInfix().size()) { char ch=problem.showInfix().get(i); switch(ch) { case 100:s+="+";break; case 101:s+="-";break; case 102:s+="*";break; case 103:s+="/";break; case 104:s+="(";break; case 105:s+=")";break; default:s+=((int)ch);break; } i++; } try { File f=new File("result.txt"); FileWriter writer=new FileWriter(f,true); PrintWriter pw=new PrintWriter(writer); pw.append(s); pw.append('='); pw.println(problem.getResult()); pw.flush(); pw.close(); } catch (IOException e) { e.printStackTrace(); } } }
六.总结:
在该小学四则运算小程序中主要分为三个类,其中RandomArithmetic类负责开启程序运行的入口,CreateProblem主要用于处理运算式,WriteResult用于文件写入,整个程序由这三个类块构成,实现了功能的模块化。其中在CreateProblem类中又分离出了private Stack<Integer> stackOfNum=new Stack<Integer>();//计算结果所用栈;private Stack<Character> suffix=new Stack<Character>();//后缀表达式private Stack<Character> stackOfOperation=new Stack<Character>();//计算后缀表达式所用栈;这样设计使得各功能再次模块化,使得程序更加的便于维护和扩展。
最后,就是完成一个项目的喜悦了,所以从事我们这一行,还是要多看,多写,理论与实践结合。
七.PSP总结
PSP2.1 |
任务内容 |
计划完成需要的时间(h) |
实际完成的时间(h) |
Planning |
计划 |
0.5 | 1 |
· Estimate |
·估计这个任务需要多少时间,并规划大致工作步骤 |
3天 | 4天 |
Development |
开发 |
2天 | 3天 |
·· Analysis |
·需求分析 (包括学习新技术) |
12 | 18 |
· Design Spec |
·生成设计文档 |
0.5 | 0.5 |
· Design Review |
·设计复审 (和同事审核设计文档) |
0.6 | 0.8 |
· Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
0.5 | 0.6 |
· Design |
具体设计 |
3 | 3.5 |
· Coding |
具体编码 |
12 | 20 |
· Code Review |
· 代码复审 |
3 | 4 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
2 | 5 |
Reporting |
报告 |
3.5 | 4 |
·· Test Report |
· 测试报告 |
1.5 | 2 |
· Size Measurement |
计算工作量 |
0.5 | 0.5 |
· Postmortem & Process Improvement Plan |
· 事后总结 ,并提出过程改进计划 |
1.5 | 1.5 |