c语言readrecord函数如何正确实现?

99ANYc3cd6
预计阅读时长 19 分钟
位置: 首页 C语言 正文

readrecord 不是一个 C 标准库函数,而是程序员根据自己项目需求自定义的函数,它的核心功能是从一个数据源(通常是文件)中读取一条或多条记录。

c语言readrecord函数
(图片来源网络,侵删)

一条“记录”是什么?这取决于你的数据格式。

  • 一行文本(如 username:password:1001
  • 一个结构体,其中包含不同类型的数据(如姓名、年龄、分数)
  • 一个固定长度的数据块

下面我将通过几个常见的例子,从简单到复杂,来展示如何实现 readrecord 函数。


示例 1:从文件中读取一行文本作为一条记录

这是最简单的形式,通常使用 fgets 函数。

场景:假设我们有一个文件 users.txt如下:

c语言readrecord函数
(图片来源网络,侵删)
Alice
Bob
Charlie

我们的目标是读取每一行(一个用户名)作为一条记录。

函数实现

#include <stdio.h>
#include <string.h>
/**
 * @brief 从文件指针中读取一行文本作为一条记录
 * 
 * @param fp     已打开的文件指针
 * @param buffer 用于存储读取内容的字符缓冲区
 * @param size   缓冲区的大小
 * @return int   成功返回读取的字符数(不包括换行符'\n'),失败或到达文件末尾返回 -1。
 */
int readrecord(FILE *fp, char *buffer, int size) {
    if (fp == NULL || buffer == NULL || size <= 0) {
        return -1; // 无效参数
    }
    if (fgets(buffer, size, fp) != NULL) {
        // fgets 会读取换行符,如果存在的话
        // 移除末尾的换行符,让数据更干净
        size_t len = strlen(buffer);
        if (len > 0 && buffer[len - 1] == '\n') {
            buffer[len - 1] = '\0';
        }
        return len > 0 ? (int)len - 1 : 0; // 返回实际字符数,不包括'\n'
    }
    return -1; // 到达文件末尾或发生错误
}
int main() {
    FILE *file = fopen("users.txt", "r");
    if (file == NULL) {
        perror("无法打开文件");
        return 1;
    }
    char record[100]; // 定义一个足够大的缓冲区
    printf("开始读取记录:\n");
    while (1) {
        int chars_read = readrecord(file, record, sizeof(record));
        if (chars_read == -1) {
            break; // 读取结束或出错
        }
        printf("读取到记录: '%s' (长度: %d)\n", record, chars_read);
    }
    fclose(file);
    return 0;
}

代码解析:

  1. 参数:
    • FILE *fp: 文件指针,调用者必须先 fopen 文件并传入。
    • char *buffer: 存储读取数据的内存地址。
    • int size: 缓冲区的大小,防止 fgets 写入越界。
  2. 核心逻辑:
    • 使用 fgetsfp 读取最多 size-1 个字符到 buffer,并在末尾自动添加 '\0'
    • 检查 fgets 的返回值,如果为 NULL,表示文件结束或出错,函数返回 -1
    • 如果成功,则检查并移除 buffer 末尾可能存在的换行符 '\n',使数据更规整。
    • 返回实际读取的字符数(不包括换行符)。

示例 2:从文件中读取格式化数据(结构体)

这是更常见和实用的场景,比如读取二进制文件或文本文件中的结构化数据。

场景:我们有一个 student.dat 文件,里面存储了多个学生的信息(姓名、年龄、分数),我们想把这些信息读入一个结构体数组中。

数据结构定义

#include <stdio.h>
#include <string.h>
// 定义学生结构体
typedef struct {
    char name[50];
    int age;
    float score;
} Student;

函数实现(文本格式)

假设 students.txt 文件内容如下:

Alice 20 95.5
Bob 21 88.0
Charlie 19 92.3
/**
 * @brief 从文本文件中读取一条格式化的学生记录
 * 
 * @param fp     已打开的文件指针
 * @param student 指向学生结构体的指针,用于存储读取的数据
 * @return int   成功返回 1,失败或到达文件末尾返回 0。
 */
int readrecord_text(FILE *fp, Student *student) {
    if (fp == NULL || student == NULL) {
        return 0; // 无效参数
    }
    // fscanf 会根据格式字符串解析数据
    // %49s 防止 name 数组溢出,最多读取49个字符,留1位给'\0'
    if (fscanf(fp, "%49s %d %f", student->name, &student->age, &student->score) == 3) {
        return 1; // 成功读取3个字段
    }
    return 0; // 读取失败(可能格式不对或到文件末尾)
}
int main() {
    FILE *file = fopen("students.txt", "r");
    if (file == NULL) {
        perror("无法打开文件");
        return 1;
    }
    Student s; // 用于存储单条记录的结构体变量
    printf("开始读取学生记录:\n");
    while (readrecord_text(file, &s) == 1) {
        printf("姓名: %s, 年龄: %d, 分数: %.1f\n", s.name, s.age, s.score);
    }
    fclose(file);
    return 0;
}

代码解析:

  • fscanf: 它按照指定的格式(%s %d %f)从文件中读取数据,并分别存入结构体的成员中。
  • 返回值: fscanf 成功匹配并赋值的字段个数,我们期望是3,所以如果返回值是3,就说明读取成功。
  • 安全性: %49s 是一个好习惯,防止输入的姓名过长导致 name 数组溢出。

函数实现(二进制格式)

students.dat 是一个二进制文件,我们可以直接读写整个结构体。

/**
 * @brief 从二进制文件中读取一条学生记录
 * 
 * @param fp     已打开的二进制文件指针(必须以 "rb" 模式打开)
 * @param student 指向学生结构体的指针,用于存储读取的数据
 * @return int   成功返回 1,失败或到达文件末尾返回 0。
 */
int readrecord_binary(FILE *fp, Student *student) {
    if (fp == NULL || student == NULL) {
        return 0;
    }
    // fread 直接从文件中读取 sizeof(Student) 字节的数据
    if (fread(student, sizeof(Student), 1, fp) == 1) {
        return 1;
    }
    return 0;
}
int main() {
    FILE *file = fopen("students.dat", "rb"); // 注意:二进制模式是 "rb"
    if (file == NULL) {
        perror("无法打开二进制文件");
        return 1;
    }
    Student s;
    printf("开始读取二进制学生记录:\n");
    while (readrecord_binary(file, &s) == 1) {
        printf("姓名: %s, 年龄: %d, 分数: %.1f\n", s.name, s.age, s.score);
    }
    fclose(file);
    return 0;
}

代码解析:

  • fread: 它从文件中读取指定大小的数据块。fread(student, sizeof(Student), 1, fp) 的意思是:从 fp 读取 sizeof(Student) 字节的数据,存入 student 指向的内存,重复 1 次。
  • 二进制模式: 打开文件时必须使用 "rb" (read binary) 模式,否则行为可能不符合预期。
  • 注意: 二进制格式对齐和平台(大小端)敏感,在不同操作系统间移植可能会有问题。

总结与最佳实践

  1. 明确 readrecord 的职责:在写函数前,先想清楚一条“记录”是什么,它包含什么数据,数据格式是什么(文本还是二进制)。
  2. 参数设计
    • 文件指针 FILE* 是必须的,因为它代表了数据源。
    • 需要一个输出参数(通常是结构体指针或缓冲区指针)来返回读取的数据。
    • 对于可变长度的数据(如字符串),需要缓冲区大小参数,防止溢出。
  3. 返回值设计
    • 成功/失败:最简单的返回 int0 表示失败,1 或正数表示成功,这是 fscanffread 的常用模式。
    • 读取的字节数:像 fgets 那样,返回实际读取的字符数,更有信息量。
    • 错误码:可以定义枚举或宏(如 #define READ_OK 0, define READ_EOF 1, define READ_ERROR -1)来提供更详细的错误信息。
  4. 错误处理:始终检查文件指针、缓冲区指针是否为 NULL,检查 fopen, fgets, fscanf, fread 等函数的返回值,以判断是否成功或到达文件末尾。
  5. 健壮性:处理换行符、限制字符串读取长度等细节能让你的函数更健壮、更安全。

readrecord 函数是文件 I/O 操作中一个非常有用的抽象,它将读取“一条逻辑记录”的操作封装起来,使得主程序逻辑更清晰。

-- 展开阅读全文 --
头像
用dede.com网站上传文件
« 上一篇 01-02
dede5.7 文档关键词维护
下一篇 » 01-02

相关文章

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

目录[+]