一、 为什么使用文件?
如果没有文件,那么我们写的程序是存储会在电脑的内存中的,此时我们若是退出程序,那么内存收回,程序的相关数据就丢失了;等到下次再次运行程序,是看不到上次程序的数据的。文件的作用就是将数据进行持久化的保存。
二、什么是文件?
在磁盘(硬盘)上的一些存储数据的存储形式的具象化就是文件。
在程序设计中,我们一般的文件有两种:程序文件和数据文件(按照文件功能来分的)
1.程序文件
程序文件包括源程序文件(后缀为.c)、目标文件(windows环境后缀是.obj)、可执行程序文件(windows环境后缀是.exe)
2.数据文件
数据文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
这里我们讨论的是数据文件,一般我们写代码时所处理的数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。而有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。
3.文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含三部分:文件路径+文件主干名+文件后缀。
例如:c:\code\test\.txt
为了方便起见,文件标识常常被称为文件名
注意有时候文件并没有后缀。
三、二进制文件和文本文件
根据数据的组织形式,数据文件被称为文本文件或者是二进制文件。
数据再内存中以二进制的形式存储,如果不加转换的输出到外村的文件中,就是二进制文件。
如果要求再外存上以ASCLL码形式存储,则需要再存储之前转换。以ASCLL字符的形式存储的文件就是文本文件。
四、文件的打开和关闭
1.流和标准流
流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作方式各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着许多字符的河流。
C语言程序针对文件、画面、键盘的数据输入输出操作都是通过流进行操作的;
一般情况下,我们想要向流里面写数据,或者从流中读取数据,都要先打开流。然后操作。
标准流
此时可能会有疑问,我们在平时写代码的时候,为什么没有打开流的具体操作呢,而是直接写代码呢?那是因为C语言程序在启动的时候,默认打开了3个流:
#stdin-标准输入流,在大多数环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
#stdout-标准输出流,大多数环境中输出到显示器界面,printf函数就是将信息输出到标准输出流中。
#stderr-标准错误流,大多数环境中输出到显示器界面,
这是默认打开的三个标准流,我们使用scanf、printf等函数就可以直接进行输入输出操作。
而它们的类型是:FILE*(通常被称为文件指针)
C语言中,就是通过FILE*的文件指针维护流的各种操作的。
2.文件指针
每个被使用的我呢见都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(例如文件名称、文件状态以及文件当前的位置等)。而这些信息是保存咋一个结构体变量中的。而这个结构体类型由系统固定声明。取名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类型数据的指针变量。可以使pf指向某个文件的文件信息区。通过这个文件信息区就能够访问这个文件。也就是说,通过文件指针变量能够间接找到与它相关联的文件。
3.文件的打开和关闭具体实现
我们要操作文件就要先打开文件,结束时就要关闭文件(就如同喝水,要先打开瓶盖,不喝了自然就要扭紧瓶盖)
ANSI C规定使用fopen函数来打开文件,fclose来关闭文件。
mode表示文件打开的模式,文件的打开模式有很多:
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输出数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向二进制文件尾添加数据 | 建立一个新的文件 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,打开一个文本文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,向文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,建立一个新的二进制文件 | 建立一个新的文件 |
“ab+” | 打开一个二进制文件,在我呢见尾进行读和写 | 建立一个新的文件 |
在这里我们主要使用其中几种打开模式;
现在我们通过一个例子来先简单地看一下是怎么样操作的
这副截图是未进行文件操作之前(Ctrl+o在VS里面快捷打开文件)
#include<stdio.h>
int main()
{
FILE* pf = fopen("crea.txt", "w");
//此部分在介绍文件的顺序读写之后会有相应的代码补充,这也是文件操作的主体部分
fclose(pf);
return 0;
}
现在我们进行文件的只写,那么按照上述表格它会自动创建一个文件
进行只写之后:
可以发现,它自动生成了一个crea文本文件,那么使用“r”一类的模式的时候必须要存在要被进行操作的文件,不然会出错(其实可以这样理解,想要读取文件,首先你得存在文件,否则到哪里读取呢?相反,想要写一个文件,在没有那个文件的时候系统可以给你创建一个文件,因为反正文件的内容是人为输入的,这并不影响操作的进行)
五、文件的顺序读写
文件的顺序读写使用的函数就是我们进行文件操作的主体部分,顺序读写就是读一个或者写一个,文件里面的光标就往后移动一个,就是按照顺序来进行操作;
我们先来简单了解一下相关的顺序读写的函数
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fpringf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入函数 | 文件 |
fwrite | 二进制输出函数 | 文件 |
上面表格中说到的所有输入流一般指的是包含了标准输入流和 其他输入流(例如文件输入流)的流;
所有输出流指包含了标准输出流和其他输出流(例如文件输入流)的流。
输入输出可以这样理解:在我们一般写代码的时候我们可以把包含着代码的程序看作一个对象,把显示器看作一个对象,我们输出就是输出到显示器那个对象上,输入就是输入到程序这个对象上;
此时对于文件来说就是把显示器换成了文件,我们进行读的时候就是把文件的内容输入到程序中,这时候我们程序就相当于多包含了一部分数据(文件中的数据),若是可以打印,我们在程序中可以打印出来,而进行写文件的时候,程序中写的代码就要被输出到从文件中了
具体代码实现:
1.fgetc和fputc
fgetc是对文件进行读取操作,即从文件中一次读取一个字符放在程序代码中;fputc是把程序代码上的一个字符输出到文件中
#include<stdio.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("test.txt", "w");//首先以写文件的方式创建一个文件
if (pf == NULL)
{
perror("fopen");
return 1;//打开失败就终止
}
int C = 'a';
for (C = 'a'; C <= 'z'; C++)
{
fputc(C, pf);
}
fclose(pf);
return 0;
}
未报错
来看我们写的文件
写了一个文件之后,我们就可以从这个文件里面读取数据了;
int main()
{
FILE* pf = fopen("test.txt", "r");//以读的方式来打开一个文件
if (pf == NULL)
{
perror(pf);
return 1;
}
char ret;
while (( ret = fgetc(pf)) != EOF)//,读一个光标就移动到下一个字符位置,遍历读取
{
printf("%c", ret);
}
fclose(pf);
return 0;
}
2.fgets和fputs
对比fgetc和fputc我们可以知道现在要介绍的两个函数都是对于文本行进行操作的
int main()
{
FILE* pf = fopen("test2.txt", "w");
if (pf == NULL)
{
perror(pf);
return 1;
}
const char* str = "abcdefg";
fputs(str, pf);
fclose(pf);
return 0;
}
int main()
{
char ret[10];
FILE* pf = fopen("test2.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fgets(ret, 4, pf);
printf("%s", ret);
fclose(pf);
return 0;
}
fgets是从pf文件流里面拿取数据,拿取多少个取决于中间那个数字参数,但是实际上放到第一个参数也就是数组里面的比你输入的数值少一个,最后一个是'\0'字符;
这是调试过程可以窥见数组中的具体数据;
接下来还有许多内容见文件操作(2) !!