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

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

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

770

Часть IV # Расширенное использование С'^-^

Та же идея используется, когда методы общего класса реализуются за фигурными скобками области действия класса. Эти функции являются видовыми функция­ ми — они должны работать с любым типом, если он определяется как фактиче­ ский тип. (Конечно, функция может ожидать некоторых свойств от объектов фактического типа, например, способности поддерживать операции присваива­ ния, которые зависят от реализованного алгоритма функции.)

void push(const Туре&);

/ / плохо, вне фигурных скобок класса

Из-за прототипа функции могут возникнуть проблемы. Во-первых, он должен указывать, что эта функция принадлежит к классу Stack. Во-вторых, следует об­ ратить внимание на то, что идентификатор Туре является параметром типа, определяемым позже, а не именем типа, заданного ранее. Если это не сделать, компилятор сообш^ит, что Туре не определен.

C + + интерпретирует функции-члены шаблонных классов как шаблонные функции. Определение (или объявление) шаблонной функции начинается с заре­ зервированного слова template, за которым в угловых скобках следует список параметров шаблона. Каждый компонент списка параметров шаблона включает ключевое слово class, за которым располагается идентификатор, определяюи;ий имя параметра типа.

template <class Туре>

/ /

список

параметров шаблона

void Stack::push(const Туре&);

/ /

лучше,

но недостаточно хорошо

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

Что надо сообш^ить компилятору? Каким станет тип Stack, неизвестно, по­ скольку на момент определения класса, он может быть любого типа. Фактический тип станет известен в момент реализации. Вы знаете лишь предположительно, каким он может быть. Он будет соответствовать типу, определенному в списке

параметров функции. Тем не менее это тип,

который должен быть определен

в угловых скобках после имени типа.

 

 

template <class Туре>

/ /

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

void Stack<Type>::push(const Туре&);

/ /

это достаточно хорошо

При использовании шаблонных классов помните об этой концепции. Подобный прототип функции строится в соответствии с теми же правилами, что и любой другой прототип функции в C+ + . Он определяет типы параметров функции (в данном случае Туре) и классы, к которым эти функции принадлежат (здесь Stack<Type>).

Список параметров шаблона должен повторяться для каждой функции-члена, определенной вне спецификации класса.

template <class Туре>

 

/ /

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

void

Stack<Type>::push (const Type& c)

/ /

префикс шаблона

