Добавить в избранное

std::string в C++: полный разбор работы со строками

Чтение ≈ 5 мин

Всё о строках в C++: создание, конкатенация, поиск, срезы, сравнение, преобразование в числа и обратно, типичные ошибки и правила выбора между std::string, std::string_view и char*.

Зачем вообще нужен std::string

Строки в C-стиле — это просто массивы char, заканчивающиеся нулевым байтом. Они быстрые, но с ними легко выстрелить себе в ногу: переполнение буфера, потерянный нуль-терминатор, ручное управление памятью. std::string из стандартной библиотеки решает эти проблемы — он сам следит за размером, выделяет и освобождает память, предоставляет десятки готовых методов.

Всё, что делает std::string под капотом — это RAII-обёртка над динамическим массивом символов. Когда объект строки выходит из области видимости, память освобождается автоматически. Именно поэтому в современном C++ практически никогда не нужны new char[] и strcpy.

#include <string>
#include <iostream>

int main() {
    std::string s = "Привет, C++";
    s += "!";                       // конкатенация через +=
    std::cout << s.size() << "\n";  // длина в байтах
    std::cout << s << "\n";
}

Важный нюанс для русскоязычных текстов: std::string хранит байты, а не символы. Кириллица в UTF-8 занимает 2 байта на символ, поэтому s.size() для строки «Привет» вернёт 12, а не 6. Об этом подробнее в разделе про Unicode ниже.

Создание и инициализация

Есть несколько способов создать строку, и выбор влияет на читаемость и производительность:

std::string a;                    // пустая строка
std::string b = "текст";          // из C-строки (копия)
std::string c("ещё текст");       // то же самое, прямой вызов
std::string d(10, '*');           // "**********" — повтор символа
std::string e(b);                 // копия b
std::string f = b.substr(0, 5);   // часть другой строки
std::string g(b.begin(), b.end()); // из диапазона итераторов
std::string h = b + c;            // конкатенация при создании

Для литералов доступен суффикс s из пространства std::literals — он создаёт std::string напрямую, без промежуточной C-строки:

using namespace std::literals;
auto s = "это уже std::string"s;

Конкатенация и модификация

Самая частая операция — склеивание строк. Вариантов несколько, и они не равны по скорости.

std::string a = "foo";
a += "bar";                 // эффективно: дописывает в конец
a.append("baz");            // то же самое методом
std::string c = a + "!";    // создаёт НОВУЮ строку

Оператор + удобен, но в цикле порождает лишние копии. Классическая ошибка новичка:

std::string result;
for (int i = 0; i < 1000; ++i)
    result = result + std::to_string(i);  // копия всей строки на каждом шаге!

Правильно — использовать += или append, а при известном итоговом размере заранее вызвать reserve:

std::string result;
result.reserve(5000);  // одна аллокация вместо логарифмических перевыделений
for (int i = 0; i < 1000; ++i)
    result += std::to_string(i);

Другие полезные модификаторы:

s.insert(0, "начало ");   // вставка в позицию
s.erase(0, 3);            // удалить 3 символа с позиции 0
s.replace(0, 4, "замена"); // заменить диапазон
s.clear();                // сделать пустой
s.push_back('!');         // добавить символ
s.pop_back();             // убрать последний символ

Поиск и срезы

Метод find возвращает позицию подстроки или std::string::npos, если ничего не найдено. npos — это максимальное значение size_t, поэтому сравнение всегда пишут именно так:

std::string s = "адрес: /var/log/syslog";

size_t pos = s.find("/var");
if (pos != std::string::npos) {
    std::cout << "найдено на позиции " << pos << "\n";
}

s.find("log", pos + 1);   // поиск со смещения
s.rfind('/');             // поиск с конца — последний '/'
s.find_first_of(":/");    // первый символ из набора
s.find_last_not_of(' ');  // последний не-пробел (для trim)

Срез делается через substr(pos, len). Обратите внимание: второй параметр — это длина, а не конечная позиция:

std::string s = "2026-10-16";
std::string year  = s.substr(0, 4);  // "2026"
std::string month = s.substr(5, 2);  // "10"
std::string tail  = s.substr(5);     // "10-16" — до конца

Сравнение строк

Строки сравниваются операторами ==, !=, <, > — лексикографически, побайтово. Для игнорирования регистра готового метода нет: приводят обе строки к одному регистру или используют std::equal с предикатом:

std::string a = "Hello", b = "hello";

