C语言:文件操作详解

创作不易,友友们给个三连吧!!

一、为什么我们需要使用文件

我们在写程序的时候,输入的数据是存储在电脑内存中的,如果程序退出内存回收,相应数据也就丢失了,等再次运行程序,就看不到上次输入的数据了,所以为了能够让我们的数据永久化保存,我们可以使用文件!

二、什么是文件

2.1 文件的分类

在磁盘(硬盘上)的文件就叫做文件,在程序设计中,文件一般分为两种:程序文件、数据文件。

程序文件包括源文件(.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)

数据文件, 因为文件的内容不一定是程序,也可能是程序运行时读写的数据,所以可以理解为程序运行需要从中读取数据的文件,或者输出内容的文件。

本文内容以数据文件为主!!

2.2 终端

**终端:**包括显示器、鼠标、键盘、耳机、麦克风、显示器和摄像头等等。 这些外围设备就被称为终端,负责向主机输入数据的就叫输入终端,比如鼠标、键盘、麦克风、摄像头,负责接收主机输出数据的设备就被称作输出终端,比如显示器、耳机。

以往在C语言程序编写中,我们处理数据的输入和输出都是以终端为对象的,即通过终端的键盘输入数据,并将运行结果显示在显示器上。

但是这样的方式显然不能达到永久保存数据的目的,所以我们需要把信息输出到磁盘的文件中,当我们需要的时候再通过磁盘中的文件将数据读取到内存中去使用!

2.3 文件名

我们为了将程序中输入的数据永久化保存起来,就需要用到文件,而每个文件都要自己的一个唯一的标识,用来方便用户的识别和引用。我们把这种文件标识叫做文件名。

文件名包括了3部分:文件路径+文件名主干+文件后缀

比如c:\code\test.txt

c:\code是文件路径,test是文件主干,.txt是文件后缀

一般想要知道某个文件的路径,即找到这个文件,右击属性,就可以找到他的位置

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

根据数据的组织形式,数据文件又被区分为文本文件二进制文件。

数据在内存中是以二进制形式存储的,所以如果不加以转换输出到磁盘文件中,那该文件就是二进制文件。 如果要求以ASCII码的形式是存储在磁盘文件中,那么在存储前需要将二进制转换成ASCII形势存储,那么该文件就被成为文本文件。

数据在内存中的存储形式:字符一律以ASCII码形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

.txt后缀的文件一般为文本文件的格式,是我们肉眼可以看得懂的,而二进制文件是我们肉眼看不懂得,但是编译器可以读懂。

四、文件的打开和关闭

如何操作文件呢?操作文件的步骤其实和我们喝饮料一样,喝饮料的时候,先打开瓶盖,然后喝,最后关紧瓶盖。而我们想要操作文件,首先是打开文件,然后对文件进行读写操作,最后是关闭文件。

4.1 流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。

也就是说,流其实就是程序底层设计出来的一种东西,他存在于内存中的某一块区域,专门负责对接不同的外部设备,所以我们程序员并不需要关心我们的数据如何和外部设备建立联系,我们只需要关注流!! 所以C程序针对⽂件、画面、键盘等的数据输⼊输出操作都是通过流操作的。

⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。

4.2 标准流

在C程序中,我们通过键盘输入数据,并向屏幕中输出数据,其实都是通过流来完成。

因为在C语言程序启动的时候,默认打开了3个流:

• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。

• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。

• stderr - 标准错误流,大多数环境中输出到显示器界流。perror函数就是将错误的信息输出到标准输出流中

有了这三个流,我们就可以通过scanf/printf/perror函数来进行输入和输出操作。

而流本身作为程序底层开发出来的内容,存在于内存中的某一块区域,C语言中,通过FILE*的文件指针来维护流的各种操作。

stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为⽂件指针。

4.3 文件指针

缓冲⽂件系统中,关键的概念是"⽂件类型指针",简称"⽂件指针"。

每一个被使用的文件都在内存中开辟了一个相应的文件区,专门用来存放文件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系 统声明的,取名FILE。

例如,VS2013编译环境提供的 stdio.h 头⽂件中有以下的⽂件类型申明:

