一.文件有不同的类型,分为两种文件:
程序文件(包括源文件.c,目标文件.obj,可执行文件.exe,这些文件的内容是程序代码)
数据文件(文件内容不一定是程序,而是程序运行时读写的数据,比如成绩数据,交易数据)
二.何为文件?
文件是程序设计中一个重要的概念,何为文件? 文件一般指的是存储在外部介质上数据的集合.也就是说,一批数据以文件的形式存放在外部的磁盘介质上,如果我们想要找存放的数据的时候,必须先按文件名找到文件,再从文件里面读取数据.当然外部介质中也必须建立一个文件,我们才能向他输出数据
需要注意的是操作系统把各种设备都统一作为文件来处理,从操作系统的角度看,每一个与主机联系的输入输出设备都是文件,比如,终端输入键盘是输入文件,显示屏和打印机就是输出文件.
我们在这里讨论的是对数据文件的操作
三.文件名
一个文件要有唯一的文件标识,便于用户识别,文件标识分为三部分 :文件路径,文件名主干,文件后缀
文件后缀,不一定就是文件的打开方式,他只是系统默认这个方式打开,其实我们用其他方式也可以打开只是打开之后,可能不是我们所要的东西.
文件标识就常被称为文件名
在这里我们需要了解的是,我们上面这个路径是一个绝对路径,何为绝对路径?
路径标识了唯一的文件,(以盘符C: D: E: 开头 或根目录开头linux 中以 / 开头)
有了绝对路径那么就有相对路径:相对路径(以 " . “, 或者”. ."开头的) " ." 表示当前目录 ". . "表示当前目录的上一层,相对路径必须有一个"当前目录"必须指定当前目录 比如
在写工程中我们相对路径用的多,因为我们是要发布给用户的,所以我们在我们的电脑下用一个绝对路径,这在我们的电脑上是唯一且存在的,那么不一定在用户的电脑上是存在的!
四.数据文件的分类
根据数据的组织形式,数据文件可分为ASCII文件和二进制文件
一个数据怎么在介质中存储?
字符一律以ASCII形式存储,数值型文件既可以用ASCII形式,也可以用二进制存储
六.文件缓冲区概念
ANSI C 采用 缓冲文件系统 处理数据文件,系统自动的在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区,从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起从到磁盘,如图,装在数据也是一样,先从磁盘取数据到缓冲区,再到层序数据区操作数据,再输出
七.文件类型指针
在文件缓冲系统中,关键的概念是"文件类型指针",简称 “文件指针”,每个被使用的文件在内存中开辟一个信息区,用来存放文件的有关信息(比如文件的名字,状态,当前位置),这些信息保存在一个结构体变量里面,这个结构体变量是由系统声明的,在stdio.h有文件中包含
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
这个声明是VS2010中的声明,在不同的编译系统中可能声明不一样,里面包含的就是文件的各种信息,我们不用深究.
FILE f1;
我们定义了一个结构体变量f1 ,用它来存放一个文件的有关信息,这些信息是在打开文件时由系统根据文件的情况自动放入的,我们不必过问.
我们一般不会对FILE类型变量命名,也就是不通过变量的名字来用这个变量,而是设置一个文件指针变量来用
FILE *fp;
定义一个变量用来指向文件的信息区(他就是一个结构体变量),而我们通过文件信息区中的信息就能够访问这个文件,也就是说我们通过这个指针变量就可以找到与他关联的文件,这就是句柄思想,正如遥控器一样,我们不直接在电视上做操作,通过遥控器就可以来操作电视.
七.文件的基本操作
围绕文件有四个最基本的操作:
1.打开文件,关闭文件
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。如过不关闭的话会如内存泄露一样造成文件泄露,在vs中默认的配置是打开512个文件,但实际我们只能打开509个,因为还有stdin,stdout,stderr
打开文件的数目是可以配置的
打开文件函数 fopen;
FILE * fopen ( const char * filename, const char * mode );
int fclose ( FILE * stream );
在定义中我们可以看出打开文件会返回一个FILE*(返回了一个文件信息区的地址) 这时候我们定义一个FILE* ,就可以对文件进行操作了
FILE* fp = fopen("../test.txt","r");
if(fp == NULL) {
perror ("fopen");
printf("%s\n",strerror (errno));
return 1;
}
fclose(fp);
我们用只读的方式打开一个文件注意我创建文件的位置
这是我们程序的文件,也就是我们的当前目录,我们在当前目录下的上一层创建了test.txt,为什么要在上一层创建,而不再这一层创建恩呢?
当前目录的上一层
因为我们的程序是要发布给用户的,用的是.exe程序,所以我们不可能把源程序发布给用户,而我们的exe文件在 Debug 文件中存放,所以我们运行Debug中的exe时候,程序执行到打开文件时候,也可以使用这个路径方便一点.
如果我们打开文件失败那么有两种原因:1.文件不存在2.文件操作没有权限)
这时候我们用错误代码提示就可以查出问题所在 (异常处理机制)
文件的打开方式有很多种 如图
3.读文件
常用的文件操作函数
fread是一个函数,它从文件流中读数据,最多读取count个项,每个项size个字节,如果调用成功返回实际读取到的项个数(小于或等于count),如果不成功或读到文件末尾返回 0。
size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
实例:我们在txt中输入这几个字符
char buf[1024] = { 0 };
size_t count = 0;
FILE* fp = fopen("../test.txt","r");
if(fp == NULL) {
perror ("fopen");
/*printf("%s\n",strerror (errno));*/
return 1;
}
count = fread(buf,1,sizeof(buf),fp);
printf("%s\ncount = %d\n",buf,count);
运行之后我们顺利的读到了TXT文本下的内容
在这里需要注意第一点 fread 是有返回值的,返回的是我们成功读到的个数,count = 12
第二点 :如何写fread 中的参数
void *buffer buf 顾名思义就是缓存的意思,存放从文件读入的数据的地址
size_t size, 我们一次要读写的字节数
size_t count 我们要读写多少个数据项
FILE *stream 要读写的文件流
再来举一个例子:
struct Student_type {
char name[1024];
int num;
int age;
char addr[1024];
}stdu[40];
我们定义了一个结构体数组stud,有40个数组,每一个元素来存放学生的数据,我们假设学生的数据已经录入到磁盘中,那么就可以这样写,从磁盘中读取这些数据,假设我们已经读入到FILE* fp中
for(int i = 0;i<40;i++) {
fread(&stud[i],sizeof(struct Student_type),1,fp)
}
4.写文件
文件操作都是相对应的,也就是成对出现的,由上面的图我们就可以看出来
与fread 对应的是 fwrite
fwrite() 是 C 语言标准库中的一个文件处理函数,功能是向指定的文件中写入若干数据块,如成功执行则返回实际写入的数据块数目。该函数以二进制形式对文件进行操作,不局限于文本文件。
在我们写程序时多用这两个函数读写文件,因为他们不局限文件格式
实例:我们还是以上面的程序为基准
char buf2[1024] = "hello"; //文件缓冲区,要写/要读的东西放到内存中,最后再使用或者写到磁盘中去
FILE* fp = fopen("../test.txt","w"); //"w"打开文件的时候,会清空文件的原有内容!
if(fp == NULL) {
perror ("fopen");
/*printf("%s\n",strerror (errno));*/
return 1;
}
fwrite(buf2,1,strlen(buf2),fp);
这时候我们运行程序是看不出有什么效果的,因为我们写操作是在文件上完成的,注意的是"w"方式打开文件 的时候,会清空原有内容,我们要根据自己的情况来填写需要完成的操作方式!
void *buffer 与上面刚好相反,把此地址开始的数据向文件输入
size_t size, 我们一次要读写的字节数
size_t count 我们要读写多少个数据项
FILE *stream 要写的文件流
那么上一个学生结构体的存储信息我们就可以这样写进磁盘中,假设我们已经在内存中写好了学生的数据
for(int i = 0;i<40;i++) {
fwrite(&stud[i],sizeof(struct Student_type),1,fp);
}
5.fprintf与fsacnf
fprintf 是往文件里面写东西,类似于我们的printf,只是将终端从显示屏换到了文件中
fscanf是将文件里面的内容分写到我们的内存中去
int x = 10;
fprintf(fp,"x = %d\r\n",x);
fprintf(fp,"hello world!\r\n");
//注意我们在windows下换行的时候要加\r\n 才可以换行,只加一个\n只会覆盖
fprintf(文件指针,格式字符串,输出列表)
再看这一段程序:
//往显示器上写 这个文件
/*fprintf(stdout,"x = %d",x);*/
这一段和
printf("x = %d",x);
是等价的,我们的stout就是标准输出,fprint()意味着往这个标准输出文件,也就是显示屏上输出文件,注意一切皆文件这个概念,前面已经说了,在vs2010下我们最多打开509个文件,剩下的三个文件是标准输入输出流三个文件!
fsacnf 从一个流中(文件)执行格式化输入,fscanf遇到空格和换行时结束,注意空格时也结束
实例
注意此时我们的txt 中是 hello world!
char buf[1024] = { 0 };
FILE* fp = fopen("../test.txt","r");
if(fp == NULL) {
perror ("fopen");
/*printf("%s\n",strerror (errno));*/
return 1;
}
while(!feof(fp)) {
fscanf(fp,"%s",&buf);
printf("%s\n",buf);
}
注意:用fprintf和fsacnf 读写磁盘方便,容易理解,但是由于在输入时候要将文件中的ASCII码转换为二进制再保存,在输出时候又将二进制转换为字符,这是要花费时间的,因为我们频繁进行交换数据的情况下,最好不用这一对,而用我们刚才上面讲解的一对fread和fwrite
6.额外扩展两个常见函数 :sprintf和sscanf
sprintf函数
sprintf函数原型为 int sprintf(char *str, const char *format, …).作用是格式化字符串,具体功能如下所示:
将数字变量转换为字符串。
实例:
int x = 10;
char buf[1024] = { 0 };
sprintf(buf,"%d",x);
printf("%s",buf);
运行结果将x的值转为了字符串并赋值给了buf
sscanf将字符串转回整数
char buf[1024] = "10";
int y = 0;
sscanf(buf,"%d",&y);
printf("%d",y);
将buf中的字符串转为整数传给y
这在我们平时转换字符和整数的时候是很常见的函数
7.文件的随机读写
1.rewind函数使文件位置指向文件开头,此函数没有返回值
rewind(fp);
2.fseek函数
根据文件指针的位置和偏移量来定位文件指针。
int fseek ( FILE * stream, long int offset, int origin );
三个参数依次为 文件指针类型,位移量,起始点
位移量是以起始点为基准进行偏移的,位移量是long型数据(在数字后末尾加一个L就表示long)
SEEK_SET 0 文件开头
SEEK_CUR 1 文件当前位置
SEEK_END 2 文件末尾位置
feek(fp,100l,0); 将文件位置标记前移到文件开头100个字节
feek(fp,50l,1); 将文件位置标记前移到当前文件位置50个字节
feek(fp,-10l,2); 将文件位置标记从文件末尾处后退10个字节
3.ftell
返回文件指针相对于起始位置的偏移量
如果调用函数时候出错(不存在fp)指向的文件,ftell返回值为-1
int i = ftell(fp);
if(i == -1L) printf("error!\n");
8.文件的结束判定
被错误使用的 feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1.文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
2. 例如: fgetc判断是否为EOF. fgets判断返回值是否为NULL.
3. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。 例如: fread判断返回值是否小于实际要读的个数。