简体   繁体   English

c使用fscanf()从文本文件中读取单独的单词

[英]c reading separate words from text file using fscanf()

I am writing a quiz program. 我正在编写测验程序。 The program should read question, answers and correct answer from csv file. 该程序应从csv文件读取问题,答案和正确答案。 Then it should store them in array. 然后,应将它们存储在数组中。

 void read(char question[][50], char answer1[10][10], char answer2[10][10], char answer3[10][10], char answer4[10][10], int correctAnswer[10], int *size, char fileName[], int noOfQuestion){
FILE *reader;
int count;
char qBuffer[50];
char ansBuffer1[50];
char ansBuffer2[50];
char ansBuffer3[50];
char ansBuffer4[50];
int iBuffer = 0;
*size = 0;


//open file
reader = fopen(fileName, "r");

//checking file is open or not
if (reader == NULL)
{
    printf("Unable to open file %s", fileName);
}
else
{
    fscanf(reader, "%100[^\t*\?,],%[^,],%[^,],%[^,],%[^,],%d", size);
    for (count = 0; feof(reader) == 0 && count<*size && count<noOfQuestion; count++){
    //Reading file
        fscanf(reader, "%100[^\t*\?,],%[^,],%[^,],%[^,],%[^,],%d", qBuffer, ansBuffer1, ansBuffer2, ansBuffer3, ansBuffer4, iBuffer);

        //Storing data
        strcpy(question[count], qBuffer);
        strcpy(answer1[count], ansBuffer1);
        strcpy(answer2[count], ansBuffer2);
        strcpy(answer3[count], ansBuffer3);
        strcpy(answer4[count], ansBuffer4);
        correctAnswer[count] = iBuffer;

        // Check Correct Number of Items Read
        if( count == noOfQuestion )
        {
            printf("There are more items in the file than MaxNoItems specifies can be stored in the output arrays.\n\n");
            *size = count;
        }
        else if( count != *size - 1 )
        {
            printf("File is corrupted. Not as many items in the file as specified at the top.\n\n");
            *size = count;
        }
        //Break if reached end of file.
        if (feof(reader))
        { break;}
        }
        fclose(reader);
}
}

This the csv file to read from. 这是要读取的csv文件。 each question and answers are in one line. 每个问题和答案都在同一行。

What function do you use to open a file?,fscanf,fclose,fopen,main,3
Which of the following is not a variable type?,int,float,char,string,4
How many bytes is a character?,8,4,2,1,4
What programming language have you been studying this term?,B,A,D,C,4
Which of the following is a comment?,#comment,//comment,$comment,%comment,2
Which of these is in the C Standard Library?,stdio.h,studio.h,iostream,diskio.h,1
What tool do we use to compile?,compiler,builder,linker,wrench,1
What function do you use to close a file?,fscanf,fclose,fopen,main,2
How do you include a file?,#include,//include,$include,%include,1
What are you doing this quiz on?,paper,whiteboard,computer,chalkboard,3

I worked to find a way to solve the issues in your code, however there just isn't a clean way to follow your double-read of each line an make it work in a reasonable way. 我努力寻找一种方法来解决您的代码中的问题,但是,没有一种干净的方法来对每行进行两次重读,以使其合理地工作。 The structural issue you have is attempting to read the line twice, first to determine the size and next to try and read the actual values. 您遇到的结构性问题是尝试读取该行两次,首先确定大小,然后尝试读取实际值。 This has many pitfalls. 这有很多陷阱。

Instead of trying to read each line in a piecemeal manner, it is far better to read an entire line at a time using the line-oriented input functions provided by C ( fgets , getline ). 而不是试图读取零碎地每一行,它远远更好使用由C(提供的面向行的输入功能中的一个的时间来读取整个行fgetsgetline )。 It will make your code much more flexible and make life easier on you as well. 这将使您的代码更加灵活,并使您的生活也更轻松。 The basic approach is to read a line at a time into a 'buffer', then using the tools provided, extract what you need from the line, store it in a way that makes sense, and move on to the next line. 基本方法是一次将一行读入“缓冲区”,然后使用提供的工具,从该行中提取所需内容,以有意义的方式存储它,然后继续进行下一行。

