1. Введение
Когда вы только начинаете программировать, легко попасть в иллюзию, что «главное — числа». Ну правда: оценки, деньги, возраст, счётчики — везде цифры. Но как только вы захотите сделать программу хоть немного дружелюбной к человеку, внезапно выясняется: человек общается словами. Имя, фамилия, город, комментарий к заказу, название фильма — всё это текст, то есть строка.
Когда С++ только появился, то типа строка (string) в нем не было - строку воспринимали просто как набор символов (тип char). Однако со временем выяснилось, что это очень неудобно, и было решено создать отдельный тип string и добавить его в стандартнутю (STD) библиотеку С++. Так в C++ появился тип std::string.
Его можно воспринимать как контейнер для последовательности символов: внутри лежат буквы, пробелы, знаки пунктуации. И, что особенно приятно, стандартная библиотека даёт нам готовые операции для строк: склеивать, узнавать длину, брать символ по номеру.
Чтобы покачать работу с std, мы будем развивать одно небольшое приложение по мере курса: «консольная визитка». Пока оно будет просто собирать красивую строку визитки и печатать её. Сегодня мы добавим туда имя и пару текстовых деталей.
2. Первая строка: подключаем <string>
Сейчас будет момент, который сначала кажется мелочью, а потом становится второй натурой: чтобы использовать std::string, обычно нужен заголовок <string>. Да, в некоторых компиляторах «как-то работает и без него», но это плохая привычка: лучше подключать то, что вы используете.
Начнём с простого: создадим строку из строкового литерала "..." и выведем её.
#include <iostream>
#include <string>
int main() {
std::string course = "C++ for beginners";
std::cout << course << '\n'; // C++ for beginners
}
Здесь важна идея: 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 = "Name: ";
badge += "Ada";
badge += " ";
badge += "Lovelace";
std::cout << badge << '\n'; // Name: 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++ это опасно тем, что программа может вести себя непредсказуемо: иногда «как будто работает», иногда печатает странные символы, иногда падает. И это как раз тот случай, когда «иногда работает» хуже, чем «сразу падает», потому что баг прячется и ждёт подходящего момента.
Можно ли менять символы в строке?
Да, символ по индексу можно заменить: строка становится изменяемой, и это удобно для простых правок.
#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 name: " << full << '\n'; // Full name: Ada Lovelace
std::cout << "Length: " << full.size() << '\n'; // Length: 12
std::cout << "First letter: " << full[0] << '\n'; // First letter: 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) это может быть сложнее, но пока наша задача — освоить базовую механику строк, а не войну с кодировками.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