第 20 天:C 语言文件操作

目录

🌷前言:

一、文件操作的基本概念

1.1 什么是文件?

1.2 文件的类型

1.3 文件操作的基本流程

二、文件指针

2.1 文件指针的声明

2.2 标准流

三、文件的打开与关闭

3.1 打开文件:fopen 函数

3.2 关闭文件:fclose 函数

四、文本文件的读写操作

4.1 字符读写

4.1.1 读取字符:fgetc 函数

4.1.2 写入字符:fputc 函数

4.2 字符串读写

4.2.1 读取字符串:fgets 函数

4.2.2 写入字符串:fputs 函数

4.3 格式化读写

4.3.1 格式化读取:fscanf 函数

4.3.2 格式化写入:fprintf 函数

五、二进制文件的读写操作

5.1 读取二进制数据:fread 函数

5.2 写入二进制数据:fwrite 函数

六、文件定位操作

6.1 获取当前位置:ftell 函数

6.2 设置文件位置:fseek 函数

6.3 定位到文件开头:rewind 函数

七、文件操作的错误处理

7.1 检测文件结束:feof 函数

7.2 检测错误:ferror 函数

7.3 清除错误标志:clearerr 函数

八、文件操作的应用示例

8.1 示例 1:文件复制程序

8.2 示例 2:学生成绩管理系统

九、总结

9.1 核心知识点回顾

9.2 编程思想提炼

🌈共勉:


🌷前言:

在前一篇内容中,我们学习了 C 语言的递归函数,包括递归的基本概念、工作原理和实现方法,以及递归在数学问题、数据结构遍历、分治算法等领域的应用。递归通过函数调用自身,能够将复杂问题分解为更小的同类问题,以简洁优雅的方式解决许多难题。

今天,我们将学习 C 语言的文件操作。在之前的学习中,我们的数据都是存储在内存中的,程序结束后数据就会丢失。文件操作允许我们将数据存储在外部存储设备(如硬盘)上,实现数据的持久化保存和读取。文件操作是程序与外部世界交互的重要方式,广泛应用于数据存储、配置管理、日志记录等场景。掌握文件操作是编写实用程序的必备技能。

一、文件操作的基本概念

1.1 什么是文件?

在计算机中,文件是存储在外部存储设备上的一组相关数据的集合。文件具有以下特点:

  • 文件有一个唯一的文件名,用于标识和访问文件
  • 文件中的数据以字节序列的形式存储
  • 文件可以长期保存,不受程序运行状态的影响
  • 文件可以被多个程序共享访问

在 C 语言中,文件被视为字节流,无论是文本还是二进制数据,都以字节为单位进行处理。

1.2 文件的类型

根据存储格式,文件可以分为两类:

  1. 文本文件(Text File)

    • 以字符编码(如 ASCII、UTF-8)的形式存储数据
    • 内容可以被文本编辑器直接阅读
    • 换行符在不同操作系统中可能不同(Windows: \r\n, Linux: \n, macOS: \n)
    • 适合存储人类可读的数据,如配置文件、日志等
  2. 二进制文件(Binary File)

    • 以数据在内存中的原始形式存储
    • 内容通常不能被文本编辑器直接阅读
    • 数据存储紧凑,没有格式转换开销
    • 适合存储程序内部数据结构,如图片、音频、序列化对象等

1.3 文件操作的基本流程

C 语言中,文件操作通常遵循以下基本流程:

  1. 打开文件:建立程序与文件的连接,准备进行读写操作
  2. 读写文件:按照需要读取或写入数据
  3. 关闭文件:断开程序与文件的连接,释放相关资源

二、文件指针

在 C 语言中,文件操作通过文件指针(File Pointer)来进行。文件指针是指向FILE结构体的指针,FILE结构体定义在<stdio.h>头文件中,包含了文件的相关信息(如文件名、文件状态、当前位置等)。

2.1 文件指针的声明

文件指针的声明格式:

c

运行

FILE *指针名;

例如:

c

运行

FILE *fp;  // 声明一个文件指针fp

2.2 标准流

C 语言预定义了三个标准文件指针,无需打开即可直接使用:

  1. 标准输入(stdin):通常关联到键盘,用于输入操作
  2. 标准输出(stdout):通常关联到屏幕,用于输出操作
  3. 标准错误(stderr):通常关联到屏幕,用于错误信息输出

