2.5 文件定位
读写文件过程中,有时会需要读某个特定位置的内容。例如,对于那种由固定大小的记录组成并能用整数索引来引用这些记录的文件,为访问其中某个特定的记录,最快捷的方法是直接定位至该记录位置进行读写,而不必一个一个地顺序跳过之前不需要的记录。为此,我们需要能够随意定位文件的位置,即随机地读写文件的任何部分。
标准I/O库提供了如下两组对随机文件进行定位的函数,用它们可以随机地读写文件的任何部分。
#include <stdio.h>
long int ftell(FILE *stream);
int fseek(FILE *stream, long int offset, int whence);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
ftell()调用成功返回stream指定流的当前文件位置,它是从文件开始的字节数;否则返回–1并置errno。
fseek()改变流stream的文件位置。具体位置由参数offset和whence指定。offset 给出相距由whence给出的起点的字节偏移,它可以是正数,也可以是负数。whence 指定起点,起点可以是文件开始、文件尾或者当前文件位置,具体为下述值之一:
SEEK_SET :文件位置定位于文件开始+offset之处,此时offset不能为负数。
SEEK_CUR:文件位置定位于文件当前位置+offset之处。
SEEK_END:文件位置定位于文件尾+offset之处。
fseek()调用成功返回0,失败返回非0。如果调用成功,它将清除流的文件结束指示器并忽略由ungetc()退回的字符。如果是输出流并且缓冲的数据还未写至相连的文件,fseek()将导致未写出的数据被写至文件。因此,对于以更新方式(“+”)打开的文件,调用fseek()之后,在此文件上的下一个操作既可以是输入,也可以是输出。
rewind()定位流stream于文件的开始,其作用等价于fseek(stream, 0L, SEEK_SET),不同的是它无返回值且重置了流的错误指示器。
fgetpos()和fsetpos()是ANSI C新补充的,它们比ftell()和fseek()的兼容性更好。这两个函数引入了一个新的抽象类型fops_t来表示文件位置。在POSIX兼容的系统中,这个类型通常就是长整型,但在其他系统中则可能有不同的内部表示。例如在有的系统中,文件位置必须同时用文件内的记录位置和记录内的字符偏移来表示。因此,如果想使程序可以移植至这种系统,最好使用函数fgetpos()和fsetpos()。
fgetpos()获取流stream的当前文件位置并将其存储在pos指定的对象中。
fsetpos()用pos给出的值设置流stream的文件位置。pos给定的值必须是前面对同一个流调用fgetpos()而得到的。如果成功,fsetpos()将清除文件结束指示器并忽略由ungetc()退回的任何字符。
例2-6 fseek()允许设置文件位置超过当前文件尾,如果之后在新文件位置写入了数据,则从原文件尾到新写入的数据之间读出的字节将用0填充,直至到达实际写入的数据为止。程序2-6说明用fseek()定位文件位置超过文件尾的两种情形。第一种是以非添加方式写数据超过文件尾,此时,在当前文件尾和新写入的数据之间将形成所谓的“空洞”。第二种是以添加方式写数据,此时,尽管可以用fseek()定位文件位置超过文件尾,但是在写文件时,文件的当前位置被忽略,所有数据都添加在文件尾并且文件当前位置被重新定位至新的文件尾。
首先保证你的运行目录中不存在名为test_file的文件,然后运行这个程序。如果你的回答是“y”,则会得到如下输出:
$ a.out
Should we use append mode? y
current file position is 21
Now we call fseek(fd,30,SEEK_END); current file position is 51
Now we write 7 bytes "abcdefg"; current file position is 28
此时用od命令可查看到文件test_file的内容如下:
$ od -c test_file
0000000 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000020 G H I J a b c d e f g
如果回答“n”,则输出结果和用od命令查看的文件内容是:
$ a.out
Should we use append mode? n
current file position is 21
Now we call fseek(fd,30,SEEK_END); current file position is 51
Now we write 7 bytes "abcdefg"; current file position is 58
$ od -c test_file
0000000 0 1 2 3 4 5 6 7 8 9 A B C D E F
0000020 G H I J \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0000040 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0000060 \0 \0 \0 a b c d e f g
我们看到以添加方式(a+)写入的数据没有理会fseek()定位的位置(此位置距文件开始为50字节),新写入的数据仍然紧接在当时的文件尾之后,即 “G”之后,此位置距文件开始20字节。而以非添加方式(w+)写入的数据“abcdefg” 则写在由fseek()定位的位置,即当时的文件尾之后30字节(第50字节,od命令输出中的第一列给出的是八进制数字),在它们之间有30个空字符。