C学习教程,从入门到精通,C语言文件 — 完整知识点详解(14)

C语言文件 --- 完整知识点详解


一、文件概述

1.1 什么是文件

文件是指存储在外部介质(如硬盘、U盘)上的数据的集合。在C语言中,文件被看作是一个字节序列,即一个一个字节按顺序排列组成的数据流。

操作系统通过文件名来管理和访问文件。一个完整的文件名通常包括三部分:

复制代码
文件名.扩展名
例如:data.txt、program.c、image.bmp

1.2 文件的分类

C语言中,文件按照数据的组织形式可以分为两大类:

(1)文本文件(ASCII文件)

  • 文件中的每一个字节存储一个ASCII码,代表一个字符
  • 例如:整数 10000 在文本文件中占5个字节,分别存储字符 '1'、'0'、'0'、'0'、'0' 的ASCII码
  • 特点:便于阅读,但占用空间较大,需要进行转换

(2)二进制文件

  • 数据按照内存中的存储形式(二进制)直接存放到文件中
  • 例如:整数 10000 在二进制文件中占4个字节(int类型大小),存储其二进制形式 00000000 00000000 00100111 00010000
  • 特点:存储效率高,读写速度快,但不便于直接阅读
对比项 文本文件 二进制文件
存储方式 以ASCII字符形式存储 以二进制形式直接存储
可读性 可以用文本编辑器直接阅读 不可以直接阅读
存储效率 较低(需要转换) 较高(直接存储)
读写速度 较慢(需要编码转换) 较快
典型应用 配置文件、日志文件、源代码 图片、音频、可执行文件

1.3 文件缓冲区

ANSI C标准采用 "缓冲文件系统" 来处理文件操作。其核心思想是:

  • 系统在内存中为每个正在使用的文件开辟一块文件缓冲区

  • 写文件时:程序先把数据输出到缓冲区,当缓冲区满了或文件关闭时,才将缓冲区中的数据一次性写入磁盘文件

  • 读文件时:系统先从磁盘文件中读取一批数据到缓冲区,程序再从缓冲区中逐个读取数据

    程序 ←→ 缓冲区 ←→ 磁盘文件

这样做的目的是减少磁盘读写次数,提高程序运行效率。

1.4 流(Stream)的概念

在C语言中,所有的I/O操作都是通过流来完成的。流是一个抽象的数据通道,程序通过流与外部设备(文件、键盘、显示器等)进行数据交换。

  • 文本流:由一系列字符组成,用于处理文本文件
  • 二进制流:由一系列字节组成,用于处理二进制文件

程序启动时,系统会自动打开三个标准流:

标准流 含义 默认设备
stdin 标准输入流 键盘
stdout 标准输出流 显示器
stderr 标准错误流 显示器

二、文件基本操作

2.1 文件指针

文件指针 是C语言中进行文件操作的核心概念。每个被使用的文件,系统都在内存中为其开辟一个文件信息区 ,用来存放文件的相关信息(如文件名、文件状态、文件当前位置等)。这些信息保存在一个结构体类型的变量中,该结构体类型由系统定义,名为 FILE。

FILE结构体的大致内容:

c 复制代码
// FILE结构体是系统定义的,以下为示意
typedef struct {
    int _fd;            // 文件描述符(文件号)
    char *_buf;         // 缓冲区指针
    int _bufsiz;        // 缓冲区大小
    int _cnt;           // 缓冲区中剩余的字符数
    char *_ptr;         // 当前读写位置指针
    int _flag;          // 文件状态标志
    // ... 其他成员
} FILE;

注意 :我们不需要关心 FILE 结构体的具体实现细节,只需要使用 FILE * 类型的指针即可。

声明文件指针的语法:

c 复制代码
FILE *指针变量名;

示例:

c 复制代码
FILE *fp;    // 声明一个FILE类型的指针变量fp

为什么需要文件指针?

文件指针就像一把"钥匙",程序通过文件指针来找到并操作对应的文件。后续所有的文件读写操作,都需要通过文件指针来进行。


2.2 打开文件(fopen函数)

函数原型:

c 复制代码
FILE *fopen(const char *filename, const char *mode);

参数说明:

  • filename:要打开的文件名(可以包含路径),字符串类型
  • mode:文件打开模式,字符串类型

返回值:

  • 成功:返回一个指向 FILE 结构体的指针
  • 失败:返回 NULL

文件打开模式:

模式 含义 文件不存在时 文件存在时 要求文件
"r" 只读(文本) 出错返回NULL 从头读取 必须存在
"w" 只写(文本) 创建新文件 清空原内容 不要求
"a" 追加(文本) 创建新文件 在末尾追加 不要求
"rb" 只读(二进制) 出错返回NULL 从头读取 必须存在
"wb" 只写(二进制) 创建新文件 清空原内容 不要求
"ab" 追加(二进制) 创建新文件 在末尾追加 不要求
"r+" 读写(文本) 出错返回NULL 从头读写 必须存在
"w+" 读写(文本) 创建新文件 清空原内容 不要求
"a+" 读写(文本) 创建新文件 可读,写在末尾 不要求
"rb+" / "r+b" 读写(二进制) 出错返回NULL 从头读写 必须存在
"wb+" / "w+b" 读写(二进制) 创建新文件 清空原内容 不要求
"ab+" / "a+b" 读写(二进制) 创建新文件 可读,写在末尾 不要求

记忆规律:

  • r = read(读),w = write(写),a = append(追加)
  • b = binary(二进制),不加b默认是文本模式
  • + = 读写都允许

入门案例:

c 复制代码
#include <stdio.h>   // 包含标准输入输出头文件
#include <stdlib.h>  // 包含exit函数所在头文件

int main()
{
    FILE *fp;  // 声明一个文件指针变量fp,类型为FILE*

    // 以只读方式打开当前目录下的data.txt文件
    fp = fopen("data.txt", "r");

    // 判断文件是否打开成功
    // fopen打开失败时返回NULL,所以必须进行判断
    if (fp == NULL)
    {
        printf("文件打开失败!\n");  // 输出错误提示信息
        exit(1);                      // 终止程序,exit需要stdlib.h
    }

    printf("文件打开成功!\n");  // 打开成功,输出提示

    fclose(fp);  // 关闭文件,释放资源

    return 0;  // 程序正常结束
}

带路径的文件打开方式:

