One Character at a Time

Underneath printf and scanf sit two humbler functions that move a single character: getchar reads the next character from input, putchar writes one to output. They are worth knowing on their own, they anchor a standard exam section, and they force two ideas into the open that the fancier functions hide: input is a stream of characters, and the end of that stream needs representing.

getchar and putchar

Run it and type a character. getchar takes no arguments and returns the next character from standard input; putchar takes one character and writes it. Together they echo one keystroke. Note that newlines are characters too: if you run this and press enter without typing, the program echoes the newline itself.

Why ch Is an int, Not a char

The declaration int ch looks like a typo and is the single most examined fact about getchar. It returns every possible character, plus one extra value to report that input has ended: EOF, a negative constant defined in <stdio.h> (traditionally -1). A char can represent every character or the extra value, but not reliably both; an int has room for all of it. So the rule, worth stating as a rule: the result of getchar goes in an int. Store it in a char and on platforms where char is unsigned, a comparison with EOF can never be true, an infinite-loop bug hiding in one declaration. This platform's char is unsigned, so the mistake is not theoretical here.

End of input is a real event, not an error: a file being read simply runs out, and a user at a keyboard signals it deliberately (Ctrl+D on Unix systems, Ctrl+Z on Windows). EOF is how getchar tells you.

Characters Compute

Because a character is a small integer, character arithmetic from chapter 2 works directly on what you read. The alphabet's codes are consecutive, so 'm' - 'a' is 12, the letter's position; ch - 'a' + 'A' converts lowercase to uppercase; ch - '0' turns the digit character '7' into the number 7:

Type a lowercase letter and its capital comes back. This is the exam's favourite form, and it is worth doing once by hand because it is what understanding character codes looks like. It is also fragile in two ways, and the next section is the fix.

Classifying Characters with ctype.h

The arithmetic above answers how character codes work. It is the wrong tool for asking what a character is. Writing ch >= 'a' && ch <= 'z' assumes the lowercase letters occupy one consecutive run in alphabetical order, which ASCII happens to guarantee and the standard does not. <ctype.h> asks the question directly, and this is what portable C uses:

Function True when the character is
isalpha(ch) a letter
isdigit(ch) a decimal digit
isalnum(ch) a letter or a digit
islower(ch) a lowercase letter
isupper(ch) an uppercase letter
isspace(ch) whitespace: space, tab, newline, carriage return, form feed, vertical tab
ispunct(ch) printable, but neither whitespace nor alphanumeric

C89 rounds the family out with iscntrl, isgraph, isprint, and isxdigit. (isblank looks like it belongs here but arrived in C99, so this dialect does not have it.)

They Return Nonzero, Not One

Each of these returns an int, and the standard promises only that it is zero for false and nonzero for true. It never promises 1. Print the raw results and this platform shows its hand:

Type k and the four lines come back as:

isalpha 1024
isdigit 0
islower 512
ispunct 0

isalpha reported 1024 for a letter, islower 512. The implementation keeps a bitmask per character and hands back the bit it tested, which is a perfectly legal "true". So never write isalpha(ch) == 1: test the result the way C tests every condition, against zero.

The Argument Rule Is the int Rule Again

These functions take an int, and it must be either EOF or a value that fits in an unsigned char. Anything else is undefined behaviour. Notice what that means: getchar's return value is exactly the allowed domain, so handing it straight to isalpha is always correct. Storing it in a plain char first is what breaks it, because on the platforms where char is signed, a byte above 127 becomes a negative int and lands outside the domain. The reason to keep the value in an int was EOF; this is the second reason.

Converting Case: toupper and tolower

toupper and tolower convert a letter and return any other character unchanged, so no test is needed before calling them:

Type k and it prints K; type 7 or ? and each comes back as itself. Compare that with ch - 'a' + 'A', which quietly produces a wrong character for every input that was not a lowercase letter. Two lessons in one: reach for <ctype.h> in code you intend to keep, and reach for the arithmetic when an exam asks you to show that a character is a number.

A First Taste of the Loop

The natural shape of character I/O is "read until EOF", and it needs a statement from chapter 6. Here it is as a preview, the most famous three lines in C teaching, from the language's original textbook onward:

while ((ch = getchar()) != EOF) {
    putchar(ch);
}

Read it aloud: while reading a character does not hit end-of-input, write that character. This program copies its entire input to its output. The extra parentheses around the assignment are required, assignment ranks below !=, and chapter 3's precedence rules explain exactly why. Chapter 6 makes while yours; this shape will be waiting there.

Where This Sits Against printf and scanf

getchar and putchar move exactly one character, no parsing, no formatting. scanf is built on the same stream but interprets characters into numbers; printf renders values into characters. When a task is inherently character-shaped, counting, filtering, transforming single keystrokes, the low-level pair is the honest tool, and exams ask for it by name.

Key Takeaways

  • getchar() returns the next input character; putchar(c) writes one character.
  • The result of getchar goes in an int, because EOF, the end-of-input marker from <stdio.h>, needs a value no char can safely hold; on this platform char is unsigned and the bug is real.
  • Newlines are ordinary characters in the stream.
  • Character arithmetic works on what you read: ch - 'a' + 'A' uppercases, ch - '0' gets a digit's value.
  • <ctype.h> classifies characters portably: isalpha, isdigit, isalnum, islower, isupper, isspace, ispunct, plus iscntrl, isgraph, isprint, and isxdigit.
  • Those functions return nonzero, not 1, so never compare their result with == 1; this platform answers isalpha('k') with 1024.
  • Their argument must be EOF or fit in an unsigned char, which is exactly what getchar returns and another reason not to funnel it through a char.
  • toupper and tolower return non-letters unchanged, so they need no test in front of them, unlike the arithmetic form.
  • The read-until-EOF while loop is previewed here and taught properly in chapter 6.