zoukankan      html  css  js  c++  java
  • 分数的加减法——C语言初学者代码中的常见错误与瑕疵(10)

    题目

    分数的加减法

    编写一个C程序,实现两个分数的加减法
    输入:输入包含多行数据
    每行数据是一个字符串,格式是"a/boc/d"。
    其中a, b, c, d是一个0-9的整数。o是运算符"+"或者"-"。
    输出:对于输入数据的每一行输出两个分数的运算结果。
    注意结果应符合书写习惯,没有多余的符号、分子、分母,并且化简至最简分数 

    样例输入:
    1/8+3/8
    1/4-1/2
    1/3-1/3
    输出:
    1/2
    -1/4
    0

    评析 

      完全看不懂题目!

      本来是一个很好的问题,可惜被出题者给败坏了。

      最大的毛病出在“每行数据是一个字符串,格式是"a/boc/d"”这行文字,驴唇不对马嘴,令人无法理解。

      字符串的定义是:A string is a contiguous sequence of characters terminated by and including the first null character.在文本输入流中并不存在null character——,因此严格地说文本流根本不可能存在字符串。

      更搞笑的是“格式是"a/boc/d"”。把"a/boc/d"这种东西叫字符串(String Literal)的前提是在C代码层面而言。若"……"这种形式的东西出现在莎士比亚戏剧中,那就绝对不是字符串。把输入文本流中的"……"说成是字符串,是“关公战秦琼”式的说法。看了后面的输入样例,我才弄明白这里的""是根本没有的。

      另一个毛病是“其中a, b, c, d是一个0-9的整数”,这是画蛇添足式的简化,并没有使问题得到任何真正的简化,只起到了迷惑解题者、束缚解题者的作用。

      综上所述,这是一个极好的问题,但却是一个败家的提法。

      对这样的问题,经常出现的情形是,初学者已经开始写代码了,高手还在呆呆的思考题目和题目的要求到底是什么。这是初学者和成熟的程序员之间一个非常显著的区别。

      初学者解决问题的时间分配通常是倒三角形

       

      而成熟的程序员则恰恰相反

      

      当然,我并不是反对新手在写代码方面进行大量练习,但是新手特别容易忽视理解问题要求和很少在数据结构方面深思熟虑的弱点应该予以充分的重视。

    原代码1:

    #include <stdio.h>
    #include <math.h>
    
    int comdiv(int x,int y);
    
    int main()
    {
     char string[8];
     int a[4],i,j,comd,b[2];
     while(gets(string)!=NULL)
     {
      for(i=0,j=0;i<4;i++,j+=2)
       a[i]=(int)string[j]-48;
    
      if(a[1]==0 || a[3]==0)
       printf("matherror
    ");
    
      else if(string[3]=='-')
      {
       b[0]=a[0]*a[3]-a[1]*a[2];
       b[1]=a[1]*a[3];
       if(b[0]==0)
        printf("0
    ");
    
       else if(b[0]>0)
         comd=comdiv(b[0],b[1]);
         else 
         { 
          comd=comdiv(-b[0],b[1]);
          printf("%d/%d
    ",b[0]/comd,b[1]/comd);
         }
      }
    
      else 
      {
       b[0]=a[0]*a[3]+a[1]*a[2];
       b[1]=a[1]*a[3];
       comd=comdiv(b[0],b[1]);
       printf("%d/%d
    ",b[0]/comd,b[1]/comd);
      }
     }
    
      return 0;
    }
    
    int comdiv(int x,int y)
    {
     int i,j;
    
     if(x==0||y==0)
      return 1;
     else if(x==1||y==1)
      return 1;
     else if(x==y)
      return x;
     else if(x>y && !(x%y))
      return y;
     else if(!(y%x))
      return x;
    
     for(i=2,j=1;i<=(x>y?x:y);i++)
     {
      if(x%i==0&&y%i==0)
       j=i;
     }
    
     return j;
    }

    评析:

    #include <math.h>

       一旦涉及计算,新手就喜欢写这个,实际上根本不需要。在math.h中列出函数原型的函数都是近似计算函数,整数精确计算通常都用不着math.h中的函数。

    char string[8];

      这里的毛病是数组定义得太小,很容易出毛病。受到谭浩强流的坏影响,不少初学者在为存储字符串定义字符数组时,经常有斤斤计较的毛病。

     int a[4],i,j,comd,b[2];

      这位小朋友把a , b ,c ,d视为一个int [4],把运算结果视为一个int [2]。应该说有初步的数据结构设计意识,但还处于很幼稚的阶段。 

    while(gets(string)!=NULL)
    

      这个完全是被题目给骗蒙了,真的把输入流中的 1/8+3/8 当作字符串存储了,这很傻。实际上,对于输入流中的  1/8+3/8 可以有很多视角。从scanf()的角度来看,也可以把 1/8+3/8 视为"%d/%d%c%d/%d"。这样就不难明白为什么前面说原题目中的“其中a, b, c, d是一个0-9的整数”是“画蛇添足式的简化,并没有使问题得到任何真正的简化,只起到了迷惑解题者、束缚解题者的作用”了。

      还有需要说明的是,gets()这个函数事实上已经被C语言开除了,在C语言正式开除它之前,很多职业程序员在更早的时候就已经非正式地把这个函数从C语言中开除了。如果要完成gets()函数的功能可以考虑使用fgets()函数,或自己写函数实现。 

      for(i=0,j=0;i<4;i++,j+=2)
       a[i]=(int)string[j]-48;
    1. 无论如何都应通过一个函数实现这段代码的功能。
    2. 那个“48”属于谭浩强流,应该写'0'。
    3. (int)string[j],似是而非。string[j]作为右值本来就是int类型。所有以右值参与运算的char类型数据事实上都是int类型。
    4. 这种写法的容错性弱爆了。哪怕输入为 1/8 + 3/8 【注: + 两侧有空格】 都会带来灭顶之灾。
      if(a[1]==0 || a[3]==0)
       printf("matherror
    ");
    
      else if(string[3]=='-')
      {
       b[0]=a[0]*a[3]-a[1]*a[2];
       b[1]=a[1]*a[3];
       if(b[0]==0)
        printf("0
    ");
    
       else if(b[0]>0)
         comd=comdiv(b[0],b[1]);
         else 
         { 
          comd=comdiv(-b[0],b[1]);
          printf("%d/%d
    ",b[0]/comd,b[1]/comd);
         }
      }
    
      else 
      {
       b[0]=a[0]*a[3]+a[1]*a[2];
       b[1]=a[1]*a[3];
       comd=comdiv(b[0],b[1]);
       printf("%d/%d
    ",b[0]/comd,b[1]/comd);
      }

      结构太差。应该

      if(a[1]==0 || a[3]==0)
      {
          printf("matherror
    ");
          continue ;
      }
       
      if(string[3]=='-')
      {
                       /* …… */
      }
      else 
      {
                       /* …… */

    }
      if(string[3]=='-')
      {
       b[0]=a[0]*a[3]-a[1]*a[2];
       b[1]=a[1]*a[3];
       if(b[0]==0)
        printf("0
    ");
    
       else if(b[0]>0)
         comd=comdiv(b[0],b[1]);
         else 
         { 
          comd=comdiv(-b[0],b[1]);
          printf("%d/%d
    ",b[0]/comd,b[1]/comd);
         }
      }
    
      else 
      {
       b[0]=a[0]*a[3]+a[1]*a[2];
       b[1]=a[1]*a[3];
       comd=comdiv(b[0],b[1]);
       printf("%d/%d
    ",b[0]/comd,b[1]/comd);
      }

      这段代码若使用switch语句会更自然些。从实现功能的角度来说,作者没有考虑到1/2+1/2这样的的情形,没有考虑到3/4+1/2这样的情形。所以这段代码是错误的。其他的大小毛病相当多,这里不一一指出了。主要的问题在于,原作者没有对数据进行很好的抽象,因此也就无法很好地组织代码;结构化程序设计思想没有得到彻底的贯彻,迷失于在main()中纠结细节。 

    续文:分数的加减法——C语言初学者代码中的常见错误与瑕疵(11) 

  • 相关阅读:
    前端Ajax/JS/HTML+后端SpringMVC(二)
    前端Ajax/JS/HTML+后端SpringMVC(一)
    Redis 简介及应用
    项目中使用 MyBatis(二)
    L2d插件
    [转载] 栈内存和堆内存
    Hbase排错
    matplotlib中文乱码
    cocos2dx 一些好网站
    esclipe中接入SDK时引用另一个工程或Jar
  • 原文地址:https://www.cnblogs.com/pmer/p/3484933.html
Copyright © 2011-2022 走看看