1.8. 文件输入输出
C 语言通过文件指针(FILE *)和标准库函数进行文件操作。
1.8.1. 文件操作基础
1.8.1.1. 核心步骤
1. 打开文件(fopen) → 获取 FILE *
2. 读写文件(fprintf / fscanf / fread / fwrite)
3. 关闭文件(fclose)
文件操作模板
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("文件打开失败!\n");
return 1;
}
// 读写操作...
fclose(fp); // 关闭文件
return 0;
}
重要
始终检查 ``fopen`` 的返回值! 文件可能因为权限、磁盘满、路径错误等无法打开。
1.8.2. 文件打开模式
模式 |
含义 |
文件存在 |
文件不存在 |
|---|---|---|---|
|
只读 |
正常打开 |
出错 |
|
只写 |
清空内容 |
创建新文件 |
|
追加 |
从末尾写入 |
创建新文件 |
|
读写 |
正常打开 |
出错 |
|
读写 |
清空内容 |
创建新文件 |
|
读追加 |
从末尾写入 |
创建新文件 |
|
二进制模式 |
见上 |
见上 |
备注
文本模式下,VC10 会将换行 \n 转换为 \r\n``(写入时)或反向转换(读取时)。
二进制模式不作转换。处理非文本文件请用 ``"b" 模式。
1.8.3. 文本文件读写
1.8.3.1. fprintf / fscanf
FILE *fp = fopen("students.txt", "w");
if (fp != NULL) {
fprintf(fp, "%d %s %.1f\n", 1001, "Alice", 95.5f);
fprintf(fp, "%d %s %.1f\n", 1002, "Bob", 87.0f);
fclose(fp);
}
// 读取
fp = fopen("students.txt", "r");
if (fp != NULL) {
int id;
char name[50];
float score;
while (fscanf(fp, "%d %s %f", &id, name, &score) == 3) {
printf("ID=%d, Name=%s, Score=%.1f\n", id, name, score);
}
fclose(fp);
}
1.8.3.2. fgets / fputs
// 写入字符串
FILE *fp = fopen("data.txt", "w");
fputs("第一行文本\n", fp);
fputs("第二行文本\n", fp);
fclose(fp);
// 逐行读取
char line[256];
fp = fopen("data.txt", "r");
while (fgets(line, sizeof(line), fp) != NULL) {
// fgets 会保留换行符
printf("读取: %s", line);
}
fclose(fp);
小技巧
fgets(buf, size, fp) 是**安全**的——最多读取 size - 1 个字符,
自动添加 \0。对比不安全的 gets(buf)``(无大小限制),
应始终使用 ``fgets。
1.8.3.3. fgetc / fputc
// 逐个字符复制文件
FILE *src = fopen("source.txt", "r");
FILE *dst = fopen("copy.txt", "w");
if (src != NULL && dst != NULL) {
int ch; // 注意是 int 不是 char!
while ((ch = fgetc(src)) != EOF) {
fputc(ch, dst);
}
fclose(src);
fclose(dst);
}
警告
fgetc 的返回值是 int 而非 char,因为 EOF 是 -1,
用 char 无法区分合法字符和文件结束。
1.8.4. 二进制文件读写
1.8.4.1. fread / fwrite
#include <stdio.h>
typedef struct {
int id;
char name[20];
float score;
} Record;
int main(void) {
Record records[] = {
{1001, "Alice", 95.5f},
{1002, "Bob", 87.0f},
{1003, "Eve", 92.3f}
};
int count = 3;
// 写入二进制文件
FILE *fp = fopen("records.dat", "wb");
if (fp != NULL) {
fwrite(records, sizeof(Record), count, fp);
fclose(fp);
}
// 读取二进制文件
Record buf[10];
fp = fopen("records.dat", "rb");
if (fp != NULL) {
int n = fread(buf, sizeof(Record), 10, fp);
for (int i = 0; i < n; i++) {
printf("%d %s %.1f\n",
buf[i].id, buf[i].name, buf[i].score);
}
fclose(fp);
}
return 0;
}
小技巧
fread / fwrite 的参数:
fread(缓冲区, 单个元素大小, 元素个数, 文件指针)
返回实际读取/写入的元素个数。
1.8.5. 文件定位
#include <stdio.h>
FILE *fp = fopen("data.bin", "rb");
if (fp == NULL) return 1;
// 获取当前读写位置(相对于文件开头)
long pos = ftell(fp);
printf("当前位置: %ld\n", pos);
// 定位到文件开头偏移 10 字节处
fseek(fp, 10, SEEK_SET); // 从开头算起
// 定位到文件末尾前 5 字节
fseek(fp, -5, SEEK_END); // 从末尾算起
// 定位到当前位置后移 3 字节
fseek(fp, 3, SEEK_CUR); // 从当前位置算起
// 获取文件大小
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
printf("文件大小: %ld 字节\n", file_size);
// 重置到文件开头
rewind(fp); // 等价于 fseek(fp, 0, SEEK_SET)
fclose(fp);
函数 |
功能 |
返回值 |
|---|---|---|
|
获取当前偏移 |
|
|
设置文件位置 |
成功 0,失败非 0 |
|
重置到文件开头 |
无返回值 |
|
检查是否文件尾 |
非 0 表示到达文件尾 |
1.8.6. 错误处理
#include <stdio.h>
#include <errno.h>
#include <string.h>
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
// 使用全局变量 errno 和 perror
printf("错误码: %d\n", errno);
perror("fopen"); // 输出: fopen: No such file or directory
// 或使用 strerror
printf("错误信息: %s\n", strerror(errno));
return 1;
}
// 检查文件尾
while (!feof(fp)) {
int ch = fgetc(fp);
if (ch != EOF) {
putchar(ch);
}
}
1.8.7. VC10 文件操作注意事项
// 1. 文件路径中的反斜杠
FILE *fp = fopen("C:\\temp\\data.txt", "r"); // 需要转义
// 2. fopen_s 安全版本
FILE *fp_safe;
errno_t err = fopen_s(&fp_safe, "test.txt", "r");
if (err != 0) {
printf("打开失败,错误码: %d\n", err);
}
// 3. _CRT_SECURE_NO_WARNINGS
// 若不想用 _s 版本,可在 stdafx.h 或项目属性中定义:
// #define _CRT_SECURE_NO_WARNINGS
// 4. 文件缓冲区设置
FILE *fp2 = fopen("large.txt", "r");
char buffer[8192];
setvbuf(fp2, buffer, _IOFBF, sizeof(buffer)); // 设置缓冲区
// _IOFBF: 全缓冲 _IOLBF: 行缓冲 _IONBF: 无缓冲
小技巧
VC10 项目属性中修改 配置属性 → C/C++ → 预处理器 → 预处理器定义,
添加 _CRT_SECURE_NO_WARNINGS 可全局禁用安全函数警告。