C语言程序设计第五次作业

C语言程序设计第五次作业

标签(空格分隔): C语言


新的一周又到来了,而随着新的一周的到来而到来的当然还有新的作业。


(一)改错题

题目要求:输入两个整数lower和upper,输出一张华氏摄氏温度转换表,华氏温度的取值范围是{lower,upper},每次增加2℉。计算公式如下:
c = 5×(f-32)/9

含错误源代码如下:

#include <stdio.h>

int main(void)
{    
    int fahr , lower, upper;  /* fahr表示华氏度 */
    double celsius;        /* celsius表示摄氏度 */ 
    
    printf("Enter lower:");
    scanf("%d",&lower);
    printf("Enter upper:");
    scanf("%d",&upper);
    printf("fahr  celsius\n");             /* 显示表头 */
    /* 温度转换 */
    for (fahr = lower , fahr <= upper, fahr ++) ;
        celsius = 5 /9 * (fahr - 32.0); 
        printf("%3.0f %6.1f\n", fahr, celsius); 
    
    return 0; 
}

将源代码复制至IDE中运行报错,报错信息如下:
--------------------配置: mingw5 - CUI Debug, 编译器类型: MinGW--------------------

检查文件依赖性...
正在编译 D:\C语言\预习\main.c...
[Error] D:\C语言\预习\main.c:14: error: syntax error before ')' token(定位于第14行)

构建中止 main: 1 个错误, 0 个警告

错误信息翻译:“在')'标记之前的语法错误”我们找到第14行。检查for语句,括号中的表达式没有用分号分开而是用逗号分开。这是一个很明显的错误,在for语句的括号中可以没有表达式,但一定要有两个分号。
就如以下例子,for可以没有参数,但一定要有分号:

#include <stdio.h>
#include <ctype.h>

int main(void)
{
	char answer = 0;
	for(;;)
	{
		printf("你还想输入更多的吗?用Y和N来表示:\n");
		scanf("%c",&answer);
		if(tolower(answer) == 'n')
			break;	
	}
}

在for语句中使用break即可结束循环,以此来代替判断语句。

修改意见:将for语句括号中的,改为;。即将第14行修改如下:
for (fahr = lower ; fahr <= upper; fahr ++);


将上面问题修改后再次尝试运行,程序成功编译,按照输入输出样例输入后的结果如下:
输入32、35:1.PNG-6.7kB
输入40、30:2.PNG-6.7kB
很明显,输出结果不符合输入输出样例,判断原因可能出在输入、运算、输出三个步骤中的一个。依次检查源代的这三个步骤,发现第15行的celsius = 5 /9 * (fahr - 32.0); 的运算不符合题目给出的c = 5×(f-32)/9。

在第16行的输出语句中fahr用的是%f,但fahr变量却声明为int类型。现在有两种修改方法:1.将%f改为整数型输出%d。2.将fahr声明为double类型。
根据输入输出案例可知fahr变量又需要含有小数位。

修改意见:1.将celsius = 5 /9 * (fahr - 32.0); 修改为celsius = 5.0 * (fahr-32.0) / 9.02.将fahr声明为double类型而不是int类型。


将上述问题修改后再次运行的结果如下:
输入32、35:5.PNG-5.9kB
输入40、30:6.PNG-7.7kB

输出结果明显不符合要求,我们已经检查过输入、赋值、输出语句确认没有问题,我们再检查最新学的for语句是否有问题。

检查后发现for后没有花括号,2个语句虽然缩进了但是编译器不会识别。每次增加的华氏温度也有问题fahr++每次只会增加一度,而题目要求每次增加2度。并且for语句后不能有分号。

并且发现之前检查输出语句是没有检查出来的错误:fahr在输出时用的%f而数据也有使用double类型的需要,但是声明时却将fahr变量声明成了int类型。

修改意见:1.将第15、16行用花括号括起来使其成为一个代码块。2.将for语句后的分号删除。


将修改后的源代码运行并运行输入输出案例的结果如下:
输入32、35:9.PNG-6.8kB
输入40、30:10.PNG-6.5kB

输出结果符合题目要求,程序完成修改后的源代码如下:

  #include <stdio.h>

