C语言学习笔记(九):文件的操作
C文件的知识
什么是文件
操作系统把各种设备都统一作为文件来处理。例如,终端键盘是输入文件,显示屏和打印机是输出文件。
文件一般指存储在外部介质上数据的集合。操作系统是以文件为单位对数据进行管理的
输入输出是数据传送的过程,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流(stream),即数据流
c语言把文件看作一个字符(或字节的序列),一个输入输出流就是一个字符流或字节(内容为二进制数据)流。
C的数据文件由一连串的字符(或字节)组成,对文件的存取是以字符(字节)为单位的。输入输出数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控。这种文件称为流式文件。
文件的分类
根据数据的组织形式,数据文件可分为ASCII文件和二进制文件。数据在内存中是以二进制形式存储的,可以认为它就是存储在内存的数据的映像,所以也称之为映像文件(image file)。如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称文本文件(text file),每一个字节存放一个字符的ASCII代码。
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以用二进制形式存储。
数据的存储方式
- 文本方式:数据以字符方式(ASCII代码)存储到文件中。如整数12,送到文件时占2个字节,而不是4个字节。以文本方式保存的数据便于阅读。
- 二进制方式:数据按在内存的存储状态原封不动地复制到文件。如整数12,送到文件时和在内存中一样占4个字节。
文件缓冲区
ANSI C标准采用缓冲文件系统处理数据文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。这样做是为了节省存取时间,提高效率,缓冲区的大小由各个具体的C编译系统确定。
文件类型指针
缓冲文件系统中,关键的概念是文件类型指针,简称文件指针。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为FILE。
typedef struct
{ short level; //缓冲区“满”或“空”的程度
unsigned flags; //文件状态标志
char fd; //文件描述符
unsigned char hold; //如缓冲区无内容不读取字符
short bsize; //缓冲区的大小
unsigned char*buffer; //数据缓冲区的位置
unsigned char*curp; //文件位置标记指针当前的指向
unsigned istemp; //临时文件指示器
short token; //用于有效性检查
}FILE;
FILE *f //定义一个执行FILE类型数据的指针变量
/*f指向某一个文件信息区(结构体变量),通过文件指针变量能够找到与它有关联的文件,通常把这种指向文件信息区的变量简称为指向文件的指针变量*/
打开与关闭文件
fopen(打开文件)
FILE* fp; //定义一个指向文件的指针变量fp
if ((fp=fopen(″file1″,″r″))==NULL) //若文件不存在.fopen返回一个null值
{ printf(″cannot open this file\n″);
exit(0);
}
文件使用方式 | 含义 | 若文件不存在 |
---|---|---|
r(只读) | 输入数据,打开已存在的文本文件 | 出错 |
w(只写) | 输出数据,打开一个文本文件 | 建立 |
a(追加) | 向文本文件末尾添加数据 | 出错 |
rb(只读) | 输入数据,打开一个二进制文件 | 出错 |
wb(只写) | 输出数据,打开一个二进制文件 | 建立 |
ab(追加) | 向二进制文件追加数据 | 出错 |
r+(读写) | 打开一个文本文件 | 出错 |
w+(读写) | 建立一个新的文件 | 建立 |
a+(读写) | 打开一个文本文件 | 出错 |
rb+(读写) | 打开一个二进制文件 | 出错 |
wb+(读写) | 建立一个新的二进制文件 | 建立 |
ab+(读写) | 打开一个二进制文件 | 出错 |
fclose(关闭文件)
fclose(fp)
顺序读写文件操作
向文件读写一个字符
函数 | 功能 | 返回值 |
---|---|---|
fgetc(fp) |
从fp指向的文件读入一个字符 | 读成功,带回所读的字符,失败则返回文件结束标志EOF(即-1) |
fputc(ch,fp) |
把字符ch写到文件指针变量fp所指向的文件中 | 输出成功,返回值就是输出的字符;输出失败,则返回EOF(即-1) |
/*向文件输入字符*/
include <stdio.h>
include <stdlib.h>
int main()
{ FILE *fp; //定义文件指针fp
char ch,filename[10];
printf("请输入所用的文件名: ");
scanf("%s",filename); //输入文件名
getchar(); //用来消化最后输入的回车符
if((fp=fopen(filename,"w"))==NULL) //打开输出文件并使fp指向此文件
{ printf("cannot open file\n"); //如果打开出错就输出“打不开”
exit(0); //终止程序
}
printf("请输入一个准备存储到磁盘的字符串(以#结束): ");
ch=getchar(); //接收从键盘输入的第一个字符
while(ch!='#') //当输入′#′时结束循环
{ fputc(ch,fp); //向磁盘文件输出一个字符
putchar(ch); //将输出的字符显示在屏幕上
ch=getchar(); //再接收从键盘输入的一个字符
}
fclose(fp); //关闭文件
putchar(10); //向屏幕输出一个换行符
return 0;
}
向文件读写一个字符串
函数 | 功能 | 返回值 |
---|---|---|
fgets(str,n,fp) |
从fp指向的文件读入一个长度为(n-1)的字符串,存放到字符数组str中 | 读成功,返回地址str,失败则返回NULL |
fputs(str,fp) |
把str所指向的字符串写到文件指针变量fp所指向的文件中 | 输出成功,返回0;否则返回非0值 |
/*将文件的内容输出到屏幕上*/
include <stdio.h>
include <stdlib.h>
int main()
{
FILE* fp;
char str[3][10];
int i = 0;
if ((fp = fopen("123.txt", "r")) == NULL) //打开有内容的文件
{
printf("can′t open file!\n");
exit(0);
}
while (fgets(str[i], 10, fp) != NULL) //从fp指向的文件读字符,然后存到str数组里
{
printf("%s", str[i]);
i++;
}
fclose(fp);
return 0;
}
以格式化的方式读写文本文件
函数 | 功能 | 返回值 |
---|---|---|
fprintf(fp,"格式化字符串", 输出变量) |
将格式化的数据写入文件指针指向的文件,可以指定输出格式。 | 返回写入字符的数目,如果失败则返回一个负数 |
fscanf(fp,"格式化字符串",输入变量) |
从fp所指向的文件读入若干数据,并根据指定的格式读入到指定的内存地址中 | 读入数据的项数,读到末尾返回EOF |
int i=1;float f = 2.1;
fprintf (fp,″%d,%6.2f″,i,f); //将变量i与变量f输出到fp指向的文件
fscanf (fp,″%d,%f″,&i,&f);
//磁盘文件上如果有字符“3,4.5”,则从中读取整数3送给整型变量i,读取实数4.5送给float型变量f
用二进制的方式向文件读写数据
函数 | 功能 | 返回值 |
---|---|---|
fread(输入缓冲区, 块大小, 块数, fp) |
从文件指针fp指向的文件中读取数据,一共读取块大小 × 块数个字节的数据,并存放到输入缓冲区中。 | 返回实际读取的块数,如果读到文件末尾或读取错误,则返回小于块数的值。 |
fwrite(输出缓冲区, 块大小, 块数, fp) |
将输出缓冲区中的数据写入文件指针fp指向的文件中,一共写入块大小 × 块数个字节的数据。 | 返回实际写入的块数,如果写入失败,则返回小于块数的值。 |
/*fread(buffer, size, count, fp)*/
float f[10];
fread(f,4,10,fp); //从fp所指向的文件读入10个4个字节的数据,存储到数组f中
/*fwrite(buffer, size, count, fp)*/
fwrite(f,4,10,fp) //将f数组里10个4字节数据写到fp文件里
include <stdio.h>
define SIZE 10
struct Student_type
{ char name[10];
int num;
int age;
char addr[15];
}stud[SIZE]; //定义全局结构体数组stud,包含10个学生数据
void save() //定义函数save,向文件输出SIZE个学生的数据
{ FILE *fp;
int i;
if((fp=fopen("stu.dat","wb"))==NULL) //打开输出文件stu.dat
{ 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;
}
随机读写数据文件
什么是随机读写
对文件进行顺序读写比较容易理解,也容易操作,但有时效率不高。而随机访问不是按数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数据进行访问,显然这种方法比顺序访问效率高得多
文件位置标记
定义
为了对读写进行控制,系统为每个文件设置了一个文件读写位置标记(简称文件位置标记或文件标记),用来指示“接下来要读写的下一个字符的位置”。
一般情况下,在对字符文件进行顺序读写时,文件位置标记指向文件开头,这时如果对文件进行读/写的操作,就读/写完第1个字符后,文件位置标记顺序向后移一个位置,依此类推,直到文件末尾。
对流式文件既可以进行顺序读写,也可以进行随机读写,关键在于控制文件的位置标记。如果文件位置标记是按字节位置顺序移动的,就是顺序读写。如果能将文件位置标记按需要移动到任意位置,就可以实现随机读写。
涉及函数
函数 | 功能 | 返回值 |
---|---|---|
rewind(fp) |
将文件指针fp重新定位到文件的开头 | 无 |
fseek(fp,offset,origin) |
将文件指针fp以origin为起始点(0表文件开始位置,1表当前位置,2表文件末尾位置), 然后移动offset个字节 | 0表示成功,非0值表示失败 |
ftell(fp) |
返回文件指针fp当前所指向的位置,单位是字节 | 返回当前位置,如果失败则返回-1L |
rewind(fp); //使文件位置标记指向文件开头,参数为文件指针,没有返回值
/*fseek(文件类型指针, 位移量, 起始点);该函数一般用于二进制文件
“位移量”:指以“起始点”为基点,向前移动的字节数(长整型)
“起始点”:用0,1或2代替,0代表“文件开始位置”,1为“当前位置”,2为“文件末尾位置”
*/
fseek (fp,100L,0); //将文件位置标记向从文件开头处向前移动100个字节
fseek (fp,50L,1); //将文件位置标记向前移到离当前位置50个字节处
fseek (fp,-10L,2); //将文件位置标记从文件末尾处向后退10个字节
/*ftell函数测定文件位置标记的当前位置,若函数出错(如不存在fp指向的文件),返回值为-1L*/
i=ftell(fp); //变量i存放文件位置标记
if(i==-1L) printf(″error\n″); //如果调用函数时出错,输出″error″
使用实例
/*
有一个磁盘文件,内有一些信息。要求第1次将它的内容显示在屏幕上,第2次把它复制到另一文件上。
*/
include<stdio.h>
int main()
{ char ch;
FILE *fp1,*fp2;
fp1=fopen("file1.dat","r"); //打开输入文件
fp2=fopen("file2.dat","w"); //打开输出文件
ch=getc(fp1); //从file1.dat文件读入第一个字符
while(!feof(fp1)) //当未读取文件尾标志
{ putchar(ch); //在屏幕输出一个字符
ch=getc(fp1); //再从file1.dat文件读入一个字符
}
putchar(10); //在屏幕执行换行
rewind(fp1); //使文件位置标记返回文件开头
ch=getc(fp1); //从file1.dat文件读入第一个字符
while(!feof(fp1)) //当未读取文件尾标志,feof是检测流上的文件结束符,如果文件结束返回非0值,否则返回0
{ fputc(ch,fp2); //向file2.dat文件输出一个字符
ch=fgetc(fp1); //再从file1.dat文件读入一个字符
}
fclose(fp1);fclose(fp2);
return 0;
}
文件读写报错检测
ferror函数
在调用各种输入输出函数(如putc,getc,fread,fwrite等)时,如果出现错误,除了函数返回值有所反映外,还可以用ferror函数检查。
ferror(fp); //如果ferror返回值为0(假),表示未出错;如果返回一个非零值,表示出错。
clearerr函数
假设在调用一个输入输出函数时出现错误,ferror函数值为一个非零值。此时应该立即调用clearerr(fp),使ferror(fp)的值变成0,以便再进行下一次的检测
clearerr(fp);