Существует важная причина, по которой итератор end
устанавливается на элемент, следующий за последним, а не на последний элемент: пустая последовательность — не особый случай. Мы не любим особые случаи, потому что — по определению — для каждого из них приходится писать отдельный код.
В нашем примере можно поступить следующим образом:
list
if (p==lst.end()) // мы достигли конца?
cout << "Список пустой";
else
cout << "максимальное значение = " << *p << endl;
Работая с алгоритмами из библиотеки STL, мы систематически используем эту проверку. Поскольку в стандартной библиотеке список предусмотрен, не будем углубляться в детали его реализации. Вместо этого кратко укажем, чем эти списки удобны (если вас интересуют детали реализации списков, выполните упр. 12–14).
20.5. Еще одно обобщение класса vector
Из примеров, приведенных в разделах 20.3 и 20.4, следует, что стандартный вектор имеет член класса, являющийся классом iterator
, а также функции-члены begin()
и end()
(как и класс std::list
). Однако мы не указали их в нашем классе vector
в главе 19. Благодаря чему разные контейнеры могут использоваться более или менее взаимозаменяемо в обобщенном программировании, описанном в разделе 20.3? Сначала опишем схему решения (игнорируя для простоты распределители памяти), а затем объясним ее.
template
public:
typedef unsigned long size_type;
typedef T value_type;
typedef T* iterator;
typedef const T* const_iterator;
// ...
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
size_type size();
// ...
};
typedef
создает синоним типа; иначе говоря, для нашего класса vector
имя iterator
— это синоним, т.е. другое имя типа, который мы решили использовать в качестве итератора: T*
. Теперь для объекта v
класса vector
можно написать следующие инструкции:
vector
и
for (vector
cout << v[i] << '\n';
Дело в том, что, для того, чтобы написать эти инструкции, нам на самом деле не обязательно знать, какие именно типы называются iterator
и size_type
. В частности, в приведенном выше коде, выраженном через типы iterator и size_type
, мы будем работать с векторами, в которых тип size_type
— это не unsigned long
(как во многих процессорах встроенных систем), а тип iterator
— не простой указатель, а класс (как во многих широко известных реализациях языка C++).
В стандарте класс list
и другие стандартные контейнеры определены аналогично. Рассмотрим пример.
template
public:
class Link;
typedef unsigned long size_type;
typedef Elem value_type;
class iterator; // см. раздел 20.4.2
class const_iterator; // как iterator, но допускает изменение
// элементов
// ...
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
size_type size();
// ...
};
Таким образом, можно писать код, не беспокоясь о том, что он использует: класс list
или vector
. Все стандартные алгоритмы определены в терминах этих имен типов, таких как iterator
и size_type
, поэтому они не зависят от реализации контейнеров или их вида (подробнее об этом — в главе 21).
20.6. Пример: простой текстовый редактор