c 复制代码
// 打开D盘test目录下的文件
fp = fopen("D:\\test\\data.txt", "r");

// 注意:路径中的反斜杠要用双反斜杠 \\ 进行转义
// 因为单个 \ 在C语言中是转义字符

2.3 关闭文件(fclose函数)

函数原型:

c 复制代码
int fclose(FILE *stream);

参数说明:

  • stream:指向要关闭的文件的FILE指针

返回值:

  • 成功:返回 0
  • 失败:返回 EOF(EOF通常定义为 -1)

为什么要关闭文件?

  1. 刷新缓冲区:将缓冲区中尚未写入文件的数据写入磁盘,防止数据丢失
  2. 释放资源:释放文件信息区(FILE结构体)占用的内存
  3. 释放文件号:操作系统能同时打开的文件数量有限,及时关闭可以释放文件号

重要原则:

  • 打开的文件必须在使用完之后关闭
  • 一个 FILE 指针关闭后,不再指向有效文件,不应当再使用
  • 可以将指针置为NULL,避免误操作
c 复制代码
fclose(fp);   // 关闭文件
fp = NULL;    // 将指针置为NULL,安全起见

入门案例:完整的文件打开与关闭

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;  // 声明文件指针

    // 以写入模式打开文件test.txt
    // 如果文件不存在会创建新文件,如果存在会清空内容
    fp = fopen("test.txt", "w");

    // 判断文件是否成功打开
    if (fp == NULL)
    {
        printf("无法打开文件!\n");  // 提示错误
        exit(1);                      // 退出程序
    }

    printf("文件已成功打开,准备写入数据...\n");

    // 在这里可以进行文件读写操作...

    // 关闭文件
    if (fclose(fp) == 0)
    {
        printf("文件已成功关闭。\n");  // fclose返回0表示成功
    }
    else
    {
        printf("文件关闭失败!\n");   // fclose返回EOF表示失败
    }

    return 0;
}

三、文件的读写

3.1 写入字符(fputc函数)

函数原型:

c 复制代码
int fputc(int ch, FILE *stream);

参数说明:

  • ch:要写入的字符(以int形式传递,实际使用char即可)
  • stream:目标文件的FILE指针

返回值:

  • 成功:返回写入的字符(以int形式)
  • 失败:返回 EOF(-1)

功能:将一个字符写入到指定的文件流中。

入门案例:向文件写入单个字符

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;        // 声明文件指针
    char ch;         // 声明字符变量,用于存储要写入的字符

    // 以写入模式打开文件output.txt
    // "w"模式:文件不存在则创建,存在则清空
    fp = fopen("output.txt", "w");

    // 判断文件是否打开成功
    if (fp == NULL)
    {
        printf("文件打开失败!\n");  // 提示错误信息
        exit(1);                      // 退出程序
    }

    // 使用循环将字符'A'到'Z'依次写入文件
    for (ch = 'A'; ch <= 'Z'; ch++)
    {
        // fputc每次写入一个字符到文件fp中
        fputc(ch, fp);
    }

    // 写入一个换行符
    fputc('\n', fp);

    printf("字符写入完成!\n");  // 提示写入完成

    fclose(fp);  // 关闭文件

    return 0;
}

运行后,output.txt 文件中的内容为:

复制代码
ABCDEFGHIJKLMNOPQRSTUVWXYZ

3.2 读取字符(fgetc函数)

函数原型:

c 复制代码
int fgetc(FILE *stream);

参数说明:

  • stream:源文件的FILE指针

返回值:

  • 成功:返回读取到的字符(以int形式表示)
  • 读到文件末尾或出错:返回 EOF(-1)

功能:从文件流中读取一个字符,并使文件位置指针向后移动一个位置。

重要说明:

  • fgetc 每次调用只读取一个字符
  • 每读取一个字符,文件内部的位置指针自动后移一位
  • 当读到文件末尾时,返回 EOF
  • EOF 既表示文件结束,也可能表示读取出错,可以用 feof() 和 ferror() 来区分

入门案例:从文件逐字符读取内容

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;       // 声明文件指针
    char ch;        // 声明字符变量,存储读取到的字符

    // 以只读模式打开前面创建的output.txt文件
    fp = fopen("output.txt", "r");

    // 判断文件是否打开成功
    if (fp == NULL)
    {
        printf("文件打开失败!\n");
        exit(1);
    }

    printf("文件内容为:\n");

    // 循环读取文件中的每一个字符
    // fgetc返回EOF表示到达文件末尾(或出错)
    while ((ch = fgetc(fp)) != EOF)
    {
        putchar(ch);  // 将读取到的字符输出到屏幕
    }

    printf("\n文件读取完成!\n");

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果:

复制代码
文件内容为:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
文件读取完成!

入门案例:fputc和fgetc配合使用------逐字符复制文件

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp_src;    // 源文件指针
    FILE *fp_dst;    // 目标文件指针
    char ch;         // 用于中转的字符变量

    // 以只读模式打开源文件
    fp_src = fopen("output.txt", "r");
    if (fp_src == NULL)
    {
        printf("源文件打开失败!\n");
        exit(1);
    }

    // 以写入模式打开(创建)目标文件
    fp_dst = fopen("copy.txt", "w");
    if (fp_dst == NULL)
    {
        printf("目标文件打开失败!\n");
        fclose(fp_src);  // 注意:关闭已打开的源文件,避免资源泄漏
        exit(1);
    }

    // 循环从源文件读取一个字符,写入目标文件
    // 直到源文件读取完毕(fgetc返回EOF)
    while ((ch = fgetc(fp_src)) != EOF)
    {
        fputc(ch, fp_dst);  // 将字符写入目标文件
    }

    printf("文件复制完成!\n");

    // 关闭两个文件
    fclose(fp_src);
    fclose(fp_dst);

    return 0;
}

3.3 写入字符串(fputs函数)

函数原型:

c 复制代码
int fputs(const char *str, FILE *stream);

参数说明:

  • str:要写入的字符串(以 '\0' 结尾的字符数组或字符串常量)
  • stream:目标文件的FILE指针

返回值:

  • 成功:返回一个非负整数(通常为0)
  • 失败:返回 EOF

功能:将一个字符串写入到指定文件流中。

重要说明:

  • fputs 会将字符串中的所有字符写入文件,但不包括结尾的 '\0'
  • fputs 不会自动在末尾添加换行符 ,需要手动添加 '\n'

