Читайте также:
|
|
Мы можем определить шаблон для ф-ии max() след. образом:
template<class T> T max(T x[], int len) //заголовок шаблона{ T max = x[0]; for(int i=1; i<len; i++) if(max<x[i]) max = x[i]; return max;}Ключевое слово template идентифицирует этот код как определение шаблона. Угловые скобки, следующие за этим ключевым словом, содержат параметры-типы, которые будут использованы при создании конкретного экземпляра функции. Ключевое слово class перед T указывает, что T есть имя параметра (идентификатор::=имя неспецифицированного типа), причем слово class используется как обобщенный термин для указания типа (по новому стандарту слово класс может быть заменено на typename). Позже мы увидим, что при определении классов мы, по сути, определяем свой собственный тип.
Где бы ни появилось имя T в самом определении шаблона, оно будет заменено специфическим (конкретным) типом, таким как long, когда экземпляр нашего шаблона создается. Если мы представим, что делаем это вручную, то увидим, что такой процесс приведет к созданию функции именно для вычисления максимума для массива типа long. На создание (или генерацию) экземпляра шаблона, т.е., конкретной ф-ии ссылаются как на инстанцирование шаблона. В нашем случае, имеем только один параметр, T, но их может быть несколько.
Каждый раз, когда мы используем ф-ию max() в нашей программе, компилятор будет отслеживать, соответствует ли функция с данным набором типов аргументов, использованных в вызове ф-ии, какой-либо из существующих ф-ий. Если требуемой функции нет, то компилятор создаст таковую, подставляя нужный тип аргумента вместо параметра T везде в исходном коде определения шаблона. Для иллюстрации, мы возьмем ту же ф-ию main(), что и в предыдущем примере:
// Ex5_07.cpp// Using function templates#include <iostream>using namespace std; // Template for function to compute the // maximum element of an arraytemplate<class T> T max(T x[], int len){ T max = x[0]; for(int i=1; i<len; i++) if(max<x[i]) max = x[i]; return max;} int main(){ int small[] = { 1,24,34,22}; long medium[] = { 23,245,123,1,234,2345}; double large[] = { 23.0,1.4,2.456,345.5,12.0, 21.0}; int lensmall = sizeof small/sizeof small[0]; int lenmedium = sizeof medium/sizeof medium[0]; int lenlarge = sizeof large/sizeof large[0]; cout << endl << max(small, lensmall); cout << endl << max(medium, lenmedium); cout << endl << max(large, lenlarge); cout << endl; return 0;}Если мы запустим эту программу, то получим тот же самый вывод, что и раньше. Для каждого оператора вывода максимального значения в массиве, новая версия ф-ии max() инстанцируется с использованием шаблона. Конечно, если мы добавим в код еще один вызов ф-ии max() с одним из уже использованных типов, то новая версия кода не генерируется.
Заметим, что применение шаблонов не укорачивает код программы. Вариант исходного кода создается компилятором для каждой требуемой ф-ии. В действительности, наличие шаблонов может привести к увеличению размера кода, так как функции могут создаваться автоматически даже при том, что для очередного вызова ф-ии нужная версия кода существует: компилятор учитывает необходимость соответствующего приведения типов. Мы можем вызвать принудительное создание конкретного варианта ф-ии на основе шаблона, явно указав сразу после тела шаблона прототип нужной ф-ии. Например, если мы хотим иметь гарантию, что экземпляр шаблона для ф-ии max() будет создан для типа float, мы могли бы поместить следующее объявление после определения шаблона:
float max(float, int);Это приведет к принудительному созданию версии функции. Для нашего примера это не очень значимо, но это может оказаться полезным, когда мы знаем, что будут созданы несколько версий функций, но хотим обеспечить принудительную генерацию кода с приведением аргументов к подходящему типу, где необходимо.
Итак, еще раз кратко, опишем синтаксис и семантику объявления, спецификации, и использование шаблонов функций.
В общем случае, объявление шаблона начинается с заголовка шаблона, за которым следует объявление или определение функции (объявление или определение класса, если пишется шаблон класса). Объявление шаблона может находиться только в границах некоторого пространства имен (или в области действия класса). Имя шаблона должно быть уникальным в области действия шаблона (за исключением перегруженных функций).
Заголовок шаблона начинается с ключевого слова template , за которым следуют параметры шаблона, заключенные в угловые скобки (<>). Если параметров несколько, то они разделяются запятой. Т. е.:
template < parameter-list > declaration
Например, определим шаблон ф-ии, вычисляющий абсолютные значения числовых величин разного типа:
template <class type>
type abs(type x) { return x > 0? x: -x; }
Слово template информирует компилятор, что он имеет дело с определением шаблона. Имеется три вида параметров шаблона: значения, типы, и шаблоны классов. Подобно параметру функции, параметр шаблона (по выбору) может иметь имя и аргумент по умолчанию (для классов).
Однако, шаблоны функций не могут иметь аргументов по умолчанию.
Пример шаблона
template <class TYPE>
TYPE max (TYPE a, TYPE b)
{ return a > b? a: b; }
Здесь TYPE – это один из типов, для которых определена операция > (“больше”). Слово class может ссылаться на встроенный тип или на тип определяемый пользователем, включая класс. Тип переменной, используемой при вызове этой функции, определяется типом аргумента (фактического параметра).
Еще раз: использование слова class здесь не означает, что TYPE обязан быть классом, оно означает только, что TYPE служит спецификатором обобщенного типа, вместо которого будет подставлен реальный тип данных, когда будет обращение к шаблону. Новейшие реализации С++ допускают, как мы уже говорили, использование в этом контексте ключевого слова typename вместо слова class :
template < typename Type>
Рассмотрим еще пример.
Предположим, что нам нужно написать функцию, возвращающую модуль числа. Обычно, наша функция имела бы дело с числами некоторого определенного типа, например, целого:
int abs(int n) // абсолютное значение для целых чисел { return (n<0)? -n: n; // если n<0, то return -n }Аналогично, если понадобится, мы поступаем и с числами другого типа, long или float:
long abs(long n) //для long { return (n<0)? -n: n; }и:
float abs(float n) //для float { return (n<0)? -n: n; }Ситуация подобна той, которая возникает при перегрузке функций. Однако, лень -двигатель прогресса и нам не хочется много раз писать одно и то же, да еще, возможно, с ошибками. Поэтому, идея шаблона приходится здесь весьма кстати. Вот как это выглядит:
// Пример 1.5.1
// Шаблон функции вычисления модуля числа
#include <iostream>
using namespace std;
//---------------------------------------------------------
template <class T> //Шаблон функции
T abs(T n)
{
return (n < 0)? -n: n;
}
//---------------------------------------------------------
int main()
{
int int1 = 5;
int int2 = -6;
long lon1 = 70000L;
long lon2 = -80000L;
double dub1 = 9.95;
double dub2 = -10.15;
//осуществления вызовов
cout << "\nabs(" << int1 << ")=" << abs(int1); //abs(int)
cout << "\nabs(" << int2 << ")=" << abs(int2); //abs(int)
cout << "\nabs(" << lon1 << ")=" << abs(lon1); //abs(long)
cout << "\nabs(" << lon2 << ")=" << abs(lon2); //abs(long)
cout << "\nabs(" << dub1 << ")=" << abs(dub1);
//abs(double)
cout << "\nabs(" << dub2 << ")=" << abs(dub2);
//abs(double)
cout << endl;
return 0;
}
Замечание. Выше мы говорили о параметризации функций. Для более сложного примера, демонстрирующего шаблоны ф-ий, хорошо бы уже иметь некоторое представление о классах. Но, поскольку этого мы “еще не проходили”, то используем структуру (в контексте будущего изучения шаблонов класса).
Структурный тип может быть параметризованным типом, тогда речь идет о шаблоне структуры (о шаблонах классов – после классов). Например, можно объявить структурный тип node, где параметр – неспецифицированный класс T:
template <class T> struct node
{
T info;
node * link;
};
Здесь мы говорим о шаблоне лишь в контексте структурных типов данных, поскольку последние послужили прототипом для введения в С++ концепции классов.
Таким образом, это - пример параметризованного структурного типа, который, как сейчас понятно, есть ни что иное, как шаблон для типа. (Иногда, в контексте ООП, для типов вводимых пользователем, расширяющих исходные типы языка, применяют термин “абстрактный тип данных” – АТД.) В нашем случае параметром является тип хранимой в связном списке информации, и реализация связного списка и соответствующие функции могли бы иметь такую форму:
Пример 1-5.2
template <class T> struct node
{
T info;
node * link;
};
template <class T> struct linked_list
{ node<T> * head; };
template <class T> void initialize(linked_list<T>& a_list) // передача по ссылке!
{ a_list.head = NULL; }
template <class T> void print_list(linked_list<T> a_list)
{
node<T> * temp = a_list.head;
while (temp)
{
cout << temp -> info;
temp = temp -> link;
}
return;
}
template <class T> void destroy(linked_list<T>& a_list)
{
node<T> * temp = a_list.head;
while(temp)
{
a_list.head = a_list.head -> link;
delete temp;
temp = a_list.head;
}
}
template <class T> void insert(linked_list<T>& a_list, Node<T> * pre, const T& t)
{
node<T> * temp = new node;
temp -> info = t;
if (! pre)
{
temp -> link = a_list.head;
a_list.head = temp;
}
else
{
temp -> link = pre -> link;
pre -> link = temp;
}
}
template <class T> T remove(linked_list<T>& a_list, Node<T> * pre)
{
node<T> * garbage = pre -> link;
T t = garbage -> info;
pre -> link = garbage -> link;
delete garbage;
return t;
}
Как мы уже говорили, параметров шаблона может быть несколько.
Пример 1-6.1. Шаблон функции с двумя параметрами.
#include <iostream.h>
template <class S, class T>
void print(S s, T t)
{
cout << s << endl;
cout << t << endl;
}
int main()
{
int n = 1;
char * str = "template";
print(n, str);
return 0;
}
à Ну и, как всегда, для справки и закрепления, о шаблонах функций из Т.Павловской …
Дата добавления: 2015-11-16; просмотров: 43 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Шаблоны Функций | | | Шаблоны функций |