C语言文件详解

我们在前面的程序中,输入数据后如果结束程序,数据也会全部被销毁,这是因为数据存储在内存空间中,当程序退出,使用的内存空间就会被回收,数据销毁。那么如何能够保存我们在程序中写下的数据呢?这个时候就可以用到磁盘中的文件或者使用数据库来进行数据的保存了。

一、概念

磁盘中的文件,就是文件。

1.文件名(文件标识)

每个文件都有它自己的唯一的文件标识,便于用户识别和使用,而文件标识被我们称为文件名:

文件名 = 文件路径+文件名主干+文件后缀

注:文件名可以没有后缀。

2.程序文件与数据文件

在程序设计中,我们一般谈到的文件有两种--->程序文件和数据文件

程序文件,顾名思义就是我们写代码程序的文件,包含头文件(.h文件)、源代码文件(.c文件)、可执行程序(.exe文件)、目标文件(.obj文件)等。

数据文件,指文件内容不是程序而是程序运行时需要读写的数据的文件,如程序运行需要读取数据的文件或者需要打印输出内容的文件,就是数据文件。

3.文件信息区

我们使用程序中fopen函数打开文件时,系统就会创建一个该文件的文件信息区,信息区中存储的是一个FILE类型的结构体,存储该文件的相关信息,而fopen函数如果成功打开文件,就会返回这个文件信息区的起始地址。我们可以通过该FILE*类型指针访问文件信息区,通过文件信息区,我们就能够得到目标文件的相关信息。

二、文件打开与关闭

对于文件的操作:1.打开文件:使用fopen函数。2.读/写文件。3.关闭文件--->使用fclose函数。

1.文件指针

文件类型为FILE,是一种结构体类型,FILE的使用需要包含头文件stdio.h,上面说到,fopen函数成功打开文件会返回一个FILE*类型的指针,FILE*即文件指针类型。

所以我们可以定义文件指针变量来指向某个文件的文件信息区起始地址,那么该指针变量就能够通过文件信息区来访问文件:

2.fopen函数--->文件的打开

FILE * fopen ( const char * Filename , const char * mode ) ;

fopen函数用于打开文件,两个传入参数为文件名Filename和文件使用方式mode,如果成功打开文件fopen函数返回一个指向该文件信息区起始地址的指针,可以通过该指针来访问文件相关信息;如果打开失败会返回NULL空指针,因此同动态内存管理一样,我们需要判断接收fopen函数返回值的指针是否为NULL空指针。

	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)//判断接收指针是否为NULL
	{
		perror("fopen");
	}

①mode--->文件使用方式

文件使用方式
含义
如果指定文件不存在
"r"( 只读)
为了输入数据,打开一个已经存在的文本文件出错
"w"(只写)为了输出数据,打开一个文本文件创建一个新的文件
"a"(追加)向文本文件尾添加数据创建一个新的文件
"rb"(只读)为了输入数据,打开一个二进制文件报错
"wb"(只写)为了输出数据,打开一个二进制文件创建一个新的文件
"ab"(追加)向一个二进制文件尾添加数据创建一个新的文件
"r+"(读写)为了读和写,打开一个文本文件报错
"w+"(读写)为了读和写,建立一个新的文件创建一个新的文件
"a+"(读写)打开一个文件,在文件尾进行读和写创建一个新的文件
"rb+"(读写)为了读和写,打开一个二进制文件报错
"wb+"(读写)为了读和写,建立一个新的二进制文件创建一个新的文件
"ab+"(读写)打开一个二进制文件,在文件尾进行读和写创建一个新的文件

②Filename--->文件名

对于fopen中传入的文件名,需要文件名主干和后缀,而文件路径就分为了相对路径和绝对路径。

对于文件路径而言,我们若省略文件路径,则默认是选择了该源文件(.c文件)目录下的路径,同时在省略路径的情况下,我们可以使用 ..\\ 来选择源文件上一级的路径,这样的做法是可以叠加的如..\\..\\data.txt表示源文件上两级的路径,这是相对路径;若不省略文件路径,那么就得将文件的路径写全,这就是绝对路径。

        [I]相对路径

