c-foundations Lesson 5 22 min read

How Do I Store and Display Data with Variables and I/O?

printf, scanf, format specifiers, and why C makes you do what Java did automatically

Reading: C Text: Ch. 2 §2–3 (pp. 74–85), Ch. 2 §5 (pp. 91–100), Ch. 2 §6 (pp. 100–117)

After this lesson, you will be able to:

  • Use printf with format specifiers (%d, %f, %.2f, %c, %s) to produce formatted output
  • Use field width, alignment, and zero-padding in printf format strings
  • Use scanf with the & operator to read typed values from user input
  • Check scanf’s return value to detect invalid input
  • Clear the input buffer to prevent the newline buffer problem between scanf calls
  • Use explicit type casting to avoid integer division truncation

Why Can’t I Just println?

In Java:

int age = 20;
System.out.println("I am " + age + " years old.");

Java automatically converts age to a string and concatenates. Easy. Now try the C equivalent:

int age = 20;
printf("I am %d years old.\n", age);

That %d is a format specifier — you must explicitly tell printf “an integer goes here.” If you use the wrong one, you don’t get a compile error — you get garbage output or a crash. Welcome to C.

Check Your Understanding
In Java, you write System.out.println("Age: " + age). Why doesn't printf("Age: " + age) work in C?
A C doesn't have a printf function
B C has no string concatenation with + — you must use format specifiers like printf("Age: %d", age)
C It does work — C and Java handle strings the same way
D You need to use println instead of printf
Answer: B. C strings are not objects — they're just arrays of characters. The + operator on strings does pointer arithmetic, not concatenation. C's printf uses format specifiers (%d, %f, %s) to embed values into output. This is more explicit but gives you precise control over formatting.

Output, Input, and Everything Between

printf: Formatted Output

printf (print formatted) takes a format string and values to substitute:

printf("format string", value1, value2, ...);

Essential format specifiers:

Specifier Type Example Output
%d int printf("%d", 42) 42
%f double printf("%f", 3.14) 3.140000
%.2f double (2 decimals) printf("%.2f", 3.14) 3.14
%c char printf("%c", 'A') A
%s string (char*) printf("%s", "hello") hello
%ld long printf("%ld", 123456L) 123456
%x int (hex) printf("%x", 255) ff
%% literal % printf("100%%") 100%

Common Pitfall: The format specifier MUST match the type of the value. printf("%d", 3.14) doesn’t print 3 — it prints garbage because it interprets the double’s bytes as an integer. The compiler won’t stop you (without -Wall).

Check Your Understanding
What does printf("Score: %08.2f\n", 3.14) output?
A Score: 3.14
B Score: 00003.14
C Score: 3.14
D Score: 3.140000
Answer: B. The format %08.2f means: total width 8, 2 decimal places, zero-padded. The value 3.14 takes 4 characters (3.14), so the remaining 4 positions are filled with zeros. Option D would be the output of %f with no width or precision specified (default is 6 decimal places).

Escape Sequences

Sequence Meaning
\n Newline
\t Tab
\\ Literal backslash
\" Literal quote
\0 Null terminator (end of string)

Unlike Java’s println, printf does NOT automatically add a newline. You must include \n yourself.

Field Width and Alignment

Control how values are formatted:

printf("%10d\n", 42);        //         42  (right-aligned, width 10)
printf("%-10d\n", 42);       // 42          (left-aligned, width 10)
printf("%08d\n", 42);        // 00000042    (zero-padded)
printf("%8.2f\n", 3.14159);  //     3.14    (width 8, 2 decimals)

This is essential for building aligned tables in your lab output.

scanf: Reading Input

scanf reads formatted input from the keyboard:

int age;
printf("Enter your age: ");
scanf("%d", &age);

Key Insight: Notice the & before age? That’s the address-of operator. scanf needs to know where in memory to store the value, so you pass the variable’s address, not the variable itself. Forgetting & is one of the most common C bugs — and it often causes a crash.

scanf format specifiers:

Specifier Type Note
%d int  
%f float NOT double!
%lf double l for “long float”
%c char  
%s string Stops at whitespace

Common Pitfall: scanf uses %lf for double, but printf uses %f. Yes, they’re different. This catches everyone at least once. When reading a double: scanf("%lf", &x). When printing a double: printf("%f", x).

Check Your Understanding
What happens if you write scanf("%d", age) instead of scanf("%d", &age)?
A It compiles but likely crashes — scanf interprets the value of age as a memory address
B Compilation error — the code won't compile
C It works fine — the & is optional
D It reads the input but stores it in a temporary variable instead
Answer: A. Without &, scanf receives the current value of age (which could be garbage if uninitialized) and tries to write to that address in memory. This is undefined behavior — usually a segmentation fault (crash). The code compiles because C doesn't enforce pointer type safety strictly, but -Wall will warn you.

The Temperature Converter

Let’s build a complete program — a Fahrenheit to Celsius converter:

#include <stdio.h>

int main(void)
{
    double fahrenheit;
    double celsius;

    printf("Enter temperature in Fahrenheit: ");
    scanf("%lf", &fahrenheit);

    celsius = (fahrenheit - 32.0) * 5.0 / 9.0;

    printf("%.1f°F = %.1f°C\n", fahrenheit, celsius);

    return 0;
}

