C++:使用 fgetc 读取 csv 文件,并用分号“;”分隔单词

Posted

技术标签:

【中文标题】C++:使用 fgetc 读取 csv 文件,并用分号“;”分隔单词【英文标题】:C++ : read csv file with fgetc and separate words on semicolon ";" 【发布时间】:2013-11-15 14:22:33 【问题描述】:

我必须读取一个包含 5 个字段(int、char[]、char[]、char[]、float)的 csv 文件,看起来像这样:

2345678;Meier;Hans;12.10.1985;2.4;      
1234567;Müller;Fritz;17.05.1990;1.9;

我必须把字段放在一个struct中,然后在一行完成后将struct放入struct类型的数组中......

为了学习效果,我们只允许使用LOW-LEVEL编码,只能使用fgetc、strcpy等函数,不能使用字符串,只能使用char[]... 现在我让我的算法逐个字符地读取文本文件,但是我在正确分离它们、将它们重新组合在一起并将它们正确分配给结构字段时遇到了问题。这是我的代码:

  #include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>

using namespace std;

int main(int argc, char **argv)

    struct Stud
        long matrnr;
        char vorname[30];
        char name[30];
        char datum[30];
        float note;
    ;

    const int MAX = 30;
    Stud stud;  
    Stud mystud[30]; // <<-- Array of "Stud" type
    //memset((void*)mystud,0,sizeof(mystud) * sizeof(Stud));
    int wordCounter(0);
    int i(0); //thats the charCounter or index
    int studentCounter(0);
    char wort[MAX];
    //int matrnr;
    //char vorname[MAX];
    //char name[MAX];
    //char datum[MAX];
    //float note;


  FILE * pFile;
  int cnr(0); 


  pFile=fopen("studentendaten.txt","r");  
  if (pFile==nullptr) 
  
      perror ("Fehler beim öffnen der Datei");
  

  else
         
    while (cnr != EOF) 
           
        (cnr=fgetc(pFile)) ;


        if ((char)cnr == '\n') 
            mystud[studentCounter] = stud;
            studentCounter++;                       
            continue;           
        

        if ((char)cnr == ';')  

            wort[i] = '\0'; 

            switch (wordCounter % 5) 

                case 0:             
                stud.matrnr = atol(wort);
                break;

                case 1:
                strcpy(stud.name, wort);
                break;

                case 2:
                strcpy(stud.vorname, wort);
                break;

                case 3:
                strcpy(stud.datum,wort);
                break;

                case 4:
                stud.note = atof(wort); 
                break;
                   

            wordCounter++;          
            i = 0;
            continue;
        

        if (wordCounter %  5 == 0 && (char)cnr != ';')         
        wort[i] = (char)cnr;
        i++;
        //stud.matrnr = atol(wort);
                   

        if (wordCounter % 5 == 1) 
            wort[i] =  (char)cnr;
            i++;
        //strcpy(stud.name, wort);
        

        if (wordCounter % 5 == 2) 
            wort[i] = (char)cnr;
            i++;
            //strcpy(stud.vorname, wort);
        

        if (wordCounter % 5 == 3) 
            wort[i] = (char)cnr;
            i++;
            //strcpy(stud.datum,wort);
        

        if (wordCounter % 5 == 4) 
            wort[i] = (char)cnr;
            i++;
            //stud.note = atof(wort);                       
        

       


    fclose (pFile);

for (int i(0) ; i <= studentCounter; i++) 
cout <<mystud[i].matrnr << "    " << mystud[i].name << "    " << mystud[i].vorname <<"    " 
<< mystud[i].datum <<"    " << mystud[i].note << endl;
  //printf("%5ld        %5s      %5s     %5s     %5f     \n",mystud[i].matrnr,mystud[i].name,mystud[i].vorname,mystud[i].datum,mystud[i].note);



    return 0;

我不确定这是否与错误的增量变量有关,或者我没有在我的 wort[] 数组的末尾放置一个 '\0' ..因此无法识别我的阵列?如果是这样,我该怎么做而不知道到底在哪里......? (我不知道单词的长度..)

编辑:我再次更新了我的代码,唯一让我感到奇怪的是最后一行没有被正确解析,它显示了一些垃圾,我在我的代码中看不到错误...

