文章目录
前言
之前我们输入的数据在程序关闭后就销毁不存在了,比如我们实现一个通讯录,每次关闭这个系统时里面的数据都被清空,这样就不能完整实现通讯录的功能。学完本章文件操作之后,我们可以将我们想要保存的数据内容永久保存到电脑文件中,下次重新运行程序,可以读取之前保存的数据,做到数据的持久化。
一、文件的定义
什么是文件?
电脑文件,也称为计算机文件,是存储在计算机的长期或临时存储设备上的数据单元。
电脑文件通常具有标识其内容的名称和扩展名,如文本文档、图片、程序等。文件是操作系统管理信息的基本单位,它们可以包含文本、图像、音频、视频等多种形式的数据。文件扩展名通常用于指示文件类型,例如,JPEG图片文件的扩展名为“.jpg”。
文件名:
文件名就是我们给文件取的名字,方便我们区分不同的文件而人工设定的一个名称。
一个完整的文件名一般都由文件主名和扩展名组成,二者之间使用小圆点隔开,例如test.txt 、work.doc等。
常见的文件扩展名(后缀)有:txt、doc、docx、xlsx、jpg。
文件路径: 文件在电脑中的存放位置。文件路径又分为绝对路径和相对路径。
绝对路径:文件的完整路径。
相对路径:相对于当前文件的路径。
在程序设计中,我们按文件的功能分为两种:程序文件和数据文件
程序文件:
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
数据文件又分为文本文件和二进制文件:
文本文件:数据在内存中以二进制的形式存储。
二进制文件:以ASCII字符的形式存储的文件
一个数据在内存中的存储方式:字符均以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
本篇我们主要介绍的就是数据文件的读取操作。
二、文件的使用
文件的操作一般分为三步:打开文件、读或写(使用文件)、关闭文件。
1.文件指针
我们对文件进行操作就需要用到文件指针 FILE*(文件类型指针)。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的,该结构体类型声名为FILE。
FILE* pf;
定义pf是一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文件信息区,就能够访问该文件。
通过文件指针变量能够找到与它关联的文件。
2.文件的打开和使用
这两个函数的头文件:stdio.h
fopen()——文件打开函数
FILE* fopen(const char* filename, const char* mode);
打开成功时返回这个文件打开后在内存中的文件信息区结构体的地址;打开失败时返会NULL
fclose()——文件关闭函数
int fclose(FILE * stream);
返回值类型为int,如果关闭成功,返回0;关闭失败,返回EOF。
简单使用:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");//相对路径
//FILE* pf = fopen("D:\\code\\study\\test_4_5\\test.txt", "w");//绝对路径
if (NULL == pf)//判断文件是否打开成功
{
perror("fopen");//打开失败则打印错误信息 并结束程序
return 1;
}
//...读或写文件操作
//
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
文件的打开方式有:
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
上述代码是以"w"(只写)
的方式打开文件,如果文件夹下没有该文件,系统则会自动新建。如果存在该文件,则会将里面的内容清空。
如果我们将上面代码的文件打开方式改为"r"(只读)
,并且该文件下没有test.txt文件(可以删掉)。
结果会怎样呢?
会出错,perror打印错误信息:找不到该文件。
总结:
- 文件打开使用完之后要记得关闭文件,并将文件指针置为NULL(好习惯),防止野指针;
- 如果以绝对路径打开文件,反斜杠\ 前面要再加一个 \,防止形成转义字符造成出错。
三、文件的顺序读写
常用到的函数:
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputc | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
接下来我们一个一个介绍用法
fgetc和fputc
头文件:stdio.h
fgetc()——单个字符输入(读取)
int fgetc ( FILE * stream );
功能:从stream流中读取一个字符。
读取成功返回读取字符的ASCII值;失败则返回EOF。
fputc()——单个字符输出(写入)
int fputc ( int character, FILE * stream );
功能:将character输出到stream中
输出成功返回读取字符的ASCII值;失败则返回EOF。
在C语言中,在程序开始运行时,会默认打开以下三个流,都是FILE* 类型
标准输入流:stdin–>键盘
标准输出流:stdout–>屏幕
标准错误流:stderr–>屏幕
平时我们在使用 scanf, printf 的时候,也都用到流,所以我们可以直接使用scanf和printf。
scanf: Read formatted data from the standard input stream 从标准输入流中读取数据、
printf: Print formatted output to the standard outputstream 打印数据到标准输出流中
比如我们用fgetc
和fputc
通过键盘输入一个字符,再通过屏幕打印出来。
如果对象变成文件呢?向文件中读取字符,输出字符。只需要将参数中的标准流改为文件指针即可。
从文件中读取数据,输出到屏幕:
从键盘输入数据,输出到文件中:
注意:
以“w”方式打开文件,若没有文件则会创建一个新文件;若已存在该文件,打开文件时会把原来的内容销毁掉。
(可以先在文件中保存一些数据,然后以“w”方式打开文件,什么也不做,会发现文件内容被清空。)
fgets和fputs
前面的两个函数是针对单个字符的,而这两个函数是针对字符串的输入输出。
头文件:stdio.h
fgets()——字符串输入(读取)
char * fgets ( char * str, int num, FILE * stream );
功能:从stream中读取num-1个字符,输入到str中。
第二个参数是读取字符串的长度,但是只读取num-1个字符,最后一个字符是’\0’。
读取成功返回str起始地址,失败则返回NULL。
注意:
fgets每次只能读一行数据。如果需要读取文本多行数据,需要多次读取。
fputs()——字符串输出(写入)
int fputs ( const char * str, FILE * stream );
功能:将str输出到stream中。
输出成功返回一个非负值,失败则返回EOF。
注意:
fputs还可以读取空格、制表符、换行符等。
fwrite和fread
头文件:stdio.h
这两个函数是以二进制的形式进行输入输出的。
fwrite()——二进制输出(写入)
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
功能:将ptr指向空间的内容中的count个元素输出到stream中,每个元素大小为size。
返回成功写入的元素个数,如果这个数字与count不同,则写入错误;size或count为0则返回0。
注意:
二进制写入是"wb"方式,且输出到文件中的数据是以二进制的方式存储,所以我们看到的是乱码。
fread()——二进制输入(读取)
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
功能:从stream中读取count个元素输入到ptr所指向的空间,每个元素大小为size。
读取成功返回读取元素的个数,如果这个数字与count不同,则读取错误;size或count为0则返回0。
fscanf和fprintf
头文件:stdio.h
fscanf()——格式化输入(读取)
int fscanf ( FILE * stream, const char * format, ... );
fprintf()——格式化输出(写入)
int fprintf ( FILE * stream, const char * format, ... );
这两个函数的用法跟scanf和printf差不多,只是多了一个流。后面的省略号…表示可变参数列表。
比如说,从键盘输入数据,再输出到屏幕。
比如我们将数据格式化输出到文件中:
将文件中的数据格式化的读取:
注意:
fscanf的读取和scanf一样,无法读取空格、水平制表符、换行等。
sscanf和sprintf
头文件:stdio.h
sscanf()函数
int sscanf ( const char * s, const char * format, ...);
功能:解析并转换字符串。从一个字符串中,还原出一个格式化的数据。
sscanf还可以将字符串转换为整数形式(类似atoi函数)
sprintf()函数
int sprintf ( char * str, const char * format, ... );
功能:把格式化的数据,转换成一个字符串。
注意:
sscanf和sprintf不能对FILE*流的文件进行操作,针对的是字符串。
总结
1.以上函数所在头文件都是:stdio.h
2.这些函数(不包括sscanf和sprintf)在读写数据的时候,每读或写一个数据,标识符就会移动一个单位的长度。所以这些函数只能实现数据的顺序读写。
3.fgets读取的是num-1个字符,最后一个是’\0’,所以如果读取一行字符串,num应该大于等于该字符串长度+1。
4.对比scanf和printf,fscanf和fprintf,sscanf和sprintf这三组函数:
scanf:从键盘上读取格式化的数据
printf:将数据输出到屏幕上
fscanf:针对所有输入流的格式化的输入函数
fprintf:针对所有输出流的格式化的输出函数
sscanf:从一个字符串中,还原出格式化的数据
sprintf:把格式化的数据,转换成字符串
四、文件的随机读写
所谓随机读写,就是我们可以指定想要读写的文件位置。
以下三个函数的头文件:stdio.h
fseek()函数
int fseek ( FILE * stream, long int offset, int origin );
功能:根据文件指针的位置和偏移量来定位文件指针,也就是修改文件光标位置。
第二个参数表示偏移量:
>0:表示向后偏移 =0:不偏移 <0:表示向前偏移
第三个参数表示偏移的起始位置
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件结尾
ftell()函数
long int ftell ( FILE * stream );
功能:返回文件指针相对于起始位置的偏移量。
rewind()函数
void rewind ( FILE * stream );
功能:让文件指针的位置回到文件的起始位置。
rewind(pf);
这一条语句即可让文件指针回到文件的起始位置。
五、文件读取结束判定
我们在使用读取文件的函数时,可以通过其返回值来判断对文件的读取是否结束。
比如:
- fgetc读取结束返回EOF;
- fgets读取结束返回NULL;
- fread读取结束返回则一个小于实际要读的元素个数的数字。
注意:读取结束有两种原因:一种是读取到文件末尾而结束;一种是读取失败而结束。
判断是哪种原因需要用到ferror和feof函数。
feof()函数
int feof ( FILE * stream );
功能:遇到文件末尾而结束读取时该函数返回非0值,其他原因返回0。
注:不能用feof函数的返回值来判断文件是否结束。
EOF:为End Of File的缩写。 通常在文本的最后存在此字符表示资料结束。
文档的结尾都有一个隐藏字符”EOF”,当程序读取它的时候,就会知道文件已经到达结尾。
EOF的值通常为 -1,但它依系统有所不同。
feof()并不是通过读取到文件的EOF来评判这个文件是否为空。
feof()的原理:
光标后面的位置如果有字符就返回0;如果没有,返回非0。它并不会读取相关信息,只是查看光标后是否还有内容。
如果按照下面的代码,文件中无论是否有内容,都会被判断为“文件不为空”。这是错误的用法。
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
if (feof(pf))
printf("文件为空\n");
else
printf("文件不为空\n");
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
分析: 对于一个空文件来说,当程序打开它的时候,它的光标会停在文件的开头,文件中什么内容都没有,但是存有一个EOF。当程序打开文件,并直接调用feof()时,因为光标后面有一个EOF,所以返回0。无论是空文件,还是存有信息的文件,当文件被打开,光标都处于默认的开头位置,光标后都有信息(EOF或是其他字符)。
正确用法: 先使用getc(),从文件中读取一个字符,让光标向后移动一个字符。这时空文件的光标就会移动到EOF的后面。
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
fgetc(pf);
if (feof(pf))
printf("文件为空\n");
else
printf("文件不为空\n");
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
ferror()函数
int ferror ( FILE * stream );
功能:有错误时,返回一个非0值;无错误时返回0。
注意:
1.对同一个文件,每一次调用输入输出函数,均产生一个新的ferror函数值。因此,应当在调用一个输入输出函数后立即检查ferror函数的值,否则信息会丢失。
2.在执行fopen函数时,ferror函数的初始值自动置为0。
总结:
- 文件读取结束返回EOF(fgetc)或者NULL(fgets)或者小于实际读取个数(fread)时,不能判断是遇到文件末尾结束,还是读取失败结束。
- feof()函数用来判断是否因为遇到文件末尾结束;ferror()函数用来判断是否因为读取失败而结束。
六、文件缓冲区
缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上;如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓
冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
比如:我们从键盘输入一个字符串,输出到文件中,放在while(1)循环中。发现输入之后,打开文件结果是空的。
刷新文件缓冲区的三种方法:
1.满刷新:缓冲区放满数据之后就会强制刷新缓冲区。
2.使用fflush函数强制刷新缓冲区。
3.关闭刷新:函数结束,或者关闭文件的时候会刷新缓冲区数据。
使用fflush()强制刷新
fflush函数:将缓冲区中未写入磁盘中的数据写入磁盘中的文件中 ,但不推荐频繁大量调用该函数
缺点:读写磁盘速度很慢 , 浪费性能 ,浪费时间;影响磁盘寿命。
调用 fclose 关闭文件函数或者函数结束返回
调用fclose函数关闭文件时会刷新缓冲区;
调用函数结束返回时也会刷新缓冲区(比如:main函数结束return 0)
总结: 因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文
件。否则可能会导致读写文件的问题。
文件操作的内容总结完毕,关于C语言的笔记内容至此就完结撒花啦。
接下来准备整理数据结构的相关内容了,如果对您有用的话可以三连支持。
最后,感谢您的观看和支持!