文件的读写顺序
文件的格式化读写
- 对文件进行格式化输入输出,这时就要用fprintf函数和fscanf函数,它们的作用与printf函数和scanf函数相仿,都是格式化读写函数。只有一点不同:fprintf和fscanf函数的读写读写不是终端而是外部文件。
- 它们的一般调用方式为:
fprintf(文件指针,格式字符串,输入表列);
fscanf(文件指针,格式字符串,输入表列);
用二进制方式读写文件
- 用fprintf和fscanf函数对磁盘文件读写,使用方便,但由于在输入时要将ASCII码转换为二进制形式,在输出时又要将二进制形式转换为字符,花费时间比较多。因此,在内存与磁盘频繁交换数据的情况下,最好不用fprinf和fscanf函数,而用fread和fwrite。
- C标准允许用fread函数从文件读一个数据块,用fwrite函数向文件写一个数据块。在进行读写时是以二进制形式进行的。在向磁盘写数据时,直接将内存中一组数据原封不动、不加转换地复制到磁盘文件上,在读入时也是将磁盘文件中若干字节的内容一批读入内存。
- 它们的一般调用形式为:
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
其中:
- buffer:是一个地址。对fread来说,它是读入数据的存放地址。对fwrite来说,是要输出数据的地址(以上指的是起始地址)。
- size:要读写的字节数。
- count:要进行读写多少个size字节的数据项。
- fp:文件型指针。
例:从键盘输入10个学生的有关数据,然后把它们转存到磁盘文件上
#include<stdio.h>
#define SIZE 10
struct Student_type
{
char name[10];
int num;
int age;
char addr[15];
}stud[SIZE]; //定义全局结构体数组stud,包含4个学生数据
void save() //定义函数save,向文件输出SIZE个学生的数据
{
FILE* fp;
int i;
if ((fp = fopen("stu.dat", "wb")) == NULL)
{
printf("cannot open file\n");
return;
}
for (i = 0; i < SIZE; i++)
if (fwrite(&stud[i], sizeof(struct Student_type), 1, fp) != 1)
printf("file write error\n");
fclose(fp);
}
int main()
{
int i;
printf("Please enter data of students: \n");
for (i = 0; i < SIZE; i++) //输入SIZE个学生的数据,存放在数组stud中
scanf("%s%d%d%s", stud[i].name, &stud[i].num, &stud[i].age, stud[i].addr);
save();
return 0;
}
文件的随机读写
- 对文件进行顺序读写比较容易理解,也容易操作,但有时效率不高;随机访问不是按数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数据进行访问,显然这种方法比顺序访问效率高得多。
文件位置标记及其定位
文件位置标记
- 为了对读写进行控制,系统为每个文件设置了一个文件读写位置标记(简称文件位置标记或文件标记),用来指示当前的读写位置。
- 一般情况下,在对字符文件进行顺序读写时,文件位置标记指向文件开头,这时如果对文件进行读的操作,就读第一个字符,然后文件位置标记顺序向后移一个位置,在下一次执行读的操作时,就将位置记指向的第二个字符读入。依此类推,直到遇文件尾,结束。
- 如果是顺序写文件,则每写完一个数据后,文件位置标记顺序向后移一个位置,然后在下一次执行写操作时把数据写入文件位置标记当前所指的位置。直到把全部数据写完,此时文件位置标记在最后一个数据之后。
- 可以根据读写的需要,人为地移动文件位置标记的位置。文件位置标记可以向前移、向后移,移到文件头或文件尾,然后对该位置进行读写,显然这就不是顺序读写了,而是随
机读写。 - 对流式文件既可以进行顺序读写,也可以进行随机读写。关键在于控制文件位置标记。如果文件位置标记是按字节位置顺序移动的,就是顺序读写。如果能将文件位置标记按需要移动到任意位置,就可以实现随机读写。所谓随机读写,是指读写完上一个字符(字节)后,并不一定要读写其后续的字符(字节),而可以读写文件中任意位置上所需要的字符(字节)。即对文件读写数据的顺序和数据在文件中的物理顺序一般是不一致的。可以在任何位置写入数据,在任何位置读取数据。
文件位置标记的定位
可以强制使文件位置标记指向需要的位置,用以下函数实现
- 用rewind函数使文件位置标记指向文件头
rewind函数的作用是使文件位置标记重新返回文件的开头,此函数没有返回值
例:有一个磁盘文件,第一次将它的内容显示在屏幕上,第二次把它复制到另一个文件上
#include<stdio.h>
int main()
{
FILE* fp1, * fp2;
fp1 = fopen("file1.dat", "r"); //打开输入文件
fp2 = fopen("file2.dat", "w"); //打开输出文件
while (!feof(fp1)) putchar(getc(fp1)); //逐个读入字符并输出到屏幕,feof函数是判断文件位置标记是否到文件末尾
putchar(10); // 输出一个换行
rewind(fp1); //使文件位置指示器返回文件头
while (!feof(fp1)) putc(getc(fp1), fp2); //从文件头重新逐个读字符,输出到file2文件
fclose(fp1);
fclose(fp2);
return 0;
}
- 用fseek函数的调用形式为:
fseek(文件类型指针,位移量,起始点)
使用时,“起始点"用0、1或2代替,0代表"文件开始”,1为"当前位置",2为"文件末尾"
起始点 | 名字 | 用数字代表 |
---|---|---|
文件开始 | SEEK_SET | 0 |
文件位置标记当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
"位移量"指以"起始点"为基点,向前移动的字节数。C标准要求位移量是long型数据(在数字的末尾加一个字母L,就表示是long型)。
fseek函数一般用于二进制文件,因为文本文件要发生字符转换,计算位置时往往会发生混乱
fseek(fp,100L,0); //将文件位置标记移到离文件头100个字节处
fseek(fp,50L,1); //将文件位置标记移到离当前位置后面50个字节处
fseek(fp,-10L,2); //将文件位置标记从文件末尾处向后退10个字节
- 用ftell函数测定文件位置标记的当前位置
ftell函数的作用是得到流式文件标记的当前位置。由于文件位置标记经常移动,人们往往不容易知道其当前位置,所以常用ftell函数得到当前位置。用相对于文件开头的位移量来表示。如果ftell函数返回值为-1L,表示出错。如:
i = ftell(fp);
if (i == -1L) printf("error\n");
随机读写文件
例:在磁盘文件stu_dat上已存有10个学生的数据。现要求将第1,3,5,7,9个学生数据输入计算机,并在屏幕上显示出来。
#include<stdio.h>
#include<stdlib.h>
struct Student_type //学生数据类型
{
char name[10];
int num;
int age;
char addr[15];
}stud[10];
int main()
{
int i;
FILE* fp;
if ((fp = fopen("stu.dat", "rb")) == NULL) //以只读方式打开二进制文件
{
printf("can not open file\n");
exit(0);
}
for (i = 0; i < 10; i += 2)
{
fseek(fp, i * sizeof(struct Student_type), 0); //移动文件位置标记
fread(&stud[i], sizeof(struct Student_type), 1, fp); //读一个数据块到结构体变量
printf("%-10s %4d %4d %-15s\n", stud[i].name, stud[i].num, stud[i].age, stud[i].addr); //屏幕输出
}
fclose(fp);
return 0;
}
补充部分
系统定义的文件指针
在标准输入输出库中,系统定义了三个FILE型的指针变量
- stdin(标准输入文件指针)。指向在内存中与键盘相应的文件信息区,因此,用它进行输入就蕴涵了从键盘输入。
- stdout(标准输出文件指针)。指向在内存中与显示器屏幕相应的文件信息区,因此,用它进行输出就蕴涵了输出到显示器屏幕。
- stderr(标准出错文件指针),用来输出出错的信息,它也指向在内存中与显示器屏幕相应的文件信息区,因此,在程序运行时的出错的信息就输出到显示器屏幕。
这三个FILE型的指针变量称为标准文件(standard file)指针,有时简称标准文件。它们是在stdio.h头文件中定义的。因此在使用键盘输入和屏幕输出时用户不需要自己定义相应的文件指针。
按规定,在程序中所有用到的文件必须先打开才能使用,但是为了方便用户,系统在程序开始运行时,自动打开3个标准文件:标准输入stdin、标准输出stdout、标准出错输出stderr。因此用户就不需要自己打开终端文件了。
回车换行符的转换
- 从计算机输入文本文件时,将回车和换行符转换为换行符,在输出时把换行符转换成回车和换行两个字符。 在用二进制文件时,不进行这种转换,在内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。
fread和fwrite函数用于二进制文件的输入输出
- 应当说明:fread和fwrite函数只能用于二进制文件的输入输出,因为它们是按数据块的长度来处理输入输出的,按数据在存储空间存放的实际情况原封不动地在磁盘文件和内存之间传送,一般不会出错。如果在ASCII文件和二进制之间传送,在字符发生转换的情况下很可能出现与原设想的情况不同。
文件读写的出错检测
- ferror函数
- 在调用各种输入输出函数(如putc、getc、fread、fwrite等)时,如果出现错误,除了函数返回值有所反映外,还可以用ferror函数检查。它的一般调用形式为:
ferror(fp);
。如果ferror返回值为0(假),表示未出错;如果返回一个非零值,表示出错。 - 应当注意,对同一个文件每一次调用输入输出函数,均产生一个新的ferror函数值,因此,应当在调用一个输入输出函数后立即检查ferror函数的值,否则信息会丢失。
- 在执行fopen函数时,ferror函数的初始值自动置为0
- clearerr函数
- clearerr的作用是使文件错误标志和文件结束标志置为0。假设在调用一个输入输出函数时出现了错误。ferror函数值为一个非零值。在调用clearerr(fp)后。ferror(fp)的值变为0,以便再进行下一次的检测。
习题
- 对C文件操作有些什么特点?什么是缓冲文件系统和文件缓冲区?
- 答: C语言把文件看作是一个字符的序列级,即由一个一个字符的数据顺序组成。根据数据的组织形式,可以分为:ASCII文件(文本文件)和二进制文件。一个C文件是一个字节流或二进制流(流式文件)。它允许对文件存取一个字符,这就增加了处理的灵活性。
- 缓冲文件系统:是指系统自动在内存中为每一个正在使用的文件开辟一个缓冲区,如果从磁盘向内存读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区,然后再从缓冲区逐个地将数据送到程序数据区中去。
- 什么是文件型指针?通过文件指针访问文件有什么好处?
- 缓冲文件系统中,关键的概念是"文件类型指针",简称"文件指针"。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为FILE。
- 通过文件指针访问文件的好处是:可以随机访问文件,有效表示数据结构,动态分配内存,方便使用字符串,有效使用数组。
- 对文件的打开与关闭的含义是什么?为什么要打开和关闭文件?
- "打开"是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据)。
- "关闭"是指撤销文件信息区和文件缓冲区,使文件指针变量不再指向该文件,显然就无法进行对文件的读写了。