std::string и std::string_view
Чему вы научитесь:
- хранить и сравнивать текст в
std::string; - передавать текст в функцию через
std::string_viewбез копии; - замечать представление, которое пережило свою строку;
- отличать C-массив от
std::arrayи узнавать длину массива.

Представьте подставку с фишками-буквами, как в настольной игре в слова. Фишки на подставке её собственные: можно добавить новую, убрать лишнюю, и сколько их будет, решается по ходу игры. Так устроена строка в C++.
Теперь возьмите картонную рамку с окошком и положите её поверх чужой подставки. Сквозь окошко видно несколько фишек подряд, но своих фишек у рамки нет. Она помнит только два числа: с какой фишки начинается окошко и сколько фишек в него видно. Так устроено представление строки, о нём вторая половина урока.
Тип char хранит ровно один символ, как одна фишка. Имя, слово или фраза состоят из многих символов, и сколько их будет, заранее неизвестно. Такой текст хранят в строке: переменной типа std::string из заголовка <string>.
#include <iostream>
#include <string>
int main() {
std::string name = "Ada";
std::string greeting = "Hello, " + name;
std::cout << greeting << ' ' << greeting.size() << '\n'; // Hello, Ada 10
std::cout << greeting[0] << greeting[7] << '\n'; // HA
std::cout << (name == "Ada") << (name < "Bob") << '\n'; // 11
}Давайте разберём три строки вывода:
+склеивает текст в кавычках со строкойname, аsize()возвращает число символов:10.greeting[0]иgreeting[7]дают символы с номерами0и7:HиA.- Сравнение
name == "Ada"истинно, иstd::coutпечатает его как1. Сравнениеname < "Bob"тоже истинно.
Запись greeting.size() вызывает функцию, которая принадлежит самой строке: имя переменной, точка, имя функции и скобки.
Строки сравниваются посимвольно: решает первая пара разных символов. Если такой пары нет, меньше та строка, которая короче. У "Ada" и "Bob" первая пара — 'A' и 'B'.
Строка владеет символами
std::string хранит свои символы сам и при необходимости выделяет под них память, как подставка, к которой добавляют фишки. Копия строки получает собственные символы, поэтому если изменить копию, оригинал останется прежним. Символы нумеруются с нуля, и последний стоит под номером size() - 1.
size() возвращает беззнаковое число типа std::size_t, объявленного в заголовке <cstddef>. Счётчик цикла по номерам символов объявляют того же типа. Если объявить счётчик как int i, компилятор с флагами -Wall -Wextra, как в задачах курса, предупредит о сравнении i < word.size().
std::string word = "code";
for (std::size_t i = 0; i < word.size(); ++i) {
std::cout << i << word[i] << ' '; // 0c 1o 2d 3e
}Если номера не нужны, подойдёт цикл по диапазону: for (char c : word) по очереди кладёт в c каждый символ строки.
Представление строки
Функции, которая только читает текст, своя копия строки не нужна. Для параметра такой функции есть тип std::string_view из заголовка <string_view>. Это представление: оно хранит адрес первого символа и длину, но не сами символы. Это та самая рамка с окошком: она лежит поверх чужих символов и помнит, где начинается окошко и сколько символов в него видно.
Копировать представление дёшево, ведь это всего два числа. Поэтому параметр std::string_view передают по значению.
#include <iostream>
#include <string>
#include <string_view>
void print_length(std::string_view text) {
std::cout << text << ' ' << text.size() << '\n';
}
int main() {
std::string name = "Ada";
print_length(name); // Ada 3
print_length("Babbage"); // Babbage 7
}Параметр std::string_view принимает и std::string, и текст в двойных кавычках, ничего не копируя. Будь параметр типа const std::string&, при вызове с "Babbage" программе пришлось бы сначала создать временную строку. Символы представления читают так же, как символы строки: через size(), [] и цикл по диапазону. Изменить их через представление нельзя.
Пользоваться представлением можно, только пока жива строка, на которую оно смотрит. В отличие от const-ссылки, представление не продлевает жизнь временной строки. Здесь аналогия перестаёт работать. Если подставку убрали, настоящая рамка покажет пустой стол, а представление продолжит читать память, где символов уже нет.
Пройдите сценарий по шагам: следите, как меняются адрес и длина представления, пока символы строки остаются на месте, и что остаётся у full, когда временная строка уничтожена.
1std::string first = "Ada";2std::string_view v = first;3v.remove_prefix(1);4std::string_view full = first + " Lovelace";5std::cout << full << '\n';
first владеет своими символами: 3 символа лежат в его собственной памяти. Нажмите «Дальше».
Частая ошибка
Представление создают из временной строки:
std::string first = "Ada";
std::string_view full = first + " Lovelace"; // ✗
std::cout << full << '\n';warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]Clang предупреждает, но программа собирается, и не каждый компилятор замечает такую ошибку. first + " Lovelace" создаёт временную строку, и она уничтожается сразу, как только full объявлено. Представление продолжает смотреть на её символы, то есть становится висячим, и чтение full — неопределённое поведение.
То же случится с представлением строки, которую функция вернула по значению: такая строка тоже временная. Чтобы сохранить результат, объявите full как std::string.
Главное
std::stringвладеет своими символами, и её копия получает собственные.std::string_viewхранит только адрес первого символа и длину, поэтому его передают по значению.- Представление нельзя читать после того, как его строка уничтожена: это неопределённое поведение.
- Номер символа и
size()имеют беззнаковый типstd::size_t.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.