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
| Function | True 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 |
#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; }
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.
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
#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; }
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:
#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; }
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:
#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; }
It was a bright cold day in April, and the clocks were striking thirteen.
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
#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; }
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.
Dwwdfn dw Gdzq, Cheud!