入门案例:向文件写入多行字符串

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;  // 声明文件指针

    // 以写入模式打开文件
    fp = fopen("student.txt", "w");

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

    // 使用fputs写入字符串到文件
    // 注意:fputs不会自动换行,需要手动添加\n
    fputs("张三\n", fp);       // 写入姓名后换行
    fputs("男\n", fp);         // 写入性别后换行
    fputs("20岁\n", fp);       // 写入年龄后换行
    fputs("计算机科学\n", fp); // 写入专业后换行

    printf("数据写入完成!\n");

    fclose(fp);  // 关闭文件

    return 0;
}

运行后,student.txt 文件中的内容为:

复制代码
张三
男
20岁
计算机科学

3.4 读取字符串(fgets函数)

函数原型:

c 复制代码
char *fgets(char *str, int n, FILE *stream);

参数说明:

  • str:存放读取字符串的字符数组(缓冲区)
  • n:最多读取的字符数(包括 '\0'),即最多读取 n-1 个有效字符
  • stream:源文件的FILE指针

返回值:

  • 成功:返回 str 的首地址(即传入的数组地址)
  • 失败或读到文件末尾:返回 NULL

功能:从文件流中读取一行字符串。

重要说明:

  • fgets 最多读取 n-1 个字符,然后自动加上 '\0'
  • 如果在读满 n-1 个字符之前遇到换行符 '\n',则读取结束,并且 '\n' 也会被读入并存储在字符串中
  • 如果读到文件末尾,也会结束读取
  • 区别于 gets 函数,fgets 更安全,可以限制读取长度

入门案例:从文件读取字符串

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>  // 包含字符串处理函数

int main()
{
    FILE *fp;           // 声明文件指针
    char buffer[100];   // 声明字符数组作为缓冲区,最多存储100个字符

    // 以只读模式打开前面创建的student.txt文件
    fp = fopen("student.txt", "r");

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

    printf("文件内容:\n");

    // 循环逐行读取文件内容
    // fgets每次读取一行,最多读取99个字符(留1个位置给'\0')
    while (fgets(buffer, 100, fp) != NULL)
    {
        printf("%s", buffer);  // 输出读取到的字符串
        // 注意:fgets会保留行末的\n,所以不需要额外加\n
    }

    printf("\n文件读取完成!\n");

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果:

复制代码
文件内容:
张三
男
20岁
计算机科学
文件读取完成!

3.5 fprintf函数

函数原型:

c 复制代码
int fprintf(FILE *stream, const char *format, ...);

参数说明:

  • stream:目标文件的FILE指针
  • format:格式控制字符串(与 printf 的格式字符串完全相同)
  • ...:可变参数列表

返回值:

  • 成功:返回实际写入的字符数
  • 失败:返回一个负数

功能:按照格式控制字符串,将数据格式化写入文件。

fprintf 与 printf 的关系:

  • printf(...) 等价于 fprintf(stdout, ...)
  • fprintf 就是可以指定输出目标(文件)的 printf

常用格式说明符:

格式符 含义 示例
%d 十进制整数 fprintf(fp, "%d", 100)
%f 浮点数 fprintf(fp, "%f", 3.14)
%c 单个字符 fprintf(fp, "%c", 'A')
%s 字符串 fprintf(fp, "%s", "hello")
%lf 双精度浮点数 fprintf(fp, "%lf", 3.14)
%5d 宽度为5的整数 右对齐,左边补空格
%.2f 保留2位小数 fprintf(fp, "%.2f", 3.1415) → 3.14

入门案例:格式化写入学生信息到文件

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;   // 声明文件指针

    // 声明并初始化学生信息变量
    char name[20] = "李四";    // 姓名
    int age = 22;              // 年龄
    float score = 95.5;        // 成绩
    char gender = 'M';         // 性别

    // 以写入模式打开文件info.txt
    fp = fopen("info.txt", "w");

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

    // 使用fprintf将格式化数据写入文件
    fprintf(fp, "姓名:%s\n", name);      // 写入姓名
    fprintf(fp, "年龄:%d岁\n", age);     // 写入年龄
    fprintf(fp, "成绩:%.1f分\n", score);  // 写入成绩,保留1位小数
    fprintf(fp, "性别:%c\n", gender);     // 写入性别

    // 也可以一行写入多个数据(如表格数据)
    fprintf(fp, "%s %d %.1f %c\n", name, age, score, gender);

    printf("格式化数据写入完成!\n");

    fclose(fp);  // 关闭文件

    return 0;
}

运行后,info.txt 文件中的内容为:

复制代码
姓名:李四
年龄:22岁
成绩:95.5分
性别:M
李四 22 95.5 M

3.6 fscanf函数

函数原型:

c 复制代码
int fscanf(FILE *stream, const char *format, ...);

参数说明:

  • stream:源文件的FILE指针
  • format:格式控制字符串(与 scanf 的格式字符串完全相同)
  • ...:接收数据的变量地址

返回值:

  • 成功:返回成功读取并赋值的数据项个数
  • 遇到文件末尾:返回 EOF

功能:按照格式控制字符串,从文件中读取格式化数据。

fscanf 与 scanf 的关系:

  • scanf(...) 等价于 fscanf(stdin, ...)
  • fscanf 就是可以指定数据来源(文件)的 scanf

入门案例:从文件中格式化读取学生信息

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;   // 声明文件指针

    // 声明变量用于存储读取的数据
    char name[20];   // 姓名
    int age;         // 年龄
    float score;     // 成绩
    char gender;     // 性别

    // 以只读模式打开文件info.txt
    fp = fopen("info.txt", "r");

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

    // 读取第一行"姓名:xxx\n" ------用fscanf按格式读取
    fscanf(fp, "姓名:%s\n", name);    // 跳过"姓名:"前缀,读取字符串
    fscanf(fp, "年龄:%d岁\n", &age);  // 跳过"年龄:"前缀,读取整数
    fscanf(fp, "成绩:%f分\n", &score); // 跳过"成绩:"前缀,读取浮点数
    fscanf(fp, "性别:%c\n", &gender);  // 跳过"性别:"前缀,读取字符

    // 输出读取到的数据
    printf("读取到的学生信息:\n");
    printf("姓名:%s\n", name);
    printf("年龄:%d岁\n", age);
    printf("成绩:%.1f分\n", score);
    printf("性别:%c\n", gender);

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果:

