学习笔记---文件基础

xiaoxiao2021-02-28  98

文件基础

概述

文件读写 常用函数 读/写指定格式以字符为单位从文件读入fscanf(文件指针,格式字符串,输入表列);int fgetc(FILE *fp);写到文件中fprintf(文件指针,格式字符串,输出表列);int fputc(int ch,FILE *fp);

概念:指存储在外部介质上的数据的集合

注:数据和程序都以文件的形式存放在外部介质上。操作系统以文件为单位对数据进行管理

文件名:路径+主名+扩展名

文件分类:

按文件类别:普通文件(文本文件、程序文件等)、设备文件(显示器、音响等)...

按存储介质:磁盘文件(存储在磁盘中的文件)、光盘文件(存储在光盘中的文件)、u盘文件(存储在U盘中的文件)...

按用途:程序文件(组成程序的文件)、数据文件(存储数据的文件)...

按文件中数据的组织形式:ASCII文件(以ASCII表示的文件,例如:.txt、.cpp、.c等以字符组成的文件)、二进制文件(用二进制形式表示的文件,可以是.o、.exe等程序文件,也可以是特定格式的数据文件如.mp3等)...

文件读写示意图:

文件系统:

标准文件系统:采用缓冲文件系统,系统自动为正在使用的文件开辟内存缓冲区

特点:为每个数据流开辟一个内存缓冲区,缓存区用于存放流中的数据,缓存区中的数据就是流。

非标准文件系统:不使用缓冲区的文件系统,由程序设计者在程序中为每个文件设定缓冲区

代码示例:

#include <stdio.h> #include <stdlib.h> /*这个程序用来测试输入缓存区*/ int main() { int n[5],i; for(i=0;i<5;i++) { scanf("%d",&n[i]); printf("%d\n",n[i]); } return 0; } 结果:

解析:

如上,一次性输入1到5的数字,都被存入了输入缓存区。在程序运行过程中从输入缓存区中逐个读取出来,并输出。

文件结构及打开关闭文件

文件结构

定义:内存会为每个使用中的文件开辟一段区域用于存放其信息。而文件的信息将作为一个结构体被保存在内存中。

C语言中的文件结构体:

为了方面对文件进行操作,C语言在stdio.h头文件中已经定义好了文件的结构体。

其具体代码如下:

