简体   繁体   中英

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

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:

J o h n Enter
"John\\n" is read by fgets() into s[0].name .

2 1 Enter
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 .

a r y Enter
"Mary\\n" is read by fgets() into s[2].name .

1 9 Enter
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;
}

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.

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.

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