chitay-knigi.com » Разная литература » C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 62 63 64 65 66 67 68 69 70 ... 121
Перейти на страницу:
тот факт, что класс string_view может применяться как ссылка на подстроки больших объектов класса string. Существует много возможностей грамотно использовать эту особенность. В данном разделе мы поработаем с классом string_view, чтобы получить представление о его преимуществах и недостатках. Кроме того, увидим, как можно скрыть пробелы в начале и конце строки путем адаптации строковых представлений, а не изменения или копирования самой строки. Этот метод позволяет избежать ненужного копирования или изменения данных.

Как это делается

В этом примере мы реализуем функцию, которая работает с особенностями класса string_view, а затем увидим, сколько разных типов можем ей передать.

1. Сначала указываем заголовочные файлы и директивы using:

#include <iostream>

#include <string_view>

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(" tn"));

  v.remove_prefix(min(words_begin, v.size()));

4. То же самое сделаем с пробелами в конце строки. Функция remove_suffix уменьшает переменную, показывающую размер строкового представления:

  const auto words_end (v.find_last_not_of(" tn"));

  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(" tn 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]

length: 0 []

length: 20 [a const char * array]

length: 27 [an std::string_view literal]

length: 23 [an std::string instance]

length: 6 [foobar]

length: 3 [abc]

Как это работает

Мы только что увидели следующее: можно вызвать функцию, принимающую аргумент типа string_view, который способен содержать все, что похоже на строку, — а именно символы, сохраненные непрерывным способом. Во время вызовов нашей функции print не было выполнено ни одной операции копирования.

Интересно отметить, что в вызове print(argv[0]) строковое представление автоматически определило длину строки, поскольку данная строка по соглашению завершается нулевым символом. С другой стороны, никто не может предполагать, что можно определить длину поля данных объекта типа string_view путем подсчета символов до тех пор, пока не встретится нулевой символ. Поэтому нужно всегда соблюдать осторожность при работе с указателями на данные строкового представления с помощью string_view::data(). Обычные строковые функции предполагают, что строка будет завершаться нулевым символом, и поэтому использование необработанных указателей способно привести к переполнению буфера. Всегда лучше применять интерфейсы, которые ожидают передачи строкового представления.

За исключением этой особенности, у нас имеется роскошный интерфейс, с которым мы уже знакомы благодаря классу std::string.

 

 Задействуйте класс std::string_view для передачи строк или подстрок, чтобы избежать копирования или выделения памяти кучей, продолжая при этом привычно использовать строковые классы. Но помните: класс std::string_view не предполагает, что строки завершаются нулевым символом.

Считываем значения из пользовательского ввода

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

В следующем примере мы лишь считаем данные от пользователя, но тщательно разобрав этот процесс, вы научитесь читать данные из любого другого потока. Пользовательские данные можно считать с помощью std::cin — по сути, это объект потока ввода, как и экземпляры классов ifstream и istringstream.

Как это делается

В этом примере мы считаем пользовательские данные в разные переменные и увидим, как обрабатывать ошибки, а также научимся выполнять более сложную токенизацию

1 ... 62 63 64 65 66 67 68 69 70 ... 121
Перейти на страницу:

Комментарии
Минимальная длина комментария - 25 символов.
Комментариев еще нет. Будьте первым.