ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 11.12.2025

Просмотров: 3231

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

" Н е в е р н ый п р и о р и т е т в P r i o r i t y Q u e u e . E n q u e u e " ) ;

Метод Dequeue()

Работа метода Dequeue () немного более хитрая. Он должен найти непустую оче­

редь элементов с наивысшим приоритетом и выбрать из нее первый элемент. Первую часть своей работы — поиск непустой очереди элементов с наивысшим приоритетом — Dequeue () делегирует закрытому методу TopQueue ( ) , который будет описан ниже. Затем метод D e q u e u e () вызывает метод D e q u e u e () найденной очереди для извлече­ ния из нее объекта, который и возвращает. Вот исходный текст метода D e q u e u e ( ) :

//Dequeue

- и з в л е ч е н и е

Т из

о ч е р е д и с

наивысшим

// приоритетом

 

 

 

 

 

 

public Т D e q u e u e ()

 

 

 

 

 

//

Просматриваем

о ч е р е д ь с

наивысшим

п р и о р и т е т о м

Queue<T>

q u e u e T o p

=

T o p Q u e u e ( ) ;

 

 

//

Очередь не

п у с т а

 

 

 

 

if

(queueTop

!= n u l l

&& q u e u e T o p . C o u n t > 0)

 

{

 

 

 

 

 

 

 

 

 

r e t u r n

q u e u e T o p . D e q u e u e ( ) ; / / Возвращаем

первый

 

 

 

 

 

 

/ / э л е м е н т

 

}

 

 

 

 

 

 

 

 

/ /

Если

в с е о ч е р е д и

п у с т ы ,

в о з в р а щ а е м n u l l

( з д е с ь

//

можно

с г е н е р и р о в а т ь и с к л ю ч е н и е )

 

 

r e t u r n d e f a u l t ( Т ) ;

}

Единственная сложность состоит в том, как поступить, если все внутренние очереди пусты, т.е. по сути пуста очередь P r i o r i t y Q u e u e в целом? Что следует вернуть в этом

случае? Представленный метод D e q u e u e ()

в этом случае возвращает значение n u l l .

Таким образом, клиент — код, вызывающий

P r i o r i t y Q u e u e . D e q u e u e () — должен

проверять, не вернул ли метод D e q u e u e О

значение n u l l . Где именно возвращается

значение n u l l ? В d e f a u l t (Т), в конце исходного текста метода. О выражении de­ fault (Т) речь пойдет чуть позже.

Вспомогательный метод TopQueue()

Метод D e q u e u e () использует вспомогательный метод T o p Q u e u e () для того, чтобы найти непустую внутреннюю очередь с наивысшим приоритетом. Метод TopQueue () начинает с очереди q u e u e H i g h и проверяет ее свойство C o u n t . Если оно больше 0, очередь содержит элементы, так что метод TopQueue () возвращает ссылку на эту внутреннюю очередь (тип возвращаемого значения метода TopQueue () — Queue<T>). Если же очередь q u e u e H i g h пуста, метод TopQueue () повторяет свои действия с очередями q u e u e M e d i u m и queueLow.

Что происходит, если все внутренние очереди пусты? В этом случае метод TopQueue () мог бы вернуть значение n u l l , но более полезным будет возврат од­ ной из пустых очередей. Когда после этого метод D e q u e u e () вызовет метод De­ queue () возвращенной очереди, тот вернет значение n u l l . Вот как выглядит ис­ ходный текст метода T o p Q u e u e ( ) :

Глава 15. Обобщенное программирование

357


//TopQueue - н е п у с т а я о ч е р е д ь p r i v a t e Queue<T> T o p Q u e u e ( )

{

if ( q u e u e H i g h . C o u n t > 0) r e t u r n q u e u e H i g h ;

if ( q u e u e M e d i u m . C o u n t > 0) r e t u r n q u e u e M e d i u m ;

if ( q u e u e L o w . C o u n t > 0) r e t u r n q u e u e L o w ;

r e t u r n q u e u e L o w ;

}

с

наивысшим

п р и о р и т е т о м

//

Очередь

с

высоким

// п р и о р и т е т о м п у с т а ?

//

Очередь

со

с р е д н и м

// п р и о р и т е т о м п у с т а ?

//

Очередь

с

н и з к и м

//