2345678;Meier;Hans;12.10.1985;2.4;      
1234567;Müller;Fritz;17.05.1990;1.9;
8392019;Thomas;Kretschmer;28.3.1920;2.5;
3471144;Mensch;Arbeit;29.2.2013;4.5;
2039482;Test;Test;30.20.2031;2.0;
7584932;Bau;Maschine;02.02.2010;2.3;
2345678;Meier;Hans;12.10.1985;2.4;      
1234567;Müller;Fritz;17.05.1990;1.9;
8392019;Thomas;Kretschmer;28.3.1920;2.5;
3471144;Mensch;Arbeit;29.2.2013;4.5;
2039482;Test;Test;30.20.2031;2.0;
7584932;Bau;Maschine;02.02.2010;2.3;
2345678;Meier;Hans;12.10.1985;2.4;      
1234567;Müller;Fritz;17.05.1990;1.9;
8392019;Thomas;Kretschmer;28.3.1920;2.5;
3471144;Mensch;Arbeit;29.2.2013;4.5;
2039482;Test;Test;30.20.2031;2.0;
7584932;Bau;Maschine;02.02.2010;2.3;
2345678;Meier;Hans;12.10.1985;2.4;      
1234567;Müller;Fritz;17.05.1990;1.9;
8392019;Thomas;Kretschmer;28.3.1920;2.5;
3471144;Mensch;Arbeit;29.2.2013;4.5;
2039482;Test;Test;30.20.2031;2.0;
7584932;Bau;Maschine;02.02.2010;2.3;

【问题讨论】:

一个建议:不要使用像 n、i 和 j 这样的变量。相反,请使用较长的单词,例如 wordCounter、wordIndex 和 studentCounter。当您调试正在发生的事情时,它会变得更加清晰。 您对char(单个字符)和char[](字符数组)感到困惑。您需要将输入中的每个字符复制到输出 - 编写您自己的 stringCopy 函数,因为不允许(可能)使用 strcpy 如果是'\n'';',您可能想要输入continue。否则,您会将分隔符复制到您保留的代码中。 我可以使用 strcpy ...我该怎么做? 啊 - 看看我的代码示例,让自己的生活更简单一点。这不是一个“完整的解决方案”,而是一个“看看你是否可以从这里开始”的答案。您可以通过这种方式了解更多信息... 【参考方案1】:

建议:使用case结构进行解析,并自己做一个“copyToSemicolon”函数:然后你可以写类似的东西

