结对第二次作业

1.题目的选择

在这次结对编程中我担任的是驾驶员的角色,主要负责基本的代码工作。
由于之前做过类似的题目,所以选择了题目一,具体要求如下:

  • 能够自动生成四则运算练习题
  • 可以定制题目数量
  • 用户可以选择运算符
  • 用户设置最大数(如十以内、百以内等)
  • 用户选择是否有括号、是否有小数
  • 用户选择输出方式(如输出到文件、打印机等)
  • 最好能提供图形用户界面(根据自己能力选做,以完成上述功能为主)

主要内容是制作一个可以自动生成四则运算的程序,并且可以根据用户的需求做适当的改变。关于代码,如果只是生成四则运算式而不用计算结果、判断对错,那么这个程序将相对简单些。但是考虑到这些功能对于使用者而言也是非常实用的,我在编写程序是也写入了计算与判断部分。至于图形界面方面,由于从来没用过Qt,放弃了.....


2.开发环境

编程语言:c++
开发环境:codeblocks
运行环境:windows 7


3.程序设计

代码已经上传到coding:https://coding.net/u/houzhuzhu/p/aaa/git/blob/master/软工.c?public=true

程序主要是通过堆栈实现,首先要用到以下几个类型节点与指针:

(1)StackChar类型的节点

SC:

typedef struct StackChar
{
    char c;
    struct StackChar *next;
}SC;

SF:

typedef struct StackFloat
{
    float f;
    struct StackFloat *next;
}SF;

(2)SC类型的的指针

Push:

SC *Push(SC *s,char c)          
{
    SC *p=(SC*)malloc(sizeof(SC));
    p->c=c;
    p->next=s;
    return p;
}

POP:

SC *Pop(SC *s)    
{
    SC *q=s;
    s=s->next;
    free(q);
    return s;
}

(3)SF类型的指针

Push:

SF *Push(SF *s,float f)        
{
    SF *p=(SF*)malloc(sizeof(SF));
    p->f=f;
    p->next=s;
    return p;
}

Pop:

SF *Pop(SF *s)      
{
    SF *q=s;
    s=s->next;
    free(q);
    return s;
}

准备好这些节点与指针以便后面堆栈函数的具体操作。

(4)符号生成函数

这个函数主要是在最后的主函数数中能够生成符合用户要求的运算式中的运算符,返回值是生成的符号

 char create_symbol(int n)
{
    int n1,j;
    char symbol[1];
    if(n==0)
    {
        n1=2;
    }
    else if(n=1)
    {
        n1=4;
    }
    j=random()%n1;
    if(j==0) symbol[0]='+';
    else if(j==1) symbol[0]='-';
    else if(j==2) symbol[0]='*';
    else symbol[0]='/';
    return symbol[0];
 }

(5)字符串生成函数

该部分有两个函数:string、combination。
string函数的功能是将数字转化为字符串,combination是将分数转化为字符串。转化为字符串的目的是为了在主函数中的生成最后的运算式

string:

string int_string(int number)
 {
    char str[200];
    itoa(number,str,10);
    string str_=str;
    return str_;
  }

combination:

string combination1(string str1,string str2,char k)
 {
     string equation;
     equation='('+str1+k+str2+')';
     return equation;
 }

(6)计算函数

运算式生成所需要的函数写完了之后就到了这个程序最难的部分————运算式计算函数。这个函数的难点主要在于乘除法的运算优先级要高于加减法,而如果只是让机器单纯的从左往右运算,就会产生错误,所以在这里我设置了一个运算符优先级表:

unsigned char Prior[7][7] =
{ 
    // '+' '-' '*' '/' '(' ')' '#'
    /*'+'*/'>','>','<','<','<','>','>',
    /*'-'*/'>','>','<','<','<','>','>',
    /*'*'*/'>','>','>','>','<','>','>',
    /*'/'*/'>','>','>','>','<','>','>',
    /*'('*/'<','<','<','<','<','=',' ',
    /*')'*/'>','>','>','>',' ','>','>',
    /*'#'*/'<','<','<','<','<',' ','=',
};

用了两个函数来实现,主要是堆栈思想,最简单的一个运算式“1+1=2”是根据数字、运算符、数字、“=”、数字的顺序逐一进行判断,是否是需要的部分,如果是则入栈,最后逐一出栈,并根据运算符优先表的有限顺序进行计算,并得出最后的结果。最后的结果存在*OPND指针中,并且将结果入栈。

float EvaluateExpression(char* MyExpression)
{
   
    SC *OPTR=NULL;       // 运算符栈,字符元素
    SF *OPND=NULL;       // 运算数栈,实数元素
    char TempData[20];
    float Data,a,b;
    char theta,*c,Dr[]={'#',''};
    OPTR=Push(OPTR,'#');
    c=strcat(MyExpression,Dr);
    strcpy(TempData,"");//字符串拷贝函数
    while (*c!= '#' || OPTR->c!='#')
    {
        if (!In(*c, OPSET))
        {
            Dr[0]=*c;
            strcat(TempData,Dr);           //字符串连接函数
            c++;
            if (In(*c, OPSET))
            {
                Data=atof(TempData);       //字符串转换函数(double)
                OPND=Push(OPND, Data);
                strcpy(TempData,"");
            }
        }
        else    // 不是运算符则进栈
        {
            switch (precede(OPTR->c, *c))
            {
            case '<': // 栈顶元素优先级低
                OPTR=Push(OPTR, *c);
                c++;
                break;
            case '=': // 脱括号并接收下一字符
                OPTR=Pop(OPTR);
                c++;
                break;
            case '>': // 退栈并将运算结果入栈
                theta=OPTR->c;OPTR=Pop(OPTR);
                b=OPND->f;OPND=Pop(OPND);
                a=OPND->f;OPND=Pop(OPND);
                OPND=Push(OPND, Operate(a, theta, b));
                break;
            } //switch
        }
    } //while
    return OPND->f;
} 
float get_ans(string str)
{
    int len;
    float ans;
    len=str.length();
    char num[len];
    for(int j=0;j<len;j++)
    {
        num[j]=str[j];
    }
    //用堆栈解决。。。
    ans=EvaluateExpression(num);
    return ans;
}