复制代码
读取到的学生信息:
姓名:李四
年龄:22岁
成绩:95.5分
性别:M

fprintf和fscanf配合使用案例:录入和显示多个学生信息

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;  // 声明文件指针
    int i;     // 循环变量

    // ========== 写入阶段 ==========
    // 以写入模式打开文件score.txt
    fp = fopen("score.txt", "w");

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

    printf("请输入3个学生的姓名和成绩:\n");

    // 循环输入3个学生的信息并写入文件
    for (i = 0; i < 3; i++)
    {
        char name[20];    // 临时存储姓名
        float score;      // 临时存储成绩

        printf("第%d个学生(姓名 成绩):", i + 1);
        scanf("%s %f", name, &score);  // 从键盘读取

        // 将数据格式化写入文件,以空格分隔
        fprintf(fp, "%s %.1f\n", name, score);
    }

    fclose(fp);  // 写入完毕,关闭文件

    // ========== 读取阶段 ==========
    // 以只读模式重新打开文件
    fp = fopen("score.txt", "r");

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

    printf("\n从文件中读取的学生信息:\n");
    printf("%-10s %s\n", "姓名", "成绩");  // 打印表头
    printf("-------------------\n");

    char name[20];    // 存储读取的姓名
    float score;      // 存储读取的成绩

    // 使用fscanf从文件中循环读取数据
    // fscanf返回成功读取的数据项数,2表示成功读取了2项
    while (fscanf(fp, "%s %f", name, &score) == 2)
    {
        printf("%-10s %.1f\n", name, score);  // 左对齐输出
    }

    fclose(fp);  // 读取完毕,关闭文件

    return 0;
}

运行示例:

复制代码
请输入3个学生的姓名和成绩:
第1个学生(姓名 成绩):Alice 90.5
第2个学生(姓名 成绩):Bob 85.0
第3个学生(姓名 成绩):Charlie 92.3

从文件中读取的学生信息:
姓名       成绩
-------------------
Alice      90.5
Bob        85.0
Charlie    92.3

3.7 读写数据块(fread和fwrite函数)

前面介绍的函数适合处理文本数据 。当我们需要高效地读写大量结构化数据 (如结构体数组)时,使用 fread 和 fwrite 以二进制方式读写数据块更加高效。

fwrite函数 ------ 写数据块

函数原型:

c 复制代码
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

参数说明:

  • ptr:指向要写入数据的内存起始地址(通常传数组名或结构体指针)
  • size:每个数据块的字节大小(通常用 sizeof 运算符获取)
  • count:要写入的数据块个数
  • stream:目标文件的FILE指针

返回值:

  • 成功:返回实际写入的数据块个数(不是字节数)
  • 如果返回值小于 count,说明写入过程中出现了错误

功能 :将内存中 count 个大小为 size 字节的数据块写入文件。

fread函数 ------ 读数据块

函数原型:

c 复制代码
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

参数说明:

  • ptr:指向存储读取数据的内存起始地址
  • size:每个数据块的字节大小
  • count:要读取的数据块个数
  • stream:源文件的FILE指针

返回值:

  • 成功:返回实际读取的数据块个数
  • 如果返回值小于 count,可能是遇到了文件末尾或发生了错误

入门案例:使用fwrite和fread读写结构体数组

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义学生结构体
struct Student
{
    char name[20];   // 姓名
    int age;         // 年龄
    float score;     // 成绩
};