{

i f (top

< size)

 

/ /

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

 

items[top++] = c;

 

 

 

else

 

 

/ / восстановление после переполнения стека

 

{ Type *р = new Type[size*2];

/ /

будет использован фактический тип

 

i f

(р -

0)

 

 

 

 

 

{ cout

«

"Out of memory\n";

e x i t ( l ) ; J

 

for

(int

i=0;

i < size; i++)

 

 

 

 

p [ i ]

= items[i];

/ /

копирование существующих данных

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

771

delete

[ ] items;

 

 

 

 

items

 

= р;

 

/ /

присоединение нового массива динамически

 

 

 

 

/ /

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

 

size

*= 2;

 

/ /

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

 

cout

«

"New size: "

«

size

« endl;

 

items[top++] = c; }

}

/ /

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

 

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

Обратите внимание, что зарезервированные слова template или class не ис­ пользуются в префиксе шаблона операции явного задания имени — только имена типов параметров. За счет префикса шаблона формальные параметры становятся доступными. Для суш,ествуюи;ей за ними функции не нужно указывать тип пара­ метров.

template <class Туре>

/ /

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

void Stack<Type>::push<Type> (const Туре& с);

/ /

избыточность

Функция с именем push<Type> здесь не имеет смысла, достаточно имени самой функции.

Подобным образом, при определении конструкторов и деструкторов шаблона Его аргументы объявляются только один раз в префиксе шаблона, а не в имени функции-члена, например, Stack<Type>: :Stack(), но не Stack<Type>: :Stack<Type>(). Второй Stack является именем функции-члена, а не спецификатора типа.

template <class Туре>

Stack<Type>::Stack(int sz = 100) : size(sz),top(O)

{items = new Type[sz];

if (items==0)

{ cout « "Out of memory\n"; e x i t ( l ) ; } }

Это справедливо и для деструктора. То, что указывается после тильды, является именем функции-члена, а не именем класса. Следовательно, оно не должно вклю­ чать параметры шаблона. Часть, которая предшествует двоеточию — оператору явного задания, является именем класса и должна включать параметры шаблона.

template <class Туре>

 

Stack<Type>::~Stack()

/ / специальный синтаксис деструктора

{ delete [ ] items; }

 

Обратите внимание, что деструктор не использует имя параметра типа в теле функции. Однако параметр типа все еш.е должен использоваться как в списке па­ раметров шаблона, так и в его префиксе. Это обш.ее правило. Зарезервированное слово template в списке параметров типов в определении функции должно вклю­ чать все параметры типов, упомянутые в списке параметров шаблона для класса. Такое утверждение справедливо для префикса шаблона, который определяет имя класса. Указывайте все параметры шаблона, даже если не все они используются в функции.

Например, функция-член isEmpty() проверяет значение целого индекса. Ее тело всегда одинаково и не зависит от того, какой фактический тип используется при реализации объекта. Тем не менее определение этой функции-члена требует пол­ ного списка параметров шаблона и его полного префикса.

template <class Т>

/ /

не используется в функции

bool

Stack<T>::isEmpty() const

/ /

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

{

return top == 0; }

/ /

одинаковое для любого типа

772Часть IV • Расширенное использование C++

Вэтом определении вместо Type для параметра типа используется идентифика­ тор Т. Имя типа является "заместителем". Можно использовать любое имя до тех пор, пока оно применяется везде, где должно использоваться. В данном примере важным является требование использовать одинаковое имя в списке параметров шаблона и в префиксе шаблона той же функции. Для другой функции может применяться имя другого параметра.

Влистинге 17.3 показана реализация стека как шаблонного класса. Использу­ ются три различных имени: Туре, Т и Тр. Для разных функций-членов эти имена совершенно независимы. Такие функции могут быть реализованы в различных

исходных файлах. С точки зрения программной инженерии — это не очень хоро­ шая идея. Классы C + + поош^ряют объединение связанных друг с другом веш,ей. Но синтаксис языка позволяет реализовать разные функции-члены одного класса

вразличных исходных файлах.

Вданной версии программы клиентская программа реализует стек объектов Point. Объекты класса Point содержат два поля целого типа для своих координат: пустой конструктор по умолчанию (для поддержки создания массива) и простой конструктор копирования (для поддержки возвраи;ения по значению из функциичлена, рорО). Для такого простого класса ни один из этих конструкторов не

Initial (jata: (1.2) (3.4) (5.6) (7.8) (9.0) New size 8

Inversed data (9.0) (7.8) (5.6) (3.4) (1.2)

Рис. 17.3. Вывод для программы из листинга 17.3

1

нужен. Для классов, обрабатываюидих динами­

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

ются оба поля.

 

 

 

Для совместимости с предыдуш^ими приме­

 

рами координаты Point отображаются на экра-

 

не операции

operator « .

Данная

операция

 

 

^

» и

»

 

перегружается

как глобальный друг

класса

 

Point. Вывод программы приведен на рис. 17.3.

Листинг 17.3. Повторное использование структуры класса для Stack, содержащего объекты Point

#include

<iostream>

 

 

 

using

namespace std;

 

 

 

class

Point {

 

 

 

 

int X, y;

(ostream& out, const Pointsp);

friend ostream& operator «

public:

 

 

 

// конструктор noумолчанию: пустой

 

Point 0 { }

 

 

 

Point (const Point &p)

 

// конструктор копирования: для return

 

{ X = p.x; у = p. у;

}

 

// установить координаты Point

 

void set (int a, int b)

 

} ;

{ X = a; у = b; }

 

 

 

 

 

 

 

 

ostream& operator « (ostream& out, const Point& p)

{ out «

"(" « p.x « "," «

p.у «

" ) " ;

return out ; }

 

 

 

template <class Type>

 

 

 

class Stack {

 

 

 

Type *i tems;

 

 

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

int top, size;

 

 

Stack (const Stack&);

 

 

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

operator = (const Stack&);

 

 

public:

(int);

 

 

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

Stack

 

 

void push (const Type&);

 

 

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

Type рор( ) ;

 

 

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

 

 

 

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

773 "I

 

bool isEmptyO const;

 

 

 

// стек пуст?

 

 

""Stack О ;

 

 

 

 

 

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

}

 

 

 

 

 

 

 

 

 

 

template <class Туре>

 

 

 

size(sz),top(0)

 

Stack<Type>: : Stack (int sz = 100)

 

{

items = new Type[sz];

 

 

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

 

if (items==0)

 

 

 

 

 

 

} }

 

 

{ cout «

"Out ofmemoryXn"; exit(l);

 

template <class Т>

 

 

 

 

 

 

 

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

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

 

 

{ if (top < size)

 

 

 

 

 

 

items[top++] = c;

 

 

// восстановление после переполнения стека

 

 

else

 

 

 

 

 

 

 

 

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

 

// получить дополнительную память в динамически

 

 

 

if (р ==0)

 

 

 

 

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

 

 

 

 

 

 

 

// проверка науспех

 

 

 

 

{ cout «

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

 

 

 

for (int i=0; i < size; i++) // копировать существующий стек

 

 

 

 

p[i] = items [i];

 

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

 

 

delete [] items;

 

 

 

 

 

items = p;

 

 

 

 

// присоединение новой памяти

 

 

 

size *= 2;

 

 

«

 

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

 

 

 

cout «

"New size:

size «

endl;

 

 

 

items [top++] = c; }

}

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

 

template <class Type>

 

 

 

 

 

 

 

Type Stack<Type>::pop()

 

 

 

// безусловное извлечение

 

{ return items[-top]; }

 

 

 

 

template <class Tp>

 

 

 

 

// что-либо извлечь

 

bool Stack<Tp>::isEmpty() const

 

{

return top ==0; }

 

 

 

 

 

 

 

template <class Type>

 

 

 

 

 

 

 

Stack<Type>: : ""Stack ( )

 

 

 

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

{ delete [] items; }

 

 

 

 

int mainO

 

 

 

 

 

 

 

 

 

 

Point data [5]

;

data[1] .set(3, 4); data[2].set (5, 6);

 

 

data[0].set(1, 2)

 

 

data[3].set(7, 8)

data[4].set(9,

0);

 

 

Stack<Point> s(4)

 

 

 

 

// объект Stack

 

 

int n = sizeof (data)/sizeof

(Point);

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

 

 

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.popO « " ";

 

 

 

 

 

 

cout «

endl;

 

 

 

 

 

 

 

 

return 0 ;

}

I 774

Часть IV # Росширенное использование С4*Ф

Имена функций-членов становятся доступными для использования в клиентской программе, как только объекту класса приписываются конкретные значения аргу­ ментов типа. Когда функция-член отправляется как сообщение объекту конкрет­ ного класса, имя функции в клиентской программе определяется без префиксов.

Stack<Point> s(4);

 

/ /

объекту присвоено

конкретное значение

for (int i = 0; i < n;

i++)

 

 

 

{ s.push(clata[i]);

}

/ /

спецификаторы типа

отсутствуют

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

template <class Т>

 

 

struct Node {

/ /

общедоступные данные

Т item;

 

 

Node *next;

/ /

поле next указывает на Node

Node(const Т&);

/ /

конструктор

} ;

 

 

Здесь имя параметра не используется в предположении, что компилятор поймет, что следующее поле соответствует определяемому классу. Более строгий подход предполагает, что класс Node не существует, ес/ш не определен тип компонентаузла.

template <class Т>

 

 

struct Node {

/ /

общедоступные данные

Т item;

 

 

Node<T> *next;

/ /

поле next указывает на Node<T> *next

Node(const Т&);

/ /

конструктор

} ;

 

 

С точки зрения программной инженерии второй вариант класса Node лучше первого. Однако компилятор должен скомпилировать каждую версию без затруд­ нений.

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

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

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

template <class Туре>

 

class Stack {

 

 

Type *items;

/ /

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

int top, size;

/ /

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

Stack(const Stack&);

 

operator = (const

Stack&);

 

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

775

public:

 

 

 

 

 

 

Stack(int sz = 100) : size(sz),top(O)

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

{

items = new Type[sz];

// выделение памяти в динамически

 

 

if (items==0)

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

 

 

 

 

 

 

 

 

{ cout «

"Out ofmemory\n"; exit(l); } }

 

 

void push(const Type& c)

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

 

{

if (top < size)

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

 

items[top++] = с;

// recover from stack overflow

 

 

else

 

памяти

 

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

// получение дополнительной

 

if (р ==0)

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

 

// проверка науспешность выполнения

 

 

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

 

стека

 

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

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

 

p[i] = items[i];

// возвращение

памяти динамически

 

 

delete

[] items;

 

 

items = р;

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

области

 

 

// присоединение

новой памяти

 

 

size *=2;

// корректировка

размера стека

 

 

cout «

"New size: " «

size «

endl;

 

 

 

 

items[top++] = c; } }

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

 

Type рорО

 

// извлечение

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

 

{ return items[-top]; }

// безусловное

выполнение

 

bool isEmptyO

const

// стек пуст?

 

 

 

{

return top ==0: }

 

 

 

 

 

"StackO

 

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

 

 

 

 

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

области

 

{delete [] items; }

};