这些标准流定义在<stdio.h>中,我们常用的printfscanf函数就是通过stdoutstdin进行操作的。

三、文件的打开与关闭

3.1 打开文件:fopen 函数

fopen函数用于打开一个文件,并返回一个文件指针。

函数原型:

c

运行

FILE *fopen(const char *filename, const char *mode);
  • 参数filename:要打开的文件名(可以包含路径)
  • 参数mode:打开文件的模式(访问方式)
  • 返回值:成功时返回文件指针;失败时返回NULL

常用的文件打开模式

模式含义对文本文件的影响对二进制文件的影响
"r"只读方式打开从文件开头读取从文件开头读取
"w"只写方式打开若文件存在则清空内容,若不存在则创建同左
"a"追加方式打开若文件存在则在末尾追加,若不存在则创建同左
"r+"读写方式打开从文件开头读写从文件开头读写
"w+"读写方式打开若文件存在则清空内容,若不存在则创建同左
"a+"读写方式打开可读取整个文件,但写入只能追加到末尾同左

对于二进制文件,需要在模式字符串后添加 "b":

  • "rb":只读打开二进制文件
  • "wb":只写打开二进制文件
  • "ab":追加打开二进制文件
  • "rb+"、"wb+"、"ab+":对应的读写模式

注意

  • 打开文件时应始终检查返回的文件指针是否为NULL,以处理文件打开失败的情况
  • 不同操作系统对文件路径的表示可能不同(Windows 使用反斜杠\,Linux 和 macOS 使用正斜杠/

3.2 关闭文件:fclose 函数

fclose函数用于关闭一个已打开的文件,释放相关资源。

函数原型:

c

运行

int fclose(FILE *stream);
  • 参数stream:要关闭的文件指针
  • 返回值:成功时返回 0;失败时返回 EOF(-1)

注意

  • 使用完文件后应及时关闭,避免资源泄露
  • 关闭文件后,文件指针不再有效,不应再使用

示例:文件的打开与关闭

c

运行

#include <stdio.h>

int main() {
    FILE *fp;
    
    // 打开文件
    fp = fopen("example.txt", "w");
    
    // 检查文件是否成功打开
    if (fp == NULL) {
        printf("无法打开文件!\n");
        return 1;  // 非正常退出
    }
    
    printf("文件打开成功!\n");
    
    // 关闭文件
    if (fclose(fp) == 0) {
        printf("文件关闭成功!\n");
    } else {
        printf("文件关闭失败!\n");
    }
    
    return 0;
}

四、文本文件的读写操作

文本文件的读写操作以字符或字符串为单位,C 语言提供了一系列函数用于文本文件的读写。

4.1 字符读写

4.1.1 读取字符:fgetc 函数

fgetc函数从文件中读取一个字符。

函数原型:

c

运行

int fgetc(FILE *stream);
  • 参数stream:文件指针
  • 返回值:成功时返回读取的字符(转换为 int);失败或到达文件末尾时返回 EOF
4.1.2 写入字符:fputc 函数

fputc函数向文件中写入一个字符。

函数原型:

c

运行

int fputc(int character, FILE *stream);
  • 参数character:要写入的字符(int 类型,通常传递 char 值)
  • 参数stream:文件指针
  • 返回值:成功时返回写入的字符;失败时返回 EOF

示例:使用 fputc 和 fgetc 读写字符

c

运行

#include <stdio.h>

int main() {
    FILE *fp;
    char ch;
    
    // 写入字符到文件
    fp = fopen("chars.txt", "w");
    if (fp == NULL) {
        printf("无法打开文件进行写入!\n");
        return 1;
    }
    
    // 写入A到Z的大写字母
    for (ch = 'A'; ch <= 'Z'; ch++) {
        fputc(ch, fp);
    }
    
    fclose(fp);
    printf("字符写入完成!\n");
    
    // 从文件读取字符
    fp = fopen("chars.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件进行读取!\n");
        return 1;
    }
    
    printf("文件内容:");
    // 读取并打印所有字符,直到文件末尾
    while ((ch = fgetc(fp)) != EOF) {
        printf("%c", ch);
    }
    printf("\n");
    
    fclose(fp);
    return 0;
}

