🐵本篇文章将会对文件相关知识进行讲解
1. 为什么要使用文件❓
当我们写了一个简单的项目比如一个通讯录程序,我们添加完成员信息并关闭程序后,这些成员的信息也会随着程序的结束而不复存在了;所以为了使得数据持久化,我们将数据存放到文件中,因为文件是放在硬盘上的,数据不会丢失
2. 什么是文件❓
磁盘上的文件就是文件,在程序设计中我们讨论的文件有两种:程序文件,数据文件
2.1 程序文件
程序文件包括源程序文件(.c)、目标文件(.obj)、可执行程序(.exe)
2.2 数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件或者输出内容的文件(.txt就是数据文件)
比如将通讯录成员的信息写到文件中,将文件中的信息读取出来;这里说的文件就是数据文件
2.3 文件名
每一个文件都要有唯一的文件标识也就是文件名,以便用户识别
文件名包含3部分:文件路径+文件主干名+文件后缀比如D:\code\data.txt
3. 文件的打开和关闭📂
3.1 文件指针
每一个使用过的文件都在内存上开辟了一块文件信息区,文件信息区实际上就是一个结构体,这个结构体用来存放文件的各种信息(比如:文件的名字、状态、位置等)该结构体类型是系统声明的,取名为:FILE
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并自动填充其中的信息,使用者不必关心细节
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。下面我们可以创建一个FILE* 的指针变量:
FILE* pf
pf就是一个指向FILE类型的指针,可以使pf指向某个文件的文件信息区(也可以说是一个结构体变量)通过pf可以找到文件信息区的信息,进而访问该文件;通过文件指针变量能够找到与它关联的文件
3.2 文件的打开和关闭
fopen函数原型:
fopen是以mode的方式打开文件filename,filename是文件名,可以字符串的形式表示,第二个参数是打开文件的方式,也是以字符串的形式表示
文件使用方式 | 含义 | 如果指定文件不存在 |
r(只读) | 为了(输入)读取数据,打开一个已存在的文本文件 | 报错 |
w(只写) | 为了(输出)写入数据,打开一个文本文件 | 建立一个新的文件 |
fclose函数原型:
参数stream是指向某个文件的指针,关闭文件后要将stream赋为空指针,以防止野指针
实例演示:
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
pf == NULL;
return 0;
}
当使用fopen打开文件data.txt后,系统会自动创建一个FILE类型的变量,并向这个变量填充信息,之后将这个变量的地址传给pf
如果在当前路径(与.c文件相同的路径)没有data.txt,系统会自动创建一个同名文件
文件路径:文件路径有两种:绝对路径和相对路径
相对路径中.\\表示当前路径,..\\表示上一级路径
FILE* pf = fopen(".\\data.txt", "r"); //相对路径(此时是当前路径)
FILE* pf = fopen("C:\\Project\\C\\study\\study_10_8\\data.txt", "r"); //绝对路径(完整表述文件位置的路径)
4. 文件的顺序读写📋
功能 | 函数 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
4.1 流
向不同的外部设备传输数据的方法是不同的,为了简化这种传输方式,引入了流,我们可以将数据传输到流中,然后流会将数据传输到对应的外部设备里
流有文件流,标准输入流,标准输出流,标准错误流
操作文件前要先打开文件,操作完文件后要关闭文件,在使用scanf和printf时为什么没有打开键盘和打开屏幕的操作,这是因为C语言程序只要运行起来就会自动打开三个流:标准输入流(stdin)标准输出流(stdout)标准错误流(stderr),这三个流的类型也都是FILE*
4.2 函数讲解
fgetc函数原型:
fgetc用于读取文件的一个字符,stream是一个FILE*类型的指针,也可以理解为是一个流,fgetc如果读取失败则会返回EOF
实例演示:
事先向data.txt文件中输入abcdef
int main()
{
FILE* pf = fopen("data.txt", "r");//读文件就要以只读的方式打开文件
//写文件就要以只写的方式打开
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c", ch);
}//结果为abcdef
fclose(pf);
pf = NULL;
return 0;
}
fputc函数原型:
fputc用于向文件输入(写)一个字符,character是要输入的那一个字符,stream是一个FILE*类型的指针,也可以理解为一个流
实例演示:
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch , pf); //输出到文件(写文件)
fputc(ch, stdout); //输出到屏幕上,此时和printf功能相同
}
fclose(pf);
pf == NULL;
return 0;
}
4.3 其他函数讲解
功能 | 函数 | 适用于 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件流 |
二进制输出 | fwrite | 文件流 |
将格式化数据转化为字符串 | sprintf | --- |
将字符串转化为格式化数据 | sscanf | --- |
fgets函数原型:
fgets函数用于文本行输入,每次读取流中一行数据,即如果流中有多行数据,在读取其中的某一行时,当读取到'\n'就停止读取;fgets会从流stream中读取num - 1个字符到str中
FILE* pf = fopen("data.txt", "r");
char arr[100] = { 0 };
fgets(arr,num, pf);
printf("%s", arr);
事先在data.txt中输入hello<换行>world,当num是5时,意思是将pf中5 - 1 = 4个字符读取到arr中,arr的打印结果为hell,当num为100时,会读取到第一行的'\n'所以会将"hello\n"读取到arr中
fputs函数原型:
fputs用于文本行输出,将str的数据写到流stream中,直到遇到'\0'
FILE* pf = fopen("data.txt", "w");
fputs("abcdef\0abcd", pf);
上述代码将"abcdef\0abcd"写到pf中,因为字符串中有'\0',所以只会将abcdef写到pf中,那么data.txt中的内容就是abcdef
fscanf函数原型:
fscanf用于格式化输入,直接看代码:
struct S
{
float f;
int i;
char c;
};
FILE* pf = fopen("data.txt", "r");
struct S s = { 0 };
fscanf(pf, "%f %d %c", &(s.f), &(s.i), &(s.c));
printf("%f %d %c", (s.f), (s.i), (s.c));
事先在data.txt中输入3.14 5 c,上述代码就是读取pf中的数据以特定的格式到结构体s对应的变量中,最后打印结果为3.140000 5 c
fprintf函数原型:
fprintf函数用于格式化输出,直接看代码:
struct S
{
float f;
int i;
char c;
};
FILE* pf = fopen("data.txt", "w");
struct S s = {3.14, 5, 'c'};
fprintf(pf, "%f %d %c", s.f, s.i, s.c);
上述代码是将结构体变量初始化的数据以特定的格式写入文件data.txt中
文件使用方式 | 含义 | 如果指定文件不存在 |
rb(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
wb(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
fwrite函数原型:
fwrite是对一个二进制文件写入数据,fwrite的功能是将ptr中的内容写到stream中,size是写入数据的字节大小,count是写入的元素个数
FILE* pf = fopen("data.txt", "wb");
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
fwrite(arr, sizeof(int), sizeof(arr)/sizeof(arr[0]), pf);
在data.txt中显示的内容是:
这些数据我们无法理解其含义,所以要通过fread将该二进制文件中的数据读取出来并打印到屏幕上(我们向文件中写的是1~10,那么读取后打印到屏幕上的也应该是1~10)
fread函数原型:
fread是读取一个二进制文件中的数据,fread的功能是将stream中的数据读取到ptr中,stream中每一个数据的字节大小是size,一共读取count个元素
FILE* pf = fopen("data.txt", "rb");
int arr1[10] = { 0 };
fread(arr, sizeof(int), 10, pf);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
打印结果为:
sprintf函数原型:
sprintf用于将格式化的数据转化为字符串
sscanf函数原型:
sscanf用于将字符串转化为格式化数据
struct S
{
float f;
char c;
int i;
};
int main()
{
struct S s = { 3.14, 'c', 100 };
char arr[100] = { 0 };
sprintf(arr, "%f %c %d", (s.f), (s.c), (s.i));
printf("%s\n", arr);
struct S t = { 0 };
sscanf(arr, "%f %c %d", &(t.f), &(t.c), &(t.i));
printf("%f %c %d\n", t.f, t.c, t.i);
return 0;
}
打印结果为:
我们可以通过调试观察arr
4.4 对比总结
接下来通过对比来总结printf, scanf, fprintf, fscanf, sprintf, sscanf这六个函数
printf:格式化输出函数,适用于标准输出流(屏幕)
scanf:格式化输入函数,适用于标准输入流(屏幕)
fprintf:格式化输出函数,适用于所有输出流(文件流,标准输出流)
fscanf:格式化输入函数,适用于所有输入流(文件流,标准输入流)
sprintf:将格式化数据转化为字符串
sscanf:将字符串转化为格式化数据
🙉至此,对文件的第一部分讲解结束,接下来会继续讲解文件相关知识