Input & Output: printf, scanf & fgets

C · Lesson 9 of 32 8 min read Updated

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.

SpecifierTypeExample output
%dint42
%uunsigned int42
%ld, %lldlong, long long5000000000
%zusize_t8
%fdouble (and float)3.141590
%edouble in scientific form3.141590e+00
%ccharQ
%sstringPune
%x, %X, %ounsigned int in hex or octalff, FF, 377
%ppointer (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:

C
#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;
}
Output
[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:

C
#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;
}
Output
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.

C
#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:

Output
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.

C
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.

C
#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:

Output
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:

C
#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;
}
Output
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 & in scanf. scanf("%d", age) passes the value instead of the address, and the program usually crashes. -Wall warns about it.
  • %f for a double in scanf. Use %lf for double in scanf; %f is for float.
  • Not checking the return value. If the user types letters, the variable keeps its old, possibly uninitialised value.
  • Mixing scanf and fgets. The leftover newline makes fgets return an empty line. Use fgets for all input.
  • Wrong specifier in printf. %d with a double prints garbage. Match every specifier to its type.

Key points

  • printf replaces 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.
  • scanf needs the address of each variable (&x) and returns how many values it read; always check it.
  • scanf leaves the newline in the input; mixing it with fgets causes skipped input.
  • The safe pattern is fgets to read a whole line, then sscanf or strtol to convert it.
  • gets was 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
C
#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;
}
Output
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?

2. What does printf("[%6.2f]", 3.14159); print?

3. Why does fgets sometimes return an empty line right after scanf("%d", &x)?

4. Which function was removed from C in C11 because it could not limit input size?

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