В уроці №60 ми визначили термін «рядок» як набір послідовних символів (наприклад, Hello, world!
). Рядки — це основний спосіб роботи з текстом в C++, а std::string спрощує цю взаємодію.
Сучасний C++ підтримує два різних типи рядків:
std::string (як частина Стандартної бібліотеки мови С++);
рядки C-style (успадковані від мови Cі).
std::string реалізований за допомогою рядків C-style.
Рядки C-style
Рядок C-style — це простий масив символів, який використовує нуль-термінатор. Нуль-термінатор — це спеціальний символ (ASCII-код якого дорівнює 0
), який використовується для позначення кінця рядку.
Для визначення рядку C-style необхідно просто оголосити масив типу char і ініціалізувати його літералом (наприклад, string
):
1 |
char mystring[] = "string"; |
Хоча string
має лише 6 букв, мова C++ автоматично додає нуль-термінатор в кінець рядку (нам не потрібно додавати його вручну). Відповідно, довжина масиву mystring
дорівнює 7!
Як приклад розглянемо наступну програму, яка виводить довжину рядка, а потім ASCII-коди всіх символів літералу string
:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { char mystring[] = "string"; std::cout << mystring << " has " << sizeof(mystring) << " characters.\n"; for (int index = 0; index < sizeof(mystring); ++index) std::cout << static_cast<int>(mystring[index]) << " "; return 0; } |
Результат виконання програми:
string has 7 characters.
115 116 114 105 110 103 0
Нуль в кінці є ASCII-кодом нуль-термінатора, який був доданий в кінець рядка.
При такому оголошенні рядків рекомендується використовувати квадратні дужки []
, щоб дозволити компілятору визначати довжину масиву самостійно. Таким чином, якщо ви зміните рядок пізніше, вам не доведеться вручну змінювати значення довжини масиву.
Зверніть увагу, що рядки C-style слідують всім тим же правилам, що і масиви. Це означає, що ви можете ініціалізувати рядок при створенні, але після цього не зможете присвоювати йому значення за допомогою оператора присвоювання:
1 2 |
char mystring[] = "string"; // ок mystring = "cat"; // не ок! |
Це те ж саме, якби ми зробили наступне:
1 2 |
int array[] = { 4, 6, 8, 2 }; // ок array = 7; // що це значить? |
Оскільки рядки C-style є масивами, то ви можете використовувати оператор []
для зміни окремих символів в рядку:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { char mystring[] = "string"; mystring[1] = 'p'; std::cout << mystring; return 0; } |
Результат виконання програми:
spring
При виведенні рядка C-style, std::cout виводить символи до тих пір, поки не зустріне нуль-термінатор. Якби ви випадково перезаписали нуль-термінатор в кінці рядка (наприклад, присвоївши що-небудь для mystring[6]
), то ви б не тільки отримали всі символи рядка, але std::cout також вивів би все, що знаходиться в сусідніх комірках пам’яті до тих пір, поки йому не попався би 0!
Зверніть увагу, що це нормально, якщо довжина масиву більше рядка, якого він зберігає:
1 2 3 4 5 6 7 8 9 |
#include <iostream> int main() { char name[15] = "Max"; // використовується тільки 4 символи (3 букви + нуль-термінатор) std::cout << "My name is: " << name << '\n'; return 0; } |
В цьому випадку рядок Max
виведеться на екран, а std::cout зупиниться на нуль-термінаторі. Решта символів в масиві будуть проігноровані.
Рядки C-style і std::cin
Є багато випадків, коли ми не знаємо заздалегідь, наскільки довгим буде наш рядок. Наприклад, розглянемо проблему написання програми, де ми просимо користувача ввести своє ім’я. Наскільки довгим воно буде? Це невідомо доти, доки користувач його не введе!
У такому випадку ми можемо оголосити масив розміром більше, ніж нам потрібно:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { char name[255]; // оголошуємо достатньо великий масив (для зберігання 255 символів) std::cout << "Enter your name: "; std::cin >> name; std::cout << "You entered: " << name << '\n'; return 0; } |
У вищенаведеній програмі ми оголосили масив з 255 символів, припускаючи, що користувач не введе ім’я більше 255 символів. Хоча це і поширена практика, але вона не дуже ефективна, так як користувачу нічого не заважає ввести ім’я більше 255 символів (випадково або навмисно).
Набагато краще зробити наступне:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { char name[255]; // оголошуємо достатньо великий масив (для зберігання 255 символів) std::cout << "Enter your name: "; std::cin.getline(name, 255); std::cout << "You entered: " << name << '\n'; return 0; } |
Виклик cin.getline()
буде приймати до 254 символів в масив name
(залишаючи місце для нуль-термінатора!). Будь-які зайві символи будуть проігноровані. Таким чином, ми можемо гарантувати, що масив не буде переповнений!
Керування рядками C-style
C++ надає безліч функцій для керування рядками C-style, які підключаються за допомогою заголовкового файлу cstring. Ось декілька найбільш корисних функцій:
Функція strcpy_s() — копіює вміст одного рядка в інший. Найчастіше це використовується для присвоювання значень рядку:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <cstring> int main() { char text[] = "Print this!"; char dest[50]; strcpy_s(dest, text); std::cout << dest; // виводимо "Print this!" return 0; } |
Тим не менше, використання strcpy_s() може легко викликати переповнення масиву, якщо не бути обережним! У наступній програмі, довжина масиву dest
менше довжини рядка, який ми копіюємо, тому в результаті ми отримаємо переповнення масиву:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <cstring> int main() { char text[] = "Print this!"; char dest[5]; // зверніть увагу, що довжина масиву dest всього 5 символів! strcpy_s(dest, text); // переповнення! std::cout << dest; return 0; } |
Функція strlen() — повертає довжину рядка C-style (не враховуючи нуль-термінатор):
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <cstring> int main() { char name[15] = "Max"; // використовується тільки 4 символи (3 букви + нуль-термінатор) std::cout << "My name is " << name << '\n'; std::cout << name << " has " << strlen(name) << " letters.\n"; std::cout << name << " has " << sizeof(name) << " characters in the array.\n"; return 0; } |
Результат виконання програми:
My name is Max
Max has 3 letters.
Max has 15 characters in the array.
Зверніть увагу на різницю між strlen() і sizeof. Функція strlen() виводить кількість символів ДО нуль-термінатора, тоді як оператор sizeof повертає розмір цілого масиву, незалежно від того, що в ньому знаходиться.
Ось ще кілька корисних функцій керування рядками C-style:
функція strcat() – додає один рядок до іншого (небезпечно);
функція strncat() – додає один рядок до іншого (з перевіркою розміру місця призначення);
функція strcmp() – порівнює два рядки (повертає 0
, якщо вони рівні);
функція strncmp() – порівнює два рядки до певної кількості символів (повертає 0
, якщо вони рівні).
Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> #include <cstring> int main() { // Просимо користувача ввести рядок char buffer[255]; std::cout << "Enter a string: "; std::cin.getline(buffer, 255); int spacesFound = 0; // Перебираємо кожний символ, який ввів користувач for (int index = 0; index < strlen(buffer); ++index) { // Підраховуємо кількість пробілів if (buffer[index] == ' ') spacesFound++; } std::cout << "You typed " << spacesFound << " spaces!\n"; return 0; } |
Чи варто використовувати рядки C-style?
Знати про рядки C-style варто, так як вони використовуються не так вже й рідко, але використовувати їх без вагомої на те причини — не рекомендується. Замість рядків C-style використовуйте std::string (підключаючи заголовок string), так як він простіший, безпечніший і гнучкіший.
Правило: Використовуйте std::string замість рядків C-style.