case1:默认路径

FILE* pf = fopen("data.txt", "r");

case2:上一级路径

FILE* pf = fopen("..\\data.txt", "r");

case3:上上级路径

FILE* pf = fopen("..\\..\\data.txt", "r");

[II]绝对路径
FILE* pf = fopen("D:\\Adobe\\data.txt", "r");

那么我们就将fopen函数的两个参数说明清楚了,对于使用方式的多样性还是需要我们去多使用多记忆的。

3.fclose函数--->文件的关闭

对于文件的打开和关闭,和动态内存空间管理是类似的,它是成套进行的,打开--->操作--->关闭,因此每次打开最后就需要关闭,就像堆区空间每次开辟使用完就需要释放同理。

int fclose ( FILE* stream ) ;

fclose函数用于关闭使用完的文件,传入参数为文件信息区起始地址,若成功关闭则返回0,若失败则返回EOF(-1)。

fclose的使用:

	FILE* pf = fopen("D:\\Adobe\\data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
	}
	//文件操作:读/写......
	//文件关闭
	fclose(pf);
	pf = NULL;

三、文件的读写

我们在前面学习了文件的打开与关闭,那么中间对于文件要进行的一系列读写操作就是我们接下来的重点内容,我们将文件的读写分为顺序读写和随机读写。

对于文件的输入与输出的图解:

1.文件的顺序读写

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入fread文件 
二进制输出fwrite文件

        ①流

程序员在写程序存储数据时,可能存储在硬盘、U盘、光盘、打印到屏幕、上传到网络等,而这些不同的外部设备所需进行的数据存储方式是不同的,因此,流作为中间介质,就被创建出来,程序员只需要将数据传入流中(所以流就是数据流),然后C语言会将这些流分配到指定的外部设备上。

所有输入流:1.文件输入流,2.标准输入流(键盘)。

所有输出流:1.文件输出流,2.标准输出流(屏幕)。

文件的输入输出流,我们可以理解为,对于硬盘的这种数据存储在文件上的形式,我们采用文件输入输出流。

标准输入输出流,即在程序上的输入与输出。我们在运行C语言程序的时候,就会默认开启了标准输入流strin、标准输出流stdout、标准错误流stderr。所以我们可以直接使用scanf和printf函数进行键盘输入和打印到屏幕。但是对于文件而言,没有默认开启的流,因此我们需要进行打开文件以及最后关闭文件的操作。

②fgetc--->字符输入函数;fputc--->字符输出函数

int fgetc ( FILE* stream ) ;

int fputc ( int character , FILE* stream ) ;

fgetc函数参数为指向文件信息区起始地址的指针,成功返回读取到的一个字符的ASCII码值;如果读到了文件的末尾,或者产生了其他的错误,那么返回EOF(-1)。

fputc函数参数为需要输出到文件中的一个字符,还有这个指向文件信息区的起始地址的指针,成功输出到文件中返回该字符的ASCII码值,失败则返回EOF(-1)。

对于fgetc和fputc而言,每一次的使用,都会使文件中的光标向前移动一位。而光标就决定了我们读写文件时的数据。

fputc的使用

注意,对于fgetc和fputc函数而言,每一次的使用只能够针对到一个字符。但是可以使用循环来多次输入输出。

对于fputc这些函数而言,\n也是用来换行的。

fgetc的使用

对于fgetc函数的输入,我们可以使用变量来接收然后打印在屏幕上:

注意:在打开文件操作中,后面的使用方式其实非常重要,后续想要怎么用,使用方式就要选择对应的方式!

同时进行读写操作

如果要同时进行写操作和读操作,我们要对于光标极其敏感!在我们使用fputc函数写入数据后,光标其实移动到了这个文件中数据的末尾,我们需要使用rewind函数将光标挪到起始位置再进行数据的读取!rewind函数后面会讲到。