输出结果

plaintext

字符写入完成!
文件内容:ABCDEFGHIJKLMNOPQRSTUVWXYZ

4.2 字符串读写

4.2.1 读取字符串:fgets 函数

fgets函数从文件中读取一个字符串。

函数原型:

c

运行

char *fgets(char *str, int n, FILE *stream);
  • 参数str:存储读取字符串的缓冲区
  • 参数n:最多读取 n-1 个字符(留出一个位置给终止符 '\0')
  • 参数stream:文件指针
  • 返回值:成功时返回 str;失败或到达文件末尾时返回 NULL

注意fgets会读取换行符 '\n' 并将其包含在字符串中,遇到换行符或文件末尾会停止读取。

4.2.2 写入字符串:fputs 函数

fputs函数向文件中写入一个字符串。

函数原型:

c

运行

int fputs(const char *str, FILE *stream);
  • 参数str:要写入的字符串
  • 参数stream:文件指针
  • 返回值:成功时返回非负值;失败时返回 EOF

注意fputs不会自动添加换行符,需要显式写入 '\n'。

示例:使用 fputs 和 fgets 读写字符串

c

运行

#include <stdio.h>
#include <string.h>

int main() {
    FILE *fp;
    char buffer[100];
    
    // 写入字符串到文件
    fp = fopen("strings.txt", "w");
    if (fp == NULL) {
        printf("无法打开文件进行写入!\n");
        return 1;
    }
    
    fputs("Hello, World!\n", fp);
    fputs("这是一个字符串写入示例。\n", fp);
    fputs("C语言文件操作很有趣!", fp);
    
    fclose(fp);
    printf("字符串写入完成!\n");
    
    // 从文件读取字符串
    fp = fopen("strings.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件进行读取!\n");
        return 1;
    }
    
    printf("文件内容:\n");
    // 读取并打印所有行,直到文件末尾
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }
    
    fclose(fp);
    return 0;
}

输出结果

plaintext

字符串写入完成!
文件内容:
Hello, World!
这是一个字符串写入示例。
C语言文件操作很有趣!

4.3 格式化读写

4.3.1 格式化读取:fscanf 函数

fscanf函数从文件中按指定格式读取数据,类似于scanf,但从文件读取而不是标准输入。

函数原型:

c

运行

int fscanf(FILE *stream, const char *format, ...);
  • 参数stream:文件指针
  • 参数format:格式控制字符串
  • 参数...:存储读取结果的变量地址列表
  • 返回值:成功读取的数据项数;失败或到达文件末尾时返回 EOF
4.3.2 格式化写入:fprintf 函数

fprintf函数按指定格式向文件中写入数据,类似于printf,但写入到文件而不是标准输出。

函数原型:

c

运行

int fprintf(FILE *stream, const char *format, ...);
  • 参数stream:文件指针
  • 参数format:格式控制字符串
  • 参数...:要写入的数据
  • 返回值:成功写入的字符数;失败时返回负数

示例:使用 fprintf 和 fscanf 进行格式化读写

c

运行

#include <stdio.h>

// 学生结构体
typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main() {
    FILE *fp;
    Student students[3] = {
        {101, "张三", 90.5},
        {102, "李四", 85.0},
        {103, "王五", 92.5}
    };
    Student s;
    
    // 格式化写入学生信息
    fp = fopen("students.txt", "w");
    if (fp == NULL) {
        printf("无法打开文件进行写入!\n");
        return 1;
    }
    
    for (int i = 0; i < 3; i++) {
        fprintf(fp, "%d %s %.1f\n", 
                students[i].id, 
                students[i].name, 
                students[i].score);
    }
    
    fclose(fp);
    printf("学生信息写入完成!\n");
    
    // 格式化读取学生信息
    fp = fopen("students.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件进行读取!\n");
        return 1;
    }
    
    printf("读取的学生信息:\n");
    printf("ID\t姓名\t成绩\n");
    printf("------------------------\n");
    
    while (fscanf(fp, "%d %s %f", &s.id, s.name, &s.score) == 3) {
        printf("%d\t%s\t%.1f\n", s.id, s.name, s.score);
    }
    
    fclose(fp);
    return 0;
}

输出结果

plaintext