int main(void)
{    
    int lower = 0, upper = 0;  /* fahr表示华氏度 */
    double celsius = 0.0,fahr = 0.0;        /* celsius表示摄氏度 */ 
    
    printf("Enter lower:");
    scanf("%d",&lower);
    printf("Enter upper:");
    scanf("%d",&upper);
    printf("fahr  celsius\n");             /* 显示表头 */
    /* 温度转换 */
    for (fahr = lower ; fahr <= upper; fahr = fahr + 2)
    {
    	celsius = 5.0 * (fahr-32.0) / 9.0; 
        printf("%3.0f %6.1f\n", fahr, celsius); 	
    }			
    
	return 0; 
}

我认为这个程序的修改其实到目前为止还没有完善,就如第二组输入输出案例是最小值大于最大值,这时这个程序的输出为空,但我认为更应该输出错误信息,我将这一点擅自更改后的源代码如下:

#include <stdio.h>

int main(void)
{    
    int lower = 0, upper = 0;  /* fahr表示华氏度 */
    double celsius = 0.0,fahr = 0.0;        /* celsius表示摄氏度 */ 
    
    printf("Enter lower:");
    scanf("%d",&lower);
    printf("Enter upper:");
    scanf("%d",&upper);
	//检验最小值一定小于最大值
	if(lower >= upper)
	{
		printf("ERROR:输入的最小值大于最大值。\n");
	} 
	/* 温度转换 */
    else
	{
		printf("fahr  celsius\n");             /* 显示表头 */
		for (fahr = lower ; fahr <= upper; fahr = fahr + 2)
    	{
    		celsius = 5.0 * (fahr-32.0) / 9.0; 
        	printf("%3.0f %6.1f\n", fahr, celsius); 	
    	}	
	}
				
	return 0; 
}

测试输入输出案例结果如下:
输入32、35:1.PNG-6.8kB
输入40、30:2.PNG-7.2kB


(二)学习总结

1. 代码分析:

分析以下源代码:

#include <stdio.h>
 
int main(void)
{    
    int i = 0,num = 0,odd = 0,even = 0;
    //初始化循环需要用到的变量。 
	odd = 0;
    even = 0;    
	for(i = 1; i <= 10;i++)	//循环10次 
    {
        scanf("%d",&num);
        if(num % 2 == 0)
        {
            even++;
        }
        else
        {
            odd++;
        }       
    }   
	//循环完成,输出结果。 
	printf("%d %d",even,odd);
	return 0; 
}

我们知道C语言的执行一般是按照行数来执行的。程序已经载入了头文件,并且使用了不返回数据的main函数。然后程序会因为声明语句为i、num、odd、even这几个变量分配与int类型相适宜的内存,并且给这几个变量赋初值。

注释会被忽略,第7、8行是赋值语句,作用是给odd和even这两个变量赋初值0。这是必须的,否则循环语句将不能正常运行。

然后就是我们的重头戏for循环语句,for循环的一般形式如下:

for(starting_condition; continuation_condition ; action_per_iteration)
    loop_statement;
    
next_statement;

执行顺序为:先执行starting_condition语句。然后判断continuation_condition语句。如果为真,执行loop_statement,为假就执行next_statement语句。当然,在每次loop_statement语句执行后都要执行一遍action_per_iteration语句。
流程图如下:IMG_20171108_072832.jpg-3612.8kB
详细解释如下:3.PNG-159.5kB

根据以上结论,我们的这个for循环第一步是将i初始化为1。然后判断i是否小于等于10,结果为true,执行循环体。在执行完循环体后再执行i++,因为是单独的i++所以也可以使用++i来代替。当判断语句结果为假时退出循环,执行循环后的第一条语句。
而我们的循环体运行的每个步骤如此:

此迭代i的值 表达式2判断结果 表达式3造成的结果
i=1 true i=1+1=2
i=2 true i=2+1=3
i=3 true i=3+1=4
i=4 true i=4+1=5
i=5 true i=5+1=6
i=6 true i=6+1=7
i=7 true i=7+1=8
i=8 true i=8+1=9
i=9 true i=9+1=10
i=10 true i=10+1=11
i=11 false 循环结束,不再执行i++

