JavaRush /Курси /C++ SELF /Рядки — std::string, конкатенація, довжина

Рядки — std::string, конкатенація, довжина

C++ SELF
Рівень 2 , Лекція 2
Відкрита

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.

Памʼятка з операцій із рядками

Щоб не тримати все в голові одночасно, зручно мати маленьку «карту місцевості». Нижче — коротка таблиця з тим, що ми сьогодні використали, без занурення в складніші методи рядків: вони будуть пізніше.

Дія Приклад Що відбувається
Створити рядок
std::string s = "Hi";
У s зберігається текст
Склеїти рядки
a + b
Створюється новий рядок
Дописати до рядка
s += part;
s стає довшим
Довжина рядка
s.size()
Отримуємо кількість символів
Символ за індексом
s[i]
Отримуємо один символ (або можемо його замінити)

Ця таблиця — ваш «мінімальний набір», який стане в пригоді майже в будь-якій консольній задачі: від виведення привітання до базової обробки тексту.

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, усе може бути складніше. Але поки що наше завдання — опанувати базову механіку рядків, а не розбиратися з кодуваннями.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