cpp 复制代码
struct _iobuf {
 char *_ptr;
 int _cnt;
 char *_base;
 int _flag;
 int _file;
 int _charbuf;
 int _bufsiz;
 char *_tmpfname;
 };
typedef struct _iobuf FILE;

不同的C编译器的FLIE类型包含的内容不完全相同,但是大同小异,理论上来说,越高级的编译器,封装得会越完善,所以越不容易观察到这些细节。

也就是说,我们每当打开一个文件,系统就会根据文件的情况况⾃动创建⼀个FILE结构的变量,并填充其中的信息,使⽤者不必关⼼细节。

⼀般都是通过⼀个FILE*的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。

如图,首先我们打开文件,其实就是向内存中申请了一块空间,该空间的类型是FILE类型,我们把它称为文本信息区 ,该文本信息区首先会根据原有的文本信息进行填充,之后再由操作者利用FILE*指针对这个文本信息区进行读或者写的操作,我们程序员不需要知道文件(data.txt)是如何和这个用结构体变量存储的文件信息区建立联系的,我们只需要知道我们可以用FILE*来操作文件,该文本信息区就相当于"流",我们通过"流"和外部设备建立联系。

4.4 文件的打开和关闭

文件在读写之前要打开文件,使用结束后应关闭文件。

而我们在打开文件的同时,都会返回一个FILE*类型的指针,来帮助我们建立和该文件的联系,我们可以通过这个指针来进行读写的操作。

ANSIC 规定使⽤ fopen 函数来打开文件, fclose 来关闭文件。

4.4.1 fopen

复制代码
const char * filename:传需要打开的文件的文件名
const char * mode :传该文件的打开方式
Open file:作用是打开文件

返回类型:FILE*指针,我们可以通过该指针完成对文件的读写操作(相当于通过该指针打开了一个文件流)

注:文件可能会打开失败,如果FILE*指针成功返回,说明打开成功,如果返回的是NULL,则打开失败!

4.4.2 fclose

复制代码
FILE * stream:传维护该文件流的指针
Close file:作用是关闭文件

4.4.3 打开模式

fopen函数中的参数mode表示文件的打开模式,下面都是文件的打开模式。

4.4.3.1 w和a的区别

w和a都可以写,但是如果我们打开的文件原来保存着一些数据,用w的话,会先清空掉文件的数据再进行写的操作,而用a则是直接在这些数据的后面进行追加

4.4.3.2 r+、w+、a+的区别

r+是覆盖写,即在原来文件数据的基础上从头开始覆盖写入

w+是清空写,即原来文件数据会被清空再开始写

a+是追加写,即在原本文件数据的基础上在后面追加写入

4.4.4 文件的存储路径

1.相对路径

cpp 复制代码
int main()
{
	FILE* pf;
	//打开文件
	pf = fopen("./../data.txt", "w");//桌面生成
	if (pf == NULL)//如果打开失败
	{
		perror("fopen");
		return 1;
	}
	//文件操作
	 
	//关闭文件
	fclose(pf);
	return 0;
}

./ 当前目录

../ 上一级目录

/ 根目录

2.绝对路径

cpp 复制代码
int main()
{
	FILE* pf;
	//打开文件
	pf = fopen("C:\\Users\\lenovo\\Desktop\\data.txt", "w");//桌面生成
	if (pf == NULL)//如果打开失败
	{
		perror("fopen");
		return 1;
	}
	//文件操作
	 
	//关闭文件
	fclose(pf);
	return 0;
}

五、文件的顺序读写

顺序读写函数介绍:

上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输⼊流和其他输⼊流(如⽂件输⼊流);所有输出流⼀ 般指适⽤于标准输出流和其他输出流(如⽂件输出流)。

5.1 fputc

复制代码
int character :传需要输出的字符
FILE * stream:传需要输出的流
Write character to stream:作用是将字符写入流中

**放置的过程其实跟光标有关,**最开始光标是在最前面,输出a的时候光标移到b的后面了,所以再输出的b就是在a的后面,以此类推。

fpuc可适用于所有输出流,所以也可以使用标准输出流

5.2 fgetc

复制代码
FILE * stream:传需要读取的流
Get character from stream:作用是从流中获取字符

注:如果读取失败,会返回EOF;

原本文件里有abcd,我们调用了四次fgetc将他读取出来并打印在屏幕上。

