高级软件工程2017第2次作业

1.github项目地址:

软件工程第二次作业链接

2.解题思路及设计过程

刚开始拿到题目后,看到要求上写着:

完成一个能自动生成小学四则运算题目的命令行 “软件”

首先想到的是用rand()函数生成操作数和运算符,然后先从简单地只生成两个操作数的四则运算式,并且只考虑整数之间的运算。实现前面一步,再看题目运算符要求三个以上,这就需要用数组来存放操作数和运算符,同样很容易能随机生成多个操作数的四则运算式。接着遇到了最困难的一步:

要求能处理用户的输入,并判断对错,打分统计正确率。

要能判断用户输入的对错就必须计算四则运算式的结果,一开始想到既然操作数和运算符都存放在数组里了,那么直接遍历数组,根据操作符将操作数取出计算。结果发现由于需要考虑运算的优先级,用数组操作过程极为复杂,很难计算出正确结果。于是从网上查找资料,发现用后缀表达式计算四则运算算法思路简单清晰。共需要准备三个容器,符号栈operatorStack,数字栈numStack和盛放后缀表达式的队列expQueue。按照下列的规则进行:

  • 用for循环,每次随机生成一个操作数或运算符并打印
  • 若产生操作数num,就expQueue.push(num);
  • 若产生运算符,分以下两步处理:
  • 1.如果当前运算符优先级不高于符号栈栈顶运算符,则弹出操作符op,并做expQueue.push(op)操作;
  • 2.将当前运算符op放入栈中,operatorStack.push(op);
  • 当上述操作完成,表示打印出运算式并生成一个后缀表达式,接下来计算后缀表达式
  • 遇到数字num,numStack.push(num);
  • 若遇到运算符,从numStack栈顶取出两个操作数并计算,结果放入栈顶
  • 不断进行上面两步操作,最后只剩一个数字,就是最终结果

由于对栈和队列的操作不够熟练,并且以上只用到了基本操作,所以决定用数组来代替栈和队列,通过下标变换可以实现同样的效果。不过又遇到一个问题,那就是盛放后缀表达式的expQueue数组需要同时存放操作数和运算符,两者类型不同无法用数组存放。经过尝试,想出一个方法那就是:把操作数单独存放到数组里面,而在expQueue中存放该操作数对应的下标。完成以上功能后,还需解决题目的最后一个需求:

参与运算的操作数(operands)除了100以内的整数以外,还要支持真分数的四则运算,例如:1/6 + 1/8 = 7/24。

看到这个要求,一时之间又遇到了难题:一个真分数需要考虑分子和分母两个操作数,如果支持真分数的四则运算,那么不如把所有操作都想转换成分数,最后对结果进行化简,那么问题就转化为如何处理两个分数的运算?经过反复思考,想到一个令自己比较满意的解决方法,那就是使用结构体,定义两个成员变量分别代表分子、分母。完成以上的设计分析再开始编程,发现代码的编写十分顺利!

3.代码说明:

基本功能

1.返回操作符的优先级

int mPriority(char op){
    if(op==‘+‘) return 1;
    else if(op==‘-‘) return 1;
    else if(op==‘x‘) return 2;
    else if(op==‘%‘) return 2;
} 

mPriority

2.求最大公约数

int gcd(int a,int b)
{
    if(b==0) return a;
    else return gcd(b,a%b);
}  

gcd

3.根据操作符计算两个操作数

Digit Calculate(Digit num1,Digit num2,char op){
    Digit res;
    int f;
    //根据运算符,作两个分数之间的四则运算
    switch(op){
        case ‘+‘:{
            res.x=num1.x*num2.y+num1.y*num2.x;
            res.y=num1.y*num2.y;
            break;
        }
        case ‘-‘:{
            res.x=num1.x*num2.y-num1.y*num2.x;
            res.y=num1.y*num2.y;
            break;
        }
        case ‘x‘:{
            res.x=num1.x*num2.x;
            res.y=num1.y*num2.y;
            break;
        }
        case ‘%‘:{
            res.x=num1.x*num2.y;
            res.y=num1.y*num2.x;
            break;
        }
        default:{
            res.x=0;
            res.y=1;
        }
    }
    if(res.x<res.y)
        f=gcd(res.y,res.x);
    else
        f=gcd(res.x,res.y);
    res.x=res.x/f;res.y=res.y/f;
    //如果分母为负数,取反
    if(res.y<0){
        res.x=-res.x;
        res.y=-res.y;
    }
    return res;
} 

Calculate

4.随机生成1个操作数并打印

