• C语言程序设计第三次作业


    C语言第三次作业:

    一:改错题:

    (错误1)将所给源代码输入编译器,编译失败,错误信息如下:

    错误原因:根据所给错误信息,可以发现,在源代码第七行的if语句后,多添加了一个“;”,因而错误。
    改正方法:将多余“;”去掉。
    (错误2)再次编译,编译器继续报错,错误信息如下:

    错误原因:在scanf语句中,x前缺少取地址符“&”。
    改正方法:在x前添加取地址符“&”。
    (错误3)改正上述错误后,继续编译,错误信息如下:

    错误原因:经过检查发现,在if语句中,“y = 1/ x”后缺少“;”,因而导致编译错误。
    改正方法:补全所缺“;”。
    (错误4)执行编译命令,错误信息如下:

    错误原因:源代码在else语句后添加了条件,而根据所学得知,在else语句后不能添加条件语句,因而导致错误。
    改正方法:将“else”后的条件去掉。
    (错误5)编译,提示错误信息如下:

    错误原因:在源代码的printf语句中,““””(引号)以及后面的变量x,y,之间均缺少“,”(逗号),因而导致编译错误。
    改正方法:将缺少的逗号补齐。
    (错误6)执行编译命令,编译成功。
    输入所给样例,但在输入前,首先发现输入格式不对,如图:

    通过观察不难发现,样例中的输入样例并未换行,因而认为有误。
    错误原因:在源代码的scanf语句中多了一个转义字符“/n”。
    改正方法:去掉多余的转义字符“/n”。
    (错误6)将上述问题改正后编译成功,开始输入样例数据(10.0),结果如下:

    显然不符合要求与期望。
    错误原因:首先发现在scanf语句中多了一个“=”,但这并不是导致错误的主要原因,经过进一步检查发现,在第七行的if语句中,所给比较条件“x == 10”被写成“x = 10”,缺少一个“=”导致条件运算符变为赋值运算符,导致比较时出错。
    改正方法:将“x = 10”改为“x == 10”。
    (错误7)继续编译,编译未出现异常,再次输入样例数据,发现结果如图:

    与上图结果一致,再次检查。
    错误原因:首先发现,在scanf语句中,所取用的数型为float型,而x,y的定义均为double型,虽然这并不是造成错误的主要原因,但谨慎起见,仍改为“%lf”,进一步检查发现,在第15行的printf语句中,“=”后的部分的“%.1f”被误写成“%lf”,可以认定,这是造成错误的主要原因,而f(x)部分的“x”,也写成了“%.2f”,因而被保留了两位小数。
    改正方法:将第15行的“%.2f”改为“%.1f”;“%.lf”改为“%.1f”。
    输入样例,结果如下:


    与样例一致。再次输入其它数据:如下图:


    符合期望,认为改正完毕。
    附上改正后的源代码:

     #include <stdio.h> 
    
    int main(void)
     { 
        double x = 0.0,y = 0.0;
        printf("Enter x:"); 
        scanf("%lf",&x);
        if(x == 10)
        { 
            y = 1 / x;
        }
        else
        {
           y = x;
        }
        printf("f(%.1f) = %.1f
    ", x,y); 
        return 0; 
     }
    

    二:学习总结:

    (1)对于所给代码段的理解:

    所给代码段“if(a)if(b) x=x+1;else y=y+1”本身应为判断是否为a且b条件,若为真,则执行“x = x + 1”这段语句,若为假,则执行后面的“y = y + 1”。
    该语句的else在此处应与第二个if配对。若要明确表达这种配对方式,应在每一个if 或else if或else后添加“{}”(花括号),同时要时刻注意if else配对的优先顺序。
    改正后的代码段应为

    if(a&&b)
    {
        x = x + 1;
    }
    else
    {
        y = y + 1;
    }
    

    (2)C语言中对于范围的表达:

    对于题设所给的方式,显然不能取得想要的效果。因为在C语言中,逻辑运算符的合并性是从左至右的,在内部实际上是被转化为布尔值的,判断结果为0或1,即“true,false”。而以题设的“ (-10< x <10)”而言,当x大于-10为真时,x的值便被转换为1,根据结合性,x与右侧的10继续比较,显然成立。但若输入大于10的数,先与左侧的-10比较,显然为真,x被赋值为1,再与10比较,为真,但显然不符合逻辑。因此,只要输入任意大于-10的值,得到的结果都只能为真。因此无法表示(-10,10)这一区间。
    正确表达方式如下:

    (x >-10 && x < 10);
    

    即添加逻辑运算符,即可得到所要效果。

    (3)非法字符的问题:

    首先将所给代码输入编译器。
    编译无误。
    运行,输入所给数据“123a”,得到结果如下:


    不难发现,b被赋予了一个毫无意义的数字“1”。
    结合题目所给信息可以得知,在输入123时,我们继续输入“a”,系统发现非法数据,便认为输入已经停止,验证过程如下:
    输入“123a 123”,结果如下:


    可以看到,笔者输入的123同样没有被读取,而是被直接丢弃。
    继续输入“123 123”,结果如下:

    程序正常输出,因此可以验证猜想以及题设所给信息,当输入非法字符时,计算机会认为用户输入完毕。
    而结合程序本身。“a,b”本身被声明时为int型,而字母为字符型变量,因此在输入时属于非法变量。
    修改程序,将scanf,printf中对应变量b的“%d”均替换为对应单个字符输入的“%c”,代码如下:

    #include <stdio.h>
    
    int main()
    {   
    int  a, b;
    scanf("%d %c", &a, &b);
    printf("a = %d, b = %c
    ", a, b);
    return 0;
    }
    

    继续输入“123a”,结果如图:

    程序成功输出“a”,而反过来,输入“123 123”,结果如图:

    问题同之前,但是此次b所给的值并不是没有意义的,而是第二个数字的首位。因为%c只能读入一个字符,因此第二个数字的“1”也被读入。
    (2)关于scanf:
    输入所给源代码,输入所给值,结果如下:

    所得返回值为2;
    输入“123a”,结果如下图:

    原因分析:
    由所给信息得知,该函数的返回值为成功读取变量的个数。在该程序中,a,b均为整型变量,在scanf中,对应的格式化输入符号也为整型,所以在输入“123 123”,即均为整型时,均为合法数据,因此被读入,返回值为2.
    而输入“123a”,其中包含了非法数据“c”,结合上题,可以得知输入被终止,所以只将123赋给了a,b未能成功被赋值,因此返回值是1。所以,也能佐证上题分析正确。
    (3)对程序1的完善:
    结合程序2,可以得到启发,若输入数值均为合法数值,则会被正常读取,scanf的返回值会为2,否则均说明未能正常读入数据,即含有非法数据,源代码如下:

    #include <stdio.h>
    
    int main()
    {   
    int  a, b,n;
    n = scanf("%d %d", &a, &b);
    if(n == 2)
    {
    	 printf("a = %d, b = %d
    ", a, b);
    }
    else
    {
    	printf("Input error!");
    }
    return 0;
    }
    

    (4)其它总结:

    首先对于这一章节内容,一些基本内容譬如按位运算符与其它运算符的优先级关系,需要熟记于心,不然往往会出现意想不到的错误。尤其是“==”与“=”的本质区别。
    其次,对于逻辑运算符的短路特性,要时刻牢记于心,不能过于想当然。如果逻辑运算上出现错误,代价很有可能是巨大的。
    对于选择结构,条件运算,以及软件测试思想,不单列出来一一总结,而是通过一些个人或身边同学出现过的错误,来同时阐述这三点。

    (1)如下是本次作业中的7 -9题的源代码(有误);

    #include<stdio.h>
    
    int main(void)
    {
    int e = 0;
    double c = 0.0;
    scanf("%d",&e);
    if(e >= 0&&e <=50)
    {
    	c = 0.53 * e;
    	printf("cost = %.2f",c);
    }
    if(e > 50)            //此处笔者将else if改为if 
    {
    	c = 50 * 0.53 + (e - 50) * 0.58;
    	printf("cost = %.2f",c);
    }
    else
    {
    	printf("Invalid Value!");
    }
    
    return 0;
    
    }
    

    输入10,得到结果如下:

    显而易见,输出是有误的。而这里我将else if改为if就是为了说明问题所在。即if的连用问题。
    对于if与else if,应当理解本质区别。
    else if可以作为if的补集,当判定if中的条件不成立时,程序会执行与if下面的else if,直到取得符合条件范围或条件,否则进入else。所以,当if 后连用多条else if时,只要其中有任一条语句符合条件,程序便跳出选择结构,不再判定剩余的条件,转而进入下一语句。而if的连用,实际上是有很大的风险的。因为对于每一条if语句而言,都各自作为一条单独的语句出现在程序中,因此,当某一if被判断为真时,不是直接略过余下if,而是按序判断所有的if,若仍有满足条件的if语句,则继续执行,直到没有if语句,因此对于上题而言,虽然笔者输入了“10”,也输出了对应的电费,但同时也输出了不该得到的结果。按序分析,输入值为10,满足第一条if语句,判断为真,执行电费计算语句,得到电费。进入下一if语句,判断为假,此处有另一需要注意的问题,即else的优先配对原则,原则为优先与最近的if语句配对,因此在本程序中,else与第二条if配对。回到程序中来,当第二条if判断为假,则执行与之配对的else,所以输出了本不应该出现的语句。希望同学多加注意。
    对于条件运算,其实老生常谈。无非是条件运算时在计算过程中会将值转为布尔值(0,1),所以一定要谨记在适当时候使用逻辑运算符,防止出错。
    而对于软件测试,其实现阶段笔者有一些个人总结,也是通过一些设错来体现,希望能给大家启发,也希望能够抛砖引玉,得到更好的经验。

    首先对于现阶段常出现的一些错误,先归类为三类:逻辑类,数学模型类,输入输出错误(即数型错误,格式错误暂不计入内);

    对于逻辑类,概括来讲就是通过程序解决问题的顺序,基础是DICO结构,不过多赘述,在之前的顺序结构中,实际上会出现逻辑错误的几率很低,但进入选择结构后,问题便接踵而至,首先是以本次作业的7-6为例,以下是设错后的源代码:

    #include<stdio.h>
    #include<math.h>
    int main(void)
    {
    int a = 0,b = 0,c = 0;
    double s = 0.0,area = 0.0,p = 0.0;
    
    if(a + b>c&&a + c>b&&b + c>a)
    {	
        scanf("%d%d%d",&a,&b,&c);        //此句本应在第六行,即if语句之前
    	p = a + b +c;
    	s = p / 2.0;
    	area = sqrt(s * (s - a) * (s - b) * (s - c));
    	printf("area = %.2f; perimeter = %.2f",area,p);
    }
    else
    {
    	printf("These sides do not correspond to a valid triangle");
    }
    return 0;
    }
    

    本例子实际上不具有太大价值,但是想通过该例子说明逻辑性错误实际上是毁灭性的,因为这类错误往往不会被编译器发现,若有兴趣可以将上例拷贝进编译器去试验。而运行结果如下:

    显然,连输入语句都未执行,便结束程序,且执行了else语句。所以,希望在动手写程序前,先大概规划好方向与顺序。如果在逻辑上出问题,再涉及修改的话,很有可能是牵一发而动全身。
    而对于数学模型类,实际上是较为常见也较为容易产生迷惑的地方,首先对于数学运算方面,不过多赘述,若在设定合理表达式前不太明朗和确定前,不妨多动笔,好记性不如烂笔头。
    而在这一方面较为有价值的错误和较为常见的错误即为取等条件与范围的取得问题,尤其是涉及条件判断时,更容易产生一些意想不到的错误,本想以条件结构(2)的一题设错来体现问题,但考虑到该作业还未到截止日期,因此先不给出源代码,转而阐述一些常见问题。
    首先是对于取等条件的判断,第一应由问题的数学关系入手,判断能否取等,即条件,范围的临界值,并从这一方面入手,通过输入临界值来观察输出,判断取等或范围是否合理。其次涉及到浮点数时,若未得到确切的误差允许范围,最好先不要做关系运算,因为浮点数本身就具有误差,因此在浮点数之间的比较时,最好通过其它特征入手,来进行判断,或者先不取等,之后通过其它手段进行判断。而对于数学问题的分析,也是遵循一般到特殊的原则,先从一般性问题入手,当一般性问题判断完毕时,再进行特殊性问题的处理,这样可以省去许多不必要的过程。再者,特殊情况往往是一般情况的补集,简单而言就是一般情况的else,因此可以在选择结构的最后执行,可以省去许多思考过程以及运算过程。
    以下给出一个本次实验中的第九题的一个代码:

    #include<stdio.h>
    int main(void)
    {
    int e = 0;
    double c = 0.0;
    scanf("%d",&e);
    if(e < 0)
    {
    	printf("Invalid Value!");
    }
    if(e >= 0&&e <=50)
    {
    	c = 0.53 * e;
    	printf("cost = %.2f",c);
    }
    else if(e > 50)
    {
    	c = 50 * 0.53 + (e - 50) * 0.58;
    	printf("cost = %.2f",c);
    }
    return 0;
    
    }
    

    实际上就结果而言,本代码并没有什么问题,但是就思考过程以及运算过程而言,显然是舍近求远的,因为本次题设的条件范围可以大致分为三段:[0,50];(50,∞),而小于0的部分虽然可以单独讨论,但实际上由简单的数学集合关系易得,小于0的部分显然是题设范围的补集,完全可以用else直接取代,在题中也属于特殊性问题,所以可以参照之前所给的代码,将语句改为else。
    最后是关于输入输出的问题。
    首先先明确几点,所求值的数型问题,输入值的数型问题,中间值的数型问题,题目要求的精确度,数型范围。如果在一开始就将这几个问题考虑周全的话,相信这种问题也不会常犯。所以结合一句古话“工欲善其事,必先利其器”,这里的器实际上就是对于数型范围的预先考虑。
    下面先给出几个设错后的代码,再结合几种典型问题,来总结这方面常出现的问题,并进行一些个人的总结。

    #include<stdio.h>
    int main(void)
    {
    int a,b,c,d,e ,f;
    double g;
    scanf("%d%d",&a,&b);
    c = a + b;
    d = a - b;
    e = a * b;
    printf("%d + %d = %d
    ",a,b,c);
    printf("%d - %d = %d
    ",a,b,d);
    printf("%d * %d = %d
    ",a,b,e); 
    if(a%b==0)
    {
    	f = a / b;
    	printf("%d / %d = %d",a,b,f);
    }
    else
    {
    	g = (double )a / b  ;
    	printf("%d / %d = %.d",a,b,g);                //此处笔者将最后一个%.2f改为%d
    }
    return 0;
    }
    

    输入5 3,结果如图:

    不难发现,最后的除法运算的值显然是一个垃圾值,这类问题可以总结为第一类问题,即输出垃圾值问题,这类问题往往是输出一个意料之外的数值,多见于输出0.00,或一些离谱的数值。
    而这类问题往往是由于数型的输入输出有误,导致意料之外的错误。
    针对这类问题,第一可以从输入输出函数内容进行判断,而对于一些复杂计算,尤其是涉及浮点运算较多的问题,较为折中和保守的方法为不妨将所有变量均设为double型,可以在一定程度上省去许多的考虑步骤和一些错误。
    而另一种方法,由于我们还未学习设置断点的方法,因此暂不列入总结范围。另一种方法为分析输出数据,并顺藤摸瓜,找到出现问题的语段。以上图为例,不难发现前三句的输出无误,出现问题的地方在除法运算部分,因此我们可以先回头分析涉及除法运算的部分,可以很大程度上提高查错的精准性和效率。
    还有一种方法,在涉及多步骤运算时,我们往往难以发现究竟是哪一步出现了问题,这时我们可以考虑在适当的地方插入输出语句,观察关键位置的输出的值是否是期望值,也能极为准确地发现问题所在,下面以7-6题为例,希望能给大家一些启发:
    首先给出有问题的代码:

    #include<stdio.h>
    #include<math.h>
    int main(void)
    {
    int a = 0,b = 0,c = 0,area = 0;               //此处笔者将面积area声明为int型
    double s = 0.0,p = 0.0;
    scanf("%d%d%d",&a,&b,&c);
    if(a + b>c&&a + c>b&&b + c>a)
    {
    	p = a + b +c;
    	s = p / 2.0;
    	area = sqrt(s * (s - a) * (s - b) * (s - c));
    	printf("area = %.2f; perimeter = %.2f",area,p);
    }
    else
    {
    	printf("These sides do not correspond to a valid triangle");
    }
    return 0;
    }
    

    将样例数据“5 5 3”输入,结果如图:

    可以看到,出现了之前提到过的典型错误。
    单从代码的计算过程分析,显然十分的费力,不妨在s的计算式后添加一段printf语句,如下:

    #include<stdio.h>
    #include<math.h>
    int main(void)
    {
    int a = 0,b = 0,c = 0,area = 0; 
    double s = 0.0,p = 0.0;
    scanf("%d%d%d",&a,&b,&c);
    if(a + b>c&&a + c>b&&b + c>a)
    {
    	p = a + b +c;
    	s = p / 2.0;
    	printf("s = %.2f
    ",s);                 //此处笔者添加printf语句,用以观察s的值
    	area = sqrt(s * (s - a) * (s - b) * (s - c));
    	printf("area = %.2f; perimeter = %.2f",area,p);
    }
    else
    {
    	printf("These sides do not correspond to a valid triangle");
    }
    return 0;
    }
    

    输出结果如下:

    很直观,s的值输出正常,再结合下面关于面积的计算式易得结论,因为三边均为整型,s值无误,因此出现问题的值只能为area,所以回头检查area的数型,不难发现问题。
    对于这种方法,私以为也可以作为软件测试的一种方法,因此放到一起总结。

    三:实验总结:

    1:计算分段函数:
    ①流程图如下:

    ②源代码如下:

    #include<stdio.h>
    #include<math.h>
    int main(void)
    {
    double x = 0.0,y = 0.0;
    scanf("%lf",&x);
    if(x>=0)
    {
    	y = pow(x,0.5);
     }
    else	 
    {
     	y = pow(x+1,2) + 2 * x + 1 / x; 
    }
     printf("f(%.2f) = %.2f",x,y);
     return 0;
     }
    

    ③测试数据及结果:
    输入所给样例数据“10”,结果如下:

    符合样例数据,输入另一组数据“-0.5”,结果如图:

    同样符合样例数据。再通过软件测试思想,输入临界值即0,结果如图:

    符合期望,测试完毕。
    ④实验分析:
    本题主要运用基本的选择结构,难度较低,实验过程中并未遇到障碍。值得强调的地方仍然是临界值即分段点的问题。其次是简单的数学函数调用问题,运用得当,可以省去许多计算过程。
    2:四则运算问题:
    ①流程图如下:

    首先声明几点,由于指导老师后来补充说明不需要保留小数,因此省略该过程。其次由于reptor默认除法运算均保留小数,所以无论两输入数据是否能整除,实际上运算符是一样的,因此为了区别,笔者将整除的部分用floor强调,无法整除的部分则保留小数部分。
    ②源代码如下:

    #include<stdio.h>
    int main(void)
    {
    int a,b,c,d,e ,f;
    double g;
    scanf("%d%d",&a,&b);
    c = a + b;
    d = a - b;
    e = a * b;
    printf("%d + %d = %d
    ",a,b,c);
    printf("%d - %d = %d
    ",a,b,d);
    printf("%d * %d = %d
    ",a,b,e); 
    if(a%b==0)
    {
    	f = a / b;
    	printf("%d / %d = %d",a,b,f);
    }
    else
    {
    	g = (double )a / b  ;
    	printf("%d / %d = %.2f",a,b,g);
    }
    return 0;
    }
    

    ③测试数据及结果:
    首先还是以样例数据为准,输入第一组“6 3”,结果如下:

    与样例一致,输入第二组“8 6”,结果如下:

    与样例一致,再次输入下一组“5 2”,结果如图:

    数据无误,测试结束。
    ④实验分析:
    错误1)在本实验过程中,笔者遇到了一些小问题同时也是之前提到过的典型错误,当笔者输入5 3时,结果如图:

    可以看到,5 / 3显然不能整除,而输出的值却只保留了整数部分。
    错误原因:
    在else之后的计算过程中未进行数型转换,导致出现了整型之间的除法运算,因而小数部分被舍弃。
    改正方法:进行强制转换操作,将除法计算中的除数或被除数强制转换为double型。
    其他方面并未出现问题,只是想再次强调时刻注意数据的类型。
    3:判断能否构成三角形且输出周长和面积:
    ①流程图如下:


    ②源代码如下:

    #include<stdio.h>
    #include<math.h>
    int main(void)
    {
    int a = 0,b = 0,c = 0;
    double s = 0.0,area = 0.0,p = 0.0;
    scanf("%d%d%d",&a,&b,&c);
    if(a + b>c&&a + c>b&&b + c>a)
    {
    	p = a + b +c;
    	s = p / 2.0;
    	area = sqrt(s * (s - a) * (s - b) * (s - c));
    	printf("area = %.2f; perimeter = %.2f",area,p);
    }
    else
    {
    	printf("These sides do not correspond to a valid triangle");
    }
    return 0;
    }
    

    ③测试数据及结果:
    还是以样例为参考,输入“5 5 3”。

    接着输入“1 4 1”

    均符合样例数据,再输入另一组“2 2 4”,如图:

    实际上此处笔者是想测试临界条件,即两边之和等于第三边的结果是否正确。
    测试结束。
    ④实验分析:
    实际上这道题也不存在什么难度与障碍,在这里也想通过这个例子说明一些问题。之前提到过关于数型的问题,在本题中显然涉及开方,除法等运算,必然会涉及大量的浮点运算,因此,折中又保险的方法就是将所有变量定义为double型,可以省去许多麻烦,只不过要注意的就是数型的声明要和输入输出中一一对应。
    4:出租车计价问题:
    ①流程图如下:



    ②源代码如下:

    #include<stdio.h>
    int main(void)
    {
    
    double k = 0.0,f = 0.0,m = 0.0;
    int w = 0,t = 0;
    scanf("%lf%d",&k,&t);
    if(k<=3)
    {
    	m = 10;
    } 
    else if(k<=10)
    {
    	m = 10 + (k - 3) * 2;
    }
    else
    {
    	m = 10 + (k - 3) * 2 + (k - 10);
    }
    if(t>=5)
    {
    w = t / 5 * 2;	
    }
    else
    {
    w = 0;
    }
    f = m + w;
    printf("%.0f",f);
    return 0;
    
     } 
    

    ③测试数据及结果:
    首先还是以样例为准,输入“2.6 2”,结果如下:

    实际上此样例是计算在起步里程内且不等待的情况下的价格,那么我们为了谨慎起见,再次测试另一组起步里程内且需要等待的数据“2.6 5”,结果如下:

    多出的两元即为等待的费用。
    那么输入下一样例"5.1 4",结果如下:

    同理,该样例是指超出起步里程但在十公里以内且不等待的结果,那么我们同样输入另一组需要等待的数据“5.1 16”,结果如下:

    多出的6元即为等待的费用。
    接着输入下一组样例“12.5 9”,结果如下:

    该数据为超出10公里且等待的价格,那么我们同样输入超出十公里但不等待的数据“12.5 4”,结果如下:

    测试数据与结果均符合期望,且包含所有已知情况,测试完毕。
    ④实验总结:
    该实验主要运用了多重选择语句,但其实逻辑方面并没有什么难度。但是笔者也出现了一些小问题,如图:
    错误1)

    可以看到,笔者输入样例中大于10公里且等待的数据,发现多出两元。
    错误原因:笔者的数学计算式有一个值计算有重复,即理解错题意。
    改正方法:改良计算式,剔除多余计算式。
    之前也提到了,实际上数学模型的问题也是时有发生的,所以非要说的话.............认真读题吧............这个真的没什么好说的....................

    PTA提交列表:



    最后,由于笔者最后截稿时还是10.24日,所以,祝老师及辛勤付出为我们判作业的助教以及同学们节日快乐.还有...好好珍惜你们的助教...因为查错...实在是太费事了...太费事了...........

  • 相关阅读:
    Mybatis框架(一)
    maven(一)
    shiro安全框架(二)
    shiro安全框架(一)
    Linux系统
    maven(二)
    Redis存储系统(二)
    Redis存储系统(一)
    1.2 性能测试(效率)
    1.3 压力测试/极限测试(可靠性)
  • 原文地址:https://www.cnblogs.com/Reloaded/p/7719971.html
Copyright © 2020-2023  润新知