cscanffgetsgets

The fgets statement doesnt get executed


Here is my code for a simple programme in C for storing details of students in a struct. The problem that I am having is that the fgets statement for taking in the name string doesn't get executed and directly prints my next printf statement The error line is marked. Also, the rest of the code works perfectly. Now I have tried the same code with changing 'fgets' to 'gets' and 'scanf'/'scanf_s' too, but I am having the same result.

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

struct Students
{
    char name[30];
    int age;
    int roll_number;
    char address[30];
}std[3];

void names(struct Students std[], int n)
{
    int i = 0;
    printf("Here are the names of students whose age is 14");
    while (i < n)
    {
        if (std[i].age == 14)
        {
            printf("%s", std[i].name);
        }
        i++;
    }
}
void everoll(struct Students std[], int n)
{
    int i = 0;
    printf("Here are the names of students whose age is 14");
    while (i < n)
    {
        if (std[i].roll_number % 2 == 0)
        {
            printf("%s", std[i].name);
        }
        i++;
    }
}
void details(struct Students std[], int n, int a)
{
    int i = 0;

    while (i < n)
    {
        if (std[i].roll_number == a)
        {
            printf("Here are the details\n");

            printf("The name is : %s\nThe address is : %s\nThe age is : %d", std[i].name, std[i].address, std[i].age);
        }
        i++;
    }
}
int main()
{
    printf("Enter the details of the students:\n\n");
    for (int i = 0; i < 3; i++)
    {
        printf("Enter the roll number of the student:\n");
        scanf_s("%d", &std[i].roll_number);
        printf("Enter the age of the student:\n");
        scanf_s("%d", &std[i].age);
        printf("Enter the name of the student:\n");  
        fgets(std[i].name, 30, stdin);                      //error line
        printf("Enter the address of the student:\n");      //This line gets executed dirctly
        fgets(std[i].address, 30, stdin);                  //This fgets works fine
    }
    eve:
    printf("Which operation do you want to perform \n1) finding students with age 14\n2) Printing names of students with even roll number\n3) Finding details by enetering the roll number\n");
    int d;
    scanf_s("%d", &d);
    int b;
    switch (d)
    {
    case 1:
        names(std, 3);
        break;
    case 2:
        everoll(std, 3);
        break;
    case 3:
        printf("Enter the roll number:\n");
        scanf_s("%d", &b);
        details(std, 3, b);
        break;
    default:
        printf("Wrong number entered");
        goto eve;
    }
    return 0;
}

Solution

  • The function scanf or scanf_s does not generally consume a whole line of input, unless you explicitly instruct it to.

    For example, if the user enters the input

    5\n
    20\n
    John Doe\n
    200 Main Street\n
    

    where \n is the newline character that is generated when the user presses the ENTER key, then the function call

    scanf_s("%d", &std[i].roll_number);
    

    will match and consume only the character 5, but leave the newline character on the input stream.

    The function call

    scanf_s("%d", &std[i].age);
    

    will then consume all leading whitespace characters, so it will consume the newline character on the first line, and then match and consume the 20 from the second line. It will not consume the newline character on the second line.

    The function call

    fgets(std[i].name, 30, stdin);
    

    will now read everything on the input stream up to and including the next newline character. Since the next character is the newline character from the second line (which was left there by scanf_s), that is all that this function call will read from the input stream, and it will write that character into std[i].name. Therefore, this function call will not read anything from the third line.

    The function call

    fgets(std[i].address, 30, stdin);
    

    will now again read everything on the input stream up to and including the next newline character. Therefore, it will read the entire contents of line 3 of the input (which is John Doe) into std[i].address, including the newline character.

    So, to summarize, your program has the following issue:

    The second call to scanf_s will not consume the newline character, so that the first call to fgets will only read the newline character from line 2, although you want it to read the entire line 3 instead. As a consequence, line 3 is read by the second fgets function call, although you intend it to be read by the first fgets call instead.

    Therefore, the simplest fix to your problem would be to consume the remainder of the line (including the newline character) after the second scanf_s statement, before the first fgets statement.

    One way to accomplish this is to use the following loop:

    int c;
    
    do
    {
        c = getchar();
    
    } while ( c != EOF && c != '\n' );
    

    A more compact way of writing this is:

    for ( int c; ( c = getchar() ) != EOF && c != '\n' )
        ;
    

    You can also use scanf for this purpose:

    //consume and discard everything up to the next newline character
    scanf( "%*[^\n]" );
    
    //consume and discard the newline character
    scanf( "%*c" );
    

    However, instead of using scanf or scanf_s to read partial lines, it would be more intuitive and less error-prone to always read exactly one whole line of input. Therefore, I would recommend using fgets for reading all 4 lines if input. You can then use the function strtol to convert a string into an integer:

    char line[500];
    
    printf("Enter the roll number of the student:\n");
    fgets( line, sizeof line, stdin );
    std[i].roll_number = strtol( line, NULL, 10 );
    
    printf("Enter the age of the student:\n");
    fgets( line, sizeof line, stdin );
    std[i].age = strtol( line, NULL, 10 );
    
    printf( "Enter the name of the student:\n" );
    fgets( std[i].name, sizeof std[i].name, stdin );
    
    printf("Enter the address of the student:\n" );
    fgets( std[i].address, sizeof std[i].address, stdin );