文件是计算机中用于存储数据的基本单位,可以理解为存储在计算机外部存储器(如硬盘、U盘等)上的数据集合
文件操作就是让你的程序能和硬盘上的数据互动起来的关键 C 语言中,文件操作主要依赖于
stdio.h头文件中的一系列函数,核心概念是文件指针(FILE *)和流(stream)
我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)
程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)
.c源文件→编译→.obj目标文件→连接→.exe可执行文
本章讨论的是数据⽂件 在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。 其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件
c:\code\test.txt⼀个数据在文件中是怎么存储的呢? 字符⼀律以
ASCII形式存储,数值型数据既可以⽤ASCII形式存储 ,也可以使⽤二进制形式存储 如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽ ⼆进制形式输出,则在磁盘上只占4个字节

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河 C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的 一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作
标准流是C语言标准库中预定义的、在程序启动时自动打开的流。 它们为程序与外部环境(通常是终端或控制台)进行输入输出提供了一个标准化的接口。程序员无需手动执行打开文件等操作,就可以直接使用这些流进行读写。
stdin
scanf()、getchar()、gets()(已废弃) 等函数都从 stdin 读取数据。
stdout
printf()、putchar()、puts() 等函数都向 stdout 输出数据。
stderr
fprintf(stderr, …)、perror() 等函数专门向 stderr 输出数据
这是默认打开了这三个流,我们使用
scanf、printf等函数就可以直接进⾏输入输出操作stdin、stdout、stderr三个流的类型是:FILE *,通常称为文件指针 C语言中,就是通过FILE*的文件指针来维护流的各种操作的
在编写程序的时候,在打开文件的同时, 都会返回一个
FILE*的指针变量指向该文件, 也相当于建立了指针和文件的关系
要使用这两个函数,你必须包含头文件:#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);fopen 函数是用来打开参数 filename 指定的文件,同时将打开的文件和一个流进行关联,后续对流的操作是通过 fopen 函数返回的指针来维护, 具体对流(关联的文件)的操作是通过参数 mode 来指定的NULL指针,所以⼀定要fopen的返回值做判断,来验证文件是否打开成功



int fclose ( FILE * stream );stream关联的文件,并取消其关联关系。
与该流关联的所有内部缓冲区均会解除关联并刷新:任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区内容将被丢弃
stream:指向要关闭的流的FILE对象的指针
stream指向的流会返回 0,否则会返回 EOF

缓冲文件系统中,关键的概念是 “文件类型指针” ,简称 “文件指针”
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。 该结构体类型是由系统声明的,取名
FILE

FILE结构的变量,并填充其中的信息,使用者不必关心细节FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便下面我们创建一个FILE*的指针变量
FILE* pf;//⽂件指针变量声明了一个变量: 这个变量的名字是pf
指定了变量的类型: 这个变量的类型是 FILE*
通过文件指针变量能够间接找到与它关联的文件
函数名 | 功能 | 适用于 |
|---|---|---|
fgetc | 从输入流中读取一个字符 | 所有输入流 |
fputc | 向输出流中写入一个字符 | 所有输出流 |
fgets | 从输入流中读取一个字符串 | 所有输入流 |
fputs | 向输出流中写入一个字符串 | 所有输出流 |
fscanf | 从输入流中读取带有格式的数据 | 所有输入流 |
fprintf | 向输出流中写入带有格式的数据 | 所有输出流 |
fread | 从输入流中读取一块数据 | 文件输入流 |
fwrite | 向输出流中写入一块数据 | 文件输出流 |
int fgetc(FILE *stream);stream:指向要读取的 FILE 对象的指针(例如,stdin或 fopen() 返回的指针)。
int 类型)。如果到达文件末尾(End-Of-File)或发生读取错误,它会返回 EOF

int fputc(int character, FILE *stream);character: 要写入的字符,以 int 类型传递。
stream: 指向要写入的 FILE 对象的指针(例如,stdout 或 fopen() 返回的指针)。
character(转换为 unsigned char)写入指定流,并推进文件位置指示器。
int 类型)
如果发生错误,返回 EOF

char *fgets(char *str, int n, FILE *stream);n-1 个字符(为末尾的 \0 留出空间)。
fgets 会包含它读到的换行符 \n(如果读到了的话),并在字符串末尾自动添加一个空终止符 \0。

int fputs(const char *str, FILE *stream);\0 结尾的)字符串的指针。
str 指向的字符串写入流中,但它不会自动添加换行符\n。它会一直写到 \0 为止(\0 本身不会被写入)。

