一、啥是文件
文件是存储在硬盘上的(C盘、D盘...),硬盘上的文件是文件。如:一个文件夹,文件夹里面的文件那都是文件。一个文件的文件名包含3部分:文件路径+文件名主干+⽂件后缀,如:c:\study\test.txt。但是在程序设计中,我们⼀般谈的文件有两种:程序文件、数据文件。
二、文件有什么用处
当我们写完一个程序并关闭之后,再来打开代码还在,当我们写完一篇论文后,再打开,我们写的内容还在,这是因为我们把程序、论文保存在文件中。这就是文件的功能,将数据进行持久化的保存。我们知道写代码的时候会向内存开辟空间,数据会存储在内存中,可当程序关闭时,内存会回收,数据就会丢失,这时就可以使用文件将数据保存。
三、二进制文件与文本文件
根据数据的组织形式,数据文件被称为文本文件或者⼆进制文件,数据是以二进制的形式存储在内存中的如果不加转换的输出到外存,就是⼆进制文件。如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。在内存中字符⼀律以ASCII形式存储,数值型数据既可以⽤ASCII形式存储,也可以使用二进制形式存储。
例如:如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字节),而二进制形式输出,则在磁盘上只占4个字节。因为当以二进制的方式输出到磁盘中,在内存中整数10000是int类型占4个字节,而如果以ASCII码的形式输出到磁盘,10000会被看成5个字符,1看成字符'1'(ASCII码为49),0看成字符'0'(ASCII码为48),占5个字节。
四、流和标准流
当我们在写程序的时候经常需要重外部获取数据,例如:从键盘上输入,从文件中输入,从网络中输入等等,也需要将一些数据输出出来,例如:将数据打印在屏幕上,保存在文件,发送到网络上等等。不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念。有了流,当我们需要输入一些数据的时候,不用考虑这些数据来自哪里,我们可以直接从流上获取,需要输出数据时,直接输出到流上,让流来和外部设备联系。

到这里你会不会有疑惑,我们平时在写代码时的输入和输出都没有打开或者看见流?其实那是因为C语⾔程序在启动的时候,默认打开了3个流:
stdin-标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。 stdout-标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
stderr-标准错误流,大多数环境中输出到显示器界面。
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进行输入输出操作的。 stdin、stdout、stderr 三个流的类型是: C语⾔中,就是通过 FILE* ,通常称为文件指针。 FILE* 的文件指针来维护流的各种操作的。
五、文件指针
每个被使用的文件都在内存中开辟了⼀个相应的文件信息区,⽤来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名FILE。每当打开文件的时候,系统会根据⽂件的情况自动创建⼀个FILE结构的变量,并填充其中的信息,使用者不必关心细节。⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量。
六、文件的打开和关闭
操作一个文件,首先我们需要先1.打开文件,然后2.读写文件,最后3.关闭文件。在C语言中我们使用fopen函数打开文件,使用fclose函数关闭文件。
fopen函数: 
eg: FILE* pf = fopen("file.txt", "w"); //定义了一个FILE *类型的指针变量pf来接收fopen函数返回的文件指针,该文件的名字是"file.txt",以"w"的方式打开。(如果fopen开通成功返回文件指针,失败返回NULL)
文件的打开方式:

eg:
fclose(pf);\\把上面开通的文件指针pf关闭
pf = NULL;\\避免pf变成野指针
七、文件的顺序读写

顺序读取文件的相关函数:

1.fgetc函数和fputc函数

eg:
#include<stdio.h>
int main()
{
FILE* pf = fopen("file.txt", "w");//以写的方式打开文件"file.txt"(如果目录中不存在则会创建)
if (pf == NULL)//判断pf是否为空指针,如果是空指针,说明文件打开失败
{
perror("fopen");//查看文件打开失败的原因
return 1;
}
fputc('A', pf);//将字符'A'写入文件"file.txt"中
fputc('B', pf);//将字符'B'写入文件"file.txt"中
fputc('C', pf);//将字符'C'写入文件"file.txt"中
fclose(pf);//关闭文件
pf = NULL;//将pf置为空指针,避免野指针
FILE* pf1 = fopen("file.txt", "r");//以读的方式打开文件"file.txt"(如果目录中不存在则打开失败)
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf1);//从文件"file.txt"中得到字符,将字符返回并将字符的ASCII码赋给变量ch
printf("%c\n", ch);//打印ch中的字符
ch = fgetc(pf1);//再使用一下fgetc函数
printf("%c\n", ch);//打印ch中的字符
ch = fgetc(pf1);//再使用一下fgetc函数
printf("%c\n", ch);//打印ch中的字符
fclose(pf1);
pf1 = NULL;
return 0;
}
(注:若没有标明文件的绝对路径,创建的文件会在该程序的目录下)
代码的结果:
从打印的结果可以看出,当使用一次fgetc时光标会向后移动一次,就是说第一次使用fgetc时光标是指在第一个字符'A'上的,使用完后光标自动向后移,第二次使用fgetc时光标指在第二个字符'B'上,所以打印出来的结果是这样,这就是文件的顺序读写。
2.fgets函数和fputs函数