int main()
{
    FILE *fp;  // 声明文件指针
    int i;     // 循环变量

    // ========== 初始化学生数据 ==========
    struct Student stu_write[3] =   // 要写入文件的学生数组
    {
        {"张三", 20, 88.5},
        {"李四", 21, 92.0},
        {"王五", 19, 95.5}
    };

    struct Student stu_read[3];     // 用于存储从文件读取的数据

    // ========== 写入阶段 ==========
    // 以二进制写入模式打开文件student.dat
    // "wb"表示写入二进制文件
    fp = fopen("student.dat", "wb");

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

    // 使用fwrite将整个数组写入文件
    // 参数1:数据的起始地址(数组名)
    // 参数2:每个元素的大小
    // 参数3:元素个数
    // 参数4:文件指针
    size_t written = fwrite(stu_write, sizeof(struct Student), 3, fp);

    printf("成功写入 %zu 个学生数据。\n", written);

    fclose(fp);  // 关闭文件

    // ========== 读取阶段 ==========
    // 以二进制只读模式打开文件
    fp = fopen("student.dat", "rb");

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

    // 使用fread从文件读取3个学生数据到数组
    fread(stu_read, sizeof(struct Student), 3, fp);

    printf("\n从文件读取的学生信息:\n");
    printf("%-10s %-6s %s\n", "姓名", "年龄", "成绩");
    printf("----------------------------\n");

    // 循环输出读取到的每个学生信息
    for (i = 0; i < 3; i++)
    {
        printf("%-10s %-6d %.1f\n",
               stu_read[i].name,
               stu_read[i].age,
               stu_read[i].score);
    }

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果:

复制代码
成功写入 3 个学生数据。

从文件读取的学生信息:
姓名       年龄   成绩
----------------------------
张三       20     88.5
李四       21     92.0
王五       19     95.5

四、文件的定位

在每个打开的文件中,都有一个文件位置指示器 (也叫文件指针位置或文件读写位置),用来指示当前读写操作的位置。每次读写操作后,位置指示器会自动向后移动。

C语言提供以下函数来人为控制文件位置指示器的位置。

4.1 fseek函数

函数原型:

c 复制代码
int fseek(FILE *stream, long offset, int origin);

参数说明:

  • stream:文件指针
  • offset:相对于基准位置的偏移量(字节数)
    • 正数:向文件末尾方向移动
    • 负数:向文件开头方向移动
    • 零:不移动
  • origin:基准位置(起始点),有三个可选值:
常量 值 含义
SEEK_SET 0 文件开头
SEEK_CUR 1 当前位置
SEEK_END 2 文件末尾

返回值:

  • 成功:返回 0
  • 失败:返回非零值

功能:将文件位置指示器移动到指定位置。

常见用法:

c 复制代码
fseek(fp, 0, SEEK_SET);      // 将位置移到文件开头
fseek(fp, 0, SEEK_END);      // 将位置移到文件末尾
fseek(fp, 100, SEEK_SET);    // 从文件开头向后移动100个字节
fseek(fp, -10, SEEK_CUR);    // 从当前位置向前移动10个字节

入门案例:使用fseek随机访问文件中的学生数据

c 复制代码
#include <stdio.h>
#include <stdlib.h>

struct Student
{
    char name[20];
    int age;
    float score;
};

int main()
{
    FILE *fp;
    struct Student stu;  // 用于存储读取的单个学生数据

    // 以二进制读模式打开文件(使用前面创建的student.dat)
    fp = fopen("student.dat", "rb");

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

    // ===== 读取第3个学生(索引为2)的数据 =====
    // 先用fseek定位到第3个学生的起始位置
    // 偏移量 = 2 * sizeof(struct Student)
    // 从文件开头(SEEK_SET)偏移2个结构体大小的位置
    fseek(fp, 2 * sizeof(struct Student), SEEK_SET);

    // 从当前位置读取1个学生数据
    fread(&stu, sizeof(struct Student), 1, fp);

    printf("第3个学生的信息:\n");
    printf("姓名:%s\n", stu.name);
    printf("年龄:%d\n", stu.age);
    printf("成绩:%.1f\n", stu.score);

    // ===== 读取第1个学生(索引为0)的数据 =====
    fseek(fp, 0 * sizeof(struct Student), SEEK_SET);  // 定位到文件开头
    fread(&stu, sizeof(struct Student), 1, fp);

    printf("\n第1个学生的信息:\n");
    printf("姓名:%s\n", stu.name);
    printf("年龄:%d\n", stu.age);
    printf("成绩:%.1f\n", stu.score);

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果:

复制代码
第3个学生的信息:
姓名:王五
年龄:19
成绩:95.5

第1个学生的信息:
姓名:张三
年龄:20
成绩:88.5

4.2 rewind函数

函数原型:

c 复制代码
void rewind(FILE *stream);

参数说明:

  • stream:文件指针

返回值:无

功能 :将文件位置指示器重置到文件开头,同时清除文件的错误标志。

等价于:

c 复制代码
fseek(fp, 0, SEEK_SET);
clearerr(fp);

入门案例:使用rewind多次读取文件

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    char ch;

    // 以只读模式打开文件
    fp = fopen("output.txt", "r");

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

    // ===== 第一次读取:逐字符输出 =====
    printf("第一次读取:\n");
    while ((ch = fgetc(fp)) != EOF)
    {
        putchar(ch);  // 输出字符
    }
    printf("\n");

    // ===== 使用rewind将文件指针重置到开头 =====
    rewind(fp);

    // ===== 第二次读取:逐字符输出 =====
    printf("第二次读取:\n");
    while ((ch = fgetc(fp)) != EOF)
    {
        putchar(ch);
    }
    printf("\n");

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果:

复制代码
第一次读取:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
第二次读取:
ABCDEFGHIJKLMNOPQRSTUVWXYZ

4.3 ftell函数

函数原型:

c 复制代码
long ftell(FILE *stream);

参数说明:

  • stream:文件指针

返回值:

  • 成功:返回当前文件位置指示器相对于文件开头的偏移量(字节数)
  • 失败:返回 -1L

功能:获取文件位置指示器的当前位置。

入门案例:使用ftell获取文件大小

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    long file_size;  // 存储文件大小

    // 以只读模式打开文件
    fp = fopen("output.txt", "r");

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

    // 先将文件指针移到文件末尾
    fseek(fp, 0, SEEK_END);

    // 使用ftell获取当前位置(即文件大小,单位为字节)
    file_size = ftell(fp);

    printf("文件大小为:%ld 字节\n", file_size);

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果(output.txt 包含26个英文字母):

复制代码
文件大小为:26 字节

综合案例:fseek、rewind和ftell配合使用

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    long pos;   // 存储文件位置
    char ch;    // 存储读取的字符

    fp = fopen("output.txt", "r");

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

    // 读取前3个字符
    ch = fgetc(fp);  putchar(ch);  // 读取'A'
    ch = fgetc(fp);  putchar(ch);  // 读取'B'
    ch = fgetc(fp);  putchar(ch);  // 读取'C'

    // 获取当前文件位置
    pos = ftell(fp);
    printf("\n当前文件位置:%ld\n", pos);  // 输出3(已读3个字节)

    // 向前回退1个字节(回到第3个字符的位置)
    fseek(fp, -1, SEEK_CUR);
    pos = ftell(fp);
    printf("回退后文件位置:%ld\n", pos);  // 输出2

    // 再次读取一个字符
    ch = fgetc(fp);
    printf("当前位置的字符:%c\n", ch);   // 输出'C'(位置2对应第3个字符)

    // 重置到文件开头
    rewind(fp);
    pos = ftell(fp);
    printf("rewind后文件位置:%ld\n", pos);  // 输出0

    fclose(fp);

    return 0;
}

运行结果:

复制代码
ABC
当前文件位置:3
回退后文件位置:2
当前位置的字符:C
rewind后文件位置:0

五、文件管理

5.1 删除文件(remove函数)

函数原型:

c 复制代码
int remove(const char *filename);

参数说明:

  • filename:要删除的文件名(可以包含路径)

返回值:

  • 成功:返回 0
  • 失败:返回 -1

功能:删除指定的文件。

入门案例:删除文件

c 复制代码
#include <stdio.h>

int main()
{
    char filename[100];  // 存储文件名

    printf("请输入要删除的文件名:");
    scanf("%s", filename);  // 从键盘读取文件名

    // 使用remove函数删除文件
    if (remove(filename) == 0)
    {
        printf("文件 \"%s\" 删除成功!\n", filename);
    }
    else
    {
        printf("文件 \"%s\" 删除失败!(文件可能不存在)\n", filename);
    }

    return 0;
}

运行示例:

复制代码
请输入要删除的文件名:copy.txt
文件 "copy.txt" 删除成功!

5.2 重命名文件(rename函数)

函数原型:

c 复制代码
int rename(const char *oldname, const char *newname);

参数说明:

  • oldname:原来的文件名
  • newname:新的文件名

返回值:

  • 成功:返回 0
  • 失败:返回 -1

功能 :将文件从 oldname 重命名为 newname。也可以用来移动文件(如果新旧文件名包含不同路径)。

入门案例:重命名文件

c 复制代码
#include <stdio.h>

int main()
{
    char old_name[100];  // 存储原文件名
    char new_name[100];  // 存储新文件名

    printf("请输入原文件名:");
    scanf("%s", old_name);

    printf("请输入新文件名:");
    scanf("%s", new_name);

    // 使用rename函数重命名文件
    if (rename(old_name, new_name) == 0)
    {
        printf("文件重命名成功!\n");
        printf("\"%s\" -> \"%s\"\n", old_name, new_name);
    }
    else
    {
        printf("文件重命名失败!\n");
    }

    return 0;
}

运行示例:

复制代码
请输入原文件名:info.txt
请输入新文件名:student_info.txt
文件重命名成功!
"info.txt" -> "student_info.txt"

5.3 复制文件

C语言标准库没有提供直接的复制文件函数,需要手动实现。实现思路是:打开源文件读取内容,同时打开目标文件写入内容。

入门案例:逐字节复制文件

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp_src;         // 源文件指针
    FILE *fp_dst;         // 目标文件指针
    char src_name[100];   // 源文件名
    char dst_name[100];   // 目标文件名
    char ch;              // 用于中转的字符变量

    // 输入源文件名和目标文件名
    printf("请输入源文件名:");
    scanf("%s", src_name);

    printf("请输入目标文件名:");
    scanf("%s", dst_name);

    // 以二进制读模式打开源文件
    fp_src = fopen(src_name, "rb");
    if (fp_src == NULL)
    {
        printf("源文件 \"%s\" 打开失败!\n", src_name);
        exit(1);
    }

    // 以二进制写模式打开(创建)目标文件
    fp_dst = fopen(dst_name, "wb");
    if (fp_dst == NULL)
    {
        printf("目标文件 \"%s\" 打开失败!\n", dst_name);
        fclose(fp_src);  // 关闭已打开的源文件
        exit(1);
    }

    // 逐字节复制:从源文件读一个字节,写入目标文件
    while ((ch = fgetc(fp_src)) != EOF)
    {
        fputc(ch, fp_dst);
    }

    printf("文件复制完成!\n");
    printf("\"%s\" -> \"%s\"\n", src_name, dst_name);

    // 关闭两个文件
    fclose(fp_src);
    fclose(fp_dst);

    return 0;
}

