【问题标题】:Read a CSV file into a dynamic array of structs C将 CSV 文件读入结构的动态数组 C
【发布时间】:2021-08-16 10:53:15
【问题描述】:

我是 C 的新手。我正在尝试读取 .CSV 文件,然后解析每一行,然后将数据存储在指向结构的指针的动态数组中。不幸的是,我在实现中的某个地方出错了,这导致了无限循环。

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

typedef struct dataSet {
    char ID;
    char postcode;
    int population;
    char contact;
    double x;
    double y;
}data;

int main(int argc, char* argv[]) {

    char line[100] = "";
    int count = 0;
    int each = 0;
    data *allData = NULL;
    data *temp = NULL;

    FILE *file = fopen("dataset.csv", "r");
    if (file == NULL)
    {
        printf("Error! File null");
        return 1;
    }

    while (fgets(line, sizeof line, file))
    {
        if(NULL == (temp = realloc(allData, sizeof(*allData) * (count + 1))))
        {
            fprintf(stderr, "realloc problem\n");
            fclose(file);
            free(allData);
            return 0;
        }

        allData = temp;
        if (6 == scanf(line, "%s, %s, %d, %s, %lf, %lf",
                        &allData[count].ID,
                        &allData[count].postcode,
                        &allData[count].population,
                        &allData[count].contact,
                        &allData[count].x,
                        &allData[count].y)) {
            count++;
        }
        else {
            printf("Problem with data\n");
        }
    }

    fclose(file);

    for (each = 0; each < count; each++)
    {
        printf("%s, %s, %d, %s, %lf, %lf\n",
        &allData[count].ID,
        &allData[count].postcode,
        &allData[count].population,
        &allData[count].contact,
        &allData[count].x,
        &allData[count].y);
    }

    free(allData);

    return 0;
}

任何帮助或提示将不胜感激。

【问题讨论】:

  • %s 格式将读取一个 string,并将 null-terminated 字符串写入您的内存指针指向。因此,即使是一个字母的字符串也需要 两个 字符的空间来包含空终止符。单个 char 变量只能容纳单个字符。
  • 另外,在读取循环之后,变量count 将是您分配的数组中的元素数。但请记住,当用作索引时,此值将超出范围。考虑一下打印值的循环(您需要再次考虑 stringscharacters)。
  • sscanf() 不能用于重要的不受信任的输入。相反,您可以创建一个有限状态机。此外,您使用的 realloc() 方案将导致二次行为。
  • 您是不是要使用sscanf 而不是scanf
  • 正如@alex01011 所说。首先扫描整个文件以确定大小,然后执行一个 malloc。

标签: arrays c csv struct


【解决方案1】:

[s]scanf() 是一个讨厌的函数。一旦失败,您就没有足够的控制权。问题是:条件太多:输入可能不正确,或者目标不够大。即使使用fgets() 读取完整的行,然后解析它们,也只会让您跳过完整的行;另外:行缓冲区大多是固定大小的,fgets() 可以读取不完整的行。保持完全控制的一种方法是基于字符的读取。这可能意味着有限状态机。

更简单的阅读器(使用零状态机器)可能是:


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

struct omg {
        char o;
        int m;
        char g[11];
        };

struct wtf {
        unsigned size;
        unsigned used;
        struct omg *array;
        };

#define INITIAL_SIZE 7