此循环循环10次,没此让用户输入一个数,在for循环中还嵌套了一个if语句来判断用户输入的数是奇数还是偶数,当时奇数时执行odd++当为偶数时执行even++而程序的编写者用了num%2==0来判断用户输入的数据是奇数还是偶数,当判断结果为真时数为偶数,反之则为奇数。

在结束循环后,程序执行循环语句后的第一个语句,即printf("%d %d",even,odd);(注释已忽略)和return 0;即输出结果(先偶再奇)并结束程序。在return 0;后,这个程序也就彻底的结束了。

最后,程序的运行顺序为:

  1. 载入头文件stdio.h
  2. 构建main函数。
  3. 声明i、num、odd、even为整数型变量。
  4. 初始化even和odd为0.
  5. 进行for循环,循环中的每一步如上列表所示。
  6. 输出结果,奇数和偶数的数量。
  7. 返回结果,结束程序。

此程序目的是判断输入的十组数据中奇数和偶数的个数,如果想要判断不同数量的数据可以将for语句中第二个表达式更改或者让用户输入一个数,当i<=此数据时运行循环。程序测试结果如下:
输入1、3、5、7、9、11、13、15、17、19:6.PNG-6.1kB
输入2、4、6、8、10、12、14、16、18、20:7.PNG-6.2kB
输入1、2、3、4、5、6、7、8、9、10:8.PNG-6.1kB

2.增量运算符

  • 运行程序一(源代码如下)
#include <stdio.h>

int main(void)
{    
    int i = 0,j = 0;
    //给i、j赋值。 
	i = 2;
    j = 2;
    //使用i++和++j。 
	i++;
    ++j;
    printf("i = %d\n",i);
    printf("j = %d\n",j);   
    return 0; 
}

运行结果如下:
5.PNG-5.8kB

我们可以从运行结果看出i++和++j的结果相同,都是将其加一。而增量运算符在单独运用时前缀和后缀并没有明显的区别,但是前缀和后缀的区别是非常的大的,在写代码时如果混淆他们两个会造成数据的运算错误。而详细区分他们两个的方法因为格式要求放在了下一题。

  • 前缀和后缀的区别
    题目给出的源代码如下:
#include <stdio.h>

int main(void)
{    
    int i = 0,j = 0;
    //给i和j统一赋值为2. 
	i = 2;
    j = 2;  
    //输出,i为后缀形式,j为前缀形式。 
	printf("%d\n",i++);
    printf("%d\n",++j);     
    return 0; 
}

运行结果如下:9.PNG-5.7kB

我们看到了输出的结果产生了差别,在输出语句中i++的值为2。而++j却是3,原因是什么呢?

外部资料详细的解释了前缀和后缀的区别和其区别产生的原因。i++输出为2的原因是因为后缀形式是先使用变量的值再给变量加一,下面我添加一个新的语句来证明此说法:

#include <stdio.h>

int main(void)
{    
    int i = 0,j = 0;
    //给i和j统一赋值为2. 
	i = 2;
    j = 2;  
    //输出,i为后缀形式,j为前缀形式。 
	printf("%d\n",i++);
    printf("%d\n",++j); 
	printf("i = %d,j = %d",i,j);    
    return 0; 
}

输出结果为:10.PNG-5.9kB
可以看见在运行上个输出语句后i和j变量的值都为3。可以证明后缀形式的增量(减量)运算符会让程序先使用变量的值后再给变量加1。

而前缀运算符输出的直接就是变量增加后的值。
例如,x += ++i其实可以拆分成:++i; x += i;x += i++可以拆分成x += i; i++所以这两个看起来十分相似的两个表达式其实最后的到的结果完全不一样,我们今后如果忽略这些编译器不会提示错误信息,但输出的结果却是错误的,很容易造成难以检查出来的bug。

3.花括号的使用和缩进的使用

我们在学习C语言的过程中,老师不仅教给了我们基本的语法,还教给了我们比较标准的格式。我们要养成使用正确标准的格式来写代码。这样,以后我们不仅写代码会更加的思路清晰,还会让我们检查错误时更加的容易。

