1. FILE* 是什么
C 的文件操作都围绕 FILE *(文件指针)展开。fopen 返回它,之后所有读写都通过它指定目标文件。
程序启动时默认打开三个流,类型都是 FILE *:
cpp
extern FILE *stdin; // 标准输入(键盘)
extern FILE *stdout; // 标准输出(显示器)
extern FILE *stderr; // 标准错误(显示器)
所以 printf(...) 等价于 fprintf(stdout, ...)。
2. 打开方式
| 模式 | 含义 | 文件不存在 | 文件已存在 |
|---|---|---|---|
r |
只读 | 返回 NULL | 位置在开头 |
r+ |
读 + 写 | 返回 NULL | 不清空,位置在开头 |
w |
只写 | 创建 | 清空 |
w+ |
写 + 读 | 创建 | 清空 |
a |
追加写 | 创建 | 不清空,写永远在末尾 |
a+ |
追加写 + 读 | 创建 | 不清空,读从开头,写在末尾 |
+ 的含义就是"再加上另一种操作",有'+'的时候,同一个 FILE * 既能读也能写 。注意 w 和 w+ 打开已有文件会直接清空内容。
3. 基本流程
cpp
FILE *fp = fopen("myfile", "w");
if (!fp) {
perror("fopen"); // 必须检查返回值
return 1;
}
// 读写...
fclose(fp); // 用完必须关闭
如果写的是相对路径,系统会基于进程的当前工作目录(cwd)解析成绝对路径。
4. 写文件:fwrite
cpp
const char *msg = "hello bit!\n";
fwrite(msg, strlen(msg), 1, fp);
参数依次是:数据地址、单个元素大小、元素个数、文件指针。
写 strlen(msg) 而不是 strlen(msg)+1,避免把 \0 写进文件。
'\0'是C语言的规定,与文件无关。不能直接把'\0'写进文件。
5. 读文件:fread
cpp
char buf[1024];
size_t s = fread(buf, 1, sizeof(buf) - 1, fp);
if (s > 0) {
buf[s] = 0;
printf("%s", buf);
}
- 返回值是实际读到的元素个数。元素大小为 1 时,等于字节数。
fread不会补\0,打印字符串前要手动buf[s] = 0,所以读sizeof(buf) - 1留出一个位置,避免越界。
6. 位置指针(文件偏移量)
每个打开的文件都有一个"当前读写位置",是一个数字 ,表示读写到第几个字节。读和写共用同一个位置。
fp:指向文件对象,决定操作哪个文件位置指针:记录读写到哪个位置 ,由
ftell查看,fseek/rewind修改类比:
fp是书,位置指针是书签。
| 函数 | 作用 |
|---|---|
ftell(fp) |
返回当前位置(距开头的字节数) |
fseek(fp, offset, whence) |
移动位置,whence 取 SEEK_SET / SEEK_CUR / SEEK_END |
rewind(fp) |
回到开头,等价于 fseek(fp, 0, SEEK_SET) |
7. 为什么 w+ 写完要 rewind
写完后位置停在末尾,直接读后面没有内容:
cpp
FILE *fp = fopen("myfile", "w+");
fwrite("hello\n", 1, 6, fp);
printf("%ld\n", ftell(fp)); // 6
rewind(fp); // 回到开头
printf("%ld\n", ftell(fp)); // 0
char buf[100];
size_t s = fread(buf, 1, 99, fp); // 读到 6 字节
这不是 + 本身的特性,而是"读写共用一个位置"的结果。+ 只是让你能在同一个文件上先写后读。
另外 C 标准规定:同一个文件读写切换之间,必须调用 fflush 或定位函数(fseek、rewind),否则行为未定义。