与fputc一样,fgetc也适用于标准输入流,所以也可以从键盘读取

但是因为读取也是按照光标去逐个读取的,所以如果加上空格,就会有的数据读取不到

写一个代码,完成将data.txt文件的内容,拷贝一份生成data1.txt文件

cpp 复制代码
int main()
{
	//打开文件
	FILE* pfread = fopen("data.txt", "r");
	if (pfread == NULL)//如果打开失败
	{
		perror("fopen->data.txt");
		return 1;
	}
	FILE* pfwrite = fopen("data1.txt", "w");
	if (pfwrite == NULL)//如果打开失败
	{
		//第二次打开文件,如果打开失败,返回前必须把前一个文件关闭掉并置空
		fclose(pfread);
		pfread = NULL;
		perror("open->data1.txt");
		return 1;
	}
	//文件操作
	//数据的拷贝
	int ch = 0;
    while ((ch = fgetc(pfread)) != EOF)//循环读取直到读取不到
	{
		fputc(ch, pfwrite);
	}
//循环结束后可以做到完全拷贝
	//关闭文件
	fclose(pfread);
	fclose(pfwrite);
	return 0;
}

5.3 fputs

复制代码
 const char * str:传需要输出的字符串
 FILE * stream:传需要输出的流
Write string to stream:作用是将字符串写入流中

5.4 fgets

复制代码
char * str:传字符串存放的地址(一般是一个字符数组)
int num:传从流中读取的最大字符串长度(由于字符串末尾有一个\0,所以默认其实最多读取num-1个)
FILE * stream:传需要读取的流
Get string from stream:作用是从流中获取字符串

我们可以看到,由于字符串的末尾要默认跟一个\0,所以最多只能读取num-1个到arr数组中

5.5 fprintf

复制代码
FILE * stream:传需要输出的流
const char * format:传格式化字符串,用于指定输出的格式。
...:可变参数列表
Write formatted data to stream:作用是将一块格式化的数据输出到流中

5.6 fscanf

复制代码
FILE * stream:传需要读取的流
const char * format:传格式化字符串,用于指定输入的格式。
...:可变参数列表
Read formatted data from stream:作用是从流中读取一块格式化的数据

注:%f打印时可以控制格式

5.7 fwrite

复制代码
const void * ptr:传要写入的数据的指针
size_t size:传每个数据的大小
size_t count:传要写入数据项的数量
FILE * stream:传需要输出的流
Write block of data to stream:作用是将整块的数据输出到流中

因为是二进制形式,所以我们看不懂,但是编译器可以看懂,所以要使用fwrite就要配套使用fread!!

注:无论是什么文件,字符都是以ascii码值存储的,所以打印出来都一样!!

5.8 fread

const void * ptr:传要写入的数据的指针

size_t size:传每个数据的大小

size_t count:传要写入数据项的数量

FILE * stream:传需要输入的流
Read block of data to stream:作用是将整块的数据输入到流中

将fwrite输出到文件里的数据通过fread成功读取回来。

六、一些函数的对比

6.1 scanf/fscanf/sscanf

复制代码
const char * s:用于读取的字符串
const char * format:格式化的字符串,用于输入指定的格式
 ...:可变参数列表
Read formatted data from string:在字符串中读取一个格式化的数据

对比一下参数,共同点都是读取一个格式化的数据,不同的是scanf是默认的标准输入流,从键盘上读取,而fscanf是所有的标准输入流都可以,参数可以传文件流也可以跟scanf一样传stdin(标准输入流),而sscanf是从一个字符串中读取。

6.2 printf/fprintf/sprintf

const char * s:用于输出的字符串

const char * format:格式化的字符串,用于输出指定的格式

...:可变参数列表
Write formatted data from string:把一个格式化的数据转换成字符串

对比一下参数,共同点都是输出一个格式化的数据,不同的是printf是默认的标准输出流,输出到屏幕上,而fprintf是所有的标准输出流都可以,参数可以传文件流也可以跟printf一样传stdout(标准输入流),而sprintf是输出到一个字符串中。

6.3 总结

scanf:针对标准输入(键盘)的格式化输入函数

printf:针对标准输出(屏幕)的格式化输出函数

fscanf:针对所有输入流的格式化输入函数