(7)主函数

  • 主函数中主要是根据用户需求输入几个限制变量,具体的输入输出都是在主函数中实现的
  • 主函数中还有判断生成的算术是是否重复的功能
  • 主函数中还有判断结果是否正确的功能
  • 设置时间种子,使得程序每次运行的结果都不同
 int main()
{
    srand((int)time(NULL)); 
    int num1,num2,num3,num4,count,n,change,amount,shuchu,range,j,repeat=0,bracket,proper_fs,right=0,wrong=0;
    string str_num1,str_num2,temp;
    float Answer,InputAns;
    cout<<"有无乘除法?1有,0没有:"<<endl;
    cin>>n;
    cout<<"是否有括号?1有,0没有:"<<endl;
    cin>>bracket;
    cout<<"是否有真分数?1有,0没有:"<<endl;
    cin>>proper_fs;
    cout<<"请输入数字范围:"<<endl;
    cin>>range;
    cout<<"请输入出题数量:"<<endl;
    cin>>amount;
    string Equation[200];
    char symbol;
    cout<<amount<<"道四则运算题如下:"<<endl;
    for(int i=0;i<amount;i++)
    {
        count=random()%3+2;
        str_num1=create_num(proper_fs,range);
        str_num2=create_num(proper_fs,range);
        symbol=create_symbol(n);
        Equation[i]=combination(str_num1,str_num2,symbol);
        if(count>2)
        {
            for(count;count>2;count--)
            {
                symbol=create_symbol(n);
                str_num1=Equation[i];
                if(bracket==1)
                {
                    change=random()%3;
                    if(change==0)
                    {
                       str_num1='('+str_num1+')';
                     }
                }
                symbol=create_symbol(n);
                str_num2=create_num(proper_fs,range);
                change=random()%2;
                if(change==0)
                {
                    temp=str_num1;
                    str_num1=str_num2;
                    str_num2=temp;
                }
                Equation[i]=combination(str_num1,str_num2,symbol);
            }
        }
        //判断是否重复
         for(j=0;j<i;j++)
         {
             if(Equation[j]==Equation[i])
             {
                i=i-1;
                repeat=1;
                break;
             }
         }
         if(repeat!=1)//若不重复,则输出
         {
            cout<<Equation[i]<<"=";
            //判断结果是否正确
            cin>>InputAns;
            Answer=get_ans(Equation[i]);
            Answer*=100;
            int temp=(int)Answer;
            Answer=((double)temp)/100.00;
            if(InputAns==Answer)
            {
                cout<<"回答正确!";
                right++;
            }
            else
            {
                cout<<"回答错误!正确答案为";
                cout<<setprecision(2)<<fixed<<Answer;
                wrong++;
            }
            cout<<endl;
         }
    }
    cout<<"一共答对"<<right<<"道题,答错"<<wrong<<"道题。"<<endl;
}

4.对伙伴的评价

这次的结对编程作业中,高阳同学担任我的领航员,辅助我完成了此次代码的编写并完成了所有的单元测试。说实话,刚开始只是打算做到生成算术式就够了,在决定做出算术结果和判断对错后,遇到了很多问题。我在算法上有很多思想都是来自于高阳同学,而且有Bug时也是他帮我改正,不得不说他在此次作业完成中占据了很大一部分,完美的完成了领航员的任务。


5.总结

不得不再一次强调,做运算结果实在是太难了!!!运算部分遇到了很多问题:没办法按照运算符的优先级来进行运算、分数的运算、结果精度的不确定以及括号的应用。翻了一些资料,最后在以前的数据结构作业中找到了思路,就是建立运算符优先表,这个发现可以说是帮了我大忙。另外的问题是用在网上查到的浮点数的相关操作解决的,过程也是相当艰辛的,甚至一度想过放弃运算步骤。不过好的是最后的结果还是挺令我满意的,毕竟一分耕耘一分收获嘛。做完运算后我当机立断要给程序加一个判断的步骤,这个过程相对比较简单,而且我想如果我是在给一个用户做程序,那么这个步骤一定是他非常需要的。可以说这次的程序是一小步一小步完成的,这大概是做软件工程作业以来写过最复杂的程序了。看到其他同学都做出了界面,有些甚至把界面功能做的十分丰富,要说不羡慕是不可能的。我在完成了最后的编码工作后,也尝试过学习界面制作,但是之前是一点都没有接触过,而且据说我最擅长的C语言是不适合做界面的,最后不得已放弃了这个想法。经过了这些,我也确实认识到了自身的实力与别人的差距。从来没有尝试过做一个完整的软件导致了我做这次的作业困难重重。认识到了这些,这次作业完成后我第一个要做的事就是尝试着了解Qt软件,学习一下界面的知识。对了,还有就是多学一门语言还是相当重要的。不说了,去学java了......
最后上传一张我们的合照:
image

原文地址:https://www.cnblogs.com/jing-hao/p/8858453.html