Определение этого класса выглядит почти как обычное определение класса, где тип компонента объявляется оператором typedef (см. листинг 17.2).

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

Шаблонный класс может использовать другие шаблоны в качестве его эле­ ментов данных. Например, шаблон-стек Stack<T> с компонентами класса Т может включать элемент данных шаблона типа List<T>. Компоненты списка должны быть того же самого типа, что и компоненты стека. Функции-члены шаблонастека могут посылать сообш.ения объекту шаблона-списка для реализаций опера­ ций со стеком.

Пусть шаблон-список обеспечивает такие операции, как insert_as_first() и remove_f irst(), добавляюш^ие компонент как первый элемент в список и удаляюш,ие первый компонент списка.

template

<class Т>

 

 

class List {

 

 

public:

 

 

 

void

insert_as_first(const

T& x);

 

T remove_first();

 

 

bool

emptyO;

/ /

список пуст?

. . .}

;

/ /

остальная часть класса List

Часть IV » Расширенное использование С^-^-

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

template <class Т>

/ /

тот же тип Т для Stack и List

class Stack {

 

 

 

List<T> 1st;

 

/ /

элемент данных шаблона

public:

 

 

 

void push(const

Т&);

/ /

константа, ссылка на Т

Т рорО;

 

/ /

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

bool isEmptyO;

} ;