③fgets--->文本行输入函数;fputs--->文本行输出函数

char * fgets ( char * str , int num , FILE * stream ) ;

int fputs ( cosnt char * str , FILE * stream ) ;

fgets函数用于一行的输入,将传入的流stream中的一行字符串的最多num-1个字符放入内存中str这个字符型指针指向的空间,返回这个指针str,失败或者读到文件末尾返回NULL。

fputs函数用于一行的输出,将str指向的字符串数据输出到文件流stream中。成功返回一个非0值,失败返回EOF(-1)。

对于fgets和fputs函数而言,它们的每一次使用都是针对于文件中一行的。

fputs的使用

下面只输出arr1的字符串到文件中:

我们再把arr2也输出到文件中,由于没有换行操作符因此会直接接在helloworld后:

同样可以加上\n换行:

fgets的使用

使用原来data.txt中的数据。即两行,第一行helloworld、第二行hellodear。

当我们只调用一次fgets函数时,如果num给的很足,我们发现会打印第一行的所有内容。

当num给的比较小,无法打印这10个字符时,便如下图所示:

上图说明在fgets函数中,num这个参数的分量是非常重的。

那么我们回到正题,重新使num=101,占满这个arr数组空间,第二次调用fgets:

我们发现,这一次第二行的数据也被我们打印了出来,也证明fgets函数正如其名,文本行输入函数,每次最多将文件中一行的数据输入到开辟的某内存空间中。

注:上述fputs代码和fgets代码使用文件操作后均没有进行文件关闭与指针置NULL,实际上必须要有这两个操作,读者不要犯同样的错误!

④fscanf--->格式化输入函数;fprintf--->格式化输出函数

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

int fprintf ( FILE * stream , const char * format,...) ;

 fscanf函数用于格式化输入,将stream这个文件流中的数据放入对应类型的变量中;fprintf函数用于格式化输出,将变量中的数据存入到文件流所对应的文件中。

fprintf的使用

这里我们创建了一个struct S类型的结构体变量s,我们可以通过fprintf函数将复杂的不同类型的数据一起写入文件中如下:

fscanf的使用

fprintf函数和fscanf函数的参数与使用其实与printf和scanf函数非常相似,仅仅是在参数上增加了一个文件流FILE* stream,毕竟可以用于文件流的数据操作,我们在使用时要注意输入的格式,输入时格式不能与文件格式中不一致,输出时格式可以凭喜好添加一些符号如-、*等。

⑤fread--->二进制输入函数;fwrite--->二进制输出函数

size_t fread ( void * ptr , size_t size , size_t count , FILE * stream ) ;

size_t fwrite ( const void * ptr , size_t size , size_t count , FILE * stream ) ;

fread函数用于二进制输入数据到ptr指向的空间中,size为每个元素大小,count为元素个数。fread要求读取count个元素大小为size的数据,如果读出了count个数据,那么fread返回count,如果读出数据个数小于count说明其中数据个数没有count那么多,因此返回读取数据真实个数。fwrite用于将数组中元素以二进制序列形式输出到文件中,同样size为每个元素大小,count为元素个数。

fwrite的使用

在文件中查看时我们会发现,看不到这些二进制序列,那么我们怎么来验证正确性呢?

我们可以通过fread函数调回一个数组中进行打印,看看打印出来的数据是否与原来传入文件的匹配,我们在fread函数的使用中来验证:

fread的使用

我们发现打印出来确实是原来的数组元素,因此验证上面的输出函数fwrite是没有问题的。

Extra---三组输入输出函数的比较

int sprintf ( char * str, const char * format, ... ) ;

int sscanf ( const char * str , const char * format, ...) ;

 sprintf函数用于将格式化数据转换为字符串,存放在str指向的字符串空间中,sscanf用于将存放在str中的字符串转换为格式化数据。