Compile and run:

gcc -Wall -o tempconv tempconv.c
./tempconv
Enter temperature in Fahrenheit: 212
212.0°F = 100.0°C

Notice that both variables are declared at the top of the function, before any executable statements. Modern C (C99+) lets you declare variables anywhere, but Dr. Steiner’s convention is top-of-block declarations — declare everything at the top of each {...} block. This makes it easy to see at a glance what variables a function uses.

From Java: In Java, you’d use Scanner scanner = new Scanner(System.in) and scanner.nextDouble(). In C, scanf("%lf", &fahrenheit) does the same thing — but you must specify the format and pass the address. There’s no Scanner object, no .nextDouble() method. It’s more manual, but it’s closer to how the hardware actually works. Fun fact: Java’s System.out.printf() uses the same format specifiers as C’s printf — Java borrowed the syntax directly.

Checking scanf’s Return Value

scanf returns the number of items successfully read:

int result = scanf("%d", &age);
if (result != 1)
{
    printf("Invalid input!\n");
    return 1;
}

If the user types “hello” when you expect a number, scanf returns 0 (no items read). Always check the return value in production code.

The Newline Buffer Problem

This is one of the trickiest bugs for beginners:

int age;
char initial;

printf("Enter age: ");
scanf("%d", &age);

printf("Enter initial: ");
scanf("%c", &initial);    // BUG: reads the leftover newline!

When you type 20 and press Enter, scanf("%d") reads 20 but leaves the \n in the input buffer. The next scanf("%c") immediately reads that \n instead of waiting for your input.

The fix:

scanf("%d", &age);
while (fgetc(stdin) != '\n') {}    // Clear the input buffer

scanf("%c", &initial);              // Now this works correctly

Common Pitfall: When mixing scanf calls for different types, the newline from pressing Enter stays in the input buffer. Use while (fgetc(stdin) != '\n') {} after reading numeric values to clear the buffer before reading characters.

Integer Division

One more gotcha from Java carries over but hits harder in C:

Predict: What do you think average equals after this code runs? Work it out on paper before reading the answer.

int sum = 7;
int count = 2;
double average = sum / count;    // average = 3.0, NOT 3.5!

Both operands are int, so C performs integer division (truncating the decimal). Fix with a cast:

double average = (double)sum / count;    // average = 3.5

The Trick: When computing an average or any division that should produce a decimal result, cast the numerator to double before dividing: (double)sum / count. Casting either operand promotes the whole expression to floating-point.

Why does this matter?

The printf/scanf format string system is how C handles all text output and input. There’s no toString() method, no string concatenation with +, no automatic type conversion. If you can’t write a correct format string, you can’t display results or read user input — and that means you can’t complete any lab in this course.

Check Your Understanding
You write scanf("%d", &age) and the user types hello. What happens?
A The program crashes with a segmentation fault
B age is set to 0
C The program asks again for input automatically
D scanf returns 0 (no items read) and age is unchanged — "hello" stays in the input buffer
Answer: D. scanf can't parse "hello" as an integer, so it reads nothing, returns 0, and leaves the input in the buffer. The variable age keeps whatever value it had before (garbage if uninitialized). This is why you should check scanf's return value: if (scanf("%d", &age) != 1). The input stays in the buffer, which can cause infinite loops if you retry without clearing it.
Quick Check: What's wrong with scanf("%f", &temperature) when temperature is a double?

%f in scanf reads a float, not a double. For double, you must use %lf. This mismatch writes 4 bytes into an 8-byte variable, causing incorrect values. (Confusingly, printf uses %f for both float and double.)

Quick Check: Why does scanf need & before the variable?

scanf needs to store a value into the variable’s memory location. The & (address-of) operator gives scanf the memory address where it should write the value. Without &, scanf receives the variable’s value (which it interprets as a memory address) — usually causing a crash.

Quick Check: What does 7 / 2 evaluate to in C? How about 7.0 / 2?

7 / 2 evaluates to 3 (integer division, truncated). 7.0 / 2 evaluates to 3.5 because one operand is a double, promoting the division to floating-point.


You Control the Format

In Java, System.out.println handles formatting automatically. In C, you control every detail: how many decimal places, how wide the columns, whether to pad with zeros. That’s more work, but it’s also more power — essential for producing professional-looking lab output.

Next lesson: operators and expressions. You’ll learn arithmetic, comparison, logical, and bitwise operators — plus the infamous = vs. == trap that catches every C beginner.

Sanity Check: Before moving on, make sure you can: (1) write a program that reads a double with scanf and prints it with printf, (2) explain why & is needed in scanf, and (3) fix the newline buffer problem. These three skills are prerequisites for every lab from here on.

Big Picture: In Java, I/O is handled by objects (Scanner, System.out). In C, it’s handled by functions with format strings. This is your first taste of C’s philosophy: explicit control over everything, automatic help with nothing. The format string system also connects to security — format string vulnerabilities are a real class of exploits you’ll encounter in cybersecurity courses.