由于文件通常被划分成行,因此很有必要一次读入整行数据。
stdio.h
中执行这一操作的函数为fgets
,其函数原型为:
string fgets(char buffer[], int bufsize, FILE *infile);
此函数的作用在于将下一行文件读入字符数组buffer
中。
通常,fgets
在读入第一个换行字符后停止读取,但如果该行的长度超过了由函数参数bufsize
限定的长度,则fgets
函数将提前返回。
因此,buffer
数组中表示终止的空字符前应为一换行符,除非该行文件过长,超出buffer
的限制。
无论fgets
读入的是一整行还是此行的一部分,通常情况下都会返回一个指针,指向函数中的第一个参数(即字符数组buffer
)。
如果fgets
在文件的末尾调用,则返回NULL
。
相应的输出函数为fputs
,其函数原型为
void fputs(string str, FILE *outfile);
调用函数fputs
可将字符从一个字符串复制到输出文件,直至达到该字符串的末尾为止。
可以利用fgets
和fputs
来实现 上一节 中介绍的函数CopyFile
,该程序如下所示:
static void CopyFile(FILE *infile, FILE *outfile) { char buffer[MaxLine]; while (fgets(buffer, MaxLine, infile)!=NULL) { fputs(buffer, outfile); } }
当使用函数fgets
时,必须为输入行提供一个缓冲区。
在CopyFile
这个例子中,数组的空间是由声明
char buffer[MaxLine];
在当前帧中显式分配的。
需要注意的是,此缓冲区的内存空间将在函数返回时释放。
如果想要将行中的字符更长久地存储起来,需将它们存储在函数调用结束后仍然存在的内存空间中。
通常来说,从堆中动态分配内存是一种更简便的方法。
如果对多次fgets
调用使用同一个临时缓冲区,则需要为之分配新的内存空间。
仅有赋值是不够的。例如,下面的代码从一个输入文件中读入两行数据,并将它们存储在字符串变量line1
和line2
中:
void ReadTwoLines(FILE *infile) { char buffer[MaxLine]; string line1, line2; fgets(buffer, MaxLine, infile); line1 = buffer; fgets(buffer, MaxLine, infile); line2 = buffer; }
上述代码使得line1
和line2
的内容相同,出现这样的问题是因为,变量buffer
虽然在概念上是一个字符串,在内存中占有特定的空间,但赋值语句:
line1=buffer;
仅仅将buffer
的地址赋给了变量line1
。
当程序继续执行,要重新利用这块空间读入第二行文件时,同一块空间就用来存放第二个字符串了。
这样变量line1
和line2
最终指向了相同的内存,因此两者的值是相同的字符串。
常见错误:
如果使用同一个字符缓冲区从同一个文件中读入多行,要记住在读入下一行数据之前,需要将数据从缓冲区中复制到其他存储空间。
如果不进行这样的操作,前一行的内容将被覆盖。
解决这个问题的办法之一是利用strlib
库中的函数CopyString
将每一个字符串写入各自的堆内存中,如下所示:
void ReadTwoLines(FILE *infile) { char buffer[MaxLine]; string line1, line2; fgets(buffer, MaxLine, infile); line1 = CopyString(buffer); fgets(buffer, MaxLine, infile); line2 = CopyString(buffer); }
另外一个解决方案就是利用在simpio.h
中定义的函数ReadLine
,它可以避免与fgets
相关的其他问题:
fgets
的过程中给定一个最大值,则意味着数据不会被写到所分配的缓冲区之外,但同样需要知道fgets
是否读入了一个完整的行。使用fgets
时的唯一做法是浏览缓冲区内的所有字符,检测是否包括换行符。fgets
存储换行符通常也会引起麻烦。在大多数的程序中,换行符只是一行结束的标记,而并不真正是数据的一部分。使用fgets
意味着还需采取其他的步骤将缓冲区中的换行符删除。与fgets
相比,ReadLine
具有以下优点:
ReadLine
在需要时自动分配堆内存,使得缓冲区不可能溢出。ReadLine
删除了标记每一行结束的换行符,所返回的数据只包括这一行中的字符。ReadLine
返回的每一个字符串都保存在各自的内存中,因此在存储一个字符串之前不必考虑是否需要进行复制。函数ReadLine
在遇到文件结束符时返回NULL
。
《C语言的科学和艺术》 —— 15 文件