Working with Characters

Many text-processing tasks come down to looking at one character at a time: is it a digit, a letter, a space? The <ctype.h> header answers those questions and converts between upper and lower case, and getchar/putchar read and write single characters.

Classifying characters

FunctionTrue for
isalpha(c)Letters
isdigit(c)0–9
isalnum(c)Letters and digits
isspace(c)Space, tab, newline and other whitespace
isupper(c), islower(c)Upper / lower case letters
ispunct(c)Punctuation
isxdigit(c)Hex digits 0-9a-fA-F
C
#include <stdio.h>
#include <ctype.h>

int main(void) {
    const char *text = "Room 101, Floor 3!";
    int letters = 0, digits = 0, spaces = 0, other = 0;
    for (int i = 0; text[i] != '\0'; i++) {
        unsigned char c = text[i];
        if (isalpha(c)) letters++;
        else if (isdigit(c)) digits++;
        else if (isspace(c)) spaces++;
        else other++;
    }
    printf("letters %d, digits %d, spaces %d, other %d\n", letters, digits, spaces, other);
    return 0;
}
Output
letters 9, digits 4, spaces 3, other 2

The functions return non-zero for "true" (not necessarily 1), so test them directly rather than comparing with 1.

Pass an unsigned char: the ctype.h functions only accept values that fit in an unsigned char (or EOF). On systems where char is signed, bytes above 127 — such as parts of UTF-8 characters — become negative, which is undefined behaviour. Convert first, as in the example above, or write isalpha((unsigned char) c).

Changing case

C
#include <stdio.h>
#include <ctype.h>

int main(void) {
    char title[] = "the old man and the sea";
    int new_word = 1;
    for (int i = 0; title[i] != '\0'; i++) {
        unsigned char c = title[i];
        if (new_word && isalpha(c)) {
            title[i] = (char) toupper(c);
        }
        new_word = isspace(c);
    }
    printf("%s\n", title);
    return 0;
}
Output
The Old Man And The Sea

toupper and tolower return the converted character, or the original if there's nothing to convert.

Case-insensitive comparison

Standard C has no case-insensitive strcmp (POSIX has strcasecmp), but it's easy to write:

C
#include <stdio.h>
#include <ctype.h>
#include <stdbool.h>

bool equals_ignore_case(const char *a, const char *b) {
    for (; *a && *b; a++, b++) {
        if (tolower((unsigned char) *a) != tolower((unsigned char) *b)) return false;
    }
    return *a == *b;   // both ended at the same time
}

int main(void) {
    printf("%d %d\n", equals_ignore_case("Hello", "hELLO"), equals_ignore_case("Hello", "Help"));
    return 0;
}
Output
1 0

Reading one character at a time

getchar reads the next character from standard input and returns it as an int — or EOF at the end of input. It must be stored in an int, not a char, so that EOF can be told apart from every real character:

C
#include <stdio.h>
#include <ctype.h>

int main(void) {
    int c;
    int lines = 0, words = 0, chars = 0;
    int in_word = 0;
    while ((c = getchar()) != EOF) {
        chars++;
        if (c == '\n') lines++;
        if (isspace(c)) {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            words++;
        }
    }
    printf("%d lines, %d words, %d characters\n", lines, words, chars);
    return 0;
}
Input
It was a bright cold day in April,
and the clocks were striking thirteen.
Output
2 lines, 14 words, 74 characters

That's a miniature version of the Unix wc command. Redirect any file into it with ./main < file.txt.

Digits and letters as numbers

C
#include <stdio.h>
#include <ctype.h>

int main(void) {
    const char *code = "A7-x9";
    int digit_sum = 0;
    for (int i = 0; code[i]; i++) {
        unsigned char c = code[i];
        if (isdigit(c)) digit_sum += c - '0';
        else if (isalpha(c)) printf("%c is letter %d of the alphabet\n", c, tolower(c) - 'a' + 1);
    }
    printf("digit sum: %d\n", digit_sum);
    return 0;
}
Output
A is letter 1 of the alphabet
x is letter 24 of the alphabet
digit sum: 16

c - '0' is guaranteed to work for digits in every C implementation. c - 'a' relies on letters being consecutive, which is true for ASCII and UTF-8 (but not for some rare old encodings).

Try it yourself

Encode a message with a Caesar cipher: shift every letter 3 places, wrapping around at the end of the alphabet and keeping case.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
Dwwdfn dw Gdzq, Cheud!