用于复制整数的 .csv 的 C 程序会少复制一个元素,除非元素大小设置为 +1

C program to copy .csv of integers copies one less element unless element size is set to +1

我刚开始学习 C 语言,我想编写一个简单的程序,将数组整数从一个 .csv 文件复制到一个新的 .csv 文件。我的代码按预期工作,但是当我的 fread/fwrite 数组大小设置为 .csv 数组中元素的确切数量(在本例中为 10)时,它仅复制九个元素。

当数组大小设置为+1时,它复制所有元素。

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

#define LISTSIZE 11
 //program that copies an array of integers from one .csv to another .csv

int main(int argc, char * argv[])
{
   if (argc != 2)
   {
        fprintf(stderr, "Usage ./file_sort file.csv\n");
        return 1;
   }
   char * csvfile = argv[1];
   FILE * input_csvile = fopen(csvfile, "r");   //open .csv     file and create file pointer input_csvile
   if(input_csvile == NULL)
   {
       fprintf(stderr, "Error, Could not open\n");
       return 2;
   }
   unsigned int giving_total[LISTSIZE];
   if(input_csvile != NULL)  //after file opens, read array from .csv input file
   {
       fread(giving_total, sizeof(int), LISTSIZE, input_csvile);
   }
   else
    fprintf(stderr, "Error\n");


   FILE * printed_file = fopen("school_currentfy1.csv", "w");

   if (printed_file != NULL)
   {
       fwrite(giving_total, sizeof(int), LISTSIZE, printed_file);  //copy array of LISTSIZE integers to new file
   }
   else
    fprintf(stderr, "Error\n");

   fclose(printed_file);
   fclose(input_csvile);

   return 0;




  }

这是否与索引为 0 的数组和索引为 1 的 .csv 文件有关?我还有一个 LISTSIZE 为 11 的输出,其中最后一个 (10) 元素显示不正确; 480 而不是 4800。

http://imgur.com/lLOozrc Output/input LISTSIZE 为 10

http://imgur.com/IZPGwsA Input/Output LISTSIZE 为 11

注意:如评论中所述,freadfwrite 用于读写 binary 数据,而不是文本。如果您正在处理 .csv(逗号分隔值——例如从 MS ExcelOpen/LibreOffice calc) 您将需要使用 fgets(或任何其他面向 character/string 的函数),然后使用 sscanf(或 strtolstrtoul)来阅读值作为文本并执行到 int 值的转换。要将值写入输出文件,请使用 fprintf。 (fscanf 也可用于输入文本处理和转换,但您在处理输入格式的变化时失去了灵活性)

但是,如果您的目标是读取 binary 数据的 10 整数(例如 40-bytes 数据),那么 freadfwrite 很好,但是与 all input/output 例程一样,您需要验证读取和写入的字节数以确保您处理的是有效数据代码。 (完成后你有一个有效的输出数据文件)

读取 .csv 文件的方法有很多种,具体取决于格式。一种通用的方法是简单地用 fgets 读取每一行文本,然后重复调用 sscanf 来转换每个值。 (与 fscanf 相比,这在处理 ',' 周围的不同间距方面有很多优势)您只需读取每一行,将指针分配给 fgets 读取的缓冲区的开头,然后然后调用 sscanf(使用 %n 到 return 每次调用处理的字符数)然后将指针前进该数字并在缓冲区中向前扫描直到下一个 '-'(对于负值)或遇到数字。 (使用%n并向前扫描可以让fscanf以类似的方式使用)例如:

/* read each line until LISTSIZE integers read or EOF */
while (numread < LISTSIZE && fgets (buf, MAXC, fp)) {

    int nchars = 0;     /* number of characters processed by sscanf */
    char *p = buf;      /* pointer to line */

    /* (you should check a whole line is read here) */

    /* while chars remain in buf, less than LISTSIZE ints read 
     * and a valid conversion to int perfomed by sscanf, update p
     * to point to start of next number.
     */
    while (*p && numread < LISTSIZE && 
            sscanf (p, "%d%n", &giving_total[numread], &nchars) == 1) {
        numread++;      /* increment the number read */
        p += nchars;    /* move p nchars forward in buf */
        /* find next digit in buf */
        while (*p && *p != '-' && (*p < '0' || *p > '9'))
            p++;
    }
}

现在要创建输出文件,您只需以逗号分隔值格式写回 numread 值。 (您可以根据需要调整每行写入的数量)

for (i = 0; i < numread; i++)   /* write in csv format */
    fprintf (fp, i ? ",%d" : "%d", giving_total[i]);
fputc ('\n', fp);   /* tidy up -- make sure file ends with '\n' */