学生信息写入完成!
读取的学生信息:
ID      姓名    成绩
------------------------
101     张三    90.5
102     李四    85.0
103     王五    92.5

五、二进制文件的读写操作

二进制文件的读写操作以字节为单位,可以直接读写内存中的数据结构,使用freadfwrite函数。

5.1 读取二进制数据:fread 函数

fread函数从二进制文件中读取数据。

函数原型:

c

运行

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  • 参数ptr:存储读取数据的缓冲区指针
  • 参数size:每个数据项的大小(以字节为单位)
  • 参数count:要读取的数据项数量
  • 参数stream:文件指针
  • 返回值:成功读取的数据项数量(可能小于 count,甚至为 0)

5.2 写入二进制数据:fwrite 函数

fwrite函数向二进制文件中写入数据。

函数原型:

c

运行

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • 参数ptr:指向要写入数据的指针
  • 参数size:每个数据项的大小(以字节为单位)
  • 参数count:要写入的数据项数量
  • 参数stream:文件指针
  • 返回值:成功写入的数据项数量(若小于 count 则表示出错)

示例:使用 fread 和 fwrite 读写二进制文件

c

运行

#include <stdio.h>
#include <string.h>

// 产品结构体
typedef struct {
    int id;
    char name[50];
    float price;
    int quantity;
} Product;

int main() {
    FILE *fp;
    Product products[3] = {
        {1001, "笔记本电脑", 5999.99f, 10},
        {1002, "智能手机", 3999.99f, 20},
        {1003, "平板电脑", 2499.99f, 15}
    };
    Product p;
    
    // 写入二进制数据
    fp = fopen("products.dat", "wb");
    if (fp == NULL) {
        printf("无法打开文件进行写入!\n");
        return 1;
    }
    
    // 写入整个产品数组
    size_t written = fwrite(products, sizeof(Product), 3, fp);
    printf("成功写入 %zu 个产品记录\n", written);
    
    fclose(fp);
    
    // 读取二进制数据
    fp = fopen("products.dat", "rb");
    if (fp == NULL) {
        printf("无法打开文件进行读取!\n");
        return 1;
    }
    
    printf("\n读取的产品信息:\n");
    printf("ID\t名称\t\t价格\t数量\n");
    printf("----------------------------------------\n");
    
    // 逐个读取产品记录
    while (fread(&p, sizeof(Product), 1, fp) == 1) {
        printf("%d\t%s\t%.2f\t%d\n", 
               p.id, p.name, p.price, p.quantity);
    }
    
    fclose(fp);
    return 0;
}

输出结果

plaintext

成功写入 3 个产品记录

读取的产品信息:
ID      名称            价格    数量
----------------------------------------
1001    笔记本电脑       5999.99 10
1002    智能手机         3999.99 20
1003    平板电脑         2499.99 15

文本文件与二进制文件的比较

  • 文本文件存储的是字符的编码,人可以直接阅读;二进制文件存储的是数据的原始字节,通常需要专用程序解析
  • 文本文件读写需要进行格式转换,效率较低;二进制文件直接读写内存数据,效率较高
  • 文本文件便于调试和移植;二进制文件更节省空间,适合存储复杂数据结构

六、文件定位操作

文件定位操作允许我们移动文件内部的读写指针,实现随机访问文件的不同位置。

6.1 获取当前位置:ftell 函数

ftell函数返回文件当前的读写位置。

函数原型:

c

运行

long int ftell(FILE *stream);
  • 参数stream:文件指针
  • 返回值:成功时返回当前位置(相对于文件开头的字节数);失败时返回 - 1L

6.2 设置文件位置:fseek 函数

fseek函数设置文件的读写位置。

函数原型:

c

运行

int fseek(FILE *stream, long int offset, int whence);
  • 参数stream:文件指针
  • 参数offset:偏移量(字节数)
  • 参数whence:起始位置,取值可以是:
    • SEEK_SET:从文件开头开始
    • SEEK_CUR:从当前位置开始
    • SEEK_END:从文件末尾开始
  • 返回值:成功时返回 0;失败时返回非 0

6.3 定位到文件开头:rewind 函数

rewind函数将文件读写指针定位到文件开头,相当于fseek(stream, 0, SEEK_SET)

函数原型:

c

运行

void rewind(FILE *stream);
  • 参数stream:文件指针

