Г. Шилтд - Самоучитель C++ (DJVU) (1114955), страница 49
Текст из файла (страница 49)
В следующей программе создается родовая функция, которая меняет местами значения двух переменных, передаваемых ей в качестве параметров. Поскольку в своей основе процесс обмена двух значений не зависит от типа переменных, этот процесс удачно реализуется с помощью родовой функции. // Пример родовой функции или шаблона ((тпс1цс(е <товЬтеат> цв(па патеьрасе вгг(; // Это функция-шаблон гегпр!аге <с1аьв Х> тоЫ виарагов(Х аа, Х аЬ) ( Х гетр; гегпр = а; а =Ь; Ь = гетпр; (пг та1п (1 1пт1= 10, д =20; Поаг х = 10.1, у = 23.3; сонг « "Исходные значения 1, ) равны: " « ! « ' ' « 1 « еппг; сои( « "Исходные значения х, у равны: " « х « ' ' <с у « епгп; аиарагдв (1, )); // обмен целых ьтчарагдв (х, у); // обмен действительных сонг « Новые значения 1., 1 равны: " «1 «« 1 «епгп; сонг « "Новыс значения х, у равны: " « х « ' ' « у « епд1; гетцгп О1 Ключевое слово 1епр1а1е используется для определения родовой функции.
Строка: тегпр(аге <с1аьа Х> уо1г( еиарагоз(Х аа, Х егЬ) сообщает компилятору две веши: во-первых, создается шаблон, и, во-вторых, начинается определение родовой функции. Здесь Х вЂ” это родовой тип данных, используемый в качестве фиктивного имени. После строки с ключевым 328 Самоучитель С++ словом 1ептр)а1е функция виарагяв() объявляется с именем Х в качестве типа данных обмениваемых значений.
В функции тпаш() функция азгарагяа() вызывается с двумя разными типами данных: целыми и действительными. Поскольку функция вчарац)вΠ— это родовая функция, компилятор автоматически создает две ее версии: одну — для обмена целых значений, другую для обмена действительных значений. Теперь попытайтесь скомпилировать программу. Имеются и другие термины, которые можно встретить при описании шаблонов в литературе по С++. Во-первых, родовая функция (то есть функция, в определении которой имеется ключевое слово 1ептр)а1е) также называется функция-шаблон (гетртате ~илсттол).
Когда компилятор создает конкретную версию этой функции, говорят, что он создал порожденную функцию фелегаМ/йлсйоп). Процесс генерации порожденной функции называют созданием экземпляра (тхГап(та(тля) функции. Другими словами, порожденная функция — это конкретный экземпляр функции-шаблона.
2. Ключевое слово 1ептр)а1е в определении родовой функции не обязательно должно быть в той же строке, что и имя функции. Например, ниже приведен еще один вполне обычный формат определения функции втгарагяаЦ: Ьетттр1асе <с1аее Х> чоте вчтарахда (Х йа, Х аЬ) Х Ьептр; При использовании такого формата важно понимать, что никаких других инструкций между инструкцией 1ептр)а1е и началом определения родовой функции быть не может. Например, следующий фрагмент программы компилироваться не будет: // Этот фрагмент компилироваться не будет 1ептр(ате <с1авв Х> 1пс // это неправильно чоттт.
атчаратттв(х Жа, Х аЬ) ХЬ арт тетттр = а; а=Ь; Ь = тетпр; Как указано в комментариях, инструкция с ключевым словом 1ептр!а1е должна находиться сразу перед определением функции. Глава т 1, Шаблоны и обработка исключительных ситуаций 329 3. Как уже упоминалось, для задания родового типа данных в определении шаблона вместо ключевого слова с1авв можно указывать ключевое слово 1урепаше. Например, ниже приведено еше одно объявление функции вчгарагпв О: // Использование ключевого слова (урепагпе 1епзр!а1е сгурепазве Х> чоЫ аиарагде(х аа, Х аь) Х гетр! (егпр = а; а=Ь; Ь = 1егпр; Ключевое слово 1урепап1е можно также указывать для задания неизвестного типа данных внутри шаблона, но такое его использование выходит за рамки данной книги, 4.
С помощью инструкции 1ешр!а1е можно определить более одного родового типа данных, отделяя их друг от друга запятыми. Например„в данной программе создается родовая функция, в которой имеются два родовых типа данных: ((ьпс1пг(е <ьовегеалс ив!пя пагпеврасе в(г(; 1сгпр1а1е <с1авв гуре1, с!авв 1уре2> чоЫ туЕппс(курев х, Гуре2 у) сов( « х « ' ' с< у «епп1; |п1 татп () тугппс(10, гву1ипс(0.23, 10Ь); ге1пгп 0; В данном примере при генерации конкретных экземпляров функции п1у(ппс(), фиктивные имена типов 1уре! и 1уре2 заменяются компилятором на типы данных ш1 и с(тат* или (1опЫе и 1опп соответственно.
Когда вы создаете родовую функцию, то по существу предписываете компилятору генерировать столько разных версий этой функции, сколько нужно для обработки всех способов вызова этой функции в вашей программе. Самоучитель С++ 330 5. Родовые функции похожи на перегруженные функции за исключением того, что они более ограничены по своим возможностям.
При перегрузке функции внутри ее тела можно выполнять совершенно разные действия. С другой стороны, родовая функция должка выполнять одни и те же базовые действия для всех своих версий. Например, следующие перегруженные функции нельзя заменить на родовую функцию, поскольку они делают не одно и то же. чоЫ оцтк(ага (тпб 1) 1 сонг « чета оигоага(поцЬ1е с() ( соцб « яебргесфяфоп(10) «яесй111 ('()') ' сопб « с); сопб « яекргес1ятоп(б) « яе 1111 (' '); Г)откчена функции-шаблона пптпс1цс(е <гояггеат> пятно пашеярасе ягс(: пешр1апе <с1аяя Х> чей яиарагяя (Х ьа, Х ьЬ) х сешр; бепр =- а; а=-Ь! келлер~ !т Здесь переопределяется родовая версия функции яиарагоя() чо1о яиарагдя(1пг а, 1пк Ь) ( соне « "это печатается внутри функции яиарагдя(тпв, гпг)Хп"; 1пс лахп() ( тпб 1 = 10, 5 = 20р 11оаг.
х = ЮЛ, у = 23.3; « 1 « ' ' « 3 « епб1; «х« ' ' «у«епс(1; соцб « "Исходные значения 1, 3 равны: соцс « "Исхо)льве значения х, у равны: б. Несмотря на то, что функция-шаблон при необходимости перегружается сама, ее также можно перегрузить явно. Если вы сами перегружаете родовую функцию, то перегруженная функция подменяет (или "скрывает") родовую функцию, которую бы создал компилятор для этой конкретной версии. Рассмотрим такой вариант примера 1: 332 Самоучитель С+ч 1 1.2.
РОДО~ы~ классы В дополнение к родовым функциям можно определить и родовые классы. При этом создается класс, в котором определены все необходимые алгоритмы, а фактические типы обрабатываемых данных задаются в качестве параметров позже, при создании объектов этого класса. Родовые классы полезны, когда класс содержит общую логику работы. Например, алгоритм, который реализует очередь целых, будет также работать и с очередью символов. Кроме того, механизм, который реализует связанный список почтовых адресов, будет также поддерживать связанный список запасных частей к автомобилям.
С помощью родового класса можно создать класс, реализующий очередь, связанный список и т. д. для любых типов данных. Компилятор будет автоматически генерировать правильный тип объекта на основе типа, заданного при создании объекта. Ниже представлена основная форма объявления родового класса: гегпр1аге <с1авв вв >с~авв имя класса ( Здесь Фрид — это фиктивное имя типа, который будет задан при создании экземпляра класса. При необходимости можно определить более одного родового типа данных, разделяя их запятыми. После создания родового класса с помощью представленной ниже формы можно создать конкретный экземпляр этого класса: мы~ хлвсса Еввп> обьвхт; Здесь тип — это имя типа данных, с которым будет оперировать класс.
Функции-члены родового класса сами автоматически становятся родовыми. Для них не обязательно явно задавать ключевое слово $ешрЫе. Как вы увидите в главе 14, в С++ имеется встроенная библиотека классов- шаблонов, которая называется библиотекой стандартных шаблонов (Яапдап1 Тешр!аге 1.1Ьгагу, ЗТ1.). Эта библиотека предоставляет родовые версии классов для наиболее часто используемых алгоритмов и структур данных.
Чтобы научиться пользоваться библиотекой" стандартных шаблонов с максимальной эффективностью, вам необходимо иметь твердые знания по классам- шаблонам и их синтаксису. ~~ч1ч$~;е':ФГт.,", -',, .:; .Примеры; 1. В следующей программе создается очень простой родовой класс, реализующий односвязный список. Затем демонстрируются возможности такого класса путем создания связанного списка для хранения символов. Глава 7 7, шаблоны и обработка исключительных ситуаций 333 Простой родовой связанный список ()1пс1пйе <1ояЕгеат> пяупд патеярасе я(.й; пеп~р1апе <с)аяя йава с> с1аяя 11яп ( йага с йава; 11яв *пехв7 рп)о11с: 11яс (йала й) 7 ыоуй айй (11яе *пойе) ( пойе->пехп = спйв7 пехп .-- О; ) 11яс *деспехв() ( теспвп пехв; йаса в деГйа~а() ( геьплп йаса; ) (егор!аге <с(аяя йа(а Г> 11яе<йаяа В>:: '1яв (оа-а В й) йага = й; пехг = 0„ пп ва1п () 11яп<спаг> явав~( 'а'); 1)яп<сйав> "р, *1аяп7 (пп 1; создание списка 1аяс = яяпалп; бог(1=17 1<267 1++) ( р = пеи 11яп<сЬап> р->айй(1авп) 7 1аяп = р; (лаю ('/ Вывод списка р = вя(.агв7 ий11е (р) ( сопя « р->де"йапа[) р = р->деппехп (); ) вепплв О; 1(ят<с)заг> ялакГ ('а') 7 Квк видите, объявление родового класса похоже нв объявление родовой функции.
Тип данных, хранящихся в списке, становится родовым в объявлении класса. Но он не проявляется, пока не объявлен объект, который и задает реальный тип данных. В данном примере объекты и указатели создаются внутри функции гпя)п0, где указывается, что типом хранящихся в списке данных является тип сйаг. Обратите особое внимание на следующее объявление: 334 Самоучитель Сч-+ Отметьте, что необходимый тип данных задается между угловыми скобками. Наберите и выполните эту программу. В ней создается связанный список с символами алфавита, который затем выводится на экран. Путем простого изменения типа данных, который указывается при создании объектов„можно изменить тип данных, хранящихся в списке. Например, с помощью следующего объявления можно создать другой объект, где можно было бы хранить целые: 11зс<гнв> зле всегда(1)4 Можно также использовать список 1Ы для хранения создаваемых вами типов данных.
Например, для хранения адресной информации можно воспользоваться следующей структурой: з1гисг а<1<1г ( с(тат пате ('40]," с]заг зСгеес(40]г с]таг с1' у(30],' с]заг згаге(3~; с(заг здр[12] г Теперь, чтобы с помощью списка 1Ы хранить объекты типа а(Ыг, используйте такое объявление (предположим, что объект в(гие$таг содержит правильную структуру аИг): 11зг<асЫг> оЬ1 (зггнсечаг) 2. Ниже представлен другой пример родового класса. Это переработанный класс зтасК, впервые приведенный в главе 1.