Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why scanf("%d", [...]) does not consume '\n'? while scanf("%c") does?

Here, I saw this statement in the accepted answer:

Most of the conversion specifiers skip leading whitespace including newlines but %c does not.

For me it is not clear the rationale under this different behaviors, I would have expected a uniform one (e.g. always skipping or never).

I came into this kind of problem with a piece of C code like this:

#include "stdio.h"

int main(void){

    char ch;
    int actualNum;

    printf("Insert a number: ");
    scanf("%d", &actualNum);
    // getchar();

    printf("Insert a character: ");
    scanf("%c", &ch);

    return 0;
}

Swapping the two scanfs solves the problem, as well as the (commented) getchar, otherwise the '\n' of the first insertion would be consumed by the second scanf with %c. I tested on gcc both on linux and windows, the behavior is the same:

gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

So my question is: Why does %d and %c behave differently w.r.t. '\n' in scanf?

like image 899
Alessandro S. Avatar asked Nov 07 '12 17:11

Alessandro S.


3 Answers

It is consistent behavior, you're just thinking about it wrong. ;)

scanf("%c", some_char); // reads a character from the key board.
scanf("%d", some_int);  // reads an integer from the key board.

So if I do this:

printf("Insert a character: ");
scanf("%c", &ch);                // if I enter 'f'. Really I entered 'f' + '\n'
                                 // scanf read the first char and left the '\n'
printf("Insert a number: ");
scanf("%d", &actualNum);      // Now scan if is looking for an int, it sees the '\n'
                              // still, but that's not an int so it waits for the 
                              // the next input from stdin

It's not that it's consuming the newline on its own in this case. Try this instead:

char ch;
char ch2;
printf("Insert a character: ");
scanf("%c", &ch);
printf("Insert another character: ");
scanf("%c", &ch2); 

It will "skip" the second scanf() because it reads in the '\n' at that time. scanf() is consistent and you MUST consume that '\n' if you're going to use it correctly.

like image 164
Mike Avatar answered Nov 18 '22 02:11

Mike


From the horse's mouth:

7.21.6.2 The fscanf function

...
5 A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character (which remains unread), or until no more characters can be read. The directive never fails.
...
7 A directive that is a conversion specification defines a set of matching input sequences, as described below for each specifier. A conversion specification is executed in the following steps:

8 Input white-space characters (as specified by the isspace function) are skipped, unless the specification includes a [, c, or n specifier. 284)

9 An input item is read from the stream, unless the specification includes an n specifier. An input item is defined as the longest sequence of input characters which does not exceed any specified field width and which is, or is a prefix of, a matching input sequence. 285) The first character, if any, after the input item remains unread. If the length of the input item is zero, the execution of the directive fails; this condition is a matching failure unless end-of-file, an encoding error, or a read error prevented input from the stream, in which case it is an input failure.
284) These white-space characters are not counted against a specified field width.
285) fscanf pushes back at most one input character onto the input stream. Therefore, some sequences that are acceptable to strtod, strtol, etc., are unacceptable to fscanf.

Emphasis added by me.

Whitespace is not part of a valid integer string, so it makes sense for the %d conversion specifier to skip any leading whitespace. However, whitespace may be valid on its own, so it makes sense for the %c conversion specifier to not skip it.

Per clause 5 above, if you put a blank space in the format string prior to the %c directive, all leading whitespace will be skipped:

scanf(" %c", &ch);
like image 10
John Bode Avatar answered Nov 18 '22 04:11

John Bode


It is because whitespace can never be an integer, but whitespace is made up of characters. If you specifically want a character try something like

scanf("%c", &ch );
while(isspace(c))
    scanf("%c" , &ch);

Or use !isalnum() if you want to allow only letters and numbers or !isalpha() for only letters.

like image 2
asbumste Avatar answered Nov 18 '22 02:11

asbumste