утомителен (и потому чреват ошиками), но с помощью макросов его можно «механизировать». К сожалению, если пользоваться стандартным C препроцессором (#4.7 и #с.11.1), это тоже может оказаться тягостным. Однако полученными в результате макросами пользоваться довольно просто.
Вот пример того, как обобщенный (generic) класс slist, названный gslist, может быть задан как макрос. Сначала для написания такого рода макросов включаются некоторые инстрменты из «generic.h»:
#include «slist.h»
#ifndef GENERICH #include «generic.h» #endif
Обратите внимание на использование #ifndef для того, чтобы гарантировать, что «generic.h» в одной компиляции не будет включен дважды. GENERICH определен в «generic.h».
После этого с помощью name2(), макроса из «generic.h» для конкатенации имен, определяются имена новых обобщенных
классов:
#define gslist(type) name2(type,gslist) #define gslist_iterator(type) name2(type,gslist_iterator)
И, наконец, можно написать классы gslist(тип) и gslist_iterator(тип):
#define gslistdeclare(type) struct gslist(type) : slist (* int insert(type a) (* return slist::insert( ent(a) ); *) int append(type a) (* return slist::append( ent(a) ); *) type get() (* return type( slist::get() ); *) gslist (type)() (* *) gslist(type)(type a) : (ent(a)) (* *) ~gslist(type)() (* clear(); *) *); struct gslist_iterator (type) : slist_iterator (* gslist_iterator(type)(gslist(type) amp; a) : ( (slist amp;)s ) (**) type operator()() (* return type( slist_iterator::operator()() ); *) *)
на конце строк указывает , что следующая строка явлется частью определяемого макроса.
С помощью этого макроса список указателей на имя, аналгичный использованному раньше классу nlist, можно определить так:
#include «name.h»
typedef name* Pname; declare(gslist,Pname); // описывает класс gslist(Pname)
gslist(Pname) nl; // описывает один gslist(Pname)
Макрос declare (описать) определен в «generic.h». Он конкатинирует свои параметры и вызывает макрос с этим именем, в данном случае gslistdeclare, описанный выше. Параметр имя типа для declare должен быть простым именем. Используемый мтод макроопределения не может обрабатывать имена типов вроде name*, поэтому применяется typedef.
Использование вывода класса гарантирует, что все частные случаи обобщенного класса разделяют код. Этот метод можно применять только для создания классов объектов того же размра или меньше, чем базовый класс, который используется в маросе. gslist применяется в #7.6.2.
7.3.6 Ограниченные Интерфейсы
Класс slist – довольно общего характера. Иногда подобная общность не требуется или даже нежелательна. Ограниченные вды списков, такие как стеки и очереди, даже более обычны, чем сам обобщенный список. Такие структуры данных можно задать, не описав базовый класс как открытый. Например, очередь целых можно определить так:
#include «slist.h»
class iqueue : slist (* //предполагается sizeof(int)«=sizeof(void*)
public: void put(int a) (* slist::append((void*)a); *) int det() (* return int(slist::get()); *) iqueue() (**) *);
При таком выводе осуществляются два логически разделеных действия: понятие списка ограничивается понятием очереди (сводится к нему), и задается тип int, чтобы свести понятие очереди к типу данных очередь целых, iqueue. Эти два действия можно выполнять и раздельно. Здесь первая часть – это список, ограниченный так, что он может использоваться только как стек:
#include «slist.h»
class stack : slist (* public: slist::insert; slist::get; stack() (**) stack(ent a) : (a) (**) *);
который потом используется для создания типа «стек укзателей на символы»:
#include «stack.h»
class cp : stack (* public: void push(char* a) (* slist::insert(a); *) char* pop() (* return (char*)slist::get(); *) nlist() (**) *);
7.4 Добавление к Классу
В предыдущих примерах производный класс ничего не добалял к базовому классу. Для производного класса функции опрделялись только чтобы обеспечить преобразование типа. Каждый производный класс просто задавал альтернативный интерфейс к общему множеству программ. Этот специальный случай важен, но наиболее обычная причина определения новых классов как проиводных классов в том, что кто-то хочет иметь то, что предотавляет базовый класс, плюс еще чуть-чуть.
Для производного класса можно определить данные и фунции дополнительно к тем, которые наследуются из его базового класса. Это дает альтернативную стратегию того, как обеспчить средства связанного списка. Заметьте, когда в тот slist, который определялся выше, помещается элемент, то создается slink, содержащий два указателя. На их создание тратится врмя, а ведь без одного из указателей можно обойтись, при услвии, что нужно только чтобы объект мог находиться в одном списке. Так что указатель next на следующий можно поместить в сам объект, вместо того, чтобы помещать его в отдельный обект slink. Идея состоит в том, чтобы создать класс olink с единственным полем next, и класс olist, который может обрабтывать указатели на такие звенья olink. Тогда olist сможет манипулировать объектами любого класса, производного от olink. Буква 'o' в названиях стоит для того, чтобы напоминать вам, что объект может находиться одновременно только в одном списке olist:
struct olink (* olink* next;
*);
Класс olist очень напоминает класс slist. Отличие состит в том, что пользователь класса olist манипулирует объектми класса olink непосредственно:
class olist (* olink* last; public: void insert(olink* p); void append(olink* p); olink* get(); // ... *);
Мы можем вывести из класса olink класс name:
class name : public olink (* // ... *);
Теперь легко сделать список, который можно использовать без накладных расходов времени на размещение или памяти.
Объекты, помещаемые в olist, теряют свой тип. Это ознчает, что компилятор знает только то, что они olink'и. Првильный тип можно восстановить с помощью явного преобразовния типа объектов, вынутых из olist. Например:
void f() (* olist ll; name nn; ll.insert( amp;nn); // тип amp;nn потерян name* pn = (name*)ll.get(); // и восстановлен *)
Другой способ: тип можно восстановить, выводя еще один класс из olist для обработки преобразования типа:
class onlist : public olist (* // ... name* get() (* return (name*)olist::get(); *) *);
Имя name может одновременно находиться только в одном olist. Для имен это, может быть, и не подходит, но в классах, для которых это подойдет полностью, недостатка нет. Например, класс фигур shape использует для поддержки списка всех фигур именно этот метод. Обратите внимание, что можно было бы опрделить slist как производный от olist, объединяя таким обрзом оба понятия. Однако использование базовых и производных классов на таком микроскопическом уровне может очень сильно исказить код.
7.5 Неоднородные Списки
Предыдущие списки были однородными. То есть, в список помещались только объекты одного типа. Это обеспечивалось апаратом производных классов. Списки не обязательно должны быть однородными. Список, заданный в виде указателей на класс, может содержать объекты любого класса, производного от этого класса. То есть, список может быть неоднородным. Верятно, это единственный наиболее важный и полезный аспект призводных классов, и он весьма существенно используется в стле программирования, который демонстрируется приведенным выше примером. Этот стиль программирования часто называют объектно
–основанным или объектно-ориентированным. Он опирается на то, что действия над объектами неоднородных списков выполняются одинаковым образом. Смысл этих действий зависит от фактичекого типа