• 四则运算生成程序-结对项目


     

    一、github项目地址:https://github.com/wc-TST-2020/Myapp  

      项目参与者:李东阳 (3118005055)   李泽辉(3118005058)

    二、PSP2.1表格

     

    PSP2.1

    Personal Software Process Stages

    预估耗时(分钟)

    实际耗时(分钟)

    Planning

    计划

     80

     60

    · Estimate

    · 估计这个任务需要多少时间

     80

     60

    Development

    开发

    1510

    1755

    · Analysis

    · 需求分析 (包括学习新技术)

     100

     105

    · Design Spec

    · 生成设计文档

     30

     25

    · Design Review

    · 设计复审 (和同事审核设计文档)

     40

     50

    · Coding Standard

    · 代码规范 (为目前的开发制定合适的规范)

     40

     50

    · Design

    · 具体设计

     50

     45

    · Coding

    · 具体编码

     1000

     1200

    · Code Review

    · 代码复审

     50

     60

    · Test

    · 测试(自我测试,修改代码,提交修改)

     200

     220

    Reporting

    报告

     240

     200

    · Test Report

    · 测试报告

     150

     120

    · Size Measurement

    · 计算工作量

     30

     40

    · Postmortem & Process Improvement Plan

    · 事后总结, 并提出过程改进计划

     60

     40

    合计

     

     1830

     2015 


    三、效能分析

      改进程序性能花费了两三天的时间,主要集中在对答案生成模块进行优化上,程序中消耗最大的函数也为答案生成函数。

        由于采用了时间函数,导致生成运算式的速度最快为一秒一条,加上答案生成模块运用了链表数据结构,故内存的分配等又造成了一定的时间花销,总体

      上运算式和答案的生成速度还比较慢。

    四、设计实现过程

      1.运算式生成模块利用随机数生成函数随机生成数字,然后随机生成加减乘除符号和自然数、真分数、带分数等运算数,最后把各项拼接成一条运算式

      2.答案生成模块利用链表,对运算式中的各部分进行拆分和连接等操作,分步运算,设计过程如下图:

     

     

     

    五、代码说明

      1.生成运算式模块

    char *creat_term(int max_num,int term_length){/* 生成项的函数 */
        int i, j, type, num1, num2;//num1、num2是随机生成的整数,type用来指明参与运算的数的类型
        char *term;//term用来存放最终拼接成的项
        char num_term[20];//num_term用来存放整数转换成的字符串
        char part[5][20];
        //二维数组part用来存放这次生成的项的各个组成部分
        //0、2、4列存放项,1、3列存放运算符
        term = (char *)malloc(20 * sizeof(char));//为term申请空间
        memset(term, 0x00, sizeof (char) * 20);//初始化term
        if (term_length) strcpy(term,"(");//如果这个项被分配有运算符则在项的开头加上'('
        srand((unsigned)time(NULL));//生成随机数的种子
        for(i = 0;i < term_length; i++){/* 生成项的运算符 */
            j = rand() % 4;    //生成并项的运算符
            switch(j){
                case 0: strcpy(part[2 * i + 1],"+"); break;
                case 1: strcpy(part[2 * i + 1],"-"); break;
                case 2: strcpy(part[2 * i + 1],"x"); break;
                case 3: strcpy(part[2 * i + 1],"/"); break;
            }
        }
        for(i = 0;i < term_length + 1; i++){/* 生成参与运算的数 */
            //Sleep(1000);
            //srand((unsigned)time(NULL));
            type = rand() % 10;//用随机来判定生成参与运算的数的类型
            switch(type){
                case 8://生成小于一的分数
                    do{
                        strcpy(part[2 * i],"(");//在分数前加上'('来区分分数和除运算
                        //srand((unsigned)time(NULL));
                        do{
                            num1 = rand() % max_num + 1;//生成分母
                            num2 = rand() % num1 + 1;//生成分子
                            num1 = num1 / common_divisor(num1,num2);//化简
                            num2 = num2 / common_divisor(num1,num2);
                        }while(num1 <= num2);/*避免生成非真分数*/
    
                        itoa(num2,num_term,10);//将分子转换成字符串
                        strcat(part[2 * i],num_term);//将二维数组的相应列与生成的字符串拼接起来
                        strcat(part[2 * i],"/");//将二维数组的相应列与'/'拼接起来
                        itoa(num1,num_term,10);//将分母转换成字符串
                        strcat(part[2 * i],num_term);//将二维数组的相应列与生成的字符串拼接起来
                        strcat(part[2 * i],")");//在分数前加上')'来区分分数和除运算
                    }while(num1 == num2);/*避免生成 m/m 类型的分数*/
                    break;
                case 9://生成大于一的分数
                    do{
                        strcpy(part[2 * i],"(");//在分数前加上'('来区分分数和除运算
                        //srand((unsigned)time(NULL));
                        do{
                            num1 = rand() % max_num + 1;//随机生成分数的小数部分
                        }while(num1 >= max_num);/*保证生成的分数在指定范围内*/
                        itoa(num1,num_term,10);//将生成的整数转换成字符串
                        strcat(part[2 * i],num_term);//将二维数组的相应列与生成的字符串拼接起来
                        strcat(part[2 * i],"'");//将二维数组的相应列与'''拼接起来
                        //srand((unsigned)time(NULL));/*以下与生成小于一的分数类似*/
                        do{
                            num1 = rand() % max_num + 1;
                            num2 = rand() % num1 + 1;
                            num1 = num1 / common_divisor(num1,num2);//化简
                            num2 = num2 / common_divisor(num1,num2);
                        }while(num1 <= num2);/*避免生成非真分数*/
    
                        itoa(num2,num_term,10);
                        strcat(part[2 * i],num_term);
                        strcat(part[2 * i],"/");
                        itoa(num1,num_term,10);
                        strcat(part[2 * i],num_term);
                        strcat(part[2 * i],")");//在分数前加上')'来区分分数和除运算
                    }while(num1 == num2);/*避免生成 m/m 类型的分数*/
                    break;
                default://随机数为0~7则生成整数
                    //srand((unsigned)time(NULL));
                    num1 = rand() % max_num + 1;//随机生成指定范围内的整数
                    itoa(num1,part[2 * i],10);//将生成的整数转换成字符串并将其存放在二维数组part的相应位置上
                    break;
            }
        }
        for(i = 0; i < 2 * term_length + 1; i++){/* 将二维数组中的各列拼接成项 */
            if(i) strcat(term," ");//在运算符前面加一个空格
            strcat(term,part[i]);
        }
        if (term_length) strcat(term,")");
        return term;
    }
    //我将不是项里面的运算符成为主干运算符
    char *creat_operation(int max_num){//系统会把空格读成第一个元素//int argc,char *argv[]
        int formula_length, i, j;//formula_length指的是生成的运算式的长度,即运算的运算符数目
        int term_length[5];
        //term指的是运算式各部分被分配的运算符数目,
        //term_length[0]是运算式主干被分配的运算符数目,
        //term_length[1~4]是运算式第1到第4项被分配的运算符数目
        char *term;//term用来存放最终生成的运算式
        char part[7][100];
        //二维数组part用来存放生成的运算式的各个部分,
        //0、2、4、6列存放项,1、3、5列存放运算符
        term = (char *)malloc(100 * sizeof(char));//为term申请空间
        memset(term, 0x00, sizeof (char) * 100);//初始化term
        //库函数memset(<字符指针>,<命令(0x00是将字符数组置空)>,<长度>)是用来初始化字符数组的
        strcpy(term," ");//本来是用来初始化
        srand((unsigned)time(NULL));//生成随机数的种子
        formula_length = rand() % 3 + 1;
        for(i = 0; i < 7; i++){//初始二维数组part
            strcpy(part[i]," ");//起初写这个语句是因为输出的字符串出现了乱码
        }                        //不过后来才清楚出现乱码是因为term没有初始化
        for(i = 0; i < 5; i++){//初始term_length,这一步是必要的
            term_length[i] = 0;
        }
        ++term_length[0];//主干运算符必须要有一个
        //srand((unsigned)time(NULL));
        for(i = formula_length - 1; i ; i--){//随机分配运算符
            j = rand() % 5;
            ++term_length[j];
        }
        //srand((unsigned)time(NULL));
        for(i = 0;i < term_length[0]; i++){ /* 为运算式生成运算符(主干运算符) */
            j = rand() % 4;
            switch(j){
                case 0: strcpy(part[2 * i + 1],"+"); break;
                case 1: strcpy(part[2 * i + 1],"-"); break;
                case 2: strcpy(part[2 * i + 1],"x"); break;
                case 3: strcpy(part[2 * i + 1],"/"); break;
            }
        }
        for(i = 0; i < term_length[0] + 1; i++){ /* 生成项 */
            Sleep(1000);//延迟一秒,不然输出的项是相同的
            strcpy(part[2 * i],creat_term(max_num,term_length[i + 1]));
        }
        for(i = 0; i < 2 * term_length[0] + 1; i++){ /* 拼接成运算式 */
            if(i) strcat(term," ");//运算符前后加上空格
            strcat(term,part[i]);
        }
        strcat(term," = ");//在运算式的最后加上'='
        return term;

      2.答案生成模块

    float fraction(float num1,float num2,float num3){//计算非真分数的函数
        num2 = num2 / num3;
        return num1 + num2;
    }
    
    float creat_part_answer(LinkedList &List){//关键函数,已经验证过,可以使用。但是没有释放掉无用的动态空间,有可能会发生内存泄漏,未来需要改进这一点
        LinkedList temp1, temp2, temp3, PartList;
        creat_LinkedList(PartList);
        temp1 = List->next;//temp1用来检查节点
        temp1 = temp1->next;//初始化指向
        while(temp1 != List->next){
            if(temp1->sign == 6){//若找到(
                temp2 = temp1->next;//temp2寻找相应的temp1指向的(对应的)
                while(1){
                    if(temp2->sign == 7 && temp1->tag - temp2->tag == 1) break;
                    insert_LinkedList(PartList,temp2->num,temp2->sign,temp2->tag);//将成对的括号里的内容截取到另一个独立的单循环链表里
                    temp2 = temp2->next;
                }
                temp1->num = creat_part_answer(PartList);//将括号里的计算结果放入(所在的节点
                temp1->sign = 0;//将temp1转化成存放数字的节点
                temp1->tag = 0;//
                if(List == temp2) List = temp1;//如果参与运算的内容有运算式末端的数字或运算符,则将List指向存放运算结果的节点
                temp1->next = temp2->next;//删除已经参与运算的内容
            }
            temp1 = temp1->next;
        }
        temp1 = List->next;
        temp2 = temp1;
        temp1 = temp1->next;
        while(temp1 != List->next){//寻找非真分数的标志'
            if(temp1->sign == 5){//以2'3/4为例,temp1指向的是’,temp2指向的是2
                temp1 = temp1->next;//将temp1的指向改为3
                temp3 = temp1->next->next;//temp3指向4
                temp2->num = fraction(temp2->num,temp1->num,temp3->num);//将运算结果存放在temp2指向的节点
                if(temp3 == List) List = temp2;
                temp2->next = temp3->next;//去除运算式中已经参与运算的部分
                temp1 = temp2->next;//调整temp1,为下一次循环做准备
                continue;
            }
            temp2 = temp1;
            temp1 = temp1->next;
        }
        temp1 = List->next;
        temp2 = temp1;
        temp1 = temp1->next;
        while(temp1 != List->next){
            if(temp1->sign == 3 || temp1->sign == 4){//检测到有运算符乘或除,temp1指向运算符,temp2指向被乘数或被除数
                temp3 = temp1->next;//temp3指向乘数或除数
                temp2->num = calculate(temp2->num,temp3->num,temp1->sign);//将运算结果存放在temp2指向的节点
                if(List == temp3) List = temp2;
                temp2->next = temp3->next;//去除运算式中已经参与运算的部分
                temp1 = temp2->next;//调整temp1,为下一次循环做准备
                continue;
            }
            temp2 = temp1;
            temp1 = temp1->next;
        }
        temp1 = List->next;
        temp2 = temp1;
        temp1 = temp1->next;
        while(temp1 != List->next){
            if(temp1->sign == 1 || temp1->sign == 2){//检测到有运算符加或减,temp1指向运算符,temp2指向被加数或被减数
                temp3 = temp1->next;//temp3指向加数或减数
                temp2->num = calculate(temp2->num,temp3->num,temp1->sign);
                if(List == temp3) List = temp2;
                temp2->next = temp3->next;//去除运算式中已经参与运算的部分
                temp1 = temp2->next;//调整temp1,为下一次循环做准备
                continue;
            }
            temp2 = temp1;
            temp1 = temp1->next;
        }
        return List->num;
    }
    
    float create_answer(char *operation){
        LinkedList List;//存放运算式的链表
        char *TempOperation;//存放运算式的字符串
        char TempPart[20];//存放被截取的一部分的运算式的字符串
        int flag;//控制是否将数字字符串转换成整型的开关
        int sign;//运算符的抽象代表
        int tag = 1;//括号的编号
        int IntNum;//转换成整型的字符串
        float FloatNum;//转换成浮点型的字符串
        int i, j;//你懂的
        TempOperation = (char *)malloc(100 * sizeof(char));
        memset(TempOperation, 0x00, sizeof (char) * 100);//初始化
        creat_LinkedList(List);//初始化链表
        strcpy(TempOperation,operation);//如果输入的字符串就是运算式
        for(i = 0; i < 20; i++){TempPart[i] = '';}
        i = 0;
        j = 0;
        flag = 0;
        while(TempOperation[i] != ''){//将运算式拆散并装入链表
            if(TempOperation[i] >= '0' &&TempOperation[i] <= '9'){//获取数字字符
                TempPart[j] = TempOperation[i];
                j++;
            }
            if(j > 0 && (TempOperation[i + 1] < '0' || TempOperation[i + 1] > '9')) {//获取字符完毕,将其转换换成字符串
                TempPart[j] = '';
                flag = 1;
            }
            if(flag == 1){//将获取的数字字符串转换成整型,进而转换成浮点型
                IntNum = atoi(TempPart);
                FloatNum = (float) IntNum;
                insert_LinkedList(List,FloatNum,0,0);
                //sign是运算符(0是数字,1是加,2是减,3是乘,4是除,5是',6是(,7是))
                //tag是括号的编号
                flag = 0;
                j = 0;
            }
            //将运算符装入单循环链表
            if(TempOperation[i] == '+' || TempOperation[i] == '-' || TempOperation[i] == 'x' || TempOperation[i] == '/' || TempOperation[i] == 39 || TempOperation[i] == '(' || TempOperation[i] == ')'){
                switch (TempOperation[i]) {
                    case '+':
                        sign = 1;
                        insert_LinkedList(List,0.0,sign,0);//装入加号
                        break;
                    case '-':
                        sign = 2;
                        insert_LinkedList(List,0.0,sign,0);//装入减号
                        break;
                    case 'x':
                        sign = 3;
                        insert_LinkedList(List,0.0,sign,0);//装入乘号
                        break;
                    case '/':
                        sign = 4;
                        insert_LinkedList(List,0.0,sign,0);//装入除号
                        break;
                    case 39:
                        sign = 5;
                        insert_LinkedList(List,0.0,sign,0);//装入字符 '
                        break;
                    case '(':
                        sign = 6;
                        tag++;//设置括号编号
                        insert_LinkedList(List,0.0,sign,tag);//装入括号(,和对应编号
                        break;
                    case ')':
                        sign = 7;
                        tag--;//设置括号编号
                        insert_LinkedList(List,0.0,sign,tag);//装入括号),和对应编号
                        break;
                }
            }
            i++;
        }
        return creat_part_answer(List);
    }
    
    //将计算结果从小数(浮点型)转化成分数(字符串)
    char* creat_char_answer(float answer, int max_num){//answer是输入的小数答案,max_num本来是控制参与运算的数的范围,如今用来控制分数精度
        char *char_answer;//存放最终结果的字符串
        char part_num[20];
        int IntAnswer;
        int num1, num2, i, j;
        float FloatAnswer;
        IntAnswer = (int) answer;//截取整数部分
        FloatAnswer = answer - (float)IntAnswer;//截取小数部分(有误差)
        char_answer = (char *)malloc(20 * sizeof(char));
        memset(char_answer, 0x00, sizeof (char) * 20);
        for(i = 0; i < 20; i++){//初始化存放一部分整数的字符数组
            part_num[i] = '';
        }
        if(FloatAnswer == 0.0 || (FloatAnswer == 0.0 && IntAnswer == 0)){//如果答案为零就输出0
            itoa(IntAnswer,char_answer,10);
            return char_answer;
        }
        if(IntAnswer == 0){//如果答案为小于一的小数
            for(i = 1; i <= max_num; i++){
                num2 = i;
                for(j = 1; j <= max_num; j++){
                    num1 = j;
                    if(answer == (float)num1 / (float)num2){//重新计算,挑选出结算结果和答案一致的两个整数
                        break;
                    }
                }
                if(answer == (float)num1 / (float)num2){
                    break;
                }
            }
            itoa(num1,char_answer,10);
            itoa(num2,part_num,10);
            strcat(char_answer,"/");
            strcat(char_answer,part_num);
        }
        if(IntAnswer != 0 && FloatAnswer != 0.0){//如果答案为大于一的小数
            itoa(IntAnswer,char_answer,10);
            strcat(char_answer,"'");
            for(i = 1; i <= max_num; i++){
                num2 = i;
                for(j = i; j <= max_num; j++){
                    num1 = j;
                    if(answer == (float)num1 / (float)num2){//重新计算,挑选出结算结果和答案一致的两个整数
                        break;
                    }
                }
                if(answer == (float)num1 / (float)num2){
                    break;
                }
            }
            /*num1 = num1 / common_divisor(num1,num2);//化简
            num2 = num2 / common_divisor(num1,num2);*/
            if(answer == (float)num1 / (float)num2){
                num1 = num1 - num2 * IntAnswer;
            }
    
            itoa(num1,part_num,10);
            strcat(char_answer,part_num);
            strcat(char_answer,"/");
            itoa(num2,part_num,10);
            strcat(char_answer,part_num);
        }
        if(num1 == num2){//如果转换失败则返回ERROR字符串
            strcpy(char_answer,"ERROR");
            return char_answer;
        }
        return char_answer;
    }

      3.求最大公因数函数

    int common_divisor(int n,int m){//求最大公因数
        int temp,r;//把大的数放在n里面
        if(n<m){
            temp=n;
            n=m;
            m=temp;
        }
        while(m!=0){
            r=n%m;
            n=m;
            m=r;
        }
        return n;//返回最大公因数
    }

      4.头文件和结构体等

    # include <stdio.h>
    # include <stdlib.h>
    # include <string.h>
    # include <windows.h>
    # include <ctime>
    
    
    
    typedef struct LinNode{//结构体,包含有参与运算的数字、运算符的代号、括号的编号
       float num;
       int sign;
       int tag;
       struct LinNode *next;
    }LinNode, *LinkedList;
    
    int creat_LinkedList(LinkedList &List){//创建一个空的单循环链表
        List = (LinkedList)malloc(sizeof(LinNode));
        if(List == NULL) return -1;
        List->next = List;
        return 1;
    }
    
    int  insert_LinkedList(LinkedList &List,float num,int sign,int tag){//向单循环链表里插入节点
        LinkedList temp;
        temp = (LinkedList)malloc(sizeof(LinNode));
        if(temp == NULL) return -1;
        temp->num  = num;
        temp->sign = sign;
        temp->tag = tag;
        temp->next = List->next;
        List->next = temp;
        List = temp;
        return 1;
    }

      5.主函数

    int main(int argc,char *argv[]){//主函数
        int i, number = 10000;//number用来控制生成题目的数量,默认是10000
        int Max = 0; //Max用来控制生成参与运算的数字的最大值
        int flag = 0;//flag用来检测是否有输入参与运算的数的最大值
        char *term;//term用来存放完成初次处理的字符串
        char times[100];//times用来存放完成最终处理的字符串
        for(i = 0; i < argc; i++){//检测输入的指令
            if(strcmp(argv[i],"-n") == 0){//检测到有输入指令 -n
                number = atoi(argv[++i]);//将生成题目的数量改成输入的数
            }
            if(strcmp(argv[i],"-r") == 0){//检测到有输入指令 -r
                Max = atoi(argv[++i]);//将Max设置为输入的数
                flag = 1;//设置已经输入Max的标志
            }
        }
        if(flag == 0){//如果没有输入Max的指令
            printf("input the max number
    ");//提示
            return -1;//结束运行
        }
        FILE *fpWrite1 = fopen("Exercises.txt","w");//创建存放运算式的文件
        FILE *fpWrite2 = fopen("Answers.txt","w");//创建存放计算结果的文件
        if(fpWrite1 == NULL || fpWrite1 == NULL){//创建失败,则提示异常并结束程序的运行
            printf("fail to open file
    ");
            return -1;
        }
        for(i = 0; i < 100; i++){//初始化存放最终结果的字符数组
            times[i] = '';
        }
        term = (char *)malloc(100 * sizeof(char));//申请动态空间
        memset(term, 0x00, sizeof (char) * 100);//初始化存放完成初步处理的字符串的字符指针
        for(i = 0; i < number; i++){//开始生成运算式
            do{
                strcpy(term,creat_operation(Max));//将生成的运算式复制到term
            }while(create_answer(term) < 0 || strcmp(creat_char_answer(create_answer(term),1000),"ERROR") == 0);//如果生成的运算式不符合要求则重新生成
            itoa(i+1,times,10);//将运算式序号转化成字符串
            strcat(times," :");
            strcat(times,term);//将序号和运算式连接起来
            strcat(times,"
    ");
            puts(times);//输出运算式
            fputs(times, fpWrite1);//将完成最终处理的运算式存入指定文件中
            itoa(i+1,times,10);//将答案序号转化成字符串
            strcat(times," :");
            strcat(times,creat_char_answer(create_answer(term),1000));//将序号和答案连接起来
            strcat(times,"
    ");
            puts(times);//输出答案
            fputs(times, fpWrite2);//将完成最终处理的答案存入指定文件中
        }
        fclose(fpWrite1);//关闭存放运算式的文件
        fclose(fpWrite2);//关闭存放答案的文件
        return 0;
    }

    六、测试运行

      1.测试用例1:在命令行下切换到存放Myapp.exe文件的目录,这里是e盘

       输入Myapp.exe,出现输入最大值的提醒,重新输入命令行

       Myapp.exe -r 10 -n 20,表示生成20条最大值为10的运算式

      

      生成的运算式存放在同目录的Exercises.txt文件中

       

      生成的答案存放在同目录的Answers.txt文件中

       

      2.测试用例2:(具体操作同上)

      

       

       

       3.测试用例3:输入myapp.exe -r 50

      表示生成最大值为50的运算式,因为没有指定数量,所以默认生成一万条,

      但是生成的速度有点慢,所以没有完全生成,故不能生成两个.txt文件

      

       4.测试用例4:生成10条最大值为500的式子

      

       5.测试用例5:成功生成5条最大值为1000的式子,

      证明该程序可以生成较大的数

      

       6.测试用例6:结果无误

      

       7.测试用例7:结果无误

      

       8.测试用例8:结果无误

      

    七、PSP实际用时:见上表。

    八、项目小结

      1.结对项目考验两个人的协作能力,时间分配能力,对初次体验这种项目的我们来说都是不小的挑战,好在程序最终得以出炉,

      2.由于时间关系和能力限制,我们这次作业的需求没有完全实现,比如改卷模块没有实现,这是一个不小的遗憾。

      3.结对编程使得代码在编写后能立即得到别人的建议和批评,对代码的开发效率有很大的提升。

  • 相关阅读:
    非阻塞式NIO 小案例(模拟聊天室)
    网络通信小案例,服务端接收成功要给客户端一个反馈(阻塞式)
    阻塞式网络通信小案例:
    NIO的非阻塞式网络通信
    字符编码
    使用分散(Scatter)与聚集(Gather)来实现文件的复制
    使用通道之间的数据传输(效果,也是实现文件的复制)
    创建直接缓存区完成文件的复制
    C++预处理详解
    C++的学习资源
  • 原文地址:https://www.cnblogs.com/lizehui/p/12616275.html
Copyright © 2020-2023  润新知