246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 47
Текст из файла (страница 47)
Значение, накотороессылаласьпеременная-членисходногоклассаCAT,копируетсявновуюячейкупамяти,адрескоторойпредставленвтакойжепеременной-члененовогоклассаCAT.В строке 47 вызывается объект frisky из класса CAT. Значение возраста, заданное в frisky,выводитсянаэкран,послечеговстроке50переменнойвозрастаприсваиваетсяновоезначение—6.Встроке52методомкопированияобъектаfriskyсоздаетсяновыйобъектbootsклассаCAT.Если бы в качестве параметра передавался объект frisky, то вызов копировщика осуществлялсябыкомпилятором.Встроках53и54выводитсявозрастобеихкошек.Обратитевнимание,чтовобоихслучаяхвобъектахfriskyиbootsзаписанвозраст6,тогдакакеслибыобъектbootsсоздавалсянеметодомкопирования,топоумолчаниюбылобыприсвоенозначение5.Встроке56значениевозраставобъекте было изменено на 7 и вновь выведены на экран значения обоих объектов.
Значениеобъектаfriskyдействительноизменилосьна7,тогдакаквbootsсохранилосьпрежнеезначениевозраста 6. Это доказывает, что переменная объекта frisky была скопирована в объект boots поновомуадресу.Рис.10.3ПримерглубинногокопированияКогда выполнение программы выходит за область видимости класса CAT, автоматическизапускается деструктор. Выполнение деструктора класса CAT показано в строках с 37 по 43.Операторdeleteприменяетсякобоимуказателям—itsAgeиitsWeigth,послечегообауказателядлянадежностиобнуляются.ПерегрузкаоператоровЯзык C++ располагает рядом встроенных типов данных, включая int, real, char и т.д. Дляработы с данными этих типов используются встроенные операторы — суммирования (+) иумножения (<<).
Кроме того, в C++ сушествует возможность добавлять и перегружать этиоператорыдлясобственныхклассов.Чтобы в деталях рассмотреть процедуру перегрузки операторов, в листинге 10.6 создаетсяновый класс Counter. Объект класса Counter будет использоваться в других приложениях дляподсчетацикловинкрементации,декрементацииидругихповторяющихсяпроцессов.Листинг10.6.КлассCounter1://Листинг10.6.2://КлассCounter3:4:int5:#include<iostream.h>6:7:classCounter8:{9:public:10:Counter();11:~Counter(){}12:intGetItsVal()const{returnitsVal;}13:voidSetItsVal(intx){itsVal=x;}14:15:private:16:intitsVal;17:18:};19:20:Counter::Counter():21:itsVal(0)22:{}23:24:intmain()25:{25:Counteri;27:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;28:return0;29:}Результат:Thevalueofiis0Анализ:Судяпоопределениювстрокахпрограммыс7по18,этосовершеннобесполезныйкласс.
В нем объявлена единственная переменная-член типа int. Конструктор по умолчанию,которыйобъявляетсявстроке10ивыполняетсявстроке20,инициализируетпеременную-членнулевымзначением.Вотличиеотобычнойпеременнойтипаint,объектклассаCounterнеможетиспользоватьсявоперацияхприращения,прибавляться,присваиватьсяилиподвергатьсядругимманипуляциям.В связи с этим выведение значения данного объекта на печать также сопряжено с рядомтрудностей.ЗаписьФункцииинкремента Ограничения использования объекта нового класса, которые упоминались выше, можнопреодолеть путем перегрузки операторов.
Например, существует несколько способоввосстановления возможности приращения объекта класса Counter. Один из них состоит в том,чтобыперегрузитьфункциюинкрементации,какпоказановлистинге10.7.Листинг10.7.Добавлениевклассоператораинкремента1://Листинг10.7.2://ДобавлениевклассCounterоператораинкремента3:4:int5:#include<iostream.h>6:7:classCounter8:{9:public:10:Counter();11:~Counter(){}12:intGetItsVal()const{returnitsVal;}13:voidSetItsVal(intx){itsVal=x;}14:voidIncrement(){++itsVal;}15:16:private:17:intitsVal;18:19:};20:21:Counter::Counter():22:itsVal(0)23:{}24:25:intmain()26:{27:Counteri;28:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;29:i.Increment();30:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;31:return0;32:}Результат:Thevalueofiis0Thevglueofiis1Анализ: В листинге 10.7 добавляется функция оператора инкремента, определенная встроке14.Хотяпрограммаработает,выглядитонадовольнонеуклюже.Программаизпоследнихсилстараетсяперегрузить++operator,ноэтоможнореализоватьдругимспособом.ПерегрузкапрефиксныхоператоровЧтобыперегрузитьпрефиксныйоператор,можноиспользоватьфункциюследующеготипа:returnTypeOperatorop(параметры)В данном случае ор — это перегружаемый оператор.
Тогда для перегрузки операторапреинкрементаиспользуемфункциюvoidoperator++()Этотспособпоказанвлистинге10.8.Листинг10.8Перегрузкаоператорапреинкремента1://Листинг10.8.2://ПерегрузкаоператорапреинкрементавклассеCounter3:4:int5:#include<iostream.h>6:7:classCounter8:{9:public:10:Counter();11:~Counter(){}12:intGetItsVal()const{returnitsVal;}13:voidSetItsVal(intx){itsVal=x;}14:voidIncrement(){++itsVal;>15:voidoperator++()<++itsVal;}16:17:private:18:intitsVal;19:20:};21:22:Counter::Counter():23:itsVal(0)24:{}25:26:intmain()27:{28:Counteri;29:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;30:i.Increment();31:icout<<"Thevalueofiis"<<i.GetItsVal()<<endl;32:++i;33:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;34:return0;35:}Результат:Thevalueofiis0Thevalueofiis1Thevalueofiis2Анализ: В строке 15 перегружается operator++, который затем используется в строке 32 врезультате объект класса Counter получает функции, которые можно было ожидать судя по егоназванию.
Далее объекту сообщаются дополнительные возможности, призванные повыситьэффективность его использования, в частности возможность контроля за максимальнымзначением,котороенельзяпревышатьвходеприращения.Новработеперегруженногооператораинкрементасуществуетодинсерьезныйнедостаток.Вданныймоментвпрограмменеудастсявыполнитьследующеевыражение:Counterа=++i;В этой строке делается попытка создать новый объект класса Counter — а, которомуприсваиваетсяприращенноезначениепеременнойi.Хотявстроенныйконструктор-копировщикподдерживает операцию присваивания, текущий оператор инкремента не возвращает объекткласса Counter.
Сейчас он возвращает пустое значение void. Невозможно присвоить значениеvoidобъектуклассаCounter.(Невозможносоздатьчто-тоизничего!)ТипывозвратовперегруженныхфункцийоператоровВсе, что нам нужно, — это возвратить объект класса Counter таким образом, чтобы eroможнобылоприсвоитьдругомуобъектуклассаCounter.Какэтосделать?Одинподходсостоитвтом,чтобысоздатьвременныйобъективозвратитьего.Онпоказанвлистинге10.9.Листинг10.8.Возвращениевременногообъекта1://Листинг10.9.2://Возвращениевременногообъекта3:4:int5:#include<iostream.h>6:7:classCounter8:{9:public:10:Counter();11:~Counter(){}12:intGetItsVal()const{returnitsVal;}13:voidSetItsVal(intx){itsVal=x;}14:voidIncrement(){++itsVal;}15:Counteroperator++();16:17:private:18:intitsVal;19:20:};21:22:Counter::Counter():23:itsVal(0)24:{}25:26:CounterCounter::operator++()27:{28:++itsVal;29:Countertemp;30:temp.SetItsVal(itsVal);31:returntemp;32:}33:34:intmain()35:{36:Counteri;37:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;38:i.Incrernent();39:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;40:++i;41:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;42:Counterа=++i;43:cout<<"Thevalueofa:"<<a.GetItsVal();44:cout<<"andi:"<<i.GetItsVal()<<endl;45:return0;46:}Результат:Thevalueofiis0Thevalueofiis1Thevalueofiis2Thevalueofa:3andi:3Анализ:Вданнойверсиипрограммыoperator++объявленвстроке15такимобразом,чтоможетвозвращатьобъектыклассаCounter.Встроке29создаетсявременныйобъектternp,иемуприсваивается значение текущего объекта Counter.
Значение временной переменнойвозвращаетсяитутже,встроке42,присваиваетсяновомуобъектуа.ВозвращениебезымянныхвременныхобъектовВдействительностинетнеобходимостиприсваиватьимявременномуобъекту,какэтобылосделановпредыдущемлистингевстроке29.ЕсливклассеCounterестьпринимающийзначениеконструктор, то параметру этого конструктора можно просто присвоить значение возвратаоператораинкремента.Этаидеяреализованавлистинге10.10.Листинг10.10.Возвращениебезымянноговременногообъекта1://Листинг10.10.2://Возвращениебезымянноговременногообъекта3:4:int5:#include<iostream.h>6:7:classCounter8:{9:public:10:Counter();11:Counter(intval);12:~Counter(){}13:intGetItsVal()const{returnitsVal;}14:voidSetItsVal(intx){itsVal=x;}15:voidIncrement(){++itsVal;}16:Counteroperator++();17:18:private:19:intitsVal;20:21:};22:23:Counter::Counter():24:itsVal(0)25:{}26:27:Counter::Counter(intval):28:itsVal(val)29:{}30:31:CounterCounter::operator++()32:{33:++itsVal;34:returnCounter(itsVal);35:}36:37:intmain()38:{39:Counteri;40:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;41:i.Increment();42:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;43:++i;44:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;45:Countera=++i;46:cout<<"Thevalueofa:"<<a.GetItsVal();47:cout<<"andi:"<<i.GetItsVal()<<endl;48:return0;49:}Результат:Thevalueofiis0Thevalueofiis1Thevalueofiis2Thevalueofa:3andi:3Анализ:Встроке11определенновыйконструктор,которыйпринимаетзначениетипаint.Данныйконструкторвыполняетсявстрокахс27по29.ПроисходитинициализацияпеременнойitsValзначением,переданнымвпараметре.Выполнение оператора инкремента в данной программе упрощено.
В строке 33осуществляется приращение переменной itsVal. Затем в строке 34 создается временный объекткласса Counter, которому присваивается значение переменной itsVal. Это значение затемвозвращаетсякакрезультатвыполненияоператораинкремента.Подобноерешениевыглядитболееэлегантно,новозникаетвопрос,длячеговообщенужносоздаватьвременныеобъекты.Напомним,чтосозданиеиудалениевременногообъектавпамятикомпьютератребуетопределенныхвременныхзатрат.Крометого,еслиобъектiужесуществуети имеет правильное значение, почему бы просто не возвратить его? Реализуем эту идею спомощьюуказателяthis.ИспользованиеуказателяthisНа прошлом занятии уже рассматривалось использование указателя this.
Этот указательможно передавать в функцию-член оператора инкремента точно так же, как в любую другуюфункцию-член. Указатель this связан с объектом i и в случае разыменовывания возвращаетобъект, переменная которого itsVal уже содержит правильное значение. В листинге 10.11показановозвращениеуказателяthis,чтоснимаетнеобходимостьсозданиявременныхобъектов.Листинг10.11.Возвращениеуказателяthis1://Листинг10.11.2://Возвращениеуказателяthis3:4:int5:#include<iostream.h>6:7:classCounter8:{9:public:10:Counter();11:~Counter(){}12:intGetItsVal()const{returnitsVal;}13:voidSetItsVal(intx){itsVal=x;}14:voidIncrement(){++itsVal;}15:constCounter&operator++();16:17:private:18:intitsVal;19:20:};21:22:Counter::Counter():23:itsVal(0)24:{};25:26:constCounter&Counter::operator++()27:{28:++itsVal;29:return*this;30:}31:32:intmain()33:{34:Counteri;35:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;36:i.Increment();37:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;38:++i;39:cout<<"Thevalueofiis"<<i.GetItsVal()<<endl;40:Counterа=++i;41:cout<<"Thevalueofa:"<<a.GetItsVal();42:cout<<"andi:"<<i.GetItsVal()<<endl;43:return0;44:}Результат:Thevalueofiis0Thevalueofiis1Thevalueofiis2Thevalueofa:3andi:3Анализ: Выполнение оператора приращения в строках с 26 по 30 замененоразыменовываниемуказателяthisивозвращениемтекущегообъекта.ВрезультатеобъектклассаCounterприсваиваетсяновомуобъектуаэтогожекласса.Какужеотмечалосьвыше,еслиобъектклассаCounterтребуетвыделенияпамяти,необходимозаместитьконструктор-копировщик.Новданном случае конструктор- копировщик, заданный по умолчанию, отлично справляется сосвоимизадачами.Обратитевнимание,чтовозвращаемоезначениепредставляетсобойссылкуклассаCounter,благодаря чему отпадает необходимость в создании каких-либо дополнительных временныхобъектов.