There is just no way to hardcode a bunch of arrays in your function argument list and have it work in a sane way. 根本没有办法在函数参数列表中对一堆数组进行硬编码,并使它以理智的方式工作。 The proper way to do it is to pass a pointer to some type datastruct to your function, have your function fill it, allocating memory as needed, and provide a pointer in return. 正确的方法是将指向某种类型数据结构的指针传递给函数,让函数填充它,根据需要分配内存,并提供一个返回的指针。 In your case a simple structure makes a lot more sense that one two-dimensional array for each question you expect to read. 在您的情况下,简单的结构比您要阅读的每个问题的二维数组更有意义。

It is far better to define an initial size for the expected number questions, ( MAXQ 128 below), and allocate storage for that amount. 最好为预期数量的问题define初始大小(以下为MAXQ 128 ),然后为该数量分配存储空间。 You can do the same for expected answers per question ( MAXA 16 below). 您可以对每个问题的预期答案进行相同的操作(以下为MAXA 16 )。 If you end up reading more than each, you can easily reallocate to handle the data. 如果最终阅读的内容不止一个,那么您可以轻松地重新分配以处理数据。

Once you have your struct filled (or array of structs ), you make that data available to your main code by a simple return. 填充struct (或array of structs )后,只需简单的返回,即可将这些数据提供给您的主代码。 You then have a single pointer to your data that you can easily pass you a print function or wherever else you need the data. 然后,您只有一个指向数据的指针,可以轻松地向您传递打印功能或其他需要数据的地方。 Since the storage for your data was allocated dynamically, you are responsible for freeing the memory used when it is no longer needed. 由于数据的存储是动态分配的,因此您有责任在不再需要使用的内存时释放它们。

I have provided examples of both a print and free function to illustrate passing the pointer to the data between functions as well as the practical printing and freeing of the memory. 我提供了打印功能和释放功能的示例,以说明将指针传递给功能之间的数据,以及实际的打印和释放内存。

Designing your code in a similar manner will save you a lot of headaches in the long run. 从长远来看,以类似的方式设计代码将为您节省很多麻烦。 There are many ways to do this, the example below is simply one approach. 有很多方法可以做到这一点,下面的示例只是一种方法。 I commented the code to help you follow along. 我对代码进行了注释,以帮助您遵循。 Take a look and let me know if you have questions. 看看,如果您有任何问题,请告诉我。


Note: I have replaced the original readQAs function with the version I originally wrote, but had a lingering issue with. 注意:我已经用我最初编写的版本替换了原始的readQAs函数,但是存在一个长期存在的问题。 When using getline you must preserve the starting address for any buffer allocated by getline or repetitive calls to getline will result in a segfault when getline attempts to reallocate its buffer. 使用getline ,必须保留getline分配的任何缓冲区的起始地址,否则当getline尝试重新分配其缓冲区时,对getline重复调用将导致段错误。 Basically, getline needs a way of keeping track of the memory it has used. 基本上, getline需要一种跟踪其已使用内存的方法。 You are free to chop the buffer allocated by getline up any way you want, as long as you preserve the starting address of the originally allocated buffer. 只要保留原始分配的缓冲区的起始地址 ,就可以随意将getline分配的缓冲区砍掉。 Keeping a pointer to the original is sufficient. 保持原始指针就足够了。

