指向标准文件的指针
stdio.h头文件把3个文件指针与3个标准文件相关联,C程序会自动打开这3个标准文件。如表1所示:
标准文件 | 文件指针 | 通常使用的设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 显示器 |
标准错误 | stderr | 显示器 |
这些文件指针都是指向FILE的指针,所以它们可用作标准I/O函数的参数。
文件I/O:fprintf(),fscanf(),fgets()和fputs()
fprintf()和fscanf()函数
文件I/O函数fprintf()和fscanf ()函数的工作方式与print()和scant()类似,区别在于前者需要用第1个参数指定待处理的文件。我们在前面用过fprintt()。以下程序演示了这两个文I/O函数和rewind()函数的用法。
#include<stdio.h>
#include<stdlib.h>
int main( )
{
int ch;//临时存储读出的字符
FILE* fp = NULL;
char word[40];
//if ((fp = fopen("word.txt", "a+")) == NULL)/C99及以前
if (fopen_s(&fp, "word.txt", "a+") != 0)//C11
{
fprintf(stderr,"Can't open word.txt\n");
exit(EXIT_FAILURE);
}
puts("Enter words to add to the file ; press the #\n");
while ((fscanf(stdin,"%39s",word) == 1) && (word[0] != '#'))
{
fprintf(fp, "%s\n", word);
}
puts("File contents : \n");
rewind(fp);//返回文件开始处
while (fscanf(fp, "%s", word) == 1)
{
puts(word);
}
if(fclose(fp)!= 0)
{
fprintf(stderr,"Error Closing File\n");
}
return 0;
}
该程序可以在文件中添加单词。使用"a+"模式可以对文作进行读写操作,首次使用这程序将创建word.txt文件,以便把单词存入其中,随后再使用该程序,可以在word。txt文件后添加单词。"a+"模式只允许在文件末尾添加内容,但是该模式下可以读整个文件。rewind() 函数让程序回到文件起始处,方便while循环打印整个文件的内容。注意,rewind()接受 一个 文件指针作为参数。
如你所见,fprintf() 和fscanf()的工作方式与printf()和scanf()类似。但是,与putc()不同的是,fprintf() 和fscanf()函数都把FILE指针作为第1个参数,而不是最后一个参数。
fegts()和fputs()函数
fgets()函数它的第1个参数和gets()函数一样, 也是表示储存输入位置的地址(char*类型);第2个参数是一个整数,表示待输入字符串的大小:最后一个参数是文件指针,指定待读取的文件。下面是一个调用该函数的例子:
fgets(buf, STLEN, fp);
这里,buf是char类型数组的名称,STLEN 是字符串的大小,fp 是指向FILE的指针。
fgets()函数读取输入直到第一个换行符的后面,或读到文件结尾,或者读取STLEN-1个字符(以上面的fgets()为例)。然后,fgets()在末尾添加个空字符使之成为 一个字符串。字符串的大小是其字符数加上个空字符。 如果fgets()在读到字符上限之前已读完一整行,它会把表示行结尾的换行符放在空字符前面。fgets() 函数在遇到EOF时将返回NULL值,可以利用这机制检查是否到达文件结尾: 如果未遇到EOF则之前返回传给它的地址。
fputs()函数接受两个参数:第1个是字符串的地址;第2个是文件指针。该函数根据传入地址找到的字符串写入指定的文件中。和puts ()函数不同,fputs ()在打印字符事时不会在其来尾添加换行符。下面是一个调用该函数的例子:
fputs(buf, fp);
这里,buf 是字符串的地址,fp 用于指定目标文件。
由于fgets()保留了换行符,fputs()就不会再添加换行符,它们配合得非常好。
随机访问:fseek()和ftell()
有了fseek()函数, 便可把文件看作是数组,在fopen()打开的文件中直接移动到任意字节处。注意,fseek()有3个参数,返回int类型的值: ftell() 函数返回一个long类型的值,表示文件中的当前位置。
fseek()和ftell()的工作原理
fseek()的第1个参数是FIlE指针,指向待查找的文件,fopen()应该已打开该文件。
fseek()的第2个参数是偏移量*(offset)*, 该参数表示从起始点开始要移动的距离。该参数必须是一 个long类型的值,可以为正(前移)、负(后移)或0(保持不动)。
fseek()的第3个参数是模式,该参数确定起始点。根据ANSI标准,在stdio.h头文件中规定了几个表示模式的明示常量,如下所示。、
模式 | 偏移量的起始点 |
---|---|
SEEK_SET | 文件开始处 |
SEEK_CUR | 当前位置 |
SEEK_END | 文件末尾 |
旧的实现可能缺少这些定义,可以使用数值0L、1L、2L分别表示这3种模式。L后缀表明其值是long类型。或者,实现可能把这些明示常量定义在别的头文件中。如果不确定,请查阅实现的使用手册或在线帮助。下面是调用feek()函数的一些示例:
fseek(fp,0L,SEEK_SET);//定位至文件起始处
fseek(fp,10L,SEEK_SET);//定位置文件中的第10个字节
fseek(fp,2L,SEEK_CUR);//定位至文件当前位置的前两个字节处
fseek(fp,0L,SEEK_END);//定位至文件末尾
fseek(fp,-10L,SEEK_END);//定位置末尾前10个字节处
如果一切正常,fseek() 的返回值为0;如果出现错误(如试图移动的距离超出文件的范围),其返回值为-1。
ftell()函数的返回类型是long,它返回的是当前的位置。ANSI C把它定义在stdio.h中。在最初实现的UNIX中,ftell()通过返回距文件开始处的字节数来确定文件的位置。文件的第1个字节到文件开始处的距离是0,以此类推。ANSI C 规定,该定义适用于以二进制模式打开的文件,以文件模式打开文件的情况不同。
二进制模式和文本模式
二进制模式和文本模式的一个不同之处是: MS-DOS用\r\n组合表示文本文件换行。 以文本模式打开相同的文件时,C程序把\r\n“看成”\n.但是,以二进制模式打开该文件时,程序能看见这两个字符。
ftell()函数在文本模式和二进制模式中的工作方式不同。许多系统的文本文件格式与UNIX的模型有很大不同,导致从文件开始处统计的字节数成为一个毫无意义的值,ANSI C规定,对于文本模式,ftell()返回的值可以作为tsek)的第2个参数。对于MS-DOS, ftell()返回的值把\r\n当作一个字节计数。
fgetpos()和fsetpos()函数
fseek()和ftell()潜在的问题是,它们都把文件大小限制在long类型能表示的范围内,也许20亿字节看起来相当大,但是随着存储设备的容量迅速增长,文件也越来越大。 鉴于此,ANSI C 新增了两个处理较大文件的新定位函数: fgetpos() 和fsetpos()。这两个函数不使用long类型的值表示位置,它们使用一种新类型: fpos_t (代表file position type, 文件定位类型)。fpos_t类型不是基本类型,它根据其他类型来定义。fpos_t类型的变量或数据对象可以在文件中指定个位置, 它不能是数组类型,除此之外,没有其他限制。实现可以提供一个满足特殊平台要求的类型, 例如,fpos_t可以实现为结构。
ANSI C定义了如何使用fpos_t类型。fgetpos() 函数的原型如下:
int fgetpos(FILE *restrict stream, fpos_t * restrict pos) ;
调用该函数时,它把fpos_t类型的值放在pos指向的位置上,该值描述了文件中的一个位置。如果成功,fgetpos()函数返回0;如果失败,返回非0.
fsetpos()函数的原型如下:
int fsetpos(FILE *stream, const fpos t *pos) ;
调用该函数时,使用pos指向位置上的fpos_ t类型值来设置文件指针指向该值指定的位置。如果成功,tsetpos()函数返回0;如果失败,则返回非0. fpos_t类型的值应通过之前调用fgetpos ()获得。
二进制I/O:fread()和fwrite()
介绍fread()和fwrite()函数之前,先要了解一些背景知识。 之前用到的标准I/O函数都是面向文本的,用于处理字符和字符串。如何要在文件中保存数值数据?用fprintf() 函数和%f转换说明只是把数值保存为字符串。例如,下面的代码:
double num = 1./3. ;
fprintf(fp, "8f", num) ;
把num储存为8个字符: 0.333333. 使用%.2f 转换说明将其储存为4个字符: 0.33,用%.12f转换说明则将其储存为14 个字符: 0.3333333333. 改变转换说明将改变储存该值所需的空间数量,也会导致储存不同的值。把num储存为0.33后,读取文件时就无法将其恢复为更高的精度。一般而言,fprintf ()把数值转换为字符数据,这种转换可能会改变值。
为保证数值在储存前后致, 最精确的做法是使用与计算机相同的位组合来储存。 因此,double类型的值应该储存在一一个double大小的单元中。如果以程序所用的表示法把数据储存在文件中,则称以二进制形式储存数据。不存在从数值形式到字符串的转换过程。对于标准I/O, fread()和 fwrite函数用于以二进制形式处理数据。
实际上,所有的数据都是以二进制形式储存的,甚至连字符都以字符码的二进制表示来储存。 如果文件中的所有数据都被解释成字符码,则称该文件包含文本数据。如果部分或所有的数据都被解释成二进制形式的数值数据,则称该文件包含二进制数据(另外,用数据表示机器语言指令的文件都是二进制文件)。
二进制和文本的用法很容易混淆。ANSI C和许多操作系统都识别两种文件格式:二进制和文本。能以二进制数据或文本数据形式存储或读取信息。可以用二进制模式打开文本格式的文件,可以把文本储存在二进制形式的文件中。 可以调用getc()拷贝包含二进制数据的文件。然而,一般而言,用二进制模式在二进制格式文件中储存二进制数据。 类似地, 最常用的还是以文本格式打开文本文件中的文本数据(通常
文字处理器生成的文件都是一进制文件, 因为这些文件中包含了大量非文本信息,如字体和格式等)。
size_t fwrite()
fwrite()函数的原型如下:
size_t fwrite(const void* restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);
fwrite()函数把进制数据写入文件。 size_ t是根据标准 C类型定义的类型,它是sizeof 运算符返回的类型,通常是unsigned int,但是实现可以选择使用其他类型。指针ptr是待写入数据块的地址。size表示待写入数据块的大小(以字节为单位),nmemb表示待写入数据块的数量。和其他函数一样,fp指定待写入的文件。例如,要保存一个大小为256字节的数据对象(如数组),可以这样做:
char buffer[256];
fwrite(buffer, 256, 1, fp);
注意fwrite()原型中的const void * restrict ptr声明。fwrite()的 个问题是, 它的第1个参数不是固定的类型。在ANSI C函数原型中,这些实际参数都被转换成指向void 的指针类型,这种指针可作为种通用类型指针 (在 ANSIC之前,这些参数使用char 类型,需要把实参强制转换成char类型)。fwrite()函数返回成功写入项的数量。正常情况下,该返回值就是nmemb, 但如果出现写入错误,返回值会比nmemb小。
size_t fread()函数
size_ t fread()函数的原型如下:
size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp):
fread()函数接受的参数和fwrite()函数相同。在fread()函数中,ptr是待读取文件数据在内存中的地址,fp 指定待读取的文件。该函数用于读取被fwrite()写入文件的数据。
int feof(FILE *fp)和int ferror(FILE *fp)函数
如果标准输入函数返回EOF, 则通常表明函数已到达文件结尾。然而,出现读取错误时,函数也会返回EOF。feof() 和ferror()函数用于区分这两种情况。当上一次输入调用检测到文件结尾时,feof()函数返回一个非零值,否则返回0。当读或写出现错误,ferror ()函数返回一个非零值,否则返回0,