struct wtf read_stuff(char *name)
{
FILE *fp;
unsigned icol,irec,len;
char buff[123];
struct wtf this = {0,0,NULL};

fp = fopen(name, "rb" );
if (!fp) return this;

for (icol=irec=len=0;   ; ) {
        int ch;
        if (this.used >= this.size) {
                size_t newsize;
                struct omg *tmp;

                newsize = this.size? this.size*2: INITIAL_SIZE;
                fprintf(stderr, "Realloc(%zu)\n", newsize);
                tmp = realloc(this.array, sizeof *this.array * newsize);
                this.array = tmp;
                this.size = newsize;
                }

        ch = getc(fp);
        switch(ch) {
        case '\r' : continue;

                /* End of field or record: terminate buffer */
#if 0
        case ',' :
#else
        case '\t' :
#endif

        case '\n' :
                buff[len] = 0;
                break;
        case EOF :
                goto done;

                /* Normal character: assign to buffer
                ** You may want to report too long fields here
                 */
        default:
                if (len >= sizeof buff -2) continue;
                buff[len++] = ch;
                continue;
                }

                /* When we arrive here, we have a new field. Let's process it ...*/
        switch (icol) {
        case 0: /* Assign first field here from buff[], (dont forget to check len!) */
                this.array[this.used].o = buff[0];
                break;

        case 1: /* Assign second field from buff[], this may need some additional checks
                ** You may want to avoid sscanf() here ...
                */
                sscanf(buff, "%d", &this.array[this.used].m );
                break;

        case 2: /* Assign third field from buff[] */
                if (len >= sizeof this.array[this.used].g)
                len = sizeof this.array[this.used].g -1;
                memcpy (this.array[this.used].g, buff, len);
                this.array[this.used].g[len] = 0;
                break;

        default: /* Ignore excess fields
                 ** You may want to report hem.
                 */
                break;
                }

                /* Do some bookkeeping */
        len = 0;
        if(ch == '\n') {
                /* You may want to check if icol==2, here */
                icol=0; irec++; this.used++;
                }
        else icol++;
        }
done:
fclose(fp);
        /* You could do a final realloc() here */

return this;
}

int main(int argc, char **argv)
{
struct wtf result;
unsigned idx;

result = read_stuff(argv[1] );
fprintf(stderr, "Result=%u/%u\n", result.used,result.size);

for (idx=0; idx < result.used; idx++) {
        printf("%c %d %s\n"
                , result.array[idx].o
                , result.array[idx].m
                , result.array[idx].g);
        if (idx >= 10) break;
        }

return 0;
}

【讨论】:

  • 在我的回答下评论负面建议后发布这一秒非常丰富。老实说,您的评论或此答案是否真的有助于回答问题?
  • 我已经编辑了一个小时,不想破坏我的努力。此外:还有更多剥猫皮的方法。 [我的方法更侧重于保持控制]
【解决方案2】:

你要小费...

1 - 如果您的计划是使用动态内存,那么您的结构是错误的。 char 成员应该是 指向字符的指针,(char * 不是 char)如下所示。但是为了降低复杂性,请使用 char 数组而不是强制为结构成员动态分配:即 not 使用这个:

typedef struct dataSet {
    char *ID;
    char *postcode;
    int population;
    char *contact;
    double x;
    double y;
}data;  

宁可使用这个:

typedef struct dataSet {
    char ID[80];
    char postcode[11];
    int population;
    char contact[80];
    double x;
    double y;
}data;  

如果长度不正确,则将它们变大,但这会减少对calloc()free() 的调用。

2 - 建议步骤:

  • 计算文件中的行数。 (例如here)。这实际上将打开文件、计算行数并关闭文件。
  • 使用该计数为data 的实例数分配内存(即data *records = malloc(sizeof(*records)*countOfLines);
  • 再次打开文件。如果file != NULL,那么……
  • 开始在循环中逐行读取文件,比如你有的fgets(...)循环。
  • 在此循环中,建议将scanf() 替换为对strtok() 的一系列调用,逐个进行适当的转换。它多了几行代码,但从长远来看更容易查看您可能遇到的解析问题。

以下伪代码说明...

data *record = malloc(CountOfLines*sizeof(*record));
if(record)
{
    int i = 0;
    while(fgets(line, sizeof line, file))
    {
        tok = strtok(line, ",");
        if(tok)
        { //convert string  
            strncpy(record[i].ID, tok, sizeof(record[i].ID) - 1);
            tok = strtok(NULL, ",");
            if(tok)
            {//convert string
                strncpy(record[i].postcode, tok, sizeof(record[i].postcode) - 1);
                tok = strtok(NULL, ",");
                if(tok)
                {//convert int
                    record[i].population = atoi(tok);
                    //and so on ...

【讨论】:

  • strtok() 是一个糟糕的建议。您将无法读取空白字段。
  • @wildplasser - 在调用strtok 之前,可以检查行的最低标准(例如空字符串)。但只要我们在分享意见,我的意见是scanf()应该被驱逐,这是一个更糟糕的选择。
猜你喜欢
  • 2013-12-16
  • 2013-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-11
  • 2019-01-31
  • 1970-01-01
相关资源
最近更新 更多