读取csv并在C中返回2d数组的函数

问题描述 投票:0回答:1

我刚开始使用C,我一直试图弄清楚这一天,这让我发疯。我正在尝试创建一个函数来读取像这样的CSV文件:

10190935A;Sonia;Arroyo;Quintana;M;70
99830067Q;Josefa;Cuenca;Orta;M;42
28122337F;Nuria;Garriga;Dura;M;43
03265079E;Manuel;Orts;Robles;H;45

并创建一个2D数组,然后将其返回以供以后在其他函数中使用。这是功能:

void cargarPacientes ()
{

    FILE *file = fopen (".\pacientes.csv", "r");
    char buffer[256 * 500];
    char *arrayOfLines[500];
    char *line = buffer;
    size_t buf_siz = sizeof (buffer);
    int i = 0, n;

    while (fgets (line, buf_siz, file)) {
        char *p = strchr (line, '\n');
        if (p) {
            *p = '\0';
        } else {
            p = strchr (line, '\0');
        }
        arrayOfLines[i++] = line;
        buf_siz -= p - line + 1;
        if (p + 1 == buffer + sizeof (buffer)) {
            break;
        }
        line = p + 1;
    }
    fclose (file);
    n = i;
    int y = 0;
    char *pacientes[20][6];
    for (i = 0; i < n; ++i) {
        char *token;
        char *paciente[6];
        int x = 0;
        token = strtok (arrayOfLines[i], ";");
        while (token != NULL) {
            paciente[x] = token;
            pacientes[y][x] = token;
            token = strtok (NULL, ";");
            x++;
        }
        y++;
    }
    // return pacientes;
}

我也尝试过使用结构,但是我真的不知道它们是如何工作的。这是结构:

struct Paciente {
    char dni[9];
    char nombre[20];
    char primerApellido[20];
    char segundoApellido[20];
    char sexo[1];
    int edad;
};

