Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
278
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

780

Часть IV * Расширенное исоользованме С^^^

 

 

public:

 

 

 

 

 

StackO;

Туре&);

/ /

конструктор no умолчанию

 

void push(const

/ /

занесение

в вершину стека

 

Type popO;

const;

/ /

извлечение

верхнего символа

 

bool isEmptyO

/ /

стек пуст?

 

 

"StackO;

 

/ /

возвращение памяти динамически

 

 

 

/ /

распределяемой области

 

}

 

 

 

 

Когда создается экземпляр объекта этого класса, совсем недостаточно опреде­ лить фактический тип элементов массива. Необходимо указать размер массива.

Stack<int,4> s;

/ / объект S стека

Обратите внимание, что объект-стек сам не содержит каких-либо параметров. Конструктору по умолчанию они не требуются. Заметьте, что параметр должен передаваться по значению. Информация пересылается только в одном направле­ нии, от клиентской программы к объекту шаблона. Параметры-ссылки не допус­ каются. Для этой цели могут использоваться только обычные параметры функции.

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

int size = 4; const int

length

 

 

Stack<int,size> s;

/ /

синтаксическая

ошибка: не константа

Stack<int,length> s;

/ /

OK: константа

во время компиляции

Когда функции-члены шаблона реализуются за пределами определения класса,

всписке должны быть перечислены все параметры шаблона. Если класс-шаблон содержит параметры-выражения, последние должны быть перечислены как

всписке параметров шаблонов, так и в префиксе шаблона класса.

template <class Type, int sz>

/ /

параметр-выражение

Stack<Type,sz>::Stack()

/ /

параметр-выражение

:

size(sz),top(O)

 

 

{ items = new Type[sz];

/ /

использование параметра-выражения

i f

(items==0)

 

 

{

cout « "Out of memory\n";

e x i t ( l ) ; } }

Подобно параметрам типа, параметры-выражения являются "заместителями". Их имена не важны до тех пор, пока они не противоречат функции. Они не должны противоречить различным функциям шаблонного класса. Приведем еш,е одну функцию-стек, в которой используются различные имена для параметра типа и для параметра-выражения.

template <class Т, int s>

 

 

/ /

разные имена для параметров

void

Stack<T,s>::push

(const

T& c)

 

/ /

непротиворечивые имена параметров

{

i f (top

< size)

 

 

 

/ /

обычный случай: занесение символа

 

items

[top++]

= c;

 

 

 

 

 

else

 

 

 

 

 

 

 

{ T *p = new T[size*2];

/ /

получить дополнительную память

 

i f (р == 0)

 

 

/ /

в динамически распределяемой области

 

 

 

 

 

 

 

{

cout «

'Out of

memory\n";

e x i t ( l ) ; }

Глава 17 • Шаблоны как еще одно средство проектирования

781

for (int i=0; i < size; i++)

// копирование существующего стека

p[i] = items[i];

// возвращение памяти динамически

delete [] items;

items = р;

// распределяемой области

 

// корректировка размера стека

size *=2;

cout « "New size: " « size «

endl;

 

items[top++] = c; } }

// занесение символа на вершину

Подобно параметрам типа, параметры выражения должны перечисляться как в списке параметров шаблона, так и в префиксе имени класса для всех функ­ ций-членов. Даже если параметр-выражение не используется в теле функции, он все равно должен быть указан всписке.

template <class Type, int sz>

// параметры не используются

Type Stack<Type.sz>::pop()

{ return items[-top]; }.

 

template <class Tp, int s>

// параметры не используются

bool Stack<Tp,s>::isEmpty() const

{ return top ==0; }

 

template <class Type, int sz>

 

Stack<Type,sz>::~Stack()

// параметры не используются

{ delete [] items; }

Основная характеристика шаблонных классов с параметрами выражениями состоит в том, что каждый создаваемый экземпляр представляетдругой тип C + + .

Как разные типы они не совместимы, и объект одного типа не может использова­

ться там, где ожидается объект другого типа.

