011-C语言文件操作

plana_lab 发布于 16 天前 115 次阅读



前面我们学习了:

数组
函数
指针
结构体
动态内存

这些数据默认都存在于:

内存中。

例如:

int a = 10;

程序运行时:

a
↓
内存

但是程序结束以后:

程序结束
↓
内存释放
↓
a 消失

如果希望数据能够在程序关闭之后仍然存在,就需要把数据保存到:

文件。

例如:

程序运行
   ↓
读取文件
   ↓
修改数据
   ↓
保存文件
   ↓
程序结束
   ↓
数据仍然存在

一、什么是文件

文件就是保存在存储设备上的数据。

例如:

student.txt
data.txt
config.txt
save.dat

程序可以:

创建文件
读取文件
写入文件
修改文件
追加内容
关闭文件

C 语言主要通过:

FILE *

来操作文件。

需要:

#include <stdio.h>

二、FILE 是什么

C 语言使用:

FILE

来表示一个文件流。

例如:

FILE *fp;

这里:

fp
↓
指向一个 FILE
↓
代表打开的文件

可以简单理解成:

磁盘上的文件
      ↓
    fopen()
      ↓
   FILE *
      ↓
    fp
      ↓
程序操作文件

三、打开文件:fopen

基本格式:

fopen("文件名", "模式");

例如:

FILE *fp = fopen("test.txt", "w");

这里:

test.txt

是文件名。

w

是打开模式。


四、fopen 的返回值

如果打开成功:

fopen(...)

会返回一个:

FILE *

如果失败:

fopen(...)

会返回:

NULL

所以应该检查:

FILE *fp = fopen("test.txt", "w");

if (fp == NULL) {
    printf("文件打开失败\n");
    return 1;
}

这个思想和前面:

malloc()

返回 NULL 时检查,是一样的。


五、关闭文件:fclose

打开文件以后,用完需要:

fclose(fp);

例如:

FILE *fp = fopen("test.txt", "w");

if (fp == NULL) {
    return 1;
}

fclose(fp);

文件操作的基本流程:

fopen
 ↓
使用文件
 ↓
fclose

可以记成:

打开
 ↓
读/写
 ↓
关闭

六、文件打开模式

fopen() 第二个参数非常重要。

常见模式:

r
w
a
r+
w+
a+

还有二进制模式:

rb
wb
ab
rb+
wb+
ab+

先重点学习:

r
w
a

七、r:读取文件

FILE *fp = fopen("test.txt", "r");

r:

read

表示读取。

如果文件不存在:

打开失败

例如:

FILE *fp = fopen("test.txt", "r");

if (fp == NULL) {
    printf("文件不存在\n");
    return 1;
}

八、w:写入文件

FILE *fp = fopen("test.txt", "w");

w:

write

表示写入。

如果文件不存在:

创建文件

如果文件已经存在:

原来的内容会被清空

这一点非常重要。

例如原来的:

test.txt

hello
world

执行:

fopen("test.txt", "w");

之后再写入:

abc

文件会变成:

abc

而不是:

hello
world
abc

九、a:追加

FILE *fp = fopen("test.txt", "a");

a:

append

表示追加。

如果文件不存在:

创建文件

如果文件已经存在:

保留原来的内容

新内容会写到文件末尾。

例如原本:

hello
world

执行:

fprintf(fp, "abc\n");

变成:

hello
world
abc

十、fputc:写入一个字符

可以使用:

fputc()

格式:

fputc(字符, 文件指针);

例如:

FILE *fp = fopen("test.txt", "w");

if (fp == NULL) {
    return 1;
}

fputc('A', fp);
fputc('B', fp);
fputc('C', fp);

fclose(fp);

文件内容:

ABC

十一、fgetc:读取一个字符

对应:

fgetc()

例如:

FILE *fp = fopen("test.txt", "r");

if (fp == NULL) {
    return 1;
}

char c = fgetc(fp);

printf("%c\n", c);

fclose(fp);

如果文件内容是:

Hello

那么第一次 fgetc() 会读取:

H

再次调用:

c = fgetc(fp);

会读取:

e

因为文件有一个:

当前位置。


十二、循环读取整个文件

可以使用:

while ((c = fgetc(fp)) != EOF)

例如:

#include <stdio.h>

int main() {

    FILE *fp = fopen("test.txt", "r");

    if (fp == NULL) {
        return 1;
    }

    int c;

    while ((c = fgetc(fp)) != EOF) {
        putchar(c);
    }

    fclose(fp);

    return 0;
}

这里:

fgetc()
↓
读取一个字符
↓
文件结束?
↓
不是 → 继续
↓
是 → EOF

十三、为什么 c 要用 int

注意这里:

int c;

而不是:

char c;

因为:

fgetc()

需要能够返回:

普通字符

以及:

EOF

所以通常使用:

int

保存 fgetc() 的返回值。

这是文件操作中一个很重要的细节。


十四、fputs:写入字符串

可以使用:

fputs()

例如:

FILE *fp = fopen("test.txt", "w");

if (fp == NULL) {
    return 1;
}

fputs("Hello World\n", fp);
fputs("C Language\n", fp);

fclose(fp);

文件:

Hello World
C Language

十五、fgets:读取一行

与 fputs() 对应:

fgets()

例如:

char buffer[100];

fgets(buffer, 100, fp);

意思是:

最多读取 99 个字符

并预留:

\0

结束字符串。


十六、使用 fgets 读取整个文件

#include <stdio.h>

int main() {

    FILE *fp = fopen("test.txt", "r");

    if (fp == NULL) {
        return 1;
    }

    char buffer[100];

    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }

    fclose(fp);

    return 0;
}

这样会:

一次读取一行
↓
继续读取
↓
直到文件结束

对于普通文本文件,这是非常常见的写法。


十七、fprintf:格式化写入

如果需要像:

printf()

一样写格式化数据,可以使用:

fprintf()

格式:

fprintf(文件指针, "格式", 数据);

例如:

FILE *fp = fopen("test.txt", "w");

if (fp == NULL) {
    return 1;
}

int age = 18;
double score = 95.5;

fprintf(fp, "age = %d\n", age);
fprintf(fp, "score = %.2f\n", score);

fclose(fp);

文件:

age = 18
score = 95.50

十八、printf 和 fprintf

你之前:

printf("age = %d\n", age);

是在:

控制台

输出。

而:

fprintf(fp, "age = %d\n", age);

是在:

文件

输出。

可以简单理解:

printf
↓
标准输出

fprintf
↓
指定 FILE 输出

十九、fscanf:从文件读取格式化数据

与:

scanf()

类似。

例如文件:

18 95.5

可以:

int age;
double score;

fscanf(fp, "%d %lf", &age, &score);

这样:

文件
 ↓
fscanf
 ↓
变量

二十、完整的文本读写

写入:

#include <stdio.h>

int main() {

    FILE *fp = fopen("student.txt", "w");

    if (fp == NULL) {
        return 1;
    }

    fprintf(fp, "Alice 18 95.5\n");
    fprintf(fp, "Bob 19 88.0\n");

    fclose(fp);

    return 0;
}

文件变成:

Alice 18 95.5
Bob 19 88.0

然后再读取:

#include <stdio.h>

int main() {

    FILE *fp = fopen("student.txt", "r");

    if (fp == NULL) {
        return 1;
    }

    char name[50];
    int age;
    double score;

    while (fscanf(fp, "%49s %d %lf",
                  name, &age, &score) == 3) {

        printf(
            "%s %d %.2f\n",
            name,
            age,
            score
        );
    }

    fclose(fp);

    return 0;
}

二十一、文件指针的位置

文件内部可以理解为:

H e l l o \n W o r l d
↑
当前位置

每读取一次:

fgetc(fp);

位置就会向后移动。

例如:

第一次
↓
H e l l o
↑

第二次
↓
H e l l o
  ↑

这就是所谓:

文件位置指示器。


二十二、rewind

可以把文件位置重新放回开头:

rewind(fp);

例如:

fgetc(fp);
fgetc(fp);

rewind(fp);

fgetc(fp);

前两次读取以后位置已经向后移动。

rewind() 后:

回到文件开头

二十三、ftell

可以查看当前文件位置:

ftell(fp);

例如:

long pos = ftell(fp);

printf("%ld\n", pos);

返回当前文件位置。

例如:

刚打开
↓
0

读取一个字符
↓
1

