Scanf and Characters
In C, scanf interprets a format string and converts input according to each conversion specification. Its treatment of whitespace depends on the conversion being used:
%creads exactly one character and does not skip leading whitespace.%d,%f, and other numeric conversions skip leading whitespace.%sskips leading whitespace, reads a sequence of non-whitespace characters, and appends'\0'.
Whitespace includes spaces, tabs, and newline characters. This distinction becomes important when numeric and character input are mixed in the same program.
The leftover-newline problem
Consider this program:
#include <stdio.h>
int main(void) {
int age;
char ch;
scanf("%d", &age);
scanf("%c", &ch);
printf("age=%d, ch=%c\n", age, ch);
return 0;
}
With the following input:
18
A
The first call reads 18, but the newline produced by pressing Enter remains in the input stream. The second call, scanf("%c", &ch), immediately consumes that newline because %c does not skip whitespace. The A is left unread.
As a result, the character assigned to ch is '\n', not 'A'.
Common ways to handle the problem
Skip whitespace before %c
A space in the format string tells scanf to skip any amount of whitespace before the next conversion:
scanf(" %c", &ch);
This is often the simplest solution when the program expects a non-whitespace character. However, it cannot be used when whitespace itself is meaningful, because the whitespace directive discards spaces, tabs, and newlines along the way.
Consume the pending newline
Another approach is to read and discard the newline with getchar:
scanf("%d", &age);
getchar();
scanf("%c", &ch);
This works when exactly one unwanted newline is pending. If several characters remain on the same line, the entire line can be discarded instead:
int c;
while ((c = getchar()) != '\n' && c != EOF) {
/* Discard the rest of the line. */
}
The return value of getchar() should be stored in an int, not a char, because EOF may not fit in the range of unsigned char.
Prefer line-based input
A robust alternative is to read complete lines with fgets and then parse each line with sscanf:
#include <stdio.h>
int main(void) {
char line[100];
int age;
char grade;
printf("Enter your age: ");
if (fgets(line, sizeof line, stdin) == NULL) {
return 1;
}
if (sscanf(line, "%d", &age) != 1) {
return 1;
}
printf("Enter your grade: ");
if (fgets(line, sizeof line, stdin) == NULL) {
return 1;
}
if (sscanf(line, " %c", &grade) != 1) {
return 1;
}
printf("age=%d, grade=%c\n", age, grade);
return 0;
}
Here, fgets reads from stdin, while sscanf parses an existing string. This avoids unpredictable interaction between line-oriented and token-oriented input.
Reading strings safely
The %s conversion is suitable for a single word or token:
char name[20];
scanf("%19s", name);
The array name already represents its address, so &name is incorrect. The width 19 is also important: %s reserves one byte for the terminating null character. Reading %19s into a 20-byte array therefore leaves room for '\0'.
Without a width limit, an overly long input can overflow the destination array:
char small[10];
char large[100];
/* Dangerous: no size limit is imposed. */
scanf("%s", small);
A bounded conversion is safer:
scanf("%9s", small);
For text that may contain spaces, use fgets:
char name[100];
if (fgets(name, sizeof name, stdin) != NULL) {
printf("Hello, %s", name);
}
Unlike scanf("%s", ...), fgets preserves spaces and includes the newline in the stored string. The newline can be removed with strcspn:
#include <string.h>
name[strcspn(name, "\n")] = '\0';
Choosing the right input function
Different input tasks call for different tools:
- Use
scanf("%c", &c)when the next character matters, including whitespace. - Use
scanf(" %c", &c)when leading whitespace should be ignored. - Use
getchar()for one character, storing its result in anintso thatEOFcan be detected. - Use a width-limited
%sconversion for a single word. - Use
fgetsfor a complete line that may contain spaces. - Use
fgetsfollowed bysscanfwhen structured input is easier to process one line at a time.
Always check the result
scanf returns the number of successfully assigned items. This allows malformed or missing input to be handled explicitly:
int age;
if (scanf("%d", &age) != 1) {
fprintf(stderr, "Invalid age\n");
return 1;
}
A return value of 0 means that the input did not match the expected conversion, while EOF indicates that the stream ended before any item was assigned.
A complete mixed-input example
#include <stdio.h>
int main(void) {
int age;
char grade;
char name[20];
printf("Enter your age: ");
if (scanf("%d", &age) != 1) {
fprintf(stderr, "Invalid age\n");
return 1;
}
printf("Enter your grade: ");
if (scanf(" %c", &grade) != 1) {
fprintf(stderr, "Invalid grade\n");
return 1;
}
printf("Enter your name: ");
if (scanf("%19s", name) != 1) {
fprintf(stderr, "Invalid name\n");
return 1;
}
printf("age=%d, grade=%c, name=%s\n", age, grade, name);
return 0;
}
The space in " %c" removes the newline left by the numeric conversion, while %19s safely limits the name to fit inside its array.
Understanding whitespace handling and the state of the input stream is essential for reliable C input. The central rule is simple: %c reads the next character exactly as it appears, so any pending whitespace must either be intentional, skipped explicitly, or removed with another input function.