如何不使用ftell及非标准函数获取文本文件中结构体数量并读取数据?
问题描述
现有标准C语言下的student结构体定义:
typedef struct student{ int grade[3]; char name[10]; } student;
该结构体的多个实例以文本形式存储在aaa.txt文件中,需要读取到结构体数组中操作后写回文件。当前的难点是无法确定文件中结构体的数量,导致读取操作难以完成。已知仅能使用stdio.h、stdlib.h和string.h三个库,且认为ftell仅适用于小型二进制文件。
用户编写了以下代码:
findSize函数
int findSize(){ FILE* fp = fopen("aaa.txt", "r"); int size = 0; fseek(fp, 0, SEEK_SET); while(fp != NULL){ student temp; fscanf(fp, "%s%d%d%d", temp.name, temp.grade + 0, temp.grade + 1, temp.grade + 2); printf("Name: %s\nGrades: %d %d %d\n", temp.name, temp.grade[0], temp.grade[1], temp.grade[2]); size++; } fclose(fp); return size; }
readFile函数
student* readFile(int size){ FILE* fp = fopen("aaa.txt", "r"); fseek(fp, 0, SEEK_SET); printf("Size of file:\n%d", size); student* stArray = allocMem(size); for(int i = 0; i < size; i++){ student temp; fscanf(fp, "%s%d%d%d", temp.name, temp.grade + 0, temp.grade + 1, temp.grade + 2); stArray[i] = temp; } fclose(fp); return stArray; }
主函数计划代码
int size = 0; student* st = allocMem(findSize()); st = readFile(&size); printStudents(st, size);
用户疑问:这种实现方式是否可行?若不可行,有没有正确的实现方法?
回答
原实现的问题
- 循环条件错误:
findSize中while(fp != NULL)永远为真,文件打开成功后指针不会变为NULL,会导致无限循环,直到fscanf读取失败后仍继续计数,最终得到错误的数量。正确的判断逻辑应该是检查fscanf的返回值是否等于4(每次需要读取1个字符串+3个整数,共4个数据项)。 - 内存泄漏:主函数中先调用
allocMem(findSize())分配内存,随后将st指向readFile返回的内存块,之前分配的内存未释放,造成内存泄漏。 - 参数类型不匹配:主函数传递
&size(int*类型)给readFile,但该函数参数是int size,编译会直接报错。 - 未处理异常情况:
fopen可能返回NULL,后续操作会直接崩溃,没有添加错误判断逻辑。
正确实现方案
方案1:两次遍历文件(先计数再读取)
先修正计数逻辑,统计出正确的结构体数量,再分配内存读取数据:
// 正确的学生计数函数 int countStudents() { FILE* fp = fopen("aaa.txt", "r"); if (fp == NULL) { perror("Failed to open file"); return -1; } int count = 0; student temp; // 每次读取4个数据项,成功则计数+1 while (fscanf(fp, "%9s%d%d%d", temp.name, &temp.grade[0], &temp.grade[1], &temp.grade[2]) == 4) { count++; } fclose(fp); return count; } // 读取学生数据到数组 student* readStudents(int count) { if (count <= 0) { return NULL; } FILE* fp = fopen("aaa.txt", "r"); if (fp == NULL) { perror("Failed to open file"); return NULL; } // 分配内存,假设allocMem是malloc的封装,需确保其正确处理分配失败情况 student* students = allocMem(count * sizeof(student)); if (students == NULL) { perror("Failed to allocate memory"); fclose(fp); return NULL; } for (int i = 0; i < count; i++) { fscanf(fp, "%9s%d%d%d", students[i].name, &students[i].grade[0], &students[i].grade[1], &students[i].grade[2]); } fclose(fp); return students; } // 主函数示例 int main() { int count = countStudents(); if (count == -1) { return 1; } student* st = readStudents(count); if (st != NULL) { printStudents(st, count); // 操作完成后释放内存 free(st); } return 0; }
方案2:动态扩容(一次遍历完成)
如果不想两次读取文件,可以采用动态扩容的方式,边读取边调整数组大小:
// 动态读取学生数据,返回数组并输出数量到outCount student* readStudentsDynamic(int* outCount) { FILE* fp = fopen("aaa.txt", "r"); if (fp == NULL) { perror("Failed to open file"); *outCount = -1; return NULL; } int capacity = 4; // 初始容量 int count = 0; student* students = allocMem(capacity * sizeof(student)); if (students == NULL) { perror("Failed to allocate memory"); fclose(fp); *outCount = -1; return NULL; } student temp; while (fscanf(fp, "%9s%d%d%d", temp.name, &temp.grade[0], &temp.grade[1], &temp.grade[2]) == 4) { // 容量不足时扩容,通常采用2倍扩容策略 if (count >= capacity) { capacity *= 2; student* tempPtr = realloc(students, capacity * sizeof(student)); if (tempPtr == NULL) { perror("Failed to reallocate memory"); free(students); fclose(fp); *outCount = -1; return NULL; } students = tempPtr; } students[count++] = temp; } fclose(fp); // 可选:将内存收缩到实际需要的大小,节省空间 student* finalPtr = realloc(students, count * sizeof(student)); if (finalPtr != NULL) { students = finalPtr; } *outCount = count; return students; } // 主函数示例 int main() { int count; student* st = readStudentsDynamic(&count); if (st != NULL && count > 0) { printStudents(st, count); free(st); } return 0; }
关键注意事项
- 读取字符串时使用
%9s限制长度,避免name数组(大小为10)出现缓冲区溢出。 - 确保
allocMem是malloc/calloc的正确封装,必须处理内存分配失败的情况。 - 所有文件操作和内存操作都要检查返回值,避免程序崩溃。
内容的提问来源于stack exchange,提问作者Kostya Chistyakov
相关产品推荐
相关产品推荐