This can be particularly subtle when you pass the buffer to functions that operate on the string such as strtok or strsep . 当您将缓冲区传递给对字符串进行操作的函数(例如strtokstrsep时,这可能特别微妙。 Regardless, the result of failing to preserve the start of the buffer allocated by getline will result in a segfault at whatever loop exhausts the initial 120-byte buffer allocated by getline receiving __memcpy_sse2 () from /lib64/libc.so.6 If you never exhaust the original 120-byte buffer, you will never experience a segfault. 无论如何,未能保留由getline分配的缓冲区的开始的结果将导致segfault ,无论循环耗尽由getline __memcpy_sse2 () from /lib64/libc.so.6接收__memcpy_sse2 () from /lib64/libc.so.6getline分配的初始120字节缓冲区,如果您从未用尽原始的120字节缓冲区后,您将永远不会遇到段错误。 Bottom line, always preserve the starting address for the buffer allocated by getline . 最重要的是,始终保留getline分配的缓冲区的起始地址。

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

#define MAXQ 128
#define MAXA 16

typedef struct {
    char *q;
    char **ans;
    unsigned int nans;
} ques;

ques **readQAs (char *fn);
void prn_ques (ques **exam);
void free_ques (ques **exam);

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

    if (argc < 2) {
        fprintf (stderr,"\n error: insufficient input. Usage: %s <csvfile>\n\n", argv[0]);
        return 1;
    }

    ques **exam = NULL;     /* pointer to pointer to struct */

    /* allocate/fill exam structs with questions/answers    */
    if ( !( exam = readQAs (argv[1]) ) ) {
        fprintf (stderr, "\n error: reading questions/answers from '%s'\n\n", argv[1]);
        return 1;
    }

    prn_ques (exam);        /* print the questions/answers  */
    free_ques (exam);       /* free all memory allocated    */

    return 0;
}

/* allocate and fill array of structs with questions/answers    */
ques **readQAs (char *fn)
{
    FILE *fp = fopen (fn, "r");         /* open file and validate   */
    if (!fp) {
        fprintf (stderr,"\n error: Unable to open file '%s'\n\n", fn);
        return NULL;
    }

    char *line = NULL;      /* line buff, if NULL getline allocates */
    size_t n = 0;           /* max chars to read (0 - no limit)     */
    ssize_t nchr = 0;       /* num chars actually read by getline   */
    char *p = NULL;         /* general pointer to parse line        */
    char *sp = NULL;        /* second pointer to parse line         */
    char *lp = NULL;        /* line ptr (preserve line start addr)  */
    size_t qidx = 0;        /* index for questions structs          */
    size_t aidx = 0;        /* index for answers within structs     */

    ques **q = calloc (MAXQ, sizeof (*q));  /* allocate MAXQ ptrs   */
    if (!q) { fprintf (stderr,"\n Allocation error.\n\n"); return NULL; }

    /* for each line in file (fn)   */
    while ((nchr = getline (&line, &n, fp)) != -1)
    {
                                    /* test qidx = MAXQ-1, realloc  */
        aidx = 0;                   /* reset ans index each line    */

        lp = line;                  /* save line start address      */
        if (line[nchr - 1] == '\n') /* test/strip trailing newline  */
            line[--nchr] = 0;

        q [qidx] = calloc (1, sizeof (**q));    /* allocate struct  */
        q [qidx]-> ans = calloc (MAXA, sizeof (*(q[qidx]-> ans)));

        /* read question */
        *(p = strchr (line, ',')) = 0;  /* null-terminate ln at ',' */
        q [qidx]-> q = strdup (line);   /* alloc/read question      */
        sp = p + 1;                     /* sp now starts next ch    */

        /* read correct answer number */
        *(p = strrchr (sp, ',')) = 0;   /* null-term ln at last ',' */
        q [qidx]-> nans = *(p+1) - '0'; /* save num ans, cvt to %zd */

        /* read multi-choice answers */
        for (p = strtok (sp, ","); p && *p; p = strtok (NULL, ","))
            q [qidx]-> ans [aidx++] = strdup (p); /* alloc/read ans */

        line = lp;                      /* avoid __memcpy_sse2 err  */

        qidx++;                         /* inc index for next Q     */
    }
    if (line) free (line);              /* free line memory         */
    if (fp) fclose (fp);                /* close file stream        */

    return q;   /* return ptr to array of structs holding Q/A(s)    */
}

