C语言的文件随机访问fseek()和ftell()函数

我们要讨论3个问题:fseek()和ftell()函数的工作原理、如何使用二进制流、如何让程序可移植。
fseek()与ftell()的工作原理
头文件:
定义函数:
**函数说明:fseek()用来移动文件流的读写位置. **
1.参数stream 为已打开的文件指针。
2.参数offset 是偏移量,该参数表示要从起始点开始要移动的距离,干参数必须是一个long类型的值,可以为正(前移),可以为负(向后移),或者为0(保持不动)。
3.参数 whence 是模式,该参数确定起始点。根据ANSI标准,在stdio.h头文件中规定了几个表示模式的**明示标量(manifest constant)。**如下表所示:
表1 文件的起始点模式
| 模式 | 偏移量的起始点 |
|---|---|
| SEEK_SET | 文件开始处 |
| SEEK_CUT | 文件当前位置 |
| SEEK_END | 文件末尾 |
旧的实现可能缺少这些定义,可以用数值0L、1L、2L分别表示这3中模式。L表明其值是long类型。
下面是调用fseek()函数的一些示例,fp是一个文件指针:
1 2 3 4 5 | fseek(fp, 0L, SEEK_SET); //定位至文件开始处 fseek(fp, 10L, SEEK_SET); //定位至文件中的第10个字节 fseek(fp, 2L, SEEK_CUR); //从文件当前位置前移2个字节 fseek(fp, 0L, SEEK_END); //定位至文件结尾 fseek(fp, -10, SEEK_END); //从文件结尾处回退10个字节 |
对于这些调用还有一些限制,后面会讨论。如果一切正常,fseek函数的返回值为0;如果出现错误(例如试图移动的距离超出了文件的范围),其返回值是-1。
ftell() 函数用来获取文件读写指针的当前位置,其原型为:
【参数】stream 为已打开的文件指针。
【返回值】成功则返回当前的读写位置,失败返回 -1。
对于二进制文件,则返回从文件开头到结尾的字节数。
对于文本文件,返回的数值可能没有实际意义,但仍然可以用来保存当前的读写位置,供 fseek() 函数使用
在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。使用fseek函数后再调用函数ftell()就能非常容易地确定文件的当前位置。ftell() 经常和 fseek() 一起使用。
在最初的unix实现中,ftell()通过返回距文件开始处的字节数来确定文件的位置。文件的第1个字节到文件开始处的距离为0,以此类推。ANSI C规定,该定义适用于以二进制模式打开的文件,以文本文件打开的文件的情况不同。
下面来分析以下程序示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /* reverse.c -- displays a file in reverse order */ #include <stdio.h> #include <stdlib.h> #define CNTL_Z '\032' /* eof marker in DOS text files */ #define SLEN 81 int main(void) { char file[SLEN]; char ch; FILE *fp; long count, last; puts("Enter the name of the file to be processed:"); scanf("%80s", file); if ((fp = fopen(file,"rb")) == NULL) { /* read-only mode */ printf("reverse can't open %s\n", file); exit(EXIT_FAILURE); } fseek(fp, 0L, SEEK_END); /* go to end of file */ last = ftell(fp); for (count = 1L; count <= last; count++) { fseek(fp, -count, SEEK_END); /* go backward */ ch = getc(fp); if (ch != CNTL_Z && ch != '\r') /* MS-DOS files */ putchar(ch); } putchar('\n'); fclose(fp); return 0; } |
下面是该程序对一个文件的输出:
1 2 3 4 5 | Enter the name of the file to be processed: Cluv[用户输入] .C ni naht ylevol erom margorp a ees reven llahs I taht kniht I |
注意,运行程序前先在可执行程序的目录下准备一个叫Cluv的文件,里面存有以下内容,才可能得到上面的输出(笔者是在windows下Cygwin模拟的Linux环境下运行)。
1 2 | I think that I shall never see a program more lovely than in C. |
该程序使用二进制模式,以便处理MS-DOS文本和UNIX文本。但是,是在使用其他格式的文本文件的环境可能无法正常工作。
首先,分析下面的语句:
?
把当前位置设置为距文件末尾0字节偏移量。也就是说,该语句把当前位置设置在文件结尾。下一条语句:
把文件开始到文件结尾的字节数赋值给last。
然后是一个for循环:
1 2 3 4 5 6 7 | for (count = 1L; count <= last; count++) { fseek(fp, -count, SEEK_END); /* go backward */ ch = getc(fp); if (ch != CNTL_Z && ch != '\r') /* MS-DOS files */ putchar(ch); } |
第一轮迭代,把程序定位到文件结尾的第一个字符(即文件的最后一个字符)。然后,长须打印该字符。然后,程序打印该字符。下一轮迭代把程序定位到前一个字符,并打印该字符。重复这一过程直到文件的第一个字符,并打印。
二进制模式和文本模式
上面的示例程序在UNIX和MS-DOS环境下都可以运行。UNIX只有一种文件格式,所以不需要进行特殊的转换。但是MS-DOS要格外注意,许多MS-DOS编辑器都用Ctrl+Z标记文本文件的结尾。以文本模式打开这样的文件时,C能识别这个作为文件结尾标记的字符。但是,以二进制模式打开文件时,Ctrl+Z字符被看做文件中的一个字符,而实际的文件结尾符在该字符的后面。文件结尾符可能紧跟Ctrl+Z字符后面,或者文件中可能用空字符填充,使该文件的大小是256的倍数。在DOS环境下不会打印空字符,上面程序就包含了贩子打印Ctrl+Z字符的代码。
二进制模式和文本模式的另一个不同之处是:MS-DOS用\r\n组合表示文本文件换行。以文本模式打开相同的文件时,C程序把
ftell函数在文本模式和二进制模式中的工作方式不同。许多系统的文本文件格式与UNIX的模型有很大的不同,导致从文件开始处统计的字节数称为一个毫无意义的值。ANSI C规定,对与文本模式,ftell()返回值可以作为fseek()的第2个参数。对于MS-DOS,ftell()返回值把
可移植性
理论上,fseek()和ftell()应该符合UNIX模型,但是,不同系统存在着差异,有时确实无法做到与UNIX模型一致。因此,ANSI 对这些函数降低了要求。下面是一些限制
- 在二进制中,实现不必支持SEEK_END模式。因此无法保证上面的程序的可移植性。移植性更高的方法是逐字节读取整个文件直到文件末尾。C预处理器的条件编译指令提供了一种系统方法处理这种情况。
- 在文本模式中,只有以下调用能保证其相应的行为。
| 函数调用 | 效果 |
|---|---|
| 定位至文件开始处 | |
| fseek(file, 0L, SEEK_CUR) | 保持当前位置不动 |
| fseek(file, 0L, SEEK_END) | 定位至文件结尾 |
| fseek(file,ftell-pos, SEEK_SET) | 到距文件开始处ftell-pos的位置,ftell-pos是ftell()的返回值 |
参考资料:
[2] 史蒂芬?普拉达. C Primer Plus (第6版) 中文版[M]. 人民邮电出版社, 2016.
[2] C语言fseek()函数:移动文件流的读写位置。-来源
[3] C语言ftell()函数:获取文件读写指针的当前位置。-来源