C语言中write与read函数如何正确使用?

99ANYc3cd6
预计阅读时长 34 分钟
位置: 首页 C语言 正文
  1. 核心概念:文件指针
  2. 打开和关闭文件:fopenfclose
  3. 核心读写函数详解
    • fwritefread (二进制读写)
    • fprintffscanf (格式化读写)
    • fputcfgetc (字符读写)
  4. 综合实例
  5. 重要总结与最佳实践

核心概念:文件指针

在 C 语言中,所有文件操作都通过一个指向 FILE 结构体的指针来完成,这个指针被称为文件指针

c语言 write read
(图片来源网络,侵删)

FILE 是在 stdio.h 头文件中定义的一个结构体,它包含了文件操作所需的各种信息,比如文件缓冲区、当前读写位置、文件描述符等。

声明文件指针:

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

打开和关闭文件:fopenfclose

在对文件进行读写之前,必须先打开它;操作完成后,必须关闭它。

fopen - 打开文件

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

c语言 write read
(图片来源网络,侵删)
FILE *fopen(const char *filename, const char *mode);
  • filename: 要打开的文件名(可以包含路径)。
  • mode: 文件打开模式,决定了你如何操作这个文件。
模式 含义 如果文件不存在 如果文件存在
"r" 只读 失败 打开,文件指针在开头
"w" 只写 创建新文件 清空文件内容
"a" 追加 创建新文件 打开,文件指针在末尾
"r+" 读写 失败 打开,文件指针在开头
"w+" 读写 创建新文件 清空文件内容
"a+" 读写 创建新文件 打开,文件指针在末尾

注意:

  • 在二进制模式下操作文件时,模式字符串后面要加一个 "b""rb", "wb", "ab+" 等,虽然在一些现代操作系统(如 Linux, macOS)上不加 "b" 也能正常工作,但为了代码的可移植性和明确性,强烈推荐使用。
  • "w""w+" 模式会销毁文件原有内容,请务必小心使用。

返回值:

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

fclose - 关闭文件

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

int fclose(FILE *stream);
  • stream: 要关闭的文件指针。
  • 返回值:成功返回 0,失败返回 EOF (通常是 -1)。

为什么必须关闭文件?

c语言 write read
(图片来源网络,侵删)
  1. 刷新缓冲区:写入操作通常是先将数据放入缓冲区,缓冲区满了才真正写入磁盘。fclose 会强制将缓冲区中剩余的数据写入文件。
  2. 释放资源:操作系统可以同时打开的文件数量是有限的,及时关闭文件可以释放文件句柄等资源。

核心读写函数详解

fwritefread (二进制读写)

这是最常用、最高效的读写方式,因为它直接在内存和文件之间复制数据块,不进行任何格式转换。

fwrite - 写入数据块

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • ptr: 指向要写入数据的内存地址的指针。
  • size: 每个数据项的大小(单位:字节)。
  • count: 要写入多少个 size 大小的数据项。
  • stream: 目标文件指针。

返回值: 成功写入的数据项的个数,如果出现错误,返回值可能小于 count

示例:

#include <stdio.h>
#include <string.h>
struct Student {
    int id;
    char name[50];
    float score;
};
int main() {
    FILE *fp = fopen("students.dat", "wb"); // "wb" 表示二进制写入模式
    if (fp == NULL) {
        perror("Failed to open file");
        return 1;
    }
    struct Student s1 = {101, "Zhang San", 95.5};
    struct Student s2 = {102, "Li Si", 88.0};
    // 写入第一个学生结构体
    fwrite(&s1, sizeof(struct Student), 1, fp);
    // 写入第二个学生结构体
    fwrite(&s2, sizeof(struct Student), 1, fp);
    fclose(fp);
    printf("Data written to students.dat\n");
    return 0;
}

fread - 读取数据块

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  • ptr: 指向存储读取数据的内存地址的指针。
  • size: 每个数据项的大小(单位:字节)。
  • count: 要读取多少个 size 大小的数据项。
  • stream: 源文件指针。

