《C程序设计语言(K&R)》习题记录 第一章 引言

第一章 引言

练习1-9:编写一个将输入复制到输出的程序,并将其中连续的多个空格用一个空格代替

  1. 很自然的,我们希望首先输入一个字符,然后判断它是否是空格,如果是,那么继续输入下一个字符并且判断其是否为空格。因为考虑可能有多个空格,那么需要一个循环,循环的条件设置为“首先输入为空格”。这样一来,在循环当中输入,每一个空格都用后面的字符覆盖,直到遇到一个非空格的字符,输出并且退出循环。如果开始的字符并非空格,输出即可。从而有以下代码:
/*复制输入,将多个空格转换成一个空格*/
#include
int main(void)
{
	char ch;
	while((ch=getchar())!=EOF)
	{
		if(ch==' ')
		{
			printf("%c",ch);
			while(ch==' ')
				ch=getchar();
			printf("%c",ch);
		}
		else 
			printf("%c",ch);
	}
	return 0;
 } 
  1. 另外一个想法是,设置两个变量,一个负责实时的输入,另一个则负责记录上一个字符。如果输入了空格,那么将上一个字符与空格比较,如果不是,则输出。无论是不是,都需要将这个值赋值给上一个(空格则不影响),且继续开始循环。代码如下:
#include
int main()
{
	int ch,lastc;
	lastc='a';
	while((ch=getchar())!=EOF)
	{
		if(ch!=' ')
			putchar(ch);
		if(ch==' ')
			if(lastc!=' ')
				putchar(ch);
		lastc=ch;
	}
	return 0;
}

练习1-1x:关于字符输入计数的相关问题
1、统计单词数量
想法:将单词视为连续的字母(只有一个字母并不无影响)。从而可以使用while循环来进行不断地输入。如果遇到了非字母的字符,循环结束,此时计数变量递增即可。这种想法和书上的不一样,但是是可行的。代码如下:

/*统计单词数*/ 
#include
int main(void)
{
	int ch,num;
	while((ch=getchar())!=EOF)
	{
		if(ch!=' '&&ch!='\n'&&ch!='\t')
		{
			while((ch=getchar())!=' '&&(ch!='\n')&&ch!='\t')
					;
			++num;
		}
	}
	printf("%d",num);
	return 0;
}

2、以每行一个单词的形式打印输入
事实上这只需要在前面计数程序的基础上做一点点改动即可:在每次while循环前和循环内部打印输入,在循环结束时打印换行符即可。代码如下:

/*以每行一个单词的形式打印输入*/
#include
int main()
{
	int ch;
	while((ch=getchar())!=EOF)
	{
		if(ch!=' '&&ch!='\n'&&ch!='\t')
		{
			putchar(ch);
			while((ch=getchar())!=' '&&(ch!='\n')&&ch!='\t')
					putchar(ch);
			putchar('\n');
		}
	}
	
	return 0;
 }

3、打印输入中单词长度的直方图

不太理解最终需要达到的效果······那么就按照我的想法去做了!

  • 效果1:在每一行打印一个单词,同时在固定的字段宽度之后打印星号,星号数等于单词长度。代码如下:
