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

OOP / Лекция 8

.pdf
Скачиваний:
23
Добавлен:
20.04.2015
Размер:
98.6 Кб
Скачать

Лекция 8

1. Шаблоны функций и шаблонные функции

Одним из самых полезных видов классов является контейнерный класс, т.е. такой класс, который хранит объекты каких-то других типов. Списки, массивы, ассоциативные массивы и множества - все это контейнерные классы.

Рассмотрим простую функцию, реализующую алгоритм сравнения двух величин: int min (int a, int b)

{

return a < b ? a : b;

}

Для каждого типа сравниваемых величин должен быть определён собственный вариант функции min(). Вот как эта функция выглядит для float:

float min (float a, float b)

{

return a < b ? a : b;

}

А для double… А для…

И так для всех используемых в программе типов сравниваемых величин. Мы можем бесконечно упражняться в создании совместно используемых функций, хотя можно воспользоваться средствами препроцессирования:

#define min(a,b) ((a)<(b)?(a):(b))

Это определение правильно работает в простых случаях: min(10, 20);

min(10.0, 20.0);

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

C++ предоставляет ещё одно средство для решения этой задачи. При этом сохраняется присущая макроопределениям краткость и строгость контроля типов языка. Этим средством является шаблон функции.

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

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

Каждый параметр шаблона состоит из служебного слова class, за которым следует идентификатор. В контексте объявления шаблона функции служебное слово class не несёт никакой особой смысловой нагрузки. Дело в том, что аналогичная конструкция используется также и для объявления шаблона класса, где ключевое слово class играет свою особую роль. В заголовке шаблона имена параметров шаблона должны быть уникальны.

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

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

Шаблон функции служит инструкцией для транслятора. По этой инструкции транслятор может самостоятельно построить определение новой функции.

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

В качестве примера рассмотрим программу, в которой для определения минимального значения используется шаблон функции min().

template <class Type> Type min (Type a, Type b);

Ключевое слово template обозначает начало списка параметров шаблона. Этот список содержит единственный идентификатор Type. Сама функция содержит два объявления шаблонных параметра, специфицированных шаблоном параметра Type. Спецификация возвращаемого значения также представлена шаблоном параметра Type.

int main (void)

{

min(10,20);// int min (int, int); min(10.0,20.0);// float min (float, float); return 1;

}

template <class Type> Type min (Type a, Type b)

{

return a < b ? a : b;

}

Вызовы шаблонной функции. Тип значений параметров определён. На основе выражения вызова (транслятор должен распознать тип параметров) и определения шаблона транслятор самостоятельно строит различные определения шаблонных функций. И только после этого обеспечивает передачу управления новорождённой

шаблонной

функции. По аналогии с определением функции, эту конструкцию будем

называть

определением шаблона функции.

 

 

Определение

шаблона

функции

заставляет

транслятор

самостоятельно

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

В результате конкретизации шаблона функции min() транслятор строится следующий вариант программы с двумя шаблонными функциями. По выражению вызова на основе шаблона строится шаблонная функция. Шаблон функции и шаблонная функция - два разных понятия.

int min (int a, int b);

float min (float a, float b); int main (void)

{

min(10,20);

min(10.0,20.0); return 1;

}

int min (int a, int b)

{

return a < b ? a : b;

}

float min (float a, float b)

{

return a < b ? a : b;

}

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

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

template <class Type>

Type min (Type *a, Type *b)

{

return a < b ? a : b;

}

и при этом вызов функции имеет вид: int a = 10, b = 20;

int *pa = &a, *pb = &b; min(pa,pb);

то в процессе конкретизации идентификатор типа Type будет замещён именем производного типа int:

int min (int *a, int *b)

{

return a < b ? a : b;

}

В процессе конкретизации недопустимы расширения типов и другие преобразования типов параметров:

template <class Type> Type min (Type a, Type b)

{

return a < b ? a : b;

}

unsigned int a = 10;

:::::

min(1024,a);