/ / н е требуется для деструктора

Реализация функции-члена стека становится очень простой. Методы push() вызыва­ ют соответствующую функцию-список и полагаются на управление памятью в них.

template <class Т>

 

void Stack<T>::push(const Т& item)

 

{ lst . insert _ as _ first(item); }

/ / передать работу в List

Функции-члены стека рор() и isEmpty() передают работу функциям-членам списка.

template

<class Т>

 

 

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

/ /

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

{ return

1st. remove_first(); }

/ /

передать работу в список

template

<class Т>

 

 

bool Stack<T>::isEmpty()

 

 

{ return

Ist.emptyO; }

/ /

вызов подобной функции

Когда клиентская программа реализует объект Stack<int>, функции-члены будут следующими:

void Stack<int>::push(const

int& item)

{ 1st. insert_as_first(item);

}

int Stack<int>::pop()

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

{ return 1st.remove_first();

}

Когда клиентская программа объявляет экземпляр объекта стека, например Stack<int>, процесс реализации повторяется, и компилятор формирует объектный код для List<int>. В свою очередь, объект-шаблон класса List<int> может потре­ бовать другие шаблоны.

Если объект класса List<int> не может быть реализован, реализация стека приводит к ошибке реализации. Это может быть либо из-за ошибки в шаблонесписке, либо недопустимой операции над параметром шаблона (например, для класса не определяется сравнение, или операция класса применяется к встроенно­ му параметру). Отладка шаблонных классов становится труднее отладки обычных классов.

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

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

