Технологии и методы программирования — на главную
Семестр 2 · ПовторениеМодуль 3. Строки и массивы в C++

Строки

Тип string, ввод через cin и getline, доступ к символам и их изменение, склейка и сравнение, методы size, substr, find, replace, insert, erase, перевод строки в число и обратно, функции <cctype>, разобранные задачи и строки в стиле C.

лекции 2 ч
лаб. 2 ч
СРС 6 ч

До сих пор программа работала с числами: читала их, считала, печатала. Но большая часть данных вокруг — текст: имена, адреса, команды, строки файла. Тип 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.

Показатели оценки

  1. степень соответствия выполненного задания поставленным целям, задачам и требованиям;
  2. оформление, структурирование и комментирование лабораторной работы;
  3. уникальность выполнения работы (отличие от работ коллег);
  4. успешные ответы на контрольные вопросы.

Баллы

  • 86–100
    отлично— оформление соответствует требованиям, критерии выдержаны, защита всего перечня контрольных вопросов.
  • 66–85
    хорошо— оформление соответствует требованиям, критерии выдержаны, защита только до 85 % контрольных вопросов.
  • 51–65
    удовлетворительно— оформление соответствует требованиям, критерии выдержаны, защита только до 51 % контрольных вопросов.
  • 0–50
    неудовлетворительно— оформление не соответствует требованиям, критерии не выдержаны, защита менее 51 % контрольных вопросов.

Проверьте себя

Викторина по теме

Вопрос 1 из 10
что напечатает программа

Что напечатает программа?

string date = "2026-09-19";
cout << date.substr(5, 2);

Ссылки на литературу

  1. Воробьев, Г. А. Основы программирования на C++ : учебно-методическое пособие. — Липецк : Липецкий ГПУ, 2022. — 89 с. — ISBN 978-5-907461-84-0. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
  2. Калитвин, В. А. Введение в программирование на C++ : учебное пособие. — Липецк : Липецкий ГПУ, 2023. — 84 с. — ISBN 978-5-907655-86-7. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
  3. Широбокова, С. Н., Кацупеев, А. А., Сулыз, А. В. Программирование на языке C++ для лабораторных занятий : учебное пособие. — Новочеркасск : ЮРГПУ, 2020. — 104 с. — ISBN 978-5-9997-0725-3. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
Мой прогресс

Содержание