那么只需关闭输出文件并检查任何流错误(在 值写入文件时始终检查关闭)

if (fclose (fp))        /* always validate close after write to */
    perror("error");    /* validate no stream errors occurred */

总而言之,您可以执行类似于以下操作的操作:

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

#define LISTSIZE 10
#define MAXC 256

int main(int argc, char *argv[])
{
    if (argc < 3) {
        fprintf(stderr, "Usage ./file_sort file.csv [outfile]\n");
        return 1;
    }

    int giving_total[LISTSIZE]; /* change to int to handle negative values */
    size_t i, numread = 0;      /* generic i and number of integers read */
    char *csvfile = argv[1],
        buf[MAXC] = "";         /* buffer to hold MAXC chars of text */
    FILE *fp = fopen (csvfile, "r");

    if (fp == NULL) {   /* validate csvfile open for reading */
        fprintf(stderr, "Error, Could not open input file.\n");
        return 2;
    }

    /* read each line until LISTSIZE integers read or EOF */
    while (numread < LISTSIZE && fgets (buf, MAXC, fp)) {

        int nchars = 0;     /* number of characters processed by sscanf */
        char *p = buf;      /* pointer to line */

        /* (you should check a whole line is read here) */

        /* while chars remain in buf, less than LISTSIZE ints read 
         * and a valid conversion to int perfomed by sscanf, update p
         * to point to start of next number.
         */
        while (*p && numread < LISTSIZE && 
                sscanf (p, "%d%n", &giving_total[numread], &nchars) == 1) {
            numread++;      /* increment the number read */
            p += nchars;    /* move p nchars forward in buf */
            /* find next digit in buf */
            while (*p && *p != '-' && (*p < '0' || *p > '9'))
                p++;
        }
    }
    if (numread < LISTSIZE) /* warn if less than LISTSIZE integers read */
        fprintf (stderr, "Warning: only '%zu' integers read from file", numread);

    fclose (fp);    /* close input file */

    fp = fopen (argc > 2 ? argv[2] : "outfile.csv", "w");  /* open output file */

    if (fp == NULL) {   /* validate output file open for writing */
        fprintf(stderr, "Error, Could not open output file.\n");
        return 3;
    }

    for (i = 0; i < numread; i++)   /* write in csv format */
        fprintf (fp, i ? ",%d" : "%d", giving_total[i]);
    fputc ('\n', fp);   /* tidy up -- make sure file ends with '\n' */

    if (fclose (fp))        /* always validate close after write to */
        perror("error");    /* validate no stream errors occurred */

    return 0;
}

就像我说的,有很多很多方法可以解决这个问题。这个想法是为您的阅读建立尽可能多的灵活性,以便它可以处理输入格式的任何变化而不会窒息。另一种非常可靠的读取方法是使用 strtol(或 strtoul 用于无符号值)。两者都允许将指针指向转换后的整数后的下一个字符,以便您可以从那里开始扫描下一个数字。

下面显示了在这些方法中的任何一种中提供的读取灵活性的示例。读取任意行数的文件,值由任何分隔符分隔,并将遇到的每个整数转换为数组中的值,例如

示例输入

$ cat ../dat/10int.csv
8572, -2213, 6434, 16330, 3034
12346, 4855, 16985, 11250, 1495

示例程序使用

$ ./bin/fgetscsv ../dat/10int.csv dat/outfile.csv

示例输出文件

$ cat dat/outfile.csv
8572,-2213,6434,16330,3034,12346,4855,16985,11250,1495

检查一下,如果您有任何问题,请告诉我。如果您的目的是阅读二进制形式的 40-bytes,请告诉我,我很乐意为您提供示例。

如果您想要真正通用地读取文件中的值,您可以调整在输入文件中查找数字的代码以在文件中向前扫描并验证任何 '-' 后跟一个数字。这允许读取任何格式并简单地从文件中选择整数。例如,进行以下小改动:

    while (*p && numread < LISTSIZE) {
        if (sscanf (p, "%d%n", &giving_total[numread], &nchars) == 1)
            numread++;      /* increment the number read */
        p += nchars;        /* move p nchars forward in buf */
        /* find next number in buf */
        for (; *p; p++) {
            if (*p >= '0' && *p <= '9') /* positive value */
                break;
            if (*p == '-' && *(p+1) >= '0' && *(p+1) <= '9') /* negative */
                break;
        }
    }

您可以轻松处理以下文件并获得相同的结果:

$ cat ../dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a

- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495

示例程序使用

$ ./bin/fgetscsv ../dat/10intmess.txt dat/outfile2.csv

示例输出文件

$ cat dat/outfile2.csv
8572,-2213,6434,16330,3034,12346,4855,16985,11250,1495