【C语言学习笔记】:文件操作

一、文件读写操作

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