Stack<int,4>

s;

// объект-стек

Stack<int,8>

s1;

// несовместимый объект-стек

Рассмотрим глобальную функцию DebugPrint(). У нее есть параметр класса Stack<int, 4>. Обратите внимание, что объект параметра передается по ссылке — закрытое объявление Stack<Type, sz> конструктора копирования предотвраш.ает передачу объектов-стеков по значению. Кроме того, заметьте, что параметр не помечен как const, поскольку он изменяется при выполнении функции.

void DebugPrint(Stack<int,4>& s)

// модификатор const отсутствует

{ Stack<int,4> temp;

 

cout «

"Debugging print: ";

// извлечение, пока стек не опустеет

while

(! s. isEmptyO)

{ int X = s.popO; temp.push(x);

// сохранение во временном стеке

cout « X « " "; }

// печать каждого компонента

cout «

endl;

// извлечение, пока стек не опустеет

while (! temp. isEmptyO)

{ S.push(temp.рорО); } }

// восстановление исходного состояния

Объекты стека s и s1 — это объекты разных типов. Объект s можно передать

как параметр в DebugPrint(). При попытке передать подобным образом объект s1 появляется синтаксическая ошибка.

DebugPrint(s);

//

OK

DebugPrint(sl);

// синтаксическая ошибка

Фактические выражения, определяемые одним значением, эквивалентны.

const intlength = 4;

// совместимо со Stack<int,4>

Stack<int,length> s2;

782

Часть IV * Расширенное использование 0-^+

Что касается только шаблонов с параметрами типа, все реализации с одинако­ выми фактическими параметрами типа бывают одного типа, и один объект может использоваться вместо другого. Рассмотрим повторно шаблонный класс с одним параметром типа.

template <class Туре>

 

 

class Stack {

 

 

 

Type *itemns;

 

/ /

стек элементов типа Type

int top, size;

 

/ /

текущая вершина, общий размер

Stack(const Stack& = 100);

 

 

operator = (const Stack&);

 

 

public:

 

 

 

Stack(int);

 

/ /

конструктор преобразования

void push(const

Type&);

/ /

помещение на вершину стека

Type рорО;

 

/ /

извлечение верхнего символа

bool isEmptyO

const;

/ /

стек пуст ?

"StackO;

/ / возвращение памяти динамически распределяемой области

} ;

 

 

 

Эти два объекта относятся к одному классу, поэтому один объект может исполь­ зоваться вместо другого.

Stack<int>

stackl(20);

/ / того же типа, что и другие объекты Stack<int>

Stack<int>

stack2(50);

 

Это более гибкая и удобная реализация, чем реализация с использованием пара­ метра-выражения. Шаблонный класс с параметрами типа и параметром-конст­ руктором может выполнять все, что делает шаблонный класс с дополнительным параметром-выражением и без параметра конструктора. Кроме того, объекты различной исходной длины совместимы. Следует избегать шаблонов с параметра­ ми-выражениями, за исключением тех случаев, когда очевидны их преимуи;ества по сравнению с шаблонами с параметрами-классами.

Взаимосвязи между реализациями шаблонных классов

Реализации шаблонов могут использоваться как фактические параметры типа для реализации других шаблонных классов. Например, можно создать стек сло­ варных статей в следуюи;ем объявлении.

Stack<DictEntry<Point,char*> > stackofEntries; / / 100 записей

Обратите внимание на большой промежуток между двумя знаками "больше". Если не включить эти пробелы, компилятор поймет код неправильно и покажет огромное количество сообщений об ошибках, совершенно не относяш,ихся к делу. Ни одно из этих сообш^ений не подскажет, что требуются дополнительные пробе­ лы. Это второй случай, когда язык C++ учитывает пробелы. Они имеют значение при определении значения параметра по умолчанию для параметра типа указателя функции, когда имя параметра не используется.

В приведенном выше объявлении реализация Stack подсказывает реализацию DictEntry. Оптимизированный компилятор может занести код объекта в кэш­ память для повторного использования при выполнении компиляции. Если компи­ лятор этого не делает, время компиляции и компоновки увеличивается.

