程序跑完数据就没了,这是很多人学 C 语言时的第一道坎。文件操作就是解决这件事的:把数据存进磁盘,程序结束还在,下次运行还能读回来。下面把文件读写的整套流程走一遍。

为什么要用文件

  • 数据持久化:程序终止后数据不丢,下次运行接着用。
  • 批量数据:上千条记录用手敲要命,文件里存好,几行代码读完。
  • 跨机器迁移:把文件拷走,数据跟着走,不用改程序。

两类文件

文本文件
普通的 .txt,记事本就能编辑,人眼直接看得懂,好维护但占空间大、没什么安全可言。
二进制文件
.bin 之类,数据按内存里的二进制形态存,人眼读不了,但存得多、也更紧凑。

C 语言里文件读写全靠 stdio.h,核心就是那句文件指针:

FILE *fptr;   /* 程序和文件之间的通道 */

打开文件:fopen()

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() {
    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 在源码目录里根本不存在。换成 "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() 往文件写一行字符串。

#include <stdio.h>
#include <stdlib.h>

int main() {
    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() 一次读一整行,配合循环能读到文件尾(EOF)。

#include <stdio.h>
#include <stdlib.h>

int main() {
    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;
}

读文件时有两个细节:

  • 读完一个字符/一段数据后,文件指针会自动移到刚才读到的位置。
  • 读到文件结尾,函数返回 EOF,所以循环用 != NULL!= EOF 判断,别傻乎乎一直读。

读一个整数也很常见(配合上面写入的例子):

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() 能把它挪到任意位置,第三个参数指定从哪算:

fseek(fptr, offset, whence);
常量含义
SEEK_SET从文件开头算
SEEK_END从文件结尾算
SEEK_CUR从当前位置算

offset 可为正可负。比如"替换文末最后 6 个字符前的内容":

fputs(data, fptr);
fputs("\n", fptr);
fseek(fptr, -6, SEEK_END);      /* 回到结尾往前 6 字节 */
fputs("new text", fptr);        /* 从这里覆盖写 */
fclose(fptr);

另外两个常用助手:

  • rewind(fptr):把指针挪回文件开头。
  • ftell(fptr):返回当前指针位置(偏移量)。

读写二进制文件:fwrite() 和 fread()

二进制文件用 rbwb 这些模式打开,写入用 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() {
    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()移动/重置/获取指针位置

文件操作的套路就四步:打开 → 检查 NULL → 读写 → 关闭。走顺了,日志、配置、存档、导入导出都能顺手搞定。