Это могут быть параметры типов, подобные рассмотренным в предыдуш^их примерах, или параметры, которые имеют сходство с обычными параметрамизначениями в функциях. За счет использования параметров и совокупности пара­ метров типов и параметров выражений повышается гибкость шаблонов С + 4-. В то же время появляются дополнительные синтаксические сложности.

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

777

Несколько параметров типов

Рассмотрим шаблонный класс с более чем одним параметром типа. Имена таких параметров должны использоваться в рамках класса для его элементов данных, локальных переменных или параметров методов. При реализации объекта клиентская программа предоставляет имена фактических типов. В СН-+ использу­ ется принцип передачи параметров. Первый параметр, определенный клиентской программой, соответствует первому параметру, заданному в шаблонном классе, и т.д.

Определяя список параметров типов в шаблонном классе, следует повторить зарезервированное слово class для каждого параметра типа. Вот пример шаблон­ ного класса для словарных статей. У класса имеются два компонента: ключевой компонент и компонент информационного наполнения. Они могут быть компо­ нентами произвольных классов в той степени, в которой они поддерживают присваивание и конструкторы копирования.

template <class Key, class Data>

 

 

 

class DictEntry

{

 

 

 

 

 

 

Key key;

 

 

 

 

 

/ /

поле ключа

 

 

Data info;

 

 

 

 

/ /

поле информации

 

public:

 

 

 

 

 

 

 

 

 

DictEntry

0

{

}

 

 

/ /

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

DictEntry(const Кеу& к,

const

Data& d)

 

 

: key(k), info(d)

{}

//инициализация компонентов данных

Key getKeyO

const

 

 

 

 

 

 

{ return key;

 

}

 

 

/ /

возвращение значения

ключа

Data getlnfoO

const

 

 

 

 

 

{ return info;

}

 

 

/ /

возвращение значения

информации

void setKey(const

Key& k)

 

 

 

 

{ key = k;

}

 

 

 

 

/ /

установить

значение

ключа

void setInfo(const

Data& d)

 

 

 

 

{ info = d;

}

 

 

 

 

/ /

установить

значение информации

} ;

 

 

 

 

 

 

 

 

 

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

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

Отсутствует необходимость обеспечения деструктора DictEntry, поскольку сам класс не управляет динамически своей памятью (элементами данных key и info). Если любой из компонентных классов (Key или Data) обрабатывает дополнитель­ ные ресурсы и включает деструктор, он будет вызываться автоматически в про­ цессе разрушения объекта словарной статьи. Это аналогично последней версии класса Stack, реализованного с элементом данных List. В классе List суш^ествует деструктор, но для этой версии класса Stack он не требуется.

Для реализации объекта класса DictEntry клиентская программа должна опре­ делить два фактических типа: один для поля ключа и второй для поля данных.

778

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

 

Можно использовать встроенные и определенные программистом типы в любом

 

сочетании, пока они поддерживают присваивание и копирование.

 

DictEntry<Point,char*> entry;

/ / семантики ссылок для строк

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

Все неприятные истории, рассказанные в главе 11, связаны с тем фактом, что деструктор принудительно освобождает память динамически распределяемой области памяти. Поскольку указатель на символ как класс здесь не реализован

иотсутствует деструктор, угрозы по поводу целостности программы, описанные

вглаве 11, к данному случаю не относятся.

Associated Data:

(1,2) Initial stage

(3,4) Analysis

(5,6) Design

(7,8) Implementation

(9,0) Testing

Рис. 17.4.