bool equal = a.size() == b.size() &&
    std::equal(a.begin(), a.end(), b.begin(),
        [](char x, char y) { return std::tolower(x) == std::tolower(y); });

Если нужно только проверить префикс/суффикс — в C++20 появились удобные starts_with и ends_with:

if (filename.ends_with(".log")) { /* ... */ }
if (line.starts_with("#"))      { /* комментарий */ }

Преобразования: числа и строки

Стандартная библиотека предоставляет std::to_string и семейство stoi/stol/stof/stod:

int n = std::stoi("42");          // 42
double d = std::stod("3.14");     // 3.14
std::string s = std::to_string(2.71828);

Функции sto* кидают исключения std::invalid_argument и std::out_of_range — в коде, где входные данные приходят от пользователя, оборачивайте в try/catch или используйте std::from_chars (C++17), который не кидает исключений и быстрее:

#include <charconv>

int value;
auto [ptr, ec] = std::from_chars("123", "123" + 3, value);
if (ec == std::errc()) { /* успех */ }

std::string_view — когда копия не нужна

С C++17 есть std::string_view — невладеющий «взгляд» на чужую строку. Он не копирует данные и идеален для параметров функций:

void process(std::string_view sv);  // принимает и std::string, и "литерал", и char*

Главный подвох: string_view не владеет памятью, поэтому нельзя возвращать его на локальную строку — получите висячий указатель.

std::string_view bad() {
    std::string tmp = "local";
    return tmp;   // висячий указатель после выхода!
}

Типичные ошибки и советы

  • .size() не считает символы. Для UTF-8 это байты. Если нужны символы — считайте кодпоинты или используйте ICU.
  • c_str() живёт, пока жива строка. Не сохраняйте указатель после уничтожения или модификации строки.
  • substr с выходом за границы кидает std::out_of_range только если pos > size(). Превышение len — не ошибка, берётся «до конца».
  • + со строковыми литералами: "a" + "b" — это арифметика указателей, не склейка! Пишите "a"s + "b"s или std::string("a") + "b".
  • reserve vs resize: первый меняет только ёмкость (строка остаётся пустой), второй — реальный размер.

Итог

std::string — рабочая лошадка C++. Запомните минимум: += для конкатенации, find/npos для поиска, substr(pos, len) для срезов, reserve для циклов, string_view для параметров. Этого покрывает 95 % повседневных задач, а для остальных 5 % есть std::format, регулярные выражения и std::from_chars.

Продвинутые приёмы

Эффективная склейка множества строк

Когда нужно собрать большую строку из частей — например, склеить вектор слов через запятую — избегайте квадратичной конкатенации:

std::string join(const std::vector<std::string>& parts, char sep) {
    std::string out;
    size_t total = 0;
    for (auto& p : parts) total += p.size() + 1;
    out.reserve(total);
    for (size_t i = 0; i < parts.size(); ++i) {
        if (i) out += sep;
        out += parts[i];
    }
    return out;
}

Форматирование через std::format (C++20)

#include <format>
auto s = std::format("pi={:.2f}, n={:5}", 3.14159, 42);  // "pi=3.14, n=   42"

До C++20 — snprintf в буфер или библиотека fmt.

Парсинг строки по разделителю

std::vector<std::string> split(const std::string& s, char d) {
    std::vector<std::string> out;
    size_t start = 0;
    for (;;) {
        size_t pos = s.find(d, start);
        if (pos == std::string::npos) { out.push_back(s.substr(start)); break; }
        out.push_back(s.substr(start, pos - start));
        start = pos + 1;
    }
    return out;
}

Мини-проект: простой CSV-парсер

Соберите изученное: getline из istringstream для строк, find/substr для полей, stod для чисел — и напишите чтение CSV-файла в vector<vector<string>>. Это 30 строк кода и полезный навык для реальных задач.

Шпаргалка методов

  • s.size(), s.empty(), s.clear() — размер и очистка.
  • s[i], s.at(i), s.front(), s.back() — доступ.
  • s.find(x), s.rfind(x), s.find_first_of(set) — поиск.
  • s.substr(pos, len) — срез; s.insert, s.erase, s.replace — модификация.
  • s.c_str(), s.data() — указатель на C-строку.
  • std::to_string, std::stoi/stod, std::from_chars — числа.
  • s.starts_with, s.ends_with, s.contains — C++20/23.
Поделиться
Понравился материал?

Комментарии

Добавить комментарий