阅读 ‧ 电子书库 说  明
如果在命令行环境下运行这个程序,程序要求文件名参数代表的文件和该可执行程序在同一个目录(或者文件夹)中。如果在IDE中运行程序,情况随具体实现不同而有差异。比如Microsoft Visual C++在包含源代码的目录中查找文件,而Metrowerks CodeWarrior在包含可执行文件的目录中查找。

我们需要讨论3个问题:fseek()和ftell()如何工作,如何使用二进制流,如何使程序可移植。

13.5.1 fseek()和ftell()如何工作

在fseek()的3个参数中,第一个参数是一个指向被搜索文件的FILE指针。应该已经使用fopen()打开了该文件。

fseek()的第二个参数称为偏移量(offset),表示从起始点开始要移动的距离(请参见表13.3中的起始点模式)。这个参数必须是一个long类型的值,可以为正(前移)、负(后移),也可以为零(保持不动)。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

第三个参数是模式,用来标识起始点。在ANSI下,stdio.h头文件指定了下列模式常量:

表13.3 文件的起始点模式

 

 

模 式 偏移量的起始点
SEEK_SET 文件开始
SEEK_CUR 当前位置
SEEK_END 文件结尾

以前的实现中可能没有这些定义,而是用数字值OL、1L和2L分别代表这些模式。回忆一下,L后缀标识long类型值。一些实现也可能在其他头文件中定义模式常量。如果对此不确定,可以参考您的用户手册或在线手册。

下面是函数调用的一些例子,其中fp是一个文件指针:

阅读 ‧ 电子书库

对于这些调用,还有一些可能的限制。我们稍候将回来讨论这个话题。

如果一切正常,fseek()的返回值为0。如果有错误出现,例如试图移动超出文件范围,则fseek()的返回值为-1。

ftell()函数为long类型,它返回文件的当前位置。在ANSI下,ftell()函数在stdio.h头文件中被声明。像最初在UNIX中实现的那样,ftell()函数通过返回距文件开始处的字节数目来确定文件的位置。文件的第一个字节到文件开始处的距离是字节0,依此类推。在ANSI C下,这种定义适用于以二进制模式打开的文件,但是对于以文本模式打开的文件来讲,不一定是这样。这也是程序清单13.5使用二进制模式的原因之一。现在我们开始研究程序清单13.5的基本元素。

首先,以下语句:

阅读 ‧ 电子书库

把当前位置设置为从文件结尾处偏移0字节处,也就是将位置设定在文件结尾。接下来的语句:

阅读 ‧ 电子书库

把从文件开始到文件结尾的字节数目赋给last。接下来是一个循环:

阅读 ‧ 电子书库

第一次循环将程序定位到文件结尾前的第一个字符,也即文件的最后一个字符。然后打印这个字符。下一次循环将程序定位到前一个字符并打印之。这种操作会一直持续到到达第一个字符并打印之。

13.5.2 二进制模式和文本模式

我们把程序清单13.5的程序设计为在UNIX和MS-DOS环境下都可以运行。UNIX只有一种文件格式,所以不需要特殊的调整。可是MS-DOS却需要额外的关注。很多MS-DOS编辑器使用字符Ctrl+Z标识文本文件的结尾。如果以文本模式打开这样的文件,C可以认出这个字符是标识文件结尾的字符。可是如果以二进制模式打开这样的文件,只会把Ctrl+Z当作文件中的一个字符。真正的文件结尾还在后面,也许紧跟着Ctrl+Z,也许用空字符填补文件以使它的大小为256(或其他数)的倍数。在DOS下不打印空字符。程序中包含了防止程序打印Ctrl+Z字符的代码。

另一个区别我们在前面也曾经提到过:MS-DOS用\r\n组合表示文本文件中的换行符。以文本模式打开文件的C程序将\r\n看作\n。但是如果使用二进制模式打开相同的文件,程序将看到这两个字符。所以需要在程序中包含防止打印\r的代码(对于Macintosh文本文件需要不同的代码,这是因为Macintosh使用\r作为行结尾的标识。程序清单13.5把有关Macintosh的代码作为注释内容显示)。

因为UNIX文本文件通常不包含Ctrl+Z和\r,所以这段额外的代码不会影响绝大多数的UNIX文本文件。

函数ftell()在文本模式和二进制模式下的工作方式有所不同。很多系统的文本文件格式都与UNIX模型有很大不同,以致从文件开始的字节计数值是无意义的数量。ANSI C规定,对于文本模式,ftell()返回一个可以用作fseek()的第二个参数的值。例如,对于MS-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-pos是ftell()的返回值

幸运的是,很多常见环境都允许这些函数的更为健壮的实现。

13.5.4 fgetpos()和fsetpos()函数

fseek()和ftell()的一个潜在的问题是它们限制文件的大小只能在long类型的表示范围之内。可能20亿字节看起来是一个很大的数字,但是日益增长的存储设备容量使得更大的文件也成为可能。ANSI C引入了两个用来处理较大文件的新的定位函数。这两个函数不是采用long类型值,而是使用一种称为fpos_t(代表file position type,文件定位类型)的新类型来代表位置。fpos_t不是一种基本类型,而是通过其他类型定义的。一个fpos_t类型的变量或者数据对象可以用来指定文件中的一个位置,它不能是一种数组类型,但除此之外不再有其他限制。因此C实现可以提供一种满足特殊平台需要的类型;例如,这种类型可以作为结构来实现。

ANSI C定义了使用fpos_t的方法。fgetpos()函数具有下面的原型:

阅读 ‧ 电子书库

被调用时,该函数在pos所指的位置放置一个fpos_t值;这个值描述了文件中的一个位置。如果成功,函数返回0;否则返回一个非零值。

fsetpos()函数具有下面的原型:

阅读 ‧ 电子书库

被调用时,该函数使用pos指向的位置上的那个fpos_t值设定文件指针指向该值所指示的位置。如果成功,函数返回0;否则返回一个非零值。这个fpos_t值应是通过调用fgetpos()函数获取的。