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

C++17 STL Стандартная библиотека шаблонов читать книгу онлайн
С++ — объектно-ориентированный язык программирования, без которого сегодня немыслима промышленная разработка ПО. В этой замечательной книге описана работа с контейнерами, алгоритмами, вспомогательными классами, лямбда-выражениями и другими интересными инструментами, которыми богат современный С++. Освоив материал, вы сможете коренным образом пересмотреть привычный подход к программированию.
Преимущество издания — в подробном описании стандартной библиотеки шаблонов С++, STL. Ее свежая версия была выпущена в 2017 году. В книге вы найдете более 90 максимально реалистичных примеров, которые демонстрируют всю мощь STL. Многие из них станут базовыми кирпичиками для решения более универсальных задач.
Вооружившись этой книгой, вы сможете эффективно использовать С++17 для создания высококачественного и высокопроизводительного ПО, применимого в различных отраслях.
Обратный итератор содержит обычный итератор и подражает его интерфейсу, но изменяет операцию инкремента на операцию декремента.
Еще одна деталь связана с позициями начала и конца. Взглянем на рис. 3.4, на котором показана стандартная последовательность чисел, хранящаяся в итерабельном диапазоне данных. Если она содержит числа от 1 до 5, то начальный итератор должен указывать на элемент 1, а конечный итератор — на элемент, стоящий сразу после 5.
При определении обратных итераторов итератор rbegin должен указывать на элемент 5, а итератор rend — на элемент, стоящий сразу перед 1. Переверните книгу вверх ногами — и убедитесь в том, что это имеет смысл.
Если мы хотим, чтобы наши собственные классы-контейнеры поддерживали обратный перебор, то не нужно реализовывать все эти детали самостоятельно, можно просто обернуть обычные итераторы в обратные с помощью вспомогательной функции std::make_reverse_iterator, и она сделает всю работу за нас.
Завершение перебора диапазонов данных с использованием ограничителей
Как алгоритмы STL, так и основанные на диапазонах циклы for предполагают, что начальная и конечная позиции для перебора известны заранее. В некоторых ситуациях, однако, нельзя узнать конечную позицию до того, как она будет достигнута при переборе.
Самый простой пример такой ситуации — это перебор в стиле С простых строк, длина которых во время выполнения неизвестна. Код, итерирующий по таким строкам, обычно выглядит следующим образом:
for (const char *c_ponter = some_c_string; *c_pointer != ' '; ++c_pointer)
{
const char c = *c_pointer;
// сделаем что-нибудь с переменной c
}
Единственный способ поработать с этими строками в основанном на диапазоне цикле for заключается в том, чтобы обернуть их в объект std::string, который поддерживает функции begin() и end():
for (char c : std::string(some_c_string)) { /* сделаем что-нибудь с c */ }
Однако конструктор класса std::string будет итерировать по всей строке до того, как этим сможет заняться созданный нами цикл. В С++17 появился класс std::string_view, но его конструктор также один раз проитерирует по всей строке. Короткие строки не стоят таких хлопот, но это только пример одного из проблемных классов, для которого подобная возня может быть оправдана в других ситуациях. Итератор std::istream_iterator тоже сталкивается с подобными случаями в момент приема входящих данных из std::cin, поскольку его конечный итератор не может реалистично указывать на конец потока данных, когда пользователь еще вводит текст.
Начиная с C++17 начальный и конечный итераторы не обязаны иметь один тип. В данном разделе мы продемонстрируем, как правильно использовать это небольшое изменение в правилах.
Как это делается
В этом примере мы создадим итератор и класс диапазона, который позволит проитерировать по строке неизвестной длины, не зная конечной позиции заранее.
1. Сначала, как и всегда, включим заголовочные файлы:
#include <iostream>
2. Ограничитель итератора — самый важный элемент этого раздела. Удивительно, но определение его класса остается полностью пустым:
class cstring_iterator_sentinel {};
3. Теперь реализуем итератор. Он будет содержать указатель на строку, которая и станет тем контейнером, по которому мы будем итерировать:
class cstring_iterator {
const char *s {nullptr};
4. В конструкторе просто инициализируется внутренний указатель на строку, предоставляемую пользователем. Сделаем конструктор явным, чтобы предотвратить неявные преобразования строк к строковым итераторам:
public:
explicit cstring_iterator(const char *str)
: s{str}
{}
5. При разыменовании итератор в какой-то момент просто вернет символьное значение в этой позиции:
char operator*() const { return *s; }
6. Операция инкремента для итератора просто инкрементирует позицию в строке:
cstring_iterator& operator++() {
++s;
return *this;
}
7. Здесь начинается самое интересное. Мы реализуем оператор сравнения !=, который используется алгоритмами STL и основанным на диапазоне циклом for. Однако в этот раз мы будем реализовывать его для сравнения итераторов не с другими итераторами, а с ограничителями. При сравнении итераторов можно проверить только тот факт, что их внутренние указатели на строку указывают на один и тот же адрес; это несколько ограничивает наши возможности. Сравнивая итератор с пустым объектом-ограничителем, можно применить совершенно другую семантику: проверить, указывает ли наш итератор на завершающий символ ' ', поскольку он представляет собой конец строки!
bool operator!=(const cstring_iterator_sentinel) const {
return s != nullptr && *s != ' ';
}
};
8. Чтобы использовать эту возможность в основанном на диапазоне цикле for, нужен класс диапазона, который предоставит конечный и начальный итераторы:
class cstring_range {
const char *s {nullptr};
9. Единственное, что пользователь должен предоставить при создании экземпляра этого класса, — строка, по которой мы будем итерировать:
public:
cstring_range(const char *str)
: s{str}
{}
10. Вернем обычный итератор cstring_iterator из функции begin(), который указывает на начало строки. Из функции end() мы вернем тип ограничителя. Обратите внимание: без типа ограничителя мы также будем возвращать итератор, но как же узнать о достижении конца строки, если мы не нашли его заранее?
cstring_iterator begin() const {
return cstring_iterator{s};
}
cstring_iterator_sentinel end() const {
return {};
}
};
11. На этом все. Мы можем мгновенно применить итератор. Строки, которые поступают от пользователя, представляют собой лишь один пример входных данных, чью длину мы не знаем заранее. Чтобы заставить пользователя предоставить какие-нибудь входные данные, мы станем завершать работу программы, если тот не указал хотя бы один параметр при ее запуске в оболочке:
int main(int argc, char *argv[])
{
if (argc < 2) {
std::cout << "Please provide one parameter.n";
return 1;
}
12. Если программа все еще работает, то мы знаем, что в argv[1] содержится какая-то пользовательская строка:
for (char c : cstring_range(argv[1])) {
std::cout << c;
}
std::cout << 'n';
}