Digit getNum()
{
    int i,j,f;
    Digit res;
    if(rand()%3==1)//1/3的概率生成一个真分数
    {
        i=rand()%11+1;
        j=rand()%11+1;
        if(i>j) {int temp=i;i=j;j=temp;}
        f=gcd(j,i);
        i=i/f;j=j/f;
        printf("%d/%d",i,j);
    }else{
        i=rand()%101+1;
        j=1;
        printf("%d",i);
    }
    res.x=i;
    res.y=j;
    return res;
}

getNum

5.随机生成一个操作符并打印

char getOperator(){
    char op=ss[rand()%4];
    //打印操作符
    if(op==‘%‘)
        printf("÷");
    else if(op==‘x‘)
        printf("×");
    else printf("%c",op);
    return op;
} 

getOperator

主函数

int main()
{
    srand((unsigned)time(NULL));  //每次运行进行初始化
    int times; //控制生成题目的个数
    float score=100; //题目得分
    scanf("-n %d",&times);
    printf("本次共%d题,满分100分\n",times);
    //第一个for循环,每次生成一个题目
    for(int j=0;j<times;j++){
        printf("%d: ",j+1);
        int t=0,q=0,p=0,top=0;
        Digit opNum[10],numStack[10];
        char op,operatorStack[10],expQueue[20];
        opNum[q++]=getNum();
        expQueue[p++]=q-1+‘0‘;
        //得到后缀表达式
        for(t=0;t<3;t++)
        {
            op=getOperator();
            if(t==0){
                operatorStack[top++]=op;
                opNum[q++]=getNum();
                expQueue[p++]=q-1+‘0‘;
                continue;
            }
            while(mPriority(op)<=mPriority(operatorStack[top-1])&&top>0){
                top--;
                expQueue[p++]=operatorStack[top];
            }
            opNum[q++]=getNum();
            expQueue[p++]=q-1+‘0‘;
            operatorStack[top++]=op;
        }
        while(top>0){
            top--;
            expQueue[p++]=operatorStack[top];
        }
        //根据后缀表达式计算结果
        top=0;
        for(int i=0;i<p;i++){
            if(expQueue[i]>=‘0‘&&expQueue[i]<=‘9‘)
                {
                    int ch=expQueue[i]-‘0‘;
                    numStack[top].x=opNum[ch].x;
                    numStack[top].y=opNum[ch].y;
                    top++;
                }else{
                    top--;
                    numStack[top-1]=Calculate(numStack[top-1],numStack[top],expQueue[i]);
                }
        }
        printf("=");
        //用户输入计算结果
        char userAns[100],rightAns[100];
//        printf("%d/%d\n",numStack[top-1].x,numStack[top-1].y);
        cin>>userAns;
        int c=getchar();
        //得到的正确结果
        if(numStack[top-1].y!=1){
            sprintf(rightAns,"%d%s",numStack[top-1].x,"/");
            sprintf(rightAns,"%s%d",rightAns,numStack[top-1].y);
        }
        else
            sprintf(rightAns,"%d",numStack[top-1].x);
        //printf("%s\n",rightAns);
        //判断对错
        if(strcmp(userAns,rightAns)==0)
            printf("正确!\n");
        else {
            printf("不正确!正确答案= %s\n",rightAns);
            //扣分
            score-=100*1.0/times;
        }

    }
    printf("本次得分%.2f",score);
    return 0;
} 

Main

附加功能(9/25改进)

上面代码只实现了基本功能,对于附加功能运算符个数随机生成,只需改变t的值即可。至于带括号的多元复合运算,整个代码的思路没有改变,特别对后缀表达式的计算部分不用作任何变动。在得到后缀表达式的过程,分别在运算符之后随机生成左括号,在合适的操作数之后生成相应的右括号,另外,需要在符号栈的进出栈作一些改变。对代码作改进的部分如下:

