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
. Однако в этот раз мы будем реализовывать его для сравнения итераторов не с другими '\0'
, поскольку он представляет собой
bool operator!=(const cstring_iterator_sentinel) const {
return s != nullptr && *s != '\0';
}
};
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';
}
13. Компиляция и запуск программы дадут следующий результат:
$ ./main "abcdef"
abcdef
Цикл выводит на экран введенные нами данные, и это неудивительно, поскольку мы рассмотрели небольшой пример реализации диапазона итераторов на базе ограничителей. Такой способ завершения перебора позволит вам реализовать собственные итераторы, если вы столкнетесь с ситуацией, когда
Автоматическая проверка кода итераторов с помощью проверяемых итераторов
Хотя итераторы очень полезны и предоставляют общие интерфейсы, есть шанс использовать их