#include
#define length 30 
int main()
{
	int ch,m,n,size;
	//m,n用于循环,size为单词长度 
	m=n=size=0; 
	while((ch=getchar())!=EOF)
	{
		if(ch!=' '&&ch!='\n'&&ch!='\t')
		{
			putchar(ch);
			++size;
			while((ch=getchar())!=' '&&(ch!='\n')&&ch!='\t')
					{
						putchar(ch);	
						++size;
					}
			//printf("%d",size);//测试输出 
			for(m=0;m

效果2:打印一个单词之后,从下一行开始打印与单词长度n相同数量的星号,考虑到美观,我们打印边长为n的正方形。还可以考虑用指定符号打印。代码如下:

/*打印正方形*/ 
#include
int main(void)
{
	char ch;
	int m,n,size;
	m=n=size=0;
	while((ch=getchar())!=EOF)
	{
		if(ch!=' '&&ch!='\t'&&ch!='\n')
		{
			putchar(ch);
			++size;
			while((ch=getchar())!=' '&&ch!='\t'&&ch!='\n')
			{
				putchar(ch);
				++size;
			}
			putchar(':');
			putchar('\n');
			//printf("%d",size);
			for(m=0;m

尽管这个程序并不复杂,但是还是有一些细节需要注意:
—为了打印一行之后打印下一行,不要忘记在内层循环结束之后添加换行符。
—由于每一行的星星之间没有空格,但是两行之间有行间距,因此考虑打印一个星星之后再打印一个空格,使其看起来更像一个正方形。
—注意每次完成一个单词的任务之后,需要将每一个变量清零。

发现记录:如果每次while循环当中有输出,则按下Enter键会执行一次程序。但是如果没有输出,则需要手动输入Ctrl+Z来结束输入,执行程序。

4、以上想法是基于使用while循环的单词计数程序的。事实上书上的做法更为简洁:采用一个变量state记录当前状态。如果处于单词之中则值为1,单词之外则值为0。关键的想法是,进入递增计数变量的条件是state==0,而在进入之后则将值更新为1,从而使得进入每个单词变量只递增一次。代码如下:

/*单词计数*/
#include
#define IN 1
#define OUT 0
int main(void)
{
	int state,ch,num;
	num=0;
	while((ch=getchar())!=EOF)
	{
		if(ch==' '||ch=='\t'||ch=='\n')
			state=OUT;
		else if(state==OUT)
		{
			state=IN;
			++num;
		}
	}
	printf("%d",num);
	return 0;	
}

练习1-16
注意以下几点:
—注意示例程序之前的思路,即设计好算法的基本框架、将程序划分为几块,分而治之。
—通过getchar读取字符到数组时,程序手动添加了空字符'\0',注意这个现象。
代码如下:(与答案不完全一样,可能有bug)

/*-----1-16-----*/
#include
#define MAX 1000
int getline(char x[]);
void copy(char to[], char from[]);

int main(void)
{
	int size;//当前行长度 
	int max;//目前最长行长度 
	char line[MAX];	//存储当前行 
	char longest[MAX];//存储最长行 

	max=0;
	while((size=getline(line))>0)
	{
		//printf("%d\n",size);
		max=size;
		copy(longest, line);
	 } 
	 
	if(max>0)
		printf("%d  %s",max,longest);
	return 0;
}
/*返回行长度,并尽可能多的存储字符*/ 
int getline(char x[])
{
	int num;
	char ch;
	for(num=0;(ch=getchar())!=EOF && ch!='\n';++num)
	{
		x[num]=ch;
	}
	if(num

While循环、getchar()函数与EOF
事实上,在以上代码当中,我们发现了一些有趣的现象:
1、在同一段程序中,针对不同的输入,按Ctrl+Z结束输入的次数是不同的,有时一次即可,有时确实需要两次甚至好多次。大致来说的情况有两种:在输入换行符之后按下Ctrl+Z一次即可结束输入;在非换行符之后按下Ctrl+Z,则必须再输入换行符、不输入,再按换行符使得出现一个空行,再按下Ctrl+Z才可以结束输入。
2、如果while循环当中有输出,则每次按下Enter会进行一次输出。
查询资料,有以下结果:

EOF虽然是文件结束符,但并不是在任何情况下输入Ctrl+D(Windows下Ctrl+Z)都能够实现文件结束的功能,只有在下列的条件下,才作为文件结束符。
(1)遇到getcahr函数执行时,要输入第一个字符时就直接输入Ctrl+D,就可以跳出getchar(),去执行程序的其他部分;
(2)在前面输入的字符为换行符时,接着输入Ctrl+D;
(3)在前面有字符输入且不为换行符时,要连着输入两次Ctrl+D,这时第二次输入的Ctrl+D起到文件结束符的功能,至于第一次的Ctrl+D的作用将在下面介绍。

EOF作为行结束符时的情况:这时候输入Ctrl+D并不能结束getchar(),而只能引发getchar()提示下一轮的输入。 这种情况主要是在进行getchar()新的一行输入时,当输入了若干字符(不能包含换行符)之后,直接输入Ctrl+D,此时的Ctrl+D并不是文件结束符,而只是相当于换行符的功能,即结束当前的输入。

其实关于第二点,并没有任何新奇的地方。因为如果while循环当中没有任何输出,按下Enter键之后也会将缓冲区当中的输入传递给程序,只不过我们看不见而已。这种情况下只有我们手动结束输入、执行循环之后的程序我们才可能明显地观察到输入结束的现象(如果程序有输出)。

练习1-17:打印长度大于80个字符的所有输入行
按照本节所涉及到的知识来推测的话这题并不复杂,在示例的基础上稍微改进即可。代码如下:

/*-----练习1-17,打印长度大于80的所有输入行-----*/ 
#include
#define LIM 80
#define MAX 1000
int getline(char x[]);

int main(void)
{
	char line[MAX];
	int size;
	while((size=getline(line))>0)
	{
		if(size>LIM)
			printf("%d	%s",size,line);
		}	
	
	return 0;
}

int getline(char x[])
{
	int size;
	char ch;
	for(size=0;size

但是我们当然不满足于当前的情况:如何实现输入多组数据,最后通过EOF来输出长度大于80的行,而不是按下Enter即进行一次输出(如果长度大于80)。简陋的版本是有输入行数限制的:创建一个二维数组。代码如下:

/*-----1-17改进版本-----*/
#include
#include
#define LIM 30   //输入行长度大于30进行输出 
#define ROW 10	 //每组输入最多10行 
#define MAX 1000 //每行最长1000字符
int getline(char x[]);

int main(void)
{
	char x[ROW][MAX];//保存当前输入行 
	char improve[ROW][MAX]; //保存符合要求的输入行
	int m,n,ch,size;//m是当前输入行的计数变量,n是符合要求的输入行的计数变量
	m=n=size=0;
	int num=0;
	for(m=0;m0;++m)
	{
		if(size>LIM)
		{
			strcpy(improve[n],x[m]);
			++n;		
		}	
	 }
	if(m==ROW)
		printf("输入行已经达到最大限度!\n");
	if(n==0)
		printf("没有发现长于%d的输入行!\n",LIM);
	else {
		printf("以下是长于%d的输入行:\n",LIM); 
		for(num=0;num<=n;num++)
			printf("%s\n",improve[num]);
	}
	return 0;
}
/*getline函数:返回输入行的长度*/ 
int getline(char x[])
{
	int size,ch;
	
	for(size=0; (ch=getchar())!='\n';++size)
		x[size]=ch;
	x[size]='\0';
	
	return size;
}

练习1-19:编写函数reverse(s)输出反序字符串s`
如果只是一次输出则非常简单,但是对于多次输入输出则必须注意一些细节,使得程序每次都能完成任务。这些细节包括:
—注意调用函数reverse的条件:一行输入结束,即遇到换行符。
—每次执行完之后需要将储存字符的数组清零,避免其中的字符影响下一次的输出。
—注意字符串长度与字符数组下标之间的对应关系。
代码如下:

/*-----1-19-----*/
#include
#include
#define MAX 1000
void reverse(char x[]);

int main(void)
{
	char x[MAX];
	int ch,num;
	num=0;
	while((ch=getchar())!=EOF)
	{
		if(ch!='\n')
		{
			x[num]=ch;
			++num;
		}
		else {
			reverse(x);
			num=0;
		}		
	}
	return 0;
}

void reverse(char x[])
{
	int count=strlen(x);
	int m;
	for(m=count-1;m>=0;--m)
	{
		putchar(x[m]);
		x[m]='\0';
	}
	putchar('\n');
}

练习1-20:将输入中的制表符换成适当数目的空格
首先分析题目:
—制表符终止位的含义。需要注意网上的一些资料说Windows下制表符自动填补4位,但是笔者实际遇到的情况是自动填补8位。
—注意可能遇到的多种情况——制表符出现的位置,可能是接连的多个制表符、制表符出现在单词之后、出现在空格之后。显然大致可以分为两种情况。如果在单词之后出现,显然需要考虑到单词长度从而自动补位,在这其中还需要考虑到单词长度为8的情况。(这与我们打印空格的算法有关)。
—为了便于我们观察程序的打印效果,不妨将空格替换成星号。
代码如下:

/*-----1-20-----*/
#include
#define NUM 8
void printf_detab(int x);

int main(void)
{
	int ch,x,count;
	/*ch输入字符,x记录单词长度,count记录制表符长度*/
	//int m; 
	while((ch=getchar())!=EOF)
	{
		/*首字符为制表符*/ 
		if(ch=='\t')
			printf_detab(NUM);
		x=0;
		/*制表符出现在单词之后*/ 
		while((ch!='\n') && (ch!='\t'))
		{
			putchar(ch);
			++x;
			ch=getchar(); 
		}
		if(ch=='\t')
			{
				if(x==8)
					printf_detab(NUM);
				/*计算需要打印的空格数*/ 
				for(count=0;count<=NUM;count++)
				{
					if((x+count) % NUM==0)
						break;
				}
				printf_detab(count);
			}
	}
	return 0;
}
/*printf_detab函数:打印x个空格*/ 
void printf_detab(int x)
{
	int m=0;
	for(m=0;m

练习1-21:将空格串替换成最少数量的制表符和空格,并且确保单词间隔不变。
花了两个小时写了一个方法在本质上是错误的程序······关键在于制表符打印的空格数并不是仅仅依赖于前面单词的长度,而是依赖于制表符前面所有的字符长度。所以这个程序仅仅能够解决两个单词时的情况。代码如下:

/*-----有本质缺陷的方法-----*/ 
#include
#define NUM 8

void print_entab(int x, int y);

int main(void)
{
	int ch,ch_temp,x,y;
	int time;
	while((ch=getchar())!=EOF)
	{
		if(time!=0)
			x=1;
		else x=0;
		y=0;
		while((ch!=' ') && (ch!='\t') &&(ch!='\n'))
		{
			++x;
			putchar(ch);
			//printf("现在x是%d。\n",x); 
			ch=getchar();
		}
		//printf("现在x是%d。\n",x); 
		while(ch==' ')
		{
			++y;
			ch=getchar();
		}
		//printf("现在y是%d。\n",y); 
		print_entab(x,y);
		putchar(ch);
		if(ch!=' ' && ch!='\t')
			time++;
		if(ch=='\n')
			time=0;
	}
	return 0;
}

void print_entab(int x, int y)//x是单词长度,y是空格个数 
{
	int x_temp;//用于将x替换成我们理想中的x 
	int num_tab,num_blank;//打印制表符的个数
	int num_key=0;//首个制表符所占空格数,剩余空格数 
	int m,n;//循环打印制表符和空格
	int p,q;//用于特殊情况的打印 
	x_temp=x/NUM;
	x-=x_temp*NUM;
	//printf("理想的x是%d。\n",x); 
	
	if(x+y=NUM && y!=0)
	{
		//num_key=0;
		while((x+num_key)%NUM!=0)
			++num_key;
		
		if(y-num_key=NUM)
		{
			//printf("%d",num_key);
			for(m=0;m

下午花了一个多小时重写了代码,发现其实自己之前的理解似乎有问题。题目中“最少数量的制表符和空格”,应该理解为制表符数加上空格数。而由于制表符长度显然不少于空格数,因此应该尽可能多地使用制表符。但不管怎样理解,写出符合想象效果的代码都是一种锻炼。代码如下:

/*-----1-21-----*/
#include
#define NUM 8
void print_entab(int x, int y);

int main(void)
{
	int x,y;//x记录全部的字符长度,y记录空格数 
	int ch;
	x=y=0;
	while((ch=getchar())!=EOF)
	{
		while((ch!=' ') && (ch!='\n'))
		{
			++x;
			putchar(ch);
			ch=getchar();
		}
		while(ch==' ')
		{
			++y;
			ch=getchar();
		}
		x+=y;
		//putchar('\n');
		//printf("x=%d,y=%d.\n",x,y);
		print_entab(x-y,y);
		y=0;
		putchar(ch);
		if(ch!='\n')++x;
		/*换行符一定是每行最后一个字符,换行时重置x、y*/ 
		if(ch=='\n')x=y=0;	
	}
	//scanf("%d %d",&x,&y);
	//print_entab(x,y);
	return 0;
 } 
 
void print_entab(int x, int y)
{
	int num_blank,num_tab;
	int key_x;//记录第一个制表符的长度 
	int m,n;
	key_x=NUM-(x-(x/NUM)*NUM);
	//printf("key_x:%d\n",key_x);
	if(y==0)
	{
		num_blank=0;
		num_tab=0;
	}
	if(y=key_x && y=key_x+NUM)
	{
		num_tab=(y-key_x)/NUM+1;
		num_blank=y-key_x-(num_tab-1)*NUM;
	}
	
	//printf("num_tab=%d,num_blank=%d.\n",num_tab,num_blank);
	for(m=0;m

总结:感觉这道题的想法很简单,但是实际写的时候却出现了很多问题,主要在于:对于程序的每个部分结束时的运行情况没有了如指掌,因此很可能出现逻辑衔接上的错误;对于各种可能的情况没有考虑周全,例如y==0就是后面程序出现问题才加上去的;对于代码没有一种清晰的认识,目前仍然有一种照葫芦画瓢的感觉,例如while循环输入的使用。

你可能感兴趣的:(C语言)