Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 5 of 8

char looks like the "text type," but it isn't. It's a one-byte integer that streams happen to print as a symbol.

A char is a number

#include <iostream>

int main() {
    char letter = 'A';

    std::cout << letter << "\n";
    std::cout << static_cast<int>(letter) << "\n";
    std::cout << static_cast<char>(letter + 1) << "\n";
    std::cout << ('z' - 'a') << "\n";
    return 0;
}
A
65
B
25

'A' is 65. Printing it as char shows the symbol; casting to int shows the number; adding 1 and casting back gives 'B'. And 'z' - 'a' is a perfectly ordinary subtraction giving 25 — which tells you the letters occupy consecutive codes.

The mapping is ASCII, and the parts worth remembering are:

| Characters | Codes | | --- | --- | | '0'–'9' | 48–57 | | 'A'–'Z' | 65–90 | | 'a'–'z' | 97–122 |

You don't need to memorise the numbers, but two consequences are genuinely useful:

#include <iostream>

int main() {
    char digit = '7';
    int value = digit - '0';
    std::cout << value * 2 << "\n";

    char lower = 'q';
    char upper = static_cast<char>(lower - ('a' - 'A'));
    std::cout << upper << "\n";
    return 0;
}
14
Q

Subtracting '0' converts a digit character into its numeric value — '7' is 55, '0' is 48, and 55 − 48 = 7. And since uppercase sits exactly 32 below lowercase, subtracting that gap changes case.

In real code prefer <cctype>'s std::toupper and std::isdigit, which handle edge cases properly. But knowing why the arithmetic works stops these from being magic.

'a' versus "a"

This is the distinction the lesson title promises, and it trips up everyone once:

#include <iostream>

int main() {
    std::cout << sizeof('a') << "\n";
    std::cout << sizeof("a") << "\n";
    return 0;
}
1
2
  • 'a' — single quotes — is one char. One byte.
  • "a" — double quotes — is a string literal: an array of characters ending with a hidden null terminator '\0'. Two bytes: the a and the terminator.

That trailing '\0' is how C-style strings mark their end, and it's why "a" is bigger than it looks. std::string (lesson 6.4) manages all this for you; raw string literals are the older C mechanism underneath.

Getting the quotes wrong produces confusing errors:

// WILL NOT COMPILE
int main() {
    char c = "A";   // double quotes: a 2-byte array, not a char
    return 0;
}

error: invalid conversion from 'const char*' to 'char'

The message names both types, which — as lesson 2.5 noted — usually tells you exactly what went wrong.

Escape sequences

Some characters can't be typed directly, so they're written with a backslash:

#include <iostream>

int main() {
    std::cout << "Tab:\there\n";
    std::cout << "Quote:\"quoted\"\n";
    std::cout << "Backslash:\\\n";
    std::cout << 'X' << '\n';
    return 0;
}
Tab:	here
Quote:"quoted"
Backslash:\
X

\n newline, \t tab, \\ backslash, \" quote, \0 null. Note the last line: '\n' in single quotes is a single char, and it's marginally more efficient than "\n" when you're printing nothing else.

The limit: char is one byte

Here's where char runs out, and it matters a great deal for this site's audience:

#include <iostream>
#include <string>

int main() {
    std::string english = "Hello";
    std::string khmer = "សួស្តី";

    std::cout << english.size() << "\n";
    std::cout << khmer.size() << "\n";
    return 0;
}
5
18

"Hello" is 5 characters and 5 bytes. "សួស្តី" is 6 characters a reader would count — and 18 bytes.

A single byte holds 256 possible values, nowhere near enough for every writing system on Earth. Modern text uses UTF-8, which encodes each character in one to four bytes: ASCII characters stay one byte (which is why UTF-8 is backward compatible), while Khmer, Chinese, and emoji take three or four.

The consequences for you:

  • .size() on a std::string returns bytes, not characters.
  • Indexing khmer[0] gives you one byte — a fragment of a character, not the character.
  • Reversing a UTF-8 string byte by byte produces garbage.

C++ has no good built-in answer here; correct Unicode handling means a library like ICU. For this course, be aware of the distinction: as long as you treat strings as opaque units — store them, compare them, print them whole — UTF-8 just works. It's indexing and counting that breaks.

Try it

Write a program that takes the character '5' and prints: the character itself, its ASCII code, and the result of multiplying its numeric value by 3.

Checkpoint

  • char is a one-byte integer; ASCII maps codes to symbols.
  • digit - '0' converts a digit character to its numeric value.
  • 'a' is one char; "a" is a two-byte array including a hidden '\0'.
  • Escape sequences (\n, \t, \\, \") write characters you can't type directly.
  • UTF-8 uses one to four bytes per character, so std::string::size() counts bytes, not characters — indexing non-ASCII text by byte will break it.

Questions about this lesson

Log in to ask a question.