sprintf和sscanf函数的使用

注:getchar/putchar只针对标准输入/输出流stdin/stdout。即使对stdin重定向,getchar针对的也只是stdin。而f系列的输入输出函数都是作用于所有流的。

2.文件的随机读写

1.fseek函数--->对光标位置进行修正

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

stream--->文件流;

offset--->光标距离起始位置的偏移值;

origin有三个值:

        ①SEEK_SET,光标置于起始位置;

        ②SEEK_CUR,光标置于现在位置;

        ③SEEK_END,光标置于文件末尾位置。

2.ftell函数--->求目前光标的偏移值

long int ftell ( FILE * stream ) ;

 ftell函数用来求当前光标位置相对于起始位置的偏移量。

3.rewind函数--->将光标置为起始位置

void rewind ( FILE * stream ) ;

rewind函数用于将光标置为起始位置。

4.fseek、ftell、rewind函数的使用

ftell函数可以求文件中当前光标相对于起始位置的偏移量

fseek函数功能是比较全面的,它可以让我们的光标挪向文件中任意的一个位置,而对于rewind函数而言,其作用就是专门让光标回到起始位置。

四、文本文件和二进制文件

数据文件包括两种类型:文本文件和二进制文件,文本文件即我们输出在文件中数据可见,能看懂的文件;而二进制文件即使用二进制输出的无法在文件中正常显示出来的二进制数据的文件。

数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。

对于文本文件而言,文件中的数据全部是以字符的形式显示的。

字符以ASCII码值形式存储和以二进制形式存储是一样的。

如我们选择将a=10这个数据以两种形式写入文件:

1.输出为二进制文件

我们打开文件无法看懂这个二进制数据,但是我们可以通过VS来打开它查看二进制数据:

我们通过源文件添加现有项,找到data.txt文件添加进去,然后打开方式选择二进制编辑器:

打开后我们能够看到data.txt文件中的二进制数据:

确实显示的是0a 00 00 00。

我们也可以使用fread来读取这个数据:读出来确实为10。

2.输出为文本文件

五、文件读取结束判定

文本文件读取是否结束,对于fgetc而言判断返回值是否为EOF(-1),对于fgets而言判断返回值是否为NULL。

(注:fgetc而言,由于返回值是字符的ASCII码值形式,-1是不对应任何字符的ASCII码值的,因此作为其返回值)

二进制文件读取是否结束,对于fread而言判断返回值是否小于实际要读取的数据个数。

注:feof函数的返回值不能用来判断文件读取是否结束!

feof函数用于判断文件结束的形式--->是读取产生问题而结束,还是读取完毕到文件尾的结束!

int feof ( FILE * stream );

int ferror ( FILE * stream );

feof函数---在文件读取结束后,用于判断文件读取是否是因为遇到结束标志(到文件尾)而结束!

如果是---返回非0值;不是---返回0。

ferror函数---在文件读取结束后,用于判断文件读取是否是因为中间过程产生错误而结束!

如果是---返回非0值;不是---返回0。

我们来写一个文件的拷贝:

其实就是通过输入到变量中存储,再通过该变量输出到另一个文件中的过程。

当然,关闭文件之前需要对文件的读取结束进行判断:

六、文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的。

所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块文件缓冲区。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

我们将数据存储到硬盘上的文件里,中间其实经过了系统调用的过程,即我们只是将数据放入了输入/输出的缓冲区,当缓冲区刷新操作(fflush)或者文件关闭操作(fclose)实施后,操作系统将缓冲区中所有数据进行输入/输出。因此对于一个刚刚输出的数据,它并不是立即被存储在了文件当中的,我们需要在文件关闭操作执行后或者手动刷新缓冲区才能存储数据到文件中。

这里可以得出一个 结论
  因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。 
fflush函数
int fflush ( FILE* stream ) ;
关于fflush函数,用于刷新缓冲区,成功刷新返回0,失败返回EOF。

有关于fflush函数的使用:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值