再读取一个字符
↓
2

二十四、fseek

可以直接移动文件位置:

fseek()

基本形式:

fseek(fp, offset, origin);

常见:

SEEK_SET
SEEK_CUR
SEEK_END

例如:

fseek(fp, 0, SEEK_SET);

表示:

移动到文件开头

二十五、文件结尾

常见判断方式:

while ((c = fgetc(fp)) != EOF)

这里:

EOF

表示:

End Of File

也就是文件结束。

注意:

EOF 不是一个真正写进文件里的普通字符。

它是读取函数用来表示:

已经没有数据可读了。


二十六、二进制文件

到目前为止,我们主要操作:

文本文件

例如:

student.txt

但是程序还可以保存:

二进制文件

例如:

student.dat
save.dat
data.bin

使用:

rb
wb
ab

等模式。


二十七、fwrite

二进制写入主要使用:

fwrite()

格式:

fwrite(数据地址, 每个元素大小, 元素数量, 文件指针);

例如:

int a = 100;

fwrite(&a, sizeof(int), 1, fp);

表示:

把 a 的内存数据
↓
写入文件

二十八、fread

读取对应:

fread()

例如:

int a;

fread(&a, sizeof(int), 1, fp);

表示:

文件中的二进制数据
↓
读取到 a

二十九、fwrite + fread

完整例子:

#include <stdio.h>

int main() {

    FILE *fp = fopen("data.bin", "wb");

    if (fp == NULL) {
        return 1;
    }

    int a = 100;
    double b = 3.14;

    fwrite(&a, sizeof(int), 1, fp);
    fwrite(&b, sizeof(double), 1, fp);

    fclose(fp);

    return 0;
}

然后读取:

#include <stdio.h>

int main() {

    FILE *fp = fopen("data.bin", "rb");

    if (fp == NULL) {
        return 1;
    }

    int a;
    double b;

    fread(&a, sizeof(int), 1, fp);
    fread(&b, sizeof(double), 1, fp);

    printf("%d\n", a);
    printf("%.2f\n", b);

    fclose(fp);

    return 0;
}

三十、结构体写入文件

现在把上一章的:

结构体

和本章:

文件

结合起来。

例如:

typedef struct {
    char name[50];
    int age;
    double score;
} Student;

创建:

Student s = {"Alice", 18, 95.5};

直接写入:

fwrite(&s, sizeof(Student), 1, fp);

三十一、结构体读取文件

读取:

Student s;

fread(&s, sizeof(Student), 1, fp);

完整程序:

#include <stdio.h>

typedef struct {
    char name[50];
    int age;
    double score;
} Student;

int main() {

    Student s = {"Alice", 18, 95.5};

    FILE *fp = fopen("student.dat", "wb");

    if (fp == NULL) {
        return 1;
    }

    fwrite(&s, sizeof(Student), 1, fp);

    fclose(fp);

    return 0;
}

读取:

#include <stdio.h>

typedef struct {
    char name[50];
    int age;
    double score;
} Student;

int main() {

    Student s;

    FILE *fp = fopen("student.dat", "rb");

    if (fp == NULL) {
        return 1;
    }

    fread(&s, sizeof(Student), 1, fp);

    printf("姓名: %s\n", s.name);
    printf("年龄: %d\n", s.age);
    printf("成绩: %.2f\n", s.score);

    fclose(fp);

    return 0;
}

三十二、结构体数组写入文件

可以一次写多个:

Student students[3] = {
    {"Alice", 18, 95.5},
    {"Bob", 19, 88.0},
    {"Charlie", 18, 92.5}
};

然后:

fwrite(
    students,
    sizeof(Student),
    3,
    fp
);

也就是:

students
↓
Student
Student
Student
↓
一次写入 3 个

三十三、结构体数组读取

例如:

Student students[3];

fread(
    students,
    sizeof(Student),
    3,
    fp
);

然后:

for (int i = 0; i < 3; i++) {
    printf(
        "%s %d %.2f\n",
        students[i].name,
        students[i].age,
        students[i].score
    );
}

三十四、动态内存 + 文件 + 结构体

现在把前三章全部连接起来。

结构体
+
动态内存
+
文件

例如:

Student *students =
    malloc(sizeof(Student) * n);

