📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

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

Шрифт:

-
+

Интервал:

-
+
1 ... 26 27 28 29 30 31 32 33 34 ... 121
Перейти на страницу:
возможно при поддержке двунаправленного перебора. Это требование выполняется любым итератором, который находится в категории двунаправленных или выше.

Обратный итератор содержит обычный итератор и подражает его интерфейсу, но изменяет операцию инкремента на операцию декремента.

Еще одна деталь связана с позициями начала и конца. Взглянем на рис. 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';

}

1 ... 26 27 28 29 30 31 32 33 34 ... 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?