13.5 随机存取:fseek()和ftell()函数
Fseek()允许您像对待数组那样对待一个文件,在fopen()打开的文件中直接移动到任意字节处。
我们创建程序清单13.5中的程序,该程序按反序显示一个文件。像前面的例子一样,它使用一个命令行参数来获得要读取的文件的名字。请注意fseek()接受3个参数,返回一个int值 。ftell()函数以一个long类型值返回一个文档的当前位置。
程序清单13.5 reverse.c程序
/*reverse.c—反序显示一个文件*/#include#include #define CNTL_Z '\032' /*DOS文本文件中的文件结尾标记*/#define SLEN 50int main(void){ char file[SLEN]; char ch; FILE * fp; long count,last; puts("Enter the name of the file to be processed: "); gets(file); if((fp=fopen(file,"rb"))==NULL) /*只读和二进制模式*/ { printf("reverse can not open %s\n",file); exit(1); } fseek(fp,0L,SEEK_END); /*定位到文件结尾处*/ last=ftell(fp); for(count=1L;count<=last;count++) { fseek(fp,-count,SEEK_END); /*回退*/ ch=getc(fp); /*针对 DOS,在UNIX下也可以工作*/ if(ch!=CNTL_Z && ch!= '\r') putchar(ch); /*针对 MACINTOSH */ /*if(ch=='\r') putchar ('\n'); else putchar(ch); */ } putchar('\n'); fclose(fp); return 0;}
我们需要讨论3个问题:fseek()和ftell()如何工作,如何使用二进制流,如何使程序可移植。
13.5.1 fseek()和ftell()如何工作
在fseek()的三个参数中,第一个参数是指向被搜索文件的FILE指针,应该已经使用fopen()打开了该文件。
fseek()的第二个参数称为偏移量(offset),表示从起点开始要移动的距离(请参见表13.3中的起点模式),这个参数必须是一个long类型的值,可以为正(前移)、负(后移),也可以为零。
第三个参数是模式,用来标识起识点。在ANSI下,stdio.h头文件指定了下列模式常量:
模式 | 偏移量的起始点 |
SEEK_SET | 文件开始 |
SEEK_CUR | 当前位置 |
SEEK_END | 文件结尾 |
下面是函数骼的一些例子:
fseek(fp,0L,SEEK_SET); //找到文件的开始处
fseek(fp,10L,SEEK_SET); //找到文件的第10个字节
fseek(fp,2L,SEEK_CUR); //从文件的当前位置向前移动两个字节
fseek(fp,0L,SEEK_END); //文件结尾
如果一切正常,fseek()的返回值为0,如果出现错误,例如试图移动超出文件范围,则fseek()的返回值为-1.
ftell()函数为long类型,它返回文件的当前位置。在ANSI下,ftell()函数在stdio.h头文件中被声明。ftell()函数通过返回距文件开始处的字节数目来确定文件的位置。文件的第一个字节到文件开始处的字节数为0,依此类推。在ANSI C 下这种定义适用于以二进制模式打开的文件,但是对于以文本模式打开的文件来讲,不一定是这样。这也是程序清单13.5使用二进制模式的原因之一。现在我们来看程序清单13.5的基本元素。
首先,以下语句:
fseek(fp,0L,SEEK_END);
把当前位置设置为从文件结尾处偏移0字节处,也就是将位置设定在文件结尾。接下来,
last=ftell(fp);
把从文件开始到文件结尾处的字节数目赋给last。接下来是一个循环:
for(count=1L;count<=last;count++)
{
fseek(fp,-count,SEEK_END);
ch=getc(fp);
}
第一次循环将程序定位到文件结尾前的第一个字符,也即文件的最后一个字符。然后打印这个字符。下一次循环,将程序定位到前一个字符并打印之。这种操作会一直持续到到达第一个字符并打印之。
13.5.2 二进制模式和文本模式
我们把程序清单13.5的程序设计为在UNIX和MS_DOS环境下都可以运行。UNIX只有一种文件格式,所以不需要特殊的调整。可是MS_DOS即需要额外的关注。很多MS_DOS编辑器使用字符 ctrl+z标识文本文件的结尾。如果以文本模式打开这样的文件,C可以认出这个字符是标识文件结尾的字符。可是以二进制模式打开这样的文件,只会把ctrl+z当作文件中的一个字符。真正的文件结尾还在后面,也许紧跟着ctrl+z也许用空字符填补文件以使它的大小为256的倍数。在DOS模式下不打印空字符。程序中包含了防止程序打印ctrl+z字符的代码。
另一个区别 ,DOS使用\r\n组合来表示文本文件的换行符。以文本模式打开的c程序将\r\n看作\n。但是使用二进制模式打开相同的文件,程序将看到这两个字符。所以需要在程序中包含防止打印\r的代码。
因数UNIX文本文件通常不包含ctrl+z和\r,所以这段额外的代码不会影响大多数的UNIX文本文件。
函数ftell()在文本模式和二进制模式下的工作方式有所不同。很多系统的文本文件格式都与UNIX模型有很大不同,以致从文件开始的字节计数值是无意义的数量。ANSI C规定,对于文本模式,ftell()返回一个可以用作fseek()第二个参数的值。例如,对于DOS,ftell()返回一个将\r\n看成一个字节的计数值 。
13.5.3 可移植性
理论上,fseek()和ftell()应该符合UNIX模型。可是由于实际系统之间的差异,有时这是不可能的。所以,ANSI降低了对这些函数的要求。下面是一些局限性:
*对于二进制模式,C实现不需要支持SEEK_END模式。这样就无法保证程序清单13.5是可移植的。然而,程序清单并没有给出定位文件结尾的可替代方法。因为可替代方法是顺序地读取整个文件以找到文件末尾,这比简单地跳到文件末尾要慢得多。第16章中将要讨论的C预处理器条件编译指令,为处理可替代的代码提供了一种更为系统的方法。
*在文本模式中,可以确保有效的fseek()调用只有以下这些:
fseek(file,0L,SEEK_SET) | 到文件开始 |
fseek(file,0L,SEEK_CUR) | 在当前位置不动 |
fseek(file,0L,SEEK_END) | 到文件结尾 |
fseek(file,ftell-pos,SEEK_SET) | ftell-pos是ftell()的返回值 |
幸运的是,很多常见环境都允许这些函数更为健壮的实现。
13.5.4 fgetpos()和fsetpos()函数
fseek()和ftell()的一个潜在问题是它们限制文件的大小只能在long类型的表示范围之内。ANSI C 引入了两个用来处理较大文件的新的定位函数。这两个函数不是采用long类型值 ,而是采用一种称为fpos_t(代表file position type,文件定位类型)的新类型来代表位置。fpos_t 不是一种基本类型,而是通过其他类型定义的。一个fpos_t类型的变量或者数据对象可以用来指定文件中的一个位置。它不能是一种数组类型,但除此之外不再有其他限制 。因此,C实现可以提供一种满足特殊平台需要的类型;例如,这种类型可以作为结构来实现 。
ANSI C 定义了使用fpos_t的方法。fgetpos()函数具有下面的原型:
int fgetpos(FILE * restrict stream,fpos_t * restrict pos);
被调用时,该函数在pos所指的位置放置一个fpos_t值;这个值描述了文件的一个位置。如果成功函数返回0,否则返回一个非0值。
fsetpos()函数具有下面的原型:
int fsetpos(FILE * stream,const fpos_t *pos);
被调用时,该函数使用pos指向的位置上的那个fpos_t值设定文件指针指向该值所指示的位置。如果成功,函数返回0,否则返回一个非0值 。这个fpos_t值应该是通过调用fgetpos()来获取的。