C++17 STL Стандартная библиотека шаблонов — страница 64 из 119

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]