Зачем вообще нужен 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".reservevsresize: первый меняет только ёмкость (строка остаётся пустой), второй — реальный размер.
Итог
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.