//加入附加功能,运算符个数随机生成
        int op_Num=rand()%5+1;
        //附加功能,控制括号的生成
        int braket_Max=2,braket=0,flag=0;//分别代表生成括号的个数和当前左括号个数
        //得到后缀表达式
        for(t=0;t<op_Num;t++)
        {
            op=getOperator();  //生成运算符
            if(t==0){
                operatorStack[top++]=op;
                 //随机决定是否生成左括号
                if(rand()%3==1&&t<op_Num-1){
                    printf("(");//打印括号
                    braket_Max--;
                    braket++;
                    operatorStack[top++]=‘(‘; //左括号入栈
                }
                opNum[q++]=getNum();
                expQueue[p++]=q-1+‘0‘;
                continue;
            }

            //当符号栈顶不是左括号,根据优先级判断出栈
            if(operatorStack[top-1]!=‘(‘){
                while(mPriority(op)<=mPriority(operatorStack[top-1])&&top>0&&operatorStack[top-1]!=‘(‘){
                    top--;
                    expQueue[p++]=operatorStack[top];
                }
            }
            operatorStack[top++]=op;
             //随机决定是否生成左括号
            if(rand()%3==1&&t<op_Num-1){
                if(braket_Max<0) break;//如果已经生成三对括号,就不再生成
                printf("(");//打印括号
                flag=t;
                braket_Max--;
                braket++;
                operatorStack[top++]=‘(‘; //左括号入栈
            }
            opNum[q++]=getNum();//产生一个随机数
            expQueue[p++]=q-1+‘0‘;
            //随机决定是否生成右括号
            if(flag!=t&&rand()%3==1){

                if(braket<=0) break;
                printf(")");//打印右括号
                braket--;
                //一直出栈直到遇到左括号
                while(operatorStack[top-1]!=‘(‘) {
                    top--;
                    expQueue[p++]=operatorStack[top];
                }
                top--;
            }
        }
        //如果还有左括号还未匹配
        while(braket>0){
            braket--;
            printf(")");
            while(operatorStack[top-1]!=‘(‘) {
                    top--;
                    expQueue[p++]=operatorStack[top];
            }
            top--;
        }
        while(top>0){
            top--;
            expQueue[p++]=operatorStack[top];
        }

4. 遇到了一些问题:

计算结果为分数可能在分母出现负号。

  • 对分子、分母求最大公约数,当最大公约数为负数,化简后分母为负数,这就需要判断:如果分母小于零,分子、分母同时取反。
if(res.y<0){
        res.x=-res.x;
        res.y=-res.y;
    } 

还有各种小问题,就不一一叙述了。

5.测试运行

根据需要在-n后面输入要生成的题目个数,每个运算式后面输入自己的计算结果,由程序自动判断对错。

基本功能:

支持附加功能

6.PSP

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10   5
· Estimate · 估计这个任务需要多少时间 10 5
Development 开发 600 695
· Analysis · 需求分析 (包括学习新技术) 60 50
· Design Spec · 生成设计文档 10 30
· Design Review · 设计复审 (和同事审核设计文档) 10 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 5
· Design · 具体设计 120 300
· Coding · 具体编码 200 180
· Code Review · 代码复审 30 20
· Test · 测试(自我测试,修改代码,提交修改) 170 100
Reporting 报告 250 210
· Test Report · 测试报告 120 100
· Size Measurement · 计算工作量 30 20
·Postmortem& Process Improvement Plan · 事后总结, 并提出过程改进计划 100 90
合计   860 910

7.心得体会

第一次做软件工程项目,虽然是一个小项目,但是也学到了软件开发的一部分流程。体会比较深的就是,在开发的过程中把更多的时间投入的开发设计和测试上,而不是把重点放在代码的编写上。用更多的时间构思,把程序需要解决的问题考虑清楚,这样写起代码思路十分清晰,不会出现代码混乱,确实提高了开发效率。另外,通过这次作业学习了GitHub的基本用法,知道了如何使用GitHub管理自己的项目,给自己的开发有很大的帮助。

时间: 2024-08-15 06:45:13

高级软件工程2017第2次作业的相关文章

高级软件工程2017第1次作业

第一部分:结缘计算机 1.你为什么选择计算机专业?你认为你的条件如何?和这些博主比呢?(必答) 我选择计算机是个偶然+阴差阳错,但是后来喜欢上了计算机.我的大学志愿里面前几个学校都是医科学校,首都医科大.天津医科大等等,但是那年医科学校的分出奇的高,前几个学校都没有要我,到了第四志愿.我的第四志愿是武汉理工大学,第一志向专业是计算机.填写志愿的时候到了第四志愿就没怎么细细思考,直接选择了几个自己"看着顺眼"的专业.就这般阴差阳错,我开始从零开始接触计算机,熟悉计算机,也渐渐喜欢上计算机

高级软件工程2017第8次作业—个人总结

一.参考第一次作业,对课程的承诺和期望都兑现了吗? 我对课程的期望是:通过这一门课,可以初步了解软件工程理论在项目开发过程中的重要性. 实事求是,确确实实是走了一个流程,还是很有启发性的,感觉像是参加了一次团体竞赛,并科学地完成各个步骤. 二.总结这门课程的实践给你带来的提升,包括 1.学习和使用的新软件 Leangoo 2.学习和使用的新工具   github:发布自己的程序,并在团队项目中查看队友的进度.   博客园:记录项目的情况.    3.学习和掌握的新语言.新平台   没有. 4.统

从高级软件工程角度分析本科毕业设计

本人是15级计算机科学与技术专业的周娜,在学习<高级软件工程>这门课程的过程中,老师交代给我们的第一次任务便是从软件工程角度去分析自己的本科毕业设计. 我的本科毕业设计题目是“基于Android的移动办公系统的设计与实现”,此课题的主要背景是随着网络技术的不断发展和移动终端的不断成熟,以及这两者之间的结合,移动办公方式逐渐引起更多人的关注和使用,方便了用户的工作使用户的工作场所不再拘泥于单一的办公室中.其使用的开发平台是Android平台,应用开发工具是Eclipse,编程语言为Java. 本

从高级软件工程角度分析毕业设计项目存在的问题

本人本科毕设的题目是:基于Android平台下小游戏的设计与开发—勇敢的老鼠.选择此毕设课程的背景主要如下:如今随着科技的快速发展,智能手机也紧随时代步伐从单纯的通讯工具转变为集学习.娱乐.通讯为一体的高端电子产品.手机小游戏这个亮点被越来越多的软件开发商和用户关注与喜爱,尤其是Android手机小游戏拥有的巨大潜力与市场并主宰着手机游戏的未来发展. 本游戏勇敢的老鼠使用JAVA语言,开发环境为eclipse,主要以Android平台框架为背景,主控制类继承自Android平台上的Activit

软件工程(第三次作业)

软件工程(第三次作业) 组员:周德莉.王铭霞 一.题目 在之前编写的四则运算程序基础之上做如下改进: 1  请参照教材Page57:4.2-4.3节中内容,修改原程序,使之符合 “代码风格和设计规范”的基本要求: 2  请采用模块化设计思想,修改之前的code,将 “计算功能” 封装起来 小提示: 假如在C语言环境下,可将函数声明与具体实现分别存放在头文件(.h)和源文件(.c)中: 3  通过测试程序和API 接口,测试其简单的加法功能. 小提示: 单元测试方法请参看教材Page21:2.1.

02组_现代软件工程_第03次作业——对于自身评价(原有水平以及长远目标分析总结)

02组_现代软件工程_第03次作业 --对于自身评价(原有水平以及长远目标分析总结) 李聿轩 ---------------------------------------------------------------------------- 一.原有水平 1.技术水平 ①Java基础开发 A.基本语法逻辑的代码完成 B.利用基本内容完成控件功能的设计以及实现 C.多线程的管理控制,队列,栈的使用相对较少 ②Android开发 A.会使用原生的控件显示,利用诸如Activity,Servic

2017面向对象程序设计寒假作业3!

实现简单电梯调度(2) GitHub:pullself 承接上文:2017面向对象程序设计寒假作业2! 上文调度方式的更新与优化 由于现在电梯可以在任意楼层停靠并且上下人.进行对应的修改. 建立在上文所使用的调度方式为基础,继续给出以预知和非预知为条件的两个程序. 代码行数 调试bug 编码时间 ?行 ?个 ?h ?行 ?个 ?h 预知版本 通过分析,我们可以知道,只需要对搜索方式进行修改即可,修改为通过接受到的请求,动态增加所需要搜索节点. 具体实现方式: 在搜索过程中加入目的地判断与记录.

软件工程网络15个人阅读作业1

软件工程网络15个人阅读作业1 Task1:博客账号 http://www.cnblogs.com/mz201521044152/ Task2:码云账号 https://gitee.com/mxz0/events Task3:完成博客-阅读与思考 ##阅读参考材料,并回答下面几个问题: (1)回想一下你初入大学时对网络工程专业的畅想 当初你是如何做出选择网络工程专业的决定的? 你认为过去两年中接触到的课程是否符合你对网络工程专业的期待,为什么? 你觉得计算机是你喜欢的领域吗,它是你擅长的领域吗?

软件工程网络15个人阅读作业2 201521123038 游舒婷

软件工程网络15个人阅读作业2 提出问题 问题1 我看了书P85这一段文字, 结对编程中有两个角色: 1.驾驶员(Driver):控制键盘输入. 2.领航员(Navigator):起到领航.提醒的作用. 这两个角色还是可以互换的. 我的疑惑是,结对编程两个人的能力不一定在同一水平线上,每个人都有自己比较擅长的地方,那么,如果两个角色可以互换,是否说明双方都要读懂对方的代码.假设,一方负责前端设计,一方负责后端开发,虽然两种技术之间有部分相关,但是这意味着两方都要了解对方的代码么? 问题2 我看了