You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:00:53