newbie
newbie

Reputation: 1

The difference between fgets() and scanf()

This is my code. I didnt want to use scanf() to read the name and I tried using fgets() but after I put the first name and age, the second and third time my program runs the for loop it doesnt take age .

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

struct student
{
    char name[15];
    int age;
};


int main()
{
    int i;
    struct student s[A];
    for (i = 0 ; i < A ; i++)
    {
        printf("enter the names and age\n");
        fgets(s[i].name, 10, stdin);
        scanf("%d", &s[i].age);
    }
    printf("\n\n");

    for (i = 0 ; i < A ; i++)
    {
        printf("%s\t%d\n", s[i].name, s[i].age);
    }
    return 0;
}

It doesnt work, why?

But when I replace fgets with

scanf("%s%d",s[i].name,&s[i].age);

It works fine

Upvotes: 0

Views: 1389

Answers (3)

chux
chux

Reputation: 154228

The difference between fgets() and scanf()

fgets(...) typically reads until receiving a '\n'

scanf("%d", ...) typically:
1. Reads and discards leading white-space.
2. Reads numeric input (sign,digits) until scanning a non-digit.
3. Non-digit is put back into stdin for the next input function.


Example:

JohnEnter
"John\n" is read by fgets() into s[0].name.

21Enter
21 is read by scanf("%d",...) into s[0].age. '\n' put back into stdin

"\n" is read by fgets() into s[1].name.

M
"M" is read by scanf("%d",...), nothing is put in s[1].age. 'M' put back into stdin.

aryEnter
"Mary\n" is read by fgets() into s[2].name.

19Enter
19 is read by scanf("%d",...) into s[2].age. '\n' put back into stdin

"\n" is read by fgets() into s[3].name.


Alternative: To read 2 lines, call fgets() twice, then parse:

int Scan_student(struct student *dest) {
  char buffer[2][80];
  dest->name[0] = '\0';
  dest->age[0] = -1;
  printf("enter the names and age\n");
  for (int i=0; i<2; i++) {
    if (fgets(buffer[i], sizeof buffer[i], stdin) == NULL) {
      return EOF; // stdin was closed, no more input (or input error)
    }
    buffer[i][strcspn(buffer[i], "\r\n")] = '\0'; // lop off potential trailing \n
  }
  // parse the 2 buffers: MANY options here - something simple for now.
  if (sscanf(buffer[0], " %14[-'A-Za-z ]", dest->name) != 1) {
    return 0;
  }
  if (sscanf(buffer[1], "%d", &dest->age) != 1) {
    return 0;
  }
  return 1;
}


int i;
struct student st[3];
for (i = 0 ; i < sizeof(st) / sizeof(st[0]) ; i++) {
  if (Scan_student(&st[i]) != 1) break;
}

Upvotes: 2

Iharob Al Asimi
Iharob Al Asimi

Reputation: 53026

If you input both the name and the age in a single line, then that's the normal behavior because fgets() will read the whole line until 9 bytes (in your case) are read or a '\n' is found.

You need one of the two, for instance you could only use fgets() like this

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

struct student
{
    char name[100];
    int age;
};


int main()
{
    int i;
    struct student s[1];
    for (i = 0 ; i < sizeof(s) / sizeof(*s) ; i++)
    {
        char number[100];
        char *unconverted;
        printf("Name > ");
        fgets(s[i].name, sizeof(s[i].name), stdin);
        if ((unconverted = strchr(s[i].name, '\n')) != NULL)
            *unconverted = '\0'; // Remove the trailing '\n'
        printf("Age  > ");
        fgets(number, sizeof(number), stdin);
        s[i].age = strtol(number, &unconverted, 10);
        if ((*unconverted != '\0') && (*unconverted != '\n'))
            s[i].age = -1; // Invalid value indicating input error
    }

    for (i = 0 ; i < sizeof(s) / sizeof(*s) ; i++)
        printf("Name: %s\nAge : %d\n", s[i].name, s[i].age);
    return 0;
}

Or scanf() only

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

struct student
{
    char name[100];
    int age;
};


int main()
{
    int i;
    struct student s[1];
    for (i = 0 ; i < sizeof(s) / sizeof(*s) ; i++)
    {
        printf("Name Age > ");
        if (scanf("%99s%d", s[i].name, &s[i].age) != 2)
        {
            fprintf(stderr, "input error\n");
            s[i].name[0] = '\0';
            s[i].age = -1;
        }
    }

    for (i = 0 ; i < sizeof(s) / sizeof(*s) ; i++)
        printf("Name: %s\nAge : %d\n", s[i].name, s[i].age);
    return 0;
}

and you can then input both name and age in a single line.

The fgets() method is better because you don't need to deal with the '\n' that scanf() doesn't pick up from stdin. A combination would work if you are careful to force the user to input the values in separate lines.

Upvotes: 1

Simon
Simon

Reputation: 19

To be honest fgets() isn't required here as you've specified to read from stdin you should simply use gets() this reads from stdin by default. If your moving between a scanf() statement and a gets() statement you should use fflush(stdin) to clear out the input stream, example below:

scanf("%99s", s[i].name);
fflush(stdin);
gets(s[i].age);

In general your better sticking with either scanf() or gets() and not combining them.

Upvotes: -1

Related Questions