以下的几个样例给我们展示了许多错误的习惯,也让我们能够更容易的理解怎样的格式更加符合规范。

(1.)

#include <stdio.h>

int main(void)
{    
    int i = 0;
    for(i = 1;i <= 5;i++)
        printf("*");
    printf("\n");   
    return 0; 
}

运行结果如下:1.PNG-5.7kB

(2.)

#include <stdio.h>

int main(void)
{    
    int i = 0;
    for(i = 1;i <= 5;i++)
        printf("*");
        printf("\n");   
    return 0; 
}

运行结果如下:1.PNG-5.7kB

(3.)

#include <stdio.h>

int main(void)
{    
    int i = 0;
    for(i = 1;i <= 5;i++)
    {
        printf("*");
        printf("\n");
    }
    return 0; 
}

运行结果如下:2.PNG-5.8kB

(4.)

#include <stdio.h>

int main(void)
{    
    int i =0;
    for(i = 1;i <= 5;i++)
    {
        printf("*");
    } 
    printf("\n");
    return 0; 
}

运行结果如下:3.PNG-5.7kB

显而易见的,除了第三个程序外,其他的三个程序的输出结果都相同,都是重复输出了5个“ * ”,而第三个程序却带上了换行符从而变成了5行“ * ”。

我们知道在c语言编程时,空位字符不会被识别,即循环体的认定与缩格形式无关。我们编程时强调格式主要目的是为了让我们的思路更加清晰和在今后查错时更容易查找。而根据以上所述,第一第二个程序在编译时是完全一样的。

第一和第二个程序完全形同的原因我们可以比较容易的看出,一个制表符并不能改变编译时的顺序。可第四个源程序和第一及第二个源程序在看起来却因为一对花括号看起来并不是那么的一样了,这也可以从侧面印证我们在写代码时标准的格式的重要性了。而原因就是因为for语句和if语句有一个相似的点,它只能和for后的第一个分号前的语句结合在一起。而第二个源程序却将两个语句缩进在了一起而没有将它们用花括号把它们统一为一个代码块,很明显虽然编写者希望两个语句在一个循环中运行,即目的为运行成第二个源程序所输出的结果却输出了和第一个源代码相同的结果。

当循环语句后有花括号时,花括号中的语句会成为一个代码块,一个代码块会被认定为一个语句而跟着它上面的循环语句。所以说:“{}”在程序中起作用,对结果产生很大的影响。

综上所述,我们在判断循环体时,当循环语句后没有花括号时,循环体只是循环语句后第一个“;”前的语句,而当循环语句后有花括号时,花括号中所包含的一切合法语句都是循环体,包括另一个循环,这也是嵌套的基础。

第一及第四个源程序都是正确的,符合了编写者的意图,猜测输出结果符合它们的预期。但第四个的代码格式却更加的规范,更加的易懂。

第三个源程序很规范且正确的实现了编写者所希望达到的目的。我今后写程序时也会争取做到为循环体加括号,哪怕这个循环体只有一个语句。

4.其他内容总结

我们的学习进度终于在开学两个多月后到达了循环结构这一最初的难点,我认为循环结构相较于我们之前所学习的其他内容,对逻辑的考察变得更多了,在写代码时耗费的时间也比之前的内容更多。而在写代码时需要注意到的点也变得更多了,下面我将举出几个我所认为的需要注意的点。

for循环一般用于计算循环的次数,在该循环中,控制变量的值再每次迭代时递增或递减指定的值,直到到达某个最终值为止。
while循环只要给定的条件为true就继续执行。如果循环条件一开始就是false,循环语句块就不执行。
do-while循环类似于while循环,但其循环条件在循环语句块执行后检查。因此循环语句块至少会执行一次。

  • 在for循环的括号中应该用;来分隔,而不是,。例如,我们这次作业的改错题中,源程序就误将,当做;从而产生了编译器不报错但会让输出结果错误的bug。我们当然不希望这种难以在今后检查出来的错误存在。
  • 在循环中使用的一些变量要在循环语句之前进行声明和初始化,例如:
#include <stdio.h>
 
