高级软件工程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

6.得到后缀表达式

char* getPostfix()
{
        int t=0,q=0,p=0,top=0;
        char* expQueue=new char[100];
        char op,operatorStack[100];
        opNum[q++]=getNum();
        expQueue[p++]=q-1+'0';
        //加入附加功能,运算符个数随机生成
        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];
        }
        return expQueue;
}
getPostfix

7.根据后缀表达式计算结果

char* getResOfPostfix(char expQueue[]){
    Digit numStack[10];
    char* rightAns=new char[100];
    int top=0;
    for(int i=0;i<strlen(expQueue);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("=");
    //得到的正确结果并转成字符串 
    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);
    return rightAns;
}
getResOfPostfix

主函数

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 main()
{
    srand((unsigned)time(NULL));  //每次运行进行初始化 
    int times; //控制生成题目的个数 
    float score=100; //题目得分 
    printf("请输入题目个数(例如输入:-n 5,将生成5个题目):");
    scanf("-n %d",&times);
    printf("本次共%d题,满分100分\n",times);
    //第一个for循环,每次生成一个题目 
    for(int j=0;j<times;j++){
        printf("%d: ",j+1);
        
        //得到后缀表达式
        char* expQueue=new char[100];
        expQueue=getPostfix(); 
        //计算四则运算式的正确结果 
        char* rightAns=getResOfPostfix(expQueue);
        //printf("%s\n",rightAns);
        
        //用户输入计算结果
        char userAns[100];
        cin>>userAns;
        int c=getchar();
        //判断对错 
        if(strcmp(userAns,rightAns)==0)
            printf("正确!\n");
        else {
            printf("不正确!正确答案= %s\n",rightAns);
            //扣分
            score-=100*1.0/times; 
        }
        
    }
    printf("本次得分%.2f",score);    
    return 0;    
}
main

4. 遇到了一些问题:

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

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

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

5.测试运行

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

基本功能:

 支持附加功能

6.PSP

PSP2.1Personal 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管理自己的项目,给自己的开发有很大的帮助。

posted @ 2017-09-26 21:57  云淡zhy  阅读(288)  评论(2编辑  收藏  举报