无论如何,都可以从该函数返回数组,或者还有其他方法可以更轻松地完成操作?我也尝试过this,但遇到问题,甚至无法编译。

    void cargarPacientes(size_t N, size_t M, char *pacientes[N][M]
    void main(){
        char *pacientes[20][6];
        cargarPacientes(20, 6, pacientes);
    }

这些是编译器错误(对不起,它们是西班牙语):

C:\Users\Nozomu\CLionProjects\mayo\main.c(26): error C2466: no se puede asignar una matriz de tama¤o constante 0
C:\Users\Nozomu\CLionProjects\mayo\main.c(26): error C2087: 'pacientes': falta el sub¡ndice
C:\Users\Nozomu\CLionProjects\mayo\main.c(88): warning C4048: sub¡ndices de matriz distintos: 'char *(*)[1]' y 'char *[20][6]'
c arrays csv return structure
1个回答
0
投票

[如果我了解您要读取文件并将每一行分隔为struct Paciente,则最简单的方法是简单地分配一个包含一些预期数量struct Paciente的内存块,并在每行中填充从文件中读取的数据跟踪填充的结构数。当填充的结构数量等于您分配的数量时,您只需realloc增加可用结构的数量并继续前进...

由于您的struct Paciente包含完全定义的成员,因此不需要单独进行任何进一步分配,因此使此过程变得容易。

基本方法很简单。您将在cargarPaciente()中分配一个内存块,以保存从文件读取的每个结构。您将以指针作为参数,并使用已填充的结构数更新该内存位置的值。您返回一个指向分配的内存块的指针,该内存块包含struct元素,使它们可在调用程序中重新使用,并且您通过作为参数传递的指针填充了可用的struct数量。

您通常还希望将打开的FILE*指针作为参数传递给函数,以从中读取数据。 (如果您无法成功地在调用程序中重新打开文件,则没有理由首先进行函数调用以填充您的结构)。稍微更改函数调用以适应打开的FILE*指针和指向填充的结构数的指针,可以执行以下操作:

struct Paciente *cargarPacientes (FILE *fp, size_t *n)

(或为方便起见在结构中创建typedef后,可以执行以下操作]

Paciente *cargarPacientes (FILE *fp, size_t *n)

[读取文件的设置,在main()中,您需要声明一个指向struct的指针,一个用于保存读取的struct数量的变量,以及一个FILE*指向文件流的指针,例如

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

    Paciente *pt = NULL;
    size_t n = 0;
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    ...
    pt = cargarPacientes (fp, &n);  /* call function assign allocated return */

除了fopen和返回cargarPacientes()上的验证以外,这是在main()中所需的全部。

该工作将在您的cargarPacientes()功能中完成。首先,只需声明一个足以容纳每一行的缓冲区,然后声明变量以跟踪struct allocated的数量,然后声明一个指向保存您的struct Paciente集合的内存块的指针。 (MAXC定义为1024的常数,NP定义为2,以便最初为2个struct Paciente分配存储空间)

Paciente *cargarPacientes (FILE *fp, size_t *n)
{
    char buf[MAXC];     /* buffer to read each line */
    size_t npt = NP;    /* no. Paciente struct allocated */
    Paciente *pt = malloc (npt * sizeof *pt);   /* allocate initial NP struct */

与任何分配一样,在使用已分配的块之前,请始终验证分配是否成功,例如

    if (!pt) {          /* validate every allocation */
        perror ("malloc-pt");
        return NULL;
    }

(<< [note:错误,您的函数返回NULL而不是分配的内存块地址来指示故障)

现在只需读取每一行并将

分号分隔的值解析为一个临时结构。这样,您可以在将结构分配给您分配的内存块中已分配的结构之一之前,验证是否能够将值解析为结构的各个成员,例如:

while (fgets (buf, MAXC, fp)) { /* read each line into buf */ Paciente tmp = { .dni = "" }; /* temp struct to hold values */ /* parse line into separate member values using sscanf */ if (sscanf (buf, FMT, tmp.dni, tmp.nombre, tmp.primerApellido, tmp.segundoApellido, &tmp.sexo, &tmp.edad) == 6) {

note:

FMT在上面被定义为字符串文字,并且还注意到ndi的大小已从9个字符增加到10个字符,因此可以将其视为字符串值,而sexo已声明为单个char,而不是char [1]的数组,例如#define FMT "%9[^;];%19[^;];%19[^;];%19[^;];%c;%d"
如果成功将数据行解析为临时结构,则接下来检查是否已填充的结构数量等于分配的数量,如果是,则检查可用内存量realloc。 (您可以添加少至1个额外的struct [in效率],也可以按某种因素扩展分配的内存量-在这里,我们只需将从2开始的分配的struct数量加倍)]]

/* check if used == allocated to check if realloc needed */ if (*n == npt) { /* always realloc using temporary pointer */ void *ptmp = realloc (pt, 2 * npt * sizeof *pt); if (!ptmp) { /* validate every realloc */ perror ("realloc-pt"); break; } pt = ptmp; /* assign newly sized block to pt */ npt *= 2; /* update no. of struct allocated */ }

[note:

,您必须使用临时指针来realloc,因为如果realloc失败,它会返回NULL,如果您将其分配给原始指针,则会由于内存泄漏
现在无法再释放的原始内存块的地址)剩下的就是将您的临时结构分配给分配的内存块并更新已填充的数字,例如

pt[(*n)++] = tmp; /* assign struct to next struct */ } }

就这样,将指针返回到您分配的块,就可以完成:

return pt; /* return pointer to allocated block of mem containing pt */ }

为了避免在代码中撒满

Magic-Numbers

,并避免Hardcoding Filenames,使用全局2, 10, 20, 1024enum定义了一组常量。您可以使用每个#define语句来完成同一件事,全局enum方便在一行中定义多个整数常量。
enum { NP = 2, DNI = 10, NAME = 20, MAXC = 1024 }; #define FMT "%9[^;];%19[^;];%19[^;];%19[^;];%c;%d"
现在您在结构定义中不再有单独的数字,并且更改常量,并且如果需要更改任何结构成员的大小,只需更改FMT字符串(您不能在其中使用常量或变量sscanf格式字符串,因此始终始终需要单独的数字。

typedef struct Paciente { char dni[DNI]; char nombre[NAME]; char primerApellido[NAME]; char segundoApellido[NAME]; char sexo; int edad; } Paciente;

为了避免对文件名进行硬编码,我们将要读取的文件名作为程序的第一个参数(或者,如果未提供参数,则从stdin读取)。这样可以避免每次输入文件名更改时都必须重新编译程序。

完全可以将其放入:

#include <stdio.h> #include <stdlib.h> enum { NP = 2, DNI = 10, NAME = 20, MAXC = 1024 }; #define FMT "%9[^;];%19[^;];%19[^;];%19[^;];%c;%d" typedef struct Paciente { char dni[DNI]; char nombre[NAME]; char primerApellido[NAME]; char segundoApellido[NAME]; char sexo; int edad; } Paciente; Paciente *cargarPacientes (FILE *fp, size_t *n) { char buf[MAXC]; /* buffer to read each line */ size_t npt = NP; /* no. Paciente struct allocated */ Paciente *pt = malloc (npt * sizeof *pt); /* allocate initial NP struct */ if (!pt) { /* validate every allocation */ perror ("malloc-pt"); return NULL; } while (fgets (buf, MAXC, fp)) { /* read each line into buf */ Paciente tmp = { .dni = "" }; /* temp struct to hold values */ /* parse line into separate member values using sscanf */ if (sscanf (buf, FMT, tmp.dni, tmp.nombre, tmp.primerApellido, tmp.segundoApellido, &tmp.sexo, &tmp.edad) == 6) { /* check if used == allocated to check if realloc needed */ if (*n == npt) { /* always realloc using temporary pointer */ void *ptmp = realloc (pt, 2 * npt * sizeof *pt); if (!ptmp) { /* validate every realloc */ perror ("realloc-pt"); break; } pt = ptmp; /* assign newly sized block to pt */ npt *= 2; /* update no. of struct allocated */ } pt[(*n)++] = tmp; /* assign struct to next struct */ } } return pt; /* return pointer to allocated block of mem containing pt */ } int main (int argc, char **argv) { Paciente *pt = NULL; size_t n = 0; /* use filename provided as 1st argument (stdin by default) */ FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin; if (!fp) { /* validate file open for reading */ perror ("file open failed"); return 1; } pt = cargarPacientes (fp, &n); /* call function assign allocated return */ if (!pt) { /* validate the return was no NULL */ fputs ("cargarPacientes-empty\n", stderr); return 1; } if (fp != stdin) /* close file if not stdin */ fclose (fp); for (size_t i = 0; i < n; i++) { /* output all struct saved in pt */ printf ("%-9s %-10s %-10s %-10s %c %d\n", pt[i].dni, pt[i].nombre, pt[i].primerApellido, pt[i].segundoApellido, pt[i].sexo, pt[i].edad); } free (pt); /* don't forget to free the memory you have allocated */ }

示例使用/输出

将示例数据保存在文件dat/patiente.csv中,程序将产生以下输出:

$ ./bin/readpatiente dat/patiente.csv 10190935A Sonia Arroyo Quintana M 70 99830067Q Josefa Cuenca Orta M 42 28122337F Nuria Garriga Dura M 43 03265079E Manuel Orts Robles H 45

内存使用/错误检查

在您编写的任何可动态分配内存的代码中,对于任何已分配的内存块,您都有2个

职责

:(1)始终保留指向起始地址的指针,因此,( 2)当不再需要它时,可以将其freed
务必使用内存错误检查程序,以确保您不会尝试访问内存或在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后,确认您释放了已分配的所有内存。

对于Linux valgrind是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

$ valgrind ./bin/readpatiente dat/patiente.csv ==1099== Memcheck, a memory error detector ==1099== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==1099== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==1099== Command: ./bin/readpatiente dat/patiente.csv ==1099== 10190935A Sonia Arroyo Quintana M 70 99830067Q Josefa Cuenca Orta M 42 28122337F Nuria Garriga Dura M 43 03265079E Manuel Orts Robles H 45 ==1099== ==1099== HEAP SUMMARY: ==1099== in use at exit: 0 bytes in 0 blocks ==1099== total heap usage: 5 allocs, 5 frees, 6,128 bytes allocated ==1099== ==1099== All heap blocks were freed -- no leaks are possible ==1099== ==1099== For counts of detected and suppressed errors, rerun with: -v ==1099== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认已释放已分配的所有内存,并且没有内存错误。

这比尝试对固定的2D数组进行硬编码以尝试解析文件中的值要简单得多。仔细检查一下,如果还有其他问题,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.