了解流的定位,需要先了解流的读写位置偏移的情况。通常每个打开的流内部都有一个当前读写位置,流被打开时,当前读写位置为 0
,表示在文件的开始位置进行读写。每当读写一次数据后,当前读写位置自动增加实际读写的大小。在读写流之前可先对流进行定位,即移动到指定的位置再操作。
测试示例如下:
#include <stdio.h> #define errlog(errmsg) \ perror(errmsg); \ printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \ return -1; int main(int argc, const char *argv[]) { FILE *fp; int ch; if ((fp = fopen("test.txt", "w+")) == NULL) { errlog("fopen error"); } fputc('a', fp); fputc('b', fp); fputc('c', fp); fputc('d', fp); while ((ch = fgetc(fp)) != EOF) { printf("ch = %c\n", ch); } fclose(fp); return 0; }点击复制复制失败已复制
编译并运行,可以看到同级的 test.txt
文件中的内容是 abcd
。
$ gcc main.c && ./a.out点击复制复制失败已复制
结果没有读到任何内容,但是文件中的数据存在。这说明读写位置发生了偏移。设想,当向文件中写入字符后,当前的读写位置已经不处于文件的开始处了。而是在写入的字符的末尾,因此当从这一位置开始读时,将无法读取任何内容。如同在 Windows
系统中写文本文件时,每次通过键盘输入之后,光标都会偏移到文字的下一位。这样,下一次写入从该位置开始写入。相反如果光标不发生移动,每次写都在一个位置,那么每次写入的数据势必会把上一次写入的数据覆盖。而如果从数据的末尾位置读取,光标前的数据将不会被读取。因此在对文件进行操作时,读写位置将十分关键。
fseek()和ftell()函数
fseek()
函数和 ftell()
函数被用来实现读写位置的定位及位置的查询。
#include <stdio.h> int fseek(FILE *stream, long offset, int whence); int ftell(FILE *stream);点击复制复制失败已复制
fseek()
函数,参数 stream
为指定的流, whence
为需要定位的位置,可设置为 SEEK_SET
、 SEEK_CUR
、 SEEK_END
,分别表示定位到文件的开始处、当前位置以及文件的末尾。 offset
表示在第三个参数已经定位的基础上再发生偏移的量,其值为长整型。
ftell()
函数则用来获取读写位置,执行成功返回当前读写位置相对于文件开始处的偏移量。使用这两个函数对上例进行修改,如下所示:
#include <stdio.h> #define errlog(errmsg) \ perror(errmsg); \ printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \ return -1; int main(int argc, const char *argv[]) { FILE *fp; int ch; long offset; if ((fp = fopen("test.txt", "w+")) == NULL) { errlog("fopen error"); } fputc('a', fp); fputc('b', fp); fputc('c', fp); fputc('d', fp); fseek(fp, 0, SEEK_SET); while ((ch = fgetc(fp)) != EOF) { printf("ch = %c\n", ch); } offset = ftell(fp); printf("offset = %ld\n", offset); fclose(fp); return 0; }点击复制复制失败已复制
上述代码中,使用 fseek()
函数进行重新定位,将读写位置定位到文件的开始处。此时执行循环读取则可以从文件开始处读取相应的内容。 ftell()
函数获取读取之后的读写位置的偏移量。编译并运行如下所示:
$ gcc main.c && ./a.out ch = a ch = b ch = c ch = d offset = 4点击复制复制失败已复制
注意
如果打开文件的方式为 "a+"
,则会出现不同的情况。
修改上述示例,使用 a+
打开文件,内容如下:
#include <stdio.h> #define errlog(errmsg) \ perror(errmsg); \ printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \ return -1; int main(int argc, const char *argv[]) { FILE *fp; int ch; long offset; if ((fp = fopen("test.txt", "a+")) == NULL) { errlog("fopen error"); } fputc('a', fp); fputc('b', fp); fputc('c', fp); fputc('d', fp); fseek(fp, 0, SEEK_SET); fputc('e', fp); offset = ftell(fp); printf("offset = %ld\n", offset); fclose(fp); return 0; }点击复制复制失败已复制
实践下来得到的结果是:
$ gcc main.c && ./a.out offset = 5点击复制复制失败已复制
但是书中说得是:
$ gcc main.c && ./a.out offset = 1点击复制复制失败已复制
与书中给出的不一致。书中的观点:在执行定位操作后,写入字符 'e'
,并没有从文件开始处写入将字符 'a'
覆盖,而是追加到文件的末尾,但是使用 ftell()
函数查询读写位置偏移量时,偏移量为 1
,并非是 5
。因此,这样的结果总会让人产生定位操作在文件采用 a
或 a+
方式打开时失效的感觉。这种情况要格外注意。使用追加的方式打开文件后,写操作默认是从文件末尾处开始的。
示例:获取文件的大小
#include <stdio.h> #define errlog(errmsg) \ perror(errmsg); \ printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \ return -1; int main(int argc, const char *argv[]) { FILE *fp; if ((fp = fopen(argv[1], "r")) == NULL) { errlog("fopen error"); } fseek(fp, 0, SEEK_END); printf("The size of %s is %ld\n", argv[1], ftell(fp)); fclose(fp); return 0; }点击复制复制失败已复制
编译运行:
$ gcc main.c && ./a.out test.txt The size of test.txt is 5点击复制复制失败已复制
通过上述示例,可以看到关于读写位置定位的简单应用。通过这些定位函数也可以用于实现文件的分隔及合并。例如, Linux
内核启动引导系统 Bootloader
的镜像合并就可以采用这些函数接口通过定位来实现。