sIndexCount = 0;
char temp[50];
while((cnr=fgetc(pFile)) != EOF) 
  offset = 0;
  for(var = 0; var < 5; var++ 
    switch(var) 
    case 0:
      offset = copyToSemicolon(temp, cnr, offset) + 1;
      stud.matrnr = atoi(temp);
      break;
    case 1:
      offset = copyToSemicolon(mystud[sIndexCount].vorname, cnr, offset) + 1;
      break;
    ... etc
    
  
  sIndexCount++;
  if(sIndexCount == 50) break;  // in case the input file is longer than our structure

并且您需要一个函数copyToSemicolon,它接受两个char* 指针作为输入,并从第二个字符串(从offset 开始)复制字符,直到它到达分号或行尾 - 并且返回它达到的偏移量(读取的最后一个字符)。

int copyToSemicolon(char* dest, char* source, int offset) 
  while(source[offset] != ';' && source[offset] != '\n') 
    *dest = source[offset++];
    dest++;
  
  return offset;
 

编辑 strtok 方法:

sIndexCount = 0;
char temp[50];
while((cnr=fgetc(pFile)) != EOF) 
  offset = 0;
  temp = strtok(cnr, ';');
  for(var = 0; var < 5; var++ 
    switch(var) 
    case 0:
      stud.matrnr = atoi(temp);
      break;
    case 1:
      strcpy(mystud[sIndexCount].vorname, strtok(NULL, ';'));
      break;
    ... etc
    case 4:
      mystud[sIndexCount].note = atof(strtok(NULL, '\n'));
    
  
  sIndexCount++;
  if(sIndexCount == 50) break;  // in case the input file is longer than our structure

【讨论】:

嗯,对于像我这样的 C++ 初学者来说,这听起来很复杂......你在那几行中做了什么:offset = copyToSemicolon(temp, cnr, offset) + 1; stud.matrnr = atoi(temp); 我实际上是想让你更简单。一些结构可以让你的代码保持干净。你可以使用strtok吗?我以为“没有字符串函数”,但也许我错了。我刚刚看到你可以使用strcpy。什么是“低级”... 据我所知,我们可能只使用strcpy,fgetc没有字符串类,只是char[],不知道我们是否可以使用strtok,不过你可以告诉我它的优点.. . :) 我为你写了copyToSemicolon...它比strtok更容易使用(在这种情况下)。 在第一次调用中,您扫描cnr 的第一个分号,并将指向该字符串的指针传递给temp(实际上,要使其工作,您需要char* temp; 而不是char temp[50]); . 然后strtok“记住”字符串,以及它到达的位置......所以你将它传递给NULL作为第一个参数,它'继续'。它完全打算为这个应用程序。查找它!【参考方案2】:

我看到的一个问题是您的代码一次复制或解析一个字符,因此当您阅读 2345678;Meier;Hans;12.10.1985;2.4; 时,您首先将 stud.matrnr 设置为 2,然后是 23,然后是 234,然后是 2345,然后是 23456,然后是 234567,然后是 2345678。同样,对于stud.name,您首先将其设置为 M,然后设置为 Me,然后设置为 Mei,等等。我建议您以不同的方式思考问题。我给你一些伪代码:

while (!eof) 
    get character from file
    if (character isn't ';' and isn't '\n') 
        copy character into buffer (increment buffer index)
     else if (character is ';') 
        it's the end of a word.  Put it in its place - turn it to an int, copy it, whatever
        reset the buffer
     else if (character is '\n') 
        it's the end of the last word, and the end of the line.  Handle the last word
        reset the buffer
        copy the structure
    

这应该会让您的生活更轻松。您几乎不会更改您的数据,如果您需要调试,您可以单独关注每个部分。

通常,在编程中,第一步是确保您可以用您的母语说出您想做的事情,然后将其翻译成代码就更容易了。你已经接近你的实施,你可以让它工作。只要确保你能解释当你看到';'时应该发生什么或'\n'。

【讨论】:

使用缓冲区是个好主意,但我不允许使用字符串类...只能使用 char[] 类...什么是缓冲区类,您可以举个例子吗?跨度> @user2774480 你已经拥有它了。缓冲区是临时保存值的东西。在您的情况下,您使用 wort 作为缓冲区。你已经编写了我给你的所有伪代码;我只是对其进行了重新排序以提高效率(并且更易于调试)。 好的我用我的列表测试了我的新代码,我还没有应用你的版本,我仍然不明白为什么在我的测试文件的最后两行,输出开始变得奇怪,之后那,只是随机垃圾被填充到数组中,因为它没有达到最大数组大小并且它填充了我猜内存中的随机东西......我如何在到达文件末尾后“关闭”数组(EOF ) ?? @user2774480 sizeof(mystud) 并不完全是您想要的。这会给你 50。你想要sizeof(mystud) * sizeof(Stud),在大多数编译器上会给你 50 * 68。如此有效地,您清除了 50 个字节,然后用良好的数据填充它们。 @user2774480 - 我建议您在填充数组时计算“有效”元素的数量,然后只打印有效元素(而不是整个数组)。【参考方案3】:

由于您已将其标记为 C++,您应该考虑使用 std::getline 从文件中读取行,使用 std::getline(file, text_before_semicolon, ';') 解析字段。

您还可以使用std::istringstream 将文本行中的文本表示转换为内部数字格式。

【讨论】:

这是个好主意,但我不确定我们是否可以使用 std::getline 或 istringstream...

以上是关于C++:使用 fgetc 读取 csv 文件,并用分号“;”分隔单词的主要内容,如果未能解决你的问题,请参考以下文章

text 从文件中读取字符并使用“fgetc()”存储它

c语言 怎么读文件中的汉字

算法练习

C语言 文件读写 fgetc 函数

C语言--如何读取 csv 文件里的数据?用户输入:巧克力, 系统读取csv文件里巧克力对应的牌子和价格,并输出

读取 .csv 文件 C++ 时的 Atoi 函数