fprintf:针对所有输出流的格式化输入函数

sscanf:从一个字符串中读取格式化的数据

sprintf:把一个格式化的数据转换成字符串

七、文件的随机读写

顺序读写,顾名思义就是按顺序读写,本质是根据光标的移动按顺序读写,而光标最开始就是在文件的最前面。

这里的随机,并不是类型随机数的那种随机,而是我们想在文件哪个位置读取就在哪个位置读取,想在文件哪个位置输出就在哪个位置输出,那么我们想要实现文件的随机读写,就要想办法去操控光标

而由于流是通过FILE*指针维护的,所以本质上,也是通过操控指针来达到操控光标的目的。

7.1 fseek

复制代码
FILE * stream:需要操作的流
long int offset: 偏移量
int origin:起始位置

这个origin可以传3个参数:

SEEK_SET:起始位置

SEEK_CUR:当前光标的位置

SEEK_END:文件的末尾

Reposition stream position indicator:作用是重新定位光标位置指示器( 根据⽂件指针的位置和偏移量来定位⽂件指针**)**

7.2 ftell

复制代码
 FILE * stream:需要操作的流
Get current position in stream:作用是获取流中的当前位置(返回⽂件指针相对于起始位置的偏移量)

利用fseek和ftell来判断文件有多少个字节(fseek操控光标到尾部,再由ftell返回长度)

7.3 rewind

复制代码
 FILE * stream:需要操作的流
Set position of stream to the beginning:作用是将流的位置设置为开头(即让文件指针返回到文件的起始位置)

八、文件读取结束的判定

8.1 feof

复制代码
 FILE * stream:需要操作的流
Check end-of-file indicator:作用是检查文件结束指示符(即在文件读取结束后,判断是否是因为遇到文件末尾而结束)

8.2 ferror

复制代码
 FILE * stream:需要操作的流
Check error indicator:作用是检查错误指示灯(即检查操作目标文件的过程中是否出现了错误)

8.3 判断文本文件是否读取结束的方法

⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )

例如:

• fgetc 判断是否为 EOF

• fgets 判断返回值是否为 NULL .

8.4 判断二进制文件是否读取结束的方法

⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。

例如:

• fread判断返回值是否小于实际要读的个数。

8.5 判断文件是因为什么原因结束的

8.5.1 文本文件

8.5.2 二进制文件

8.6 putchar

复制代码
int character:要输出的字符
Write character to stdout:作用是将字符写到屏幕上

8.7 getchar

Get character from stdin:作用是从键盘获取字符

九、文件缓冲区

ANSIC 标准采⽤"缓冲⽂件系统"处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为 程序中每⼀个正在使⽤的⽂件开辟⼀块"⽂件缓冲区"。**从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。**缓冲区的⼤⼩根据C编译系统决定的。

这⾥可以得出⼀个结论:

因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件。 如果不做,可能导致读写⽂件的问题(数据丢失)。

相关推荐
SomeB1oody1 分钟前
【Rust自学】7.4. use关键字 Pt.2 :重导入与换国内镜像源教程
开发语言·后端·rust
新手小袁_J4 分钟前
No Python at ‘C:\Users\MI\AppData\Local\Programs\Python\Python39\python.exe‘
开发语言·python·error·no python
stormjun4 分钟前
基于 Python Django 的二手电子设备交易平台(附源码,文档)
开发语言·python·django·二手电子设备·电子设备售卖·电子设备交易
新知图书6 分钟前
Rust编程与项目实战-箱
开发语言·后端·rust
SomeB1oody10 分钟前
【Rust自学】7.3. use关键字 Pt.1:use的使用与as关键字
开发语言·后端·rust
DARLING Zero two♡17 分钟前
【优选算法】Sliding-Chakra:滑动窗口的算法流(上)
java·开发语言·数据结构·c++·算法
hjxxlsx20 分钟前
二维数组综合
c++·算法
君败红颜23 分钟前
Apache Commons Pool2—Java对象池的利器
java·开发语言·apache
意疏32 分钟前
JDK动态代理、Cglib动态代理及Spring AOP
java·开发语言·spring
程序员_三木32 分钟前
使用 Three.js 创建圣诞树场景
开发语言·前端·javascript·ecmascript·three