返回值: 成功读取的数据项的个数,如果到达文件末尾,返回值可能小于 count,如果返回 0,可能表示文件已读完或发生了错误。

示例:

#include <stdio.h>
struct Student {
    int id;
    char name[50];
    float score;
};
int main() {
    FILE *fp = fopen("students.dat", "rb"); // "rb" 表示二进制读取模式
    if (fp == NULL) {
        perror("Failed to open file");
        return 1;
    }
    struct Student s;
    // 循环读取,直到文件末尾
    while (fread(&s, sizeof(struct Student), 1, fp) == 1) {
        printf("ID: %d, Name: %s, Score: %.2f\n", s.id, s.name, s.score);
    }
    fclose(fp);
    return 0;
}

fprintffscanf (格式化读写)

这两个函数和 printf/scanf 非常相似,只是它们的目标是文件而不是屏幕/键盘。

fprintf - 格式化写入

int fprintf(FILE *stream, const char *format, ...);
  • stream: 目标文件指针。
  • format: 格式化字符串,和 printf 一样。
  • 可变参数,要写入的数据。

示例:

#include <stdio.h>
int main() {
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) {
        perror("Failed to open file");
        return 1;
    }
    fprintf(fp, "Hello, World!\n");
    fprintf(fp, "The value of pi is approximately: %.2f\n", 3.14159);
    fprintf(fp, "User: %s, Age: %d\n", "Alice", 30);
    fclose(fp);
    printf("Formatted data written to data.txt\n");
    return 0;
}

fscanf - 格式化读取

int fscanf(FILE *stream, const char *format, ...);
  • stream: 源文件指针。
  • format: 格式化字符串。
  • 可变参数,用于存储读取数据的变量的地址。

返回值: 成功匹配并赋值的字段数量,如果到达文件末尾或发生错误,返回 EOF

示例:

#include <stdio.h>
int main() {
    FILE *fp = fopen("data.txt", "r");
    if (fp == NULL) {
        perror("Failed to open file");
        return 1;
    }
    char greeting[50];
    float pi;
    char name[50];
    int age;
    fscanf(fp, "%s", greeting); // 读取 "Hello,"
    fscanf(fp, "%[^\n]", greeting); // 读取整行 " World!"
    printf("Greeting: %s\n", greeting);
    fscanf(fp, "The value of pi is approximately: %f", &pi);
    printf("Pi: %.2f\n", pi);
    fscanf(fp, "User: %s, Age: %d", name, &age);
    printf("User: %s, Age: %d\n", name, age);
    fclose(fp);
    return 0;
}

注意: fscanf 在处理复杂或格式不规范的文本文件时可能不太健壮,对于简单的、格式固定的文件,它很方便。


fputcfgetc (字符读写)

这两个函数用于逐个字符地读写文件,效率较低,但在处理某些特定任务(如配置文件解析)时很有用。

fputc - 写入一个字符

int fputc(int char, FILE *stream);
  • char: 要写入的字符(类型是 int,但通常用 char)。
  • stream: 目标文件指针。
  • 返回值:成功返回写入的字符,失败返回 EOF

fgetc - 读取一个字符

int fgetc(FILE *stream);
  • stream: 源文件指针。
  • 返回值:成功返回读取的字符(类型是 int),如果到达文件末尾或出错,返回 EOF

示例:

#include <stdio.h>
int main() {
    // 写入
    FILE *fp_out = fopen("char.txt", "w");
    if (fp_out) {
        fputc('H', fp_out);
        fputc('e', fp_out);
        fputc('l', fp_out);
        fputc('l', fp_out);
        fputc('o', fp_out);
        fclose(fp_out);
    }
    // 读取
    FILE *fp_in = fopen("char.txt", "r");
    if (fp_in) {
        int ch;
        printf("Reading from char.txt:\n");
        while ((ch = fgetc(fp_in)) != EOF) {
            putchar(ch); // putchar 是向标准输出(屏幕)写一个字符
        }
        fclose(fp_in);
    }
    return 0;
}

综合实例

