Символи та таблиця ASCII
Символи та таблиця ASCII
Чому 'A' + 1 дорівнює 'B'
Ви вже знаєте, що в C++ існує тип char для зберігання одного символу. Але подивіться на цей фрагмент коду:
#include <iostream>
using namespace std;
int main()
{
char first = 'A';
char next = first + 1;
char digit = '7' - '0';
cout << next << "\n"; // B
cout << (int)digit << "\n"; // 7
return 0;
}
Чому додавання одиниці до символу 'A' дає символ 'B'? Чому віднімання '0' від символу-цифри '7' дає звичайне число 7? І чому взагалі можна виконувати арифметику над символами? Це не магія — це пряме наслідування того, що char насправді є числом. А яке число відповідає якому символу — визначає таблиця кодування.
Тип char — це просто маленький цілочисельний тип
Байт, біти та діапазон значень
Ви вже знаєте зі статті про типи даних, що char займає рівно 1 байт — 8 біт. Вісім біт дають 256 можливих комбінацій: від 0000 0000 до 1111 1111. Отже, char може зберігати одне з 256 різних чисел.
Залежно від того, як компілятор інтерпретує 8-й біт (знаковий чи ні), розрізняють:
signed char— діапазон від −128 до 127 (знаковий тип, старший біт = знак)unsigned char— діапазон від 0 до 255 (беззнаковий тип)char— залежить від платформи; на більшості сучасних систем signed, але це не гарантовано стандартом
#include <iostream>
using namespace std;
int main()
{
signed char sc = -1;
unsigned char uc = 255;
char ch = 'A'; // 65 у внутрішньому представленні
cout << (int)sc << "\n"; // -1
cout << (int)uc << "\n"; // 255
cout << (int)ch << "\n"; // 65
return 0;
}
Зверніть увагу на (int)ch — без явного приведення std::cout виведе символ A, а не число 65. Ми говоримо компілятору: «інтерпретуй цей байт як ціле число, а не символ».
char є окремим від signed char і unsigned char навіть попри те, що фізично відповідає одному з них. Стандарт C++ трактує char, signed char та unsigned char як три різних типи. Це важливо при перевантаженні функцій та шаблонах.char у пам'яті
Символ 'A' зберігається в пам'яті як байт зі значенням 65 (або 0x41 у шістнадцятковій):
char ch = 'A'
Порівняйте з int x = 65 — значення те саме, але займає 4 байти:
int x = 65
char ch = 'A' і char ch = 65 — це абсолютно ідентичні оголошення. Компілятор зберігає в обох випадках один байт зі значенням 65. Різниця лише в тому, як програміст записує ініціалізатор — одинарні лапки ('A') або числовий літерал (65).
Що таке кодування символів
Проблема: число є, але що воно означає?
Отже, char — це число від 0 до 255. Але саме по собі число нічого не каже. Байт зі значенням 65 — це буква 'A'? Кирилична 'А'? Якийсь спеціальний символ? Відповідь залежить від того, яку таблицю відповідності ми використовуємо.
Кодування символів (character encoding) — це стандартизована таблиця, що визначає, яке число відповідає якому символу. Це договір між усіма учасниками: комп'ютером, операційною системою, компілятором, текстовим редактором та людиною.
Аналогія з реального світу: уявіть азбуку Морзе. Три крапки (...) — це буква S. Але якщо хтось не знає, що ми використовуємо саме азбуку Морзе, а не, скажімо, шифр Цезаря — три крапки для нього лишаться беззмістовним сигналом. Кодування і є таким «ключем до розшифровки».
65 існує незалежно від кодування. Кодування лише визначає, який символ ми намалюємо на екрані, коли зустрінемо байт 65. Саме тому «змінити кодування» файлу — це не змінити байти, а лише змінити правило їх інтерпретації.Чому потрібен єдиний стандарт
На зорі комп'ютерної ери кожен виробник мав власну таблицю кодування. IBM використовувала EBCDIC, різні термінали — власні схеми. Це означало, що текстовий файл, створений на одній системі, перетворювався на «кракозябри» (mojibake) на іншій. Обмін даними між різними комп'ютерами був вкрай складним завданням.
Рішення прийшло у 1963 році у вигляді стандарту ASCII.
Таблиця ASCII
Що таке ASCII
ASCII (American Standard Code for Information Interchange — Американський стандартний код для обміну інформацією) — перший широко прийнятий стандарт кодування символів, розроблений у США у 1963 році і затверджений як стандарт ANSI у 1968 році.
Ключові характеристики ASCII:
- Використовує 7 біт → 128 символів (коди від 0 до 127)
- Розроблявся для телетайпів (teletypes) — пристроїв передачі тексту по телефонних лініях
- Орієнтований на англійську мову: латинські букви, цифри, пунктуація
- Досі є основою всіх сучасних кодувань — UTF-8 повністю сумісний з ASCII
Структура таблиці ASCII
Таблиця ASCII поділяється на чотири логічні групи:
Керуючі символи (0–31)
Невидимі символи для керування пристроями та форматування тексту. Жодного «малюнку» вони не мають — це команди.
Найважливіші:
0(\0) — нуль-термінатор, кінець C-style рядка7(\a) — дзвінок (bell) — звуковий сигнал8(\b) — backspace — крок назад9(\t) — горизонтальна табуляція10(\n) — новий рядок (LF, Line Feed)13(\r) — повернення каретки (CR, Carriage Return)27— escape-символ (ESC)
Пунктуація і спецсимволи (32–47, 58–64, 91–96, 123–127)
32), !, ", #, $, %, &, ', (, ), *, +, ,, -, ., / та інша пунктуація. Також :, ;, <, =, >, ?, @, дужки, \, ^, _, `, {, |, }, ~, DEL (127).Цифри (48–57)
'0' через '9'. Зверніть увагу: символ '0' має код 48, а не 0. Саме тому '7' - '0' = 55 - 48 = 7.Літери (65–90 та 97–122)
'A'–'Z': коди 65–90.Малі літери
'a'–'z': коди 97–122.Різниця між великою і малою літерою: рівно 32. Саме тому перетворення регістру — це проста арифметика.
Повна таблиця ASCII (32–127)
| Dec | Hex | Sym | Dec | Hex | Sym | Dec | Hex | Sym | Dec | Hex | Sym |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 32 | 20 | | 56 | 38 | 8 | 80 | 50 | P | 104 | 68 | h |
| 33 | 21 | ! | 57 | 39 | 9 | 81 | 51 | Q | 105 | 69 | i |
| 34 | 22 | " | 58 | 3A | : | 82 | 52 | R | 106 | 6A | j |
| 35 | 23 | # | 59 | 3B | ; | 83 | 53 | S | 107 | 6B | k |
| 36 | 24 | $ | 60 | 3C | < | 84 | 54 | T | 108 | 6C | l |
| 37 | 25 | % | 61 | 3D | = | 85 | 55 | U | 109 | 6D | m |
| 38 | 26 | & | 62 | 3E | > | 86 | 56 | V | 110 | 6E | n |
| 39 | 27 | ' | 63 | 3F | ? | 87 | 57 | W | 111 | 6F | o |
| 40 | 28 | ( | 64 | 40 | @ | 88 | 58 | X | 112 | 70 | p |
| 41 | 29 | ) | 65 | 41 | A | 89 | 59 | Y | 113 | 71 | q |
| 42 | 2A | * | 66 | 42 | B | 90 | 5A | Z | 114 | 72 | r |
| 43 | 2B | + | 67 | 43 | C | 91 | 5B | [ | 115 | 73 | s |
| 44 | 2C | , | 68 | 44 | D | 92 | 5C | \ | 116 | 74 | t |
| 45 | 2D | - | 69 | 45 | E | 93 | 5D | ] | 117 | 75 | u |
| 46 | 2E | . | 70 | 46 | F | 94 | 5E | ^ | 118 | 76 | v |
| 47 | 2F | / | 71 | 47 | G | 95 | 5F | _ | 119 | 77 | w |
| 48 | 30 | 0 | 72 | 48 | H | 96 | 60 | ` | 120 | 78 | x |
| 49 | 31 | 1 | 73 | 49 | I | 97 | 61 | a | 121 | 79 | y |
| 50 | 32 | 2 | 74 | 4A | J | 98 | 62 | b | 122 | 7A | z |
| 51 | 33 | 3 | 75 | 4B | K | 99 | 63 | c | 123 | 7B | { |
| 52 | 34 | 4 | 76 | 4C | L | 100 | 64 | d | 124 | 7C | | |
| 53 | 35 | 5 | 77 | 4D | M | 101 | 65 | e | 125 | 7D | } |
| 54 | 36 | 6 | 78 | 4E | N | 102 | 66 | f | 126 | 7E | ~ |
| 55 | 37 | 7 | 79 | 4F | O | 103 | 67 | g | 127 | 7F | DEL |
Арифметика символів
Оскільки char — це число, з символами можна виконувати усі арифметичні та порівняльні операції. Це не просто цікавий факт — це основа багатьох алгоритмів обробки тексту.
Конвертація символу-цифри в числове значення
Символи '0'–'9' розташовані в ASCII послідовно (коди 48–57). Тому щоб отримати числове значення з символу-цифри, достатньо відняти код символу '0':
#include <iostream>
using namespace std;
int main()
{
char ch = '7';
// Неправильно: ch сам по собі — це число 55, а не 7
// Правильно:
int digit = ch - '0'; // 55 - 48 = 7
cout << "Символ: " << ch << "\n"; // 7
cout << "Код: " << (int)ch << "\n"; // 55
cout << "Число: " << digit << "\n"; // 7
return 0;
}
Цей прийом є повсюдним у реальному коді: конвертація рядка з цифрами у число (ручна реалізація atoi), перевірка коректності введення тощо. Важливо пам'ятати, що він лише для символів '0'–'9' — якщо ch не є цифрою, результат буде безглуздим.
Навігація по алфавіту
Літери 'A'–'Z' і 'a'–'z' також розташовані в ASCII послідовно, тому зміщення по алфавіту — це проста арифметика:
#include <iostream>
using namespace std;
int main()
{
char letter = 'E';
// Зсув вперед по алфавіту
char shifted = letter + 3; // 'E' (69) + 3 = 72 = 'H'
cout << shifted << "\n"; // H
// Позиція літери в алфавіті (0-indexed)
int pos = letter - 'A'; // 69 - 65 = 4
cout << "Позиція 'E': " << pos << "\n"; // 4
// Циклічний шифр Цезаря (зсув на 13, ROT13)
char rotated = 'A' + (letter - 'A' + 13) % 26; // 'R'
cout << "ROT13('E'): " << rotated << "\n"; // R
return 0;
}
Перетворення регістру вручну
Оскільки між кодами великих і малих літер різниця рівно 32, перетворення регістру — це додавання або віднімання константи:
#include <iostream>
using namespace std;
int main()
{
char upper = 'G';
char lower = 'g';
// Велика → мала: додати 32 (або 'a' - 'A')
char toLower = upper + ('a' - 'A'); // 71 + 32 = 103 = 'g'
// Мала → велика: відняти 32
char toUpper = lower - ('a' - 'A'); // 103 - 32 = 71 = 'G'
cout << toLower << "\n"; // g
cout << toUpper << "\n"; // G
// Перевірка: чи є символ великою літерою
bool isUpper = (upper >= 'A' && upper <= 'Z');
cout << boolalpha << isUpper << "\n"; // true
return 0;
}
32 краще писати 'a' - 'A' — це самодокументований код, який явно показує різницю між регістрами. Компілятор обчислить 'a' - 'A' на етапі компіляції і підставить 32 — жодних витрат часу виконання.Порівняння символів
Оскільки порівняння символів — це порівняння їх числових кодів, 'a' < 'b' є правдою (97 < 98), а 'A' < 'a' — теж правда (65 < 97). Це є основою лексикографічного (алфавітного) сортування рядків:
#include <iostream>
using namespace std;
int main()
{
cout << boolalpha;
// Порівняння символів
cout << ('a' < 'b') << "\n"; // true (97 < 98)
cout << ('A' < 'a') << "\n"; // true (65 < 97)
cout << ('Z' < 'a') << "\n"; // true (90 < 97) — усі великі < малих
cout << ('9' < 'A') << "\n"; // true (57 < 65) — цифри < букв
return 0;
}
Це має практичний наслідок: функція strcmp для рядків порівнює їх саме побайтово за ASCII-кодами. «Banana» стоятиме після «Apple» лексикографічно, бо 'B' (66) > 'A' (65). Але «apple» стоятиме після «Banana», бо 'a' (97) > 'B' (66). Регістр символів впливає на лексикографічний порядок у чистому ASCII.
Бібліотека <cctype>: стандартні функції для символів
Писати власні перевірки на кшталт ch >= 'A' && ch <= 'Z' — це не лише багатослівно, але й ненадійно: такий підхід не враховує локалізацію і може давати неправильні результати для розширеного ASCII. Стандартна бібліотека C++ пропонує готові функції у заголовку <cctype>.
Функції класифікації символів
Усі функції приймають int (неявне розширення char) і повертають ненульове значення (true) або нуль (false):
#include <iostream>
#include <cctype>
using namespace std;
int main()
{
char samples[] = {'A', 'z', '5', ' ', '!', '\n'};
for (char ch : samples)
{
cout << "'" << ch << "' (код " << (int)ch << "):\n";
cout << " isalpha: " << boolalpha << (bool)isalpha(ch) << "\n";
cout << " isdigit: " << (bool)isdigit(ch) << "\n";
cout << " isalnum: " << (bool)isalnum(ch) << "\n";
cout << " isspace: " << (bool)isspace(ch) << "\n";
cout << " ispunct: " << (bool)ispunct(ch) << "\n";
cout << " isupper: " << (bool)isupper(ch) << "\n";
cout << " islower: " << (bool)islower(ch) << "\n";
}
return 0;
}
Таблиця функцій <cctype>
isalpha('A') → true, isalpha('5') → false.isdigit('7') → true, isdigit('a') → false.isalpha і isdigit.\t (9), \n (10), \r (13), \f (12), \v (11).!, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, ~.int — не забувайте про static_cast<char>.int.Практичний приклад: валідація та нормалізація введення
#include <iostream>
#include <cctype>
#include <string>
using namespace std;
// Перевіряє, чи рядок є коректним ідентифікатором C++:
// починається з літери або _, далі — букви, цифри або _
bool isValidIdentifier(const string& s)
{
if (s.empty()) return false;
if (!isalpha(static_cast<unsigned char>(s[0])) && s[0] != '_')
return false;
for (size_t i = 1; i < s.size(); ++i)
{
if (!isalnum(static_cast<unsigned char>(s[i])) && s[i] != '_')
return false;
}
return true;
}
// Перетворює рядок у верхній регістр
string toUpperCase(string s)
{
for (char& ch : s)
ch = static_cast<char>(toupper(static_cast<unsigned char>(ch)));
return s;
}
int main()
{
cout << boolalpha;
cout << isValidIdentifier("myVar") << "\n"; // true
cout << isValidIdentifier("_count") << "\n"; // true
cout << isValidIdentifier("2fast") << "\n"; // false
cout << isValidIdentifier("my-var") << "\n"; // false
cout << toUpperCase("Hello, World!") << "\n"; // HELLO, WORLD!
return 0;
}
<cctype> приймають int, а не char. Якщо ваша платформа використовує signed char і символ має від'ємне значення (наприклад, кирилиця в CP1251), передача такого char напряму є невизначеною поведінкою (UB). Правильний спосіб: std::isupper(static_cast<unsigned char>(ch)).Extended ASCII та проблема кодових сторінок
8-й біт: ще 128 символів
Оригінальний ASCII використовує лише 7 біт (коди 0–127). Восьмий біт залишався вільним, і різні компанії та організації почали заповнювати коди 128–255 власними символами. Це і є розширений ASCII (Extended ASCII).
Проблема в тому, що єдиного стандарту розширеного ASCII не існує. Кожна «кодова сторінка» (code page) по-своєму визначає, що означають коди 128–255:
| Кодова сторінка | Призначення | Де використовується |
|---|---|---|
| CP437 | Псевдографіка DOS (░, ▒, ▓, █) | Старі DOS-програми |
| CP1251 (Windows-1251) | Кирилиця (українська, російська) | Windows-системи |
| CP1252 (Windows-1252) | Западноєвропейські мови | Windows, Захід Європи |
| ISO 8859-1 (Latin-1) | Западноєвропейські мови | Unix-системи, HTTP |
| ISO 8859-5 | Кирилиця | Unix-системи |
| KOI8-U | Кирилиця (українська) | Старі Unix/email системи |
«Кракозябри» — наочна демонстрація проблеми
Уявіть: ви зберегли текст "Привіт" у файлі з кодуванням CP1251. Байти виглядають так:
0xCF 0xF0 0xE8 0xE2 0xB3 0xF2 (CP1251: "Привіт")
Якщо відкрити той самий файл, але вказати кодування ISO 8859-1 (Latin-1), браузер або редактор інтерпретує байт 0xCF як Ï, 0xF0 як ð, 0xE8 як è — і замість «Привіт» ви побачите: Ïðèâ³ò. Це і є «кракозябри» (mojibake).
Тизер: чому ASCII більше недостатньо
ASCII відмінно вирішив задачу для англійської мови. Але наш світ — багатомовний. Китайська мова має тисячі ієрогліфів, арабська — пишеться справа наліво, математична нотація вимагає грецьких та спеціальних літер, емодзі стали частиною цифрового спілкування.
Жодна кодова сторінка з 256 символами не здатна охопити всю людську писемність. У 1991 році світ отримав Unicode — єдиний каталог для понад мільйона символів усіх мов і скриптів. Але це — тема наступної статті.
Практика
Рівень 1: Розуміння та базові операції
Напишіть програму, що зчитує один символ з клавіатури і виводить про нього повну інформацію: ASCII-код у десятковому та шістнадцятковому форматі, категорію (велика літера / мала літера / цифра / пробільний / пунктуація / керуючий), а також для літер — відповідний символ іншого регістру.
Очікуваний вивід для 'G':
Символ: G
Код: 71 (0x47)
Категорія: велика літера
Мала версія: g
Підказка: використайте std::hex для шістнадцяткового виводу та функції <cctype>.
Знайдіть і виправте всі помилки у наступному коді:
#include <iostream>
using namespace std;
int main()
{
char ch = "A"; // помилка 1
int code = ch;
char upper = ch - 32; // помилка 2 (логічна): ch вже велика
char digit = '5';
int num = digit; // помилка 3 (логічна): це дасть 53, а не 5
cout << code << "\n";
cout << upper << "\n";
cout << num << "\n";
return 0;
}
Рівень 2: Алгоритми обробки символів
Напишіть програму, що зчитує рядок (через std::getline) і виводить статистику:
- Кількість великих літер
- Кількість малих літер
- Кількість цифр
- Кількість пробілів
- Кількість знаків пунктуації
- Кількість інших символів
Використайте функції <cctype>.
Реалізуйте функцію std::string caesarCipher(const std::string& text, int shift), що зсуває кожну літеру тексту на shift позицій в алфавіті (з оберненням через кінець: після 'Z' іде 'A'). Нелітерні символи залишати без змін. Реалізуйте також функцію дешифрування.
Приклад: caesarCipher("Hello, World!", 13) → "Uryyb, Jbeyq!"
Рівень 3: Більш складні завдання
Напишіть функцію void charInfo(char ch), що виводить:
- Символ та його ASCII-код (dec, hex, oct, bin)
- Категорію символу (за
<cctype>) - Для літер: позицію в алфавіті (1-indexed), відповідний символ іншого регістру
- Для цифр: числове значення
- Для керуючих символів: назву (наприклад,
\n→ «Line Feed,\t` → «Tab»)
Продемонструйте роботу функції для набору символів: 'A', 'z', '5', '\n', '!', ' '.
Напишіть програму, що генерує «ASCII-арт» рамку навколо введеного тексту. Програма зчитує рядок і виводить його у рамці з символів +, -, |:
Введіть текст: Hello, World!
+---------------+
| Hello, World! |
+---------------+
Ускладнення: якщо текст довший за 40 символів, автоматично перенести на наступний рядок всередині рамки.
Резюме
🔢 char — це число
charзаймає 1 байт і може зберігати значення 0–255char ch = 'A'≡char ch = 65— ідентичні оголошенняsigned char(−128..127),unsigned char(0..255),char(платформозалежний)- Для виводу числового значення:
(int)chабоstatic_cast<int>(ch)
📋 Таблиця ASCII
- 128 символів (7 біт), коди 0–127
- 0–31: керуючі символи (
\0,\n,\t,\r) - 48–57: цифри
'0'–'9' - 65–90: великі літери
'A'–'Z' - 97–122: малі літери
'a'–'z' - Різниця великих і малих: 32
➕ Арифметика символів
'7' - '0'→ числове значення цифри (7)'a' - 'A'→ 32 (різниця регістрів)'A' + pos→ літера за позицією в алфавітіch >= 'A' && ch <= 'Z'→ перевірка на велику літеру'A' + (ch - 'A' + n) % 26→ циклічний зсув по алфавіту
🔧 Бібліотека <cctype>
isalpha,isdigit,isalnum— перевірка типу символуisspace,ispunct,isprint,iscntrl— додаткові категоріїisupper,islower— перевірка регіструtoupper,tolower— зміна регістру (повертаютьint!)- ⚠️ Завжди:
static_cast<unsigned char>(ch)перед викликом
⚠️ Extended ASCII і кодові сторінки
- Коди 128–255 не стандартизовані
- Різні кодові сторінки (CP1251, CP1252, ISO 8859-x) — різна інтерпретація
- «Кракозябри» (mojibake) — наслідок неправильного кодування
- ASCII стандартизував лише 128 символів для англійської мови
🌍 Що далі: Unicode
- 256 значень
charне вистачає для всіх мов світу - Множинні кодові сторінки → несумісність та помилки
- Unicode (1991): єдиний каталог для всіх символів усіх мов
- UTF-8, UTF-16, UTF-32 — різні способи кодування Unicode
- Наступна стаття: детально про Unicode та кодування UTF
"Привіт".length() повертає 12, а не 6, і які символьні типи є в C++ для роботи з Unicode.Патерни struct та межі застосування
Архітектурні патерни використання struct: Record, Value Object, Result type, Named Parameters. Семантика значення, struct vs class і позиція Страуструпа щодо методів у struct.
Unicode та кодування UTF
Що таке Unicode, чим відрізняється код-поінт від кодування, як влаштовані UTF-8, UTF-16 та UTF-32 на рівні байтів, чому char у C++ — це не символ, та які символьні типи існують для роботи з Unicode.