Вывод для программы из листинга 17.4

В листинге 17.4 показано использование класса словарной статьи д/ш приложения, которому требуется дать комментарии для некоторых точек на экране и найти комментарии для каждой указанной точки. Ключевым полем в словарной статье является класс Point, как и в листинге 17.3. (Здесь добавлен общий конструктор для упрощения инициализации данных и операция сравнения для облегчения поиска.) Информационное поле инициализируется указателями на строковые литералы.

После инициализации массива словарных статей main() проверяет драйвер, выполняя вывод на печать каждой записи массива. Результаты показаны на ряс. 17.4.

Листинг 17.4. Пример шаблонного класса с двумя параметрами типа

#inclucle <iostream> using namespace std;

class Point { int X, y;

friend ostream& operator « (ostream& out, const Point& p);

public:

// конструктор noумолчанию: пустой

Point0 { }

Point(const Point &p)

// конструктор копирования: для возвращения

{ X = р.х; у = р.у; }

// общий конструктор: установить Point

Point (int а, int b)

{ X = а; у = b; }

// задать координаты Point

void set(int а, int b)

{ X = а; у = b; }

 

bool operator == (const Point& p) const

{ return X == p.x && у == p. y; }

ostream& operator «

(ostream& out, const Point& p)

{ out « "(" p.x «

"," « p.у « " )";

return out ; }

 

template <class Key,

class Data>

class DictEntry {

 

Key key;

 

Data info;

 

public:

// пустой конструктор по умолчанию

DictEntry 0 { }

 

 

 

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

779

DictEntry(const Кеу& к, const Data& d)

// инициализация

полей данных

 

 

 

: кеу(к), info(cl) {}

 

 

Key getKeyO const

 

// возвращения значения key

 

{ return key; }

 

 

Data getlnfoO const

 

// возвращения значения info

 

{ return info; }

 

 

void setKey(const Key& k)

 

// задание значения key

 

{ key = k; }

 

 

void setInfo(const Data& d)

 

// задание значения info

 

{ info = d; }

 

 

} ;

 

 

 

 

 

 

 

int mainO

 

 

 

 

 

{

DictEntry<Point,char*> data[5];

 

 

 

data[0].setKey(Point(1,2)); data[0].setInfo("Initial stage");

 

data[l].setKey(Point(3,4)); data[l].setInfo("Analysis");

 

 

data[2].setKey(Point(5.6)); data[2]. setInfo("Design");

 

 

data[3].setKey(Point(7,8)); data[3].setInfo("Implementation");

 

data[4].setKey(Point(9,0)); data[4].setInfo("Testing");

// рискованно

 

int n = sizeof(data)/sizeof(DictEntry<Point,char*>);

 

cout «

"Associated Data:\n";

 

 

 

 

for (int j = 0; j < n; j++)

 

 

 

 

{

cout

«

data[j].getKey() «

" "

 

 

 

 

 

« data[j] . getInfo() «

endl; }

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

входных данных

 

cout «

endl;

 

 

 

 

return

0;

}

 

 

 

 

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

А как насчет нескольких параметров? Можно ли определить шаблонные клас­ сы, предусматривая, что они имеют различное количество параметров типа?

Ответ — нет. Имена шаблонов класса не могут перегружаться. Программа содержит только один шаблон класса с указанным именем.

Шаблоны с параметрами — константные выражения

Как уже упоминалось, параметры шаблона также могут быть выражениями, а не типами. Эти выражения могут быть любого встроенного или определенного программистом типа. Тип указывается явно в определении шаблона (а не как параметр типа). Клиентская программа подставляет значение данного типа во время реализации объекта.

Ниже приведен пример шаблона Stack, в котором исходный размер массива в динамически распределяемой области памяти указывается как параметр шаб­ лона, а не как параметр конструктора.

template <class Type, int sz>

/ /

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

class Stack {

 

 

Type *items;

/ /

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

int top, size;

/ /

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

Stack(const Stack&);

 

 

operator = (const Stack&);

 

 

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