Одной из важнейших характеристик контейнера является доступ к его элементам. Обычно различают прямой, последовательный и ассоциативный доступ. Прямой доступ к элементу — это доступ по номеру (или, еще говорят, по индексу) элемента. Именно таким образом мы обращаемся к элементам массива, например:
v[7]
Это выражение означает, что мы хотим оперировать элементом контейнера v, имеющим номер (индекс) 7. Нумерация элементов может начинаться, вообще говоря, с любого числа, однако в С++ принято нумерацию начинать с нуля, так как для встроенных массивов (которые являются частным случаем контейнера) принята именно такая нумерация.
Последовательный доступ отличается тем, что мы не имеем в распоряжении индексов элементов, зато можем перемещаться последовательно от элемента к элементу. Можно считать, что существует невидимая «стрелка»-индикатор, которую перемещают по элементам контейнера с помощью некоторого множества операций. Тот элемент, на который в данный момент «стрелка» показывает, называется текущим.
Обычно набор операций для последовательного доступа включает операции:
? перехода к первому элементу;
? перехода к последнему элементу;
? перехода к следующему элементу;
? перехода к предыдущему элементу;
? перехода на п элементов вперед (от первого в сторону последнего элемента контейнера);
? перехода на п элементов назад (от конца к началу контейнера);
? получения (изменения) значения текущего элемента.
Эти операции могут быть представлены в функциональной форме, например:
next(v); // перейти к следующему
prev(v); // перейти к предыдущему
first(v); // перейти к первому
last(v); // перейти к последнему
current(v); // получить текущий
forward(v, п); // перейти на п элементов вперед
back(v, п); // перейти на п элементов назад
Операция изменения текущего элемента — это, естественно, операция присваивания, например:
current(v) = value;
В этом случае функция current() должна возвращать ссылку на элемент контейнера.
Те же операции, реализованные как методы класса (контейнера), можно представить следующим образом:
v.nextO; // перейти к следующему
v.prevQ; // перейти к предыдущему
v.firstO; // перейти к первому
v.last(); // перейти к последнему
v.currentO; // получить текущий
v.skip(n); // перейти на п элементов вперед
v.skip(-n); // перейти на п элементов назад
Однако в С++ «стрелку»-индикатор удобнее представить в виде некоторого объекта, связанного с контейнером. Если этот объект имеет имя i v, то те же операции могут быть реализованы и так:
iv = v.beginO; // перейти к первому
iv = v.last(); // перейти к последнему
++iv; // перейти к следующему
--iv; // перейти к предыдущему
iv+=n; // перейти на п элементов вперед
iv-=n; // перейти на п элементов назад
*iv // получить значение текущего элемента
1 Обычным указателем такой объект будет только для контейнера-массива.
Не правда ли, очень похоже на указатель?! Однако это не указатель1 — в практике объектно-ориентированного программирования такой объект называется итератором. Итератор — это объект, обеспечивающий последовательный доступ к элементам контейнера. Так же как контейнер представляет собой более общую концепцию, чем массив, так и итератор является более общей концепцией, чем указатель. В [17] итератор описан как один из шаблонов (паттернов) программирования — Iterator.
Ассоциативный доступ похож на прямой, однако основан не на номерах элементов, а на содержимом элементов контейнера. Например, в банковской системе контейнер может содержать записи о счетах клиентов. Обязательным элементом записи является поле, содержащее фамилию клиента, например:
class TAccount
{ string Family: // фамилия
unsigned long Count: // номер счета
Date Open: // дата открытия счета
public: // . . . }:
Если контейнер v содержит такие объекты, то выражение представляет собой счет, открытый на имя Стенли Липпмана:
v["Lippman"]
Такое выражение в С++ является вполне корректным, так как операция индексирования может быть перегружена для аргумента любого типа.
Поле, с содержимым которого ассоциируется элемент контейнера, называется ключом (полем доступа). Элемент контейнера, соответствующий некоторому значению ключа, обычно так и называется значением. Ассоциативный контейнер, таким образом, состоит из множества пар «ключ-значение». Как правило, ассоциативный контейнер упорядочен некоторым образом по ключу. В данном случае контейнер, содержащий счета клиентов, отсортирован по полю Family, поэтому элементом, предшествующим записи с фамилией Lippman, может быть запись с фамилией Kupaev, а следующим — запись с фамилией Martin.
Методы доступа к контейнеру — настолько важная характеристика, что в стандартной библиотеке (см. п. 17 в [1]) различают контейнеры последовательные и ассоциативные. Последовательными контейнерами, которые обеспечивают и прямой, и последовательный варианты доступа, являются контейнеры vector и deque, а ассоциативным — контейнер тар. Контейнер, в котором доступ только последовательный, — это list. |