2016012063 小学四则运算练习软件项目报告
源码仓库地址:https://git.coding.net/deervw/four-operations.git
一、需求分析
1.程序可从命令行接收一个输入参数n,然后随机产生n道加减乘除习题。
2.每个数字在 0 和 100 之间,运算符3到5个。
3.每道习题要包含3-5种运算符。
4.所出的练习题在运算过程中不得出现负数与非整数。
5.将学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中。
6.支持有括号的运算式,包括出题与求解正确答案;算式中存在的括号必须大于2个,且不得超过运算符的个数。
二、功能设计
1.基本功能:能够根据用户输入的参数n随机产生n道符合要求的练习题,自动算出答案,并将式子与答案以文档的形式呈现。
2.扩展功能:支持有括号的运算式,包括出题与求解正确答案。(注意,算式中存在的括号必须大于2个,且不得超过运算符的个数。)
三、设计实现
我设计了一个主函数和两个自定义函数,都在Main.java
1.判断类
功能:带参的有返回值的类,返回的是一个整数。通过返回的参数大小,从而来判断符号的优先级。
关系:可以直接调用。
2.共因数divisor类
功能:带参的有返回值的类,返回的是一个整数,该整数是两个数的最大公因数。此类通过计算两个数的最大公因数,生成化简的最终结果。
关系:可以直接调用。
四、算法详解
1.输入产生的题目数n:判断输入的数是否为整数,若不是则抛出异常。
try { n=Integer.parseInt(args[0]); if(n<=0)System.out.println("请重新输入整数!"); } catch (Exception e){ System.out.println("请重新输入整数!"); }
2.生成随机数和运算符:将运算符存在一个数组arr里,以便可以随机生成;用rand()方法可以随机生成0-1之间的数;若不能整除或出现负数可以使用循环重新生成a和b,直到满足要求为止。
char[] arr= {'+','-','*','÷'}; int temp=rand.nextInt(4); charArr=arr[temp]; int a=rand.nextInt(100);//数字范围 int b=rand.nextInt(100); while(a%b!=0)//判断整除 { a=rand.nextInt(100); b=rand.nextInt(99)+1;//不能出现0 } while(a<b)//判断是否出现负数 { a=rand.nextInt(100); b=rand.nextInt(100); }
3.最大公因数:保证生成的为真分数,用辗转相除法
public static int divisor(int x,int y){ while(1){ if(x%y==0)return y; int temp=y; y=x%y; x=temp; } }
4.计算后缀表达式:我创建了一个栈st,保存的是char类型的字符,然后我int了一个op1来分别保存两个栈顶的操作数,所以这里先用到了String.valueOf
方法先将char转换成String,然后使用Integer.parseInt方法。
int op1 = Integer.parseInt(String.valueOf(st.pop())); int op2 = Integer.parseInt(String.valueOf(st.pop())); int op = op1 - op2; lkj.push(op);
五、测试运行
六、代码展示
将中序表达式转化为后序表达式:
static bool isNumber(string message) { //判断是否为整数字符串 int result = -1; try { result = Convert.ToInt32(message); return true; } catch { return false; } } //判断操作符优先级大小 static bool comparePriority(string op1, string op2) { return getPriorityValue(op1) > getPriorityValue(op2); } private static int getPriorityValue(string op1) { throw new NotImplementedException(); } static Stack<string> changeExpression(List<string> beforeExps) { Stack<string> operand = new Stack<string>();//操作数 Stack<string> opertor = new Stack<string>();//操作符 //遍历中序表示 int length = beforeExps.Count; int len = opertor.Count; //判断是否为操作数 for (int i = 0; i < length; i++) { string c = beforeExps[i]; if (isNumber(c)) { //操作数 存在操作数栈 operand.Push(c); } else { if (c == "(") { opertor.Push(c); } else if (c == ")") { //该运算符为右括号")",则输出运算符堆栈中的运算符到操作数堆栈,将"("出栈 while (opertor.Peek() != "(") { string stringvalue = opertor.Pop(); operand.Push(stringvalue); } opertor.Pop(); } else { if (len <= 0) { opertor.Push(c); continue; }//符合为左括号 直接存入运算符 if (opertor.Peek() == "(") { opertor.Push(c); } else { //若比运算符堆栈栈顶的运算符优先级高或相等,则直接存入运算符堆栈。 if (comparePriority(c, opertor.Peek())) { opertor.Push(c); } else { // 若比运算符堆栈栈顶的运算符优先级低,则输出栈顶运算符到操作数堆栈,并将当前运算符压入运算符堆栈。 string stringvalue = opertor.Pop(); operand.Push(stringvalue); opertor.Push(c); } } } } } //依序取出运算符到操作数堆栈 while (len > 0) { string stringvalue = opertor.Pop(); operand.Push(stringvalue); } //获取正常的后缀表达式 Stack<string> resultSt = new Stack<string>(); while (len > 0) { string stringvalue = operand.Pop(); resultSt.Push(stringvalue); } return resultSt; }
七、总结:
所谓“模块化”就是要功能独立,功能块里要明确和调试好函数之间的调用关系,以便降低程序的复杂度和更进一步的维护。比如,在我的程序中,产生随机数和计算题目这两个功能是分开的,只要在产生随机数的程序里确定好操作数是合法的,那么计算部分可以和操作数之间做到完全独立。
八、展示PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
---|---|---|---|
Planning | 计划 | 1 | 1 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 24 |
· Analysis | · 需求分析 (包括学习新技术) | 1 | 4 |
· Design Spec | · 生成设计文档 | 0.5 | 0.5 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0.5 | 0.5 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 1.5 | 1 |
· Design | · 具体设计 | 2 | 2.5 |
· Coding | · 具体编码 | 2 | 3 |
· Code Review | · 代码复审 | 2 | 2 |
· Test | · 测试(自我测试,修改代码,提交修改) | 2 | 1 |
Reporting | 报告 | 1 | 1.5 |
· Test Report | · 测试报告 | 1.5 | 2 |
· Size Measurement | · 计算工作量 | 1.5 | 1 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 1 | 1 |
一开始我认为该程序的难点在于代码仓库的使用,这一部分没有学过,所以我以为在这上面会花掉很长的时间。但是后来学习了一段时间才发现这其实并不是难点,更难的在于代码部分,我平时没有在做代码练习,所以我的代码能力非常薄弱,感觉什么都不会,一切都得从头开始。我投入了大量的时间和精力,才勉强完成了任务,可是我还是有很多不满意的地方,但只能以后慢慢学习了。