Розділ 13. Символьні рядки
13.1. Символьний тип
Символами в С/С++ вважаються великі та малі літери, цифри, знаки арифметичних операцій (‘+’, ‘–’, ‘*’, ‘/’, ‘=‘), пробіл, розділові знаки (‘.’, ‘,’, ‘;’, ‘:’, ‘!’, ‘?’, ‘–’) та службові символи, які відповідають клавішам <Enter>, <Esc>, <Tab> тощо.
Типом символьних змінних у С/С++ є тип char. При оголошенні змінних символьного типу в оперативній пам’яті для кожної з них виділяється по одному байту. Тобто для кодування символів використовується один байт (8 бітів), за допомогою якого можна закодувати 28 = 256 різних символів.
Значення символьних констант записують в одиничних лапках.
13.1.1. Коди символів
Для уникнення розходжень при кодуванні символів розроблено єдиний міжнародний стандарт кодів для обміну інформацією – таблиця ASCII-кодів (American Standard Code for Information Interchange).
Перші 128 ASCII-кодів (від 0 до 127) спільні для всіх країн, а наступні 128 кодів (від 128 до 255) утворюють розширену частину ASCII-таблиці, в якій залежно від країни розміщається національний алфавіт і символи псевдографіки.
В ASCII-таблиці всі символи мають власний унікальний код і вони є впорядкованими.
Символ |
Код |
‘ ‘ (пробіл) |
32 |
‘0’…’9’ |
48…57 |
‘A’…’Z’ |
65…90 |
‘a’…’z’ |
97…122 |
Кодування другої половини ASCII-таблиці має різні варіанти. Найпоширенішими є DOS-кодування (866 кодова сторінка) і кодування 1251, яке є основним для ОС Windows.
Варто звернути увагу на різницю між цифрами та їх символьним зображенням. Наприклад, символ цифри ‘4’ має ASCII-код 52 і не має безпосереднього відношення до числа 4.
Під час роботи з символами часто використовуються керуючі символи (<Esc>, <Enter>, <Tab> тощо), які не мають символьного подання, і називаються недрукованими (non-printed). Керуючі символи розташовані в перших 32-х кодах ASCII-таблиці. Звертатися до керуючих символів потрібно, використовуючи їхній код або ескейп-послідовність (escape). Ескейп-послідовність – це спеціальна комбінація символів, яка починається зі зворотної косої риски і записується в одиничних лапках. Кожна з поданих у табл. 13.1 ескейп-послідовностей вважається одним символом.
Таблиця 13.1.
Основні ескейп-послідовності
Символ |
Опис |
\n |
символ переведення курсора на початок наступного рядка |
\r |
перехід на початок поточного рядка (переведення каретки) |
\t |
горизонтальна табуляція (відповідає клавіші <Tab>) |
\v |
вертикальна табуляція |
\b |
символ вилучення попереднього символу перед курсором (відповідає клавіші <BackSpace>) |
\a |
символ звукового сигналу системного динаміка |
\\ |
зворотна коса риска |
\? |
знак запитання |
\’ |
апостроф (одинична лапка) |
\" |
подвійні лапки |
\0 |
завершальний символ рядка символів (нуль-символ) |
\0oo |
вiсiмковий код символа, де oo – одна, дві чи три вісімкові цифри (0…7) |
\0хhh |
шiстнадцятковий код символа, де hh – одна, дві чи більше шістнадцяткових цифр (0…9, a…f, A…F) |
Коли символьні змінні отримують певні значення, то комп’ютер зберігає в пам’яті не символи, а їхні коди. Тому, якщо символьній змінній присвоїти певне ціле число, то компілятор сприйме його як код певного символа з ASCII-таблиці. Це правило поширюється лише на цілі числа. Зважаючи на різні кодування розширеної частини ASCII-таблиці, для уникнення помилок при роботі з символами, потрібно використовувати їхнє символьне подання, а не коди.
Приклад 13.1.
char c, d = ‘Y’, r = 115; // оголошення змінних символьного типу
int k;
// виведення символа, що зберігається в змінній d
cout << "Output char variable d: " << d << endl; // Результат: Y
// виведення коду символа, що зберігається в змінній d
cout << "\nOutput code variable d: " << (int)d << endl; // Результат: 89
// виведення символа ‘s’
cout << "\n Output code variable r: " << r << endl; // Результат: s
// введення символа за допомогою cin
cout << "\nEnter char: ";
cin >> c;
k = c;
// виведення коду символа, введеного з клавіатури, за допомогою cout
cout << "Code of entered char: " << k;
cout << "\nEnter char: ";
На відміну від більшості мов програмування, в C/С++ дані типу char змінюються у діапазоні –128...127, причому числа 0...127 є кодами символів спільної частини ASCII-таблиці, а від’ємні числа – розширеної частини ASCII-таблиці. Наприклад, літера кирилиці ‘ю’ має код –2.
Крім типу char, для оголошення символьних змінних (констант) також використовують тип unsigned char (беззнаковий символьний тип), значення даних якого належать діапазону чисел 0...255. В ASCII-таблиці значення кодів літер кирилиці більші за 127, тому символьні змінні, значеннями яких є літери кирилиці, потрібно оголошувати як unsigned char.
При порівнянні символів більшим вважається той, код якого більший, тобто той символ, який у таблиці ASCII-кодів розміщений пізніше. Наприклад, справедливі такі нерівності: ‘5’<‘A’, ‘B’<‘C’, ‘C’<‘c’, ‘c’<‘z’.
Оскільки символьний тип у C/С++ вважається цілим типом, то змінні цього типу можна додавати та віднімати. Результатом додавання двох символів буде символ, код якого дорівнює сумі кодів символів-доданків, а результатом різниці – символ, код якого дорівнює різниці кодів символів зменшуваного та від’ємника.
Приклад 13.2. Додавання та віднімання символів:
char c = ‘A’;
char c1 = c + 3; // Результат: c1 = ‘D’
char c2 = c - 15; // Результат: c2 = ‘2’
Приклад 13.3. Виведення кодів символів:
cout << int(‘0’)<< endl; // Результат: 48
cout << int(‘A’)<< endl; // Результат: 65
cout << int(‘z’)<< endl; // Результат: 122
Приклад 13.4. Виведення зображення ASCII-символів згідно порядкового номера:
cout << "Number\t ASCII-symbol\n";
for (int i = 32; i <= 125; i++)
cout << i << "\t - \t" << (char) i << ‘\n’;
13.1.2. Опрацювання символів
Для опрацювання символьних даних використовуються стандартні бібліотеки С/С++ <ctype.h> та <cctype>, в яких визначені функції, подані у табл. 13.2.
Таблиця 13.2.
Функції для роботи з символами
Функція |
Призначення |
tolower() |
повертає символ у нижньому регістрі |
toupper() |
повертає символ у верхньому регістрі |
isalnum() |
перевіряє чи належить символ множині латинських літер та цифр |
isalpha() |
перевіряє чи належить символ множині латинських літер |
iscntrl() |
перевіряє чи належить символ множині керуючих символів (з кодами 0...31 та 127) |
isdigit() |
перевіряє чи належить символ множині цифр (0…9) |
isxdigit() |
перевіряє чи належить символ множині шістнадцяткових цифр (A…F, a…f, 0…9) |
isgraph() |
перевіряє чи належить символ множині друкованих символів, крім пробіла (isalpha(), isdigit(), ispunct()) |
islower() |
перевіряє чи належить символ множині латинських літер нижнього регістра (a…z) |
isupper() |
перевіряє чи належить символ множині латинських літер верхнього регістра (A…Z) |
isprint() |
перевіряє чи належить символ множині друкованих символів (isgraph()+ пробіл) |
ispunct() |
перевіряє чи належить символ множині знаків пунктуації |
isspace() |
перевіряє чи належить символ множині символів-роздільників: пробіл, TAB чи нова стрічка |
Функції, імена яких починаються зі слова is, за яким слідує назва множини символів, мають один аргумент цілого типу – код символа. Кожна з таких функцій повертає значення 1, якщо символ належить певній множині символів, і 0 – в іншому випадку.
Розглянемо найбільш поширені алгоритми опрацювання символьних змінних:
1) для визначення кода символа потрібно його значення присвоїти цілій змінній. І, навпаки, щоб визначити, який символ відповідає певному числу, це число потрібно присвоїти змінній символьного типу, при цьому програмне середовище саме виконає всі необхідні перетворення:
Приклад 13.5.
int x;
char c = ‘g’;
x = c;
printf("%c\t%d\n\n", c, x); // Результат: g 103
2) перевірити, чи символ є цифрою, можна за допомогою умови:
char c;
if (c >= ‘0’ && c <= ‘9’) ...
або використавши функцію для перевірки належності символа множині цифр:
if (isdigit(c)) ...
перевірити, чи символ є великою латинською літерою, можна за допомогою умови: if (c >= ‘A’ && c <= ‘Z’) ...
або використавши функцію для перевірки належності символа множині великих латинських літер: if (isupper(c)) ...
перевірити, чи є символ є латинською літерою, можна за допомогою умови:
if (c >= ‘A’ && c <= ‘Z’ || c >= ‘a’ && c <= ‘z’) ...
або використавши функцію відповідною функцією:
if (isalpha(c)) ...
перевірити, чи є символ с малою латинською літерою, можна за допомогою умови: if (c >= ‘a’ && c <= ‘z’) ...
або використавши функцію, що перевіряє належність символа множині малих латинських літер: if (islower(c)) ...
3) для зміни малої латинської літери c на велику (переведення у верхній регістр) можна віднявши від значення символа різницю кодів між відповідними великими і малими літерами, яка дорівнює 32:
char c = c - 32;
або використавши функцію, що збільшує регістр: c = toupper(c);
4) для зміни великої латинської літери c на малу (переведення у нижній регістр) можна додавши до значення символа різницю кодів між відповідними великими і малими літерами, яка дорівнює 32:
char c = c + 32;
або використавши функцію, що зменшує регістр: c = tolower (c);
Розглянуті функції перевірки та зміни регістра працюють лише з латинськими літерами. Для виконання дій 1)–5) з літерами кирилиці слід використовувати перевірку належності символа відповідному символьному діапазону, а для виконання дій 6)–7) зменшення або збільшення коду символа на різницю кодів між великими і малими літерами, яка для більшості символів також дорівнює 32.
13.1.3. Введення та виведення символів
Використання функцій scanf() та printf():
printf("\nEnter char: ");
scanf("%c",&c); // Введення символа
printf("c=%c\n", c); // Виведення символа
Використання функцій cin та cout:
char c;
cin >> c; // Введення символа
cout << c; // Виведення символа
Використання функцій getch() та putch():
int c;
c = getch(); // Введення символа
putch(c); // Виведення символа
Приклад 13.6.
int c;
cputs("Enter ‘Y’ when finished typing keys: "); // Введення повідомлення
do {
c = getch(); // Введення символа
c = toupper(c); // Переведення символа c у верхній регістр
} while(c != ‘Y’);
putch(c); // Виведення символа
Використання функцій getc() та putc():
int c;
c = getc(stdin); // Введення символа
putc(c, stdout); // Виведення символа
Якщо користувач введе більше символів ніж потрібно зчитати, то всі ці функції зчитають лише одних символ, а решту проігнорують. При цьому незчитані символи залишаться в буфері клавіатури і будуть зчитані наступними викликами функцій зчитування даних. Тобто при наступному виклику однієї з функцій зчитування спочатку будуть зчитані ті дані, що є в буфері, і лише після того, як вони повністю будуть зчитані, функції введення зчитають нові дані. Ця проблема буде повторюватися поки буфер повністю не звільниться. Для того, щоб очистити буфер програмно, перед зчитуванням даних потрібно викликати функцію очищення буфера: fflush(stdin);
13.2. Рядки символів
На відміну від інших мов програмування, в мові С немає типу даних стрічка чи рядок. Тут рядок символів є послідовністю символів (масивом символів), який закінчується нуль-символом, тобто символом з кодом 0, що записується як керуюча послідовність ‘\0’ і сприймається як один символ. Нуль-символ ще називають нуль-термінальним або термінальним (завершальним) символом.
Рядкова константа записується у подвійних лапках ("s"), а символьна – в одиничних (‘s’), це константи різних типів. Символ в одиничних лапках є символьною константою, а символ у подвійних лапках – рядковою константою, що крім певного символа, містить ще нуль-символ ‘\0’, який автоматично дописується компілятором.
Особливістю символьного масива є те, що в ньому насправді може бути менше символів, ніж задано в оголошенні.
Пам’ять для розміщення рядків може виділятись як статично (компілятором), так і динамічно (під час виконання програми).
ВАЖЛИВО: довжина динамічного рядка повинна задаватися певною змінною з визначеним наперед значенням, а довжина статичного рядка повинна задаватися лише константою.
13.2.1. Оголошення рядків
Оголошення рядка можна зробити одним із таких способів:
1) пам’ять виділяється компілятором:
Вказівник на рядок
Створення вказівника на “літерний рядок”, але пам’ять під нього не виділяється:
char *s; // оголошення вказівника на перший символ рядка
Тоді якщо на цій ділянці пам’яті будуть розміщені інші змінні, то рядок буде втрачено.
Створення вказівника на константу типу “літерний рядок”, змінити яку неможливо, тому що символ “=” перед літерною константою означає ініціалізацію, а не присвоєння. Операція присвоєння для літерних рядків в мові С невизначена:
char *st = "Lviv city"; // оголошення вказівника на перший символ рядка
Рядок заданої довжини
Синтаксис оголошення та ініціалізації рядка заданої довжини такий:
<тип> <ім’я> [<розмір>] = " <текст> "
char xs[5] = "Lviv"; // Виділено і заповнено 5 байтів
(Для ознайомлення з повним текстом статті необхідно залогінитись)