六、文件状态检测

6.1 文件读取结束检测(feof函数)

函数原型:

c 复制代码
int feof(FILE *stream);

参数说明:

  • stream:文件指针

返回值:

  • 如果文件位置指示器已经到达文件末尾 :返回非零值(真)
  • 如果文件位置指示器未到达文件末尾 :返回 0(假)

功能:检测文件是否读取到末尾。

重要说明:

  • feof 是在上一次读操作之后检测是否已经到达文件末尾
  • 不能在读取之前调用 feof 来预判是否到达末尾
  • 正确的使用方式:先读取,再判断

常见的使用模式:

c 复制代码
// 方式一:先读后判断(推荐)
while (1)
{
    ch = fgetc(fp);        // 先读取一个字符
    if (feof(fp)) break;   // 判断是否到达文件末尾
    putchar(ch);           // 处理读取到的字符
}

// 方式二:利用fgetc的返回值判断(前面章节使用的方式)
while ((ch = fgetc(fp)) != EOF)
{
    putchar(ch);
}

入门案例:使用feof判断文件读取结束

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    char buffer[50];  // 缓冲区

    // 以写入模式创建测试文件
    fp = fopen("feof_test.txt", "w");
    if (fp == NULL)
    {
        printf("文件创建失败!\n");
        exit(1);
    }

    // 写入测试数据
    fputs("Hello\n", fp);
    fputs("World\n", fp);
    fputs("C Language\n", fp);
    fclose(fp);  // 关闭写入文件

    // 以只读模式重新打开文件
    fp = fopen("feof_test.txt", "r");
    if (fp == NULL)
    {
        printf("文件打开失败!\n");
        exit(1);
    }

    printf("文件内容:\n");

    // 使用feof检测文件末尾
    // 先fgets读取一行,再用feof判断是否到达末尾
    while (1)
    {
        // 读取一行到缓冲区
        if (fgets(buffer, 50, fp) == NULL)
        {
            break;  // 读取失败则退出
        }
        printf("%s", buffer);  // 输出该行

        // 检测是否到达文件末尾
        if (feof(fp))
        {
            printf("(已到达文件末尾)\n");
            break;
        }
    }

    fclose(fp);  // 关闭文件

    return 0;
}

运行结果:

复制代码
文件内容:
Hello
World
C Language
(已到达文件末尾)

6.2 文件读写出错检测(ferror函数)

函数原型:

c 复制代码
int ferror(FILE *stream);

参数说明:

  • stream:文件指针

返回值:

  • 如果文件操作过程中发生了错误 :返回非零值(真)
  • 如果未发生错误 :返回 0(假)

功能:检测文件流中是否发生了读写错误。

常见的错误原因:

  • 磁盘已满
  • 没有写权限
  • 磁盘硬件故障
  • 试图向只读文件写入

入门案例:检测文件读写错误

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;

    // 尝试以写入模式打开一个位于受保护路径的文件
    // 这里我们故意尝试写入一个可能出错的情况
    fp = fopen("test_error.txt", "w");

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

    // 尝试写入数据
    fputs("Testing ferror function.\n", fp);

    // 使用ferror检测是否发生错误
    if (ferror(fp))
    {
        printf("文件写入过程中发生错误!\n");
        clearerr(fp);  // 清除错误标志
    }
    else
    {
        printf("文件写入正常,未发生错误。\n");
    }

    fclose(fp);

    return 0;
}

运行结果:

复制代码
文件写入正常,未发生错误。

6.3 清除状态标志(clearerr函数)

函数原型:

c 复制代码
void clearerr(FILE *stream);

参数说明:

  • stream:文件指针

返回值:无

功能 :清除文件流的错误标志 和文件结束标志。

为什么需要clearerr?

  • 当文件操作出现错误(ferror 返回非零)或到达文件末尾(feof 返回非零)后,这些标志位会一直保持
  • 如果想继续操作同一个文件流(如重新定位后再读取),必须先用 clearerr 清除这些标志
  • 否则后续操作可能受到影响

