简体   繁体   中英

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. 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. 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 ). 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. You can do the same for expected answers per question ( MAXA 16 below). 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. 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. 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. Basically, getline needs a way of keeping track of the memory it has used. 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. 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 . 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. Bottom line, always preserve the starting address for the buffer allocated by 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:

==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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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