一、文件存在的意义
① 文件的定义是什么?
文件是以单个名称在计算机上存储的信息集合。文件可以是文本文档、图片、程序等等。文件通常具有三个字母的文件扩展名,用于指示文件类型(例如,图片文件常常以 JPEG 格式保存并且文件扩展名为 .jpg)(文件的范畴很广,比如运行的程序,杀毒的软件之类的,都叫做文件。)
(总之:所谓“文件”,就是在我们的电脑中,以实现某种功能、或某个软件的部分功能为目的而定义的一个单位。)
② 文件的作用是什么?
首先我们要知道,在通常情况下,我们在编译器中编写的代码,每次关闭编译器再打开,上次使用的代码总是仍然存在,这就是因为电脑将它们存储到了"文件"中,而如果没有文件,就无法保存信息,导致数据丢失。
这就引出了文件的作用:文件能够将数据进行持久化的保存。
③ 什么是文件?
在程序设计中,一般有两种文件:程序文件、数据文件(从文件功能的角度来分类的)。
1. 程序文件:程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows 环境后缀为.exe)。
2. 数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
在之前我们所了解到的,处理数据的方式都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。而有时当我们将运行结果显示到显示器上时,其实过程是将信息输出到磁盘上,需要时再调出(读取到内存中)使用。
让我们看一段最最简单的代码作为例子:
int main(void)
{
printf("hello world!!!\n");
return 0;
}
运行代码后会将结果输出到显示器,并且还会显示出其代码路径:
然后我们可以通过代码路径寻找到这个程序:
这就是一个"程序文件"。
而当我们返回刚刚的路径"C",会发现里面还有一个"debug":
他们也都属于"程序文件"。
而如果此时我们再此路径下,我们再右键创建出一个文件,那么这个文件就可被称为"数据文件"。
④ 文件名
文件要有独特的标识,便于对文件的查找与存储。
文件名的三个部分:文件路径 + 文件名主干 + 文件后缀
比如:C:\Users\10720\data.txt
为了方便起见,文件标识通常被称为 文件名 。
⑤ 二进制文件与文本文件
文件是用来存储数据的,而根据数据的形式,还要细分为:文本文件 或 二进制文件。
1. 二进制文件:顾名思义,就是将数据在内存中以二进制的形式存储,并且不加以任何的转换输出到外存文件中。
2. 文本文件:将数据转换成ASCII码的形式后,再输出到外存文件中。
接下来让我们看一下,这两种文件的存储形式:
比如此时,我们想将一个整数20000在内存中存储,然后我们想将它分别的在"二进制文件"与"文本文件"中存储,那么会是怎样的形式呢:
我们用代码来运行一下看看:
int main()
{
int a = 20000;
FILE* pf = fopen("data.txt", "wb");
fwrite(&a, 4, 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
(这个代码看不懂不要紧,我们马上就会讲到,此时我们只需要知道这段代码的作用是将20000的二进制形式写入文件中就好)
此时我们就将20000写入了"data.txt"中(二进制文件)。
接下来让我们选择打开方式,对此文件的内容进行查看:
我们选择"二进制编译器":
最后读取到的内容为:
这是因为为了节省空间,存储时将2进制的格式又改成了16进制存储:
二、文件的打开与关闭
① 流和标准流
💧流:在进行代码的编写时,我们会创造出很多不同的数据,而这些数据有时需要输出到各种外部设备,有时还需要从外部设备获取数据,而不同外部设备下,输入与输出也都有不同,而为了时输入输出更加方便,便有了"流"的概念~我们可以把"流"想象成一个流淌着字符的河~
有了流,也就有了标准流的概念:
而既然有了流,就说明我们平常编写代码时,肯定不是直接就实现了数据的输入输出,而是有着潜在的"流"在暗中为我们的输入输出推波助澜~
在C语言程序启动时,默认打开了三个流:
• stdin-标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
• stdout-标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出 流中。
• stderr-标准错误流,大多数环境中输出到显示器界面。
也就是因为这三个流,才能够使我们使用输入(scanf)输出(printf)函数时直接就能输入输出。
stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为文件指针。
② 文件指针
和之前我们学习过的各种指针一样,函数指针是一个指向函数的指针变量,数组指针是一个指向数组的指针变量,而文件指针,就是一个指向文件的指针变量。
我们知道,文件的作用是用来使数据持久化的保存。那么当我们想要创建一个文件的时候,我们需要对其命名,并且需要文件能够存储对应的路径,而存储这些还需要它能够接收各种数据。
而怎么让文件拥有这些功能呢?其实每次使用文件时,都会在内存中开辟一个对应的文件信息区。
这个文件信息区用来存放文件的相关信息,这些信息会被存储在一个结构体变量中,此结构体类型是由系统声明的,取名 FILE。
例如由 VS2013 编译环境提供的 stdio.h 头文件中有以下的文件类型声明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
需要注意的是:
Ⅰ 在不同的编译器下,FILE的结构中所包含的内容并不是完全相同的,但没有太大出入。
Ⅱ 当每次打开一个文件时,系统都会随着相应的文件状况创建一个FILE结构变量,并为其放置信息。
Ⅲ 通常是通过一个FILE指针来管理对应的FILE结构变量。
那么让我们看看如何来创建一个 FILE* 的指针变量:
FILE* pf;
定义了一个文件指针型变量pf,用于指向FILE类型数据,如图:
③ 文件的打开与关闭
当我们进行对文件的操作时,对文件的打开和关闭是必不可少的操作。
在我们编写程序,想要实现对文件的读写前,我们需要先打开一个文件,我们可以看到fopen的参数:filename 是文件名,其代表的就是想要进行操作的对应文件(有时文件并不在程序中,我们可以将此处替换成此文件的绝对路径)。
mode 代表的是文件的打开模式,文件的打开模式:
(文件打开成功时,返回文件起始位置的文件指针;文件打开失败时,返回空指针NULL!!!)
文件使用方式 | 含义 | 如指定文件不存在 |
" r "(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
" w "(只写) | 为了输出数据,打开一个文本文件 | 建立一个新文件 |
" a "(追加) | 像文本文件尾添加数据 | 建立一个新文件 |
" rb "(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
" wb "(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新文件 |
" ab "(追加) | 向一个二进制文件尾添加数据 | 出错 |
" r+ "(读写) | 为了读和写,打开一个文本文件 | 出错 |
" w+ "(读写) | 为了读和写,建立一个新的文件 | 建立一个新文件 |
" a+ "(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新文件 |
" rb+ "(读写) | 为了读和写,打开一个二进制文件 | 出错 |
" wb+ "(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新文件 |
" ab+ "(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新文件 |
而既然我们打开了一个文件,相应的就需要关闭文件:
此函数的作用就是用于关闭文件。
FILE* stream 指的是想要关闭的文件。
(关闭文件后,需要将pf置空,否则pf会变成危险的野指针!!!)
(就像之前学习动态内存管理时,使用free释放内存后,需要再置空一次~)
那么既然了解了文件的打开与关闭,现在让我们练习一下吧:
int main()
{
FILE* pf = fopen("data1.txt", "w");
//检查是否打开失败
if (pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
//防止pf变成野指针
pf = NULL;
}
在我们运行此代码之前,我们的文件中是没有"data1.txt"的,而当我们运行代码后,再进行查看,会发现文件中出现了"data1.txt"。
这是因为 " w " 在查找不到目标文件时,就会创建一个新文件,并且命名为目标文件的名字。
而当我们将此文件删除,再使用 " r " 来读取该文件会发生什么呢:
没错,就是报错(找不到该文件)。
(如果我们将文件 "data1.txt" 存入其他的文件路径中,那么对 "data1.txt" 的读取是否能够成功呢?)
我们可以看到,这种情况下是无法找到该文件的,那么这时我们就不能找到这个文件了吗?答案是,可以找到~因为此时我们输入的文件名并不完整,所以它找不到另一个文件路径中的该文件,此时我们可以将全部文件名写入:"data1.txt"—>"D:\\data1.txt"
(正常是一个\,写\\会防止\与后面字符结合)
此时我们可以看到,再次进行该文件的查找,就能够找到了~
三、文件的顺序读写
什么是文件的顺序读写?
顾名思义,顺序读写就是一种逐个或逐行地进行输出输入操作读取文件内容的方法。
当我们对文件进行操作时,基本流程大概为:
当我们使用顺序读写的方式去对文件进行操作时:
① 顺序读取
此时我们对文件进行读取,会从文件的开头开始读取,若是读取字符便是一个一个字符的读取,若是读取字符串便是一个一个字符串读取,总之是按照从头到尾的顺序,读取完一个数据后,指针自动向后移动,读取下一个数据。
② 顺序写入
此时我们对文件进行写入,会从文件的开头开始写入,每写入一个数据后,写入指针会自动指向下一个位置,从而按照从头到尾的顺序将数据添加到文件中。
四、文件的随机读写
顾名思义,文件的随机读写与顺序读写的差别在于:C语言中的随机读写通常指的是,使用随机的形式访问文件中的数据,而并不是按照顺序的去进行访问。随机读写具有灵活性和多变性,适合对文件中的数据进行使用。
那么关于C语言文件的基础知识就为大家分享到这里了,如果有哪里写的不够清楚或者不够详细的,还请各位多多指出,我也会吸取教训多多改正的,那么我们下一期再见啦~
(关于文件的知识有很多!!!在这一篇文章中我们并未讲解完全,这篇文章只是为下一篇文章进行铺垫的基础知识,帮助我们了解文件的各种相关知识,而在下一篇文章中,我们将详细的对文件各种读写函数进行讲解,并且以此篇文章为基础,对文件的相关知识进行更深度的研究~)