Строки
Тип string, ввод через cin и getline, доступ к символам и их изменение, склейка и сравнение, методы size, substr, find, replace, insert, erase, перевод строки в число и обратно, функции <cctype>, разобранные задачи и строки в стиле C.
До сих пор программа работала с числами: читала их, считала, печатала.
Но большая часть данных вокруг — текст: имена, адреса, команды, строки файла.
Тип string позволяет хранить текст в одной переменной и обращаться с ним
почти так же просто, как с числом: складывать, сравнивать, передавать дальше.
Отдельно стоит запомнить главное отличие от числа: у строки есть длина и есть отдельные символы, к которым можно добраться по номеру. Из этого растут почти все задачи урока.
Тип string
Строка объявляется как обычная переменная. Начальное значение — текст
в двойных кавычках; одинарные кавычки означают одиночный символ типа char
и для строки не годятся.
string greeting = "Hello";
string name("world");
string empty;
cout << greeting << ", " << name << "!" << endl;
cout << "длина greeting: " << greeting.size() << endl;
cout << "пустая ли empty: " << empty.empty() << endl;Разберём, что здесь нового:
greeting.size()— длина строки, количество символов. Уstringесть и методlength(): это то же самое, разными именами, выбирайте любой.empty.empty()— признак «строка пуста». Печатается1, потому чтоboolвыводится числом: строкаemptyобъявлена без начального значения, и в ней ноль символов.- Запись через точку — вызов метода: функции, привязанной к конкретной
переменной. Это то же самое действие, что
cout << ..., только записанное с другой стороны: сначала объект, потом что с ним сделать.
Русские буквы занимают больше одного символа
Исходный текст программы хранится в кодировке UTF-8, и в ней латинская буква
занимает один байт, а кириллическая — два. string считает именно байты,
поэтому string s = "Привет"; s.size() даст 12, а не 6, и s[0]
не будет буквой «П».
Это не ошибка string, а плата за то, что тип не знает ничего о языке текста.
Поэтому во всех примерах ниже, где важны длина и отдельные символы, текст
латинский. Для русского текста посимвольная работа требует других
инструментов и в курс не входит; выводить и хранить кириллицу string умеет
прекрасно — этого достаточно.
Ввод строки: cin >> и getline
Привычное cin >> s для строки работает, но читает одно слово: ввод
заканчивается на первом же пробеле или переводе строки. Введите в примере ниже
Ivan Petrov — во вторую переменную попадёт фамилия, а не остаток строки.
string first, second;
cin >> first;
cout << "первое слово: [" << first << "]" << endl;
cin >> second;
cout << "второе слово: [" << second << "]" << endl;Квадратные скобки в выводе — приём, которым стоит пользоваться при отладке: они показывают границы строки, и сразу видно, попал ли в неё лишний пробел.
Чтобы прочитать строку целиком, вместе с пробелами, есть функция getline:
string line;
getline(cin, line);
cout << "[" << line << "]" << endl;
cout << "символов: " << line.size() << endl;getline(cin, line) читает всё до конца строки, сам перевод строки
выбрасывает, а в line кладёт остальное.
Дальше — ловушка, на которую попадаются все. Если перед getline был обычный
cin >>, то перевод строки после числа остаётся непрочитанным, и getline
честно читает то, что осталось до конца строки, — пустоту:
int age;
string name;
cin >> age;
getline(cin, name); // читает остаток строки с числом — пустую
cout << "возраст: " << age << endl;
cout << "имя: [" << name << "]" << endl;Лечится одной командой: перед getline нужно пропустить всё, что осталось
в строке с числом.
int age;
string name;
cin >> age;
cin.ignore(10000, '\n'); // выбросить остаток строки с числом
getline(cin, name);
cout << "возраст: " << age << endl;
cout << "имя: [" << name << "]" << endl;cin.ignore(10000, '\n') означает «пропусти до 10000 символов, но остановись
сразу после перевода строки». Правило простое: смешиваете cin >>
и getline — ставьте cin.ignore между ними.
Символы строки
К символу строки обращаются по номеру в квадратных скобках, и нумерация
начинается с нуля: у строки из n символов номера идут от 0 до n - 1.
Символ — это char, и с ним работают все правила из урока о типах данных.
string s = "programming";
int n = s.size();
cout << "первый символ: " << s[0] << endl;
cout << "последний символ: " << s[n - 1] << endl;
cout << "по одному: ";
for (int i = 0; i < n; i++) {
cout << s[i] << ".";
}
cout << endl;
cout << "только буквы m: ";
for (int i = 0; i < n; i++) {
if (s[i] == 'm') cout << i << " ";
}
cout << endl;Обратите внимание на int n = s.size(); перед циклом. Так делают не только ради
краткости: size() возвращает беззнаковое целое, и сравнение его со знаковым
i компилятор встречает предупреждением. Один раз записать длину в int —
самый простой способ этого избежать.
Символы можно не только читать, но и менять — строка изменяемая:
string s = "hello";
s[0] = 'H'; // заглавная первая буква
cout << s << endl;
for (int i = 0; i < (int)s.size(); i++) {
if (s[i] == 'l') s[i] = 'L'; // заменить все l на L
}
cout << s << endl;Здесь длина понадобилась один раз, и она приведена к int прямо в условии —
запись (int)s.size() делает то же самое, что отдельная переменная.
Если перебирать символы нужно, а их номера — нет, цикл пишется короче:
string s = "abcabc";
int count = 0;
for (char c : s) { // «для каждого символа c из строки s»
if (c == 'a') count++;
}
cout << "букв a: " << count << endl;Это цикл по диапазону: переменная c на каждом шаге получает очередной
символ. Важная тонкость: c — копия символа, и присваивание c = 'x' изменит
копию, а не строку. Чтобы менять, нужна ссылка — for (char &c : s).
Выход за границы строки ничем не помогает
s[i] при i больше длины не выдаёт ошибку: программа просто читает чужую
память и продолжает работать — до момента, когда сломается в другом месте.
Если хочется проверки, есть метод at: s.at(i) при выходе за границы
прерывает программу с сообщением. В учебных задачах проще следить за границами
самому, но знать про at полезно.
Склейка и сравнение
Строки складываются оператором +: результат — новая строка, в которой
второй операнд приписан к первому. Работает и +=.
string first = "Ivan";
string last = "Petrov";
string full = first + " " + last;
cout << full << endl;
full += "!"; // приписать в конец
cout << full << endl;
string line;
for (int i = 0; i < 5; i++) {
line += '-'; // к строке можно прибавлять и один символ
}
cout << line << endl;Складывать две строковые константы ("a" + "b") нельзя — хотя бы одна
из сторон должна быть string. Поэтому string full = first + " " + last;
работает, а string full = "Ivan" + " " + "Petrov"; не скомпилируется.
Сравниваются строки привычными операторами. == проверяет полное совпадение,
а < и > сравнивают лексикографически: по первому различающемуся символу,
по его коду. Порядок получается почти словарный, с одной оговоркой —
все заглавные латинские буквы идут раньше строчных.
cout << ("apple" == string("apple")) << endl; // 1 — совпадают
cout << (string("apple") < "banana") << endl; // 1 — a раньше b
cout << (string("apple") < "applesauce") << endl; // 1 — короткая раньше
cout << (string("Zebra") < "apple") << endl; // 1 — 'Z' = 90, 'a' = 97Последняя строка — та самая оговорка: "Zebra" оказывается «меньше»
"apple", потому что сравниваются коды символов, а не буквы алфавита.
Если нужен человеческий порядок, строки перед сравнением приводят
к одному регистру — как это сделать, ниже.
Методы строк
Строка умеет больше, чем size() и empty(). Вот методы, которых хватает
на подавляющее большинство учебных задач.
substr — вырезать кусок
s.substr(pos, len) возвращает подстроку длины len, начиная с номера pos.
Если len не указать, берётся весь остаток строки.
string date = "2026-09-19";
cout << date.substr(0, 4) << endl; // год
cout << date.substr(5, 2) << endl; // месяц
cout << date.substr(8) << endl; // день — и всё, что послеfind — найти подстроку
s.find(что) возвращает номер, с которого начинается первое вхождение.
Если вхождения нет, возвращается особое значение string::npos — «не найдено».
Проверять надо именно его: результат find — номер, и нулевой номер
означает «нашлось в самом начале», а вовсе не «не нашлось».
string s = "the quick brown fox";
cout << s.find("quick") << endl; // 4
cout << s.find('o') << endl; // 12 — первая o
cout << (s.find("cat") == string::npos) << endl; // 1 — не найдено
if (s.find("fox") != string::npos) {
cout << "лиса на месте" << endl;
}Поиск можно продолжить с нужного места: s.find("o", 13) ищет со символа
с номером 13. На этом строится подсчёт всех вхождений:
string s = "abracadabra";
string what = "abra";
int count = 0;
size_t pos = s.find(what);
while (pos != string::npos) {
count++;
pos = s.find(what, pos + 1); // искать дальше, со следующего символа
}
cout << "вхождений: " << count << endl;Тип size_t здесь не прихоть: find возвращает именно его, и npos —
тоже значение этого типа. Если объявить pos как int, сравнение
с string::npos перестанет работать так, как выглядит.
insert, erase, replace — изменить содержимое
string s = "programming";
s.insert(0, "C++ "); // вставить в позицию 0
cout << s << endl;
s.erase(0, 4); // удалить 4 символа с позиции 0
cout << s << endl;
s.replace(0, 7, "gram"); // заменить 7 символов с позиции 0 на "gram"
cout << s << endl;
s.clear(); // опустошить строку целиком
cout << "осталось символов: " << s.size() << endl;Все три метода меняют саму строку, а не возвращают новую, — в отличие
от substr. Это частая причина путаницы: s.substr(0, 3) строку не портит,
а s.erase(0, 3) портит.
Связка find + replace даёт замену подстроки по содержимому, а не по номеру:
string s = "мне нравится Python";
string what = "Python";
string to = "C++";
size_t pos = s.find(what);
if (pos != string::npos) {
s.replace(pos, what.size(), to);
}
cout << s << endl;Строки и числа
Строка "123" и число 123 — разные вещи: первое три символа, второе одно
значение. Переводы в обе стороны делаются готовыми функциями из <string>:
string s = "2026";
int year = stoi(s); // строка → int
cout << year + 1 << endl; // 2027 — это уже число
double price = stod("19.99"); // строка → double
cout << price * 2 << endl;
int n = 42;
string text = to_string(n) + " попугаев"; // число → строка
cout << text << endl;
cout << "цифр в числе: " << to_string(n).size() << endl;Последняя строка — приём, который стоит запомнить: чтобы узнать количество цифр числа, не обязательно делить его на 10 в цикле; можно превратить его в строку и спросить длину.
stoi на строке, которая числом не является (stoi("abc")), прерывает
программу. В учебных задачах ввод обычно не проверяют, но помнить об этом стоит.
Функции для символов
Отдельные символы удобно разбирать функциями из заголовка <cctype>:
isdigit, isalpha, isspace, toupper, tolower. Этого заголовка нет
среди подключаемых по умолчанию, поэтому пример — целиком, с #include
и main.
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main()
{
string s = "Room 101, floor 3";
int digits = 0, letters = 0, spaces = 0;
for (char c : s) {
if (isdigit(c)) digits++;
else if (isalpha(c)) letters++;
else if (isspace(c)) spaces++;
}
cout << "цифр " << digits << ", букв " << letters
<< ", пробелов " << spaces << endl;
string upper;
for (char c : s) {
upper += char(toupper(c)); // toupper возвращает int — приводим обратно
}
cout << upper << endl;
return 0;
}Две детали. Первая: toupper возвращает int, и без приведения char(...)
в строку попало бы число. Вторая: эти функции знают только латиницу —
на кириллице toupper ничего не сделает по той же причине, о которой
сказано в начале урока.
Приведение к одному регистру — это и есть способ сравнивать строки
«по-человечески»: приводим обе к нижнему регистру и сравниваем обычным ==.
Разобранные задачи
Подсчёт гласных
string line;
getline(cin, line);
string vowels = "aeiouy";
int count = 0;
for (char c : line) {
if (vowels.find(c) != string::npos) count++;
}
cout << "гласных: " << count << endl;Приём с vowels.find(c) заменяет длинное условие с шестью ||: строка
vowels работает как список допустимых символов. Тот же приём годится
для проверки «символ — разделитель», «символ — знак операции» и так далее.
Разворот строки
string s;
cin >> s;
int n = s.size();
for (int i = 0; i < n / 2; i++) {
char temp = s[i];
s[i] = s[n - 1 - i];
s[n - 1 - i] = temp;
}
cout << s << endl;Цикл идёт до середины: каждый шаг меняет местами сразу два символа, и полный проход развернул бы строку дважды, вернув её в исходный вид. Средний символ нечётной строки остаётся на месте — и правильно.
Обмен значений через временную переменную temp — тот самый приём, который
пригодится в следующем уроке при работе с массивами: без temp первое же
присваивание затрёт значение, которое ещё нужно.
Палиндром
string s;
cin >> s;
int n = s.size();
bool isPalindrome = true;
for (int i = 0; i < n / 2; i++) {
if (s[i] != s[n - 1 - i]) {
isPalindrome = false;
}
}
if (isPalindrome) cout << "палиндром" << endl;
else cout << "не палиндром" << endl;Разворачивать строку и сравнивать с исходной не нужно: достаточно пройти
до середины и сравнить симметричные символы. Проверьте на level,
на abcba и на abca.
Подсчёт слов
string line;
getline(cin, line);
int words = 0;
bool inWord = false;
for (char c : line) {
if (c != ' ') {
if (!inWord) { // начало нового слова
words++;
inWord = true;
}
} else {
inWord = false; // слово кончилось
}
}
cout << "слов: " << words << endl;Считать пробелы и прибавлять единицу — решение неверное: в примере пробелы
идут подряд, а в начале и в конце строки их вообще быть не должно.
Правильный признак слова — переход из пробела в не-пробел, и флаг inWord
ровно это и отслеживает.
Строки в стиле C
Кроме string в C++ есть и более старый способ хранить текст — массив
символов, доставшийся от языка C. Он встречается в чужом коде, в примерах
из книг и в аргументах main, поэтому его стоит узнавать.
Строка в стиле C — это char s[], у которого последний символ — нулевой
байт '\0'. Он не печатается, но именно по нему все функции понимают,
где строка кончилась.
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char a[20] = "Hello";
char b[20];
cout << "длина a: " << strlen(a) << endl; // 5, сам '\0' не считается
strcpy(b, a); // скопировать a в b
strcat(b, ", world"); // приписать в конец
cout << b << endl;
cout << "сравнение: " << strcmp(a, "Hello") << endl; // 0 — строки равны
return 0;
}Что здесь важно:
- размер массива (
20) задаётся заранее, и расти строка сама не может — в отличие отstring, который перевыделяет память сам; - сравнение
a == bне сравнивает текст: для массивов это сравнение адресов, и оно почти всегда даёт «не равно»; сравнивают функциейstrcmp, которая возвращает0при совпадении; strcpyиstrcatне проверяют, влезает ли результат: если не влезет, программа испортит чужую память. Это и есть та самая причина, по которой в C++ пишутstring, аchar[]оставляют для чтения чужого кода.
Перевод между двумя видами строк односторонне простой: string s = a;
работает как есть, а обратно — s.c_str().
Что выбирать в своих программах
string. Он сам следит за длиной, сам выделяет память, складывается
оператором +, сравнивается оператором == и не умеет молча испортить
соседние данные. Массив char[] берут, когда этого требует чужой
интерфейс, — и тогда пользуются функциями из <cstring>.
Задание к лабораторной
Вариант 1 (из рабочей программы). Программа считывает строку-разделитель
и три строки, а затем выводит указанные строки через разделитель. Проверьте
работу на разделителях из одного символа (,) и из нескольких (—),
а также на пустых строках.
Вариант 2. Программа читает строку и печатает: её длину, количество букв,
цифр и пробелов, а также саму строку в верхнем и нижнем регистре. Ввод
латинский, чтение — getline.
Вариант 3. Программа читает строку и слово, после чего печатает количество вхождений слова в строку и номера позиций, с которых они начинаются. Предусмотрите случай, когда вхождений нет.
Вариант 4. Программа читает фамилию, имя и отчество (три отдельные строки)
и печатает фамилию с инициалами: Petrov I. S. Отчество может отсутствовать —
тогда печатается только один инициал.
Вариант 5. Программа читает строку и проверяет, является ли она
палиндромом, игнорируя пробелы и регистр. Для a man a plan a canal panama
ответ должен быть «палиндром».
Вариант 6. Шифр Цезаря: программа читает строку и число k, после чего
сдвигает каждую латинскую букву на k позиций по алфавиту, оставляя остальные
символы без изменения. Буквы z и Z должны переходить в начало алфавита.
В отчёте покажите, что расшифровка сдвигом -k восстанавливает исходный текст.
Как оценивается лабораторная
Работа сдаётся преподавателю очно: сайт её не принимает и не оценивает. Шкала и показатели ниже — из рабочей программы дисциплины, раздел 7.
Показатели оценки
- степень соответствия выполненного задания поставленным целям, задачам и требованиям;
- оформление, структурирование и комментирование лабораторной работы;
- уникальность выполнения работы (отличие от работ коллег);
- успешные ответы на контрольные вопросы.
Баллы
- 86–100отлично— оформление соответствует требованиям, критерии выдержаны, защита всего перечня контрольных вопросов.
- 66–85хорошо— оформление соответствует требованиям, критерии выдержаны, защита только до 85 % контрольных вопросов.
- 51–65удовлетворительно— оформление соответствует требованиям, критерии выдержаны, защита только до 51 % контрольных вопросов.
- 0–50неудовлетворительно— оформление не соответствует требованиям, критерии не выдержаны, защита менее 51 % контрольных вопросов.
Проверьте себя
Викторина по теме
Что напечатает программа?
string date = "2026-09-19";
cout << date.substr(5, 2);Ссылки на литературу
- Воробьев, Г. А. Основы программирования на C++ : учебно-методическое пособие. — Липецк : Липецкий ГПУ, 2022. — 89 с. — ISBN 978-5-907461-84-0. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
- Калитвин, В. А. Введение в программирование на C++ : учебное пособие. — Липецк : Липецкий ГПУ, 2023. — 84 с. — ISBN 978-5-907655-86-7. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
- Широбокова, С. Н., Кацупеев, А. А., Сулыз, А. В. Программирование на языке C++ для лабораторных занятий : учебное пособие. — Новочеркасск : ЮРГПУ, 2020. — 104 с. — ISBN 978-5-9997-0725-3. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
Модуль 3. Строки и массивы в C++
Предыдущая тема
Массивы
Назначение массивов, объявление и инициализация, индексы с нуля и выход за границы, ввод и вывод циклом, сумма, минимум и максимум, поиск, перестановка элементов, сортировка пузырьком и выбором, передача массива в функцию, динамические массивы new и delete.