1. 文件打开与关闭的基本概念
- 打开(Open):占用系统资源(如文件、设备等)。
- 关闭(Close):释放已占用的资源,避免其他程序无法访问。
- 在操作系统底层(文件描述符层面),通过
open()和close()实现资源的占用与释放。 - 在 C 标准库中,使用
FILE*流指针进行操作,接口形式不同但本质一致。
重要提醒:程序结束时系统会自动释放资源,但这不是良好编程习惯。应主动调用关闭函数以确保资源及时释放,防止资源泄漏或被恶意利用。
2. 标准库中的 fopen() 函数
基本信息
- 头文件:
<stdio.h> - 函数原型:
FILE *fopen(const char *pathname, const char *mode); - 参数说明:
pathname:文件路径(字符串形式)mode:打开模式(字符串),决定对文件的操作权限和行为
- 返回值:成功返回
FILE*指针;失败返回NULL
注意:
fopen()只有两个参数,不像系统调用open()那样有第三个权限参数(用于创建文件时指定权限)。
3. 文件打开模式详解
| 模式 | 含义 | 文件必须存在? | 是否清空内容? | 是否可读? | 是否可写? | 文件不存在时行为 |
|---|---|---|---|---|---|---|
"r" |
只读 | ✅ 是 | ❌ 否 | ✅ | ❌ | 打开失败 |
"r+" |
读写 | ✅ 是 | ❌ 否 | ✅ | ✅ | 打开失败 |
"w" |
只写 | ❌ 否 | ✅ 是 | ❌ | ✅ | 自动创建新文件 |
"w+" |
读写 | ❌ 否 | ✅ 是 | ✅ | ✅ | 自动创建新文件 |
"a" |
追加写 | ❌ 否 | ❌ 否 | ❌ | ✅ | 自动创建新文件 |
"a+" |
追加读写 | ❌ 否 | ❌ 否 | ✅ | ✅ | 自动创建新文件 |
关键区别说明:
"r"/"r+":要求文件必须存在,否则fopen()返回NULL。"w"/"w+":- 若文件存在 → 清空内容后从头开始写入。
- 若文件不存在 → 创建新文件。
"a"/"a+":- 写入操作始终追加到文件末尾(即使使用
fwrite或fprintf)。 - 不会清空已有内容。
- 支持读操作(
"a+"),但写操作仍只能在末尾。
- 写入操作始终追加到文件末尾(即使使用
实践验证:
- 使用
"r"打开不存在的test.txt→ 失败。- 使用
"w"打开不存在的test.txt→ 成功并创建文件。- 若
test.txt原有内容为"hello",用"w"打开后内容被清空。- 若用
"a"打开,原有内容保留,后续写入追加至末尾。
4. fopen() 与系统调用 open() 的对比
| 特性 | fopen()(标准库) |
open()(系统调用) |
|---|---|---|
| 接口层级 | 高层(带缓冲) | 底层(无缓冲) |
| 参数数量 | 2 个 | 至少 2 个,创建文件时需第 3 个(权限) |
| 文件不存在时创建 | 由模式隐式控制(如 "w"、"a") |
需显式传 O_CREAT 标志 + 权限参数 |
| 返回类型 | FILE*(流指针) |
int(文件描述符) |
| 缓冲机制 | 有(提升性能) | 无(直接系统调用) |
对应关系示例:
"r"→open(path, O_RDONLY)"w"→open(path, O_WRONLY | O_CREAT | O_TRUNC, 0664)"a"→open(path, O_WRONLY | O_CREAT | O_APPEND, 0664)
5. 文件关闭:fclose()
- 头文件:
<stdio.h> - 函数原型:
int fclose(FILE *stream); - 功能:
- 刷新缓冲区(将未写入的数据同步到磁盘)
- 释放
FILE*结构体内存 - 关闭底层文件描述符
- 返回值:
- 成功:
0 - 失败:
EOF(通常为-1)
- 成功:
最佳实践:每次
fopen()后都应配对调用fclose(),即使程序即将退出。
6. 错误处理机制
C 标准库通过全局变量 errno(全称:error number)记录最近一次系统或库函数调用的错误码。
6.1 errno 特性
- 类型:
int - 作用域:全局变量(定义在
<errno.h>中) - 行为:
- 成功时通常为
0 - 失败时被设为特定错误码(如
2表示 “No such file or directory”) - 每次调用可能覆盖,因此应在出错后立即检查
- 成功时通常为
6.2 错误信息输出函数
(1) perror(const char *s)
- 头文件:
<stdio.h> - 功能:自动读取当前
errno,输出格式为:s: 错误描述文本 - 示例:
FILE *fp = fopen("nonexist.txt", "r"); if (fp == NULL) { perror("fopen failed"); // 输出:fopen failed: No such file or directory }
(2) strerror(int errnum)
- 头文件:
<string.h> - 功能:根据错误码返回对应的错误描述字符串(
char*) - 示例:
#include <errno.h> #include <string.h> FILE *fp = fopen("nonexist.txt", "r"); if (fp == NULL) { printf("Error %d: %s\n", errno, strerror(errno)); // 输出:Error 2: No such file or directory }
对比:
perror()更简洁,适合快速调试。strerror()更灵活,可嵌入自定义日志格式。
7. 文件权限与 umask
7.1 默认文件权限
- 当使用
"w"或"a"创建新文件时,标准库默认使用权限0664(八进制)。 - 实际权限 = 指定权限 & ~umask
- 例如:若 umask =
0022,则0666 & ~0022 = 0644
- 例如:若 umask =
- 常见 umask 值:
- 普通用户:
0022→ 新文件权限为rw-r--r--(644) - root 用户:
0022或0002
- 普通用户:
7.2 权限表示法
- 符号表示:
rwxrwxrwx(读=4,写=2,执行=1) - 数值表示:八进制数,如
0644、0755- 注意:C 代码中应写作
0664(前导零表示八进制)
- 注意:C 代码中应写作
提示:可通过
umask 000临时禁用掩码,使创建的文件完全按指定权限设置。
8. 完整代码示例
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
// 方法1:使用 perror
perror("fopen failed");
// 方法2:使用 strerror + errno
fprintf(stderr, "Error code: %d, Message: %s\n",
errno, strerror(errno));
return EXIT_FAILURE;
}
printf("File opened successfully!\n");
// ... 进行文件读写操作 ...
if (fclose(fp) != 0) {
perror("fclose failed");
return EXIT_FAILURE;
}
printf("File closed successfully.\n");
return EXIT_SUCCESS;
}