Здесь транслятор сообщит об ошибке. В вызове функции тип второго фактического параметра модифицирован по сравнению с типом первого параметра - int и unsigned int. Это недопустимо. В процессе построения новой функции транслятор не распознаёт модификации типов. В вызове функции типы параметров должны совпадать. Исправить ошибку можно с помощью явного приведения первого параметра.

min((unsigned int)1024,a);

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

возвращаемого

значения,

определяется

тип

объектов,

локализованных в теле

функции. Имя параметра шаблона скрывает

объекты с аналогичным именем в

глобальной по

отношению

к определению

шаблонной функции

области видимости. Если

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

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

#include <iostream.h> #include <typeinfo.h>

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

template <class YYY, class ZZZ> YYY Tf (ZZZ, YYY, int);

Шаблон прототипа функции. Функция Tf возвращает значение пока ещё неопределённого типа, обозначенного параметром шаблона YYY. Список её параметров представлен двумя параметрами шаблона и одним параметром типа int.

void main()

{

cout << Tf((int) 0,

'1', 10) << endl;

Эти вызовы и управляют работой транслятора. Тип передаваемых значений параметров предопределяет структуру шаблонной функции. В первом случае шаблону параметра ZZZ присваивается значение "int", шаблону параметра YYY присваивается значение "char", после чего прототип шаблонной функции принимает вид

char Tf (int, char, int);

cout << Tf((float) 0, "This is the string...", 10) << endl;

Во втором случае шаблону параметра ZZZ присваивается значение "float", шаблону параметра YYY присваивается значение "char *", после чего прототип шаблонной функции принимает вид

char* Tf (float, char *, int);

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

}

Шаблон функции. Первый параметр не используется, поэтому в списке параметров он представлен спецификатором объявления. Второй шаблонный параметр определён и также зависит от шаблона, третий параметр от шаблона не зависит.

template <class YYY, class ZZZ> YYY Tf (ZZZ, YYY yyyVal, int x)

{

ZZZ zzzVal; int i;

for (i = 0; i < x; i++)

{

cout << "Tf() for " << typeid(zzzVal).name() << endl;

}

return yyyVal;

}

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

Шаблон типа для класса задает способ построения отдельных классов, подобно тому, как описание класса задает способ построения его отдельных объектов. Можно определить стек, содержащий элементы произвольного типа:

template<class T> class stack

{

T* v; T* p; int sz; public:

stack(int s)

{

v = p = new T[sz=s];

}

~stack()

{

delete[] v;

}

void push(T a)

{

*p++ = a;

}

T pop()

{

return *--p;

}

int size() const

{

return p-v;

}

};

Префикс template<class T> указывает, что описывается шаблон типа с

параметром

T, обозначающим тип,

и

что это обозначение будет использоваться в

последующем описании. После того, как идентификатор T указан в префиксе, его

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

видимости T

продолжается

до конца

описания, начавшегося

префиксом template<class T>. Отметим, что в

префиксе T

объявляется типом,

и

оно не обязано

быть именем

класса. Имя

шаблонного класса, за которым следует тип, заключенный в угловые скобки <>, является именем класса (определяемым шаблоном типа), и его можно использовать как все имена класса. Например, ниже определяется объект sc класса stack<char>:

stack<char> sc(100);

// стек символов

Можно подумать, что

шаблон типа - это хитрое макроопределение,

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

stack<shape*> ssp(200); // стек указателей на фигуры stack<Point> sp(400); // стек структур Point

void f(stack<complex>& sc) // параметр типа `ссылка на

//complex'

{

sc.push(complex(1,2)); complex z = 2.5*sc.pop();

stack<int>*p = 0; // указатель на стек целых

p = new stack<int>(800); // стек целых размещается / / в свободной памяти

for ( int i = 0; i<400; i++) { p->push(i); sp.push(Point(i,i+400));

}

//...

}

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

template<class T>

void stack<T>::push(T a)

{

*p++ = a;

}

template<class T> stack<T>::stack(int s)

{

v = p = new T[sz=s];

}

Соседние файлы в папке OOP