int fscanf(FILE *stream, const char *format, ...);stream:要读取的输入流。
format:格式控制字符串,与scanf相同 (例如 “%d %s”)
...:可变参数,必须是指针,用于接收读取到的数据。
fscanf 会根据 format 字符串的指示,从 stream 中解析数据,并将数据存入后续参数指向的内存地址中。
0 或 EOF

int fprintf(FILE *stream, const char *format, ...);stream: 要写入的输出流。
format: 格式控制字符串,与 printf 相同。
...: 可变参数,即要格式化并写入的数据。
fprintf 的工作方式与 printf 完全相同,只是 printf 默认写入到 stdout,而 fprintf 写入到你指定的 stream

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);ptr: 指向用于存储读取数据的内存块的指针(例如一个数组或结构体)。
size: 要读取的每个元素的字节大小(例如 sizeof(int))。
nmemb: 要读取的元素个数。
stream: 要读取的输入流。
size * nmemb 个字节。它主要用于二进制I/O
nmemb 的数量)。如果这个数字小于 nmemb,则表示可能到达了文件末尾或发生了错误

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);size * nmemb个字节(通常是二进制数据)写入文件。
nmemb,则表示发生了错误

对比一组函数
int sprintf(char *str, const char *format, ...);str (字符串): 这是一个指向目标字符数组(缓冲区)的指针。格式化后的结果将存储在这里
format (格式化): 格式控制字符串,与 printf 完全相同(例如 “%d %s”)
... (可变参数): 要格式化的数据(变量、常量等)
int): 返回成功写入到str中的字符总数,不包括末尾自动添加的\0空终止符。如果发生错误,返回一个负数
sprintf 非常危险,因为它不检查目标缓冲区 str 是否有足够的空间。如果格式化后的字符串长度超过了str的大小,就会发生缓冲区溢出 (Buffer Overflow),这是 C 语言中最严重的安全漏洞之一
在现代 C 编程中,强烈建议使用 snprintf 代替 sprintf
snprintf (更安全):
int snprintf(char *str, size_t n, const char *format, ...);n: 它额外增加了一个参数 n,表示 str 缓冲区的最大容量。snprintf 保证写入的字符数(包括 \0)绝对不会超过 n,从而避免了溢出


int sscanf(const char *str, const char *format, ...);str (字符串): 这是你要从中读取数据的源字符串(例如 const char *data = "Age: 30";)。
format (格式化): 格式控制字符串,与 scanf 完全相同(例如 “%d %s”)。
... (可变参数): 必须是指针,用于接收解析出来的数据(例如 &my_var)。
int): 返回成功匹配并赋值的项数。如果一开始就匹配失败,返回 0。如果发生错误或在任何数据被成功读取前就到达了字符串末尾,返回 EOF


允许你立即跳转到文件中的任意指定位置进行读取或写入
int fseek(FILE *stream, long offset, int whence);

示例用途:
fseek(fp, 0L, SEEK_SET);
100 个字节:fseek(fp, 100L, SEEK_SET);
50 个字节:fseek(fp, -50L, SEEK_CUR);
fseek(fp, 0L, SEEK_END);
10 个字节的位置:fseek(fp, -10L, SEEK_END);
返回值:
0
long ftell(FILE *stream);
返回值:
long 类型的整数,表示当前位置相对于文件开头的字节偏移量。
-1L
示例用途:
ftell()最常用的两个场景是:
fseek 准确返回。
fseek(fp, 0L, SEEK_END); 跳转到文件末尾,然后 size = ftell(fp); 即可得到文件总字节数
// 示例:保存当前位置
long current_pos = ftell(fp);
// ... 进行一些其他读写操作 ...
// 恢复到之前的位置
fseek(fp, current_pos, SEEK_SET);void rewind(FILE *stream);
功能:
rewind(fp) 的效果完全等同于执行以下两个操作:
fseek(fp, 0L, SEEK_SET);(将位置设为文件开头)返回值:
void (无返回值)。
int fflush ( FILE * stream );功能: 强制刷新参数 stream 指定流的缓冲区,确保数据写⼊底层设备。
NULL 时:刷新所有打开的输出流参数:
stream :指向⽂件流的指针(如 stdout 、文件指针等)
返回值:成功返回 0 ,失败返回 EOF
exit )或调⽤ fclose 时会⾃动刷新,但程序崩溃时缓冲区数据可能丢失