п р и о р и т е т о м п у с т а ?

//

Все о ч е р е д и пусты

Остальные члены PriorityQueue

Полезно знать, пуста ли очередь P r i o r i t y Q u e u e или нет, и если нет, то сколько элементов в ней содержится (каждый объект отвечает сам за себя!). Вернитесь к листингу демонстрационной программы и рассмотрите исходный текст метода Is Empty () и свой ства Count класса P r i o r i t y Q u e u e .

Незавершенные дела

P r i o r i t y Q u e u e все еще нуждается в небольшой доработке.

Сам по себе класс P r i o r i t y Q u e u e не защищен от попыток инстанцирования для типов, например, i n t , s t r i n g или S t u d e n t , т.е. типов, не имеющих приоритетов, Вы должны наложить ограничения на класс с тем, чтобы он мог быть инстанцирован только для типов, реализующих интер­ фейс I P r i o r i t i z a b l e . Попытки инстанцировать P r i o r i t y Q u e u e да классов, не реализующих I P r i o r i t i z a b l e , должны приводить к ошибк времени компиляции.

Метод D e q u e u e () класса P r i o r i t y Q u e u e возвращает значение null вместо реального объекта. Однако обобщенные типы наподобие <Т> не имеют естественного значения n u l l по умолчанию, как, например, int или s t r i n g . Эта часть метода D e q u e u e () также требует обобщения.

Добавление ограничений

Класс P r i o r i t y Q u e u e должен быть способен запросить у помещаемого в очередь объекта о его приоритете. Для этого все классы, объекты которых могут быть размеще­ ны в P r i o r i t y Q u e u e , должны реализовывать интерфейс I P r i o r i t i z a b l e , как это делает класс P a c k a g e . Класс P a c k a g e указывает интерфейс I P r i o r i t i z a b l e в за­ головке своего объявления:

c l a s s P a c k a g e : I P r i o r i t i z a b l e

после чего реализует свойство P r i o r i t y интерфейса I P r i o r i t i z a b l e .

Компилятор в любом случае сообщит об ошибке, если один из методов обобщенно­ го класса вызовет метод, отсутствующий у типа, для которого инстанцируется обобщенный класс. Однако лучше использовать явные ограничения. Поскольку вы можете инстанцировать обобщенный класс буквально для любого типа, должен быть способ указать компилятору, какие типы допустимы, а какие — нет.

358

Часть V. За базовыми классами


Вы добавляете ограничение путем указания интерфейса I P r i o r i t i z a b l e в заголовке P r i o r i t y Q u e u e :

c l a s s P r i o r i t y Q u e u e < T > where T: IPrioritizable

Обратите внимание на выделенную полужирным шрифтом конструкцию, начинаю­ щуюся со слова w h e r e . Это принудителъ (enforcer), который указывает, что тип Т обя­ зан реализовывать интерфейс I P r i o r i t i z a b l e , т.е. как бы говорит компилятору: "Убедись, подставляя конкретный тип вместо Т, что он реализует интерфейс I P r i o r i ­ tizable, а иначе просто сообщи об ошибке".

Вы указываете ограничения, перечисляя в конструкции w h e r e одно или не­ сколько имен:

имя базового класса, от которого должен быть порожден класс Т (или должен быть этим классом);

имя интерфейса, который должен быть реализован классом Т, как было показано в предыдущем примере.

Дополнительные варианты ограничений включают ключевые слова s t r u c t , c l a s s inew(). С new () вы встретитесь чуть позже в этой главе, а об ограничениях s t r u c t и class можно прочесть в разделе "Generics, constraints" справочной системы.

Эти варианты ограничений повышают гибкость в описании поведения обобщенного иасса. Вот пример гипотетического обобщенного класса, объявленного с несколькими ограничениями на Т:

class MyClass<T> : w h e r e Т: c l a s s , I P r i o r i t i z a b l e , new ()

}

Здесь тип T должен быть классом, а не типом-значением; он должен реализовать интерфейс I P r i o r i t i z a b l e и содержать конструктор без параметров. Достаточно

А если у вас есть два обобщенных параметра и оба должны иметь ограниче­ ния? (Да, да — вы можете использовать несколько обобщенных параметров одновременно!) Вот как можно записать две конструкции w h e r e :