入门案例:使用clearerr清除文件结束标志后重新读取

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    char ch;

    // 创建测试文件并写入数据
    fp = fopen("clearerr_test.txt", "w");
    if (fp == NULL) { printf("创建失败!\n"); exit(1); }
    fputs("ABCDE", fp);
    fclose(fp);

    // 以只读模式打开文件
    fp = fopen("clearerr_test.txt", "r");
    if (fp == NULL) { printf("打开失败!\n"); exit(1); }

    // 第一次读取:读到文件末尾
    printf("第一次读取:");
    while ((ch = fgetc(fp)) != EOF)
    {
        putchar(ch);
    }
    printf("\n");

    // 此时feof(fp)应该返回非零值(文件结束标志被设置)
    printf("feof状态:%d(非0表示已到末尾)\n", feof(fp));

    // ===== 使用clearerr清除文件结束标志 =====
    clearerr(fp);
    printf("清除后feof状态:%d(0表示标志已清除)\n", feof(fp));

    // 使用rewind将文件指针移回开头
    rewind(fp);

    // 第二次读取:可以正常读取
    printf("第二次读取:");
    while ((ch = fgetc(fp)) != EOF)
    {
        putchar(ch);
    }
    printf("\n");

    fclose(fp);

    return 0;
}

运行结果:

复制代码
第一次读取:ABCDE
feof状态:32(非0表示已到末尾)
清除后feof状态:0(0表示标志已清除)
第二次读取:ABCDE

七、综合实践案例

案例一:简单的通讯录系统(文本文件版)

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义联系人结构体
struct Contact
{
    char name[50];    // 姓名
    char phone[20];   // 电话号码
};

// 函数声明
void addContact(FILE *fp);          // 添加联系人
void showAllContacts(FILE *fp);     // 显示所有联系人

int main()
{
    FILE *fp;      // 文件指针
    int choice;    // 用户选择

    // 主循环:显示菜单,接收用户选择
    while (1)
    {
        // 显示操作菜单
        printf("\n===== 通讯录管理系统 =====\n");
        printf("1. 添加联系人\n");
        printf("2. 显示所有联系人\n");
        printf("3. 退出\n");
        printf("请输入选择:");
        scanf("%d", &choice);

        switch (choice)
        {
            case 1:
                // 以追加模式打开文件(在末尾添加,不覆盖原有内容)
                fp = fopen("contacts.txt", "a");
                if (fp == NULL)
                {
                    printf("文件打开失败!\n");
                    break;
                }
                addContact(fp);    // 调用添加联系人函数
                fclose(fp);        // 关闭文件
                break;

            case 2:
                // 以只读模式打开文件
                fp = fopen("contacts.txt", "r");
                if (fp == NULL)
                {
                    printf("通讯录为空或文件打开失败!\n");
                    break;
                }
                showAllContacts(fp);  // 调用显示所有联系人函数
                fclose(fp);           // 关闭文件
                break;

            case 3:
                printf("感谢使用,再见!\n");
                exit(0);  // 退出程序

            default:
                printf("无效选择,请重新输入!\n");
        }
    }

    return 0;
}

// 添加联系人函数
void addContact(FILE *fp)
{
    struct Contact c;  // 声明一个联系人变量

    printf("请输入姓名:");
    scanf("%s", c.name);

    printf("请输入电话号码:");
    scanf("%s", c.phone);

    // 将联系人信息写入文件,格式为:姓名 电话号码
    fprintf(fp, "%s %s\n", c.name, c.phone);

    printf("联系人添加成功!\n");
}

// 显示所有联系人函数
void showAllContacts(FILE *fp)
{
    struct Contact c;  // 用于临时存储读取的联系人信息

    printf("\n%-15s %s\n", "姓名", "电话号码");
    printf("------------------------------\n");

    // 循环从文件读取联系人信息
    while (fscanf(fp, "%s %s", c.name, c.phone) == 2)
    {
        printf("%-15s %s\n", c.name, c.phone);
    }
}

运行示例:

复制代码
===== 通讯录管理系统 =====
1. 添加联系人
2. 显示所有联系人
3. 退出
请输入选择:1
请输入姓名:张三
请输入电话号码:13800001111
联系人添加成功!

===== 通讯录管理系统 =====
1. 添加联系人
2. 显示所有联系人
3. 退出
请输入选择:2

姓名            电话号码
------------------------------
张三            13800001111

案例二:学生管理系统(二进制文件版)

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 定义学生结构体
struct Student
{
    int id;           // 学号
    char name[20];    // 姓名
    float score;      // 成绩
};

// 函数声明
void addStudent();         // 添加学生信息
void displayAll();         // 显示所有学生信息
void searchById();         // 按学号查找学生
void modifyScore();        // 修改学生成绩

int main()
{
    int choice;  // 用户菜单选择

    while (1)
    {
        // 显示主菜单
        printf("\n===== 学生信息管理系统 =====\n");
        printf("1. 添加学生信息\n");
        printf("2. 显示所有学生信息\n");
        printf("3. 按学号查找学生\n");
        printf("4. 修改学生成绩\n");
        printf("5. 退出\n");
        printf("请输入选择:");
        scanf("%d", &choice);

        switch (choice)
        {
            case 1: addStudent();   break;  // 添加
            case 2: displayAll();   break;  // 显示所有
            case 3: searchById();   break;  // 查找
            case 4: modifyScore();  break;  // 修改
            case 5:
                printf("程序结束!\n");
                exit(0);
            default:
                printf("无效选择!\n");
        }
    }

    return 0;
}

// 添加学生信息
void addStudent()
{
    FILE *fp;              // 文件指针
    struct Student stu;    // 学生变量

    // 以二进制追加模式打开文件
    // "ab"表示在二进制文件末尾追加数据
    fp = fopen("students.dat", "ab");

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

    // 输入学生信息
    printf("请输入学号:");
    scanf("%d", &stu.id);
    printf("请输入姓名:");
    scanf("%s", stu.name);
    printf("请输入成绩:");
    scanf("%f", &stu.score);

    // 将学生信息以二进制方式写入文件
    fwrite(&stu, sizeof(struct Student), 1, fp);

    printf("学生信息添加成功!\n");

    fclose(fp);  // 关闭文件
}

