第一章 引言
练习1-9:编写一个将输入复制到输出的程序,并将其中连续的多个空格用一个空格代替
/*复制输入,将多个空格转换成一个空格*/
#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;
}
#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、打印输入中单词长度的直方图
不太理解最终需要达到的效果······那么就按照我的想法去做了!
#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循环输入的使用。