在C语言开发中,文件操作是实现数据持久化的核心手段,能让程序数据脱离内存限制、长期存储在硬盘中。本文将从文件基础概念、指针操作、顺序/随机读写,到缓冲区、错误判断,全方位拆解C语言文件操作的核心知识点,附完整代码示例与避坑指南,零基础也能快速掌握。
一、文件操作的核心意义
程序运行时,所有数据默认存储在内存中,程序退出/电脑关机后内存数据会完全丢失;
文件操作的核心目的是实现数据持久化,将数据保存到硬盘的文件中,实现数据的长期存储和跨程序使用。
二、文件的分类(程序设计视角)
在C语言程序开发中,文件分为程序文件和数据文件两类,核心操作对象为数据文件。
2.1 程序文件
用于构成程序的文件,仅被编译器/操作系统解析,程序运行时一般不主动读写:
• 源文件:后缀.c,编写的C语言代码文件
• 目标文件:后缀.obj,源文件编译后生成的二进制文件(Windows系统)
• 可执行文件:后缀.exe,目标文件链接后生成的可运行文件(Windows系统)
2.2 数据文件
程序运行时主动读写的文件,用于存储程序的输入/输出数据,比如配置文件、日志文件、用户数据文件等。
文件名规范:一个完整的文件名由3部分组成,文件路径 + 文件名主干 + 文件后缀,示例:
• 绝对路径:D:\\C_Code\\project\\test.txt
• 相对路径:./data/info.dat(./表示当前程序所在目录)
三、文本文件 vs 二进制文件(数据存储形式)
根据数据在文件中的存储格式,数据文件分为文本文件和二进制文件,核心区别是是否按ASCII码转换存储。
3.1 文本文件
• 数据以ASCII字符的形式存储,可被记事本、VS Code等文本编辑器直接打开查看
• 存储前需将内存中的二进制数据转换为ASCII码,存在转换开销
• 示例:整数10000(内存中4字节二进制),会被拆分为1、0、0、0、05个ASCII字符,占5字节
3.2 二进制文件
• 数据直接按内存中的二进制形式存储,无需转换,无格式开销
• 无法被文本编辑器直接识别(打开为乱码),需对应程序解析
• 示例:整数10000直接按4字节二进制存储,占用空间更小
3.3 核心对比
| 特性 | 文本文件 | 二进制文件 |
| 存储格式 | ASCII字符 | 内存原二进制 |
| 可读性 | 高(可直接打开) | 低(乱码) |
| 空间占用 | 较大 | 较小 |
| 读写效率 | 较低(需转换) | 较高(无转换) |
| 适用场景 | 配置文件、日志文件等 | 数据备份、大型数据存储等 |
四、文件指针(FILE*)—— 文件操作的核心句柄
4.1 文件信息区
系统为每个打开的文件,在内存中自动创建一个结构体变量,称为文件信息区。
该结构体中存储了文件的核心信息:文件名、文件打开模式、文件当前读写位置、文件缓冲区地址、文件状态等。
C语言标准库中规定了该结构体的通用名FILE(不同编译器的FILE结构体内部实现略有差异,但核心成员一致)。
4.2 文件指针的定义与作用
• 定义:FILE* 指针名;(如FILE* pf;),FILE*是指向FILE结构体的指针类型
• 作用:通过文件指针指向文件的信息区,程序对文件的所有操作,都通过操作该指针完成(无需直接操作FILE结构体)
• 注意:文件指针仅在文件打开后有效,文件关闭后失效,需及时置NULL避免野指针。
4.3 标准流文件指针(程序启动默认打开)
C语言程序启动时,系统会自动打开3个标准流文件,对应3个默认的文件指针,可直接使用:
| 标准流指针 | 对应设备 | 作用 |
| stdin | 键盘 | 标准输入流 |
| stdout | 屏幕 | 标准输出流 |
| stderr | 屏幕 | 标准错误输出流 |
示例:fputc('a', stdout); 等价于printf("a");,直接在屏幕输出字符a。
五、文件的打开(fopen)与关闭(fclose)—— 必成对使用
文件操作的基本流程:打开文件(fopen) → 读写文件 → 关闭文件(fclose),打开和关闭必须成对出现,否则会导致文件泄漏、缓冲区数据丢失、文件被占用等问题。
5.1 打开文件:fopen函数
函数原型
#include <stdio.h> // 头文件必须包含
FILE *fopen(const char *filename, const char *mode);
参数解析
• filename:字符串类型,传入文件路径+文件名(绝对路径/相对路径)
• mode:字符串类型,传入文件打开模式,决定文件的读写权限、操作类型
返回值
• 成功:返回指向该文件信息区的FILE*指针(文件指针)
• 失败:返回NULL(如文件不存在、权限不足、路径错误),并设置错误码
必做操作:判空
由于fopen可能失败,打开文件后必须判断返回值是否为NULL,避免后续操作野指针:
// 示例:以只读模式打开test.txt
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) // 判空:失败则打印错误信息并退出
{
perror("fopen fail"); // perror:打印错误原因(需包含stdio.h)
return 1; // 非0退出,标识程序异常
}
// 成功则继续后续读写操作
5.2 核心:文件打开模式(mode)
mode是字符串常量(必须加双引号),不同模式对应不同的文件操作权限,最常用r/w/a/rb/wb,其余为扩展模式,核心模式详解如下(分文本/二进制):
文本文件打开模式(核心)
| 模式 | 中文描述 | 核心权限 | 文件不存在时 | 文件存在时 | 初始读写位置 |
| "r" | 只读打开 | 仅读,不可写 | 打开失败 | 保留原有内容 | 文件开头 |
| "w" | 只写打开 | 仅写,不可读 | 创建新文件 | 清空原有内容 | 文件开头 |
| "a" | 追加写入 | 仅写,不可读 | 创建新文件 | 保留原有内容 | 文件末尾 |
| "r+" | 读写打开 | 可读可写 | 打开失败 | 保留原有内容 | 文件开头 |
| "w+" | 读写创建 | 可读可写 | 创建新文件 | 清空原有内容 | 文件开头 |
| "a+" | 读写追加 | 可读可写 | 创建新文件 | 保留原有内容 | 文件末尾 |
二进制文件打开模式(核心)
在文本模式后加b(binary),表示按二进制格式操作,无ASCII转换,核心模式:
| 模式 | 中文描述 | 核心权限 | 文件不存在时 | 文件存在时 |
| "rb" | 只读打开二进制 | 仅读,二进制 | 打开失败 | 保留原有内容 |
| "wb" | 只写打开二进制 | 仅写,二进制 | 创建新文件 | 清空原有内容 |
| "ab" | 追加写入二进制 | 仅写,二进制 | 创建新文件 | 保留原有内容 |
模式使用注意
1. "r"是最安全的读模式,不会修改文件,文件不存在时直接失败,避免误创建;
2. "w"模式慎用,文件存在时会直接清空内容,无法恢复;
3. "a"模式是追加写,所有写入操作都在文件末尾,不会覆盖原有内容,适合写日志;
4. 二进制模式的b必须小写,部分编译器(如VC++)不支持大写B。
5.3 关闭文件:fclose函数
函数原型
#include <stdio.h>
int fclose(FILE *stream);
参数解析
• stream:传入需要关闭的文件指针(fopen成功返回的指针)
返回值
• 成功:返回0
• 失败:返回EOF(EOF是C语言常量,值为-1,定义在stdio.h中)
必做操作:关闭后置NULL
文件关闭后,系统会释放该文件的信息区,原文件指针变为野指针,必须手动置NULL:
// 示例:关闭文件指针pf
if (fclose(pf) != 0) // 可选:判断关闭是否成功
{
perror("fclose fail");
return 1;
}
pf = NULL; // 核心:置NULL,避免野指针
5.4 打开+关闭完整代码示例
#include <stdio.h>
int main()
{
// 1. 以只读模式打开文本文件test.txt
FILE* pf = fopen("test.txt", "r");
// 2. 判空:打开失败则退出
if (pf == NULL)
{
perror("fopen: test.txt");
return 1;
}
// 3. 此处编写文件读写代码(暂时省略)
// 4. 关闭文件
fclose(pf);
// 5. 置NULL,避免野指针
pf = NULL;
return 0;
}
六、文件的顺序读写——按顺序从开头/末尾读写
顺序读写是指:从文件的初始位置开始,按字节/字符/行的顺序,依次读写,读写位置自动后移,核心对应6组函数,按操作单位分类,附完整代码示例+解析。
前置说明
1. 所有读写函数都需包含<stdio.h>头文件;
2. 函数参数中的stream均为文件指针(fopen成功的指针);
3. 读写失败/到达文件末尾时,多数函数返回EOF或NULL;
4. 以下示例均基于“文件打开成功”,省略判空代码(实际开发必须加)。
6.1 字符级读写:fputc(写)+ fgetc(读)—— 单字符操作
1. 写字符:fputc
• 原型:int fputc(int ch, FILE *stream);
• 作用:将单个字符写入指定文件
• 参数:ch为要写入的字符(int型是为了兼容EOF,实际传char即可);stream为文件指针
• 返回值:成功返回写入的字符(ASCII值);失败返回EOF
• 示例:向test.txt写入字符a、b、c
FILE* pf = fopen("test.txt", "w"); // 只写模式打开
fputc('a', pf);
fputc('b', pf);
fputc('c', pf); // 写入后文件内容:abc
fclose(pf);
pf = NULL;
2. 读字符:fgetc
• 原型:int fgetc(FILE *stream);
• 作用:从指定文件读取单个字符,读取后文件指针自动后移1字节
• 参数:stream为文件指针
• 返回值:成功返回读取的字符(ASCII值);到达文件末尾/失败返回EOF
• 示例:读取test.txt中的字符(内容abc)
FILE* pf = fopen("test.txt", "r"); // 只读模式打开
int ch = 0;
// 循环读取:直到fgetc返回EOF(末尾)
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch); // 输出:a b c
}
fclose(pf);
pf = NULL;
• 关键:fgetc执行一次,指针后移一次,再次执行读取下一个字符,直到末尾返回EOF。
6.2 字符串级读写:fputs(写)+ fgets(读)—— 整行/整串操作
1. 写字符串:fputs
• 原型:int fputs(const char *str, FILE *stream);
• 作用:将一个字符串写入指定文件(不会自动加换行符,需手动加\\n)
• 参数:str为要写入的字符串;stream为文件指针
• 返回值:成功返回非负数;失败返回EOF
• 示例:向test.txt写入字符串hello和world(换行)
FILE* pf = fopen("test.txt", "w");
fputs("hello\\n", pf); // 手动加\\n,实现换行
fputs("world", pf); // 文件内容:hello\\nworld
fclose(pf);
pf = NULL;
2. 读字符串:fgets
• 原型:char *fgets(char *str, int num, FILE *stream);
• 作用:从指定文件读取最多num-1个字符,存入str数组,自动在末尾加'\\0' 构成字符串
• 停止读取的3种情况:① 读取了num-1个字符;② 读到换行符\\n;③ 到达文件末尾
• 参数:str为存储字符串的数组;num为读取的最大字符数;stream为文件指针
• 返回值:成功返回str数组的首地址;到达末尾/失败返回NULL
• 示例:读取test.txt中的字符串(内容hello\\nworld,数组大小10)
FILE* pf = fopen("test.txt", "r");
char buf[10] = {0}; // 定义缓冲区存储读取的字符串
// 循环读取:直到fgets返回NULL
while (fgets(buf, 10, pf) != NULL)
{
printf("%s", buf); // 输出:hello 换行 world
}
fclose(pf);
pf = NULL;
• 关键:fgets会保留换行符\\n,是区别于gets(已废弃)的核心点,避免缓冲区溢出。
6.3 格式化读写:fprintf(写)+ fscanf(读)—— 按格式操作(类似printf/scanf)
1. 格式化写:fprintf
• 原型:int fprintf(FILE *stream, const char *format, …);
• 作用:按指定格式将数据写入文件(比printf多了第一个参数stream,其余完全一致)
• 适用场景:写入结构体、整数、浮点数等复合数据,无需手动转换格式
• 示例:将结构体数据写入文件
// 定义结构体
struct Student
{
char name[20];
int age;
float score;
};
int main()
{
struct Student s = {"张三", 18, 95.5};
FILE* pf = fopen("student.txt", "w");
// 格式化写入:格式符与printf一致
fprintf(pf, "姓名:%s 年龄:%d 成绩:%.1f", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
// 文件内容:姓名:张三 年龄:18 成绩:95.5
2. 格式化读:fscanf
• 原型:int fscanf(FILE *stream, const char *format, …);
• 作用:从文件按指定格式读取数据(比scanf多了第一个参数stream,其余完全一致)
• 适用场景:读取文件中的格式化数据,直接存入变量/结构体
• 示例:从student.txt读取数据到结构体
struct Student
{
char name[20];
int age;
float score;
};
int main()
{
struct Student s = {0};
FILE* pf = fopen("student.txt", "r");
// 格式化读取:格式符与写入时一致
fscanf(pf, "姓名:%s 年龄:%d 成绩:%f", s.name, &s.age, &s.score);
// 打印验证:输出姓名:张三 年龄:18 成绩:95.500000
printf("姓名:%s 年龄:%d 成绩:%f", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
• 关键:读取格式必须与写入格式完全一致,否则读取数据错误。
6.4 二进制读写:fwrite(写)+ fread(读)—— 二进制块操作(高效)
核心特点
• 按内存二进制格式读写,无ASCII转换,效率最高,占用空间最小;
• 直接操作数据块(如整个结构体、整个数组),适合存储复合数据;
• 写入的文件为二进制文件,文本编辑器打开为乱码,需对应程序解析。
1. 二进制写:fwrite
• 原型:size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
• 核心参数解析(size_t是无符号整数,定义在stdio.h):
◦ ptr:要写入的数据首地址(void*兼容所有类型);
◦ size:单个数据的字节大小(如int为4,结构体为sizeof(结构体));
◦ count:要写入的数据个数;
◦ stream:文件指针。
• 返回值:成功返回实际写入的数据个数;失败返回小于count的数(或0)。
• 示例:将结构体数组以二进制格式写入student.dat
struct Student
{
char name[20];
int age;
float score;
};
int main()
{
// 定义结构体数组并初始化
struct Student s[2] = {{"张三", 18, 95.5}, {"李四", 19, 90.0}};
// 二进制只写模式打开(wb)
FILE* pf = fopen("student.dat", "wb");
// 写入:单个数据大小sizeof(struct Student),个数2
fwrite(s, sizeof(struct Student), 2, pf);
fclose(pf);
pf = NULL;
return 0;
}
2. 二进制读:fread
• 原型:size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
• 作用:从二进制文件读取count个size字节的数据,存入ptr指向的内存空间
• 参数:与fwrite完全一致(ptr为存储数据的内存首地址)
• 返回值:成功返回实际读取的数据个数;到达文件末尾/失败返回小于count的数(或0)
• 示例:从student.dat读取二进制数据到结构体数组
struct Student
{
char name[20];
int age;
float score;
};
int main()
{
struct Student s[2] = {0}; // 初始化数组为0
// 二进制只读模式打开(rb)
FILE* pf = fopen("student.dat", "rb");
// 读取:单个数据大小sizeof(struct Student),个数2
size_t ret = fread(s, sizeof(struct Student), 2, pf);
// 打印读取结果
for (int i = 0; i < ret; i++)
{
printf("姓名:%s 年龄:%d 成绩:%.1f\\n", s[i].name, s[i].age, s[i].score);
}
fclose(pf);
pf = NULL;
return 0;
}
// 输出:
// 姓名:张三 年龄:18 成绩:95.5
// 姓名:李四 年龄:19 成绩:90.0
• 关键:fread/fwrite的size和count必须与写入时一致,否则数据读取错乱。
6.5 顺序读写函数对比表(核心)
| 函数 | 操作类型 | 操作单位 | 适用文件 | 核心特点 |
| fputc | 写 | 单个字符 | 文本文件 | 逐字符写入,无自动换行 |
| fgetc | 读 | 单个字符 | 文本文件 | 逐字符读取,指针自动后移 |
| fputs | 写 | 字符串 | 文本文件 | 逐串写入,需手动加\\n |
| fgets | 读 | 字符串 | 文本文件 | 最多读num-1个,保留\\n,加\\0 |
| fprintf | 写 | 格式化数据 | 文本文件 | 兼容所有数据类型,格式灵活 |
| fscanf | 读 | 格式化数据 | 文本文件 | 按格式读取,需与写入格式一致 |
| fwrite | 写 | 二进制块 | 二进制文件 | 无转换,高效,适合复合数据 |
| fread | 读 | 二进制块 | 二进制文件 | 无转换,高效,参数需严格匹配 |
七、文件的随机读写——任意位置读写(核心3函数)
顺序读写只能从开头/末尾依次操作,而随机读写可以通过移动文件指针,直接跳转到文件的任意位置进行读写,核心依赖3个函数:fseek(定位指针)、ftell(获取指针位置)、rewind(重置指针)。
7.1 核心前提:文件指针的“当前位置”
文件指针的当前读写位置是随机读写的核心,默认规则:
• "r"/"w"模式:初始位置为文件开头(偏移量0);
• "a"模式:初始位置为文件末尾;
• 顺序读写时,指针会自动后移(如fgetc后移1字节,fgets后移n字节);
• 随机读写的本质:手动修改指针的当前位置。
7.2 定位文件指针:fseek(核心)
函数原型
int fseek(FILE *stream, long int offset, int origin);
核心参数解析
• stream:文件指针
• offset:偏移量(long int型),单位为字节;正数表示向后偏移,负数表示向前偏移
• origin:偏移起始位置,C语言标准库定义了3个常量,必须使用:
| 常量 | 对应位置 | 含义 |
| SEEK_SET | 0 | 文件开头 |
| SEEK_CUR | 1 | 文件指针当前位置 |
| SEEK_END | 2 | 文件末尾 |
返回值
• 成功:返回0
• 失败:返回非0值
示例:文件内容为abcdef(6个字符,偏移量0-5),通过fseek定位并读取
FILE* pf = fopen("test.txt", "r");
int ch = 0;
// 1. 从文件开头(SEEK_SET)向后偏移3字节,定位到d(偏移量3)
fseek(pf, 3, SEEK_SET);
ch = fgetc(pf); // 读取d
printf("%c\\n", ch); // 输出d
// 2. 从当前位置(d,偏移3)向前偏移2字节,定位到b(偏移1)
fseek(pf, -2, SEEK_CUR);
ch = fgetc(pf); // 读取b
printf("%c\\n", ch); // 输出b
// 3. 从文件末尾(SEEK_END)向前偏移2字节,定位到e(偏移4)
fseek(pf, -2, SEEK_END);
ch = fgetc(pf); // 读取e
printf("%c\\n", ch); // 输出e
fclose(pf);
pf = NULL;
7.3 获取当前指针偏移量:ftell
函数原型
long int ftell(FILE *stream);
作用
返回文件指针当前位置相对于文件开头的字节偏移量(long int型)
返回值
• 成功:返回偏移量(非负整数)
• 失败:返回-1L(long型的-1)
示例:获取上述示例中指针的偏移量
FILE* pf = fopen("test.txt", "r");
fseek(pf, 3, SEEK_SET);
long pos = ftell(pf);
printf("%ld\\n", pos); // 输出3(当前偏移量3)
fclose(pf);
pf = NULL;
7.4 重置文件指针到开头:rewind
函数原型
void rewind(FILE *stream);
作用
1. 将文件指针重置到文件开头(偏移量0);
2. 同时清除文件的错误标志和文件结束标志(feof/ferror的标志)。
无返回值,直接使用
示例:读取后通过rewind重置指针,重新读取
FILE* pf = fopen("test.txt", "r");
int ch = fgetc(pf); // 读取a,指针偏移1
printf("%c\\n", ch); // 输出a
rewind(pf); // 重置指针到开头(偏移0)
ch = fgetc(pf); // 再次读取a
printf("%c\\n", ch); // 输出a
fclose(pf);
pf = NULL;
7.5 随机读写完整示例
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf); // 写入abcdef,偏移0-5
fclose(pf);
pf = fopen("test.txt", "r");
int ch = 0;
// 1. 定位到偏移3(d)
fseek(pf, 3, SEEK_SET);
ch = fgetc(pf);
printf("偏移3:%c\\n", ch); // d
// 2. 获取当前偏移量
printf("当前偏移:%ld\\n", ftell(pf)); // 4
// 3. 重置到开头
rewind(pf);
printf("重置后偏移:%ld\\n", ftell(pf)); // 0
// 4. 从末尾向前偏移2(e)
fseek(pf, -2, SEEK_END);
ch = fgetc(pf);
printf("末尾前2:%c\\n", ch); // e
fclose(pf);
pf = NULL;
return 0;
}
八、文件结束与错误判断——feof + ferror(必用)
8.1 核心误区:feof的作用
feof不是用来判断文件是否读取结束的,而是用来判断“文件读取结束的原因”:
• 读取结束的两种可能:① 正常到达文件末尾(EOF);② 读写过程中发生错误(如文件损坏、权限丢失);
• feof的作用:当读取结束后,判断是否是正常到达末尾;
• ferror的作用:当读取结束后,判断是否是发生错误导致结束。
8.2 核心函数:feof + ferror
1. feof函数
• 原型:int feof(FILE *stream);
• 作用:判断文件是否正常到达末尾
• 返回值:是(正常末尾)返回非0值;否返回0
2. ferror函数
• 原型:int ferror(FILE *stream);
• 作用:判断文件读写是否发生错误
• 返回值:是(发生错误)返回非0值;否返回0
8.3 正确使用流程(必背)
1. 先通过读写函数的返回值判断文件是否读取结束(如fgetc返回EOF,fgets返回NULL,fread返回0);
2. 若读取结束,再通过feof判断是否是正常到达末尾;
3. 同时通过ferror判断是否是错误导致结束。
8.4 完整示例:判断文件读取结束的原因
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
// 1. 通过fgetc返回值判断是否读取结束
while ((ch = fgetc(pf)) != EOF)
{
putchar(ch); // 正常读取则输出字符
}
printf("\\n");
// 2. 读取结束后,判断原因
if (feof(pf))
{
printf("读取结束:正常到达文件末尾\\n");
}
else if (ferror(pf))
{
printf("读取结束:文件读写发生错误\\n");
}
fclose(pf);
pf = NULL;
return 0;
}
九、文件缓冲区——系统级优化(关键概念)
9.1 缓冲区的定义
系统为每个打开的文件,在内存中分配一块临时的内存空间,称为文件缓冲区(分为输入缓冲区、输出缓冲区),是系统为了提高文件读写效率设计的优化机制。
9.2 缓冲区的工作原理
写文件的缓冲区流程
程序调用写函数(fputc/fprintf等)→ 数据先写入内存的输出缓冲区 → 缓冲区满/程序正常退出/调用fflush/fclose → 缓冲区数据一次性写入硬盘文件。
读文件的缓冲区流程
程序调用读函数(fgetc/fscanf等)→ 系统先从硬盘文件读取一批数据到内存的输入缓冲区 → 程序从缓冲区中读取数据 → 缓冲区数据读完后,再从硬盘读取下一批。
9.3 核心函数:fflush——强制刷新缓冲区
函数原型
int fflush(FILE *stream);
作用
强制将输出缓冲区中的数据,立即写入硬盘文件(忽略缓冲区是否已满)
返回值
• 成功:返回0
• 失败:返回非0值
适用场景
需要立即保存数据时使用(如写重要日志、程序长时间运行不退出),避免缓冲区数据因程序崩溃丢失。
示例:强制刷新缓冲区
FILE* pf = fopen("test.txt", "w");
fputc('a', pf); // 数据先存入缓冲区,未写入硬盘
fflush(pf); // 强制刷新,数据立即写入硬盘
fclose(pf);
pf = NULL;
9.4 缓冲区的关键注意事项
1. 程序正常退出/调用fclose:系统会自动刷新缓冲区,数据不会丢失;
2. 程序异常崩溃:缓冲区数据未写入硬盘,会永久丢失;
3. fflush仅对输出缓冲区有效,对输入缓冲区无作用;
4. 缓冲区的大小由编译器/操作系统决定(一般为4KB或8KB)。
十、拓展:sprintf + sscanf——内存中的格式化读写
这两个函数并非文件操作函数,但与fprintf/fscanf语法高度一致,用于内存中的格式化数据转换,常与文件操作配合使用,补充说明:
10.1 sprintf:将格式化数据写入内存字符串
• 原型:int sprintf(char *str, const char *format, …);
• 作用:按格式将数据写入字符数组str(内存中),生成格式化字符串
• 示例:将结构体数据转为字符串
#include <stdio.h>
struct Student
{
char name[20];
int age;
};
int main()
{
struct Student s = {"张三", 18};
char buf[100] = {0};
// 将结构体数据转为字符串存入buf
sprintf(buf, "姓名:%s,年龄:%d", s.name, s.age);
printf("%s\\n", buf); // 输出:姓名:张三,年龄:18
return 0;
}
10.2 sscanf:从内存字符串读取格式化数据
• 原型:int sscanf(const char *str, const char *format, …);
• 作用:从字符串str中,按格式读取数据存入指定变量
• 示例:从字符串中读取数据到结构体
#include <stdio.h>
struct Student
{
char name[20];
int age;
};
int main()
{
struct Student s = {0};
char buf[100] = "姓名:张三,年龄:18";
// 从buf中按格式读取数据到结构体
sscanf(buf, "姓名:%s,年龄:%d", s.name, &s.age);
printf("姓名:%s,年龄:%d\\n", s.name, s.age); // 输出一致
return 0;
}
10.3 4个格式化函数对比(核心)
| 函数 | 数据流向 | 操作对象 | 核心作用 |
| printf | 程序 → 屏幕 | 标准输出流 | 格式化输出到屏幕 |
| scanf | 键盘 → 程序 | 标准输入流 | 格式化读取从键盘 |
| fprintf | 程序 → 文件 | 文件流 | 格式化输出到文件 |
| fscanf | 文件 → 程序 | 文件流 | 格式化读取从文件 |
| sprintf | 程序 → 内存字符串 | 字符数组 | 格式化数据转字符串 |
| sscanf | 内存字符串 → 程序 | 字符数组 | 字符串转格式化数据 |
十一、文件操作的常见错误与避坑指南
1. 忘记判空fopen的返回值:导致操作野指针,程序崩溃;
2. 打开与关闭文件不配对:导致文件泄漏,系统资源被占用;
3. 关闭文件后未置NULL:导致野指针,后续误操作引发未知错误;
4. 滥用"w"模式:误清空原有文件内容,数据无法恢复;
5. fread/fwrite的size/count参数不匹配:导致数据读取错乱;
6. 用feof判断文件是否读取结束:导致死循环或错误判断;
7. 格式化读写的格式不匹配:导致读取的数据错误;
8. 二进制文件用文本模式打开:导致数据读写错误(如换行符转换);
9. 忘记刷新缓冲区:程序崩溃时数据丢失(重要数据需用fflush);
10. 路径书写错误:如Windows下路径用\\需转义(D:\\\\test.txt),或相对路径错误。
十二、文件操作的完整开发流程(必背)
1. 包含头文件:#include <stdio.h>
2. 定义文件指针:FILE* pf = NULL;
3. 打开文件:pf = fopen(文件名, 模式);
4. 判空:if (pf == NULL) { perror("fopen"); return 1; }
5. 读写文件:根据需求选择fputc/fgetc/fprintf/fread等函数;
6. 判断读写结束原因:读取结束后用feof+ferror判断;
7. 关闭文件:fclose(pf);
8. 置NULL:pf = NULL;
9. 返回0:程序正常结束。
C语言文件操作是程序实现数据持久化的核心技能,从基础的文件打开关闭、指针运用,到顺序/随机读写的函数使用,再到缓冲区机制、错误判断的细节把控,每一步都关乎程序的稳定性与数据安全性。本文梳理的知识点与代码示例覆盖了文件操作的全流程核心要点,掌握这些内容,便能灵活实现文件的读写、数据存储与解析需求。
文件操作的关键在于规范流程、注重细节,牢记“打开必判空、读写必校验、关闭必置空”的原则,避开常见的操作误区,就能让文件操作成为开发中的实用利器。后续可结合实际场景多做练习,将知识点转化为实操能力,让程序的数处理更灵活、更高效。
网硕互联帮助中心








评论前必须登录!
注册