// 显示所有学生信息
void displayAll()
{
    FILE *fp;              // 文件指针
    struct Student stu;    // 临时存储读取的学生信息
    int count = 0;         // 学生计数器

    // 以二进制只读模式打开文件
    fp = fopen("students.dat", "rb");

    if (fp == NULL)
    {
        printf("文件打开失败或没有数据!\n");
        return;
    }

    printf("\n%-6s %-10s %s\n", "学号", "姓名", "成绩");
    printf("------------------------------\n");

    // 循环读取文件中的学生信息
    // fread返回成功读取的块数,1表示成功读取1个学生
    while (fread(&stu, sizeof(struct Student), 1, fp) == 1)
    {
        printf("%-6d %-10s %.1f\n", stu.id, stu.name, stu.score);
        count++;  // 计数器加1
    }

    if (count == 0)
    {
        printf("(暂无学生信息)\n");
    }
    else
    {
        printf("------------------------------\n");
        printf("共 %d 名学生\n", count);
    }

    fclose(fp);  // 关闭文件
}

// 按学号查找学生
void searchById()
{
    FILE *fp;              // 文件指针
    struct Student stu;    // 临时存储读取的学生信息
    int searchId;          // 要查找的学号
    int found = 0;         // 是否找到的标志,0表示未找到

    // 以二进制只读模式打开文件
    fp = fopen("students.dat", "rb");

    if (fp == NULL)
    {
        printf("文件打开失败或没有数据!\n");
        return;
    }

    printf("请输入要查找的学号:");
    scanf("%d", &searchId);

    // 遍历文件中的所有学生记录
    while (fread(&stu, sizeof(struct Student), 1, fp) == 1)
    {
        // 判断当前学生的学号是否匹配
        if (stu.id == searchId)
        {
            printf("\n找到该学生:\n");
            printf("学号:%d\n", stu.id);
            printf("姓名:%s\n", stu.name);
            printf("成绩:%.1f\n", stu.score);
            found = 1;  // 设置标志为已找到
            break;       // 找到后退出循环
        }
    }

    // 如果遍历完所有记录仍未找到
    if (!found)
    {
        printf("未找到学号为 %d 的学生!\n", searchId);
    }

    fclose(fp);  // 关闭文件
}

// 修改学生成绩
void modifyScore()
{
    FILE *fp;              // 文件指针
    struct Student stu;    // 临时存储读取的学生信息
    int searchId;          // 要修改的学号
    float newScore;        // 新成绩
    int found = 0;         // 是否找到的标志
    long pos;              // 记录找到时的文件位置

    // 以二进制读写模式打开文件
    // "r+b"表示以读写方式打开已有的二进制文件
    fp = fopen("students.dat", "r+b");

    if (fp == NULL)
    {
        printf("文件打开失败或没有数据!\n");
        return;
    }

    printf("请输入要修改成绩的学号:");
    scanf("%d", &searchId);

    // 遍历文件查找目标学生
    while (fread(&stu, sizeof(struct Student), 1, fp) == 1)
    {
        if (stu.id == searchId)
        {
            printf("找到学生:%s,当前成绩:%.1f\n", stu.name, stu.score);

            printf("请输入新成绩:");
            scanf("%f", &newScore);

            // 记录当前文件位置(即该学生记录的末尾)
            pos = ftell(fp);

            // 将文件指针回退到该学生记录的起始位置
            fseek(fp, pos - sizeof(struct Student), SEEK_SET);

            // 更新成绩
            stu.score = newScore;

            // 将修改后的数据写回文件(覆盖原数据)
            fwrite(&stu, sizeof(struct Student), 1, fp);

            printf("成绩修改成功!\n");
            found = 1;
            break;
        }
    }

    if (!found)
    {
        printf("未找到学号为 %d 的学生!\n", searchId);
    }

    fclose(fp);  // 关闭文件
}

八、知识点总结表

文件操作函数速查表

函数名 功能 所属分类
fopen() 打开文件 基本操作
fclose() 关闭文件 基本操作
fputc() 写入一个字符 字符读写
fgetc() 读取一个字符 字符读写
fputs() 写入一个字符串 字符串读写
fgets() 读取一行字符串 字符串读写
fprintf() 格式化写入 格式化读写
fscanf() 格式化读取 格式化读写
fwrite() 写入数据块(二进制) 数据块读写
fread() 读取数据块(二进制) 数据块读写
fseek() 定位文件位置 文件定位
rewind() 重置到文件开头 文件定位
ftell() 获取当前位置 文件定位
remove() 删除文件 文件管理
rename() 重命名文件 文件管理
feof() 检测文件末尾 状态检测
ferror() 检测读写错误 状态检测
clearerr() 清除错误/结束标志 状态检测

文件操作的标准流程

复制代码
1. 声明文件指针:FILE *fp;
2. 打开文件:fp = fopen("文件名", "模式");
3. 判断是否打开成功:if (fp == NULL) ...
4. 读写操作:fputc/fgetc/fputs/fgets/fprintf/fscanf/fread/fwrite
5. 关闭文件:fclose(fp);

核心原则:每次打开文件后都要判断是否成功,使用完毕后必须关闭文件。这是文件操作中最基本也最重要的规范。

相关推荐
羊羊一洋1 小时前
ROM&RAM
c语言·stm32·学习
zhuhai_xigedian2 小时前
源网荷储一体化柜通信层:网络架构与协议矩阵设计
网络·矩阵·架构
shiyi.十一2 小时前
【CISP教材】第9知识域:计算环境安全
网络·安全·cisp
物联网软硬件开发-轨物科技2 小时前
【轨物方案】深度解读开关柜智能感知及一键顺控专家系统
大数据·运维·网络·人工智能·算法·机器人
小小、码农2 小时前
[特殊字符]【C++11】类的新功能全面解析:移动构造、移动赋值、default、delete、final 与 override
linux·开发语言·网络·c++
库玛西2 小时前
外部排序全解析:败者树、置换-选择排序与最佳归并树
c语言·数据结构·c++·笔记·考研·算法
库玛西3 小时前
计算机网络第三章:数据链路层
服务器·网络·计算机网络·考研·算法·面试
竣达技术3 小时前
Modbus 转 SNMP 协议转换器|自定义协议 + SNMP TRAP 主动上报
运维·服务器·网络·数据库·安全
优化Henry3 小时前
LTE小区光路降质告警排查与处理实录
运维·网络·学习·5g·tdd