Why this matters
Text is everywhere: file names, configuration files, user input, network protocols, log lines. In C there is no special "letter" kind of value. A character is a small integer, and text is a sequence of those integers in memory. Once you see that, a lot of C idioms make sense at a glance: turning '7' into the number 7 with a subtraction, converting case by adding 32, or checking whether a character is a digit with two comparisons. It also explains several bugs, such as characters that print as negative numbers.
Character encodings and ASCII
To store text, everyone has to agree which number stands for which character. That agreement is a character encoding. The foundation of almost every encoding in use is ASCII (1963), which assigns the numbers 0 to 127:
code char code char code char code char
10 \n 48 0 65 A 97 a
32 (space) 49 1 66 B 98 b
33 ! ... ... ...
... 57 9 90 Z 122 z
Some facts about this table are used constantly:
- The digits
'0'to'9'are consecutive (48 to 57). The C standard guarantees this for every encoding. - The uppercase letters
'A'to'Z'are consecutive (65 to 90), and so are the lowercase'a'to'z'(97 to 122). ASCII guarantees this; the C standard does not, but every system you will use is ASCII-based. - Lowercase is uppercase plus 32. In binary, that is a single bit:
'A'is0100 0001,'a'is0110 0001. - Codes 0 to 31 and 127 are control characters: newline (10), tab (9), carriage return (13), and the null character (0), which plays a central role in C strings.
The full table is one command away in the sandbox terminal: man 7 ascii.
The char type and character literals
C's char type is one byte, the smallest integer type. A character literal is a character in single quotes, 'A', and it is simply another way to write the integer 65. These two lines store the same byte:
char letter = 'A';
char same = 65;
printf decides how to show it: %c prints the character with that code, %d prints the number.
#include <stdio.h>
int main(void)
{
char letter = 'A';
printf("%c is stored as %d (0x%x)\n", letter, letter, letter);
printf("'a' = %d, 'z' = %d, '0' = %d, ' ' = %d, '\\n' = %d\n", 'a', 'z', '0', ' ', '\n');
printf("%c%c%c\n", 72, 105, 33);
return 0;
}
A is stored as 65 (0x41)
'a' = 97, 'z' = 122, '0' = 48, ' ' = 32, '\n' = 10
Hi!
Single and double quotes mean different things: 'A' is one character (an integer), "A" is a string (an array of characters ending with a zero byte; strings get their own lessons). Escape sequences work in character literals too: '\n', '\t', '\'', '\\', and '\0' for the character with code 0.
Note: a curious detail: in C, the type of the literal
'A'is actuallyint, notchar, sosizeof('A')is 4. It makes no practical difference when you store it in a char.
Arithmetic on characters
Because characters are integers, you can compute with them. The three idioms you will see in real C code over and over:
char next = 'A' + 1; /* 'B': the next letter */
int value = '7' - '0'; /* 7: digit character to its numeric value */
char upper = 'g' - 'a' + 'A'; /* 'G': lowercase to uppercase */
after A comes B
'7' - '0' = 7
g -> G
The second one deserves a picture, because converting text to numbers is how every program reads numeric input:
character: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'
code: 48 49 50 51 52 53 54 55 56 57
code - '0': 0 1 2 3 4 5 6 7 8 9
Writing '0' instead of 48 makes the intent obvious and does not depend on the encoding.
The third idiom works by computing the letter's position in the alphabet ('g' - 'a' is 6) and adding it to the start of the other range. Positions are a powerful trick: to wrap around the alphabet, compute the position, do arithmetic modulo 26, and convert back.
ctype.h: classification without the arithmetic
For everyday checks, the standard library has <ctype.h>:
| function | true for |
|---|---|
isdigit(c) | '0' to '9' |
isalpha(c) | letters |
isalnum(c) | letters and digits |
isspace(c) | space, tab, newline, carriage return, vertical tab, form feed |
isupper(c), islower(c) | uppercase / lowercase letters |
toupper(c), tolower(c) | return the converted character (others unchanged) |
The is functions return an int that is nonzero for true and 0 for false (not necessarily 1). They also handle locales and non-ASCII systems, so prefer them in real code. You will use them a lot once you can make decisions with if.
Is char signed?
char is a byte, but is it -128 to 127 or 0 to 255? The C standard leaves it to the implementation. On x86-64 Linux, char is signed; on ARM Linux (Raspberry Pi, phones) it is unsigned. C therefore has three distinct types: char, signed char and unsigned char.
char c = 200;
printf("char 200 -> %d\n", c);
char 200 -> -56
on x86-64, because 200 does not fit in a signed char and is reduced to 200 - 256. For ASCII text this never matters (all codes are below 128). For raw bytes it matters a lot: when processing binary data or the individual bytes of UTF-8 text, use unsigned char or uint8_t.
Beyond ASCII: UTF-8 in one paragraph
ASCII has no é, no €, no Chinese or emoji. Today's standard is Unicode, which assigns a number (a code point) to over a hundred thousand characters, and UTF-8, which encodes each code point as 1 to 4 bytes. ASCII characters stay single bytes with the same values, which is why C code that only cares about ASCII keeps working on UTF-8 text. But one character may now be several chars: "héllo" takes 6 bytes plus the terminating zero, not 5. Proper Unicode text processing in C needs extra libraries; this track sticks to ASCII.
Common mistakes
- Confusing
'5'with5. The character'5'is 53.printf("%d", '5')prints 53. Convert with- '0'. - Double quotes for a character:
char c = "A";gives (only a warning in compilers older than GCC 14)."A"is a string, not a character.error: initialization of 'char' from 'char *' makes integer from pointer without a cast [-Wint-conversion] - Multi-character literals like
'ab'. gcc warnsmulti-character character constantand produces an implementation-defined int. A character literal holds one character. - Assuming char is unsigned (or signed). Use
unsigned charwhen the values can exceed 127.
Recap
- A
charis a one-byte integer; a character literal'A'is just its code (65 in ASCII). %cprints the character for a code,%dprints the code.- Digits and letters have consecutive codes, giving the idioms
c - '0',c - 'a' + 'A', and position arithmetic with% 26. <ctype.h>providesisdigit,isalpha,toupperand friends.- Plain
charmay be signed or unsigned; useunsigned charfor raw bytes. - UTF-8 keeps ASCII as single bytes but encodes other characters as several bytes.
Going further: run printf 'h\xc3\xa9llo\n' | od -c in the sandbox terminal to see the two bytes that make up é in UTF-8.