Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lections_rus.doc
Скачиваний:
31
Добавлен:
06.02.2016
Размер:
1.41 Mб
Скачать

12.2. Шаблоны классов

Шаблоны классов также называют "генераторами классов" или "обобщенными классами". Они позволяют определять структуру семейс­тва классов, по которой компилятор создаст классы в дальнейшем, ос­новываясь на типах используемых данных.

Итак, мы с Вами выяснили, что в С++ существует возможность определить обобщенный класс. Это значит, что Вы можете создать класс, который определяет все используемые в нем алгоритмы, но реальный тип обрабатываемых данных будет задан как параметр при создании объектов этого класса. Другими словами, когда Вам необходимо разработать класс, который имеет одинаковую логику работы с разными типами данных (например, класс МАССИВ котрый одинаково работал бы как c данными типа int так и с double, Вашими собстенными типами данных), то лучше всего воспользоваться механизмом шаблонов.

Вот как выглядит общая форма объявления параметризованного (обобщенного) класса.

template <class Tтип_данных> class имя_класса {

//....описание класса.......

};

Рассомотрим это объявление. Здесь Tтип_данных представляет собой имя типа шаблона, которое в каждом случае конкретезации будет замещаться фактическим типом данных. При необходимости, можно определить более одного параметризовнного типа данных, используя список с разделителем-запятой, т.е. параметризованные классы могут иметь несколько аргументов шаблона. Заметим, что в пределах определения класса имя Tтип_данных можно использовать в любом месте. Когда создан параметризованный класс, Вы можете создать конкретную реализацию этого класса, используя следующий синтаксис:

имя_класса <тип_данных> объект;

В приведенном синтаксисе тип_данных представляет собою имя типа данных, над которыми фактически оперирует класс, и заменяет собой переменную Tтип_данных

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

Вот как может выглядеть шаблон класса Array:

template <class elemType>

class Array {

protected: static const int DefaultArraySize = 12; int _size; elemType *_ia;

void init( const elemType*, int ); void swap( int, int );

public:

explicit Array( int sz = DefaultArraySize );

Array(const elemType *ar, int sz );

Array(const Array &iA );

virtual ~Array() { delete[] _ia; }

Array& operator=(const Array &); int size() const { return _size; }

virtual elemType& operator[]( int ix ) { return _ia[ix]; }

virtual void sort( int,int ); virtual int find( const elemType& ); virtual elemType min(); virtual elemType max(); };

Ключевое слово template говорит о том, что задается шаблон, параметры которого заключаются в угловые скобки (<>). В нашем случае имеется лишь один параметр elemType; ключевое слово class перед его именем сообщает, что этот параметр представляет собой тип. При конкретизации класса-шаблона Array параметр elemType заменяется на реальный тип при каждом использовании, как показано в примере:

#include <iostream>

#include "Array.h"

int main() { const int array_size = 4;

// elemType заменяется на int

Array<int> ia(array_size);

// elemType заменяется на double Array<double> da(array_size);

// elemType заменяется на char Array<char> ca(array_size);

int ix;

for ( ix = 0; ix < array_size; ++ix ) { ia[ix] = ix; da[ix] = ix * 1.75; ca[ix] = ix + 'a'; }

for ( ix = 0; ix < array_size; ++ix ) cout << "[ " << ix << " ] ia: " <<

ia[ix] << "\tca: " << ca[ix] << "\tda: " << da[ix] << endl;

return 0; }

Здесь определены три экземпляра класса Array:

Array<int> ia(array_size);

Array<double> da(array_size);

Array<char> ca(array_size);

Что делает компилятор, встретив такое объявление? Подставляет текст шаблона Array, заменяя параметр elemType на тот тип, который указан в каждом конкретном случае. Следовательно, объявления членов приобретают в первом случае такой вид:

// Array<int> ia(array_size);

int _size;

int *_ia;

Заметим, что это в точности соответствует определению массива IntArray. Для оставшихся двух случаев мы получим следующий код:

// Array<double> da(array_size);

int _size;

double *_ia;

// Array<char> ca(array_size); int _size; char *_ia;

Что происходит с функциями-членами? В них тоже тип-параметр elemType заменяется на реальный тип, однако компилятор не конкретизирует те функции, которые не вызываются в каком-либо месте программы.

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

Согласно Страуструпу [1], использование шаблонов не подразумевает обязательного уменьшения сгенерированного кода. Хорошей идеей является тестирование конкретного класса до преобразования его в шаблон. Таким образом решаются проблемы проектирования и обнаруживается большая часть ошибок кода.

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

Наиболее показательный пример в данном случае - это создание семейства "вмещающих" классов, например, векторов.

#include <iostream.h>

template <class T>

class Vector

{

T *elements; //Указатель на массив элементов пока еще не известного типа

int size; //Максимальное число элементов

public:

Vector(int);

~Vector()

delete elements;

T& operator[](int i)

return elements[i];

void print_contents();

;

// Определение функций членов !!!!

template <class T>

Vector<T>::Vector(int)

elements=new T[n];

for(int i=0; i<n; elements[i]=(T)0,i++);

size=n;

;

template <class T>

void Vector<T>::print_contents()

cout<<"size="size<<"elements are:";

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

cout<<' '<<elements[i];

cout<<'\n';

;

main()

//Создание векторов с элементами типа int, double и char,

//вмещающих по 10 элементов

Vector<int>i(10);

Vector<double>x(10);

Vector<char>ch(10);

//Присвоить элементам значения

for(int count=0; count<10; count++)

i[count]=count;

x[count]=0.1+count;

ch[count]='a'+count;

//Распечатать содержимое векторов

i.print_contents();

x.print_contents();

ch.print_contents();

И в этом случае применение template избавляет от лишней работы и делает программу изящной и компактной.

Некоторые замечания:

1) Шаблон может иметь более одного аргумента, и аргументом не обязательно должен быть класс:

template<class T, int size>

второй аргумент шаблона, size - это целое число, используемое непосредственно внутри класса.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]