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

(图片来源网络,侵删)
一条“记录”是什么?这取决于你的数据格式。
- 一行文本(如
username:password:1001) - 一个结构体,其中包含不同类型的数据(如姓名、年龄、分数)
- 一个固定长度的数据块
下面我将通过几个常见的例子,从简单到复杂,来展示如何实现 readrecord 函数。
示例 1:从文件中读取一行文本作为一条记录
这是最简单的形式,通常使用 fgets 函数。
场景:假设我们有一个文件 users.txt如下:

(图片来源网络,侵删)
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;
}
代码解析:
- 参数:
FILE *fp: 文件指针,调用者必须先fopen文件并传入。char *buffer: 存储读取数据的内存地址。int size: 缓冲区的大小,防止fgets写入越界。
- 核心逻辑:
- 使用
fgets从fp读取最多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) 模式,否则行为可能不符合预期。 - 注意: 二进制格式对齐和平台(大小端)敏感,在不同操作系统间移植可能会有问题。
总结与最佳实践
- 明确
readrecord的职责:在写函数前,先想清楚一条“记录”是什么,它包含什么数据,数据格式是什么(文本还是二进制)。 - 参数设计:
- 文件指针
FILE*是必须的,因为它代表了数据源。 - 需要一个输出参数(通常是结构体指针或缓冲区指针)来返回读取的数据。
- 对于可变长度的数据(如字符串),需要缓冲区大小参数,防止溢出。
- 文件指针
- 返回值设计:
- 成功/失败:最简单的返回
int,0表示失败,1或正数表示成功,这是fscanf和fread的常用模式。 - 读取的字节数:像
fgets那样,返回实际读取的字符数,更有信息量。 - 错误码:可以定义枚举或宏(如
#define READ_OK 0,define READ_EOF 1,define READ_ERROR -1)来提供更详细的错误信息。
- 成功/失败:最简单的返回
- 错误处理:始终检查文件指针、缓冲区指针是否为
NULL,检查fopen,fgets,fscanf,fread等函数的返回值,以判断是否成功或到达文件末尾。 - 健壮性:处理换行符、限制字符串读取长度等细节能让你的函数更健壮、更安全。
readrecord 函数是文件 I/O 操作中一个非常有用的抽象,它将读取“一条逻辑记录”的操作封装起来,使得主程序逻辑更清晰。
