若要把计算结果、用户设置或日志等保存下来,供本程序下次运行或其他程序使用,应把数据写入磁盘上的文件,并在需要时再从文件中读回。仅存放在内存(如局部变量、未写盘的缓冲区)中的内容,在程序正常结束或异常退出后通常会丢失。C 语言通过 stdio.h 中的文件流接口完成文本与二进制文件的打开、读写与关闭。打开失败时的处理见错误处理;结构体二进制读写须注意对齐与填充。
使用文件的目的
- 持久化:程序退出后数据仍可保留。
- 批量数据:大量记录可一次读入处理,无需交互输入。
- 可移植性:通过复制文件在不同环境间迁移数据。
文本文件与二进制文件
- 文本文件
- 以字符形式存储,可用普通文本编辑器查看;可读性好,体积与解析开销相对较大。
- 二进制文件
- 按内存中的字节序列直接存储;不可直接阅读,但适合保存数值数组或结构体块。
操作文件时通常使用指向 FILE 的指针作为句柄:
FILE *fptr;打开文件:fopen()
FILE * fopen(const char *file_name, const char *access_mode);| 模式 | 含义 | 文件不存在时 |
|---|---|---|
r | 只读 | 返回 NULL |
w | 只写,内容清空重写 | 新建文件 |
a | 追加,写到末尾 | 新建文件 |
r+ | 读写 | 返回 NULL |
w+ | 读写,内容清空重写 | 新建文件 |
a+ | 读写追加 | 新建文件 |
rb / wb / ab… | 上述模式 + 二进制方式 | 同文本规则 |
应始终检查返回值;NULL 表示打开失败:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fptr;
fptr = fopen("filename.txt", "r");
if (fptr == NULL) {
printf("The file is not opened.\n");
exit(1);
}
return 0;
}输出:
The file is not opened.若当前目录不存在 filename.txt,以 r 模式打开将失败。以 w 模式可在不存在时创建文件:
FILE *fptr = fopen("file.txt", "w");
if (fptr == NULL)
printf("The file is not opened.\n");
else
printf("The file is created successfully.\n");在 Windows 路径字符串中,反斜杠应写成 \\,例如 "E:\\cprogram\\new.txt"。
写文本文件:fprintf() 和 fputs()
fprintf 在格式上与 printf 相同,额外接受文件指针;fputs 写入一行字符串(不含自动换行,需自行写入 \n)。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fptr;
char data[50] = "This is a line of text.";
fptr = fopen("file.txt", "w");
if (fptr == NULL) {
printf("The file is not opened.\n");
exit(1);
}
fputs(data, fptr);
fputs("\n", fptr);
fprintf(fptr, "The number is %d\n", 42);
fclose(fptr);
return 0;
}从标准输入读入数值再写入文件的示例:
int num;
FILE *fptr = fopen("program.txt", "w");
if (fptr == NULL) { printf("Error!\n"); exit(1); }
printf("Enter num: ");
scanf("%d", &num);
fprintf(fptr, "%d", num);
fclose(fptr);读文本文件:fscanf() 和 fgets()
fscanf 对应 scanf;fgets 每次读取一行,适合循环读取直至返回 NULL。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fptr;
char data[50];
fptr = fopen("file.txt", "r");
if (fptr == NULL) {
printf("file.txt failed to open.\n");
exit(1);
}
while (fgets(data, 50, fptr) != NULL) {
printf("%s", data);
}
fclose(fptr);
return 0;
}- 每次读取后,文件位置指针自动前移。
- 到达文件尾时,
fgets返回NULL,循环应据此结束。
int num;
FILE *fptr = fopen("program.txt", "r");
if (fptr == NULL) { printf("Error! opening file\n"); exit(1); }
fscanf(fptr, "%d", &num);
printf("Value of n = %d\n", num);
fclose(fptr);关闭文件:fclose()
fclose(fptr);未关闭的流可能导致缓冲区未刷新或句柄泄漏,长期运行的程序应确保配对关闭。
定位:fseek()、rewind()、ftell()
fseek(fptr, offset, whence);| 常量 | 含义 |
|---|---|
SEEK_SET | 从文件开头算 |
SEEK_END | 从文件结尾算 |
SEEK_CUR | 从当前位置算 |
fputs(data, fptr);
fputs("\n", fptr);
fseek(fptr, -6, SEEK_END);
fputs("new text", fptr);
fclose(fptr);rewind(fptr):将位置设回文件开头。ftell(fptr):返回当前偏移量。
二进制读写:fwrite() 和 fread()
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);ptr:缓冲区地址size:每个元素的字节数nmemb:元素个数stream:文件指针
#include <stdio.h>
#include <stdlib.h>
struct threeNum {
int n1, n2, n3;
};
int main(void) {
int n;
struct threeNum num;
FILE *fptr;
fptr = fopen("binaryFile.bin", "wb");
if (fptr == NULL) { printf("Error! opening file\n"); exit(1); }
for (n = 1; n < 5; ++n) {
num.n1 = n;
num.n2 = 5 * n;
num.n3 = 5 * n + 1;
fwrite(&num, sizeof(struct threeNum), 1, fptr);
}
fclose(fptr);
fptr = fopen("binaryFile.bin", "rb");
if (fptr == NULL) { printf("Error! opening file\n"); exit(1); }
for (n = 1; n < 5; ++n) {
fread(&num, sizeof(struct threeNum), 1, fptr);
printf("n1: %d\tn2: %d\tn3: %d\n", num.n1, num.n2, num.n3);
}
fclose(fptr);
return 0;
}输出:
n1: 1 n2: 5 n3: 6
n1: 2 n2: 10 n3: 11
n1: 3 n2: 15 n3: 16
n1: 4 n2: 20 n3: 21结合 fseek 可从文件尾部向前按记录大小逐步读取:
fseek(fptr, -sizeof(struct threeNum), SEEK_END);
for (n = 1; n < 5; ++n) {
fread(&num, sizeof(struct threeNum), 1, fptr);
printf("n1: %d\tn2: %d\tn3: %d\n", num.n1, num.n2, num.n3);
fseek(fptr, -2 * sizeof(struct threeNum), SEEK_CUR);
}常用函数一览
| 函数 | 作用 |
|---|---|
fopen() | 打开/创建文件 |
fclose() | 关闭文件 |
fprintf() / fscanf() | 格式化读写文本 |
fputs() / fgets() | 写/读整行 |
fputc() / fgetc() | 写/读单个字符 |
fwrite() / fread() | 二进制读写 |
fseek() / rewind() / ftell() | 移动/重置/获取指针位置 |
文件操作的一般步骤为:打开并检查返回值、读写、关闭。本节为持久化数据打下基础;下一节讨论多维数组在内存中的布局与用法。