目录
[1.1 路径类型](#1.1 路径类型)
[1.2 相对路径符号](#1.2 相对路径符号)
[1.3 路径示例](#1.3 路径示例)
[2.1 常用转义字符](#2.1 常用转义字符)
[2.2 路径中的转义](#2.2 路径中的转义)
[2.3 转义字符演示](#2.3 转义字符演示)
[3.1 文件操作基本流程](#3.1 文件操作基本流程)
[3.2 fgetc - 逐字符读取](#3.2 fgetc - 逐字符读取)
[3.3 fgets - 逐行读取](#3.3 fgets - 逐行读取)
[3.4 fread - 二进制读取](#3.4 fread - 二进制读取)
[3.5 读取函数对比](#3.5 读取函数对比)
[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.1 文本模式](#5.1 文本模式)
[5.2 二进制模式](#5.2 二进制模式)
[5.3 模式选择指南](#5.3 模式选择指南)
[5.4 文本模式 vs 二进制模式](#5.4 文本模式 vs 二进制模式)
[6.1 fseek - 移动文件指针](#6.1 fseek - 移动文件指针)
[6.2 ftell - 获取当前位置](#6.2 ftell - 获取当前位置)
[6.3 rewind - 回到文件开头](#6.3 rewind - 回到文件开头)
[6.4 文件指针函数对比](#6.4 文件指针函数对比)
[7.1 文本文件拷贝](#7.1 文本文件拷贝)
[7.2 二进制文件拷贝(通用)](#7.2 二进制文件拷贝(通用))
[7.3 带进度显示的文件拷贝](#7.3 带进度显示的文件拷贝)
[8.1 学生成绩管理系统(文件版)](#8.1 学生成绩管理系统(文件版))
[8.2 日志记录系统](#8.2 日志记录系统)
[8.3 配置文件读写](#8.3 配置文件读写)
文件操作:程序与外部存储介质进行数据交换的核心方式
核心价值:数据持久化、配置管理、日志记录、数据交换
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 |