Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по С++ глава 6.doc
Скачиваний:
4
Добавлен:
05.11.2018
Размер:
93.7 Кб
Скачать

6. Использование шаблонов языка C++

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

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

6.1. Определение шаблонов функций

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

Вспомним параграф «Перегруженные функции» гл. 2, в котором указывалось, что для использования одной и той же функции с различными типами данных нужно определить отдельную перегруженную версию этой функции для каждого типа. Если требуется функция, возвращающая абсолютную величину значения как типа int, так и типа double, то нужно написать две перегруженные функции, приведенные в гл. 2 в качестве примера.

int Abs(int N)

{

return N < 0 ? -N : N;

}

double Abs (double N)

{

return N < 0.0 ? -N : N;

}

Используя шаблон языка C++, можно создать единственное определение, автоматически обрабатывающее значения типа int, double или любого другого подходящего типа. Такой шаблон выглядит следующим образом.

template <class T> T Abs (T N)

{

return N < 0 ? -N : N;

}

В этом определении идентификатор т является параметром типа (type parameter). Он переопределяет тип переменной или константы, передаваемой при вызове функции. Если программа вызывает функцию Abs и передает ей значение типа int, например,

cout << "absolute value of -5 is " << Abs (-5);

то компилятор автоматически сгенерирует версию функции, в которой идентификатор T имеет тип int, и добавит в программу вызов данной версии функции. Генерируемая функция будет эквивалентна функции, определенной явно.

int Abs (int N)

{

return N < 0 ? -N : N;

}

Аналогично, если программа вызывает функцию Abs и передает ей значение типа double, например,

double D = -2.54;

cout << "absolute value of D is " << Abs (D);

то компилятор сгенерирует версию функции, в которой тип идентификатора T будет заменен double, и добавит в программу вызов данной функции. Эта версия функции эквивалентна следующей.

double Abs (double N)

{

return N < 0 ? -N : N;

}

Таким же образом компилятор генерирует дополнительные версии функции для каждого вызова, в котором указывается новый числовой тип данных, например short или float. Генерация новой версии функции называется созданием экземпляра (instantiating) шаблона функции.

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

Примечание

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

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

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

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

template <class Т> Т Мах (Т А, Т В)

{

return А > В ? А : В;

}

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

cout << "The greater of 10 and 5 is " << Max (10, 5) << '\n';

cout << "The greater of 'A' and 'M' is " << Max ('A', 'M') << '\n';

cout << "The greater of 2.5 and 2.6 is " << Max (2.5, 2.6) << '\n';

А следующий вызов является недопустимым.

cout << "The greater of 15.5 and 10 is " << Max (15.5, 10) << '\n';

// ОШИБКА!

Обратите внимание: компилятор не преобразует второй параметр int в double для приведения типов, хотя это преобразование является стандартным.

Чтобы передавать параметры различных типов, нужно определить шаблон функции.

template <class Type1, class Type2 > Type1 Max (Type1 A, Type2 B)

{

return Type1 (A > В ? A : B);

}

В данном шаблоне Type1 обозначает тип значения, передаваемого в качестве первого, а Туре2 – второго параметров. Для новой версии шаблона следующий оператор является допустимым и печатает значение 15.5.

cout << "The greater of 15.5 and 10 is " << Max (15.5, 10) << '\n';

// теперь допустимо

Заметьте: в новом определении Мах параметр Type1 появляется внутри тела функции, где он используется для приведения возвращаемого значения (если это необходимо) к типу первого параметра функции.

return Type1 (А > В ? А : В);

Вообще в языке C++ параметр типа можно использовать в любом месте кода, в котором используется имя типа.

Так как возвращаемое значение преобразуется к типу первого параметра, то при изменении порядка параметров предыдущего примера

cout << "The greater of 15.5 and 10 is " << Max (10, 15.5) << '\n';

полученный результат сравнения (15.5) будет округлен и составит 15.

Отметим: каждый параметр типа, встречающийся внутри символов «<» и «>», должен также появляться в списке параметров функции. Следовательно, следующее определение шаблона не допустимо:

// ОШИБКА: список параметров функции должен

// включать Туре2 как параметр типа:

template <class Type1, class Type2> Type1 Max (Type1 A, Type1 B)

{

return A > В ? A : B;

}

При таком определении компилятор, встретив вызов функции, не сможет определить значение идентификатора Туре2. Это – ошибка, даже если идентификатор Туре2 не использован.