若要把计算结果、用户设置或日志等保存下来,供本程序下次运行或其他程序使用,应把数据写入磁盘上的文件,并在需要时再从文件中读回。仅存放在内存(如局部变量、未写盘的缓冲区)中的内容,在程序正常结束或异常退出后通常会丢失。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()移动/重置/获取指针位置

文件操作的一般步骤为:打开并检查返回值、读写、关闭。本节为持久化数据打下基础;下一节讨论多维数组在内存中的布局与用法。