1. Вступ
Коли ви тільки починаєте програмувати, легко повірити, ніби «головне — числа». І справді: оцінки, гроші, вік, лічильники — всюди цифри. Але щойно ви захочете зробити програму бодай трохи зручнішою для людини, одразу зʼясується: люди спілкуються словами. Імʼя, прізвище, місто, коментар до замовлення, назва фільму — усе це текст, тобто рядок.
Коли C++ лише зʼявився, окремого типу рядка (string) у ньому не було: рядок просто розглядали як набір символів типу char. Однак згодом стало зрозуміло, що це дуже незручно, тож створили окремий тип string і додали його до стандартної бібліотеки C++. Так у C++ зʼявився тип std::string.
Його можна сприймати як контейнер для послідовності символів: літер, пробілів, розділових знаків. І, що особливо зручно, стандартна бібліотека дає нам готові операції для роботи з рядками: їх можна поєднувати, визначати довжину, брати символ за індексом.
Щоб краще опанувати роботу з рядками, ми розвиватимемо один невеликий застосунок упродовж курсу: «консольну візитку». Поки що він просто збиратиме гарний рядок візитки й виводитиме його на екран. Сьогодні ми додамо туди імʼя та кілька текстових деталей.
2. Перший рядок: підключаємо <string>
Зараз буде момент, який спочатку здається дрібницею, а згодом стає другою натурою: щоб використовувати std::string, зазвичай потрібно підключити заголовок <string>. Так, у деяких компіляторах це «якось працює й без нього», але це погана звичка. Краще підключати саме те, чим ви користуєтеся.
Почнемо з простого: створімо рядок із рядкового літерала "..." і виведімо його.
#include <iostream>
#include <string>
int main() {
std::string course = "C++ для початківців";
std::cout << course << '\n'; // C++ для початківців
}
Тут важлива проста ідея: std::string — це тип, як int, тільки для тексту. Ми оголосили змінну course і поклали в неї текст. Далі вона поводиться як значення, яке можна передавати у виведення.
Рядок "A" і символ 'A'
У програмуванні є жарт: «дві лапки вирішують долю проєкту». І, на жаль, це майже правда. У C++ є рядкові літерали в подвійних лапках "..." і символьні літерали в одинарних '...'. Різниця принципова: рядок — це послідовність символів, а символ — це рівно один символ.
Якщо вам потрібно зберігати імʼя "Ada", це рядок. Якщо потрібен один символ, наприклад перша літера імені 'A', це вже символ. std::string зберігає рядок, а тип для одного символу ми поки що сприйматимемо просто як «один знак» (формально це char, але про це пізніше).
Порівняйте:
#include <iostream>
#include <string>
int main() {
std::string word = "Cat"; // рядок тексту
char firstLetter = 'C'; // один символ
std::cout << word << '\n'; // Cat
std::cout << firstLetter << '\n'; // C
}
Зовні це схоже, але зміст інший: word може бути будь-якої довжини, а firstLetter — це рівно один символ.
3. Конкатенація: + і +=
Переходимо до дуже «людської» операції: зібрати з окремих шматочків одну гарну фразу. У рядках це називається конкатенацією. Уявіть, що рядок — це конструктор LEGO: замість кубиків тут слова й пробіли. А ви збираєте речення, яке потім покажете користувачу.
Є два основні способи поєднання:
- a + b створює новий рядок, ніби ви взяли два аркуші й зробили третій, де написано все разом.
- a += b дописує до наявного рядка, ніби ви просто продовжили писати на тому самому аркуші.
Почнемо з +:
#include <iostream>
#include <string>
int main() {
std::string first = "Ada";
std::string last = "Lovelace";
std::string full = first + " " + last;
std::cout << full << '\n'; // Ada Lovelace
}
Зверніть увагу на " ". Пробіл — це теж символ і часто важлива частина сенсу. Якщо про нього забути, вийде AdaLovelace, а це вже більше схоже на назву класу, ніж на імʼя людини.
Тепер варіант із +=. Він зручний, коли ви будуєте рядок поступово:
#include <iostream>
#include <string>
int main() {
std::string badge = "Імʼя: ";
badge += "Ada";
badge += " ";
badge += "Lovelace";
std::cout << badge << '\n'; // Імʼя: Ada Lovelace
}
Такий запис зручно читати, коли ви збираєте рядок крок за кроком. У майбутньому, коли зʼявляться умови та цикли, += буде особливо корисним. Але навіть зараз цей запис добре показує саму ідею «дописування».
Є й одна важлива ремарка: рядок і число не можна просто взяти й «скласти» як рядки. Наприклад, "Age: " + 10 — так не працює, тому що це різні типи. Ми поки що не заглиблюватимемося в перетворення; просто зафіксуємо: рядки поєднуються з рядками.
4. Довжина рядка: size()
Коли ви працюєте з рядком, часто потрібно розуміти, скільки в ньому символів. Для цього у std::string є метод size() (іноді також трапляється length(), але сьогодні нам достатньо size()). Це як спідометр, який показує, скільки символів зараз є в рядку.
Подивімося, як це працює:
#include <iostream>
#include <string>
int main() {
std::string city = "Boston";
std::cout << city.size() << '\n'; // 6
}
Важливо звикнути до простої думки: size() — це перше, що варто згадати, перш ніж звертатися до символів за індексом. Індексація без розуміння довжини — це як іти сходами в темряві й думати, що вони нескінченні. Насправді сходинки закінчуються.
Ось ще один приклад, де довжина допомагає побачити: пробіл — теж символ.
#include <iostream>
#include <string>
int main() {
std::string s = "Hi Bob";
std::cout << s.size() << '\n'; // 6 (H i _ B o b)
}
5. Індексування: s[i]
Як ви вже знаєте, рядок — це набір символів. А якщо це набір символів, то можна отримувати окремі символи за їхнім індексом.
У програмуванні майже вся нумерація починається з нуля. Це означає, що перший символ — це s[0], другий — s[1], і так далі.
Уявімо рядок як ящик із комірками:
flowchart LR
A["s = 'c a t'"] --> B["індекси: 0, 1, 2"]
Подивімося на код:
#include <iostream>
#include <string>
int main() {
std::string animal = "cat";
std::cout << animal[0] << '\n'; // c
std::cout << animal[1] << '\n'; // a
std::cout << animal[2] << '\n'; // t
}
Про межі
Щоб менше плутатися, у програмуванні зручніше вживати термін індекс замість номер. Індекс завжди починається з нуля.
Індекс має бути в діапазоні від 0 до s.size() - 1. Якщо рядок має довжину 3, допустимі індекси — 0, 1, 2. Якщо спробувати взяти animal[3], ви вийдете за межі рядка, і це може призвести до помилки.
У C++ це небезпечно, бо програма може поводитися непередбачувано: іноді «ніби працює», іноді друкує дивні символи, а іноді падає. І це якраз той випадок, коли «іноді працює» гірше, ніж «одразу падає», бо така помилка ховається й чекає слушного моменту.
Чи можна змінювати символи в рядку?
Так, символ за індексом можна замінити: рядки в C++ змінювані, і це зручно для простих правок.
#include <iostream>
#include <string>
int main() {
std::string word = "bat";
word[0] = 'c';
std::cout << word << '\n'; // cat
}
Зверніть увагу: праворуч стоїть 'c' в одинарних лапках, тому що ми присвоюємо один символ, а не рядок "c".
6. Перше знайомство з методами
Ви, мабуть, помітили дивний запис:
city.size()
Чому ми не пишемо просто size(city)? Тому що size() — це метод.
Метод — це функція, яка належить конкретному типу або обʼєкту. У нашому випадку std::string — це тип, а city — обʼєкт цього типу. І в рядків є свої «вбудовані вміння»: визначати довжину, дописувати текст, отримувати символ за індексом та багато іншого.
Коли функція належить обʼєкту, до неї звертаються через крапку:
обʼєкт.метод()
Крапку тут можна читати майже по-людськи: «Візьміть обʼєкт city і викличте для нього метод size()».
Тобто рядок ніби сам знає, як визначити свою довжину. У цьому й полягає ідея методів: поведінка повʼязана з даними.
Подивіться на вже знайомі приклади:
std::string full = first + " " + last;
std::cout << full.size() << '\n';
std::cout << full[0] << '\n';
full.size() — рядок сам повідомляє свою довжину. full[0] — тут ми звертаємося до його внутрішнього символу.
Пізніше ви побачите, що майже всі складні типи в C++ мають власні методи. Наприклад, у std::vector є push_back(), у std::string є substr(), у потоків є clear(). Це один із ключових принципів мови: дані та повʼязані з ними операції обʼєднані.
Важливо звикнути до такого запису. Усе, що виглядає так:
щось_таке.щось_таке()
майже завжди означає виклик методу обʼєкта.
Саме тому ми пишемо full.size(), а не просто size(full). Рядок сам надає інструменти для роботи із собою.
7. Міні-застосунок: консольна візитка
Тепер зберімо все в один невеликий, але цілісний приклад: зробімо візитку, яка друкує імʼя, довжину повного імені та першу літеру. Поки що дані просто візьмемо з коду (введення буде в наступній лекції), щоб не змішувати нові теми в одну купу.
У цьому прикладі зʼявляється відчуття застосунку: не просто окремі операції, а маленьке корисне виведення.
#include <iostream>
#include <string>
int main() {
std::string first = "Ada";
std::string last = "Lovelace";
std::string full = first + " " + last;
std::cout << "Повне імʼя: " << full << '\n'; // Повне імʼя: Ada Lovelace
std::cout << "Довжина: " << full.size() << '\n'; // Довжина: 12
std::cout << "Перша літера: " << full[0] << '\n'; // Перша літера: A
}
Тут одразу є кілька важливих звичок.
По-перше, ми явно додали пробіл між іменем і прізвищем, тому що компʼютер не здогадається, що між словами має бути пробіл. По-друге, ми вивели довжину рядка: це допомагає побачити, що пробіл теж рахується. По-третє, ми взяли full[0] і дістали перший символ, а це в майбутньому дасть нам змогу формувати ініціали або прості скорочення.
Якщо хочеться трохи поекспериментувати, можна дістати останню літеру прізвища. Формально остання літера має індекс size() - 1. Ми робимо це лише для завідомо непорожнього рядка, щоб не ризикувати виходом за межі:
#include <iostream>
#include <string>
int main() {
std::string last = "Lovelace";
char lastChar = last[last.size() - 1];
std::cout << lastChar << '\n'; // e
}
Так, вираз виглядає трохи «математично», але логіка проста: якщо довжина 8, індекси йдуть 0…7, отже останній — 7, тобто size() - 1.
Памʼятка з операцій із рядками
Щоб не тримати все в голові одночасно, зручно мати маленьку «карту місцевості». Нижче — коротка таблиця з тим, що ми сьогодні використали, без занурення в складніші методи рядків: вони будуть пізніше.
| Дія | Приклад | Що відбувається |
|---|---|---|
| Створити рядок | |
У s зберігається текст |
| Склеїти рядки | |
Створюється новий рядок |
| Дописати до рядка | |
s стає довшим |
| Довжина рядка | |
Отримуємо кількість символів |
| Символ за індексом | |
Отримуємо один символ (або можемо його замінити) |
Ця таблиця — ваш «мінімальний набір», який стане в пригоді майже в будь-якій консольній задачі: від виведення привітання до базової обробки тексту.
8. Типові помилки під час роботи зі std::string
Помилка № 1: забули #include <string>.
Іноді код «ніби» компілюється через непрямі підключення, а іноді раптом перестає. Правильна звичка проста: використовуєте std::string — підключайте <string>. Це не примха, а нормальна дисципліна: навіть у прикладах і документації трапляються виправлення, де додають відсутній include, бо інакше приклад буде некоректним.
Помилка № 2: переплутали "A" і 'A'.
Коли ви пишете word[0] = "b";, компілятор цілком справедливо скаже: ліворуч один символ, а праворуч рядок. Для одного символу використовуйте одинарні лапки: 'b'. Для тексту — подвійні: "b".
Помилка № 3: забули про пробіл під час конкатенації.
Класика: first + last дає «AdaLovelace», а ви потім довго дивитеся на результат і думаєте, що програма зламалася. Насправді вона просто надто буквальна: не попросили пробіл — не додала. Тому пробіл " " треба додавати явно.
Помилка № 4: індекс вийшов за межі рядка.
s[100] у рядка "Hi" — це не «порожнеча», не null і не «просто нічого». Це вихід за межі, і поведінка програми стає непередбачуваною. До появи if ми просто не використовуємо такі індекси навмання й завжди спираємося на size().
Помилка № 5: очікували, що size() — це «кількість літер у будь-якій мові».
На початку курсу ми зазвичай працюємо з простими латинськими прикладами, де один символ приблизно дорівнює одній «літері». У реальних текстах, особливо з кирилицею в UTF‑8, усе може бути складніше. Але поки що наше завдання — опанувати базову механіку рядків, а не розбиратися з кодуваннями.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