c l a s s MyClass<T, U> : w h e r e T: I P r i o r i t i z a b l e , w h e r e U: new()

Определение значения null д л я типа T

Как уже упоминалось ранее, у каждого типа есть свое значение по умолчанию, озна­

чающее "ничто" для данного типа. Для

i n t

и других типов чисел это 0 (или 0.0). Для

string— пустая строка " " . Для b o o l

это

f a l s e , а для всех ссылочных типов, таких

как Package, это n u l l .

 

 

Однако поскольку обобщенный класс

наподобие P r i o r i t y Q u e u e может быть ин-

панцирован практически для любого типа данных, С# не в состоянии предсказать, каким должно быть правильное значение n u l l в исходном тексте обобщенного класса. Напри­ мер, в методе Dequeue () класса P r i o r i t y Q u e u e вы можете оказаться именно в такой ситуации: вы вызываете Dequeu e ( ) , но очередь пуста и пакетов нет. Что вы должны вер­ нуть, что бы могло означать "ничего"? Поскольку P a c k a g e — класс, следует вернуть зна­ чение n u l l . Это сообщит вызывающей функции, что ничего вернуть не удалось (вызывающая функция, само собой, должна проверять, не вернулось ли значение n u l l ) .

Глава 15. Обобщенное программирование

359


Компилятор не может придать смысл ключевому слову null в исходном тек сте обобщенного класса, поскольку обобщенный класс может быть инстанци

рован для любых типов данных. Вот почему в исходном тексте метода De­ q u e u e () используется следующая конструкция:

r e t u r n d e f a u l t ( Т ) ; / / З н а ч е н и е n u l l дл я т и п а Т

Эта строка указывает компилятору, что нужно посмотреть, что собой представляет тип Т и вернуть верное значение n u l l для этого типа. В случае P a c k a g e , который в ка честве класса представляет собой ссылочный тип, верным возвращаемым значением у дет n u l l . Однако для некоторых других Т это значение может быть иным, и компилятор сможет верно определить, что именно следует вернуть.

Если вы думаете, что обобщенный класс P r i o r i t y Q u e u e достаточно ги бок, то на прилагаемом компакт-диске вы можете найти еще более гибкую версию обобщенного класса P r i o r i t y Q u e u e и познакомиться с некоторыми принципами объектно-ориентированного дизайна, обратившись к де-| монстрационной программе P r o g r a m m i n g T o A n l n t e r f а с е .

Часто методы в обобщенных классах также должны быть обобщенными. Вы уже ви­

дели это

в примере в предыдущем разделе. Метод D e q u e u e () в классе Priori­

t y Q u e u e

имеет возвращаемый тип Т. В этом разделе рассказывается, как можно ис­

пользовать обобщенные методы как в обобщенных, так и в необобщенных классах. Обобщенными могут быть даже методы в обычных необобщенных классах. Напри­

мер, приведенный далее код показывает обобщенный метод Swap ( ) , разработанный дм обмена двух своих аргументов. Первый аргумент получает значение второго аргумента и наоборот (такой обмен проиллюстрирован на рис. 6.2 в главе 6, "Объединение дан­

н ы х —

классы и массивы"). Чтобы

увидеть, что он работает, объявите два параметра

Swap ()

с применением ключевого

слова r e f , чтобы аргументы типов-значений также

можно было передавать по ссылке, и посмотрите на результат работы метода (об исполь­ зовании ключевого слова r e f рассказывается в главе 7, "Функции функций").

Перед вами исходный текст демонстрационной программы, в которой объ­ является и используется метод Swap ( ) .

// G e n e r i c M e t h o d - м е т о д , к о т о р ы й может р а б о т а т ь с данными / / р а з н ы х т и п о в

u s i n g S y s t e m ;

n a m e s p a c e G e n e r i c M e t h o d

{

c l a s s P r o g r a m

{

//Main -

//р а б о т а е т

// M a i n ( ) ;

s t a t i c v o i d

{

п р

о в е р к а д в у х

в е р с и й обобщенного

м е т о д а ; один

с

к л а с с о м на

том

же у р о в н е ,

ч т о

и функция

в т о р о й н а х о д и т с я

в обобщенном

к л а с с е

M a i n ( s t r i n g [ ]

a r g s )

 

 

360

Часть V. За базовыми классам»