Input / Output
How C programs talk to the outside world — printing to the screen with printf, reading from the keyboard with scanf, and understanding format specifiers.
Talking to the terminal
Think of your program as someone sitting at a desk. The terminal screen in front of them is standard output — where they write notes for others to see. The keyboard is standard input — where they receive messages coming in. printf is the act of writing on the screen. scanf is the act of reading what's been typed on the keyboard. Every C program, from "Hello World" to operating systems, uses these two channels.
In Python you used print("Hello") to display text and input("Name: ") to read from the keyboard. In C, the equivalents are printf("Hello\n") and scanf("%s", name). The concept is identical — but C requires one extra detail that Python hides from you: you must tell C exactly what type of data you are printing or reading.
This is done through format specifiers — short codes inside the format string that begin with %. When you write printf("%d", age), the %d is a placeholder that says "insert an integer here." When you write scanf("%f", &price), the %f says "read a float from the keyboard and store it."
Why the & in scanf? This is the most common beginner confusion. When you pass a variable to scanf, you are not giving it the value of the variable — you are giving it the memory address where scanf should write the result. The & operator means "give me the address of." You will understand this deeply when you study pointers in Topic 08. For now: always put & before variable names in scanf (except strings/arrays, which are already addresses).
What about getchar and putchar? These are the most basic I/O functions — they handle one character at a time. getchar() reads the next character from stdin and returns it as an int. putchar(c) writes one character to stdout. They are rarely used alone but they power all of C's higher-level I/O under the hood.
# Output — Python
print("Hello")
print("Age:", 25)
print(f"Score: {score:.2f}")
# Input — Python
name = input("Enter name: ")
age = int(input("Enter age: "))
/* Output — C */
printf("Hello\n");
printf("Age: %d\n", 25);
printf("Score: %.2f\n", score);
/* Input — C */
char name[50];
int age;
scanf("%s", name); /* no & for arrays */
scanf("%d", &age); /* & required here */
Python's print() adds a newline at the end automatically. C's printf() does not. You must always write \n yourself when you want to move to the next line. Forgetting \n means your next output will appear on the same line — a very common beginner mistake.
Where does this live in your code? All I/O functions come from the standard I/O library, which you import at the top of every C file with #include <stdio.h>. Without this line, the compiler will not know what printf and scanf are. The .h stands for "header file" — it declares function signatures so the compiler can check your usage.
The man page is your best friend. When you want to know exactly how a function works — what it returns, what format strings it accepts, what errors it can produce — consult the manual: run man 3 printf in the terminal. Section 3 is C library calls. This is a habit every professional C programmer has.
printf = write to screen, you control the format. scanf = read from keyboard, you specify the type AND give the address with &. Include <stdio.h> at the top. That's the whole picture — everything else is details about format specifiers, which you'll memorize quickly with practice.
printf, scanf, and format specifiers
Here is the annotated signature and usage of both functions:
/* printf — print formatted output to stdout */ int printf(const char *format, ...); ^ ^ ^ ^ | | | └── zero or more extra arguments (the values to print) | | └────────────────── format string: text + %specifiers | └─────────────────────────── function name └─────────────────────────────── returns int: number of characters printed printf("%d %f\n", 10, 10.5); ^ ^ ^ ^ (one extra argument per % specifier) /* scanf — read formatted input from stdin */ int scanf(const char *format, ...); ^ ^ ^ | | └── pointers to variables — use & for non-arrays | └──────────────────────────── function name └──────────────────────────────── returns int: number of items successfully read int x; float f; scanf("%d %f", &x, &f); ^ ^ ^ ampersand gives scanf the ADDRESS to write into
% specifiers in the format string must exactly match the number of extra arguments. One %d → one integer argument. Two specifiers %d %f → two arguments. Mismatch causes undefined behavior.
Format Specifiers — Complete Reference
| Specifier | Type it reads/prints | Example (printf) | Output |
|---|---|---|---|
%d |
Signed integer (int) |
printf("%d", 42); |
42 |
%u |
Unsigned integer (unsigned int) |
printf("%u", 42u); |
42 |
%f |
Float or double (decimal) | printf("%f", 3.14); |
3.140000 |
%.2f |
Float/double with 2 decimal places | printf("%.2f", 3.14159); |
3.14 |
%g |
Float/double, shorter representation | printf("%g", 3.14); |
3.14 |
%e |
Float/double in scientific notation | printf("%e", 3.14); |
3.140000e+00 |
%c |
Single character (char) |
printf("%c", 'A'); |
A |
%s |
String (char array ending in \0) |
printf("%s", "hi"); |
hi |
%p |
Pointer address (hexadecimal) | printf("%p", &x); |
0x7ffd12ab |
%x |
Unsigned integer as hexadecimal | printf("%x", 255); |
ff |
%ld |
Long integer (long) |
printf("%ld", 1234567L); |
1234567 |
%lf |
Double for scanf (use %f for printf) |
scanf("%lf", &d); |
reads double |
%zu |
size_t (result of sizeof) |
printf("%zu", sizeof(int)); |
4 |
%% |
Literal percent sign | printf("100%%"); |
100% |
For printf: use %f for both float and double — C automatically promotes float to double when passed to printf.
For scanf: use %f for float and %lf (lowercase L + f) for double. Getting this wrong silently corrupts data. This is a very common exam trick.
Escape Sequences
| Sequence | Meaning | Use case |
|---|---|---|
\n | Newline (move to next line) | End of every output line |
\t | Horizontal tab | Aligned columns |
\r | Carriage return (go to line start) | Overwrite current line |
\\ | Literal backslash | Print a backslash character |
\" | Literal double quote | Print " inside a string |
\0 | Null character (ASCII 0) | String terminator in C |
Width and Precision Formatting
/* Width: minimum field width */ printf("%8d", 42); // " 42" (right-aligned, padded to 8 chars) printf("%-8d", 42); // "42 " (left-aligned with -) printf("%08d", 42); // "00000042" (zero-padded) /* Precision: decimal places for floats */ printf("%.2f", 3.14159); // "3.14" printf("%.0f", 3.7); // "4" (rounds) printf("%8.2f", 3.14); // " 3.14" (width 8, 2 decimal places)
%[flags][width][.precision]specifier — flags are optional (- left-align, 0 zero-pad), width and precision are optional integers.
getchar and putchar
/* getchar — read one character from stdin, returns int */ int getchar(void); ^ returns EOF (-1) when no more input (Ctrl+D on Linux) /* putchar — write one character to stdout */ int putchar(int c); /* Classic pattern: echo all input back to output */ int c; while ((c = getchar()) != EOF) { putchar(c); } /* Why int not char? EOF is -1, which doesn't fit in a char on some systems */
getchar()'s return value in an int, not a char. On systems where char is unsigned, comparing a char to EOF (-1) can never be true — the loop runs forever. This is a classic C bug.
Complete programs you can compile and run
#include <stdio.h> /* Always needed for printf/scanf */
int main(void) {
int age = 21;
double height = 1.75;
char grade = 'A';
char name[] = "Alice";
/* %d = integer, %f = float/double, %c = char, %s = string */
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Height: %.2f\n", height); /* .2 = 2 decimal places */
printf("Grade: %c\n", grade);
/* Combining multiple values in one printf call */
printf("%s is %d years old, %.1fm tall, grade %c\n",
name, age, height, grade);
/* Width formatting: right-align numbers in columns */
printf("%10s | %5d | %8.2f\n", "Alice", 21, 1.75);
printf("%10s | %5d | %8.2f\n", "Bob", 19, 1.82);
return 0;
}
Age: 21
Height: 1.75
Grade: A
Alice is 21 years old, 1.8m tall, grade A
Alice | 21 | 1.75
Bob | 19 | 1.82
#include <stdio.h>
int main(void) {
int ftemp; /* fahrenheit temperature entered by user */
/* Prompt the user — printf does NOT flush newline automatically */
printf("Please enter a Fahrenheit temperature: ");
/* scanf reads one integer from stdin and stores it at &ftemp */
/* &ftemp = "the address of ftemp" — scanf writes INTO that address */
int n = scanf("%d", &ftemp);
/* Always check scanf's return value: it returns number of items read */
/* If the user types letters instead of a number, n will be 0 */
if (n != 1) {
printf("Error: please enter a whole number.\n");
return 1;
}
/* Formula: C = (F - 32) * 5 / 9 */
/* Integer division! (ftemp-32)*5/9 truncates — use doubles for exactness */
int ctemp = (ftemp - 32) * 5 / 9;
printf("%d Fahrenheit is %d Celsius\n", ftemp, ctemp);
return 0;
}
100 Fahrenheit is 37 Celsius
#include <stdio.h>
int main(void) {
int age;
float height;
char grade;
char name[50];
/* Read multiple values — space in format string skips whitespace */
printf("Enter age and height: ");
scanf("%d %f", &age, &height);
/* After scanf reads numbers, a newline '\n' remains in the buffer.
Read and discard it before reading a character with %c */
scanf(" %c", &grade); /* leading space in " %c" skips whitespace */
printf("Age: %d, Height: %.1f, Grade: %c\n", age, height, grade);
/* --- getchar / putchar demo --- */
printf("\nEcho program (type text, press Ctrl+D to stop):\n");
int c;
while ((c = getchar()) != EOF) { /* EOF = -1, returned on end-of-input */
putchar(c); /* write the same character to stdout */
}
printf("\n(End of input reached)\n");
return 0;
}
A
Age: 20, Height: 1.8, Grade: A
Echo program (type text, press Ctrl+D to stop):
hello
hello
(End of input reached)
Practice problems with solutions
Without running the code, predict exactly what each printf statement outputs.
#include <stdio.h>
int main(void) {
int x = 42;
float f = 3.14159f;
char c = 'Z';
printf("%d\n", x);
printf("%f\n", f);
printf("%.2f\n", f);
printf("%c has ASCII value %d\n", c, c);
printf("%10d|\n", x);
printf("%-10d|\n", x);
printf("%05d\n", x);
return 0;
}
42
3.141590
3.14
Z has ASCII value 90
42|
42 |
00042
%d prints 42. %f with no precision prints 6 decimal places by default → 3.141590. %.2f rounds to 2 places → 3.14. %c prints the character 'Z', %d on the same char prints its ASCII code (90). %10d right-aligns in a field of 10. %-10d left-aligns. %05d zero-pads to 5 digits.
The code below has three bugs — all related to scanf. Find and fix each one.
#include <stdio.h>
int main(void) {
int age;
double salary;
char name[50];
scanf("%d", age); /* Bug 1 */
scanf("%f", &salary); /* Bug 2 */
scanf("%s", &name); /* Bug 3 */
printf("Age: %d, Salary: %.2f, Name: %s\n", age, salary, name);
return 0;
}
#include <stdio.h>
int main(void) {
int age;
double salary;
char name[50];
scanf("%d", &age); /* Fix 1: need & — age is not a pointer */
scanf("%lf", &salary); /* Fix 2: double needs %lf in scanf, not %f */
scanf("%s", name); /* Fix 3: name is already a pointer (array) — no & */
printf("Age: %d, Salary: %.2f, Name: %s\n", age, salary, name);
return 0;
}
& before age. Without &, you pass the value of age (garbage, since uninitialised) as an address — this causes a segfault or undefined behavior.Bug 2: Using
%f for a double in scanf. In scanf, %f reads into a float (4 bytes) but salary is a double (8 bytes) — this corrupts memory. Use %lf for double in scanf.Bug 3: Adding
& before name. An array name already decays to a pointer to its first element — adding & gives a pointer-to-array, which has the wrong type. Just write name.
The lecture's Fahrenheit converter uses integer division, which truncates. Rewrite it to use double for exact results, accepting decimal Fahrenheit input and printing to 2 decimal places. Expected: 98.6°F → 37.00°C.
#include <stdio.h>
int main(void) {
double ftemp;
printf("Enter Fahrenheit temperature: ");
if (scanf("%lf", &ftemp) != 1) { /* %lf for double in scanf */
printf("Invalid input.\n");
return 1;
}
double ctemp = (ftemp - 32.0) * 5.0 / 9.0; /* use .0 literals to avoid int division */
printf("%.2f F = %.2f C\n", ftemp, ctemp);
return 0;
}
double instead of int. Read with %lf (scanf's double specifier). Use 32.0, 5.0, 9.0 (or cast) so arithmetic stays floating-point — if all literals are int, C does integer arithmetic and truncates before converting. Print with %.2f for 2 decimal places.
Write a program that reads characters one by one from stdin (using getchar) and counts how many vowels (a, e, i, o, u — lowercase only) appear before EOF. Print the count at the end.
#include <stdio.h>
int main(void) {
int c;
int vowel_count = 0;
while ((c = getchar()) != EOF) {
/* Check if c is a lowercase vowel */
if (c == 'a' || c == 'e' || c == 'i' ||
c == 'o' || c == 'u') {
vowel_count++;
}
}
printf("Vowel count: %d\n", vowel_count);
return 0;
}
c must be int, not char. EOF is -1 and does not fit in a char on all platforms. The assignment c = getchar() inside the condition is intentional — it reads, assigns, and checks all in one step. The outer parentheses are required: (c = getchar()) forces evaluation before != EOF.
Consider this code. What happens when the user types "hello" instead of a number? What does scanf return? How do you make the program handle this gracefully?
#include <stdio.h>
int main(void) {
int x;
scanf("%d", &x);
printf("You entered: %d\n", x);
return 0;
}
#include <stdio.h>
int main(void) {
int x;
int result = scanf("%d", &x);
/* scanf returns: number of items successfully matched and assigned.
If user types "hello", scanf cannot parse an integer.
result == 0 (matched zero items), x remains UNINITIALISED. */
if (result == 0) {
printf("Error: expected a number.\n");
return 1;
}
/* result == EOF (-1) if stdin was closed before any input */
if (result == EOF) {
printf("Error: end of input.\n");
return 1;
}
printf("You entered: %d\n", x);
return 0;
}
scanf("%d", &x), success = 1, failure = 0, end-of-file = EOF (-1). If scanf fails, x is left uninitialised — reading it is undefined behavior. Always check scanf's return value in production code. The man page (man 3 scanf) gives the full specification.
Key concepts to memorize
Test your understanding
double with exactly 3 decimal places?LO1double with scanf, the correct format specifier is %lf (not %f).LO1getchar() return when it reaches end-of-file?LO1printf("___", 255); to print 255 in hexadecimal (answer: the format specifier only, e.g. %d).LO1char c;
c = getchar();
if (c == EOF) {
printf("End of file\n");
}
#include to use printf and scanf?LO8