【问题标题】:Reading CSV from text file in C从 C 中的文本文件中读取 CSV
【发布时间】:2012-10-22 21:13:44
【问题描述】:

我正在尝试从 C 中的文本文件中读取 CSV。文本文件格式为

1,Bob,bob@gmail.com
2,Daniel,daniel@gmail.com
3,John,john@gmail.com

当我运行程序时,数字显示正常,但姓名和电子邮件显示为垃圾。这是我的程序...

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

typedef struct {
    int number;
    char* name;
    char* email;
} Owner;

Owner owners[100];

int load(char* filename)
{
    char buffer[200];
    char token[50];
    Owner* owner;
    int owners_size = 0;
    FILE* file = fopen(filename, "r");

    while(fgets(buffer, 200, file) != NULL)
    {
        owner = (Owner*)malloc(sizeof(Owner));
        owner->number = atoi(strtok(buffer, ","));
        owner->name = strtok(NULL, ",");
        owner->email = strtok(NULL, ",");
        owners[owners_size++] = *owner;
    }

    fclose(file);
    return owners_size;
}

int main()
{
    int choise, owners_size, index;
    char* owners_filename = "owners2.txt";

    owners_size = load(owners_filename);

    if(owners_size)
    {
        printf("owners size: %d\n\n", owners_size);

        for(index = 0; index < owners_size; index++)
            printf("%d, %s %s\n", owners[index].number, owners[index].name, owners[index].email);
    }
}

谁能告诉我是什么原因。感谢您的帮助。

【问题讨论】:

    标签: c


    【解决方案1】:

    两个问题:

    1. 您没有为结构中的字符串分配空间:

      typedef struct
      {
          int   number;
          char *name;
          char *email;
      } Owner;
      

      您需要为这些指针提供空间来指向以保存名称。

    2. 您继续提供指向缓冲区的指针,该缓冲区可重复用于每一行输入:

      while(fgets(buffer, 200, file) != NULL)
      {
          owner = (Owner*)malloc(sizeof(Owner));
          owner->number = atoi(strtok(buffer, ","));
          owner->name = strtok(NULL, ",");
          owner->email = strtok(NULL, ",");
          owners[owners_size++] = *owner;
      }
      

      第一行作为一些指针存储到缓冲区中。下一行然后覆盖缓冲区并再次切断行,践踏原始输入。

    考虑使用strdup()

    while (fgets(buffer, 200, file) != NULL)
    {
        owner = (Owner *)malloc(sizeof(Owner));
        owner->number = atoi(strtok(buffer, ","));
        owner->name = strdup(strtok(NULL, ","));
        owner->email = strdup(strtok(NULL, ","));
        owners[owners_size++] = *owner;
    }
    

    这是一个有点危险的代码(我不会在生产代码中使用它),因为它不会检查strtok() 是否在预期的时候找到了一个令牌(或者strdup() 是否成功)。再说一次,我也不会在生产代码中使用strtok();如果可用,我会使用 POSIX strtok_r() 或 Microsoft 的 strtok_s(),或者一些替代技术,可能使用 strspn()strcspn()。如果strdup() 不可用,您可以编写自己的,使用相同或不同的名称:

    char *strdup(const char *str)
    {
        size_t len = strlen(str) + 1;
        char *dup = malloc(len);
        if (dup != 0)
            memmove(dup, str, len);  // Or memcpy() - that is safe in this context
        return(dup);
    }
    

    您可能会注意到您的代码仅适用于简单的 CSV 文件。如果您遇到这样的一行(这是合法的 CSV),您就会遇到问题(值中有引号,并且由于引号字符串中的逗号而导致错误拆分):

    1,"Bob ""The King"" King","Bob King, Itinerant Programmer <bob@gmail.com>"
    

    【讨论】:

    • +1 特别指出 CSV 实际上并不像人们通常认为的那么简单。
    【解决方案2】:

    strtok() 返回的指针指向它正在解析的缓冲区中的地址,在本例中为局部变量 buffer。当load() 返回变量时,它超出了范围(即使不是owners 的所有实例都指向同一个地址)。您需要复制strtok() 返回的字符串。如果可用,您可以使用strdup() 或使用malloc()strcpy()

    不需要malloc() Owner 的新实例,因为它们的数组已经存在(原代码存在内存泄漏)。

    请注意,没有防止超出owners 数组边界的保护措施。如果文件有多个100 条目,则循环将超出数组的范围。扩展while 的终止条件以防止这种情况:

    while(owners_size < sizeof(owners) / sizeof(owners[0]) &&
          fgets(buffer, 200, file) != NULL)
    {
    }
    

    【讨论】:

    • Dargh,我错过了内存泄漏。
    • 嗯……我也是。它只会强化一句格言,即有一个错误,通常也有另一个潜伏着,不管你认为你发现了多少。不错,先生!
    【解决方案3】:

    您只是将指针存储到本地缓冲区中。当您离开 load() 时,此 buffer 已消失且无法再访问。

    您必须先为nameemail 分配内存,然后才能将其复制到Owner 结构中。

    char *tok;
    tok = strtok(NULL, ",");
    len = strlen(tok);
    owner->name = malloc(len + 1);
    strcpy(owner->name, tok);
    ...
    

    [编辑:您需要分配len+1 字节,以便为终止NUL 字符留出空间。 -扎克]

    【讨论】:

    • 请注意,您使用了len = strlen(tok); owner-&gt;name = malloc(len);,其中包含一个错误和一个堆覆盖。您应该分配 len + 1 字节! len = strlen(tok) + 1; owner-&gt;name = malloc(len);。这就是为什么您应该尽可能使用库函数的原因;与临时代码不同,它们是经过调试的。
    • @JonathanLeffler 现在我明白了,这太令人震惊了,我将为奥拉夫纠正它。
    • @JonathanLeffler, Zack 感谢您发现并修复了我令人尴尬的错误。
    【解决方案4】:

    您只有一个行缓冲区。 load 中循环的每个循环都会破坏前一个循环中的文本。如果这还不够糟糕,当load 返回时,缓冲区会被销毁。

    快速解决办法是改变

    owner->name = strtok(NULL, ",");
    owner->email = strtok(NULL, ",");
    

    owner->name = strdup(strtok(NULL, ","));
    owner->email = strdup(strtok(NULL, ","));
    

    (如果你没有strdup买一台真正的电脑写起来很简单。)

    但是,如果我正在查看您的代码,我会为您推荐固定大小的行缓冲区、固定大小的所有者数组、内存泄漏,使用 atoi 而不是 strtol,而是使用 strtokstrsep,并且没有引用处理和解析错误恢复,并指出将每一行作为一个单元分配然后将指针保存到其中会更有效。

    【讨论】:

      猜你喜欢
      • 2013-05-03
      • 1970-01-01
      • 2021-11-19
      • 2019-08-03
      • 2020-02-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多