Информационный сайт

 

Реклама
bulletinsite.net -> Книги на сайте -> Программисту -> Лафоре Р. -> "Объектно-ориентированное программирование в С++" -> 268

Объектно-ориентированное программирование в С++ - Лафоре Р.

Лафоре Р. Объектно-ориентированное программирование в С++ — М.: Питер, 2004. — 992 c.
Скачать (прямая ссылка): obektnoorentprogramm2004.djvu
Предыдущая << 1 .. 262 263 264 265 266 267 < 268 > 269 270 271 272 273 274 .. 341 >> Следующая


же механизм указателей). Например, в нижеследующем отрывке кода производится проход (итерация) по массиву типа float с выводом каждого элемента:

float* ptr = start_adc!ress: fort іnt j=0: j<SIZE: j++) cout « *ptr++;

Мы снимаем косвенность с указателя ptr с помощью оператора * для получения значения элемента, на который он ссылается, затем инкрементируем ptr, используя ++. После этого значением указателя является адрес следующего элемента контейнера.

Недостатки обычных указателей

Несмотря на приведенный выше пример, использовать указатели с более сложными контейнерами, нежели простые массивы, довольно затруднительно. Во-пер-вых, если элементы контейнера хранятся не последовательно в памяти, а сегментирование, то методы доступа к ним значительно усложняются. Мы не можем в этом случае просто инкрементировать указатель для получения следующего значения. Например, при движении от элемента к элементу в связном списке мы не можем по умолчанию предполагать, что следующий является соседом предыдущего. Приходится идти по цепочке ссылок.

К тому же нам может понадобиться хранить адрес некоторого элемента контейнера в переменной-указателе, чтобы в будущем иметь возможность доступа к нему. Что случится со значением указателя, если мы вставим или удалим что-нибудь из середины контейнера? Он не сможет указывать на корректный адрес, если со структурой данных что-то произошло. Было бы, конечно, здорово, если бы нам не нужно было проверять все наши указатели после каждой вставки и удаления.

Одним из решений проблем такого рода является создание класса «интеллектуальных указателей». Объект такого класса обычно является оболочкой для методов, работающих с обычными указателями. Операторы ++и * перегружаются и поэтому в курсе того, как нужно работать с элементами контейнера, даже если они расположены не последовательно в памяти или изменяют свое местоположение. Вот как это может выглядеть на практике (приводится только схема работы):

class SmartPointer {

private:

float* р: //обычный указатель public:

float operator*() { }

float operator++() { }

};

void mainO {

SmartPointer sptr = start_address: for(int j=0: j<SIZE; j++) cout « *sptr++;

} 708

Глава 15. Стандартная библиотека шаблонов (STL)

На ком ответственность?

Интересен теперь такой вопрос: должен ли класс интеллектуальных указателей непременно включаться в контейнер, или он может быть отдельным классом? Подход, используемый в STL, заключается как раз в том, чтобы сделать интеллектуальные указатели полностью независимыми и даже дать им собственное имя — итераторы. (На самом деле, они представляют собой целое семейство шаблонных классов.) Для создания итераторов необходимо определять их в качестве объектов таких классов.

Итераторы в качестве интерфейса

Кроме того, что итераторы являются «умными указателями» на элементы контейнеров, они играют еще одну немаловажную роль в STL. Они определяют, какие алгоритмы использовать с какими контейнерами. Почему это важно?

Дело в том, что теоретически вы, конечно, можете применить любой алгоритм к любому контейнеру. И, на самом деле, так зачастую можно и нужно делать. Это одно из достоинств STL. Но правил без исключений не бывает. Иногда оказывается, что некоторые алгоритмы ужасно неэффективны при работе с определенными типами контейнеров. Например, алгоритму sort() требуется произвольный доступ к тому контейнеру, который он пытается сортировать. В противном случае приходится проходить все элементы до тех пор, пока не найдется нужный, что, разумеется, займет немало времени, если контейнер солидных размеров. А для эффективной работы алгоритму reverse() требуется иметь возможность обратной и прямой итерации, то есть прохождения контейнера как в обратном, так и в нормальном порядке.

Итераторы предлагают очень элегантный выход из таких неловких положений. Они позволяют определять соответствие алгоритмов контейнерам. Как уже отмечалось, итераторы можно представлять себе в виде кабеля, соединяющего, например, компьютер и принтер. Один конец «вставляется» в контейнер, другой — в алгоритм. Не все кабели можно воткнуть в любой контейнер, и не все кабели можно воткнуть в любой алгоритм. Если вы попытаетесь использовать слишком мощный для данного контейнера алгоритм, то просто вряд ли сможете найти подходящий итератор для их соединения. Попробуйте-попробуйте, а потом посмотрите, как недоволен будет компилятор вашими действиями.

Сколько типов итераторов (кабелей) необходимо для работы? Получается, что всего пять. На рис. 15.3 показаны эти пять категорий в порядке, соответствующем возрастанию их сложности (на начальном уровне «входные» и «выходные» итераторы одинаково просты).

Если алгоритму требуется только лишь продвинуться на один шаг по контейнеру для осуществления последовательного чтения (но не записи!), он может использовать «входной» итератор для связывания себя с контейнером. В реальной практике входные итераторы используются не с контейнерами, а при чтении из файлов или из потока сіп.
Предыдущая << 1 .. 262 263 264 265 266 267 < 268 > 269 270 271 272 273 274 .. 341 >> Следующая
Реклама
Авторские права © 2009 AdsNet. Все права защищены.
Rambler's Top100