前言:哈喽小伙伴们好久不见,国庆假期已经结束,接下来我们还是要马不停蹄的投入到学习当中,只有尽快调整状态回归学习,才能弯道超车。
今天我们一起来学习C语言——文件操作。
本篇文章讲到的所有函数均需要头文件#include
我们电脑上磁盘中的各种文件就是我们今天所要讲的文件。
对于我们程序猿来说,从文件的功能角度来分析,我们一般所使用的文件有两种:
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件
那么本篇文章我们讲述的便是数据文件。
一个文件要有一个唯一的文件标识,以便于识别和辨认。
文件名包含三部分:文件路径 + 文件名主干 + 文件后缀
例如:c:\project\test.txt
c:\project 表示文件的当前路径
test 是文件名的主干
txt 是文件的后缀
为了方便,我们就将文件标识称为文件名。
前边我们已经学习过了如何制作简易的通讯录,并讲解了如何通过动态内存函数来实现一个大小可变的通讯录。
但是现在我们又有问题了,因为我们实现的通讯录并不能够真正的保存信息。
当程序运行起来时,我们所输入的数据被保存在内存中,但是当我们退出程序时这些数据就会自动被内存所释放。
所以如果想要保存这些数据,以便于我们下次打开通讯录时这些数据依然存在,就要用到文件了。
对于文件的相关使用,我们有文件指针这样一个定义。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态以及文件当前的位置等)。
这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名FILE。
同俗的来说,就是编译器系统内部已经帮你声明了一个结构体类型,这个结构体类型就被命名为文件,当你要对文件进行操作时,只需要用到FILE这个类型名就行,就类似于库函数的使用。
那么想要真正实现对文件的操作,就需要一个FILE类型的指针:
FILE* pf;//文件指针变量
通过这个指针来找到文件的信息区,并进行各种操作。
我们想要操作一个文件,必然就需要先打开它。
那么对于文件的打开,我们有一个专门的函数:
该函数有两个参数:
filename 是文件名,字符串形式输入
mode 是文件的打开方式,字符串形式输入
此外,这个函数会返回一个FILE*类型的指针来指向我们打开的这个文件,这样便建立起了文件与指针的关系。
既然有打开,那就一定会有关闭,关闭文件同样是通过一个函数:
这个函数只有一个参数:
stream 文件操作指针
通过指向文件的操作指针来关闭文件。
那么文件都有哪些打开方式呢???
这张表包含了所有的文件打开方式,下面我们就通过实际应用来讲解文件到底如何使用:
#include
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//操作文件
//.....
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
用fopen函数通过“w”(只写)文件的方式来打开一个名为“test.txt”的文件,并用pf指针来接收。
要注意的是,打开文件也是会出错的,如果出错,就会返回NULL空指针,所以我们一定要进行一次判断,判断pf指针是否为空,为空则通过perror函数报错,用return 1的方式来异常结束程序。
操作完文件之后,我们要将文件关闭,这时候便用到了fclose函数。
值得注意的是,文件关闭之后,pf指针就等于指向了一片未打开的文件信息区,这样pf就成为了野指针,所以要将pf指针及时置空。
运行程序, 通过“w”(只写)方式来打开文件,如果我们前面已经仔细看过了文件打开方式表,就会发现表的最后一列表示,如果文件不存在的话,就会新建一个文件。
此时如果你查看你的文件目录,就会发现我们创建了一个“test.txt”的文件。
如果我们一开始没有文件,想要通过“r”(只读)的方式来打开文件的话,就会报错:
很容易能够读出此英文含义为:没有这样的文件或者文件夹存在。
我们上述所打开的文件,是在我们当前的编译器的工程文件夹中,那如果我想打开一个其他位置的文件怎么办呢???
- 通过“..//”逐级往前
- 直接通过文件的绝对路径
例如:
fopen("..\\test.txt","w");
就是打开我们当前工程文件夹的上一级文件夹中的“tese.txt”文件,称为文件的相对路径,依次类推。
此外,我们还可以直接通过文件的绝对路径:
fopen("D:\\MyC\\nans-friends-c-language\\Project.88\\test.txt","w");
注意这里都要是双斜杠。
除此之外呢,我们还需要介绍一下输入,输出这两个概念:
当我们写文件,并将文件里的信息存储在硬盘上,这样称为输出。
反之,当我们调用硬盘上已有的文件信息,将它打开呈现,称为输入。
那么我们学习完如何打开和关闭文件之后,就要紧接着来学习如何对文件进行读写啦。
首先要提醒大家一点,对于文件读写一定要注意“w”和“r”的切换。
文件的读写方式有两种:
顺序读写
随机读写
文件的顺序读写有以下表格中的这些函数:
这些函数一共分为四类,下面我们就来逐一讲解。
字符输入函数 fgetc
字符输出函数 fputc
字符输入输出函数,是针对单个字符的函数,一次只能操作一个字符。
先来看输出函数,它有两个参数:
character 要输出的字符
stream 文件操作指针
下面来看具体应用:
//操作文件
char ch;
for(ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}
因为每次只能操作一个字符,所以我们用for循环将26个英文字母输出给文件,来看结果:
这样我们就算是程序退出,文件里的数据依然存在,真正的实现了数据的保存。
当然,如果你想要实现换行,空格这样的操作,用fputc函数也是能够实现的。
学习完怎么向文件输出之后,我们再接着来学怎么从文件输入。
这个函数只需要一个参数,也就是文件操作指针。
起初文件操作指针会指向字符的第一位,每读取一次之后,指针就会往后移一位。
当这个函数读取结束时,会返回EOF,所以我们也可以凭借这个来实现循环遍历。
既然是从外部输入数据,就需要东西来接收。
//操作文件
int ch = 0;
while ((ch = fgetc(pf))!= EOF)
{
printf("%c ", ch);
}
只要ch不是EOF,就一直遍历,得到结果如下:
顾名思义,这个函数会一次性的操作一行的数据。
fputs 文本行输出函数
fgets 文本行输入函数
先来看输出函数:
第一个参数是一个指针,指向我们所要操作的字符串的首地址,第二个依然是文件操作指针。
//操作文件
char arr[] = "hello";
fputs(arr, pf);
fputs("world\n", pf);
fputs("!", pf);
来看,第一个参数既然是字符串的首地址,那么我们自然就可以用数组。
值得注意的是,fputs函数也不会自动换行,必须手动换行。
当这个函数读取结束时,会返回一个NULL。
文本行输入函数相比于输出函数,多了一个num,也就是要输入的字符串大小。
但是实际上我们输入的字符串大小会比num少一个。
//操作文件
char arr1[100] = "0";
char arr2[100] = "0";
fgets(arr1, 100, pf);
fgets(arr2, 3, pf);
printf("%s", arr1);
printf("%s", arr2);
来看,用数组来接收字符串,我们的文本文件内容为:
得到结果如下:
能够看出,不管num有多大,都只会输出一行的字符,第二个结果也应证了实际输出的字符串大小是num - 1个。
那么我们前边说的这些个函数都是只能单一的操作字符,那么接下来,我们就来讲解一下能够操作结构体这种含有多种数据类型的函数。
先来看输出函数,第一个参数为文件操作指针,后边还可以有很多个参数,没有限制。
那么到底该如何使用呢???
#include
struct S
{
char c;
int n;
float f;
};
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//操作文件
struct S s = { 'a', 10, 3.14 };
fprintf(pf, "%c %d %f", s.c, s.n, s.f);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
我们随便定义一个结构体类型,并输入一些数据。
大家有没有发现,实际上我们fprintf函数的参数传递和我们熟知的printf函数基本一模一样,只是多了一个pf文件操作指针。
不只是输出函数,输入函数亦是如此:
跟scanf函数相比,fscanf函数同样是多了一个文件操作指针pf。
#include
struct S
{
char c;
int n;
float f;
};
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//操作文件
struct S s = {0};
fscanf(pf, "%c %d %f", &(s.c), &(s.n), &(s.f));
printf("%c %d %f", s.c, s.n, s.f);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
得到结果:
所谓二进制输入输出,也就是将一组数据以二进制的形式在文件中进行读和写,此时我们的文件打开方式就要改为“rb”和“wb”啦。
我们依然是先来看输出函数,这个函数的参数就多了,分别是数据的首地址,单个数据的字节大小,数据的个数,以及字符操作指针。
#include
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//操作文件
int arr[] = { 1,2,3,4,5 };
fwrite(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
将arr数组的五个整型数据以二进制的形式输出到文件,结果如下:
因为是二进制,所以是这样的结果,那到底有没有成功呢???
我们通过二进制输入函数来判断:
能看出来二进制输入和输出两个函数的参数完全相同。
#include
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//操作文件
int arr[5] = {0};
fread(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
通过arr数组来接收二进制函数的输入值,并通过循环打印,得到结果如下:
可见我们的操作都是正确的。
至此,我们讲完了表中的所有文件操作函数。
但是小伙伴们有没有发现,我们讲的这些函数好像都只能从头开始按顺序操作。
那么接下来,我们就接着来学习不按顺序的文件操作函数。
关于文件的随机读写有以下三个函数需要我们掌握:
fseek
ftell
rewind
那么这个函数的作用是什么呢???我们根据它的参数来分析:
stream 我们已经很熟悉,是文件操作指针
offset 这个参数的类型是整型,它的英文含义是偏移量
origin 参数类型也是整型,英文含义为起始地
也就是说,这个函数可以帮助我们通过文件操作指针将文件内的操作系统光标移动到我们想要的起始地处,也可以从起始地的左右来回偏移。
那么对于起始地,我们只有一下三种规定的实际参数:
依次为文件的首地址,文件指针当前指向的地址和尾地址。
我们当前的文件内容为:
下面我们就来实际操作一下:
//操作文件
char ch = 0;
fseek(pf, 4, SEEK_SET);
ch = fgetc(pf);
printf("%c", ch);
来看,我们选择的起始地为文件的首地址,偏移量为正则为右,为负则为左,所以此时指针从a开始向右移动四位,指向e,在通过fgetc函数进行输出,结果如下:
现在我们的文件操作指针是指向了e,那么我们希望从e开始,去得到最后的g怎么做呢???
char ch = 0;
fseek(pf, 4, SEEK_SET);
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c", ch);
很简单,只需要在指针当前位置,在向右移动两位就可以啦:
SEEK_END同理我们这里就不在过多讲解啦。
这个函数就比较简单啦,它的作用是返回文件操作指针的当前位置相对于起始位置的偏移量。
//操作文件
char ch = 0;
fseek(pf, 4, SEEK_SET);
fseek(pf, 2, SEEK_CUR);
int set = ftell(pf);
printf("%d", set);
我们利用上边的函数,此时我们的pf指向了‘g’,那么相对于‘a’的偏移量就是6。
这个函数同样简单,作用是将文件操作指针还原到文件的起始位置。
//操作文件
char ch = 0;
fseek(pf, 4, SEEK_SET);
rewind(pf);
ch = fgetc(pf);
printf("%c", ch);
得到结果如下:
关于文件操作的相关知识到这里就讲完啦。
国庆假期后的补课生活着实艰难,这篇文章我断断续续写了三天。
最后希望各位小伙伴们能够得到自己想要的知识。
最后不要忘记一键三连哦!!!
我们下期再见!