Menu

Строки в C++: основы std::string, методы и подводные камни

Как использовать std::string в C++ - безопасно собирать, объединять, искать и нарезать текст, а также почему для реальной работы почти никогда не нужен сырой char*.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

std::string: текст, который управляет собой сам

На предыдущей странице вы видели, как умные указатели позволяют объекту владеть ресурсом и очищать его автоматически. std::string - та же идея, применённая к тексту: он владеет буфером символов, увеличивает его при добавлении и освобождает, когда строка выходит из области видимости. Вы никогда не вызываете new, никогда не считаете байты и никогда не переживаете о пропущенном нулевом завершителе.

Чтобы использовать его, подключите <string>. Класс находится в пространстве имён std.

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

Сборка и объединение строк

Конкатенировать можно с помощью +, но рабочая лошадка для наращивания строки на месте - это +=. Он дописывает в существующий буфер вместо того, чтобы каждый раз порождать совершенно новую строку, что важно внутри циклов.

Частая неожиданность: + требует, чтобы хотя бы один операнд уже был std::string. Два строковых литерала - это просто const char*, поэтому "a" + "b" не компилируется: для двух сырых указателей нет operator+. Сначала сделайте один из них строкой:

string s = "a" + "b";              // error: can't add two const char*
string s = string("a") + "b";      // fine - left side is a std::string
string s = "a"s + "b";             // fine in C++14+, the "s" literal suffix

Обратите внимание, что последняя форма использует суффикс s из <string> (using namespace std::string_literals;), который превращает литерал напрямую в std::string.

Доступ к содержимому строки

std::string ведёт себя как контейнер из char, поэтому его можно индексировать, перебирать в цикле и запрашивать у него фрагменты.

substr(pos, len) возвращает совершенно новую строку, скопированную из исходной; она не изменяет источник. Следите за границами: word[10] для строки из 5 символов - это неопределённое поведение: он ничего не бросит, просто прочитает мусор. Если нужен проверенный доступ, который бросает std::out_of_range, используйте word.at(10) вместо word[10].

Ещё одна классическая ловушка: .size() возвращает беззнаковый тип (size_t). Цикл назад вроде for (size_t i = word.size() - 1; i >= 0; --i) никогда не завершится, потому что беззнаковое i никогда не может опуститься ниже нуля - оно переполняется и превращается в огромное число. Используйте знаковый индекс или перестройте условие, когда нужно пройти строку в обратном порядке.

Поиск и замена

find находит подстроку или символ и возвращает начальный индекс. Когда цель отсутствует, он возвращает специальную константу std::string::npos - всегда сравнивайте с ней, а не предполагайте, что find возвращает -1.

Чтобы изменить текст на месте, replace(pos, len, text) заменяет участок новым содержимым (которое может быть другой длины), а insert/erase добавляют или удаляют фрагменты:

Строки и числа

Текст и числа не преобразуются сами собой - "42" это три символа, а не целое 42. Стандартная библиотека даёт вам функции преобразования в обе стороны. Используйте stoi, stod и им подобные, чтобы разбирать текст в числа, и to_string для движения в обратном направлении.

Здесь две ловушки. Во-первых, stoi("abc") бросает std::invalid_argument, поэтому защищайте недоверенный ввод с помощью try/catch. Во-вторых, to_string(3.99) даёт полное 3.990000 - если вам нужен контроль над точностью и форматированием, это задача для строковых потоков (string streams), и именно туда мы направимся дальше.

try {
    int n = std::stoi(userInput);
} catch (const std::invalid_argument&) {
    std::cout << "That wasn't a number.\n";
}

Далее: ввод и вывод

Всё это время вы выводили строки с помощью cout, но их чтение от пользователя таит свои сюрпризы - cin >> name останавливается на первом пробеле, поэтому для целой строки вроде "Ada Lovelace" вместо этого нужен std::getline. Следующая страница как следует разбирает ввод и вывод в C++: потоковые операторы, чтение целых строк и сочетание >> с getline без спотыкания об оставшиеся символы новой строки.

Часто задаваемые вопросы

В чём разница между std::string и char* в C++?

std::string - это класс, который владеет собственной памятью и управляет ею, растёт по мере необходимости и очищается автоматически. char* - это всего лишь сырой указатель на символы с завершающим '\0': буфер, длину и время жизни вы отслеживаете сами. Для почти всего предпочитайте std::string; он устраняет целые классы ошибок переполнения буфера и висячих указателей.

Как получить длину строки в C++?

Вызовите .size() или её псевдоним .length() у std::string: name.size() возвращает количество символов как size_t. Оба возвращают одно и то же значение; size() более распространённый стиль, потому что он соответствует всем остальным контейнерам STL.

Как преобразовать строку в число в C++?

Используйте std::stoi для int, std::stod для double и подобные (stol, stof). Пример: int n = std::stoi("42");. Эти функции бросают std::invalid_argument, если текст не является числом, поэтому оборачивайте их в try/catch, когда ввод не вызывает доверия.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