示例:文件定位操作

c

运行

#include <stdio.h>

int main() {
    FILE *fp;
    char buffer[100];
    long position;
    
    // 创建一个测试文件
    fp = fopen("position.txt", "w+");
    if (fp == NULL) {
        printf("无法打开文件!\n");
        return 1;
    }
    
    // 写入一些内容
    fprintf(fp, "C语言文件定位操作示例\n");
    fprintf(fp, "这是第二行\n");
    fprintf(fp, "这是第三行\n");
    
    // 获取当前位置
    position = ftell(fp);
    printf("写入后当前位置: %ld 字节\n", position);
    
    // 定位到文件开头
    rewind(fp);
    printf("rewind后当前位置: %ld 字节\n", ftell(fp));
    
    // 读取第一行
    fgets(buffer, sizeof(buffer), fp);
    printf("读取的第一行: %s", buffer);
    
    // 获取当前位置
    position = ftell(fp);
    printf("读取第一行后位置: %ld 字节\n", position);
    
    // 从当前位置向后移动5个字节
    fseek(fp, 5, SEEK_CUR);
    printf("向后移动5字节后位置: %ld 字节\n", ftell(fp));
    
    // 读取剩余内容
    fgets(buffer, sizeof(buffer), fp);
    printf("从新位置读取: %s", buffer);
    
    // 定位到文件末尾前10个字节
    fseek(fp, -10, SEEK_END);
    printf("定位到末尾前10字节后位置: %ld 字节\n", ftell(fp));
    
    // 读取最后10个字节
    fgets(buffer, sizeof(buffer), fp);
    printf("读取的最后内容: %s", buffer);
    
    fclose(fp);
    return 0;
}

输出结果

plaintext

写入后当前位置: 43 字节
rewind后当前位置: 0 字节
读取的第一行: C语言文件定位操作示例
读取第一行后位置: 21 字节
向后移动5字节后位置: 26 字节
从新位置读取: 二行
这是第三行
定位到末尾前10字节后位置: 33 字节
读取的最后内容: 第三行

七、文件操作的错误处理

文件操作可能会出现各种错误(如文件不存在、权限不足等),良好的错误处理是编写健壮程序的重要部分。

7.1 检测文件结束:feof 函数

feof函数判断文件是否到达末尾。

函数原型:

c

运行

int feof(FILE *stream);
  • 参数stream:文件指针
  • 返回值:如果已到达文件末尾,返回非 0 值;否则返回 0

7.2 检测错误:ferror 函数

ferror函数检查文件操作是否发生错误。

函数原型:

c

运行

int ferror(FILE *stream);
  • 参数stream:文件指针
  • 返回值:如果发生错误,返回非 0 值;否则返回 0

7.3 清除错误标志:clearerr 函数

clearerr函数清除文件的错误标志和文件结束标志。

函数原型:

c

运行

void clearerr(FILE *stream);
  • 参数stream:文件指针

示例:文件操作的错误处理

c

运行

#include <stdio.h>
#include <string.h>

int main() {
    FILE *fp;
    char buffer[100];
    
    // 尝试打开一个不存在的文件
    fp = fopen("nonexistent.txt", "r");
    if (fp == NULL) {
        perror("文件打开失败");  // perror打印错误信息
        return 1;
    }
    
    // 读取文件内容
    if (fgets(buffer, sizeof(buffer), fp) == NULL) {
        if (feof(fp)) {
            printf("已到达文件末尾\n");
        } else if (ferror(fp)) {
            perror("读取文件时发生错误");
        }
    } else {
        printf("读取的内容: %s", buffer);
    }
    
    // 尝试写入只读文件
    if (fputs("尝试写入", fp) == EOF) {
        perror("写入文件时发生错误");
        clearerr(fp);  // 清除错误标志
    }
    
    fclose(fp);
    return 0;
}

输出结果(假设文件不存在):

plaintext

文件打开失败: No such file or directory

perror函数会打印一个描述性的错误信息,它使用全局变量errno来确定错误类型。errno定义在<errno.h>头文件中,不同的错误对应不同的数值。

八、文件操作的应用示例

8.1 示例 1:文件复制程序

下面的程序实现了文件复制功能,可以复制文本文件和二进制文件。

c

