行 I/O 可以用两种方式执行——未格式化的或格式化的。这两种形式都用于操作字符串。区别在于未格式化的 I/O(unformatted line I/O) 简单读取或写入字符串,而格式化的 I/O 则执行数字和其他变量的内部和外部表示形式之间的转换。本文中,讨论的是未格式化的行 I/O 。
gets 和 puts 函数家族是用于操作字符串而不是单个字符。这个特征使它们在那些处理一行行文本输入的程序中非常有用。这些函数的原型如下所示:
char * fgets(char * buffer, int buffer_size, FILE * stream);
char * gets(char * buffer);
int fputs(const char * buffer, FILE * stream);
int puts(const char * buffer);fgets 从指定的 stream 读取字符并把它们复制到 buffer 中。当它读取一个换行符并存储到缓冲区之后就不再读取。如果缓冲区内存储的字符数达到 baffer_size - 1 个时它也停止读取。在这种情况下,并不会出现数据丢失的情况,因为下一次调用 fgets 将从流的下一个字符开始读取。在任何一种情况下,一个 NUL 字符将被添加到缓冲区所存储数据的末尾,使它成为一个字符串。
如果在任何字符读取前就达到了文件尾,缓存区就未进行修改, fgets 函数返回一个 NULL 指针。否则, fgets 返回它的第一个参数(指向缓冲区的指针)。这个返回值通常只用于检测是否达到了文件尾。
传递给 fputs 的缓冲区必须包含一个字符串,它的字符被写入到流中。这个字符串预期以 NUL 字节结尾,所以这个函数没有一个缓冲区长度参数。这个字符串是逐字写入的:如果它不包含一个换行符,就不会写入换行符。如果它包含好几个换行符,所有的换行符都会被写入。因此,当 fgets 每次都读取一整行时, fputs 却既可以一次写入一行的一部分,也可以一次性写入一整行,甚至可以一次写入好几行。如果写入时出现了错误, fputs 返回常量值 EOF ,否则它将返回一个非负值。
请看下面这个函数:
/*
** 把标准输入读取的文本行逐行复制到标准输出。
*/
#include <stdio.h>
#define MAX_LINE_LENGTH 1024 //可以复制的最长行
void copyline(FILE * input, FILE * output)
{
char buffer[MAX_LINE_LENGTH];
while(fgets(buffer, MAX_LINE_LENGTH, input) != NULL)
fputs(buffer, output);
}它从一个文件读取输入行并原封不动地写入到另一个文件。常量 MAX_LINE_LENGTH 决定了缓冲区的长度,也就是可以被读取的一行文本的最大长度。在这个函数中,这个值并不重要,因为不管长行是被一次性读取还是分段读取,它所产生的结果文件都是相同的。另一方面,如果函数需要计数被复制的行的数目,太小的缓冲区将产生一个不正确的计数,因为一个长行可能被分为数段进行读取。我们可以通过增加代码,观察每段是否以换行符结尾来修正这个问题。
缓冲区的长度的正确值通常是根据需要执行的处理过程的本质而作出的折衷。但是,即使溢出它的缓冲区, fgets 也绝不引起错误。
注意 fgets 无法把字符串读入到一个长度小于两个字符的缓冲区,因为其中一个字符需要为 NUL 字节保留。
gets 和 puts 函数几乎和 fgets 与 fputs 相同。之所以存在它们是为了允许向后兼容。它们之间的一个主要功能性区别在于当 gets 读取一行输入时,它并不在缓冲区中存储结尾的换行符。当 puts 写入一个字符串时,它在字符串写入之后向输出再添加一个换行符。另一个区别仅存在于 gets ,这个从函数的原型中就清晰可见:它没有缓冲区长度参数。因此 gets 无法判断缓冲区的长度。如果一个长输入行读到一个短的缓冲区,多出来的字符将被写入到缓冲区后面的内存位置,这将破坏一个或多个不相关变量的值。这个事实导致 gets 函数只适用于玩具程序,因为唯一防止输入缓冲区溢出的方法就是声明一个巨大的缓冲区。但不管它多大,下一个输入行仍有可能比缓冲区更大,尤其是当标准输入被重定向到一个文件时。