前言:
倘若数据一直存留在内存中,那么我们的程序执行一次后,对应的数据也会跟着丢失,倘若是一些计算性质的程序还好,当时对于需要保留的数据来说,迫切需要一种方式可以将其永久性的保留下来,目前据我所知的有两种方法,第一种是将其存储到对应的数据库中保留下来,第二种则是直接写入文件内部硬盘上(也就是写入磁盘上保存下来)。那么我今天要总结的便是第二种方式,即写入硬盘上保留的方式。
1.计算机的数据基本运转过程:
我们写完的程序,它是怎样经过一系列处理在屏幕上显示出来?我们输入的数据又是如何被计算机接收的呢?数据是怎样进入文件内部的呢?下面便让我们来深入了解一下这些问题:
1.流:
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是同流操作的。⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。
如图:
这也就是为什么我们可以使用scanf向内存输入,同时屏幕又可以输出的原因,他们之间的数据流通是依靠这样的一个流来驱动的,数据通过这样的方式可以到达任何地方。那么我们今天所要进行的便是进入硬盘文件这一步骤。
2.标准流:
那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流:
• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊。
• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯。
• stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr三个流的类型是: FILE* ,通常称为⽂件指针。
C语⾔中,就是通过 FILE* 的⽂件指针来维护流的各种操作的。**也就是说,任何关于流数据的掌控和流向,都是由对应的FILE的结构体指针来操控的,我们这里称之为文件指针,接下来的文件操作中,几乎都需要用FILE这个数据类型。
3.文件操作:
1.文件指针:FILE*
缓冲⽂件系统中,关键的概念是“⽂件类型指针”,简称“⽂件指针”。
每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名
字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系
统声明的,取名FILE.也就是说,我们是通过文件指针来直接操作文件的。
常见形式为:
FILE* pf
2.文件的分类:
1.程序⽂件
程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows
环境后缀为.exe)。
2. 数据⽂件
⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或
者输出内容的⽂件。
而对于我们来说,编译好的程序通过之前说过的编译链接过程,就是生成一个执行文件.exe。所以,这里我们重点讨论的是数据文件,即将程序得到的数据写入到文件内部或者直接从数据文件中得到数据进入到内存中进行我们的操作
1.数据文件的分类:
1.文本文件:
文本文件即将得到的数据以字符形式存储起来,无论传入时这个数据是什么类型的:
比如:10000这个数字,以整型的形式被写入文本文件时,文本文件是这样识别的:‘1’ ‘0’ ‘0’‘0’‘0’,也就是以五个字符的形式写入到文件中,而存储的正是他们符号对应的ASCII的数值,且是char类型一字节8比特位存储以二进制形式存储他们的ASCII值。
2.⼆进制⽂件:
顾名思义,即以二进制形式把数据存储到文件中。
3.文件操作的基本格式(文件的打开和关闭):
在学习文件时,我们可以联想我们的动态内存开辟的知识点,他们有很多的相似之处。
首先是文件操作基本的格式,**我们首先清楚,一个程序能打开的文件是有限的,倘若达到了这个临界值,后续是不能打开文件了。**这就要求我们在打开文件后一定要养成及时关闭文件的好习惯。
故我给出了文件使用的基本格式:
int main() { //首先要打开文件: FILE*pf=fopen("文件名“,"打开方式”); if(pf==NULL)//打开文件后要养成检验文件是否打开成功的好习惯,方式同动态开辟差不多 { perror("fopen failed"); return 1; } //使用过程 // // fclose(pf); pf=NULL; return 0; }
如图我上面所写的程序所示,文件操作最关键的便是文件的打开和关闭,想要操作文件,我们首先一定要使用一个FILE*的指针接收fopen函数。然后在结尾将文件关闭并且将文件指针置空。哦对了,作为一名程序员,一定要在必要的地方给自己留下一些方便调试的程序,在文件操作时,我们也要跟动态开辟内存那里一样写一个检验是否成功的函数,以方便我们后续找错误。
所以,下面让我们首先介绍一下文件操作最基本的两个函数:fopen fclose
1.fopen函数:
由函数的名字,我们便知道它是用来打开文件的函数,其基本格式如下:
FILE * fopen ( const char * filename, const char * mode )它的两个参数,第一个即对应一个你想要打开的文件的名字,第二个参数对应你想要打开的方式(!!!这里一定要注意,这个打开方式的符号要用双引号,而不是单引号,一定要注意!!!!!)
常见的文件打开方式的符号表如下:
我们在这里常用的有r w a rb wb ab。
!!!还是要强调一遍,打开文件的方式一定要用双引号而不是单引号,这个可别再错了!!!!!
2.fclose函数:
即关闭文件操作函数,格式如下:
int fclose ( FILE * stream ),这个参数即我们的FILE*的指针,使用后文件就会关闭,就跟动态开辟函数close一样,这个也是不能自动置空的,所以我们使用完也要人工置空一下。
4.文件操作函数的介绍:
1.文件的读写方式:
文件的读写方式分为两种:第一种为顺序读写,第二种为随机读写
2.文件的顺序读写:
顺序读写,顾名思义,即按照先后顺序依次写入文件或者从文件中读取数据:
首先,我们必须清楚的一个概念是,我们所谓的读取和写入都是针对内存来说,即内存向其写入,内存从中读取数据,这是我们操作文件时必须要清晰的知道的一个概念
其常见的操作函数包括:
1.fgetc fputc函数:
1.fputc函数:
其作用是内存将数据以单个数据依次写入的方式写入文件。**格式为:
int fputc ( int character, FILE * stream )第一个参数即是我们想要写入的东西,可以是字符也可以是数字,但注意,只能是单个字符或者数字,不能是字符串。第二个参数即是我们想要写入文件的文件指针。
2.fgetc函数:
其作用是从文件中以单个数据的方式依次读取数据到内存中.格式为:
int fgetc ( FILE * stream ) 其参数即为对应的文件指针,对于fgetc读取到的数据,我们要用int整型来接收,不管是ASCII形式还是数字形式倘若文件读取不到数据,则返回EOF,故我们也可以这样说,当文件读取到EOF时,标志着文件读取到结尾了。
2.fgets fputs函数:
如果把fgetc fputc中的c理解为char字符的话,那么这里的fgets fputs则把s理解为string,即字符串形式,故我们马上得知,他们是用来处理字符串读取的。
1.fputs函数:
将字符串从内存中写入文件.格式为:
int fputs ( const char * str, FILE * stream ),基本参数信息与fputc差不多,就是第一个不再是单引号的字符,而是一个字符串直接写入文件中了。
2.fgets函数:
将文件中的整段数据直接读取到内存中,格式为:
**char * fgets ( char * str, int num, FILE * stream )**三个参数分别为,第一个:要读取接收的字符串,第二个:要读取的个数 第三个:读取的文件指针。
注意一些细节:
1.对于int num它读取的原则是,会给\0留一个位置,比如读取14个数据,最后只能读取13个,那一个位置要留给\0,故我们考虑长度前一定要给\0预留出位置来。
2.倘若文件读取不到数据,则返回NULL,故NULL返回也可以作为文件读取结束的标志 .
3.fsanf fprintf函数:
1.fprintf函数:
即向文件里面写入内存中的数据的函数,格式如下:
**int fprintf ( FILE * stream, const char * format, … ),**参数与printf基本相同,只是在最开头要加上对应的函数指针,表示要写入的文件。
2.fscanf函数:
从文件中读取内容存入到内存的函数,格式如下:
int fscanf ( FILE * stream, const char * format, … ),参数与scanf基本相同,在最开头的参数对应函数指针,表示从哪个文件中读取数据到内存中。
4.fread fwrite函数:
二进制的读取和写入。
1.fwrite函数:
以二进制的形式写入文件中,格式为:
**size_t fread ( void * ptr, size_t size, size_t count, FILE * stream ),**第一个参数为要传入的数据指针(注意,哪怕是单个的数据,传入的也是指针,别记错了),第二个,第三个数据分别为一个参数的大小和要传入的参数的个数,最后一个参数即为对应的文件指针。
经过这样的处理,文件传入的即为二进制形式的数据。
2.fread函数:
即以二进制的形式读取文件中的数据,之后再根据需要转换成相应的读取方式即可。格式如下:
**size_t fread ( void * ptr, size_t size, size_t count, FILE * stream )**第一个参数是将数据读取后放到的数据指针里,第二个,第三个为单个数据大小和要读取参数个数,最后一个即要读取的函数指针(也可以说是要读取的函数的流)
注意:fread返回的读取到的数据个数,可以利用这一点判断文件读取是否结束
3.文件的随机读写:
顺序读写,实质上是由一个指针进行控制,保证每次的位置。而随机读写同样也是如此,但是我们是可以改变这个指针的位置的,所以才会形成**”随机“**这样的说法。
1.fseek函数:
其作用为可以任意改变文件操作的起始位置,其格式如下:
int fseek ( FILE * stream, long int offset, int origin ),第一个参数为文件指针,第二个参数为偏移量,可以向左(即为负数)也可以向右(即为正数),最后一个参数是基准的初始位置,即对应的offset的移动的基准是哪个位置。
常见的origin有三种:也就是说,我们传入的origin基本也就这三种
1.SEEK_CUR 指针的当前位置
2.SEEK_END 从文件的结尾开始
3.SEEK_SET 从文件的开头开始
2.ftell函数:
用来返回文件指针相对于起始位置的偏移量,格式为:
long int ftell ( FILE * stream )
3.rewind函数:
让文件指针的位置回到文件的起始位置,格式为:
void rewind(FILE*stream)
4.文件的结束情况判断:
!!!!!首先必须要强调的一点:feof不是用来判断文件是否结束的!!!!!
文件结束的判断方式有:1.利用fgetc读取到EOF即为结束 2.利用fgets读取到NULL即为结束 3.二进制文件的读取结束的个数判断文件读取是否结束
而我们的feof和ferror是来判断文件结束的原因的,而不是判断文件何时结束的。
1.feof函数:
int feof ( FILE * stream ),判断文件是否以正常读取结束而结束
2.ferror函数:
int ferror ( FILE * stream ),判断文件是否已读取出现错误而结束
一般在使用的时候,都是feof配合ferror一起使用,来查看文件读取到底是以哪种方式结束的
4.文件缓冲区:
ANSIC 标准采⽤“缓冲⽂件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为
程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓
冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输
⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓
冲区的⼤⼩根据C编译系统决定的。
5.总结:
以上便是我对文件操作的一些粗浅的理解,掌握了文件,我们实际上就可以做一些真正能存储数据的程序了,虽然很简陋,但是也比存不下来要强很多,希望各位通过这篇文章能够彻底弄懂文件操作。