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

C语言读取CSV到结构体数组仅保留最后一行数据的问题

问题分析与解决方案

你遇到的核心问题是所有Student结构体的nume和prenume都指向了同一个内存地址——也就是main里定义的myLine数组地址。因为strtok返回的是传入字符串内部的指针,而myLine是固定大小的数组,每次循环fgets都会覆盖它的内容。循环内部打印时,myLine里还是当前行的数据,所以看起来正常;但循环结束后,myLine里只剩最后一行的内容,所有结构体的字符串指针都指向这里,自然就只显示最后一行的数据了。

除此之外,你的代码还有几个其他问题,我一起帮你梳理并修正:

1. 字符串指针未分配独立内存

Student结构体里的nume和prenume是char*类型,但你直接把strtok返回的指针赋值给它们,没有为每个字符串分配独立的内存空间。当myLine被覆盖后,这些指针指向的内容就变成了最新的行数据。

解决方法:用strdup(或者malloc+strcpy)为每个字符串复制一份独立的内存,比如:

myStudents[index].nume = strdup(myWord);

注意:strdup需要包含string.h,而且使用后要记得在程序结束时释放内存,避免内存泄漏。

2. fileNrLines函数的逻辑错误

你的fileNrLines一开始就执行了lines++,这会导致统计的行数比实际多1(比如空文件会返回1)。正确的统计方式应该是先初始化lines=0,然后在循环里遇到'\n'时才递增,最后还要考虑文件最后一行没有换行符的情况。

修正后的fileNrLines:

int fileNrLines() {
    FILE *fp = fopen("students.csv", "r");
    if (!fp) { // 要检查文件是否打开成功
        perror("Failed to open file");
        return 0;
    }
    char ch;
    int lines = 0;
    int prev_char = '\n'; // 处理最后一行没有换行符的情况
    while ((ch = fgetc(fp)) != EOF) {
        if (ch == '\n') {
            lines++;
        }
        prev_char = ch;
    }
    // 如果最后一行没有换行符,额外加1
    if (prev_char != '\n' && lines > 0) {
        lines++;
    } else if (prev_char != '\n') { // 文件只有一行且无换行
        lines = 1;
    }
    fclose(fp);
    return lines;
}

3. printStudents的参数错误

你调用printStudents(myStudents, &lines);,但函数定义是void printStudents(Student *myStudents, int size),第二个参数应该是int类型,不是指针。正确调用是printStudents(myStudents, lines);。

4. strtok的嵌套循环逻辑冗余

每行CSV固定有4个字段,不需要嵌套while (myWord!=NULL)循环——这个循环会重复处理字段,导致同一行的字段被多次赋值(甚至可能越界)。直接按顺序取4个strtok的结果即可。

5. 数值转换错误

你用atoll转换浮点数,atoll是用来转长整型的,应该用atof来转换浮点数。


修正后的完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Student {
    char* nume;
    char* prenume;
    int cod;
    float nota;
} Student;

int fileNrLines() {
    FILE *fp = fopen("students.csv", "r");
    if (!fp) {
        perror("Failed to open file");
        return 0;
    }
    char ch;
    int lines = 0;
    int prev_char = '\n';
    while ((ch = fgetc(fp)) != EOF) {
        if (ch == '\n') {
            lines++;
        }
        prev_char = ch;
    }
    if (prev_char != '\n' && lines > 0) {
        lines++;
    } else if (prev_char != '\n') {
        lines = 1;
    }
    fclose(fp);
    return lines;
}

void printStudents(Student *myStudents, int size) {
    printf("| %20s | %20s | %3s | %5s |\n", "Nume", "Prenume", "Cod", "Nota");
    printf("-------------------------------------------------------------------------\n");
    for (int i = 0; i < size; ++i) {
        printf("| %20s | %20s | %3d | %5.2f |\n", 
               myStudents[i].nume, 
               myStudents[i].prenume, 
               myStudents[i].cod, 
               myStudents[i].nota);
    }
}

// 释放Student数组的内存
void freeStudents(Student *myStudents, int size) {
    for (int i = 0; i < size; ++i) {
        free(myStudents[i].nume);
        free(myStudents[i].prenume);
    }
    free(myStudents);
}

int main() { // main的返回类型应该是int,不是void
    int lines = fileNrLines();
    if (lines == 0) {
        printf("No data in file or file error.\n");
        return 1;
    }
    Student *myStudents = malloc(lines * sizeof(Student));
    if (!myStudents) {
        perror("Failed to allocate memory");
        return 1;
    }

    FILE *fp = fopen("students.csv", "r");
    if (!fp) {
        perror("Failed to open file");
        freeStudents(myStudents, lines);
        return 1;
    }

    char myLine[50];
    int index = 0;
    while (fgets(myLine, sizeof(myLine), fp) != NULL && index < lines) {
        // 去掉换行符(如果存在)
        size_t len = strlen(myLine);
        if (len > 0 && myLine[len-1] == '\n') {
            myLine[len-1] = '\0';
        }

        char* myWord = strtok(myLine, ",");
        if (myWord == NULL) break;
        myStudents[index].nume = strdup(myWord);

        myWord = strtok(NULL, ",");
        if (myWord == NULL) break;
        myStudents[index].prenume = strdup(myWord);

        myWord = strtok(NULL, ",");
        if (myWord == NULL) break;
        myStudents[index].cod = atoi(myWord);

        myWord = strtok(NULL, ",");
        if (myWord == NULL) break;
        myStudents[index].nota = atof(myWord);

        index++;
    }

    fclose(fp);
    printStudents(myStudents, index);
    freeStudents(myStudents, index);

    return 0;
}

关键修正点总结

  • 为每个字符串字段分配独立内存,避免共用myLine的地址
  • 修复行数统计的逻辑错误,处理无换行符的最后一行
  • 修正printStudents的参数传递错误
  • 简化strtok的使用逻辑,避免冗余循环
  • 用atof替代atoll进行浮点数转换
  • 添加内存释放逻辑,避免内存泄漏
  • 规范main函数的返回类型,添加文件和内存分配的错误检查

内容的提问来源于stack exchange,提问作者Mareș Ștefan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:22:00