13.4 文件I/O:fprintf()、fscanf()、fgets()和fputs()函数

前面章节中的每个I/O函数都存在一个相似的文件I/O函数。主要的区别在于您需要使用一个FILE指针来为这些新函数指定要操作的文件。与getc()和putc()相似,这些函数要求您使用指向FILE的指针(如stdout),或者使用fopen()函数的返回值来指定文件。

13.4.1 fprintf()和fscanf()函数

文件I/O函数fprintf()和fscanf()的工作方式与printf()和scanf()相似,区别在于前两者需要第一个参数来指定合适的文件。前面您已经使用过fprintf()。程序清单13.3演示了这两个文件I/O函数以及rewind()函数的使用。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

程序清单13.3 addaword.c程序

阅读 ‧ 电子书库

阅读 ‧ 电子书库

通过该程序可以向文件中加入单词。使用“a+”模式,程序可以对文件进行读写操作。第一次使用该程序的时候会创建一个worddy文件以供添加单词。在随后的使用中,可以向以前的内容后面添加(追加)单词。追加模式只能向文件结尾添加内容,但“a+”模式可以读取整个文件。rewind()命令使程序回到文件开始处,这样最后的while循环就可以打印文件的内容。注意rewind()函数接受一个文件指针参数。

如果您键入一个空行,gets()函数将数组的第一个元素置为空字符,程序据此来终止循环。

下面是一个DOS环境下的运行示例:

阅读 ‧ 电子书库

正如您所看到的,fprintf()和fscanf()函数与printf()和scanf()函数的工作方式类似。与putc()函数不同,fprintf()和fscanf()函数将FILE指针作为第一个参数而不是最后一个参数。

13.4.2 fgets()和fputs()函数

第11章中出现过fgets()函数。fgets()函数接受3个参数,而gets()函数只接受1个参数。fgets()函数的第一个参数和gets()函数一样,是用于存储输入的地址(char *类型)。第二个参数为整数,表示输入字符串的最大长度。最后一个参数是文件指针,指向要读取的文件。下面是一个函数调用的例子:

阅读 ‧ 电子书库

这里,buf是一个char数组的名称,MAX是字符串的最大长度,fp是一个FILE指针。

fgets()函数读取到它所遇到的第一个换行字符的后面,或者读取比字符串的最大长度少一个的字符,或者读取到文件结尾。然后fgets()函数向末尾添加一个空字符以构成一个字符串。所以,字符串的最大长度代表字符的最大数目再加上一个空字符。如果fgets()函数在达到字符最大数目之前读完了一整行,它将在字符串的空字符之前添加一个换行符以标识一行结束。这是fgets()函数和gets()函数的不同之处,后者读取换行符后将其丢弃。

与gets()类似,fgets()遇到EOF的时候会返回NULL值,可以据此检查文件结尾。否则,它返回传递给它的地址值。

fputs()函数接受两个参数,它们依次是一个字符串的地址和一个文件指针。它把字符串地址指针所指的字符串写入指定的文件。与puts()函数不同,fputs()函数打印的时候并不添加一个换行符。下面是一个函数调用的例子:

阅读 ‧ 电子书库

这里buf是字符串地址,fp指定目标文件。

由于fgets()函数保留了换行符,而fputs()函数不会添加换行符,所以它们配合得相当好。程序清单13.4是利用这两个函数实现的一个回显程序。

程序清单13.4 parrot.c程序

阅读 ‧ 电子书库

如果您在一行起始处键入回车键,fgets()函数读入换行符然后把它放进数组line的第一个元素中。程序使用这一事实终止输入循环,遇到文件结尾也会终止该循环(清单13.3的判断条件中使用的是‘\0’而不是‘n’,因为gets()函数将丢掉换行符)。

下面是一个运行示例,注意到奇怪的事情了吗?

阅读 ‧ 电子书库

程序运行良好。这看起来似乎很让人吃惊,因为输入的第二行包含了 44个字符,而line数组中只能容纳20个字符(包括换行符在内)。到底是怎么回事?当fgets()函数读取第二行时,它只读入前19个字符,直到down中的w。它把这些字符复制到line数组中,由fputs()进行打印。由于fgets()还没有遇到行尾,line数组中不包含换行符,所以fputs()也就不会打印换行符。第三次调用时,fgets()函数在第二次调用停下的地方重新开始,因此它把接下来的19个字符(从down中w后面的n开始)读到数组line中。这一块内容代替了line数组中以前的内容,然后依次打印到与上次的输出相同的那行上,因为上一次的输出不包含换行符。一句话,fgets()函数每次读取第二行的19个字符,然后fputs()函数以同样长度的块将其打印。

如果一行恰好包含19个字符,程序也会终止。这种情况下,fgets()函数在读入19个字符以后停止读入,所以下一次调用时该函数将从行尾的换行符开始。这个换行符将成为读入的第一个字符,于是循环被终止。所以,尽管程序在本例中的输入情况下可以正常工作,但并非在所有情况下都可以正确执行。应该使用一个大得足以容纳整行的存储数组,或者更为简单地每次只读入一个字符。

您也许会感到奇怪,为什么程序没有在键入第二行的前19个字符后立刻将其打印出来?这是由于有屏幕缓冲区存在。直到遇到了换行符时,才把第二行发送到屏幕上。

13.4.3 注释:gets()函数和fgets()函数

因为fgets()函数可以防止存储溢出,所以对于严格的编程来说,它是一个更好的选择。由于它将换行符读入到字符串,而puts()函数会在输出中追加一个换行符,所以fgets()函数应该和fputs()而不是puts()一起使用。否则,输入时有一个换行符而在输出时却变成两个。

前边讨论过的这6个I/O函数已经足以让您进行文件读写了。到目前为止,我们还只是使用它们进行顺序访问,即按顺序处理文件内容。接下来,我们将学习随机存取,也就是说,以任何需要的顺序访问文件内容。