5
, поскольку символ 'a'
— первый символ, который не входит в строку "bfo"
. Порядок символов в строке-аргументе неважен.Существуют подобные функции с инвертированной логикой, однако мы не использовали их в этом примере:
string::find_first_of and string::find_last_of
По аналогии с функциями, основанными на итераторах, нужно проверять, возвращают ли эти функции реальную позицию в строке или же значение, которое указывает, что позиция символа, отвечающего условию, не была найдена. Если такой позиции нет, то они возвращают значение
string::npos
.На основе позиций символов, полученных из этих функций, мы в рамках вспомогательной функции создаем подстроку, не содержащую пробелов в начале и конце, с помощью функции
string::substring
. Она принимает относительное смещение и длину строки, а затем возвращает новый экземпляр строки, для которого выделен собственный фрагмент памяти, содержащий только эту подстроку. Например, вызов string{"abcdef"}.substr(2, 2)
вернет новую строку "cd"
.Преимущества использования std::string без затрат на создание объектов std::string
Класс
std::string
очень полезен, поскольку значительно упрощает работу со строками. Его недостаток заключается в том, что при необходимости передать подстроку нужно передавать указатель и переменную, содержащую длину подстроки, два итератора или копию подстроки. Мы делали это в предыдущем примере, когда удаляли лишние пробелы из строки, вернув копию подстроки, которая не содержит их.Если мы хотим передать строку или подстроку в библиотеку, которая не предоставляет поддержку класса
std::string
, то можем передать только необработанный указатель на строку, что несколько разочаровывает, поскольку этот способ использовался еще во времена С. Как и в случае с выделением подстроки, необработанный указатель не несет информации о длине строки. Таким образом, кто-то должен будет реализовать связку указателя и длины строки.Говоря упрощенно, такой конструкцией как раз и является класс
std::string_view
. Он доступен, начиная с версии C++17, и предоставляет способ объединения указателя на некую строку и ее размера. Он воплощает идею наличия ссылочного типа для массивов данных.Представим, что разрабатываем функции, которые ранее в качестве параметров принимали объекты типа
std::string
, но при этом не изменяли их так, чтобы экземплярам класса string потребовалось повторно выделять память, содержащую реальные данные. Мы могли бы использовать тип std::string_view
для повышения совместимости с библиотеками, которые не знают об STL. Можно позволить другим библиотекам предоставлять string_view
для строк, содержащих полезные данные, скрытые за сложной реализацией типа string
, а затем применять их в нашем коде, совместимом с STL. Таким образом, класс string_view
ведет себя как минималистичный и полезный интерфейс, пригодный для использования многими библиотеками.Еще одной приятной особенностью является тот факт, что класс
string_view
может применяться как ссылка на подстроки больших объектов класса string
. Существует много возможностей грамотно использовать эту особенность. В данном разделе мы поработаем с классом string_view
, чтобы получить представление о его преимуществах и недостатках. Кроме того, увидим, как можно скрыть пробелы в начале и конце строки путем адаптации строковых представлений, а не изменения или копирования самой строки. Этот метод позволяет избежать ненужного копирования или изменения данных.
Как это делается
В этом примере мы реализуем функцию, которая работает с особенностями класса
string_view
, а затем увидим, сколько разных типов можем ей передать.
1. Сначала указываем заголовочные файлы и директивы
using
:
#include
#include
using namespace std;
2. Реализуем функцию, которая принимает в качестве единственного аргумента объект типа
string_view
:
void print(string_view v)
{
3. Прежде чем сделать что-то с входной строкой, удалим все пробелы из ее начала и конца. Мы будем изменять не строку, а ее представление, сузив его до значащей части. Функция
find_first_not_of
найдет первый символ строки, который не является пробелом (' '
), символом табуляции ('\t'
) или символом перехода на новую строку ('\n'
). С помощью функции remove_prefix
мы переместим внутренний указатель класса string_view
на первый символ, не являющийся пробелом. В том случае, если строка содержит только пробелы, функция find_ first_not_of
вернет значение npos
, которое равно size_type(-1)
. Поскольку size_type
— беззнаковая переменная, мы получим очень большое число. Поэтому выберем меньшее число из полученных: words_begin или размер строкового представления:
const auto words_begin (v.find_first_not_of(" \t\n"));
v.remove_prefix(min(words_begin, v.size()));
4. То же самое сделаем с пробелами в конце строки. Функция
remove_suffix
уменьшает переменную, показывающую размер строкового представления:
const auto words_end (v.find_last_not_of(" \t\n"));
if (words_end != string_view::npos) {
v.remove_suffix(v.size() - words_end - 1);
}
5. Теперь можно вывести на экран строковое представление и его длину:
cout << "length: " << v.length()
<< " [" << v << "]\n";
}
6. В функции
main
воспользуемся новой функцией print
, передав ей разные типы аргументов. Сначала передадим ей во время выполнения строку char*
из указателя argv
. Во время выполнения программы он будет содержать имя нашего исполняемого файла. Затем передадим ей пустой объект string_view
. Далее передадим ей символьную строку, созданную в стиле С, а также строку, образованную с помощью литерала ""sv
, который динамически создаст объект типа string_view
. И наконец, передадим ей объект класса std::string
. Положительный момент заключается в следующем: ни один из данных аргументов не изменяется и не копируется, чтобы вызвать функцию print
. Не происходит выделения памяти в куче. Для большого количества строк и/или для длинных строк это очень эффективно.
int main(int argc, char *argv[])
{
print(argv[0]);
print({});
print("a const char * array");
print("an std::string_view literal"sv);
print("an std::string instance"s);
7. Мы не протестировали функцию удаления пробелов. Передадим ей строку, которая содержит множество пробелов в начале и в конце:
print(" \t\n foobar \n \t ");
8. Еще одна приятная особенность класса
string_view
: он позволяет создавать строки, не завершающиеся нулевым символом. Если мы введем строку, например "abc"
, которая не будет заканчиваться нулем, то функция print
сможет безопасно ее обработать, поскольку объект класса string_view
также содержит размер строки, на которую указывает:
char cstr[] {'a', 'b', 'c'};
print(string_view(cstr, sizeof(cstr)));
}
9. Компиляция и запуск программы дадут следующий результат. Все строки обработаны корректно. Строка, которую мы заполнили большим количеством пробелов в начале и конце, также была корректно отфильтрована, а строка
abc
, не имеющая завершающего нулевого символа, корректно выведена на экран, не вызвав переполнения буфера:
$ ./string_view
length: 17 [./string_view]