int main(void)
{    
    int i = 0,num = 0,odd = 0,even = 0;
    //初始化循环需要用到的变量。 
	odd = 0;
    even = 0;    
	for(i = 1; i <= 10;i++)	//循环10次 
    {
        scanf("%d",&num);
        if(num % 2 == 0)
        {
            even++;
        }
        else
        {
            odd++;
        }       
    }   
	//循环完成,输出结果。 
	printf("%d %d",even,odd);
	return 0; 
}

的第7、8行就是初始化语句,如果在变量声明时和循环前都不进行初始化,那么运算的结果将会十分的出人意料。下面我将上述程序的初始化语句删除得到:

#include <stdio.h>
 
int main(void)
{    
    int i,num,odd,even;
    //初始化循环需要用到的变量。   
	for(i = 1; i <= 10;i++)	//循环10次 
    {
        scanf("%d",&num);
        if(num % 2 == 0)
        {
            even++;
        }
        else
        {
            odd++;
        }       
    }   
	//循环完成,输出结果。 
	printf("%d %d",even,odd);
	return 0; 
}

运行结果如下:捕获.PNG-7.3kB可以看到,运行结果很明显的不符合题目要求。原因我猜测是因为程序自动的将一个垃圾值赋值给了变量,而计算的正确结果却加在了这个垃圾值上。我们可以用一下的程序来验证我所提出的猜测。

#include <stdio.h>
 
int main(void)
{    
    int i,num,odd,even;
    int i_b,num_b,odd_b,even_b;
    //尝试将垃圾值储存下来。
	i_b = i;
	num_b = num;
	odd_b = odd;
	even_b = even; 
	//初始化循环需要用到的变量。   
	for(i = 1; i <= 10;i++)	//循环10次 
    {
        scanf("%d",&num);
        if(num % 2 == 0)
        {
            even++;
        }
        else
        {
            odd++;
        }       
    }   
	//循环完成,输出结果。 
	printf("%d %d",even-even_b,odd-odd_b);
	return 0; 
}

输入同样的数据输出结果为:1.PNG-6.2kB成功证明我的猜测正确。

  • 在写for循环中的判断时,一定要清楚自己想要让循环体执行几次。想要实现可以调整循环体中语句的顺序,如先num += t和先t = t+3的结果会相差一次循环。我们可以在纸上先运行一下自己的程序顺序来理清思路。

(三)实验总结

1.求奇数分之一序列前N项和

本题要求编写程序,计算序列 1 + 1/3 + 1/5 + ... 的前N项之和。

Raptor流程图如下:
4.PNG-16.5kB

源代码如下:

//1 求奇数分之一序列前N项和
#include <stdio.h>

int main(void)
{
	int N = 0;
	double sum = 0.0;
	
	scanf("%d",&N);
	unsigned int i = 1;
	for(;i <= N;i++)
	{sum += 1.0/(i*2-1);}
	
	printf("sum = %f",sum);
	return 0;
}

这道题是我们学习完for语句后的第一道作业题,虽然简单,但我们因为是第一次运用for语句,可能还不能熟练的使用它。可能产生几个问题:1.没有初始化i的值使得程序随机执行与结束。2.for后的括号中缺少或没有分号,将分号误用为逗号。3.没有理清楚for循环的次数。

而避免产生这些错误的方法为:1.记住for中的第一个表达式初始化要在for循环中使用的变量,大部分是将其初始化为0或1。2.明确记住for的括号后必定有两个分号。可以没有表达式,但一定要有分号。3.在演算纸上将程序运行循环的每一步写出来。(较多时中间可以省略。)

PTA提交列表如下:
5.PNG-12.7kB

3.奇偶分家

给定N个正整数,请统计奇数和偶数各有多少个?

Raptor流程图如下:
6.PNG-20.5kB
7.PNG-10.4kB
我的流程图拼接错误,在老铁的提醒下修改为:
1.PNG-20.6kB
2.PNG-4.3kB

本题源代码如下:

//3 奇偶分家
#include <stdio.h>