eg:
#include<stdio.h>
int main()
{
FILE* pf = fopen("file.txt", "w");//以写的方式打开文件"file.txt"(如果目录中不存在则会创建)
if (pf == NULL)//判断pf是否为空指针,如果是空指针,说明文件打开失败
{
perror("fopen");//查看文件打开失败的原因
return 1;
}
fputs("haha", pf);//将字符串"haha"写入文件"file.txt"中
fclose(pf);//关闭文件
pf = NULL;//将pf置为空指针,避免野指针
FILE* pf1 = fopen("file.txt", "r");//以读的方式打开文件"file.txt"(如果目录中不存在则打开失败)
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
char str[20] = { 0 };
fgets(str, 3, pf1);//从文件"file.txt"中读取(3-1)个字符到str中
printf("%s", str);//打印str
fclose(pf1);
pf1 = NULL;
return 0;
}
代码的结果:
3.fscanf函数和fprintf函数
fscanf和scanf类似,fprintf和printf类似。
eg:
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d", n);
return 0;
}
上面这个代码我们再熟悉不过了, fscanf和scanf类似,fprintf和printf类似。只是在他们前面加上流。
eg:
#include<stdio.h>
int main()
{
int n = 0;
FILE* pf = fopen("file.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf, "%d", n);//和printf类似只是在前面加上了文件指针pf,表示把n的值写入文件"file.txt"中
fclose(pf);
pf = NULL;
}
fscanf也和scanf类似,在前面加上文件指针,表示读取文件的内容。
5.fread函数和fwrite函数

eg:
#include<stdio.h>
struct stu//创建结构体
{
char name[20];
int age;
float score;
};
int main()
{
struct stu s = { "zhangsan",20,95.5 };//结构体初始化
FILE* pf = fopen("file.txt", "wb");//以二进制写的方式打开文件"file.txt"
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&s, sizeof(s), 1, pf);//将起始地址为&s,大小为sizeof(s)的一个数据写入pf中
fclose(pf);
pf = NULL;
return 0;
}
代码运行后文件里的内容:
这就是以二进制写入的内容。
eg2:
#include<stdio.h>
struct stu//创建结构体
{
char name[20];
int age;
float score;
};
int main()
{
struct stu s = { 0 };//结构体初始化
FILE* pf = fopen("file.txt", "rb");//以二进制读的方式打开文件"file.txt"
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(&s, sizeof(s), 1, pf);//将pf中大小为sizeof(s)的一个数据传给地址&s
printf("%s %d %.1f\n", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
代码运行结果:
由于将文件中的数据传入到结构体变量s中,所以打印出来的结果是这样。
八、文件的随机读写
由第一个代码我们知道,当我们使用上述顺序读取文件的相关函数去读写的时候,使用一次函数光标就会向后移动一次,这就会导致序文件的读取。我们可以使用fseek函数、ftell函数和rewind函数改变光标位置从而实现文件的随机读写。
1.fseek函数
一个文本文件file.txt中有一些内容:
eg:
#include<stdio.h>
int main()
{
FILE* pf = fopen("file.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
}
当我们使用fgetc函数去读取这个文件的时候的我们知道读取一次光标向后移动一次,所以上述代码的结果为:
当再次使用fgetc的时候我们知道这时光标在文本内容中D的位置,这时我们就可以使用fseek函数来改变光标位置,从而改变读取文件的内容。
eg:
int main()
{
FILE* pf = fopen("file.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
fseek(pf, 2, SEEK_CUR);//从光标位置向后偏移2个位置
ch = fgetc(pf);
printf("%c\n", ch);
fseek(pf, 0, SEEK_SET);//从起始位置偏移0个位置
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
代码的运行结果:
2.ftell函数
ftell这个函数是返回光标与起始位置的偏移量,当我们不清楚光标在哪个位置时,就可以使用ftell函数。
3.rewind函数
rewind函数是让文件指针的位置回到起始位置,就是让光标回到文件的第一个元素。
九、文件缓冲区
ANSIC标准采⽤“缓冲文件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程序中每⼀个正在使用的文件开辟⼀块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘⽂件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
本文详细介绍了文件的基本概念,包括文件的用途、二进制文件与文本文件的区别,以及流和标准流在程序设计中的作用。讲解了文件指针、文件的打开与关闭、顺序读写(如fgetc/fputc、fgets/fputs等)和随机读写(如fseek/ftell/rewind)的方法,以及文件缓冲区的工作原理。
2299

被折叠的 条评论
为什么被折叠?