用户输入 n。

然后:

malloc
↓
创建 n 个 Student
↓
输入数据
↓
fwrite
↓
保存文件
↓
free

读取时:

fopen
↓
确定数据数量
↓
malloc
↓
fread
↓
使用
↓
free
↓
fclose

这已经开始接近真正的软件数据存储了。


三十五、文本文件和二进制文件

两种方式各有特点。

文本文件

例如:

student.txt

内容:

Alice 18 95.5
Bob 19 88.0

特点:

人可以直接打开查看
方便调试
跨语言处理比较方便

常用:

fprintf
fscanf
fgets
fputs

二进制文件

例如:

student.dat

内容是二进制数据。

特点:

人不容易直接阅读
适合程序直接读写
可以直接保存结构体的字节数据

常用:

fread
fwrite

三十六、文件操作安全问题

1. fopen 失败

一定要检查:

FILE *fp = fopen("test.txt", "r");

if (fp == NULL) {
    return 1;
}

2. 文件没关闭

使用完:

fclose(fp);

3. 覆盖原文件

注意:

fopen("test.txt", "w");

会清空原文件。

如果只想在后面添加内容:

fopen("test.txt", "a");

4. scanf / fscanf 字符串长度

例如:

char name[50];

fscanf(fp, "%49s", name);

预留一个位置给:

\0

这样可以避免简单的缓冲区越界问题。


三十七、文件和你的课程表项目

你之前做过课程表程序。

之前的数据可能放在:

char classname[7][6][291];

程序关闭之后:

数据消失

现在可以:

课程表
↓
结构体
↓
动态内存
↓
文件
↓
永久保存

例如设计:

typedef struct {
    char name[100];
    char teacher[100];
    char room[100];
} Course;

然后:

Course courses[7][6];

可以保存到文件。

甚至以后:

C 程序
↓
生成 JSON
↓
网页 / 手机 App
↓
读取 JSON

这样就和你之前做的课程表项目真正连接起来了。


三十八、文件操作的完整流程

最核心:

                文件
                 │
          ┌──────┴──────┐
          ↓             ↓
        读取            写入
          │             │
        fopen          fopen
          │             │
        fread          fwrite
          │             │
          └──────┬──────┘
                 ↓
               fclose

文本文件:

fopen
 ↓
fgets / fscanf
 ↓
处理数据
 ↓
fprintf / fputs
 ↓
fclose

二进制文件:

fopen
 ↓
fread / fwrite
 ↓
处理数据
 ↓
fclose

三十九、本章最重要的函数

建议先记住这几个:

fopen
fclose
fprintf
fscanf
fgets
fputs
fread
fwrite

然后再学习:

fgetc
fputc
fseek
ftell
rewind

四十、知识联系

到这里,你已经可以把前面的知识串起来:

数组
 ↓
可以保存多个相同类型的数据

结构体
 ↓
把不同类型的数据组合起来

指针
 ↓
保存地址

动态内存
 ↓
运行时申请空间

文件
 ↓
把数据保存到磁盘

最终形成:

结构体
   +
指针
   +
malloc/free
   +
fread/fwrite
   ↓
可以保存复杂的数据

四十一、练习

练习 1

创建:

test.txt

写入:

Hello C

要求使用:

fopen
fputs
fclose

练习 2

读取:

test.txt

逐行输出。

要求使用:

fgets

练习 3

创建一个学生:

Student s;

把:

姓名
年龄
成绩

使用 fprintf() 保存到文本文件。


练习 4

从文本文件中读取多个学生。

使用:

fscanf

然后放入:

Student students[100];

练习 5

使用:

fwrite

把一个 Student 保存到:

student.dat

再使用:

fread

读取回来。


练习 6

创建 5 个学生:

Student students[5];

一次性写入:

fwrite(
    students,
    sizeof(Student),
    5,
    fp
);

然后再一次性读取。


练习 7

综合练习:

让用户输入:

学生数量

然后:

输入 n
↓
malloc
↓
创建 n 个 Student
↓
输入学生信息
↓
保存到文件
↓
free

重新启动程序:

读取文件
↓
malloc
↓
读取所有学生
↓
输出
↓
free

此作者没有提供个人介绍。
最后更新于 2026-09-18