四則運算(C語言實現)


四則運算(c語言實現)

 合伙人:魏甫——3118004973  ,溫欽益——3118004975

 https://github.com/iamdate/work/tree/master 

一.項目及其要求

  1.題目:實現一個自動生成小學四則運算題目的命令行程序(也可以用圖像界面,具有相似功能)。

  2說明:

    自然數:0, 1, 2, …。

    •   真分數:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
    •   運算符:+, −, ×, ÷。
    •   括號:(, )。
    •   等號:=。
    •   分隔符:空格(用於四則運算符和等號前后)。
    •   算術表達式:

      e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),

      其中e, e1和e2為表達式,n為自然數或真分數。

    •   四則運算題目:e = ,其中e為算術表達式。

 

  3需求:

  1.   使用 -n 參數控制生成題目的個數,例如Myapp.exe -n 10將生成10個題目。
  1.   使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的范圍,例如

 

Myapp.exe -r 10

 

將生成10以內(不包括10)的四則運算題目。該參數可以設置為1或其他自然數。該參數必須給定,否則程序報錯並給出幫助信息。

  1. 生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1− e2的子表達式,那么e1≥ e2。
  2. 生成的題目中如果存在形如e1÷ e2的子表達式,那么其結果應是真分數。
  3. 每道題目中出現的運算符個數不超過3個。
  4. 程序一次運行生成的題目不能重復,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換為同一道題目。例如,23 + 45 = 和45 + 23 = 是重復的題目,6 × 8 = 和8 × 6 = 也是重復的題目。3+(2+1)和1+2+3這兩個題目是重復的,由於+是左結合的,1+2+3等價於(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重復的兩道題,因為1+2+3等價於(1+2)+3,而3+2+1等價於(3+2)+1,它們之間不能通過有限次交換變成同一個題目。

生成的題目存入執行程序的當前目錄下的Exercises.txt文件,格式如下:

 

  1. 四則運算題目1
  2. 四則運算題目2

……

 

其中真分數在輸入輸出時采用如下格式,真分數五分之三表示為3/5,真分數二又八分之三表示為2’3/8。

  1. 在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件,格式如下:

 

  1. 答案1
  2. 答案2

 

特別的,真分數的運算如下例所示:1/6 + 1/8 = 7/24。

  1. 程序應能支持一萬道題目的生成。
  2. 程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計,輸入參數如下:

 

Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt

 

統計結果輸出到文件Grade.txt,格式如下:

 

Correct: 5 (1, 3, 5, 7, 9)

Wrong: 5 (2, 4, 6, 8, 10)

 

其中“:”后面的數字5表示對/錯的題目的數量,括號內的是對/錯題目的編號。為簡單起見,假設輸入的題目都是按照順序編號的符合規范的題目。

 

二、所遇困難及解決辦法

  1.如何實現隨機運算符和數字?

   解:1.1:每道題目中出現的運算符個數不超過3個相當於e=a*b/c+d,其中未知數有4個,運算符最大值為三個。問:如何產出最多含4個數字的題目

      1.2:經百度得可使用random()函數隨機生成數字及運算符,后使用switch語句判斷生成題目類型。待續~

   2.如何進行計算?

  解:2.1:題目中所有參數為隨機,只有生成后的題目為已知。問:如何讀取已知的題目,並進行計算測試?

    2.2:第一次測試:采用數組儲存題目,利用switch語句判斷運算符。結果:失敗,運算符多,計算式復雜,數組可能太少。

    2.3:第二次測試:經詢問,采用鏈表方式,編寫逆波蘭式將中綴表達式轉為后綴表達式進行計算。結果:成功,一般情況下中綴表達式計算比較復雜,但將其轉為后綴表達式簡潔很多,創建兩個棧,一個存放操作數,一個存放運算符,計算時將其拿出。

  3.如何將程序寫進文件

  解:3.1:由於之前用java寫個人項目的緣故,得知了io數據流,但c和java用法不一,故前去學習。后用fprintf()將生成表達式一並寫入文件,再將答案數組也寫入文件1。

三、關鍵代碼

  1.中綴轉后綴並計算

復制代碼
void qiuzhi(char *bds)//中轉后並求值 
{  FILE *fp;
    int i = 0;
    stack *ysf = (stack*)malloc(sizeof(stack));//為表達式開辟一個stack
    ysf->size = 0;
    float num[50];//用於求值的數組
    int numpos = 0;//用於求值的數組位置,因使用較少為提高效率選擇數組
    printf("后綴表達式為:");//附加
    while (bds[i] != '=')
    {
        if (bds[i] == '\0')
        {
            printf("表達式應該有=");
            return;
        }
        if (bds[i] <= '9'&&bds[i] >= '0')//轉化數字
        {
            num[++numpos] = 0;
            while (bds[i] <= '9'&&bds[i] >= '0')
            {
                num[numpos] *= 10;
                num[numpos] += (bds[i] - '0');
                ++i;
            }
            if (bds[i] == '.')
            {
                double f_car = 0.1;//定義基數
                ++i;
                while (bds[i] <= '9'&&bds[i] >= '0')
                {
                    num[numpos] += ((bds[i] - '0')*f_car);
                    f_car *= 0.1;
                    ++i;
                }
            }//計算小數點
        }
        else
        {
            if (empty(ysf))
                push(ysf, bds[i]);
            else
            {
                if (bds[i] == '(')
                    push(ysf, bds[i]);
                else if (bds[i] == ')')
                {
                    while (top(ysf) != '(')
                    {
                        reckon(&num[numpos - 1], num[numpos], top(ysf));
                        printf("%c", pop(ysf));
                        --numpos;
                    }
                    pop(ysf);//彈出右括號
                }
                else
                {
                    while (compare(bds[i])<=compare(top(ysf)))
                    {
                        reckon(&num[numpos - 1], num[numpos], top(ysf));
                        printf("%c", pop(ysf));
                        --numpos;
                    }
                    push(ysf, bds[i]);
                }
            }
            ++i;
        }
    }
    while (!empty(ysf))
    {
        reckon(&num[numpos - 1], num[numpos], top(ysf));
        printf("%c", pop(ysf));
        --numpos;
    }

    fopen("/練習程序/answer.txt","w+");
      printf("\n運算結果為:%.2f\n", num[1]);
        fprintf(fp,"%.2f\n",num[1]);

}

復制代碼
復制代碼
void reckon(float *a, float b, char c)//用於將兩數字合並,前數傳地址 
{    //表達式運算定義
    int t;
    if (c == '-')
    {    if(*a<b)//非負 
        {     t=*a;*a=b;b=t;}
        (*a)-=b;
    }
    else if (c == '+')
    {
        (*a) += b;
    }
    else if (c == '*')
    {
        (*a) *= b;
    }
    else     
        if(b!=0)
            (*a) /= b;
               
}
復制代碼

 

 2.分數運算

復制代碼
void qiuzhi1(int a,int b,int c,int d,char s)
   {  int x,y,t,m;
       float p,q;//中間數 
       int re1,re2,u;//分子分母 
       
    x=getGcd(a,b);//對a,b約分
    a/=x;
    b/=x;
    y=getGcd(c,d);//對c,d約分
    c/=y;
    d/=y;
   FILE *fp=fopen("/練習程序/subject.txt","w+");//讀寫文件位置
   
   switch(s)//選取運算符
   {
   case '+':
     re1=a*d+c*b;
     re2=b*d;
     t=getGcd(re1,re2);
     re1/=t;
     re2/=t;
     printf("%d/%d + %d/%d=%d/%d\n",a,b,c,d,re1,re2);
     fprintf(fp, "%d/%d + %d/%d=%d/%d\n",a,b,c,d,re1,re2);
     break;
   case '-':
       p=a/b;
       q=c/d;
       if(p<q)//判斷結果不為負
       {
           u=a;
           a=c;
           c=u;
           u=b;
           b=d;
           d=u;
       }
       re1=a*d-c*b;//結果分子的運算
        re2=b*d;
        t=getGcd(re1,re2);//約分
     re1/=t;
     re2/=t;
        printf("%d/%d - %d/%d=%d/%d\n",a,b,c,d,re1,re2);
        fprintf(fp, "%d/%d - %d/%d=%d/%d\n",a,b,c,d,re1,re2);

     break;
   case '*':
       re1=a*c;
       re2=b*d;
        t=getGcd(re1,re2);
     re1/=t;
     re2/=t;
     if(a==0||c==0)//有分數為0時
        {
        printf("%d/%d * %d/%d=0",a,b,c,d);
         fprintf(fp, "%d/%d * %d/%d=0",a,b,c,d);
     }
     else
        printf("%d/%d * %d/%d=%d/%d\n",a,b,c,d,re1,re2);
        fprintf(fp, "%d/%d * %d/%d=%d/%d\n",a,b,c,d,re1,re2);
        
     break;
   case '/':
       
       re1=a*d;
       re2=b*c;
        t=getGcd(re1,re2);
     re1/=t;
     re2/=t;
     if(a==0)
        {printf("%d/%d / %d/%d=0",a,b,c,d);//結果為0
        fprintf(fp, "%d/%d / %d/%d=0",a,b,c,d);
    }
     else{
        printf("%d/%d / %d/%d=%d/%d\n",a,b,c,d,re1,re2);
        fprintf(fp, "%d/%d / %d/%d=%d/%d\n",a,b,c,d,re1,re2);
          }
        
    }

}
復制代碼

  3.讀取題目信息

復制代碼
void creat1(int num,int r)
{
    char a[]={'+','-','*','/'};
    int X,c,t;
    int i,j,b,count;
    int x,y,x1,y1,z,e;
    int n=sizeof(a);
    srand(time(NULL));
    
    FILE *fp = NULL;//打開文件 
     fp = fopen("/練習程序/test.txt", "w+");
     
    for(j=0;j<num;j++)
    { x=rand()%r;
      y=rand()%r;
      x1=rand()%r;
      y1=rand()%r;
      z=rand()%r;
      X=rand()%4;
      i=rand()%n;
      b=rand()%n;
      c=rand()%n;
      x!=y;
      switch(X)//控制符號數 
        {    case 0:
             printf("第%d題:%d%c%d= \n",j+1,x,a[i],y);
            fprintf(fp, "%d%c%d= \n",x,a[i],y);
            
            break;
            case 1:
                printf("第%d題:%d%c%d%c%d=  \n",j+1,x,a[i],y,a[b],z);
                fprintf(fp, "%d%c%d%c%d=  \n",x,a[i],y,a[b],z);
                break;
            case 2:
                fprintf(fp, "%d%c%d%c%d%c%d= \n",x,a[i],y,a[b],x1,a[c],z);
                printf("第%d題:%d%c%d%c%d%c%d= \n",j+1,x,a[i],y,a[b],x1,a[c],z);
                break;
            
        }
        
    }
    fclose(fp);
    
}
復制代碼
復制代碼
void save()
{    stack *p;
    char bds[50];
    FILE *fx;//讀取文件 
    int line,i;
    if((fx = fopen("/練習程序/test.txt","r")) == NULL)
     {
     printf("error\n");
     exit (1) ;
     }    
     char buf[1024];
     for (i = 0; i < 1024; i++) 
        {    while(fgets(bds,50,fx) != NULL)//輸出文件
 {
         
             line = strlen(bds);
                 bds[line-1] = '\0';  /*去掉換行符*/
                     printf("%s  \n",bds);
                    qiuzhi(bds);
                     
         } 
         fclose (fx);
     } 
     } 
復制代碼

  4.主函數

復制代碼
int main()

{        
        int num1,num2,m,n,r,type;
        printf("請選擇輸出類型:1or2");
        scanf("%d\n",&type);
        if(type==1)
        {    printf("請輸入生成題目數量: ");
            scanf("%d \n",&num1);
            printf("請輸入分子分母取值范圍: ");
            scanf("%d %d",&m,&n);
            creat2(num,m,n);
         } 
         else if(type==2)
         {
          
            printf("請輸入生成題目數量: ");
            scanf("%d \n",&num2);
            printf("請輸入取值范圍: ");
            scanf("%d %d",&r);
            creat1(num,r);
            save();//輸出答案 
        }
        return 0;
}
復制代碼

 四、測試

    1.分數計算

    

 

 

     2.文件生成

   

 

 

    

五、psp表格

    

 

 

 

  六、總結

  魏甫:這次的項目不能說是很完美的運行,由於基礎較差,在經過很多天的學習,實驗下,我們僅僅只是完成了個大概,我在我們隊伍里是負責測試,審核代碼。為了讓代碼更加美觀好看,我將代碼整體結構改正了一下,導致我們總體的程序出現了bug,例如整數有時會運行不出,但調試卻沒有問題。這是我的責任,同時對我來說這個項目並沒有結束,之后我會仔細檢查debug,讓它更完美些。最后一次總結會議上我們在總結自己收獲的同時,也互相反思自己在項目上出現了什么問題,有什么不足之處。我們的問題主要有:溝通不到位,由於第一次進行結對項目,難免會有些個人思想,在反思過程中,明顯發現,函數中參數設置和定義有很大區別,這讓我們不得不去仔細對照查看代碼含義。也告訴我在下次結對時,一定一定要在一開始就定好函數和參數的各類功能和類型,提高效率,節省時間。這次的結對對我們來說是一個很好的經歷,彌足珍貴。

  溫欽益:這次的結對項目,一開始看到題目感覺並不是很難,等到和同伴開始對題目的各個要求完成代碼時,才感受到了題目中復雜的地方。我們首先先確定了使用的語言,由於沒有對其他語言的學習,故選擇了C語言。在完成要求的過程中,我們首先開了個會,先各自完成自己認為能完成的部分。然后把對方的代碼發出來一起審核,進行測試。在判斷運算符優先級時,意識到要運用數據結構的內容時,又抽出了時間去復習。除此,我們是第一次做結對項目,,在討論和交流時出現來了偏差。原因是函數定義及參數設置不一,想法未溝通好。在其他的一些功能要求上,由於我們的能力有限,只能完成一部分內容。另外,在交流代碼的過程中,學習到了代碼的規范的重要性,寫的時候要想到如何寫才能讓對方也能理解你的意思。最后,此次結對項目讓我們有了交流的對象,不再是自己一個人的苦思冥想,我們能分享自己遇到的困難,分享自己的經驗,讓我收獲到了許多。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM