一、文件读写操作
1. 打开与关闭
fopen 文件的打开
fclose 文件的关闭
FILE *
2. 文件模式
r | 只读模式 | 文件流位于文件开头 | 没有文件时不创建 |
r+ | 读写模式 | 文件流位于文件开头 | 没有文件时不创建 |
w | 清空写 | 文件流位于文件开头 | 没有文件时创建 |
w+ | 清空读写 | 文件流位于文件开头 | 没有文件时创建 |
a | 追加写 | 文件流位于文件末尾 | 没有文件时创建 |
a+ | 追加读写 | 文件流位于文件末尾 | 没有文件时创建 |
/*
FILE * 指针
打开文件
FILE *fopen(const char *pathname, const char *mode);
参数
pathname 文件路径
mode 模式
返回值
成功 返回 文件指针
失败 返回 NULL
r 只读模式 文件流位于文件开头 没有文件时不创建
r+ 读写模式 文件流位于文件开头 没有文件时不创建
w 清空写 文件流位于文件开头 没有文件时创建
w+ 清空读写 文件流位于文件开头 没有文件时创建
a 追加写 文件流位于文件末尾 没有文件时创建
a+ 追加读写 文件流位于文件末尾 没有文件时创建
int fclose(FILE *stream);
*/
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 打开文件
FILE *fp = fopen("hello.txt", "r+");
if (NULL == fp)
{
printf("文件打开失败\n");
exit(-1);
}
// 关闭文件
fclose(fp);
return 0;
}
二、文件读写函数
数据类型 | 推荐函数 | 注意事项 |
---|---|---|
原始字节流 | fgetc/fputc + 缓冲 | 注意字节序问题 |
可变长度文本行 | fgets + 动态内存 | 检查换行符和缓冲区溢出 |
结构化配置数据 | fscanf/fprintf | 严格校验输入格式 |
硬件寄存器映射 | fread/fwrite | 配合内存对齐使用 |
大文件随机访问 | fseek + 内存映射 | 注意文件锁机制 |
1. 字符读写函数
这类函数用于读取或写入单个字符,例如 fgetc
和 fputc
。
int fgetc(FILE *stream); // 返回值是int型(考虑EOF)
int fputc(int char, FILE *stream);
EOF处理陷阱:
// 错误示例(char溢出)
char c;
while ((c = fgetc(fp)) != EOF) { ... }
// 正确写法(int接收)
int ch;
while ((ch = fgetc(fp)) != EOF) {
char byte = (char)ch;
// 处理字节
}
2. 行读写函数
这类函数用于读取或写入一行字符串,例如 fgets
和 fputs
。
char *fgets(char *str, int n, FILE *stream);
int fputs(const char *str, FILE *stream);
缓冲区溢出防御:
// 危险操作(无长度限制)
char buf[256];
fgets(buf, sizeof(buf), fp); // 正确做法
// 错误示例(易导致溢出)
gets(buf); // 绝对禁止使用!
3. 格式化读写函数
这类函数用于读取或写入特定格式的数据,例如 fscanf
和 fprintf
。
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
安全格式化模板:
// 防御缓冲区溢出攻击
char username[32];
fscanf(fp, "%31s", username); // 限定最大长度
// 更安全的替代方案(POSIX)
fscanf(fp, "%ms", &username); // 自动分配内存
文件写入
#include <stdio.h>
#include <stdlib.h>
/*
文件写入
fputs
fputc
int fprintf(FILE *stream, const char *format, ...);
*/
typedef struct
{
// 登录用
char user_name[128]; // 账号
char password[128]; // 密码
// 学生信息
char name[128]; // 学生姓名
char sex[5]; // 学生性别
int id; // 学生id
int age; // 学生年龄
int soc; // 学生成绩
} stu_user;
int main(int argc, char const *argv[])
{
// 打开文件
FILE *fp = fopen("hello.txt", "r+");
if (NULL == fp)
{
printf("文件打开失败\n");
exit(-1);
}
// 向文件中写入字符
fputc('H', fp);
fputc('e', fp);
fputc('l', fp);
fputc('l', fp);
fputc('o', fp);
fputc('\n', fp);
// 向文件中写入字符串
fputs("hello\n", fp);
// 向文件中写入格式化字符
stu_user s1[20] = {
{"石昊", "1", "石昊", "男", 1, 18, 100},
{"萧炎", "2", "萧炎", "男", 1, 18, 100},
{"牧尘", "3", "牧尘", "男", 1, 18, 100},
{"唐三", "4", "唐三", "男", 1, 18, 100},
{"林动", "6", "林动", "男", 1, 18, 100},
};
for (size_t i = 0; i < 5; i++)
{
fprintf(fp, "%s\t%s\t%s\t%s\t%d\t%d\t%d\n",
s1[i].user_name, s1[i].password,
s1[i].name, s1[i].sex, s1[i].id,
s1[i].age, s1[i].soc);
}
fclose(fp);
return 0;
}
文件读取
#include <stdio.h>
#include <stdlib.h>
/*
文件写入
fgetc
fgets // 读一行
fsacnf
*/
typedef struct
{
// 登录用
char user_name[128]; // 账号
char password[128]; // 密码
// 学生信息
char name[128]; // 学生姓名
char sex[5]; // 学生性别
int id; // 学生id
int age; // 学生年龄
int soc; // 学生成绩
} stu_user;
int main(int argc, char const *argv[])
{
// 打开文件
FILE *fp = fopen("hello.txt", "r+");
if (NULL == fp)
{
printf("文件打开失败\n");
exit(-1);
}
// 从文件中读取字符
char c = 0;
while ('\n' != (c = fgetc(fp)))
{
fputc(c, stdout);
}
fputc(c, stdout);
// 从文件中读取字符串
char buf[128];
fgets(buf, sizeof(buf), fp);
fputs(buf, stdout);
// 从文件中读取格式化字符
stu_user s1[20];
for (size_t i = 0; i < 5; i++)
{
fscanf(fp, "%s\t%s\t%s\t%s\t%d\t%d\t%d\n",
s1[i].user_name, s1[i].password,
s1[i].name, s1[i].sex, &s1[i].id,
&s1[i].age, &s1[i].soc);
}
// 打印数据
for (size_t i = 0; i < 5; i++)
{
printf("%s\t%s\t%s\t%s\t%d\t%d\t%d\n",
s1[i].user_name, s1[i].password,
s1[i].name, s1[i].sex, s1[i].id,
s1[i].age, s1[i].soc);
}
fclose(fp);
return 0;
}
三、 文件的定位函数
1. fseek
函数
移动文件指针到指定位置
int fseek(FILE *stream, long offset, int whence);
参数解析:
-
whence
定位基准:-
SEEK_SET
:文件起始位置(绝对定位) -
SEEK_CUR
:当前位置(相对定位) -
SEEK_END
:文件末尾(逆向定位)
-
开发陷阱与解决方案:
文本文件偏移失真:
// Windows文本模式下的陷阱
FILE *fp = fopen("log.txt", "r");
fseek(fp, 0, SEEK_END);
long size = ftell(fp); // 可能小于实际字节数(\r\n被转换为\n)
// 解决方案:始终以二进制模式打开
FILE *fp = fopen("log.txt", "rb");
大文件定位溢出:
// 超过2GB文件使用fseeko(POSIX标准)
int fseeko(FILE *stream, off_t offset, int whence);
2. ftell
函数
获取当前文件指针的位置
long ftell(FILE *stream);
断点续传实现:
// 记录传输进度
typedef struct {
char filename[256];
long last_position;
time_t timestamp;
} TransferState;
void save_transfer_state(FILE *log, const TransferState *state) {
fseek(log, 0, SEEK_END);
fwrite(state, sizeof(TransferState), 1, log);
}
3. rewind
函数
将文件指针重置到文件的开头
void rewind(FILE *stream);
// 循环写入Flash存储(延长寿命)
#define MAX_RECORDS 1000
void circular_write(FILE *flash, SensorData *data) {
static int write_count = 0;
if (write_count >= MAX_RECORDS) {
rewind(flash); // 回到起始位置覆盖写入
write_count = 0;
}
fwrite(data, sizeof(SensorData), 1, flash);
write_count++;
}
问题现象 | 可能原因 | 解决方案 |
---|---|---|
fseek后读取数据错误 | 文本模式打开导致偏移计算错误 | 改用二进制模式打开文件 |
ftell返回负数 | 文件超过2GB且未使用64位接口 | 改用ftello/fseeko(POSIX扩展) |
rewind后写入覆盖原有数据 | 未以追加模式打开文件 | 使用"a+"模式打开或手动定位到末尾 |
频繁seek导致性能下降 | 机械硬盘寻道时间过长 | 优化访问顺序或改用SSD存储 |
文件指针位置异常跳变 | 未正确处理缓冲区的fflush | 在关键seek操作前执行fflush |