下面是一个完整的例子,演示如何创建一个简单的学生信息管理系统,将学生信息写入文件,然后再读取出来。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STUDENTS 100
#define FILENAME "students.dat"
typedef struct {
    int id;
    char name[50];
    float score;
} Student;
// 函数声明
void write_students();
void read_students();
int main() {
    int choice;
    do {
        printf("\n--- Student Management System ---\n");
        printf("1. Write student data to file\n");
        printf("2. Read student data from file\n");
        printf("0. Exit\n");
        printf("Enter your choice: ");
        scanf("%d", &choice);
        switch (choice) {
            case 1:
                write_students();
                break;
            case 2:
                read_students();
                break;
            case 0:
                printf("Exiting...\n");
                break;
            default:
                printf("Invalid choice. Please try again.\n");
        }
    } while (choice != 0);
    return 0;
}
void write_students() {
    FILE *fp = fopen(FILENAME, "wb");
    if (fp == NULL) {
        perror("Error opening file for writing");
        return;
    }
    int num_students;
    printf("How many students to add? ");
    scanf("%d", &num_students);
    Student s;
    for (int i = 0; i < num_students; i++) {
        printf("\nEnter details for student %d:\n", i + 1);
        printf("ID: ");
        scanf("%d", &s.id);
        printf("Name: ");
        scanf("%s", s.name); // 注意:这里不处理带空格的名字
        printf("Score: ");
        scanf("%f", &s.score);
        fwrite(&s, sizeof(Student), 1, fp);
    }
    fclose(fp);
    printf("\nStudent data successfully written to %s\n", FILENAME);
}
void read_students() {
    FILE *fp = fopen(FILENAME, "rb");
    if (fp == NULL) {
        perror("Error opening file for reading");
        return;
    }
    printf("\n--- Reading Student Data ---\n");
    Student s;
    int count = 0;
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        printf("Student %d:\n", ++count);
        printf("  ID: %d\n", s.id);
        printf("  Name: %s\n", s.name);
        printf("  Score: %.2f\n", s.score);
    }
    if (count == 0) {
        printf("No student data found in the file.\n");
    }
    fclose(fp);
}

重要总结与最佳实践

  1. 始终检查 fopen 的返回值:永远不要假设文件打开成功。fopen 返回 NULL,程序应该优雅地处理错误(例如打印错误信息并退出),而不是继续操作一个无效的指针。

    FILE *fp = fopen("my_file.txt", "r");
    if (fp == NULL) {
        // perror 会打印 "my_file.txt: " 然后加上系统错误信息
        perror("Error opening my_file.txt"); 
        exit(EXIT_FAILURE); // 使用 exit 终止程序
    }
  2. 始终关闭文件:使用 fclose 释放资源,确保数据被正确写入磁盘。

  3. 二进制模式 vs 文本模式

    • 二进制模式 ("rb", "wb"):推荐用于处理结构体、数组等非文本数据,它在不同操作系统之间具有最好的兼容性,因为它不会对换行符等特殊字符进行转换。
    • 文本模式 ("r", "w"):用于处理纯文本文件,在 Windows 系统上,文本模式下的 \n 会被转换为 \r\n 写入文件,读取时再转换回 \n,在 Linux/macOS 上则没有这种转换。
  4. 选择合适的读写函数

    • fwrite/fread:处理大量数据、结构体、数组等,性能最高。
    • fprintf/fscanf:处理需要人类可读的、格式化的文本文件。
    • fputc/fgetc:逐个字符处理,效率低,适用于特定场景。
  5. 错误处理freadfwrite 等函数的返回值可以用来判断是否成功。ferror 函数可以用来检查文件流是否发生了错误。

    if (fwrite(...) != 1) {
        if (ferror(fp)) {
            perror("A write error occurred");
        }
    }

掌握了这些核心函数,你就可以在 C 语言中进行几乎所有的文件操作了。

-- 展开阅读全文 --
头像
dede列表页如何调用当前栏目名称?
« 上一篇 02-10
dede栏目内容长度如何增加?
下一篇 » 02-10

相关文章

取消
微信二维码
支付宝二维码

目录[+]