C语言基础-十、文件操作

目录

1、文件路径

[1.1 路径类型](#1.1 路径类型)

[1.2 相对路径符号](#1.2 相对路径符号)

[1.3 路径示例](#1.3 路径示例)

2、转义字符

[2.1 常用转义字符](#2.1 常用转义字符)

[2.2 路径中的转义](#2.2 路径中的转义)

[2.3 转义字符演示](#2.3 转义字符演示)

3、文件读取

[3.1 文件操作基本流程](#3.1 文件操作基本流程)

[3.2 fgetc - 逐字符读取](#3.2 fgetc - 逐字符读取)

fgetc特点

[3.3 fgets - 逐行读取](#3.3 fgets - 逐行读取)

fgets特点

去除换行符

[3.4 fread - 二进制读取](#3.4 fread - 二进制读取)

fread参数说明

fread示例:读取结构体

[3.5 读取函数对比](#3.5 读取函数对比)

4、文件写入

[4.1 文件写入基本流程](#4.1 文件写入基本流程)

[4.2 fputc - 逐字符写入](#4.2 fputc - 逐字符写入)

[4.3 fputs - 字符串写入](#4.3 fputs - 字符串写入)

[4.4 fwrite - 二进制写入](#4.4 fwrite - 二进制写入)

[4.5 fprintf - 格式化写入](#4.5 fprintf - 格式化写入)

[4.6 写入函数对比](#4.6 写入函数对比)

5、文件打开模式

[5.1 文本模式](#5.1 文本模式)

[5.2 二进制模式](#5.2 二进制模式)

[5.3 模式选择指南](#5.3 模式选择指南)

[5.4 文本模式 vs 二进制模式](#5.4 文本模式 vs 二进制模式)

6、文件指针操作

[6.1 fseek - 移动文件指针](#6.1 fseek - 移动文件指针)

[6.2 ftell - 获取当前位置](#6.2 ftell - 获取当前位置)

[6.3 rewind - 回到文件开头](#6.3 rewind - 回到文件开头)

[6.4 文件指针函数对比](#6.4 文件指针函数对比)

7、文件拷贝

[7.1 文本文件拷贝](#7.1 文本文件拷贝)

[7.2 二进制文件拷贝(通用)](#7.2 二进制文件拷贝(通用))

[7.3 带进度显示的文件拷贝](#7.3 带进度显示的文件拷贝)

8、综合练习

[8.1 学生成绩管理系统(文件版)](#8.1 学生成绩管理系统(文件版))

[8.2 日志记录系统](#8.2 日志记录系统)

[8.3 配置文件读写](#8.3 配置文件读写)

9、常见错误与避坑指南

10、最佳实践总结

11、文件操作函数速查表

文件操作:程序与外部存储介质进行数据交换的核心方式

核心价值:数据持久化、配置管理、日志记录、数据交换


1、文件路径

1.1 路径类型

类型 说明 示例 特点
绝对路径 从盘符开始的完整路径 C:\Users\name\file.txt 位置固定,不受程序位置影响
相对路径 相对于程序运行目录的路径 ./data/file.txt 位置灵活,便于项目移植

1.2 相对路径符号

符号 含义 示例
. 当前目录 ./file.txt
.. 上一级目录 ../file.txt
./ 当前目录下的子目录 ./data/file.txt
../ 上一级目录下的内容 ../data/file.txt

1.3 路径示例

cpp 复制代码
#include <stdio.h>

int main() {
    // 绝对路径(Windows)
    FILE *f1 = fopen("C:\\Users\\name\\Desktop\\test.txt", "r");
    
    // 绝对路径(Linux/Mac)
    FILE *f2 = fopen("/home/name/documents/test.txt", "r");
    
    // 相对路径
    FILE *f3 = fopen("./data/test.txt", "r");      // 当前目录下的data文件夹
    FILE *f4 = fopen("../test.txt", "r");          // 上一级目录
    FILE *f5 = fopen("../../test.txt", "r");       // 上两级目录
    
    // 当前运行目录
    FILE *f6 = fopen("test.txt", "r");             // 程序运行目录
    
    return 0;
}

⚠️ 注意 :Windows路径中的\需要转义为\\,或使用/代替


2、转义字符

2.1 常用转义字符

转义字符 含义 ASCII值 说明
\\ 反斜杠 92 路径中需要使用
\' 单引号 39 字符常量中使用
\" 双引号 34 字符串中使用
\n 换行 10 光标移到下一行
\r 回车 13 光标移到行首
\t 制表符 9 相当于Tab键
\b 退格 8 光标后退一格
\0 空字符 0 字符串结束标志
\a 响铃 7 发出提示音
\f 换页 12 打印机换页

2.2 路径中的转义

cpp 复制代码
#include <stdio.h>

int main() {
    //  错误:\会被解释为转义字符
    // FILE *f = fopen("C:\Users\name\file.txt", "r");
    
    // 正确方式1:双反斜杠
    FILE *f1 = fopen("C:\\Users\\name\\file.txt", "r");
    
    // 正确方式2:使用正斜杠(推荐,跨平台)
    FILE *f2 = fopen("C:/Users/name/file.txt", "r");
    
    // 正确方式3:原始字符串(C11标准,部分编译器支持)
    // FILE *f3 = fopen(R"(C:\Users\name\file.txt)", "r");
    
    return 0;
}

2.3 转义字符演示

cpp 复制代码
#include <stdio.h>

int main() {
    printf("换行演示:\n");
    printf("第一行\n第二行\n");
    
    printf("\n制表符演示:\n");
    printf("姓名\t年龄\t城市\n");
    printf("张三\t25\t北京\n");
    
    printf("\n引号演示:\n");
    printf("他说:\"你好\"\n");
    printf("字符:\'A\'\n");
    
    printf("\n反斜杠演示:\n");
    printf("路径:C:\\Users\\name\n");
    
    return 0;
}

3、文件读取

3.1 文件操作基本流程

cpp 复制代码
打开文件 → 读取数据 → 关闭文件
 fopen   fgetc/fgets  fclose
         fread/fscanf

3.2 fgetc - 逐字符读取

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 1. 打开文件
    FILE *file = fopen("test.txt", "r");
    
    // 重要:检查文件是否打开成功
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    // 2. 读取数据(逐字符)
    int ch;  // 必须用int,不能用char(EOF是-1)
    while ((ch = fgetc(file)) != EOF) {
        putchar(ch);
    }
    
    // 3. 关闭文件
    fclose(file);
    
    return 0;
}
fgetc特点
特性 说明
返回值 int类型(成功返回字符ASCII,失败返回EOF)
EOF值 -1(End Of File)
效率 较低(每次读取1字节)
适用 小文件、逐字符处理

3.3 fgets - 逐行读取

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("test.txt", "r");
    
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    char buffer[1024];
    
    // 逐行读取
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);  // fgets保留换行符
    }
    
    fclose(file);
    return 0;
}
fgets特点
特性 说明
返回值 成功返回buffer指针,失败返回NULL
读取范围 读到换行符或达到指定长度-1
换行符 会保留在buffer中
安全性 高(可指定最大读取长度)
适用 文本文件、配置文件
去除换行符
cpp 复制代码
#include <stdio.h>
#include <string.h>

void removeNewline(char *str) {
    size_t len = strlen(str);
    if (len > 0 && str[len - 1] == '\n') {
        str[len - 1] = '\0';
    }
}

int main() {
    FILE *file = fopen("test.txt", "r");
    char buffer[1024];
    
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        removeNewline(buffer);  // 去除换行符
        printf("读取: %s\n", buffer);
    }
    
    fclose(file);
    return 0;
}

3.4 fread - 二进制读取

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("data.bin", "rb");
    
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    char buffer[1024];
    size_t bytesRead;
    
    // 循环读取直到文件结束
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
        // 处理读取到的数据
        for (size_t i = 0; i < bytesRead; i++) {
            printf("%02X ", (unsigned char)buffer[i]);
        }
        printf("\n");
    }
    
    // 检查读取结束原因
    if (feof(file)) {
        printf("正常到达文件末尾\n");
    }
    if (ferror(file)) {
        printf("读取发生错误\n");
    }
    
    fclose(file);
    return 0;
}
fread参数说明
cpp 复制代码
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);

// 参数:
// buffer  - 存储读取数据的缓冲区
// size    - 每个元素的大小(字节)
// count   - 要读取的元素个数
// stream  - 文件指针

// 返回值:实际读取的元素个数
fread示例:读取结构体
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

int main() {
    FILE *file = fopen("students.dat", "rb");
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    Student stu;
    while (fread(&stu, sizeof(Student), 1, file) == 1) {
        printf("学号: %d, 姓名: %s, 成绩: %.1f\n", 
               stu.id, stu.name, stu.score);
    }
    
    fclose(file);
    return 0;
}

3.5 读取函数对比

函数 读取单位 返回值 适用场景
fgetc() 1字符 int(字符或EOF) 逐字符处理
fgets() 1行 char*(或NULL) 文本文件
fread() 任意字节 size_t(读取个数) 二进制文件
fscanf() 格式化 int(成功个数) 格式化输入

4、文件写入

4.1 文件写入基本流程

cpp 复制代码
打开文件 → 写入数据 → 关闭文件
fopen     fputc/fputs  fclose
         fwrite/fprintf

4.2 fputc - 逐字符写入

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("output.txt", "w");
    
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    // 逐字符写入
    const char *str = "Hello, World!";
    for (int i = 0; str[i] != '\0'; i++) {
        fputc(str[i], file);
    }
    
    // 写入换行
    fputc('\n', file);
    
    fclose(file);
    printf("写入完成!\n");
    
    return 0;
}

4.3 fputs - 字符串写入

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("output.txt", "w");
    
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    // 写入字符串(不自动添加换行)
    fputs("第一行内容\n", file);
    fputs("第二行内容\n", file);
    fputs("第三行内容\n", file);
    
    fclose(file);
    printf("写入完成!\n");
    
    return 0;
}

4.4 fwrite - 二进制写入

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

int main() {
    FILE *file = fopen("students.dat", "wb");
    
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    // 写入多个学生
    Student students[] = {
        {1001, "张三", 85.5},
        {1002, "李四", 92.0},
        {1003, "王五", 78.5}
    };
    int count = sizeof(students) / sizeof(students[0]);
    
    // 写入结构体数组
    size_t written = fwrite(students, sizeof(Student), count, file);
    printf("成功写入 %zu 个学生记录\n", written);
    
    fclose(file);
    return 0;
}

4.5 fprintf - 格式化写入

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("report.txt", "w");
    
    if (file == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    
    // 格式化写入(类似printf)
    fprintf(file, "=== 学生成绩报告 ===\n\n");
    fprintf(file, "%-10s %-15s %-10s\n", "学号", "姓名", "成绩");
    fprintf(file, "--------------------------------\n");
    fprintf(file, "%-10d %-15s %-10.1f\n", 1001, "张三", 85.5);
    fprintf(file, "%-10d %-15s %-10.1f\n", 1002, "李四", 92.0);
    fprintf(file, "%-10d %-15s %-10.1f\n", 1003, "王五", 78.5);
    
    fclose(file);
    printf("报告生成完成!\n");
    
    return 0;
}

4.6 写入函数对比

函数 写入单位 返回值 适用场景
fputc() 1字符 int(字符或EOF) 逐字符输出
fputs() 字符串 int(成功非负) 文本行写入
fwrite() 任意字节 size_t(写入个数) 二进制数据
fprintf() 格式化 int(写入字符数) 格式化输出

5、文件打开模式

5.1 文本模式 (Text Mode)

适用场景 :读写 .txt, .c, .log 等可读的文件。
特点 :会自动处理换行符(\n\r\n)。

模式 一句话含义 文件存在时 文件不存在时 能读吗? 能写吗? 清空原内容?
"r" 只读 打开 (保留原样) 报错 (打不开) ✅ 能 ❌ 不能 不操作 (很安全)
"w" 只写 清空! (内容全没) 新建 (空白文件) ❌ 不能 ✅ 能 会清空 (⚠️高危!)
"a" 追加 保留 (接在末尾) 新建 (空白文件) ❌ 不能 ✅ 能 不清空 (很安全)
"r+" 读写 打开 (保留原样) 报错 (打不开) ✅ 能 ✅ 能 不操作 (但可手动改)
"w+" 读写 清空! (内容全没) 新建 (空白文件) ✅ 能 ✅ 能 会清空 (⚠️高危!)
"a+" 读追加 保留 (接在末尾) 新建 (空白文件) ✅ 能 ✅ 能 不清空 (很安全)

5.2 二进制模式 (Binary Mode)

适用场景 :读写 .jpg, .mp4, .exe, .dat 等非文本文件。
特点原样读写 ,一个字节都不改(不会自动转换换行符)。
注意 :除了底层处理方式不同,逻辑与文本模式完全一致

模式 一句话含义 文件存在时 文件不存在时 能读吗? 能写吗? 清空原内容?
"rb" 只读 打开 报错 ✅ 能 ❌ 不能 不操作
"wb" 只写 清空! 新建 ❌ 不能 ✅ 能 会清空 (⚠️高危!)
"ab" 追加 保留 新建 ❌ 不能 ✅ 能 不清空
"rb+" 读写 打开 报错 ✅ 能 ✅ 能 不操作
"wb+" 读写 清空! 新建 ✅ 能 ✅ 能 会清空 (⚠️高危!)
"ab+" 读追加 保留 新建 ✅ 能 ✅ 能 不清空

5.3 模式选择指南

cpp 复制代码
读取已有文件      → "r" 或 "rb"
创建新文件        → "w" 或 "wb"
追加内容          → "a" 或 "ab"
读取并修改        → "r+" 或 "rb+"
完全重写          → "w+" 或 "wb+"
读取并追加        → "a+" 或 "ab+"

文本文件          → 使用文本模式(r, w, a)
二进制文件        → 使用二进制模式(rb, wb, ab)

5.4 文本模式 vs 二进制模式

特性 文本模式 二进制模式
换行符 自动转换(\n ↔ \r\n) 原样存储
EOF处理 特殊处理 按实际字节
适用 文本文件 图片、音频、结构体等
跨平台 可能有差异 完全一致

6、文件指针操作

6.1 fseek - 移动文件指针

cpp 复制代码
#include <stdio.h>

int main() {
    FILE *file = fopen("test.txt", "r");
    
    // fseek(文件指针, 偏移量, 起始位置)
    // 起始位置:SEEK_SET(开头), SEEK_CUR(当前), SEEK_END(末尾)
    
    // 移动到文件开头
    fseek(file, 0, SEEK_SET);
    
    // 移动到文件末尾
    fseek(file, 0, SEEK_END);
    
    // 从当前位置向后移动10字节
    fseek(file, 10, SEEK_CUR);
    
    // 从文件末尾向前移动10字节
    fseek(file, -10, SEEK_END);
    
    fclose(file);
    return 0;
}

6.2 ftell - 获取当前位置

cpp 复制代码
#include <stdio.h>

int main() {
    FILE *file = fopen("test.txt", "r");
    
    // 获取文件大小
    fseek(file, 0, SEEK_END);
    long fileSize = ftell(file);
    printf("文件大小: %ld 字节\n", fileSize);
    
    // 回到开头
    fseek(file, 0, SEEK_SET);
    
    // 获取当前位置
    long pos = ftell(file);
    printf("当前位置: %ld\n", pos);
    
    fclose(file);
    return 0;
}

6.3 rewind - 回到文件开头

cpp 复制代码
#include <stdio.h>

int main() {
    FILE *file = fopen("test.txt", "r");
    
    // 读取一部分
    char buffer[100];
    fgets(buffer, sizeof(buffer), file);
    
    // 回到文件开头(等价于 fseek(file, 0, SEEK_SET))
    rewind(file);
    
    // 重新读取
    fgets(buffer, sizeof(buffer), file);
    
    fclose(file);
    return 0;
}

6.4 文件指针函数对比

函数 功能 返回值 示例
fseek() 移动指针 0成功,-1失败 fseek(f, 10, SEEK_SET)
ftell() 获取位置 当前位置(long) long pos = ftell(f)
rewind() 回到开头 rewind(f)
feof() 检查文件尾 非0表示到末尾 if(feof(f))
ferror() 检查错误 非0表示有错误 if(ferror(f))

7、文件拷贝

7.1 文本文件拷贝

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int copyTextFile(const char *src, const char *dest) {
    FILE *srcFile = fopen(src, "r");
    if (srcFile == NULL) {
        printf("源文件打开失败:%s\n", src);
        return -1;
    }
    
    FILE *destFile = fopen(dest, "w");
    if (destFile == NULL) {
        printf("目标文件创建失败:%s\n", dest);
        fclose(srcFile);
        return -1;
    }
    
    char buffer[1024];
    while (fgets(buffer, sizeof(buffer), srcFile) != NULL) {
        fputs(buffer, destFile);
    }
    
    fclose(srcFile);
    fclose(destFile);
    
    printf("文件拷贝完成:%s -> %s\n", src, dest);
    return 0;
}

int main() {
    copyTextFile("source.txt", "destination.txt");
    return 0;
}

7.2 二进制文件拷贝(通用)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int copyBinaryFile(const char *src, const char *dest) {
    FILE *srcFile = fopen(src, "rb");
    if (srcFile == NULL) {
        printf("源文件打开失败:%s\n", src);
        return -1;
    }
    
    FILE *destFile = fopen(dest, "wb");
    if (destFile == NULL) {
        printf("目标文件创建失败:%s\n", dest);
        fclose(srcFile);
        return -1;
    }
    
    char buffer[4096];  // 4KB缓冲区
    size_t bytesRead;
    size_t totalBytes = 0;
    
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), srcFile)) > 0) {
        size_t bytesWritten = fwrite(buffer, 1, bytesRead, destFile);
        if (bytesWritten != bytesRead) {
            printf("写入失败!\n");
            fclose(srcFile);
            fclose(destFile);
            return -1;
        }
        totalBytes += bytesRead;
    }
    
    fclose(srcFile);
    fclose(destFile);
    
    printf("文件拷贝完成:%s -> %s\n", src, dest);
    printf("总字节数: %zu\n", totalBytes);
    return 0;
}

int main() {
    // 可拷贝任意类型文件
    copyBinaryFile("source.jpg", "copy.jpg");
    copyBinaryFile("video.mp4", "video_copy.mp4");
    copyBinaryFile("data.bin", "data_backup.bin");
    return 0;
}

7.3 带进度显示的文件拷贝

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int copyWithProgress(const char *src, const char *dest) {
    FILE *srcFile = fopen(src, "rb");
    if (srcFile == NULL) {
        return -1;
    }
    
    // 获取文件大小
    fseek(srcFile, 0, SEEK_END);
    long fileSize = ftell(srcFile);
    fseek(srcFile, 0, SEEK_SET);
    
    FILE *destFile = fopen(dest, "wb");
    if (destFile == NULL) {
        fclose(srcFile);
        return -1;
    }
    
    char buffer[4096];
    size_t bytesRead;
    long totalBytes = 0;
    
    printf("开始拷贝...\n");
    
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), srcFile)) > 0) {
        fwrite(buffer, 1, bytesRead, destFile);
        totalBytes += bytesRead;
        
        // 显示进度
        int progress = (int)(totalBytes * 100 / fileSize);
        printf("\r进度: %d%% (%ld/%ld 字节)", progress, totalBytes, fileSize);
        fflush(stdout);
    }
    
    printf("\n拷贝完成!\n");
    
    fclose(srcFile);
    fclose(destFile);
    return 0;
}

8、综合练习

8.1 学生成绩管理系统(文件版)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_STUDENTS 100
#define DATA_FILE "students.dat"

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

// 保存学生数据到文件
int saveStudents(Student students[], int count) {
    FILE *file = fopen(DATA_FILE, "wb");
    if (file == NULL) {
        printf("文件打开失败!\n");
        return -1;
    }
    
    fwrite(&count, sizeof(int), 1, file);
    fwrite(students, sizeof(Student), count, file);
    
    fclose(file);
    printf("保存成功!共 %d 条记录\n", count);
    return 0;
}

// 从文件加载学生数据
int loadStudents(Student students[]) {
    FILE *file = fopen(DATA_FILE, "rb");
    if (file == NULL) {
        printf("文件不存在,创建新数据\n");
        return 0;
    }
    
    int count;
    fread(&count, sizeof(int), 1, file);
    fread(students, sizeof(Student), count, file);
    
    fclose(file);
    printf("加载成功!共 %d 条记录\n", count);
    return count;
}

// 添加学生
int addStudent(Student students[], int count) {
    if (count >= MAX_STUDENTS) {
        printf("学生数量已达上限!\n");
        return count;
    }
    
    Student *s = &students[count];
    printf("请输入学号: ");
    scanf("%d", &s->id);
    printf("请输入姓名: ");
    scanf("%s", s->name);
    printf("请输入成绩: ");
    scanf("%f", &s->score);
    
    return count + 1;
}

// 显示所有学生
void displayStudents(Student students[], int count) {
    if (count == 0) {
        printf("暂无学生记录\n");
        return;
    }
    
    printf("\n%-10s %-20s %-10s\n", "学号", "姓名", "成绩");
    printf("--------------------------------\n");
    for (int i = 0; i < count; i++) {
        printf("%-10d %-20s %-10.1f\n", 
               students[i].id, students[i].name, students[i].score);
    }
}

int main() {
    Student students[MAX_STUDENTS];
    int count = loadStudents(students);
    int choice;
    
    while (1) {
        printf("\n=== 学生成绩管理系统 ===\n");
        printf("1. 添加学生\n");
        printf("2. 显示所有学生\n");
        printf("3. 保存数据\n");
        printf("4. 退出\n");
        printf("请选择: ");
        scanf("%d", &choice);
        
        switch (choice) {
            case 1:
                count = addStudent(students, count);
                break;
            case 2:
                displayStudents(students, count);
                break;
            case 3:
                saveStudents(students, count);
                break;
            case 4:
                // 退出前自动保存
                saveStudents(students, count);
                printf("再见!\n");
                return 0;
            default:
                printf("无效选择\n");
        }
    }
}

8.2 日志记录系统

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#define LOG_FILE "system.log"

// 获取当前时间字符串
void getCurrentTime(char *buffer, size_t size) {
    time_t now = time(NULL);
    struct tm *t = localtime(&now);
    strftime(buffer, size, "%Y-%m-%d %H:%M:%S", t);
}

// 写入日志
void writeLog(const char *level, const char *message) {
    FILE *file = fopen(LOG_FILE, "a");
    if (file == NULL) {
        printf("日志文件打开失败!\n");
        return;
    }
    
    char timeStr[32];
    getCurrentTime(timeStr, sizeof(timeStr));
    
    fprintf(file, "[%s] [%s] %s\n", timeStr, level, message);
    fclose(file);
}

// 日志级别宏
#define LOG_INFO(msg) writeLog("INFO", msg)
#define LOG_WARN(msg) writeLog("WARN", msg)
#define LOG_ERROR(msg) writeLog("ERROR", msg)

int main() {
    LOG_INFO("系统启动");
    LOG_INFO("初始化完成");
    LOG_WARN("内存使用率较高");
    LOG_ERROR("连接数据库失败");
    LOG_INFO("系统关闭");
    
    printf("日志已写入 %s\n", LOG_FILE);
    return 0;
}

8.3 配置文件读写

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CONFIG_FILE "config.ini"

// 读取配置值
int readConfig(const char *key, char *value, size_t valueSize) {
    FILE *file = fopen(CONFIG_FILE, "r");
    if (file == NULL) {
        return -1;
    }
    
    char line[256];
    while (fgets(line, sizeof(line), file) != NULL) {
        // 跳过注释和空行
        if (line[0] == '#' || line[0] == '\n') continue;
        
        char *equals = strchr(line, '=');
        if (equals != NULL) {
            *equals = '\0';
            char *configKey = line;
            char *configValue = equals + 1;
            
            // 去除换行符
            size_t len = strlen(configValue);
            if (len > 0 && configValue[len-1] == '\n') {
                configValue[len-1] = '\0';
            }
            
            if (strcmp(configKey, key) == 0) {
                strncpy(value, configValue, valueSize - 1);
                value[valueSize - 1] = '\0';
                fclose(file);
                return 0;
            }
        }
    }
    
    fclose(file);
    return -1;  // 未找到
}

// 写入配置值
int writeConfig(const char *key, const char *value) {
    // 先读取现有配置
    char lines[100][256];
    int lineCount = 0;
    int found = 0;
    
    FILE *file = fopen(CONFIG_FILE, "r");
    if (file != NULL) {
        char line[256];
        while (fgets(line, sizeof(line), file) != NULL && lineCount < 100) {
            // 检查是否是要更新的键
            if (strncmp(line, key, strlen(key)) == 0 && 
                line[strlen(key)] == '=') {
                snprintf(lines[lineCount++], 256, "%s=%s\n", key, value);
                found = 1;
            } else {
                strcpy(lines[lineCount++], line);
            }
        }
        fclose(file);
    }
    
    // 如果未找到,添加新配置
    if (!found) {
        snprintf(lines[lineCount++], 256, "%s=%s\n", key, value);
    }
    
    // 写回文件
    file = fopen(CONFIG_FILE, "w");
    if (file == NULL) {
        return -1;
    }
    
    for (int i = 0; i < lineCount; i++) {
        fputs(lines[i], file);
    }
    
    fclose(file);
    return 0;
}

int main() {
    // 写入配置
    writeConfig("server_ip", "192.168.1.100");
    writeConfig("port", "8080");
    writeConfig("debug", "true");
    
    // 读取配置
    char value[256];
    if (readConfig("server_ip", value, sizeof(value)) == 0) {
        printf("服务器IP: %s\n", value);
    }
    if (readConfig("port", value, sizeof(value)) == 0) {
        printf("端口: %s\n", value);
    }
    if (readConfig("debug", value, sizeof(value)) == 0) {
        printf("调试模式: %s\n", value);
    }
    
    return 0;
}

9、常见错误与避坑指南

错误类型 错误示例 后果 正确做法
未检查 fopen FILE *f = fopen(...); 直接使用 可能崩溃 if(f==NULL) 检查
忘记 fclose 打开后不关闭 资源泄漏 配对使用fclose
模式错误 用"r"打开不存在的文件 打开失败 根据需求选模式
文本/二进制混用 文本模式读写二进制 数据损坏 二进制用rb/wb
char接收EOF char c = fgetc(f) 无法判断EOF int c接收
路径转义错误 "C:\user\file" 路径错误 "C:\\user\\file"
缓冲区溢出 fgets(buf, 100, f) 但buf只有50 内存损坏 sizeof(buf)
未检查返回值 不检查fread/fwrite返回值 数据不完整 检查返回值
重复关闭 fclose(f); fclose(f); 未定义行为 关闭后置NULL

10、最佳实践总结

复制代码
✅ 文件操作原则:
1. 打开文件后立即检查NULL
2. 使用完毕后及时fclose
3. 二进制文件用二进制模式
4. 检查所有函数的返回值
5. 使用合适的缓冲区大小
6. 路径使用正斜杠或双反斜杠
7. 重要数据操作前备份

✅ 代码规范:
1. 封装文件操作函数
2. 统一错误处理方式
3. 添加必要的注释
4. 使用宏定义文件路径
5. 日志记录关键操作

✅ 安全建议:
1. 验证文件路径合法性
2. 限制文件大小
3. 检查磁盘空间
4. 处理并发访问
5. 敏感数据加密存储

11、文件操作函数速查表

函数 头文件 功能 返回值
fopen() stdio.h 打开文件 FILE*
fclose() stdio.h 关闭文件 int
fgetc() stdio.h 读字符 int
fputc() stdio.h 写字符 int
fgets() stdio.h 读字符串 char*
fputs() stdio.h 写字符串 int
fread() stdio.h 二进制读 size_t
fwrite() stdio.h 二进制写 size_t
fscanf() stdio.h 格式化读 int
fprintf() stdio.h 格式化写 int
fseek() stdio.h 移动指针 int
ftell() stdio.h 获取位置 long
rewind() stdio.h 回到开头 void
feof() stdio.h 检查文件尾 int
ferror() stdio.h 检查错误 int
remove() stdio.h 删除文件 int
rename() stdio.h 重命名文件 int
相关推荐
云泽8082 小时前
C++ 多态入门:虚函数、重写、虚析构及 override/final 实战指南(附腾讯面试题)
开发语言·c++
yanghuashuiyue3 小时前
lambda+sealed+record
java·开发语言
yzx9910133 小时前
Python数据结构入门指南:从基础到实践
开发语言·数据结构·python
衍生星球4 小时前
【JSP程序设计】Servlet对象 — page对象
java·开发语言·servlet·jsp·jsp程序设计
扶苏瑾4 小时前
线程安全问题的产生原因与解决方案
java·开发语言·jvm
QQ24391974 小时前
语言在线考试与学习交流网页平台信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·spring boot·sql·学习·java-ee
小小小米粒4 小时前
函数式接口 + Lambda = 方法逻辑的 “插拔式解耦”
开发语言·python·算法
风吹乱了我的头发~5 小时前
Day31:2026年2月21日打卡
开发语言·c++·算法
蜜獾云6 小时前
JAVA面试题速记-第1期-java基础
java·开发语言