Шрифт:
Интервал:
Закладка:
Как это делается
В этом примере мы реализуем функцию, которая работает с особенностями класса 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.
Как это делается
В этом примере мы считаем пользовательские данные в разные переменные и увидим, как обрабатывать ошибки, а также научимся выполнять более сложную токенизацию