Reputation: 51
So I'm new to C programming and have some trouble figuring out how to file scan and input each different data into each variable. I already think file scan with fscanf, fgets or fread, but i don't know how to do it. This is the code that i already write:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct testfile
{
char name[20];
int number;
int height;
float weight;
}file[100];
int main ()
{
char buffer[100];
char choose='T';
int x,y,z;
FILE *fp;
fp=fopen("File.txt","r");
/* Fscanf, but fail
for(x=0;x<100;x++)
{
fscanf(fp,"%d %[^\n] %d %f",&file[x].number,file[x].name,&file[x].height,&file[x].weight);
printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].height,file[x].weight);
}*/
/* fgets, same fail too
while(!feof(fp))
{
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%d",&file[x].number);
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%[^\n]",file[x].name);
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%d",&file[x].height);
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%f",&file[x].weight);
printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
x++;
}
*/
/*After the file is success scanned and save into each variable, the user can choose what number from the data and retrieve the following data on that number
while (choose !='Y')
{
printf("Choose Data: ");
scanf("%d",&file[x].number);
printf("Data number : %d\nName : %s\nHeight : %d\nWeight : %.2f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
x++;
printf("Get Another Data? (Y/N) : ");
scanf("%c",&choose);
}*/
fclose(fp);
return 0;
}
File.txt content:
No Name Height (CM) Weight (KG)
1 Josh Broclin 175 83
2 Ryan Andreas 184 98
3 Tom Norton 162 111.6
4 Harry Syd 190 68
5 Hayu Beurang 181 75
6 Jeff Rick 169 108
7 Asley Thomas 179 104
This is the attempt using fscanf (not mixed with fgets)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct testfile
{
char name[20];
int number;
int height;
float weight;
}file[100];
int main ()
{
char buffer[100];
char choose='T';
int x,y,z;
FILE *fp;
fp=fopen("File.txt","r");
//Fscanf, but fail
for(x=0;x<10;x++)
{
fscanf(fp,"%d %[^\n] %d %f",&file[x].number,file[x].name,&file[x].height,&file[x].weight);
printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
}
fclose(fp);
return 0;
}
And this is the output:
0 0 0.000000
0 0 0.000000
0 0 0.000000
0 0 0.000000
0 0 0.000000
0 0 0.000000
0 0 0.000000
0 0 0.000000
0 0 0.000000
0 0 0.000000
Process returned 0 (0x0) execution time : 0.020 s
Press any key to continue.
This one is using fgets (not mixed with fscanf)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct testfile
{
char name[20];
int number;
int height;
float weight;
}file[100];
int main ()
{
char buffer[100];
char choose='T';
int x,y,z;
FILE *fp;
fp=fopen("File.txt","r");
while(!feof(fp))
{
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%d",&file[x].number);
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%[^\n]",&file[x].name);
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%d",&file[x].height);
fgets(buffer,sizeof buffer,fp);
sscanf(buffer,"%f",&file[x].weight);
printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
x++;
}
fclose(fp);
return 0;
}
The output for fgets:
538976309 1 Josh Broclin 175 ☻ 4225408 0.000000
153100320 5 Hayu Beurang ♠ 4225440 0.000000
Process returned 0 (0x0) execution time : 0.105 s
Press any key to continue.
Both method that i currently using give a wrong output. After the file is scanned, it can retrieve data information depend on what number the user choose, for example:
Choose Data: 3
Data number :3
Name : Tom Norton
Height : 162
Weight : 111.6
Get Another Data? (Y/N) :
So how to solve this? I've been thinking about this problem and its solution, but can't find the solution. It really disturbs me whenever I want to sleep this problem comes again and figure out how to solve it. Thank you.
Upvotes: 0
Views: 91
Reputation: 26355
Reiterating and building upon the comments, the primary problem here is allowing spaces in a field but also using spaces as delimiters between fields.
1 Josh Broclin 175 83
-^^^----*-------^^^^^^^^^^^^^^^^^^^^---^^^^^^^^^^^^^--
(In this figure: -
is data, ^
are delimiters, and *
wants to be both.)
With fscanf
, the %s
specifier works by reading and ignoring leading whitespace characters, then reading as many non-whitespace characters as it can, until stopping when it encounters a trailing whitespace character.
After %s
reads the J
in the example above, the next whitespace character encountered will end the conversion. There is no special distinction between the spaces marked ^
and the space marked *
.
As %[^\n]
, the specifier %[
will read and store any character, stopping when new line character is encountered.
Note that using these specifiers without a field-width is as unsafe as gets
, as they will not limit how many characters are read into the buffer. This is an easy way to overflow a buffer. A field-width is specified in the form %19s
, and should be at most the size of the buffer minus one - always leaving room for the null-terminating byte.
A secondary problem is the use of fgets
four times per iteration. This will attempt to read four separate lines of input, when really you want to read one line at a time.
Additionally, you do not check the return values of fgets
or fscanf
, meaning you may be operating on indeterminate values if these functions failed.
See here why while(!feof(fp))
is always wrong.
A quick and simple solution is to just treat the first and last names as separate fields in the file, and concatenate them in memory. snprintf
can be used to safely determine if concatenating the two names resulted in truncation of the resulting string.
This will work for the data file you have shown.
#include <stdio.h>
#include <stdlib.h>
#define MAX_PEOPLE 100
struct person {
char name[20];
int number;
int height;
float weight;
};
int parse(struct person *p, char *input)
{
char first[20];
char last[20];
int cons = sscanf(input, "%d%19s%19s%d%f",
&p->number, first, last, &p->height, &p->weight);
return 5 == cons &&
snprintf(p->name, sizeof p->name, "%s %s", first, last) < sizeof p->name;
}
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: %s FILENAME\n", *argv);
return EXIT_FAILURE;
}
FILE *stream = fopen(argv[1], "r");
if (!stream) {
perror("fopen");
return EXIT_FAILURE;
}
size_t n = 0;
struct person people[MAX_PEOPLE] = { 0 };
char buffer[512];
/* consume the header */
(void) fgets(buffer, sizeof buffer, stream);
while (n < MAX_PEOPLE && fgets(buffer, sizeof buffer, stream)) {
if (parse(people + n, buffer))
n++;
}
fclose(stream);
for (size_t i = 0; i < n; i++)
printf("%d - <<%s>> H:%d W:%f\n",
people[i].number,
people[i].name,
people[i].height,
people[i].weight);
}
While simple, the solution above does impose the requirement that people have exactly two names - something that does not hold true for everyone. Using comma-separated values, would be a more robust solution.
For the file:
1,Josh Broclin,175,83
2,Ryan Andreas,184,98
3,Tom Norton,162,111.6
4,Harry Syd,190,68
5,Hayu Beurang,181,75
6,Jeff Rick,169,108
7,Asley Thomas,179,104
8,This Person Has Many Names,200,100
Here is a cursory CSV parser, using strtok
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_PEOPLE 100
#define EXPECTED_FIELDS 4
#define DEL ",\r\n"
struct person {
char name[64];
int number;
int height;
float weight;
};
typedef int (*parser_action)(struct person *, char *);
int parse_int(int *d, char *s)
{
return 1 == sscanf(s, "%d", d);
}
int parse_number(struct person *p, char *field)
{
return parse_int(&p->number, field);
}
int parse_name(struct person *p, char *field)
{
return snprintf(p->name, sizeof p->name, "%s", field) < sizeof p->name;
}
int parse_height(struct person *p, char *field)
{
return parse_int(&p->height, field);
}
int parse_weight(struct person *p, char *field)
{
return 1 == sscanf(field, "%f", &p->weight);
}
int parse(struct person *p, char *input)
{
parser_action field_actions[EXPECTED_FIELDS] = { parse_number, parse_name, parse_height, parse_weight };
size_t i = 0;
char *tok = strtok(input, DEL);
while (i < EXPECTED_FIELDS && tok) {
if (!field_actions[i++](p, tok))
return 0;
tok = strtok(NULL, DEL);
}
return EXPECTED_FIELDS == i;
}
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: %s FILENAME\n", *argv);
return EXIT_FAILURE;
}
FILE *stream = fopen(argv[1], "r");
if (!stream) {
perror("fopen");
return EXIT_FAILURE;
}
size_t n = 0;
struct person people[MAX_PEOPLE] = { 0 };
char buffer[512];
while (n < MAX_PEOPLE && fgets(buffer, sizeof buffer, stream)) {
if (parse(people + n, buffer))
n++;
}
fclose(stream);
for (size_t i = 0; i < n; i++)
printf("%d - <<%s>> H:%d W:%f\n",
people[i].number,
people[i].name,
people[i].height,
people[i].weight);
}
Upvotes: 1