如果您想解决在将每行中具有 4 个字段的 3 行转换为具有 3-每行字段。因此,当您的数据文件包含:
输入文件示例
$ cat dat/col2csv3x4.txt
JACK Maria Stephan Nora
20 34 45 28
London NewYork Toronto Berlin
您想读取三行中的每一行,然后将列转换为行以供.csv 输出。这意味着您最终将得到 4 行 3-csv 字段,例如
预期程序输出
$ ./bin/transpose2csv < dat/col2csv3x4.txt
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
做起来没有什么难的,但是在处理对象的内存存储和分配/重新分配来处理从3行4块数据到4行3块数据的转换需要一丝不苟数据。
一种方法是将所有原始行读入典型的指向字符的指针设置。然后将列转换/转置为行。由于可以想象下一次可能会有 100 行和 500 个字段,因此您将需要使用索引和计数器来进行转换,以跟踪您的分配和重新分配要求,以使您完成的代码能够处理将通用数量的行和字段转换为字段 - 每行具有与原始行一样多的 vales 的行数。
您可以设计代码以提供两种基本功能的转换。第一个读取和存储行 (saygetlines`),第二个将这些行转换为新的指向 char 的指针,以便它可以输出为 逗号分隔值
处理这两个函数的一种方法类似于以下将要读取的文件名作为第一个参数(如果没有给出参数,则默认从stdin 读取)。代码并不简单,但也不难。只需跟踪所有分配,保留指向每个分配开头的指针,以便在不再需要时释放内存,例如
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NPTR 2
#define NWRD 128
#define MAXC 1024
/** getlines allocates all storage required to read all lines from file.
* the pointers are doubled each time reallocation is needed and then
* realloc'ed a final time to exactly size to the number of lines. all
* lines are stored with the exact memory required.
*/
char **getlines (size_t *n, FILE *fp)
{
size_t nptr = NPTR; /* tracks number of allocated pointers */
char buf[MAXC]; /* tmp buffer sufficient to hold each line */
char **lines = calloc (nptr, sizeof *lines);
if (!lines) { /* validate EVERY allocaiton */
perror ("calloc-lines");
return NULL;
}
*n = 0; /* pointer tracks no. of lines read */
rewind (fp); /* clears stream error state if set */
while (fgets (buf, MAXC, fp)) { /* read each line o finput */
size_t len;
if (*n == nptr) { /* check/realloc ptrs if required */
void *tmp = realloc (lines, 2 * nptr * sizeof *lines);
if (!tmp) { /* validate reallocation */
perror ("realloc-tmp");
break;
}
lines = tmp; /* assign new block, (opt, zero new mem below) */
memset (lines + nptr, 0, nptr * sizeof *lines);
nptr *= 2; /* increment allocated pointer count */
}
buf[(len = strcspn(buf, "\r\n"))] = 0; /* get line, remove '\n' */
lines[*n] = malloc (len + 1); /* allocate for line */
if (!lines[*n]) { /* validate */
perror ("malloc-lines[*n]");
break;
}
memcpy (lines[(*n)++], buf, len + 1); /* copy to line[*n] */
}
if (!*n) { /* if no lines read */
free (lines); /* free pointers */
return NULL;
}
/* optional final realloc to free unused pointers */
void *tmp = realloc (lines, *n * sizeof *lines);
if (!tmp) {
perror ("final-realloc");
return lines;
}
return (lines = tmp); /* return ptr to exact no. of required ptrs */
}
/** free all pointers and n alocated arrays */
void freep2p (void *p2p, size_t n)
{
for (size_t i = 0; i < n; i++)
free (((char **)p2p)[i]);
free (p2p);
}
/** transpose a file of n rows and a varying number of fields to an
* allocated pointer-to-pointer t0 char structure with a fields number
* of rows and n csv values per row.
*/
char **transpose2csv (size_t *n, FILE *fp)
{
char **l = NULL, **t = NULL;
size_t csvl = 0, /* csv line count */
ncsv = 0, /* number of csv lines allocated */
nchr = MAXC, /* initial chars alloc for csv line */
*offset, /* array tracking read offsets in lines */
*used; /* array tracking write offset to csv lines */
if (!(l = getlines (n, fp))) { /* read all lines to l */
fputs ("error: getlines failed.\n", stderr);
return NULL;
}
ncsv = *n;
#ifdef DEBUG
for (size_t i = 0; i < *n; i++)
puts (l[i]);
#endif
if (!(t = malloc (ncsv * sizeof *t))) { /* alloc ncsv ptrs for csv */
perror ("malloc-t");
freep2p (l, *n); /* free everything else on failure */
return NULL;
}
for (size_t i = 0; i < ncsv; i++) /* alloc MAXC chars to csv ptrs */
if (!(t[i] = malloc (nchr * sizeof *t[i]))) {
perror ("malloc-t[i]");
while (i--) /* free everything else on failure */
free (t[i]);
free (t);
freep2p (l, *n);
return NULL;
}
if (!(offset = calloc (*n, sizeof *offset))) { /* alloc offsets array */
perror ("calloc-offsets");
free (t);
freep2p (l, *n);
return NULL;
}
if (!(used = calloc (ncsv, sizeof *used))) { /* alloc used array */
perror ("calloc-used");
free (t);
free (offset);
freep2p (l, *n);
return NULL;
}
for (;;) { /* loop continually transposing cols to csv rows */
for (size_t i = 0; i < *n; i++) { /* read next word from each line */
char word[NWRD]; /* tmp buffer for word */
int off; /* number of characters consumed in read */
if (sscanf (l[i] + offset[i], "%s%n", word, &off) != 1)
goto readdone; /* break nested loops on read failure */
size_t len = strlen (word); /* get word length */
offset[i] += off; /* increment read offset */
if (csvl == ncsv) { /* check/realloc new csv row as required */
size_t newsz = ncsv + 1; /* allocate +1 row over *n */
void *tmp = realloc (t, newsz * sizeof *t); /* realloc ptrs */
if (!tmp) {
perror ("realloc-t");
freep2p (t, ncsv);
goto readdone;
}
t = tmp;
t[ncsv] = NULL; /* set new pointer NULL */
/* allocate nchr chars to new pointer */
if (!(t[ncsv] = malloc (nchr * sizeof *t[ncsv]))) {
perror ("malloc-t[i]");
while (ncsv--) /* free everything else on failure */
free (t[ncsv]);
goto readdone;
}
tmp = realloc (used, newsz * sizeof *used); /* realloc used */
if (!tmp) {
perror ("realloc-used");
freep2p (t, ncsv);
goto readdone;
}
used = tmp;
used[ncsv] = 0;
ncsv++;
}
if (nchr - used[csvl] - 2 < len) { /* check word fits in line */
/* realloc t[i] if required (left for you) */
fputs ("realloc t[i] required.\n", stderr);
}
/* write word to csv line at end */
sprintf (t[csvl] + used[csvl], used[csvl] ? ",%s" : "%s", word);
t[csvl][used[csvl] ? used[csvl] + len + 1 : len] = 0;
used[csvl] += used[csvl] ? len + 1 : len;
}
csvl++;
}
readdone:;
freep2p (l, *n);
free (offset);
free (used);
*n = csvl;
return t;
}
int main (int argc, char **argv) {
char **t;
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;
}
if (!(t = transpose2csv (&n, fp))) {
fputs ("error: transpose2csv failed.\n", stderr);
return 1;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < n; i++)
if (t[i])
puts (t[i]);
freep2p (t, n);
return 0;
}
使用/输出示例
$ ./bin/transpose2csv < dat/col2csv3x4.txt
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。
对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/transpose2csv < dat/col2csv3x4.txt
==18604== Memcheck, a memory error detector
==18604== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==18604== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==18604== Command: ./bin/transpose2csv
==18604==
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
==18604==
==18604== HEAP SUMMARY:
==18604== in use at exit: 0 bytes in 0 blocks
==18604== total heap usage: 15 allocs, 15 frees, 4,371 bytes allocated
==18604==
==18604== All heap blocks were freed -- no leaks are possible
==18604==
==18604== For counts of detected and suppressed errors, rerun with: -v
==18604== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放已分配的所有内存并且没有内存错误。
查看一下,如果您还有其他问题,请告诉我。