246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 52
Текст из файла (страница 52)
В строках 61—66происходит инициализация переменной itsAge значением 5, переданным в параметреконструктора.ВклассеDogвстроках35—39создаетсяпятьперегруженныхконструкторов.Первый—этоконструктор,заданныйпоумолчанию.Второйпринимаетвозрастииспользуетдляэтогототжепараметр, что и конструктор класса Mammal. Третий принимает возраст и вес, четвертый —возрастипороду,апятый—возраст,весипороду.Обратите внимание, что в строке 74 конструктор по умолчанию класса Dog вызываетконструктор по умолчанию класса Mammal. Хотя в этом нет необходимости, но данная записьлишний раз документирует намерение вызвать именно базовый конструктор, не содержащийпараметров.
Базовый конструктор будет вызван в любом случае, но в данной строке это былосделаноявно.В строках 80—85 выполняется конструктор класса Dog, который принимает одноцелочисленное значение. Во время инициализации (строки 81 и 82) возраст принимается избазовогоклассаввидепараметра,послечегоприсваиваетсязначениепороды.Другой конструктор класса Dog выполняется в строках 87—93. Этот конструкторпринимает два параметра. Первое значение вновь инициализируется обращением ксоответствующему конструктору базового класса, тогда как второе берется из переменнойбазовогоклассаitsWeightсамимконструкторомклассаDog.Обратитевнимание,чтоприсвоениезначения переменной базового класса не может осуществляться на стадии инициализацииконструктора произведенного класса.
Поскольку в классе Mammal нет конструктора,присваивающегозначениеэтойпеременной,топрисвоениезначениядолжновыполнятьсявтелеконструктораклассаDog.Самостоятельно проанализируйте работу остальных конструкторов в программе, чтобызакрепить полученные знания. Обращайте внимание, какие переменные можноинициализировать одновременно с инициализацией конструктора, а в каких случаяхинициализациюследуетвыполнятьвтелеконструктора.Для удобства анализа работы программы строки вывода были пронумерованы. Первые двестрокивыводасоответствуютинициализацииобъектаFidoспомощьюконструкторов,заданныхпоумолчанию.Строки 3 и 4 соответствуют созданию объекта rover, а строки 5 и 6 — объекта buster.Обратите внимание, что в последнем случае из конструктора класса Dog с двумяцелочисленными параметрами происходит вызов конструктора класса Mammal, содержащегоодинцелочисленныйпараметр.После создания всех объектов программа использует их и наконец выходит за областьвидимостиэтихобъектов.УдалениекаждогообъектасопровождаетсяобращениемкдеструкторуклассаDog,послечегоследуетобращениекдеструкторуклассаMammal.ЗамещениефункцийОбъект класса Dog имеет доступ ко всем функциям-членам класса Mammal, а также клюбойфункции-члену,чьеобъявлениедобавленовклассDog,напримеркфункцииWagTaill().Нокромеэтого,базовыефункциимогутбытьзамещенывпроизводномклассе.Подзамещениембазовой функции понимают изменение ее выполнения в производном классе для объектов,созданныхвэтомклассе.Есливпроизводномклассесоздаетсяфункциястакимжевозвратомисигнатуройкакивбазовомклассе,новыполняемаяособымобразом,тоимеетместозамещениеметода.В случае замещения функций должно сохраняться соответствие между типом возврата исигнатурой функций в базовом классе.
Под сигнатурой понимают установки, заданные впрототипе функции, включая ее имя, список параметров и, в случае использования, ключевоесловоconst.В листинге 11.5 показано замещение в классе Dog функции Speak(), объявленной в классеMammal.Дляэкономииместазнакомыепопредыдущимлистингамобъявленияметодовдоступавэтомпримеребылиопущены.Листинг11.5.Замещениеметодабазовогоклассавпроизводномклассе1://Листинг11.5.Замещениеметодабазовогоклассавпроизводномклассе2:3:#include<iostream.h>4:enumBREED{GOLDEN,CAIRN,DANDIE,SHETLAND,DOBERMAN,LAB};5:6:classMammal7:{8:public:9://Конструкторы10:Mammal(){cout<<"Mammalconstructor...\n";}11:~Mammal(){cout<<"Mammaldestructor...\n";}12:13://Другиеметоды14:voidSpeak()const{cout<<"Mammalsound!\n";}15:voidSleep()const{cout<<"shhh.I'msleeping.\n";}16:17:18:protected:19:intitsAge;20:intitsWeight;21:};22:23:classDog:publicMammal24:{25:public:26:27://Конструкторы28:Dog(){cout<<"Dogconstructor...\n";}29:~Dog(){cout<<"Dogdestructor...\n";}30:31://Другиеметоды32:voidWagTail()const{cout<<"Tailwagging...\n";}33:voidBegForFood()const{cout<<"Beggingforfood...\n";}34:voidSpeak()const{cout<<"Woof!\n";}35:36:private:37:BREEDitsBreed;38:};39:40:intmain()41:{42:MammalbigAnimal;43:Dogfido;44:bigAnimal.Speak();45:fido.Speak();46:return0;47:}Результат:Mammalconstructor...Mammalconstructor...Dogconstructor...Mammalsound!Woof!Dogdestructor...Mammaldestructor...Mammaldestructor...Анализ:Встроке34вклассеDogпроисходитзамещениеметодабазовогоклассаSpeak(), в результате чего в случае вызова этой функции объектом класса Dog на экранвыводится Woof!.
В строке 42 создается объект bigAnimal класса Mammal, в результате чеговызывается конструктор класса Mammal и на экране появляется первая строка. В строке 43создается объект Fido класса Dog, что сопровождается последовательным вызовом сначалаконструктора класса Mammal, а затем конструктора класса Dog. Соответственно на экранвыводитсяещедвестроки.Встроке44объектклассаMammalвызываетметодSpeak(),австроке45ужеобъектклассаDogобращаетсякэтомуметоду.Наэкранприэтомвыводитсяразнаяинформация,таккакметодSpeak() в классе Dog замещен. Наконец выполнение программы выходит за область видимостиобъектовидляихудалениявызываютсясоответствующиепарыдеструкторов.ПерегрузкаилизамещениеЭти схожиеподходы приводят почти к одинаковым результатам. При перегрузке методасоздается несколько вариантов этогометода с одним и тем же именем, но с разнымисигнатурами.
При замещении в производном классе используется метод с тем же именем исигнатурой,чтоивбазовомклассе,носизменениямивтелефункции.СокрытиеметодабазовогоклассаВпредыдущемпримереприобращениикметодуSpeak()изобъектаклассаDogпрограммавыполняласьнетак,какбылоуказаноприобъявленииметодаSpeak()вбазовомклассе.Казалосьбы, это то, что нам нужно. Если в классе Mammal есть некоторый метод Move(), которыйзамещаетсявклассеDog,томожносказать,чтометодMove()классаDogскрываетметодстемже именем в базовом классе. Однако в некоторых случаях результат может оказатьсянеожиданным.Усложним ситуацию. Предположим, что в классе Mammal метод Move() триждыперегружен.
В одном варианте метод не требует параметров, в другом используется одинцелочисленныйпараметр(дистанция),автретьем—двацелочисленныхпараметра(скоростьидистанция). В классе Dog замещен метод Move() без параметров. Тем не менее попыткаобратиться из объекта класса Dog к двум другим вариантам перегруженного метода классаMammalокажетсянеудачной.Сутьпроблемыраскрываетсявлистинге11.6.Листинг11.6.Сокрытиеметодов1://Листинг11.6.Сокрытиеметодов2:3:#include<iostream.h>4:5:classMammal6:{7:public:8:voidMove()const{cout<<"Mammalmoveonestep\n";}9:voidMove(intdistance)const10:{11:cout<<"Mammalmove";12:cout<<distance<<"steps.\n";13:}14:protected:15:intitsAge;16:intitsWeight;17:};18:19:classDog:publicMammal20:{21:public:22://Возможно,последуетсообщение,чтофункцияскрыта!23:voidMove()const{cout<<"Dogmove5steps.\n";}24:};25:26:intmain()27:{28:MammalbigAnimal;29:Dogfido;30:bigAnimal.Move();31:bigAnimal.Move(2);32:fido.Move();33://fido.Move(10);34:return0;35:}Результат:MammalmoveonestepMammalmove2steps.Dogmove5steps.Анализ: В данном примере из программы были удалены все другие методы и данные,рассмотренныенамиранее.Встроках8и9вобъявленииклассаMammalперегружаютсяметодыMove().Встроке23происходитзамещениеметодаMove()безпараметроввклассеDog.Данныйметодвызываетсядляобъектовразныхклассоввстроках30и32,иинформация,выводимаянаэкран,подтверждает,чтозамещениеметодапрошлоправильно.Однако строка 33 заблокирована, так как она вызовет ошибку компиляции.
Хотя логичнобылопредположить,чтовклассеDogсвободноможноиспользоватьметодMove(int),посколькузамещен был только метод Move(), но в действительности в данной ситуации, чтобыиспользоватьMove(int),еготакженужнозаместитьвклассеDog.Вслучаезамещенияодногоизперегруженных методов скрытыми оказываются все варианты этого метода в базовом классе.Есливыхотитеиспользоватьвпроизводномкласседругиевариантыперегруженногометода,тоихтакженужнозаместитьвэтомклассе.Часто случается ошибка, когда после попытки заместить метод в производном класседанный метод оказывается недоступным для класса из-за того, что программист забылустановить ключевое слово const, используемое при объявлении метода в базовом классе.Вспомните, что слово const является частью сигнатуры, а несоответствие сигнатур ведет кскрытиюбазовогометода,анекегозамещению.ЗамещениеисокрытиеВ следующем разделе главы будут рассматриваться виртуальные методы.
Замещениевиртуальных методов ведет к полиморфизму, а сокрытие методов разрушает поли- морфизм.Скоровыузнаетеобэтомбольше.ВызовбазовогометодаДаже если вы заместили базовый метод, то все равно можете обратиться к нему, указавбазовыйкласс,гдехранитсяисходноеобъявлениеметода.Дляэтоговобращениикметодунужноявно указать имя базового класса, за которым следуют два символа двоеточия и имя метода.Например:Mammal::Move().Если в листинге 11.6 переписать строку 32 так, как показано ниже, то ошибка во времякомпиляциибольшевозникатьнебудет:32:fido.Mammal::Move();Такая запись, реализованная в листинге 11.7, называется явным обрашением к методубазовогокласса.Листинг11.7.Явноеобращениекметодубазовогокласса1://Листинг11.7.Явноеобращениекметодубазовогокласса2:3:#include<iostream.h>4:5:classMammal6:{7:public:8:voidMove()const{cout<<"Mammalmoveonestep\n";}9:voidMove(intdistance)const10:{11:cout<<"Mammalmove"<<distance;12:cout<<"steps.\n";13:}14:15:protected:16:intitsAge;17:intitsWeight;18:};19:20:classDog:publicMammal21:{22:public:23:voidMove()const;24:25:};26:27:voidDog::Move()const28:{29:cout<<"Indogmove...\n";30:Mammal::Move(3);31:}32:33:intmain()34:{35:MammalbigAnimal;36:Dogfido;37:bigAnimal.Move(2);38:fido.Mammal::Move(6);39:return0;40:}Результат:Mammalmove2steps.Mammalmove6steps.Анализ: В строке 35 создается объект bigAnimal класса Mammal, а в строке 36 — объектfidoклассаDog.Встроке37вызываетсяметодMove(int)избазовогоклассадляобъектаклассаDog.Впредыдущейверсиипрограммымыстолкнулисьспроблемойиз-затого,чтовклассеDogдоступентолькоодинзамещенныйметодMove(),вкоторомнезадаютсяпараметры.ПроблемабыларазрешенаявнымобращениемкметодуMove(int)базовогоклассавстроке38.Рекомендуется:Повышайте функциональные возможности класса путем создания новыхпроизводных классов.
Изменяйте выполнение отдельных функций в производных классах спомощьюзамещенияметодов.Не рекомендуется:Ненесоответствиясигнатур.допускайтесокрытиефункцийбазовогоклассаиз-заВиртуальныеметоды В этой главе неоднократно подчеркивалось, что объекты класса Dog одновременноявляютсяобъектамиклассаMammal.Досихпорподэтимподразумевалось,чтообъектыклассаDogнаследуютвсеатрибуты(данные)ивозможности(методы)базовогокласса.НовязыкеC++принципыиерархическогопостроенияклассовнесутвсебеещеболееглубинныйсмысл.ПолиморфизмвC++развитнастолько,чтодопускаетсяприсвоениеуказателямнабазовыйклассадресовобъектовпроизводныхклассов,каквследующемпримере:Mammal*pMammal=newDog;Данное выражение создает в области динамической памяти новый объект класса Dog ивозвращает указатель на этот объект, который является указателем класса Mammal.
Это вполнелогично,таккаксобака—представительмлекопитающих.Примечание:В этом суть полиморфизма. Например, можно объявить множество оконразныхтипов,включаядиалоговые,прокручиваемыеокнаиполясписков,послечегосоздаватьих в программе с помощью единственного виртуального метода draw(). Создав указатель набазовое окно и присваивая этому указателю адреса объектов производных классов, можнообращатьсякметодуdraw()независимооттого,скакимизобъектоввданныймоментсвязануказатель.