Input & Output: printf, scanf & fgets
Almost every program talks to its user: it prints results and reads input. In C, the header stdio.h provides the tools: printf for formatted output, scanf for formatted input, and fgets for reading a whole line of text safely.
In this lesson you will learn to format output neatly, read numbers and text, check that the input was valid, and avoid the input bugs that catch almost every C beginner.
printf and format specifiers
printf prints a format string. Each % code in it, called a format specifier, is replaced by the next value in the list after the string. The specifier must match the type of the value.
| Specifier | Type | Example output |
|---|---|---|
%d | int | 42 |
%u | unsigned int | 42 |
%ld, %lld | long, long long | 5000000000 |
%zu | size_t | 8 |
%f | double (and float) | 3.141590 |
%e | double in scientific form | 3.141590e+00 |
%c | char | Q |
%s | string | Pune |
%x, %X, %o | unsigned int in hex or octal | ff, FF, 377 |
%p | pointer (memory address) | 0x7ffd... |
%% | a literal % sign | % |
You can add a width (the minimum number of characters), a precision (digits after the decimal point) and flags between the % and the letter:
#include <stdio.h>
int main(void)
{
int n = 255;
double price = 3.14159;
printf("[%d] [%5d] [%-5d] [%05d] [%+d]\n", n, n, n, n, n);
printf("[%x] [%X] [%#x] [%o]\n", n, n, n, n);
printf("[%f] [%.2f] [%8.3f] [%e]\n", price, price, price, price);
printf("[%c] [%s] [%10s] [%-10s]\n", 'Q', "Pune", "Pune", "Pune");
printf("Discount: 15%%\n");
return 0;
}[255] [ 255] [255 ] [00255] [+255] [ff] [FF] [0xff] [377] [3.141590] [3.14] [ 3.142] [3.141590e+00] [Q] [Pune] [ Pune] [Pune ] Discount: 15%
%5d pads the number to 5 characters on the left, - pads on the right instead, 0 pads with zeros, + always shows the sign, and # adds the 0x prefix. %f shows 6 decimal places unless you give a precision such as %.2f. The square brackets are only there so you can see the padding.
Printing neat tables
Widths make columns line up, which is perfect for bills and reports:
#include <stdio.h>
int main(void)
{
printf("%-12s %5s %10s\n", "Item", "Qty", "Amount");
printf("%-12s %5d %10.2f\n", "Notebook", 4, 4 * 45.0);
printf("%-12s %5d %10.2f\n", "Gel pen", 10, 10 * 12.5);
printf("%-12s %5d %10.2f\n", "Geometry box", 1, 149.0);
printf("%-12s %5s %10.2f\n", "Total", "", 4 * 45.0 + 10 * 12.5 + 149.0);
return 0;
}Item Qty Amount Notebook 4 180.00 Gel pen 10 125.00 Geometry box 1 149.00 Total 454.00
Text is left-aligned with %-12s and numbers are right-aligned with %5d and %10.2f, the usual layout for tables. Special characters in strings are written as escape sequences: \n (new line), \t (tab), \\ (a backslash), \" (a double quote inside a string) and \' (a single quote).
Reading numbers with scanf
scanf reads input and converts it using format specifiers, much like printf in reverse. Two details matter. First, you pass the address of each variable with &, so scanf knows where to store the value. Second, scanf returns the number of values it successfully read, and you should always check it.
#include <stdio.h>
int main(void)
{
int age;
double height;
printf("Enter your age and height in metres: ");
if (scanf("%d %lf", &age, &height) != 2) {
printf("Please enter two numbers.\n");
return 1;
}
printf("Next year you will be %d.\n", age + 1);
printf("Your height is %.2f m.\n", height);
return 0;
}When Aarav types 21 1.72 and presses Enter, the screen shows:
Enter your age and height in metres: 21 1.72 Next year you will be 22. Your height is 1.72 m.
If he types twenty, scanf cannot convert it, returns 0, and the program prints "Please enter two numbers." The if statement runs its block only when the condition is true; you will study it in if, else if & else. Note that scanf needs %lf for a double, while printf uses %f for both float and double.
The leftover newline problem
When you press Enter, the newline character '\n' goes into the input too. scanf("%d", ...) reads the number but leaves that '\n' waiting. If the next input function reads a line, it finds the newline at once and returns an empty line, without waiting for the user.
int roll;
char name[40];
printf("Roll number: ");
scanf("%d", &roll); // reads 17, leaves '\n' behind
printf("Name: ");
fgets(name, sizeof name, stdin); // reads only the leftover '\n'Beginners often "fix" this with tricks such as an extra getchar(). A cleaner rule is: read every line of input with fgets, then convert it. The next two sections show how.
Reading a line safely with fgets
char name[40]; creates space for up to 40 characters; this is a character array, explained in Strings. fgets(buffer, size, stdin) reads one line from the keyboard (stdin, the standard input) into it. It never writes more than size - 1 characters plus the closing '\0', so it cannot overflow the array. It keeps the '\n' at the end if there was room, so you usually remove it.
#include <stdio.h>
#include <string.h>
int main(void)
{
char name[40];
printf("Full name: ");
if (!fgets(name, sizeof name, stdin)) {
return 1; // no input at all
}
name[strcspn(name, "\n")] = '\0'; // remove the newline, if any
printf("Welcome, %s!\n", name);
printf("Your name has %zu characters.\n", strlen(name));
return 0;
}When Meera types Meera Nair, the output is:
Full name: Meera Nair Welcome, Meera Nair! Your name has 10 characters.
fgets returns a null pointer (a special "nothing" value) when no input could be read, and ! checks for that. strcspn(name, "\n") gives the position of the first newline, or the string's length if there is none, so writing '\0' there removes the newline safely. Unlike scanf("%s", ...), fgets also keeps spaces, so it reads full names correctly.
The safe pattern: fgets, then sscanf
To read a number safely, read the line with fgets and then convert it with sscanf, which works like scanf but reads from a string:
#include <stdio.h>
int main(void)
{
char line[64];
int quantity;
printf("How many samosas? ");
if (!fgets(line, sizeof line, stdin) || sscanf(line, "%d", &quantity) != 1) {
printf("That was not a number.\n");
return 1;
}
printf("Bill: Rs %d\n", quantity * 15);
return 0;
}How many samosas? 6 Bill: Rs 90
Every line is consumed completely, so no newline is ever left behind, and bad input is detected. For stricter number checks, strtol from stdlib.h also tells you exactly where the number ended; you will use it in Command-line Arguments.
Other handy functions
puts("text") prints a string and adds a newline automatically. putchar('A') prints one character, and getchar() reads one character, returning the special value EOF when input ends. If a prompt does not appear before the program waits for input, which can happen in some IDE consoles, call fflush(stdout); after the printf.
Common mistakes
- Forgetting
&inscanf.scanf("%d", age)passes the value instead of the address, and the program usually crashes.-Wallwarns about it. %ffor adoubleinscanf. Use%lffordoubleinscanf;%fis forfloat.- Not checking the return value. If the user types letters, the variable keeps its old, possibly uninitialised value.
- Mixing
scanfandfgets. The leftover newline makesfgetsreturn an empty line. Usefgetsfor all input. - Wrong specifier in
printf.%dwith adoubleprints garbage. Match every specifier to its type.
Key points
printfreplaces each format specifier with the next value; the specifier must match the type.- Width, precision and flags (
%-12s,%10.2f,%05d) format tables and money neatly. scanfneeds the address of each variable (&x) and returns how many values it read; always check it.scanfleaves the newline in the input; mixing it withfgetscauses skipped input.- The safe pattern is
fgetsto read a whole line, thensscanforstrtolto convert it. getswas removed in C11; never read a string without a size limit.
Exercise
Write a program for a cricket academy in Rohtak that asks for a player's name and then their runs in the last match. Read both with fgets, remove the newline from the name, convert the runs with sscanf, and print a message such as Kabir Singh scored 64 runs. If the runs are not a number, print an error and return 1.
Show solution
#include <stdio.h>
#include <string.h>
int main(void)
{
char name[50];
char line[32];
int runs;
printf("Player name: ");
if (!fgets(name, sizeof name, stdin)) {
return 1;
}
name[strcspn(name, "\n")] = '\0';
printf("Runs: ");
if (!fgets(line, sizeof line, stdin) || sscanf(line, "%d", &runs) != 1) {
printf("Runs must be a number.\n");
return 1;
}
printf("%s scored %d runs.\n", name, runs);
return 0;
}Player name: Kabir Singh Runs: 64 Kabir Singh scored 64 runs.
Both inputs are read as whole lines, so the newline after the name never disturbs the second read.
Quick Quiz
1. Which call correctly reads an int into the variable marks?
scanf needs the address of the variable, which &marks gives.
2. What does printf("[%6.2f]", 3.14159); print?
The precision .2 keeps two decimal places and the width 6 pads 3.14 with two spaces on the left.
3. Why does fgets sometimes return an empty line right after scanf("%d", &x)?
The leftover '\n' ends the line immediately, so fgets reads nothing else.
4. Which function was removed from C in C11 because it could not limit input size?
gets had no buffer size and caused overflows, so C11 removed it.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
