3

我正在尝试从 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);
    }
}

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

4

4 回答 4

5

两个问题:

  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()在生产代码中使用;如果可用,我会使用 POSIXstrtok_r()或 Microsoft 的,或者一些替代技术,可能使用and 。如果不可用,您可以使用相同或不同的名称编写自己的名称: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>"
于 2012-10-22T21:20:03.033 回答
3

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

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

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

while(owners_size < sizeof(owners) / sizeof(owners[0]) &&
      fgets(buffer, 200, file) != NULL)
{
}
于 2012-10-22T21:19:03.403 回答
1

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

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

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

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

于 2012-10-22T21:18:21.460 回答
1

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

快速解决方法是改变

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

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

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

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

于 2012-10-22T21:21:51.703 回答