typedef struct _iobuf { char* _ptr;//指向buffer中第一个未读的字节(buffer即缓存区) int _cnt;//记录剩余未读字节的个数 char* _base;//记录该文件的缓存区的地址 int _flag;//记录打开的文件的一些属性值 int _file;//用于获取文件的描述 int _charbuf;//单字节的缓冲 int _bufsiz;//缓冲区的大小 char* _tmpfname;//由系统访问的临时文件名 }FILE; 只要在程序中调用了stdio.h头文件,就可以直接使用该自定义类型来方面的操作文件。

如:

FILE *fp; 以上代码段即定义一个指向FILE类型的指针,该指针也被称为“文件指针”。使用这种指针,可以配合C语言中的各种文件操作函数方面快捷的对文件进行操作。

打开文件

定义:为文件流对象和指定的磁盘文件建立关联,并指定文件的工作方式(只读、只写、追加等)

方式:FILE *fp = fopen("文件地址","打开方式");

注1:以上fopen函数为C语言中打开一个磁盘文件的函数。其中文件地址的格式为:"file.txt"或"c:\\dict\\dictionary.dat".前者只写文件名,则默认文件和程序在同一目录下。后者加上了地址,则文件在指出的地址目录下。

注2:以上打开方式除常用的只读只写追加外还有多种,其中的区分将在以下解析。

打开文件的方式 方式文件类型和操作要求r只读打开一个文本文件,只允许读数据w只写打开或建立一个文本文件,只允许写数据a追加打开一个文本文件,并在文件末尾写数据rb只读打开一个二进制文件,只允许读数据wb只写打开或建立一个二进制文件,只允许写数据ab追加打开一个二进制文件,并在文件末尾写数据r+读写打开一个文本文件,允许读写w+读写打开或建立一个文本文件,允许读写a+读写打开一个文本文件,允许读,或在文件末尾追加数据rb+读写打开一个二进制文件,允许读和写wb+读写打开或建立一个二进制文件,允许读和写ab+读写打开一个二进制文件,允许读,或在文件末尾追加数据

注3:以上r+和w+的区别在于,当文件不存在时,r+方式会打开失败,而w+方式会新建一个文件。

注4:以上b(banary)代表二进制文件,而t(text)代表文本文件。因t是默认的。所以rt写为r

注5:使用fopen打开文件时,当使用r方式且文件不存在。或使用w方式且磁盘损坏无法建立新文件时,打开文件会失败。此时需要对此进行异常处理。如:

if(fp=fopen("file.txt","w")==NULL) { printf("error!cannot open file.txt!\n"); exit(1); }

关闭文件

定义:当对文件的操作完成时,需要关闭文件,将缓冲区中的数据妥善处理,解除磁盘文件与文件流的关联,不再通过文件流对该文件进行输入或输出。

方式:fclose(文件指针);

如:

int main() { int a[10]; FILE *fp; fp=fopen("file.txt","w"); //对文件的操作 fclose(fp);//关闭文件 return 0; } 注: fclose关闭文件时,如果缓冲区中有剩余的待读、写的数据,会将该数据读取、写入。以保证所有数据都不丢失。

代码示例:

#include <stdio.h> #include <stdlib.h> /*这个程序用于测试文件的操作*/ int main() { FILE *fp; int i,d; fp=fopen("data.txt","w"); if(!fp) { printf("File cannot open!"); exit(1); } for(i=0;i<10;i++) { scanf("%d",&d); fprintf(fp,"%d\t",d); } fclose(fp); return 0; } 结果:

解析:

1.如上,成功的打开/新建了文件并写入了数据

2.当打开失败时fopen函数会返回NULL值,所以在以上if中的判断条件可以是fp==NULL。但因为在c语言中,NULL的值即为0,而0作为判断条件时是假。所以!fp为真时,打开文件失败。

3.以上exit()函数的参数,当为0时,即程序正常退出。无任何特殊效果。当为1时,即程序异常退出,会在电脑的错误日志中增加一条日志,以供其他程序或用户查看

标准文件读写方式

读写字符

函数: int fgetc(FILE* stream);

功能:从指定的文件中读一个字符

形式参数:

stream:FILE类型的指针,要求文件以读或读写的方式打开(r/r+等)

返回值:读取到的字符的ASCII码值

函数:int fputc(int ch,FILE* stream);

功能:把一个字符写入指定的文件中

形式参数:

ch:要写入文件中的字符的ASCII码,可直接以字符的形式提供

stream:FILE类型的指针,要求文件用写、读写、或追加的方式打开(w/w+/r+/a等)

返回值:写入到文件的字符的ASCII码值

代码示例:

#include <stdio.h> #include <stdlib.h> /*这个程序用于以字符为单位复制文件内容*/ int main() { FILE *fp1,*fp2; char ch; if((fp1=fopen("a.txt","r"))==NULL) { printf("Cannot open source file.\n"); exit(1); } if((fp2=fopen("b.txt","w"))==NULL) { printf("Cannot open target file.\n"); exit(1); } printf("copying........"); while((ch=fgetc(fp1))!=EOF)//从源文件中逐个读出字符 fputc(ch,fp2);//将读出的字符逐个写入目标文件 fclose(fp1); fclose(fp2); printf("done!"); return 0; } 结果:

解析:

1.如上,成功的将a.txt中的内容复制到b.txt中。

2.EOF作为文件结束符,代表文件已到末尾,在C语言中,EOF的值为-1。因此,以上while的判断条件也可以用:(ch=fgetc(fp1))!=-1

读写字符串

函数: char* fgets(char *str,int n,FILE *stream);

功能:从指定文件中读入一个字符串

形式参数:

str:用于存放读取到的字符串的内存单元的起始地址

n:一个正整数,表示从文件中读出的字符串不超过n-1个字符,在读入的最后一个字符后加上串结束标志'\0'。(在读出n-1个字符之前,如遇到了换行符或EOF,则本次读取结束)

stream:文件指针,限制条件同上。

返回值:读取到的字符串的首地址str(形参中的str的地址),读取出错或缓冲区满时,返回NULL

函数:int fputs(char *str,FILE *stream);

功能:向文件写入一个字符串

形式参数:

str:要向文件中写入的字符串,可以是字符串常量,也可以是字符数组名,或指针变量

stream:文件指针,限制条件同上

返回值:写入的字符数

代码示例:

#include <stdio.h> #include <stdlib.h> /*这个程序用于以字符串为单位复制文本文件*/ int main() { FILE *fp1,*fp2; char str[80]; if((fp1=fopen("a.txt","r"))==NULL) { printf("Cannot open source file.\n"); exit(1); } if((fp2=fopen("b.txt","w"))==NULL) { printf("Cannot open target file.\n"); exit(1); } printf("copying......"); while(!feof(fp1)) { fgets(str,80,fp1);//从源文件中读入字符串 fputs(str,fp2);//将字符串写入目标文件 } fclose(fp1); fclose(fp2); printf("done!"); return 0; } 结果:

解析:

1.如上,成功的拷贝的文件

2.以上while的判断条件使用了feof函数,该函数将判断当前文件的读取位置是否在文件末尾,如果是,则返回1.否则返回0;

3.以上设置最大一次性读取80字节的字符串,但实际上每次读取到换行符时,就会停止当前读取。(即:以上程序对a.txt进行两次读取,第一次读取Hello World!并写入目标文件。第二次读取Goodbye World!并写入目标文件)

格式化读写

函数: int fprintf(FILE *stream,char *format,...);

功能:以format的格式向文件中写入字符串。

形式参数:

format:包含格式控制符的字符串(例:"%d,%6.2f Hey!"),同printf中的格式控制字符串。

...:个数和类型均受格式控制符的限制的按照格式要输出的表达式。

stream:文件指针,限制条件同上。

返回值:输出的字符数,当输出中发生错误时,返回一个负值。

函数: int fscanf(FILE *stream,char *format,...);

功能:以format的格式从文件中读取数据。

形式参数:

format:包含格式控制符的字符串(例:"%d,%6.2f Hey!"),同scanf中的格式控制字符串。

...:个数和类型均受格式控制符的要求提供接收输入的地址。

stream:文件指针,限制条件同上。

返回值:输入数据的个数(即得到值的变量的个数),如果未读入任何数据,则返回EOF。

代码示例:

#include <stdio.h> #include <stdlib.h> /*这个程序用于实现从键盘上输入数据,并将之保存到文件中*/ typedef struct { int number; char name[11]; float score; }Student; int main() { FILE *fpout,*fpin; if((fpout=fopen("t_student.dat","w"))==NULL) { printf("Cannot open file.\n"); exit(1); } if((fpin=fopen("s_student.dat","r"))==NULL) { printf("Cannot open file.\n"); exit(1); } Student stu; int i; printf("copying...."); for(i=0;i<3;i++) { fscanf(fpin,"%d %s %f",&stu.number,stu.name,&stu.score); fprintf(fpout,"%d %s %f\n",stu.number,stu.name,stu.score); } printf("done!"); return 0; } 结果:

解析:

1.如上,成功的将文件按给出的格式进行了拷贝。

2.可以看出,以上fscanf、fprintf的用法和scanf、printf几乎完全相同。

标准输入输出

前言:在C语言中,将外部设备也看作文件。对外部设备的输入、输出等同于对磁盘文件的读写。在此基础上,定义标准的输入输出文件指针。使用这些指针进行的输入输出,称为标准输入输出(标准输入输出不等于标准文件操作)

文件指针分类(以下文件指针在程序运行时自动打开)

stdin:标准输入文件指针,系统分配为键盘。

stdout:标准输出文件指针,系统分配为显示器。

stderr:标准错误输出文件指针,系统分配为显示器。

代码示例:

#include <stdio.h> #include <stdlib.h> /*这个程序用于测试标准输入输出*/ int main() { int m,n; fscanf(stdin,"%d %d",&m,&n); int k=m+n; fprintf(stdout,"%d",k); return 0; } 结果:

解析:

1.如上,在fprintf和fscanf中使用标准输入输出文件作为文件指针,则起到的效果和printf/scanf相同。可以证实:在C语言中stdout代表显示器,stdin代表键盘。

2.在fprintf和fscanf中使用标准输入输出文件指针起到的效果,在fputc/fputs等函数中一样可以起效。这里不再赘述。

文件随机读写

定义:文件指针可随意移动的读写方式

前言:

C语言中对文件进行读写时,会自动创建文件位置指针。该指针的起始位置在文件头部,当使用诸如fscanff等函数对文件进行一次读取时,文件位置指针就会后移一定的位置(后移的字节数取决于读取的字节数)此为文件的顺序读写

在以上顺序读写的基础上,C语言提供了用于操作文件位置指针的几个函数用于实现读写文件指定位置的内容。使用这些函数对文件进行的相对自由的读写,称为文件的随机读写

随机读写函数:

void rewind(FILE *stream)

该函数可将文件位置指针移到文件的起始位置(文件头)

int fseek(FILE *stream,long offset,int startPos);

该函数可将文件位置指针移到任意指定位置

参数说明:

stream:文件指针

offset:相对起始点的偏移量(字节),当该值为正时,指针向文件头移动。当该值为负时,指针向文件尾移动。

startPos:起始点,表示从何处开始计算位移量。其可能的取值:

SEEK_SET(其值为0):以文件头作为定位的起始点;(此时offset只能为正)

SEEK_CUR(其值为1):以文件指示器当前位置(当前的文件位置指针位置)作为起始点;(应控制offset大小使文件位置指针不至于移出文件外)

SEEK_END(其值为2):以文件尾作为起始点。(此时offset只能为负)

int feof(FILE *stream);

该函数用于判断文件位置指针是否处于文件结束位置(文件末尾)

是则返回1,否则返回0;

代码示例:

#include <stdio.h> #include <stdlib.h> /*该程序用于测试文件的随机读写*/ int main() { int a[10],b[10],i; FILE *iofile; if((iofile=fopen("f1.dat","w+"))==NULL) { printf("Cannot open file!"); exit(1); } printf("enter 10 integer numbers:\n"); for(i=0;i<10;i++) { scanf("%d",&a[i]); fprintf(iofile,"%d ",a[i]); } printf("The numbers have been writen to file.\n"); printf("Display the data by read from file:\n"); fseek(iofile,0,SEEK_SET);//或fseek(iofile,0,0);或rewind(iofile); for(i=0;i<10;i++) { fscanf(iofile,"%d",&b[i]); printf("%d ",b[i]); } printf("\n"); fclose(iofile); return 0; } 结果:

解析:

1.如上,将输入的10个数字写入文件,并将文件位置指针移到文件头后重新读取出来。

二进制文件

概述:在存储数据时,常用的格式有两种:使用ASCII形式以及使用二进制形式。前者将数据的每个字符转成ASCII码进行存储,后者同样能以ASCII形式存储文本数据。同时也允许的使用自定义的格式存储特殊数据。

示例:

对于short int x=12321;

计算机内部存储:

使用记事本打开两种存储形式下的文件:

解析:

1.前者将12321每个数字作为一个字符,转换成ASCII码,再从ASCII码转换成二进制数字进行存储。而使用记事本打开时,默认将每四个字节的二进制数转换成一个十六进制数,并将该十六进制数按ASCII码表转换成字符。最后显示出12321。

2.后者将12321作为short int型数据进行存储。则12321直接转换成二进制为:11000000100001因为是short int,所以存储时以两个字节为一个单位(共16位)。则将11000000100001补齐十六位得:0011000000100001将之拆分成两个字节得:00110000,   00100001  因为通常计算机CPU采用小端存储(计算机底层的内容,此处不赘述)因此将00100001放在前,将00110000放在后

3.对于以上使用二进制形式直接存储short int数据的文件binary.dat。在将其用记事本打开时,会将0010 0001转换成十六进制21,将00110000转换成十六进制30。对应ASCII码表得到其字符分别为! 和 0

4.以ASCII形式存储的数据文件,便于阅读,但相对占用存储空间较多,且在程序中调用此类文件中的数据时。需要花费时间转换(将二进制转换成十六进制再转换成字符最后在程序中根据需要转换成指定格式的数据)

5.以二进制直接存储指定格式的数据文件,不便于阅读(如上的binary.dat需要使用特定的二进制阅读器才可阅读)。但相对更节省空间(如上图所示)且在程序中调用此类文件中的数据时,不需要花费时间转换

6.综上所述,在需要将程序中的数据保存在文件中,且该文件不需要供人阅读时,应优先使用二进制存储

7.对于特定的数据类型,或基本数据类型来说,使用二进制存储拥有以上优点。但字符信息在内存中必定是以ASCII代码形式存储的,因此无论使用以上的哪种存储形式其空间消耗和转换时间都不会改变,即当要存储的数据是字符时仍可使用ASCII(文本文件)形式存储。

附:ASCII码表

代码示例:

#include <stdio.h> #include <stdlib.h> /*该程序用于实现将12321分别存储入二进制文件和文本文件*/ int main() { /*写入文本文件*/ short int x=12321; FILE *Toutfile=fopen("ascii.txt","w"); if(Toutfile==NULL) { printf("Cannot open file!"); exit(1); } fprintf(Toutfile,"%d",x); fclose(Toutfile); /*写入二进制文件*/ FILE *Boutfile=fopen("binary.dat","wb");//此处使用wb,才可写入二进制文件 if(Boutfile==NULL) { printf("Cannot open file!"); exit(1); } fwrite((char*)&x,sizeof(short int),1,Boutfile);//此处的参数将在解析中讲解 fclose(Boutfile); return 0; } 结果:

解析:

1. fwrite函数是用于将数据写入二进制文件的函数。对于以上fwrite中的参数,因fwrite的第一个参数必须要使用char*类型的指针,因此将&x进行强制类型转换以满足条件。而fwrite的第二个参数为一次写入多少字节的数据。因为要将12321以short int形式存入文件,所以一次写入一个short int数据占用的字节数最为合适(因为不同的系统中short int占用的字节数可能不同,因此使用sizeof(short int)而非直接写死,以保证程序的兼容性),fwrite的第三个参数为写入的次数,此处需要存入的只有一个short int数据,因此只需要写入一次即可。

数据块读写函数(多用于读写二进制文件):

读:

int fread(char *buffer,int size,int count,FILE *stream);

写:

int fwrite(char *buffer,int size,int count,FILE *stream);

参数:

char *buffer:指向内存中一段存储空间,表示要读写的区域

int size:表示数据块的字节数

int count:表示要读写的数据块块数

FILE *stream:表示文件指针

返回值:实际读写了的数据块块数

代码示例1:

#include <stdio.h> #include <stdlib.h> /*该程序用于测试fwrite和fread函数*/ typedef struct { int num; char name[10]; int age; char addr[15]; }Student; int main() { FILE *fp; int i; Student stu1[10],stu2[10]; fp=fopen("stu_list.dat","wb+"); printf("input data:\n"); for(i=0;i<10;i++) scanf("%d%s%d%s",&stu1[i].num,stu1[i].name,&stu1[i].age,stu1[i].addr); fwrite((char*)stu1,sizeof(Student),10,fp); rewind(fp); printf("number\tname\tage\taddr\n"); for(i=0;i<10;i++) { fread((char*)&stu2[i],sizeof(Student),1,fp); printf("%d\t%s\t%d\t%s\n",stu2[i].num,stu2[i].name,stu2[i].age,stu2[i].addr); } printf("done!\n"); return 0; } 结果:

解析:

1.以上fwrite,因为需要以自定义类型Student为单位存取数据。所以第二个参数使用sizeof(Student),一次写入一个Student。另因为有十个Student信息,所以第三个参数为10(第一个参数stu1代表的是整个stu1数组,里面共包含10个Student结构的数据)

2.以上fread则同理,只是因为一次性只从文件中读出一个Student结构的数据,因此将第三个参数改为1。

代码示例2:

#include <stdio.h> #include <stdlib.h> /*该程序用于测试二进制文件的随机读写*/ /*对于代码示例1中的stu_list.dat文件,通过随机读写函数修改第六位同学的年龄*/ typedef struct { int num; char name[10]; int age; char addr[15]; }Student; int main() { FILE *fp; Student stu; fp=fopen("stu_list.dat","rb+");//此处应使用rb+而非wb+否则源文件将被替代成空文件 fseek(fp,5*sizeof(Student),SEEK_SET);//使用fseek函数将文件位置指针从文件头向后移动5个Student结构单位 fread((char*)&stu,sizeof(Student),1,fp);//此时读出一个Student,则该学生就是第六个学生 printf("该学生修改前的年龄为:%d\n",stu.age); stu.age=20;//修改其年龄 fseek(fp,-1*sizeof(Student),SEEK_CUR);//在fread的读取之后,文件位置指针将自动后移,使用fseek将文件位置指针移回原处 fwrite((char*)&stu,sizeof(Student),1,fp);//将修改后的数据写入文件 fseek(fp,-1*sizeof(Student),SEEK_CUR); fread((char*)&stu,sizeof(Student),1,fp); printf("该学生修改后的年龄为:%d",stu.age); fclose(fp); return 0; } 结果:

解析:

1.如上,成功的修改了学生的年龄。

2.文件随机读写函数在读写文本文件时会遇到难以精确定位的问题。而在操作以特定数据格式组成的二进制文件时,可以根据需要对数据进行精确定位。

非标准文件简述

概述:

1.以上介绍的一切文件操作及其函数,都被称为标准文件操作。这是基于C语言头文件中定义的FILE文件类型的一系列操作函数的整合,当使用标准文件操作时,系统会自动提供缓冲区。因此标准文件的输入和输出也被称为高层I/O。

2.非标准文件操作,不使用FILE文件指针。读写文件时和文件联系的是一个整数(文件号)且系统不会自动提供文件缓冲区(需要程序设计者自行设置缓存区)。相对标准文件操作,其涉及到的层面更接近计算机底层。虽然较为不便,却也有自由度更高的优点。因此非标准文件的输入输出被称为低层I/O。

非标准文件函数(需要导入头文件fcntl.h):

打开:

int open(char *filename,int mode);

形参:

char *filename:文件名

int mode:打开方式(常用的取值有:O_RDONLY(1)只读;O_WRONLY(2)只写;O_RDWR(4)读写;(括号后的数字为该常量的值)

返回值:

成功返回一个整型数字代表文件号,失败返回-1

关闭:

int close(int fd);

形参:

int fd:文件号

返回值:

成功返回0,失败返回-1

读:

int read(int fd,void *buf,unsigned size);

形参:

int fd:文件号

void *buf:指向内存中一段存储空间,表示要读写的区域

unsigned size:一次读写的字节数

返回值:成功返回一次读入的字节数,失败返回-1

写:

int write(int fd,void *buf,unsigned size);

形参:

int fd:文件号

void *buf:指向内存中一段存储空间,表示要读写的区域

unsigned size:一次读写的字节数

返回值:成功返回一次写入的字节数,失败返回-1

注:非标准文件的操作函数实际上和标准文件操作函数十分相似,只是不涉及到FILE类型并使用文件号取代其作用。

代码示例:

#include <stdio.h> #include <stdlib.h> #include <fcntl.h> /*这个程序用来测试非标准文件操作*/ /*这个程序实现用户输入两个文件名,将文件一复制至文件二*/ int main() { int handle1,handle2; char ch,filename1[20],filename2[20]; printf("enter source filename:"); gets(filename1); if((handle1=open(filename1,O_RDONLY))==-1) { puts("source file error!"); exit(1); } printf("enter destination filename:"); gets(filename2); if((handle2=open(filename2,O_WRONLY))==-1) { puts("destination file error!"); exit(1); } while(read(handle1,&ch,1)>0) write(handle2,&ch,1); puts("Success"); close(handle1); close(handle2); return 0; } 结果:

解析:

1.如上,非标准文件操作和标准文件操作的步骤基本一致。

2.使用O_WRONLY打开文件同样需要保证目标文件存在。open函数并不会自动创建目标文件

转载请注明原文地址: https://www.6miu.com/read-75480.html

最新回复(0)