C语言如何在未知行数时读取CSV文件至末尾?
解决CSV文件动态读取(无需预先知晓行数)的问题
嘿,我来帮你搞定这个CSV读取的难题!你之前遇到的前8行全0的异常,大概率是文件指针位置不对或者初始化/解析逻辑出了问题,咱们一步步来解决:
先排查你之前fgets方法的异常原因
你提到用char buffer[1000]; while(fgets(buffer, 1000, fp)) {}出现前8行全0,常见的坑有这几个:
- 文件指针没在开头:如果打开文件后你先做了其他操作(比如用
fseek跳到某个位置,或者之前读取过一部分内容没重置),fgets会从当前指针位置开始读,前面的行自然读不到,数组初始值是0就会显示全0。解决方法:打开文件后调用rewind(fp)确保指针回到文件开头。 - 没跳过CSV表头:如果你的CSV第一行是表头(比如字段名),直接把表头解析成数据的话,可能会出现解析失败(比如把字符串转成数字得到0),看起来像前几行全0。
- 数组未初始化:如果你用来存储数据的数组是全局变量或者未手动初始化,默认值是0,要是读取逻辑没覆盖到前8行,就会显示全0。
正确的动态读取方案(无需预先知道行数)
不用在CSV里存行数,咱们用动态数组来存储数据,随着读取过程自动扩容,完美适配任意行数的CSV。下面是完整的实现示例(结合你的readBeer场景,假设你要读取啤酒数据):
1. 定义数据结构体(根据你的CSV字段调整)
typedef struct { char beerName[64]; float alcoholContent; int productionYear; // 其他字段根据你的CSV实际内容添加 } Beer;
2. 动态读取并解析CSV的完整代码
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char beerName[64]; float alcoholContent; int productionYear; } Beer; // 解析单一行CSV到Beer结构体 int parseBeerLine(const char *line, Beer *beer) { if (!line || !beer) return 0; // 复制一行内容到临时缓冲区,避免修改原字符串 char tempBuffer[1000]; strncpy(tempBuffer, line, sizeof(tempBuffer)-1); tempBuffer[sizeof(tempBuffer)-1] = '\0'; // 分割CSV字段(假设格式是:啤酒名称,酒精度,生产年份) char *token = strtok(tempBuffer, ","); if (!token) return 0; strncpy(beer->beerName, token, sizeof(beer->beerName)-1); beer->beerName[sizeof(beer->beerName)-1] = '\0'; token = strtok(NULL, ","); if (!token) return 0; beer->alcoholContent = atof(token); token = strtok(NULL, ","); if (!token) return 0; // 去掉换行符(fgets会把换行符读进来) token[strcspn(token, "\n\r")] = '\0'; beer->productionYear = atoi(token); return 1; } int main() { FILE *fp = fopen("beers.csv", "r"); if (!fp) { perror("无法打开CSV文件"); return 1; } // 初始化动态数组:初始容量设为4,后续自动扩容 Beer *beerList = NULL; size_t currentCapacity = 4; size_t totalRows = 0; // 跳过CSV表头(如果你的CSV没有表头,就删掉这段) char lineBuffer[1000]; if (fgets(lineBuffer, sizeof(lineBuffer), fp) == NULL) { fclose(fp); return 1; } // 循环读取到文件末尾 while (fgets(lineBuffer, sizeof(lineBuffer), fp)) { // 检查是否需要扩容:当前行数达到容量时,把容量翻倍 if (totalRows >= currentCapacity) { currentCapacity *= 2; Beer *tempList = realloc(beerList, currentCapacity * sizeof(Beer)); if (!tempList) { perror("内存扩容失败"); free(beerList); fclose(fp); return 1; } beerList = tempList; } // 解析当前行到Beer结构体 if (parseBeerLine(lineBuffer, &beerList[totalRows])) { totalRows++; } else { printf("解析第%zu行失败,跳过该行\n", totalRows+1); } } // 读取完成,输出结果验证 printf("成功读取%zu行啤酒数据:\n", totalRows); for (size_t i = 0; i < totalRows; i++) { printf("第%zu行:名称=%s,酒精度=%.2f%%,生产年份=%d\n", i+1, beerList[i].beerName, beerList[i].alcoholContent, beerList[i].productionYear); } // 清理资源 free(beerList); fclose(fp); return 0; }
关键要点说明
- 动态扩容:用
realloc在需要的时候扩展数组容量,避免预先分配过大的内存浪费,也不会因为行数不够导致溢出。 - 文件指针重置:打开文件后如果有不确定的操作,调用
rewind(fp)确保从文件开头读取。 - 解析容错:添加解析失败的判断,避免一行出错导致整个程序崩溃。
- 避免硬编码行数:完全不需要预先知道行数,程序会自动统计实际读取的有效行数。
内容的提问来源于stack exchange,提问作者David Hermansson
相关产品推荐
相关产品推荐

