1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 90
Текст из файла (страница 90)
Затем одна строка добавляется в начало списка, и еще одна — в его середину.Первый цикл вывода создает итератор для связанного списка. Программа проходитпо всему списку путем вызова M o v e N e x t () до тех пор, пока функция не вернет значение f a l s e , указывающее, что достигнут конец списка. Для каждого узла программа получает его объект и преобразует его обратно в строку, из которой он был создан.Затем функция M a i n () делает все то же, но с использованием цикла f o r e a c h .Цикл f o r e a c h точно так же проходит по всем узлам связанного списка, как функцияM a i n () делала это вручную.Предпочтительно использовать цикл f o r e a c h . Он дает лучший код, прости меньше подвержен ошибкам. Однако он имеет два ограничения: в нем нетсчетчика i n t i, но можно объявить собственный счетчик перед циклом и самостоятельно увеличивать его в теле цикла.
Также нельзя удалять элементы изколлекции внутри цикла f o r e a c h . В этом случае вам нужна коллекция rem o v e d l t e m s , в которой вы сохраняете индексы или ссылки на элементы, найденные в цикле f o r e a c h и которые должны быть удалены из исходной коллекции. Затем используйте цикл f o r e a c h еще раз для прохода по коллекцииr e m o v e d l t e m s и удалите указанные в нем элементы из исходной коллекции,Лучше один раз увидеть, чем сто — услышать, так что вот как это выглядитв исходном тексте:List<string>removedltems=newList<string>();// Цикл no o r i g i n a l C o l l e c t i o nforeach(string sin o r i g i n a l C o l l e c t i o n ){// Если s т р е б у е т с я у д а л и т ь ,//removedltemsremovedltems.Add(s);сохраняемссылкуилииндексв}foreach(stringsinremovedltems)//Циклnoremovedltems{originalCollection.Remove(s);}Вывод программы L i n k e d L i s t C o n t a i n e r выглядит следующим образом:Проход по контейнеру вручнуюПеред первой строкойПервая строкаВторая строкаМежду в т о р о й и т р е т ь е й с т р о к о йПоследняя строкаОбход с и с п о л ь з о в а н и е мПеред первой строкойПервая строка460foreachЧасть VII.
Дополнительные главыГлавВтораястрокаМежду в т о р о й и т р е т ь е й с т р о к о йПоследняя с т р о к аНажмите < E n t e r > д л я з а в е р ш е н и япрограммы...Обобщенную версию этого связанного списка можно найти в демонстрационной программе G e n e r i c L i n k e d L i s t C o n t a i n e r на прилагаемом компакт-диске. Обратите внимание, что G e n e r i c L i n k e d L i s t C o n t a i n e rпродолжает использовать интерфейс I E n u m e r a t o r , который будет рассмотрен немного позже, но в некоторых ситуациях следует применять вместо него новую обобщенную версию I E n u m e r a t o r < T > .
Однако пока нестоит начинать анализировать обобщенные классы. Кроме того, обратитеськ встроенному обобщенному классу L i n k e d L i s t , который, несомненно,превосходит написанный здесь.Зачем нужен связанный списокСвязанный список может показаться пустыми хлопотами. Его основное преимущество заключается в большой скорости вставки и удаления узлов. "Ну хорошо, — можетесказать вы. — Но ведь добавление s t r i n g в массив из четырех или пяти строк не сложнее перемещения нескольких ссылок для освобождения места." А что вы скажете, еслиэтот массив будет содержать несколько сотен тысяч строк, и вы должны выполнять массу вставок и удалений из него?Второе преимущество связанного списка в том, что он может расти и уменьшаться.Если вы думаете, что вам будут нужны 1000 объектов, то вы должны создать массив на1000 элементов, независимо от того, будете вы их использовать или нет.
Что еще хуже,если вы в действительности создадите 2000 объектов, то можете считать, что в этот развам крупно не повезло. (Да, конечно, можно создать второй массив с большей емкостьюи скопировать в него содержимое первого, но что при этом можно сказать об эффективности и затратах памяти?)На этом принципе основаны многие распространенные вирусы. Например, некоторый исполненный благих намерений программист решает, что 256 символовбудет достаточно для любого имени файла, и объявляет массив c h a r [ 2 5 6 ] .
Если программист забудет убедиться, что имя на самом деле не длиннее, чем ожидается, то у хакера появляется шанс сломать программу, передав ей неправдоподобно длинное имя, и переписать тем самым часть кода за массивом (это называется переполнением буфера). Впрочем, это проблема в первую очередь C/C++:С# автоматически проверяет выход за границы массива.В оставшейся части главы будут проанализированы три разных подхода к общей задаче итерирования коллекции. В этом разделе будет продолжено обсуждение наиболеетрадиционного (как минимум, для программистов на С#) подхода с использованием итераторов, которые реализуют интерфейс I E n u m e r a t o r . В качестве примера рассматривается итератор для связанного списка из предыдущего раздела.Глава 20.
Работа с коллекциями461Термины итератор (iterator) и перечислитель (enumerator) являются синонимами. Термин итератор более распространен, несмотря на имя реализуемогоим интерфейса. От обоих терминов можно произвести глагольную форму —выможете итерировать контейнер, а можете перечислять. Другими подходамик решению этой же задачи являются индексаторы и новые блоки итераторов.Доступ к коллекции: общая задачаРазличные типы коллекций могут иметь разные схемы доступа. Не все виды коллекциимогут быть эффективно доступны с использованием индексов наподобие массивов — таков, например, связанный список. Различия между типами коллекций делают невозможнымнаписание без специальных средств функции наподобие приведенной далее:// п е р е д а е т с я коллекция любого видаvoid myClearFunction(Collection aColl,i n t index){aColl[index]= 0; // Индексирование р а б о т а е т не// типов коллекций//...
продолжение...длявсех}Коллекции каждого типа могут сами определять свои методы доступа (и делают это).Например, связанный список может предоставить метод G e t N e x t () для выборки следующего элемента из цепочки объектов; стек может предложить методы P u s h ( )и P o p () для добавления и удаления объектов и т.д.Более общий подход состоит в предоставлении для каждого класса коллекции отдельного так называемого класса итератора, который знает, как работать с конкретнойколлекцией.
Каждая коллекция X определяет свой собственный класс I t e r a t o r X . В отличие от X, I t e r a t o r X представляет общий интерфейс I E n u m e r a t o r , золотой стандарт итерирования. Этот метод использует второй объект, именуемый итератором, в качестве указателя внутрь коллекции.Итератор (перечислитель) обладают следующими преимуществами.Каждый класс коллекции может определить свой собственный класс итератора.Поскольку итератор реализует стандартный интерфейс I E n u m e r a t o r , с нимобычно легко работать.Прикладной код не должен знать о внутреннем устройстве коллекций. Пока программист работает с итератором, тот берет на себя все заботы о деталях. Это —хорошая инкапсуляция.Прикладной код может создать много независимых объектов-итераторов для одной и той же коллекции.
Поскольку итератор содержит информацию о своем собственном состоянии (знает, где он находится в процессе итерирования), каждыйитератор может независимо проходить по коллекции. Вы можете одновременновыполнять несколько итераций, причем в один и тот же момент все они могут находиться в разных позициях.Чтобы сделать возможным наличие цикла f o r e a c h , интерфейс I E n u m e r a t o r долженподдерживать различные типы коллекций — от массивов до связанных списков. Следовательно, его методы должны быть максимально обобщенными, насколько это возможно.Например, нельзя использовать итератор для произвольного доступа к элементам коллекции, поскольку большинство коллекций не обеспечивают подобного доступа.462Часть VII. Дополнительные главыI E n u m e r a t o r предоставляет три следующих метода.R e s e t () — устанавливает итератор таким образом, чтобы он указывал на началоколлекции.
Примечание: обобщенная версия I E n u m e r a t o r , I E n u m e r a t o r < T > ,н е предоставляет метод R e s e t ( ) . В случае обобщенного L i n k e d L i s t простоначинайте работу с вызова M o v e N e x t ( ) .M o v e N e x t () — перемещает итератор от текущего объекта в контейнере к следующему.C u r r e n t — свойство (не метод), которое дает объект данных, хранящийся в текущей позиции итератора.Описанный принцип продемонстрирован приведенной далее функцией. Программист класса M y C o l l e c t i o n (не показанного здесь) создает соответствующийкласс и т е р а т о р а — скажем, I t e r a t o r M y C o n t a i n e r (применяя соглашение о бименах I t e r a t o r X , упоминавшееся ранее). Прикладной программист ранее сохранил ряд объектов C o n t a i n e d D a t a O b j e c t s в коллекции M y C o l l e c t i o n .
Приведенный далее фрагмент исходного текста использует три стандартных методаI E n u m e r a t o r для чтения этих объектов:// Класс M y C o l l e c t i o n хранит// C o n t a i n e d D a t a O b j e c t d a t avoid M y F u n c t i o n ( M y C o l l e c t i o nобъектытипаmyColl){// Программист,создавший класс MyCollection,создал также// и к л а с с и т е р а т о р а I t e r a t o r M y C o l l e c t i o n ; прикладной// программист создает объект итератора для прохода по// объекту myCollI E n u m e r a t o r i t e r a t o r = new I t e r a t o r M y C o l l e c t i o n ( m y C o l l ) ;// перемещаем и т е р а т о р в "следующую п о з и ц и ю " в н у т р и// к о л л е к ц и иwhile(iterator.MoveNext()){// Получаем ссылку на о б ъ е к т данных в текущей позиции// к о л л е к ц и иContainedDataObject containedData;// datacontained =(ContainedDataObject)iterator.Current;//.
. . и с п о л ь з у е м объект данных c o n t a i n e d . . .Функция M y F u n c t i o n ( )принимает вкачестве аргумента коллекциюCont a i n e d D a t a O b j e c t s . Она начинается с создания итератора типа I t e r a t o r M y C o l l e c t i o n . Функция начинает цикл с вызова M o v e N e x t ( ) . При первом вызовеM o v e N e x t () перемещает итератор к первому элементу коллекции. При каждом последующем вызове M o v e N e x t () перемещает указатель " н а одну п о з и ц и ю . " Функция M o v e N e x t () возвращает f a l s e , когда коллекция исчерпана и итератор больше нельзя передвинуть.Свойство C u r r e n t возвращает ссылку на объект данных в текущей позиции итератора.