int main(void)
{
	int N = 0,odd = 0,event = 0,number = 0;
	
	scanf("%d",&N);
	
	for(unsigned int  i = 1;i <= N;i++)
	{
		scanf("%d",&number);
		if((number % 2) == 0)
		{
			odd++;
		}
		else 
		{
			event++;
		}
	}
	
	printf("%d %d",event,odd);
	
	return 0;
}

实验分析:
这道题的重点在于循环中会嵌套判断语句,以及在判断语句后怎么处理数据。在这道题中我直接在for语句中声明了变量i,但我这种声明方式会使得i变量只能在循环中使用,在循环外无法使用。此外,需要注意的是0属于偶数,所幸它与2求余的结果为零使得程序不需要再增加判断语句。

本题PTA提交列表:
8.PNG-29.9kB

5.统计字符

本题要求编写程序,输入10个字符,统计其中英文字母、空格或回车、数字字符和其他字符的个数。

本题Raptor流程图如下:
9.PNG-23.5kB
10.PNG-15.8kB

本题源代码如下:

//5 统计字符
#include <stdio.h>
#include <ctype.h>

int main(void)
{
	char x = ' ';
	int letter = 0,blank = 0,digit = 0,other = 0;
	
	unsigned int i = 1;
	for(;i <= 10;i++)
	{
		scanf("%c",&x);
		
		if(isalpha(x))
		{letter++;}
		else if(isspace(x))
		{blank++;}
		else if(isdigit(x))
		{digit++;}
		else
		{other++;}
	}
	
	printf("letter = %d, blank = %d, digit = %d, other = %d",letter,blank,digit,other);
	
	return 0;
}

实验总结:
这道题既可以用ASCII码值来判断输入的为哪种类型,也可以直接调用ctype.h头文件所定义的值。而我的源代码所用的方法为后者,当使用前者时要注意大写字母和小写字母之间在ASCII码中还隔有其他字符。

本题PTA提交列表如下:
11.PNG-8.2kB

7.求交错序列前N项和

本题要求编写程序,计算交错序列 1-2/3+3/5-4/7+5/9-6/11+... 的前N项之和。

Raptor流程图如下:
12.PNG-15.7kB
13.PNG-1.9kB

本题源代码如下:

//7 求交错序列前N项和
#include <stdio.h>
#include <math.h>

int main(void)
{
	int N = 0;
	double sum = 0.0;
	
	scanf("%d",&N);
	
	unsigned int i = 1;
	for(;i <= N;i++)
	{
		sum += pow(-1.0,i+1) * (double)i/(i*2-1);
	}
	
	printf("%.3f",sum);
	return 0;
}

实验总结:
这道题的难点在于加时的符号问题,我们既可以从数学角度来解决问题,也可以用flag变量来解决问题。当使用flag变量时,需要注意的是flag在每一次循环中的正负值。

int flag = 1;

flag = -flag;   //此语句在循环中的赋值语句后。

甚至还要注意flag变量的变号是在赋值语句之前还是之后。

本题PTA提交列表如下:
14.PNG-10.5kB

(四)博客互评

我对李晓晓同学博客的评论:

大佬你在修改错误时将错误都用红笔划了出来。而且你将比较大的流程图切割开再在网页上拼接上使得图片更容易看清。这些都是我没有做到应该向你学习的。
可大佬从来不回我。

我对风离你在同学博客的评论:

大佬你的博客非常的符合规范,排版整齐。流程图的图片截了两次使得我们更容易看清。但我认为你的实验总结好像有一些显得单薄。

我对陈魔同学的博客的评论:

大佬你将题目的要求叙述的更加详细,对截图图片进行了处理,还对文中重点部分用格式突出显示了出来。这些都让我们更容易的阅读,看大佬的博客很赏心悦目,心情轻松。

我对孤寂一粒沙同学的博客的评论:

大佬的改错完全按照助教的要求分每一步的把错误原因和改进方法写了出来,非常符合格式。而且大佬你的插入代码非常多,使得证明显得非常的专业和有力。

感谢早交的同学们。我的评论如有冒犯,请多多包涵。我也希望同学们评价我的博客是请尽量指出我的错误与不足,你的意见是我进步的动力。

posted @ 2017-11-09 23:33  人满为患  阅读(1574)  评论(8编辑  收藏  举报