Г. Шилтд - Самоучитель C++ (PDF) (1114887), страница 48
Текст из файла (страница 48)
В следующей программе создается родовая функция, которая меняет местамизначения двух переменных, передаваемых ей в качестве параметров. Поскольку в своей основе процесс обмена двух значений не зависит от типа переменных, этот процесс удачно реализуется с помощью родовой функции.// Пример родовой функции или шаблона^include <iostream>using namespace std;// Это функция-шаблонtemplate <class X> void swapargs(X &a, X &b){X temp;temp = a;a = b;b = temp;Iint main(){int i = 10, j = 20;float x = 10.1, у = 23.3;cout « "Исходные значения i, j равны: " « i « ' ' « j « endl;cout « "Исходные значения x, у равны: " « x « ' ' « у « endl;swapargs (i, j ) ;swapargs (x, y) ;// обмен целых// обмен действительныхcout « "Новые значения i, j равны: " « i « ' ' « j « endl;cout « "Новые значения x, у равны: " « x « ' ' « у « endl;1return 0;'Ключевое слово template используется для определения родовой функции.Строка:template <class X> void swapargs(X &а, X & b )сообщает компилятору две вещи: во-первых, создается шаблон, и, во-вторых,начинается определение родовой функции.
Здесь X — это родовой тип данных, используемый в качестве фиктивного имени. После строки с ключевым328_;__СамоучительC++словом template функция swapargs() объявляется с именем X в качестве типаданных обмениваемых значений. В функции mainO функция swapargs() вызывается с двумя разными типами данных: целыми и действительными. Поскольку функция swapargs() — это родовая функция, компилятор автоматически создает две ее версии: одну — для обмена целых значений, другую дляобмена действительных значений.
Теперь попытайтесь скомпилировать программу.Имеются и другие термины, которые можно встретить при описании шаблонов в литературе по C++. Во-первых, родовая функция (то есть функция, вопределении которой имеется ключевое слово template) также называетсяфункция-шаблон (template function). Когда компилятор создает конкретнуюверсию этой функции, говорят, что он создал порожденную функцию(generated function). Процесс генерации порожденной функции называют созданием экземпляра (instantiating) функции. Другими словами, порожденнаяфункция — это конкретный экземпляр функции -шаблона.2.
Ключевое слово template в определении родовой функции не обязательнодолжно быть в той же строке, что и имя функции. Например, ниже приведенеще один вполне обычный формат определения функции swapargs():template <class X>void swapargs (X &a, X &b){X temp;temp = a;a = b;b = temp;При использовании такого формата важно понимать, что никаких других инструкций между инструкцией template и началом определения родовой функции быть не может. Например, следующий фрагмент программы компилироваться не будет:// Этот фрагмент компилироваться не будетtemplate <class X>int i;// это неправильноvoid swapargs (X &а, X &Ь){X temp;temp = а;а = b;b = temp;Как указано в комментариях, инструкция с ключевым словом template должна находиться сразу перед определением функции.Глава11.Шаблоныиобработкаисключительныхситуаций_3293.
Как уже упоминалось, для задания родового типа данных в определениишаблона вместо ключевого слова class можно указывать ключевое словоtypename. Например, ниже приведено еще одно объявление функцииswapargsQ:// Использование ключевого слова typenametemplate <typename X> void swapargs (X &a, X Sb)IX temp;temp = a;a = b;b = temp;Ключевое слово typename можно также указывать для задания неизвестноготипа данных внутри шаблона, но такое его использование выходит за рамкиданной книги,4. С помощью инструкции template можно определить более одного родовоготипа данных, отделяя их друг от друга запятыми.
Например, в данной программе создается родовая функция, в которой имеются два родовых типаданных:#include <iostream>using namespace std;template <class typel, class type2>void myfunc (typel x, type2 y)cout « x « ' ' « у « endl;int m a i n ( ){myfunc (10, "hi") ;myfunc(0.23, 10L) ;return 0;}В данном примере при генерации конкретных экземпляров функцииmyfunc(), фиктивные имена типов typel и type2 заменяются компилятором натипы данных int и char* или double и long соответственно.Когда вы создаете родовую функцию, то по существу предписываете компилятору генерировать столько разных версий этой функции, сколько нужнодля обработки всех способов вызова этой функции в вашей программе.Самоучитель C++3305.
Родовые функции похожи на перегруженные функции за исключением того,что они более ограничены по своим возможностям. При перегрузке функциивнутри ее тела можно выполнять совершенно разные действия. С другой стороны, родовая функция должка выполнять одни и те же базовые действиядля всех своих версий. Например, следующие перегруженные функции нельзязаменить на родовую функцию, поскольку они делают не одно и то же.void outdataCint i)jcout « i;}void outdata(double d){cout « setprecision(lO) « setfill ('#');cout « d;cout « setprecision(6) « setfill (' ');}6. Несмотря на то, что функция-шаблон при необходимости перегружается сама, ее также можно перегрузить явно. Если вы сами перегружаете родовуюфункцию, то перегруженная функция подменяет (или "скрывает") родовуюфункцию, которую бы создал компилятор для этой конкретной версии.
Рассмотрим такой вариант примера 1:// Подмена функции-шаблонаttinclude <iostream>using namespace std;template <class X> void swapargs(X &a, X &b){X temp;temp = a;a = b;b = temp;}// Здесь переопределяется родовая версия функции swapargs()void swapargs(int a, int b){cout « "это печатается внутри функции swapargs(int, int)\n";}int main(){int i = 10, j = 20;float x = ЮЛ, у = 23.3;cout « "Исходные значения i, j равны: " « i « ' ' « j « endl;cout « "Исходные значения х, у равны: " « x « ' ' « у « endl;Глава 11. Шаблоны и обработка исключительных ситуацийswapargs(i, j ) ;swapargs(х, у) ;337// вызов явно перегруженной функции s w a p a r g s ( )// обмен действительными числамиcout « "Новые значения i, j равны: " « i « ' ' « j « endl;cout « "Новые значения х, у равны: " « х « ' ' « у « endl;return 0;Как отмечено в комментариях, при вызове функции swapargs(i, j) вызываетсяопределенная в программе явно перегруженная версия функции swapargs()Таким образом, компилятор не генерирует этой версии родовой функцииswapargs(), поскольку родовая функция подменяется явно перегруженнойфункцией.Ручная перегрузка шаблона, как показано в данном примере, позволяет изменить версию родовой функции так, чтобы приспособить ее к конкретнойситуации.
Однако в большинстве случаев, если вам нужно несколько разныхверсий функции для разных типов данных, вместо шаблонов лучше использовать перегруженные функции.УпраЛненения]1. Если этого еще не сделано, попытайтесь откомпилировать каждый из предыдущих примеров.2. Напишите родовую функцию min(), возвращающую меньший из двух своихаргументов. Например, версия функции min(3, 4) должна возвратить 3, а версия min('c', 'a') — а. Продемонстрируйте работу функции с помощью программы.3. Прекрасным кандидатом на функцию -шаблон является функция fmd(). Этафункция ищет объект в массиве. Она возвращает либо индекс найденногообъекта (если его удалось найти), либо — 1, если заданный объект не найден.Ниже представлен прототип конкретной версии функции flndQ.
Переделайтефункцию find() в родовую функцию и проверьте ваше решение в программе.(Параметр size задает количество элементов массива.)int find(int object, int *list, int size)4. Объясните своими словами, зачем нужны родовые функции и как они могутупростить исходный код ваших программ.332__Самоучитель C++11.2. Родовые классыВ дополнение к родовым функциям можно определить и родовые классы.При этом создается класс, в котором определены все необходимые алгоритмы, а фактические типы обрабатываемых данных задаются в качестве параметров позже, при создании объектов этого класса.Родовые классы полезны, когда класс содержит общую логику работы.
Например, алгоритм, который реализует очередь целых, будет также работать ис очередью символов. Кроме того, механизм, который реализует связанныйсписок почтовых адресов, будет также поддерживать связанный список запасных частей к автомобилям. С помощью родового класса можно создатькласс, реализующий очередь, связанный список и т. д. для любых типовданных. Компилятор будет автоматически генерировать правильный типобъекта на основе типа, заданного при создании объекта.Ниже представлена основная форма объявления родового класса:template <class ФУИП> class имя класса {Здесь Фтип — это фиктивное имя типа, который будет задан при созданииэкземпляра класса.
При необходимости можно определить более одного родового типа данных, разделяя их запятыми.После создания родового класса с помощью представленной ниже формыможно создать конкретный экземпляр этого класса:хмя_класса <тяп> объект;Здесь тип — это имя типа данных, с которым будет оперировать класс.Функции-члены родового класса сами автоматически становятся родовыми.Для них не обязательно явно задавать ключевое слово template.Как вы увидите в главе 14, в C++ имеется встроенная библиотека классовшаблонов, которая называется библиотекой стандартных шаблонов (StandardTemplate Library, STL).