Реализации шаблонов для различных фактических типов (и значений выраже­ ний) обособлены, и между ними нет связи или доступа друг к другу.

Например, DictEntry<int, int> и DictEntry<float, recorcl> представляют собой два независимых индивидуальных класса. Такими же являются реализации д/ш Stack<int> и Stack<float>. Объекты этих типов не могут заменять друг друга.

Глава 17 • Шаблоны как еще одно средство проектирования

783

Класс может объявить все свои реализации шаблонов как имеющие общий базо­ вый нешаблонный класс.

template <class Т>

class Stack : public BaseStack {

Все реализации класса Stack будут иметь доступ к объектам BaseStack в соот­ ветствии с правилами наследования. Эти реализации не имеют доступа к необще­ доступным компонентам друг друга.

Шаблонные классы как ^^друзья

Нешаблонный класс (или функцию) можно объявить "другом" всех реализаций шаблонного класса, если использование созданных экземпляров объектов не за­ висит от их типа:

template <class Т> class Stack {

friend class StackUser;

Здесь класс StackUser имеет доступ к необщедоступным компонентам любой реа­ лизации класса Stack независимо от типа, используемого в качестве фактического.

Шаблонный класс можно объявить другом нешаблонного класса, даже когда его параметр типа не связан с каким-либо фактическим значением.

class Node {

template <class Т> friend class Stack; int item;

Node *next;

/ / Node *next' также OK

Node(const int val)

: item(val)

{ next = NULL; }

 

} ;

 

Здесь класс Node может поддерживать информационное поле и связь со следую­ щим узлом в связанном списке. Для него не требуется никаких функций-членов. Необходим конструктор, который инициализирует оба поля данных.

Каждая реализация класса Stack является другом нешаблонного класса Node и имеет доступ к его необщедоступным компонентам. Это может быть полезным, если при вынесении за скобки общего кода уменьшается размер (и время компи­ ляции/компоновки) объектной программы. Вместо динамически распределяемой области памяти, выделенной при реализации (или при переполнении массива), класс Stack может выделять объект Node и освобождать верхний объект Node.

Однако это не слишком полезно. Node одного типа (например, с полем инфор­ мации целого типа) не сможет вместить объекты разных типов, которые клиент­ ская программа хотела бы поместить в стек. Следовательно, класс Node также должен быть шаблоном.

Кроме того, класс Node должен определяться как шаблонный класс. Тогда объекты стека разных типов смогут создавать экземпляры и осуществлять доступ к объектам класса Node разных типов с различными типами поля item.

template <class

Туре>

/ /

шаблонный класс

class Node {

 

 

 

friend class Stack<T>;

/ /

компонент любого типа

Type item;

 

 

 

Node<Type>

*next;

/ /

Node *next' также OK

Node(const

Type& val) : item(val)

 

 

{ next = NULL; }

784

Часть IV • Расширенное использование C-f^

Технический термин для такого использования шаблонов — неограниченные типы. Параметр Туре не зависит от параметра Т и каждый параметр может при­ нимать любые фактические значения. Каждый созданный экземпляр класса Stack (например, типа float) имеет доступ к любой реализации класса Node (например, класса Point). Данная программа не реализует в полной мере модель реального мира.

Вам следует отобразить связанные реализации так, чтобы стек целых значений стал "другом" только узла целого типа, а не узла с другими типами поля item. Чтобы получить такое отображение, нужно связать "дружественный" (клиент­ ский) шаблонный класс (в данном случае Stack) с таким же типом, что и шаблон­ ный класс, который предоставляет сервисы (в данном случае Node).

template <class

Туре>

 

/ /

шаблонный класс

class Node {

 

 

/ /

компонент одинакового типа

friend class Stack<Type>;

Type item;

 

 

/ /

Node *next', также OK

Node<Type>

*next

;

Node(const

Type& val) : item(val)

 

 

{ next = NULL;

}

 

 

Здесь для каждого созданного экземпляра Node конкретного типа (например, класса Point) реализация Stack для того же типа (класс Point) объявляется "другом" этой реализации класса Node.

Теперь у класса Stack имеется элемент данных указателя класса Node, который инициализируется в нулевое значение с помош,ью конструктора Stack. Когда в стек заносится следуюш^ий узел, этот указатель ссылается на следующий узел (а новый узел указывает на узел, который обычно является первым узлом в спис­ ке). Функция-член isEmptyO проверяет, является ли данный указатель нулевым и указывает ли он на узел. Следовательно, функция рор() должна установить такой указатель в нулевое значение при удалении стека последнего узла.

template <class Т> class Stack {

Node<T> *top;

 

/ /

Node *top; не допустима здесь

public:

 

 

 

 

StackO

 

/ /

по умолчанию: исходная длина отсутствует

{ top

= NULL;

}

 

 

void

push(const T&);

 

 

T popO;

 

 

 

int isEmptyO

const

/ /

указывает ли вершина на узел?

{ return top == NULL;

"StackO;

 

 

 

} :

Как и для любого шаблона, использование Node вне определения Node уточня­ ется списком параметров. Именно поэтому элемент данных top, принадлежаи|ий Stack, не может иметь тип Node*. Он должен быть типа Node<T>*, где Т является параметром типа в Stack. В результате определения Stack создание экземпляра объекта класса Stack ведет к автоматическому заданию поля данных класса Node того же самого типа.

Метод pushO класса Stack выделяет новый объект Node в динамически рас­ пределяемой области памяти. Вызов конструктора Node инициализирует поле item объекта Node в значение параметра push(). Следуюш.ее поле нового Node устанав­ ливается в указатель на узел, на который в настояш,ее время указывает поле top класса Stack, а поле top устанавливается в указатель на объект нового Node.

Глава 17 • Шаблоны кок еще одно средство проектирования

785

template <class Т>

 

 

 

void Stack<T>::push (const T&val)

// тип Node<T>, неNode

 

{ Node<T> *p = new Node<T>(val);

 

if (p == NULL)

 

 

 

{ cout « "Out of memory\n";exit(l); }

 

p->next =top;

/ /

установить указатель

на первый узел

top = p; }

/ /

обозначает новый узел

 

В push О не надо проверять переполнение массива, потому что в этой реализа­ ции массив отсутствует. Советуем уточнить, является ли успешным выделение объекта Node. Обратите внимание на тип указателя — это Node<T>*, а не Node*. Подобным же образом, когда оператор new запрашивает память в динамически распределяемой области, тип этого оператора Node<T>, а не Node. Тип Т задается при создании экземпляра Stack.

Метод рорО класса Stack устанавливает локальный указатель (типа Node<T>, а не просто Node) на первый узел стека, копирует поле информации в локальную переменную (типа Т), изменяет поле top так, чтобы оно указывало на второй узел, и удаляет узел top, поскольку он больше не нужен.

template <class Т>

 

 

Т Stack<T>::рор()

/ /

возвращаемое значение типа Т

{ Node<T> *р = top;

/ /

Node типа Т, а не Node

Т val

= top->item;

/ /

получить значение типа Т

top = top->next;

/ /

обозначает второй узел

delete

p;

/ /

вернуть узел в динамически

 

 

/ /

распределяемую область памяти

return

val; }

 

 

Когда рорО удаляет последний узел списка (второй узел для указания в поле top отсутствует), указатель top снова становится NULL. Когда в push() заносится пер­ вый узел, оператор p->next = top устанавливает это поле в NULL. Убедитесь, что функции-члены одного класса тесно связаны друг с другом через данные класса. Исходный код функций-членов должен быть скоординирован для того, чтобы убедиться в правильном взаимодействии функции.

Деструктор Stack должен сканировать связанный список остальных узлов и возвраш[,ать их в динамически распределяемую область памяти. Снова использу­ ется локальный указатель р типа Node<T> с компонентом типа Т. Он устанавлива­ ется в указатель на первый узел списка. Обратите внимание, что указатель на первый узел списка, элемент данных top, того же типа, что и указатель р: Node<T>. В цикле while указатель top перемеидается на следуюш.ий узел. Узел, на который указывал указатель р, удаляется, а указатель р смеш^ается на следуюш^ий узел.

template

<class Т>

 

 

Stack<T>: rStackO

 

 

{ Node<T> *p = top;

/ /

тип Node типа T

while

(top != NULL)

/ /

если узлы отсутствуют, то top равен О

 

{ top = top->next;

/ /

указатель на следующий узел

 

delete p;

/ /

удалить предыдущий узел

 

P = top; } }

/ /

проход к следующему узлу

Преимуш,ество этого подхода состоит в том, что класс Node не зависит от класса Stack. Значит, класс Node может использоваться другими "дружественны­ ми" классами, например Queue и List. Поскольку все компоненты Node (включая конструктор) являются закрытыми в этой структуре, то не "дружественные" клиенты не могут создать или осуш,ествить доступ к объектам Node.

Другой подход состоит в обеспечении каждого клиента своим собственным сер­ верным классом. Если определение Node вложено в закрытую часть клиента, тогда этот клиент (и его "друзья") могут осуш^ествить доступ к классу Node.

786 Част IV * Расширенное использование C-i-^f

Вложенные шаблонные классы

При использовании вложенной структуры определение шаблонного класса Node вкладывается в определение контейнерного класса, который обрабатывает объекты Node. Поскольку определение Node полностью находится в области виде­ ния класса-контейнера (например, Stack), имя Node не видно другим потенциаль­ ным клиентам (например. Queue и List). Тем не менее, компоненты Node могут стать обш,едоступными. Вам не нужно объявлять его единственного клиента (например, Stack) "другом" класса Node.

Это первая попытка проектирования с вложенными классами. Класс Node опре­ деляется при помош,и зарезервированного слова struct, и все его компоненты являются обш.едоступными.

template <class Т>

 

 

class Stack

{

 

 

 

template

<class

Type>

/ /

Допустимо? Необходимо?

struct

Node {

 

 

 

Type item;

 

 

 

Node<Type>

*next;

/ /

тип зависит от реализации

Node(const

Type&

val): item(val)

{ next = NULL; } } ;

 

Node<T> *top;

 

/ /

элемент данных Stack

public:

 

 

 

 

StackO

 

 

/ /

по умолчанию; исходная длина не определена

{ top = NULL; }

 

 

 

void push(const

T&);

 

 

TpopO;

 

 

 

 

int isEmptyO const

 

 

{ return top == NULL; }

/ /

указывает ли top на узел'?

"StackO;

 

 

 

 

При таком определении возникают проблемы. Во-первых, некоторые компилято­ ры не принимают определения вложенных шаблонов — они могут обрабатывать только глобальные шаблоны. Во-вторых, отсутствует необходимость использова­ ния шаблонов несвязанных типов. В этой структуре отображение классов Stack и Node относится к типу "один ко многим". Для любого типа аргумента класса Stack класс Node может использовать любой другой тип. Отображение между Stack и Node должно быть "один к одному", а не "один ко многим". Классу Stack требуется объект Node, который реализуется с тем же фактическим типом, что и сам класс Stack.

Хорошим способом достижения этого является определение класса Stack как шаблона, а затем класса Node как обычного нешаблонного класса, используюидего параметр тип Stack для типов своего элемента данных и для его параметра метода.

template <class Т> class Stack {

struct Node { T item; Node *next;

Node(const T& val) { next = NULL; } }

Node *top; public:

StackO

{ top = NULL; }

void push(const T&);

/ /

это зависит от типа параметра

/ /

некоторые типы как в Stack

/ /

здесь Node<T> неверно

item(val)

/ / некоторые типы как в Stack

/ / это теперь не шаблон

/ / по умолчанию: исходная длина не определена

Initial data: 1234567890 Inversed data: 0987654321

Глава 17 • Шаблоны как еще одно средство проектирования

787

Т рорО;

 

 

 

Int isEmptyO

const

/ / указывает ли top на узел?

 

{ return top

== NULL; }

 

"StackO;

}

Каждая реализация шаблонов Stack генерирует класс Node, который использу­ ет тот же тип, что и параметр фактического типа Stack. Тип Node в определении Stack уточнять не требуется.

Это справедливо и для функций-членов Stack. Локальный указатель в функ­ ции-члене определяется как указатель на тип Node, а не на тип Node<T>. Например, метод push() почти такой же, как и в предыдущем варианте, но указатель р опре­ деляется иначе. Для объяснения предыдущей версии указателя можно сравнить обе версии.

Рис. 17.5.

Вывод для из листинга

template <class Т>

 

 

 

void Stack<T>::push (const T& val)

/ /

тип Node<T>, a не Node

/ /

{ Node<T> *p = new Node<T>(val);

{

Node *p = new Node(val);

/ /

тип Node,

a не Node<T>

 

i f

(p

=-

NULL)

exit(l);

}

 

 

 

{

cout « "Out of memory\n";

 

 

p->next

= top;

/ /

установить его на первый узел

 

top

= p;

}

/ /

установить

его на новый узел

 

Это же справедливо для других методов класса-контейнера. В лис­

 

тинге 17.5 показана реализация шаблонного класса Stack со вло­

 

женным классом Node. Вывод программы показан на рис. 17.5.

 

Как можно видеть, связывание типов компонентов для согласо­

программы

ванных шаблонных классов зависит от обш,его подхода к проекти­

17.5

рованию. Приводит в уныние, что решение для одной структуры

 

(например, глобальных классов) не подходит для другой структуры

(например, вложенных классов). В любом случае убедитесь в том, что когда для одного класса создается экземпляр конкретного типа, то для второго класса задается экземпляр того же типа.

Листинг 17.5. Пример шаблонного класса с вложенным серверным классом

#include <iostream> using namespace std;

template <class T> class Stack {

struct Node {

/ /

это зависит от типа параметра

T item;

/ /

тот же тип, что и в Stack

Node *next;

/ /

здесь Node<T> неверно

Node(const T& val) item(val)

/ /

тот же тип, что и в Stack

{ next = NULL; }

 

 

Node *top; public:

StackO

{ top = NULL; }

void push(const T&); T popO;

int isEmptyO const

{ return top == NULL; } "StackO;

/ / теперь это не шаблон

/ / по умолчанию: исходная длина не определена

/ / указывает ли top на узел?

788

Часть IV • Расширенное использование C4--I-

template <class Т>

 

void Stack<T>::push (const T&val)

// тип Node<T>, a неNode

// { Nocle<T> *p = new Node<T>(val);

 

{ Node *p = new Node(val);

// тип Node, a неNode<T>

 

if (p == NULL)

 

 

{ cout « "Out ofmemory\n"; exit(l); }

 

p->next = top;

// установить его на первый узел

 

top = p; }

// установить его на новый узел

template <class T>

TStack<T>::pop()

//{Node<T> *p = top;

{Node *p = top;

T val =top->item; top = top->next; delete p;

return val; }

template <class T> Stack<T>::~StaGk()

//{ Node<T> *p = top;

{Node *p = top; while (top !=NULL)

{top = top->next; delete p;

P =top; } }

int main( )

{

int data[] = {1,2, 3, 4, 5, 6, 7, Stack<int> s;

int n = sizeof (data)/sizeof (int); cout « "Initial data: ";

for (int j =0; j < n;

j++)

{ cout « data[j] «

" " }

cout « endl;

i++)

for (int i = 0; i < n;

{ s. push(data[i]); } cout « " Inversed data: "; while ( ! s. isEmptyO)

cout « s . pop( ) « " " cout « endl;

return 0 ;

//возвращаемое значение типа Т

//тип Node<T>, а неNode

//тип Node, а неNode<T>

//получить значение типа Т

//установить на второй узел

//возвратить узел top в динамически

//распределяемую область памяти

//тип Node типа Т

//тип Node типа Т

//при отсутствии узлов top равен О

//установить наследующий узел

//удалить предыдущий узел

//переход кследующему узлу

9, О } ;

//объект-стек

//количество компонентов

//вывод на печать входных данных

//занесение данных в стек

//извлечение, пока неопустеет стек

Шаблоны со статическими компонентами

Если в шаблонном классе объявляются статические данные, то каждая реали­ зация шаблона будет включать отдельный набор этих статических компонентов. Все объекты, принадлежаш,ие к конкретной реализации, совместно используют

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

Например, класс Stack может объявить свой элемент данных top статическим. Это интересная альтернатива для проектирования. Если элемент данных top объ­ явлен статическим, то поля данных item и next могут быть перемеидены в класс

Глава 17 • Шаблоны как еще одно средство проектирования

789

Stack как элементы данных, не являющиеся статическими. Что тогда останется в классе Node? Ничего. Он становится избыточным. Следовательно, с помощью подобной структуры можно избавиться от класса Node.

В этой структуре класс Stack объединяет роли Stack из предыдущ'их примеров (вызовы функций-членов push(), рор() и isEmpty()) и роль класса Node (поля item

иnext). Именно поэтому в нем два конструктора: конструктор по умолчанию

иконструктор преобразования.

Конструктор по умолчанию вызывается, когда в клиентской программе созда­ ется экземпляр объекта Stack. Он должен присутствовать, чтобы исключить син­ таксическую ошибку. Конструктор преобразования вызывается из метода push(). Когда должен быть выделен новый узел, push() создает новый объект Stack, а не Node. Конструктор инициализирует поле item (в значение, которое должно сохра­ няться) и поле next (для указания на узел top класса Stack).

Функция рорО удаляет узел top, используя локальный указатель. Указатель имеет тип — указатель на Stack<T>. Поскольку это указатель на объект типа Stack, вызывается деструктор Stack. В предыдущих вариантах деструктор Stack удалял оставшиеся узлы стека. Здесь это опасно. Именно поэтому в классе Stack отсутствует деструктор. (Там имеется деструктор по умолчанию, который ничего не делает.)

template <class Т> class Stack {

static

Stack

*top;

 

T item;

 

 

 

Stack

*next;

 

 

 

public:

 

 

 

 

StackO { }

 

 

 

Stack(const

T& val)

 

 

 

: item(val),

next(top)

{

top = this;

}

 

void

push(const

T& val)

{

Stack<T> *p=new

Stack<T>(val);

T pop

0

 

 

 

{Stack<T> *p= top; T val = top->item; top = top->next; delete p;

return val; }

int isEmptyO const

{return top == I^ULL; } void removeO

{Stack<T> *p = top;

while (top '= NULL)

{top = top->next; delete p;

P = top; } }

} ;

/ / статический элемент данных

/ / из Node

/ / создать объект на клиенте

/ / создать новый узел в push()

 

/ /

нет Node<T>, нет Node

/ /

не Node<T>,

не Node

/ /

установить

на второй узел

/ /

удалить узел top: деструктор

/ /

деструктор не вызывается

/ /

конечный указатель

/ /

переход к следующему узлу

/ /

удалить предыдущий вызов

/ /

перейти к следующему узлу

Отсутствие деструктора создает опасность утечки памяти. Чтобы избежать этого, класс Stack предоставляет метод remove(), выполняющий то же самое, что и деструктор класса Stack в предыдущих вариантах. Недостаток такой структуры состоит в том, что клиентская программа должна явно вызывать метод removeO для удаления оставшихся узлов в Stack.

Инициализация статического компонента шаблонного класса осуществляется не в начале выполнения программы (как для статических компонентов обычных классов), а при создании экземпляра для объекта шаблона. В этот момент созда­ ется статический компонент для этого конкретного фактического типа.

Соседние файлы в предмете Программирование на C++