/* print formatted exam read from file */
void prn_ques (ques **exam)
{
    if (!exam) {
        fprintf (stderr, "\n %s() error: invalid exam pointer.\n\n", __func__);
        return;
    }

    size_t qidx = 0;        /* index for questions structs          */
    size_t aidx = 0;        /* index for answers within structs     */

    printf ("\nClass Exam\n\n");
    while (exam [qidx])
    {
        printf (" %2zd.  %s\n\n", qidx + 1, exam[qidx]-> q);
        aidx = 0;
        while (exam[qidx]->ans[aidx])
        {
            if (exam[qidx]-> nans == aidx + 1)
                printf ("\t(%c)  %-16s    (* correct)\n", (int)aidx + 'a', exam[qidx]->ans[aidx]);
            else
                printf ("\t(%c)  %s\n", (int)aidx + 'a', exam[qidx]->ans[aidx]);
            aidx++;
        }
        printf ("\n");
        qidx++;
    }
    printf ("\n");
}

/* free all memory allocated */
void free_ques (ques **exam)
{
    if (!exam) {
        fprintf (stderr, "\n %s() error: invalid exam pointer.\n\n", __func__);
        return;
    }

    size_t qidx = 0;        /* index for questions structs          */
    size_t aidx = 0;        /* index for answers within structs     */

    while (exam[qidx])
    {
        if (exam[qidx]->q) free (exam[qidx]->q);
        for (aidx = 0; aidx < MAXA; aidx++) {
            if (exam[qidx]->ans[aidx]) {
                free (exam[qidx]->ans[aidx]);
            }
        }
        free (exam[qidx]->ans);
        free (exam[qidx++]);
    }
    free (exam);
}

output/verification: 输出/验证:

$ ./bin/readcsvfile  dat/readcsvfile.csv

Class Exam

  1.  What function do you use to open a file?

        (a)  fscanf
        (b)  fclose
        (c)  fopen               (* correct)
        (d)  main

  2.  Which of the following is not a variable type?

        (a)  int
        (b)  float
        (c)  char
        (d)  string              (* correct)

  3.  How many bytes is a character?

        (a)  8
        (b)  4
        (c)  2
        (d)  1                   (* correct)

  4.  What programming language have you been studying this term?

        (a)  B
        (b)  A
        (c)  D
        (d)  C                   (* correct)

  5.  Which of the following is a comment?

        (a)  #comment
        (b)  //comment           (* correct)
        (c)  $comment
        (d)  %comment

  6.  Which of these is in the C Standard Library?

        (a)  stdio.h             (* correct)
        (b)  studio.h
        (c)  iostream
        (d)  diskio.h

  7.  What tool do we use to compile?

        (a)  compiler            (* correct)
        (b)  builder
        (c)  linker
        (d)  wrench

  8.  What function do you use to close a file?

        (a)  fscanf
        (b)  fclose              (* correct)
        (c)  fopen
        (d)  main

  9.  How do you include a file?

        (a)  #include            (* correct)
        (b)  //include
        (c)  $include
        (d)  %include

 10.  What are you doing this quiz on?

        (a)  paper
        (b)  whiteboard
        (c)  computer            (* correct)
        (d)  chalkboard

valgrind verification: valgrind验证:

==16221==
==16221== HEAP SUMMARY:
==16221==     in use at exit: 0 bytes in 0 blocks
==16221==   total heap usage: 73 allocs, 73 frees, 3,892 bytes allocated
==16221==
==16221== All heap blocks were freed -- no leaks are possible
==16221==
==16221== For counts of detected and suppressed errors, rerun with: -v
==16221== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM