用C语言从文本文件中读取CSV

用C语言从文本文件中读取CSV,c,C,我正在尝试从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; c

我正在尝试从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);
    }
}
#包括
#包括
#包括
类型定义结构{
整数;
字符*名称;
字符*电子邮件;
}所有者;
业主[100];
int加载(字符*文件名)
{
字符缓冲区[200];
字符标记[50];
业主*业主;
int\u size=0;
FILE*FILE=fopen(文件名,“r”);
while(fgets(缓冲区,200,文件)!=NULL)
{
业主=(业主*)malloc(业主规模);
所有者->编号=atoi(strtok(缓冲区,“,”);
所有者->名称=strtok(空,“”,“”);
所有者->电子邮件=strtok(空,“,”);
所有者[owners_size++]=*所有者;
}
fclose(文件);
返回单位大小;
}
int main()
{
整数选择、所有者大小、索引;
char*owners\u filename=“owners2.txt”;
所有者\大小=加载(所有者\文件名);
若有(业主尺寸)
{
printf(“所有者大小:%d\n\n”,所有者大小);
对于(索引=0;索引<所有者大小;索引++)
printf(“%d,%s%s\n”,所有者[索引]。编号,所有者[索引]。名称,所有者[索引]。电子邮件);
}
}

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

您刚刚将指针存储到本地缓冲区中。当您离开
load()
时,此
缓冲区将消失,不再可访问

您必须为
名称
电子邮件
分配内存,然后才能将其复制到
所有者
结构中

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

[编辑:您需要分配
len+1
字节,以便为终止的
NUL
字符留出空间。-Zack]
strtok()
返回的指针指向它正在解析的缓冲区内的地址,在本例中是局部变量
buffer
。当
load()
返回变量时,它超出范围(即使不是所有
所有者的实例都指向相同的地址)。您需要复制由
strtok()
返回的字符串。如果可用,您可以使用
strdup()
,或者使用
malloc()
strcpy()

无需
malloc()
所有者的新实例
,因为它们的数组已经存在(现有代码存在内存泄漏)

注意:没有防止超出
所有者
数组界限的保护。如果文件中有超过100个
条目,则循环将超出数组的边界。扩展
的终止条件,同时
以防止出现这种情况:

while(owners_size < sizeof(owners) / sizeof(owners[0]) &&
      fgets(buffer, 200, file) != NULL)
{
}
while(所有者\u大小
两个问题:

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

    typedef struct
    {
        int   number;
        char *name;
        char *email;
    } Owner;
    
    您需要为指向的指针提供空间来保存名称

  • 您将继续提供指向缓冲区的指针,缓冲区将重新用于每一行输入:

    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()

    这是一个有点危险的代码(我不会在生产代码中使用它),因为它不会检查
    strtok()
    是否在预期的时候找到令牌(或者
    strdup()
    是否成功)。同样,我也不会在生产代码中使用;如果有POSIX或Microsoft的,我会使用它们,或者一些替代技术,可能使用
    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,“鲍勃”“国王”“国王”“鲍勃·金,巡回程序员”
    
    您只有一行缓冲区。
    load
    中循环的每个循环都会从上一个循环中删除文本。如果这还不够糟糕,那么当
    load
    返回时,缓冲区就会被破坏

    快速解决办法是改变

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

    (如果你没有
    strdup
    ,那就买一台真正的电脑,它的编写非常简单。)


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

    Dargh,我错过了内存泄漏。嗯……我也是。这只是为了强化一句格言:哪里有一个bug,通常也会有另一个潜伏着,不管你认为你已经发现了多少。好的,先生!请注意,您使用了
    len=strlen(tok);所有者->名称=malloc(len)包含错误和堆覆盖。您应该分配
    len+1
    字节<编码>len=strlen(tok)+1;所有者->名称=malloc(len)。这就是为什么您应该尽可能使用库函数;它们是经过调试的,不像特别的代码。@JonathanLeffler现在我明白了,这太离谱了,我要用Olaf来纠正它。@JonathanLeffler,Zack感谢你发现并修复了我令人尴尬的错误。+1特别是指出CSV实际上并不像人们常说的那么简单。
    1,"Bob ""The King"" King","Bob King, Itinerant Programmer <bob@gmail.com>"
    
    owner->name = strtok(NULL, ",");
    owner->email = strtok(NULL, ",");
    
    owner->name = strdup(strtok(NULL, ","));
    owner->email = strdup(strtok(NULL, ","));