周立功教授数年之心血之作《程序设计与数据结构》,电子版已无偿性分享到电子工程师与高校群体,在公众号回复【程序设计】即可在线阅读。书本内容公开后,在电子行业掀起一片学习热潮。经周立功教授授权,本公众号特对本书内容进行连载,愿共勉之。
第一章为程序设计基础,本文为1.8.2 字符串常量第二点:字符串的输入输出。
(1)scanf()函数和gets()函数
在读取字符串时,scanf()和转换格式符%s只能读取一个单词,比如:
scanf("%s\n", str);
在scanf函数调用中,不需要在str前添加&,因为str是数组名,编译器在将它传递给函数时,会将它当作指针来处理。调用时,scanf函数会跳过空字符,然后读入字符并存储到str中,直到遇到空字符为止,scanf函数始终会在字符串末尾存储一个空字符。
在程序中经常要读取一整行输入,而不仅仅是一个单词,gets()就是用于处理这种情况的。它读取整行输入直至遇到换行符,然后丢弃换行符存储其余字符,并在这些字符的末尾添加一个空字符使其成为一个字符串。它经常和puts()配对使用,该函数用于显示字符串,并在末尾添加换行符。即gets()是从标准输入设备中输入若干个字符,并保存到参数s指向的字符数组中,直到文件结束或读到一个换行符。换行符将被丢弃,在输入最后一个字符后会立即写入一个结束符'\0'。其函数原型如下:
char *gets(char *s);
其中的s指向保存输入字符串的内存空间,如果gets()成功地获得了字符串,则返回s,否则返回NULL。比如,通过命令行输入一个字符'9',但'9'不是整数9,如果将'9'-'0',则会得到整数9。即:
char cStr[256];
int cmdNum;
cmdNum = getchar() - '0';
gets(cStr); //清空缓冲区
如果将数组作为参数传递,则传递的是指向数组首元素的指针,当gets()作为被调用函数时,则完全不知道数组究竟有多大,而调用者又不能向gets()传递缓冲区的大小,因此gets()无法检查数组的长度。显然必须有足够的空间保存输入的字符串,否则可能出现莫名其妙的问题。如果你故意将尺寸很大的数据传递给gets(),就可以达到数组越界且改写返回地址的目的。1988年名震互联网的“互联网蠕虫”病毒,就是利用了gets()的这个弱点。
由于gets()的不安全行为造成了隐患,因此制定C11标准的委员采取了强硬的态度,直接从标准中废除了gets()函数。不妨自己编写一个输入函数,假设函数不会跳过空字符,在第一个换行符(不存储到字符串中)处停止读取,且忽略额外的字符。其函数原型如下:
int readLine(char str[], int n);
readLine()函数主要由一个循环构成,只要str中还有空间,此循环就会调用getchar()函数逐个读入字符并将它存储在str中,在读入换行符时循环终止,详见程序清单 1.40。
程序清单 1.40 readLine()函数的实现
1 int readLine(char str[], int n)
2 {
3 int ch, i = 0;
4
5 while((ch = getchar()) != '\n')
6 if(i < n)
7 str[i++] = ch;
8 str[i] = '\0';
9 return 0;
10 }
(2)printf()函数和puts()函数
转换格式符s%允许printf()写字符串,与puts不同的是,printf()不会自动地在每个字符串的末尾加上一个换行符,因此必须在参数中指明应该在哪里使用换行符。比如:
char str[] = "hello world";
printf("%s\n", str);
printf()会逐个写字符串中的字符,直到遇到空字符为止。如果只想显示字符串的一部分,可以使用转换格式符%.ps,这里的p是显示的字符数量。比如,显示hello:
printf("%.5s\n", str);
虽然printf()用起来比较复杂,但可以打印多个字符串。除了printf(),C标准库还提供了puts(),其函数原型如下:
int puts(const char *s);
其中,s为指定输出的字符串,puts()函数将参数s指向的字符串输出到标准输出设备中,但不输出结束符'\0'。在输出字符串后,puts()函数会多输出一个换行符'\n',然后通过标准输出设备显示指定的字符串。如果显示成功,则返回0,否则返回预定义常量EOF。puts()如何知道在何处停止呢?该函数在遇到空字符时就停止输出,所以必须确保有空字符。
(3)fgets()函数和fputs()函数
fgets()和fputs()分别是gets()和puts()针对文件的定制的版本,fgets()通过第2个参数限制读入的字符数来解决溢出的问题,该函数专门用于处理文件输入。如果第2个参数的值是n,那么fgets()将读入n-1个字符,或遇到第1个换行符为止。如果读到一个换行符将它存储在字符串中,这点与gets()不同,gets()会丢弃换行符。
fgets()的第1个参数与gets()一样,也是存储输入位置的地址(char *类型),第2个参数是一个整数,表示待输入字符串的大小,最后一个参数是文件指针,指定待读取文件。如果读入从键盘输入的数据,则以标准输入stdin作为参数,该标识定义在stdio.h中。其调用示例如下:
fgets(buf, STLEN, fp);
其中,buf是char类型数组的名称,STLEN是字符串的大小,fp是指向FILE的指针。以上面的gets()为例,fgets()读取输入直到第1个换行符的后面,或读到文件结尾,或读取STLEN-1个字符,然后fgets()在末尾添加一个空字符使之成为一个字符串,字符串的大小是其字符数加上一个空字符。如果fgets()在读到字符上限之前已经读完一整行,它会将表示行结尾的换行符放在空字符前面。fegts()在遇到EOF时将返回NULL,因此可以利用这一机制检查是否到达文件结尾。如果未遇到EOF,则返回它的地址。
fgets()存储换行符有好处也有坏处,坏处是你可能不想将换行符存储在字符串中,这样的换行符会带来一些麻烦。好处是对于存储的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。如果不是一整行,则要妥善处理一行中剩下的字符。
首先,如何处理换行符?一个方法是在已经存储的字符串中查找换行符,并将其替换成空字符。假设\n在st中:
while(st[i] != '\n' )
i++;
st[i] = '\0';
其次,如果仍有字符串留在输入行怎么办?一个可行的办法是,如果目标数组装不下一整行输入,就丢弃那些多出的字符。即读取但不存储输入,包括\n:
while(getchar() != '\0')
continue;
为何要丢弃输入行中余下的字符?因为输入行中多出来的字符会留在缓冲区中,成为下一次读取语句的输入。比如,如果下一条读取语句要读取的是double类型的值,就可能导致程序崩溃,而丢弃输入行余下的字符是为了保证读取语句与键盘输入同步。既然没有这样的函数,那么就创建一个,s_gets()函数详见程序清单 1.41。
程序清单1.41 s_gets()函数
1 char * s_gets(char *st, int n)
2 {
3 char *ret_value;
4 int i = 0;
5
6 ret_value = fgets(st, n, stdin);
7 if(ret_value){
8 while(st[i] != '\n' && st[i] != '\0')
9 i++;
10 if(st[i] == '\n')
11 st[i] = '\0';
12 else
13 while(getchar() != '\0')
14 continue;
15 }
16 return ret_value;
17 }
如果fgets()返回NULL,说明读到文件结尾或出现读取错误,s_gets()跳过了这个过程。其中的循环:
while(st[i] != '\n' && st[i] != '\0')
i++;
遍历字符串,直到遇到换行符或空字符。如果先遇到换行符,下面的if语句将其替换成空字符;如果先遇到空字符,else部分便丢弃输入行的剩余字符,然后返回与fgets()相同的值。
尽管s_gets()用于替换fgets()已经有了很大的改进,但还是不完美。如果遇到不合适的输入时,它毫无反应。它丢弃多余的字符时,也不通知程序也不告知用户,请读者完善。
由于fgets()将换行符放在字符串的末尾(假设输入行不溢出),通常要与fputs()配对使用,除非该函数不在字符串末尾添加换行符。
fputs()函数接受两个参数:第1个是字符串的地址,第2个是文件指针,指明要写入的文件,该函数根据传入地址找到的字符串写入指定的文件中。如果要显示在计算机显示器上,应使用标准输出stdout作为参数。和puts()不同的是,puts()在打印字符串时,不会在其末尾添加换行符。其调用示例如下:
fputs(buf, fp);
其中,buf是字符串的地址,fp用于指定目标文件。注意,gets()丢弃输入中的换行符,但puts()在输出中添加换行符。而另一方面,fgets()保留了输入中的换行符,fputs()在输出中不会添加换行符。
-
字符串
+关注
关注
1文章
578浏览量
20506
原文标题:周立功:字符真正价值在于形成字符序列——字符串的输入输出
文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论