C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов читать книгу онлайн
С++ — объектно-ориентированный язык программирования, без которого сегодня немыслима промышленная разработка ПО. В этой замечательной книге описана работа с контейнерами, алгоритмами, вспомогательными классами, лямбда-выражениями и другими интересными инструментами, которыми богат современный С++. Освоив материал, вы сможете коренным образом пересмотреть привычный подход к программированию.
Преимущество издания — в подробном описании стандартной библиотеки шаблонов С++, STL. Ее свежая версия была выпущена в 2017 году. В книге вы найдете более 90 максимально реалистичных примеров, которые демонстрируют всю мощь STL. Многие из них станут базовыми кирпичиками для решения более универсальных задач.
Вооружившись этой книгой, вы сможете эффективно использовать С++17 для создания высококачественного и высокопроизводительного ПО, применимого в различных отраслях.
Как это делается
В этом примере мы реализуем функцию, которая работает с особенностями класса 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.
Как это делается
В этом примере мы считаем пользовательские данные в разные переменные и увидим, как обрабатывать ошибки, а также научимся выполнять более сложную токенизацию