0

a couple of days ago I started a small project where I should do two things:

  1. Create a function to read file information and show it on the screen.
  2. Create a function to write new records to file. (Add a new record)

My structure

struct cofeeShop
{
char cofeeName [20]
char cofeeColor [20]
chat cofeeWorld [20]
int CofeePockets
int onePocketSize 
}

Txt file “records.txt” with this information:

Lavazza Gray Europe 433 10 
Machito Black Europe 433 10 
Machito White Asia 24 18 
Chiley Black Asia 198 17 
Hucki White America 11 11

I’m new at programming with C and because of that I need some help.

I know that the C language has 4 different methods of scanning (fscanf, fgets, fgetc, freada) and I don’t know which method is the best for structure scanning in my situation. What are the main differences?

Do I need an array to show all data from the file on the screen? How to show all data from file txt to the screen?

Any sugestions?

Thanks for your time!

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
NewAtC
  • 55
  • 9
  • The key is read your input into a adequately sized buffer (character arra) using `fgets()` and then parsing the values from the line into a temporary `struct cofeeShop` using `sscanf()`, and validating the conversion by ***checking the return*** of `sscanf()`, you can simply add the values in the temporary struct to an array of struct. When you are done, loop over your array, writing the values as formatted output to the screen with `printf()` and write the values to a file with `fprintf()`. – David C. Rankin Dec 12 '20 at 01:39
  • [Spliting each line by spaces in C](https://stackoverflow.com/a/57682699/3422102) may be helpful. Though, if the name of the coffee can be something other than 2 words separated by whitespace -- that will complicate separating the input line into the individual struct values. – David C. Rankin Dec 12 '20 at 01:45

1 Answers1

0

If you want a short example to help get you started, the first thing to keep in mind when reading strings into a character array (buffer) is Don't Skimp on Buffer Size!. Using 10 and 20 may cut it for the small set of data you have shown, but what happens when you try and read Mandheling Black? Take your longest anticipated string and at minimum, double that number of characters when using a fixed array (if it is a single buffer to be reused such as for reading each line, a 1K buffer size is fine, 2K is also fine)

Try to avoid using MagicNumbers like 10 and 20, instead:

#include <stdio.h>

#define MAXC 1024               /* if you need a constant, #define one (or more) */
#define MAXNM  32               /* (don't skimp on buffer size) */
#define MAXCT  16

typedef struct cofeeshop {      /* typedef avoid writing 'struct name' in code */
    char cofeeName [MAXNM],
        cofeeColor [MAXNM],
        cofeeWorld [MAXNM];
    int CofeePockets,
        onePocketSize; 
} cofeeshop;
...

If you note above, a typedef of struct cofeeshop is created in the name cofeeshop. By creating a typedef, you can simply use cofeeshop as the type anywhere it is needed in your code instead of having to write struct cofeeshop each time.

What you need to actually read from your file, is a buffer (character array) large enough to hold each line, a counter to keep track of how many coffee types you have read, and then an array of cofeeshop to hold the values contained in each line. You can do that while taking the filename to read from as the first argument to your program (or read from stdin if no argument is given) as follows:

int main (int argc, char **argv) {
    
    char line[MAXC];                                        /* buffer to hold each line */
    cofeeshop cofee[MAXCT] = {{.cofeeName = ""}};           /* array of chofeeshop */
    size_t n = 0;                                           /* cofeetype counter */
    /* 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;
    }
    ...
    

Note above you always VALIDATE the return from any input/output function used, including whether fopen() succeeded or failed, Before you attempt to read from the file.

You can then read each line from the file with fgets() into your buffer line and then pass line to sscanf() to separate the parts of line into the next available element in your array of struct, cofee, e.g.

    ...
    /* while array not full, read each line in file and add to array */
    while (n < MAXCT && fgets (line, MAXC, fp)) {
        /* separate into name, color, world, pockets, size & VALIDATE return */
        if (sscanf (line, "%s %s %s %d %d", cofee[n].cofeeName, cofee[n].cofeeColor, 
                    cofee[n].cofeeWorld, &cofee[n].CofeePockets,
                    &cofee[n].onePocketSize) == 5) {
            n++;    /* increment cofeetype counter */
        }
    }
    ...

Note: you VALIDATE the return of sscanf() to check that the conversion with each format-specifier in the format-string "%s %s %s %d %d" succeeded. There are five format-specifiers in the format-string, so you check that the return == 5 before considering the variables to be filled with valid input from line.

At this point your reading of input is done, so simply close the file if not reading from stdin and output all values stored in your array to stdout, e.g.

    ...
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    for (size_t i = 0; i < n; i++)                          /* output stored values */
        printf ("%-12s  %-12s  %-12s  %4d  %4d\n", cofee[i].cofeeName, cofee[i].cofeeColor,
                cofee[i].cofeeWorld, cofee[i].CofeePockets, cofee[i].onePocketSize);
}

All that remains is writing the values to an output file in a format of your choosing -- that is left to you.

Putting it altogether (that is the full code above), you would have:

#include <stdio.h>

#define MAXC 1024               /* if you need a constant, #define one (or more) */
#define MAXNM  32               /* (don't skimp on buffer size) */
#define MAXCT  16

typedef struct cofeeshop {      /* typedef avoid writing 'struct name' in code */
    char cofeeName [MAXNM],
        cofeeColor [MAXNM],
        cofeeWorld [MAXNM];
    int CofeePockets,
        onePocketSize; 
} cofeeshop;

int main (int argc, char **argv) {
    
    char line[MAXC];                                        /* buffer to hold each line */
    cofeeshop cofee[MAXCT] = {{.cofeeName = ""}};           /* array of chofeeshop */
    size_t n = 0;                                           /* cofeetype counter */
    /* 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;
    }
    
    /* while array not full, read each line in file and add to array */
    while (n < MAXCT && fgets (line, MAXC, fp)) {
        /* separate into name, color, world, pockets, size & VALIDATE return */
        if (sscanf (line, "%s %s %s %d %d", cofee[n].cofeeName, cofee[n].cofeeColor, 
                    cofee[n].cofeeWorld, &cofee[n].CofeePockets,
                    &cofee[n].onePocketSize) == 5) {
            n++;    /* increment cofeetype counter */
        }
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    for (size_t i = 0; i < n; i++)                          /* output stored values */
        printf ("%-12s  %-12s  %-12s  %4d  %4d\n", cofee[i].cofeeName, cofee[i].cofeeColor,
                cofee[i].cofeeWorld, cofee[i].CofeePockets, cofee[i].onePocketSize);
}

Compile

Compile your program with full warnings enabled, every time -- and do not accept code until it compiles without warning, e.g.

gcc -Wall -Wextra -pedantic -Wshadow -std=c11 -Ofast -o bin/cofeeshop cofeeshop.c

-Wall -Wextra -pedantic enable full warnings for gcc/clang, add -Wshadow to catch any shadowed variables (like i declared and used in two different scopes in your code that may cause problems). For VS use /W3 for full warnings. For other compilers, just read the option documentation to determine what is needed.

Example Use/Output

With your sample data in the file dat/cofeeshop.txt, you would have:

$ ./bin/cofeeshop dat/cofeeshop.txt
Lavazza       Gray          Europe         433    10
Machito       Black         Europe         433    10
Machito       White         Asia            24    18
Chiley        Black         Asia           198    17
Hucki         White         America         11    11

If you wanted to test reading from stdin, you could just redirect the input file on stdin, e.g.:

$ ./bin/cofeeshop < dat/cofeeshop.txt
Lavazza       Gray          Europe         433    10
Machito       Black         Europe         433    10
Machito       White         Asia            24    18
Chiley        Black         Asia           198    17
Hucki         White         America         11    11

Look things over and let me know if you have further questions.

(note: cofee is generally spelled coffee -- but will go with what you have :)

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thank you very much for this code, but I still have some questions left. Is it possible to set the lengths of each coffee and country name? Because in this program, I will allow people to enter new coffee information themselves, and how to safe from too long name entered. Also, how to solve problem if I want to have coffee name from two words? – NewAtC Dec 12 '20 at 12:03
  • Yes, you change the arrays to pointers and then allocate memory for each string, e.g. char *cofeeName, *cofeeColor, *cofeeWorld; Then with `sscanf()` you would read into temporary arrays, say `char name[128], color[128], world[128];` After verifying the return is `5`, you would allocate, e.g. `size_t len = strlen(name); cofee[n].cofeeName = malloc (len + 1);` then `memcpy (cofee[n].cofeeName, name, len + 1);` (you would validate the allocation before `memcpy`) So the same for the other pointers. Then your storage has been sized exactly for your data. – David C. Rankin Dec 12 '20 at 23:45
  • If I was doing it, I'd use `char name[1024], color[1024], world[1024];` (which is about 12 - 20 full lines of text on the screen (or the cat stepping on the keyboard). Linux provides a 4-Meg stack, Windows a 1-Meg stack. So 3 1K buffers, that's only `0.0029` (or `0.29%`) of the usable stack space -- on windows. If you are on a micro-controller, then `128` byte buffers make more sense. – David C. Rankin Dec 12 '20 at 23:56