运行

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

// 文件复制函数
int copy_file(const char *source_path, const char *dest_path, int is_binary) {
    FILE *source_fp, *dest_fp;
    int ch;
    const char *source_mode = is_binary ? "rb" : "r";
    const char *dest_mode = is_binary ? "wb" : "w";
    
    // 打开源文件
    source_fp = fopen(source_path, source_mode);
    if (source_fp == NULL) {
        perror("无法打开源文件");
        return 0;
    }
    
    // 打开目标文件
    dest_fp = fopen(dest_path, dest_mode);
    if (dest_fp == NULL) {
        perror("无法打开目标文件");
        fclose(source_fp);
        return 0;
    }
    
    // 复制文件内容
    if (is_binary) {
        // 二进制文件复制,使用fread/fwrite效率更高
        char buffer[4096];
        size_t bytes_read;
        
        while ((bytes_read = fread(buffer, 1, sizeof(buffer), source_fp)) > 0) {
            if (fwrite(buffer, 1, bytes_read, dest_fp) != bytes_read) {
                perror("写入文件时出错");
                fclose(source_fp);
                fclose(dest_fp);
                return 0;
            }
        }
        
        if (ferror(source_fp)) {
            perror("读取文件时出错");
            fclose(source_fp);
            fclose(dest_fp);
            return 0;
        }
    } else {
        // 文本文件复制,使用fgetc/fputc
        while ((ch = fgetc(source_fp)) != EOF) {
            if (fputc(ch, dest_fp) == EOF) {
                perror("写入文件时出错");
                fclose(source_fp);
                fclose(dest_fp);
                return 0;
            }
        }
        
        if (ferror(source_fp)) {
            perror("读取文件时出错");
            fclose(source_fp);
            fclose(dest_fp);
            return 0;
        }
    }
    
    // 关闭文件
    fclose(source_fp);
    fclose(dest_fp);
    
    return 1;
}

int main(int argc, char *argv[]) {
    if (argc != 3 && argc != 4) {
        printf("用法: %s <源文件> <目标文件> [b]\n", argv[0]);
        printf("  复制文件,可选参数b表示复制二进制文件\n");
        return 1;
    }
    
    int is_binary = (argc == 4 && argv[3][0] == 'b');
    
    if (copy_file(argv[1], argv[2], is_binary)) {
        printf("文件复制成功!\n");
        return 0;
    } else {
        printf("文件复制失败!\n");
        return 1;
    }
}

这个程序接受命令行参数,支持复制文本文件和二进制文件,展示了文件操作的综合应用。

8.2 示例 2:学生成绩管理系统

下面的程序实现了一个简单的学生成绩管理系统,使用文件存储学生信息。

c

运行

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

#define MAX_NAME 50
#define FILENAME "students.dat"

// 学生结构体
typedef struct {
    int id;
    char name[MAX_NAME];
    float score;
} Student;

// 添加学生
void add_student() {
    Student s;
    FILE *fp;
    
    printf("\n----- 添加新学生 -----\n");
    printf("请输入学生ID: ");
    scanf("%d", &s.id);
    getchar();  // 吸收换行符
    
    printf("请输入学生姓名: ");
    fgets(s.name, MAX_NAME, stdin);
    // 移除姓名中的换行符
    s.name[strcspn(s.name, "\n")] = '\0';
    
    printf("请输入学生成绩: ");
    scanf("%f", &s.score);
    
    // 以追加模式打开文件
    fp = fopen(FILENAME, "ab");
    if (fp == NULL) {
        perror("无法打开文件");
        return;
    }
    
    // 写入学生信息
    if (fwrite(&s, sizeof(Student), 1, fp) != 1) {
        perror("写入文件失败");
    } else {
        printf("学生信息添加成功!\n");
    }
    
    fclose(fp);
}

// 显示所有学生
void display_all_students() {
    Student s;
    FILE *fp;
    int count = 0;
    
    printf("\n----- 所有学生信息 -----\n");
    
    fp = fopen(FILENAME, "rb");
    if (fp == NULL) {
        perror("无法打开文件");
        return;
    }
    
    printf("ID\t姓名\t\t成绩\n");
    printf("-------------------------\n");
    
    // 读取所有学生信息
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        printf("%d\t%s\t\t%.1f\n", s.id, s.name, s.score);
        count++;
    }
    
    if (count == 0) {
        printf("没有学生信息!\n");
    } else {
        printf("-------------------------\n");
        printf("共 %d 名学生\n", count);
    }
    
    fclose(fp);
}

// 查找学生
void search_student() {
    Student s;
    FILE *fp;
    int target_id;
    int found = 0;
    
    printf("\n----- 查找学生 -----\n");
    printf("请输入要查找的学生ID: ");
    scanf("%d", &target_id);
    
    fp = fopen(FILENAME, "rb");
    if (fp == NULL) {
        perror("无法打开文件");
        return;
    }
    
    // 查找学生
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        if (s.id == target_id) {
            printf("\n找到学生信息:\n");
            printf("ID: %d\n", s.id);
            printf("姓名: %s\n", s.name);
            printf("成绩: %.1f\n", s.score);
            found = 1;
            break;
        }
    }
    
    if (!found) {
        printf("没有找到ID为 %d 的学生!\n", target_id);
    }
    
    fclose(fp);
}

// 菜单
void show_menu() {
    printf("\n===== 学生成绩管理系统 =====\n");
    printf("1. 添加学生\n");
    printf("2. 显示所有学生\n");
    printf("3. 查找学生\n");
    printf("4. 退出\n");
    printf("请选择操作 (1-4): ");
}

int main() {
    int choice;
    
    do {
        show_menu();
        scanf("%d", &choice);
        
        switch (choice) {
            case 1:
                add_student();
                break;
            case 2:
                display_all_students();
                break;
            case 3:
                search_student();
                break;
            case 4:
                printf("谢谢使用,再见!\n");
                break;
            default:
                printf("无效的选择,请重新输入!\n");
        }
    } while (choice != 4);
    
    return 0;
}

这个程序实现了一个简单的学生成绩管理系统,使用二进制文件存储学生信息,支持添加、显示和查找学生功能,展示了文件操作在实际应用中的使用。

九、总结

9.1 核心知识点回顾

  • 文件操作允许程序将数据存储在外部存储设备上,实现数据的持久化
  • 文件分为文本文件和二进制文件,各有适用场景
  • C 语言通过文件指针(FILE*)进行文件操作
  • 文件操作的基本流程:打开文件(fopen)-> 读写文件 -> 关闭文件(fclose)
  • 文本文件读写函数:fgetc、fputc、fgets、fputs、fscanf、fprintf
  • 二进制文件读写函数:fread、fwrite
  • 文件定位函数:ftell、fseek、rewind
  • 文件错误处理函数:feof、ferror、clearerr

9.2 编程思想提炼

  • 持久化存储:文件操作实现了数据的持久化,使程序的数据能够在程序退出后保留
  • 数据序列化:二进制文件操作实际上是数据的序列化和反序列化过程
  • 资源管理:文件是一种系统资源,需要正确管理(打开后必须关闭)
  • 错误处理:文件操作可能受外部因素影响(如文件不存在),必须进行错误处理
  • 接口设计:文件操作函数提供了抽象接口,隐藏了不同设备和文件系统的细节

🌟个人主页:编程攻城狮

🌟人生格言:得知坦然 ,失之淡然

🌈共勉:

今天我们学习了 C 语言的文件操作,这是程序与外部世界交互的重要方式。通过文件操作,程序可以将数据存储在外部存储设备上,实现数据的持久化保存和读取。

我们学习了文件的基本概念、文件指针的使用、文件的打开与关闭、文本文件和二进制文件的读写操作、文件定位以及错误处理等内容。这些知识使我们能够编写能够存储和读取数据的实用程序。

文件操作在实际编程中应用广泛,无论是配置文件处理、数据存储还是日志记录,都离不开文件操作。掌握文件操作是编写实用程序的基础,也是从编写玩具程序向实用程序迈进的重要一步。

在下一篇内容中,我们将学习 C 语言的预处理指令,包括宏定义、文件包含、条件编译等。预处理指令是 C 语言的一个重要特性,能够在编译前对源代码进行处理,提高代码的灵活性和可维护性。

继续加油,掌握文件操作将使你能够编写更实用、更有价值的程序,处理真实世界中的数据存储和读取需求,向成为专业的程序员迈进一大步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值