246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 49
Текст из файла (страница 49)
Этот оператор используется всякий раз, когда нужно присвоитьобъектуновоезначение,например:CATcatOne(5,7);CATcatTwo(3,4);//...другиестрокипрограммыcatTwo=catOneВданномпримересозданобъектcatOne,переменнойкоторогоitsAgeприсвоенозначение5,а переменной itsWeigth — 7. Затем создается объект catTwo со значениями переменныхсоответственно3и4.Через некоторое время объекту catTwo присваиваются значения объекта catOne. Чтопроизойдет, если переменная itsAge является указателем, и что происходит со старымизначениямипеременныхобъектаcatTwo?Работа с переменными-членами, которые хранят свои значения в области динамическойпамяти,рассматриваласьранееприобсуждениииспользованияконструктора-копировщика(см.такжерис.10.1и10.2).В C++ различают поверхностное и глубинное копирование данных.
При поверхностномкопировании происходит передача только адреса от одной переменной к другой, в результатечегообаобъектауказываютнаодниитежеячейкипамяти.Вслучаеглубинногокопированиядействительно происходит копирование значений переменных из одной области памяти вдругую.Различиямеждуэтимиметодамикопированияпоказанынарис.10.3.Всевышесказанноесправедливодляприсвоенияданных.Вслучаеиспользованияоператораприсваивания, процесс обмена данных протекает с некоторыми особенностями.
Так, объектcatTwo уже существует вместе со своими переменными, для каждой из которых выделеныопределенные ячейки памяти. В случае присвоения объекту новых значений предварительнонеобходимо освободить эти ячейки памяти. Что произойдет, если выполнить присвоениеобъектаcatTwoсамомусебе:catTwo=catTwoВряд ли такая строка в программе может иметь смысл, но в любом случае программадолжна уметь поддерживать подобные ситуации. Дело в том, что присвоение объекта самомусебе может произойти по ошибке в случае косвенного обращения к указателю, которыйссылаетсянатотжеобъект.Если не предусмотреть поддержку такой ситуации, то оператор присваивания сначалаочистит ячейки памяти объекта catTwo, а затем попытается присвоить объекту catTwo своисобственныезначения,которыхуженебудетивпомине.Чтобыпредупредитьподобнуюситуацию,вашоператорприсваиванияпреждевсегодолженопределить,несовпадаютлидругсдругомобъектыпообестороныотоператораприсваивания.Этоможноосуществитьспомощьюуказателяthis,какпоказановлистинге10.15.Листинг10.15.Операторприсваивания1://Листинг10.15.2://Конструктор-копировщик3:4:#include<iostream.h>5:6:classCAT7:{8:public:9:CAT();//конструкторпоумолчанию10://конструктор-копировщикидеструкторпропущены!11:intGetAge()const{return*itsAge;}12:intGetWeight()const{return*itsWeight;}13:voidSetAge(intage){*itsAge=age;}14:CAT&operator=(constCAT&);15:16:private:17:int*itsAge;18:int*itsWeight;19:};20:21:CAT::CAT()22:{23:itsAge=newint;24:itsWeight=newint;25:*itsAge=5;26:*itsWeight=9;27:}28:29:30:CAT&CAT::operator=(constCAT&rhs)31:{32:if(this==&rhs)33:return*this;34:*itsAge=rhs.GetAge();35:*itsWeight=rhs.GetWeight();36:return*this;37:}38:39:40:intmain()41:{42:CATfrisky;43:cout<<"frisky'sage:"<<frisky.GetAge()<<endl;44:cout<<"Settingfriskyto6...\n";45:frisky.SetAge(6);46:CATwhiskers;47:cout<<"whiskers'age:"<<whiskers.GetAge()<<endl;48:cout<<"copyingfriskytowhiskers...\n";49:whiskers=frisky;50:cout<<"whiskers'age:"<<whiskers.GetAge()<<endl;51:return0;52:}Результат:frisky'sage:5Settingfriskyto6...whiskers'age:5copyingfriskytowhiskers...whiskers'age:6Анализ:Влистинге10.15вновьиспользуетсяклассCAT.Чтобынеповторяться,вданномкодепропущеныобъявленияконструктора-копировщикаидеструктора.Встроке14объявляетсяоператорприсваивания,определениекоторогопредставленовстроках30—37.В строке 32 выполняется проверка того, не является ли объект, которому будет присвоенозначение,темжесамымобъектомклассаCAT,чьезначениебудетприсвоено.Чтобыпроверитьэто,сравниваютсяадресавуказателяхrhsиthis.Безусловно, оператор присваивания (=) может быть произвольно перегружен такимобразом,чтобыотвечатьпредставлениямпрограммиста,чтоозначаетравенствообъектов.ОператорыпреобразованийЧто происходит при попытке присвоить значение переменой одного из базовых типов,такихкакintилиunsignedshort,объектукласса,объявленногопользователем?Влистинге10.16мы опять вернемся к классу Counter и попытаемся присвоить объекту этого класса значениепеременнойтипаint.Предупреждение:Листинг10.16некомпилируйте!Листинг10.16.ПопыткаприсвоитьобъектуклассаCounterзначениепеременнойтипаint1://Листинг10.16.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:private:15:intitsVal;16:17:};18:19:Counter::Counter():20:itsVal(0)21:{}22:23:intmain()24:{25:inttheShort=5;26;CountertheCtr=theShort;27:cout<<"theCtr:"<<theCtr.GetItsVal()<<endl;28:return0;29:}Результат:Компилятор покажет сообщение об ошибке, поскольку не сможет преобразовать тип int вCounter.Анализ:КлассCounter,определенныйвстроках7—17,содержиттолькоодинконструктор,заданныйпоумолчанию.Внемнеопределенониодногометодапреобразованияданныхтипаintв тип Counter, поэтому компилятор обнаруживает ошибку в строке 26.
Компилятор ничего несможетподелать,поканеполучитчеткихинструкций,чтоданныетипаintнеобходимовзятьиприсвоитьпеременной-членуitsVal.В листинге 10.17 эта ошибка исправлена с помощью оператора преобразования типов.Определен конструктор, который создает объект класса Counter и присваивает ему полученноезначениетипаint.Листинг10.17.ПреобразованиеintвCounter1://Листинг10.17.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:private:16:intitsVal;17:18:};19:20:Counter::Counter():21:itsVal(0)22:{}23:24:Counter::Counter(intval):25:itsVal(val)26:{}27:28:29:intmain()30:{31:inttheShort=5;32:CountertheCtr=theShort;33:cout<<"theCtr:"<<theCtr.GetItsVal()<<endl;34:return0;35:}Результат:theCtr:5Анализ: Важные изменения произошли в строке 11, где конструктор перегружен такимобразом,чтобыприниматьзначениятипаint,атакжевстроках24—26,гдеданныйконструкторприменяется.
В результате выполнения конструктора переменной-члену класса Counterприсваиваетсязначениетипаint.Дляприсвоениязначенияпрограммаобращаетсякконструктору,вкоторомприсваиваемоезначениепередаетсявкачествеаргумента.Процессосуществляетсявнесколькошагов.Шаг1:созданиепеременнойклассаCounterсименемtheCtr.Это то же самое, что записать: int x = 5, где создается целочисленная переменная x и ейприсваивается значение 5.
Но в нашем случае создается объект theCtr класса Counter, которыйинициализируетсяпеременнойtheShortTHnashortint.Шаг2:присвоениеобъектуtheCtrзначенияпеременнойtheShort.Но переменная относится к типу short, а не Counter! Первое, что нужно сделать, — этопреобразовать ее к типу Counter. Компилятор может делать некоторые преобразованияавтоматически,ноемунужноточноуказать,чегоотнегохотят.ИменнодляинструктированиякомпиляторасоздаетсяконструкторклассаCounter,которыйсодержитединственныйпараметр,напримертипаshort:classCounter{Counter(shortintx);//...};Данный конструктор создает объект класса Counter, используя временный безымянныйобъект этого класса, способный принимать значения типа short. Чтобы сделать этот процессболеенаглядным,предположим,чтодлязначенийтипаshortсоздаетсянебезымянныйобъект,аобъектклассаCounterсименемwasShort.Шаг3:присвоениезначенияобъектаwasShortобъектуtheCtr,чтоэквивалентнозаписи"theCtr=wasShort";НаэтомшагевременныйобъектwasShort,созданныйпризапускеконструктора,замещаетсяна постоянный объект theCtr, принадлежащий классу Counter.
Другими словами, значениевременногообъектаприсваиваетсяобъектуtheCtr.Чтобы понять, как происходит этот процесс, следует четко уяснить принципы работы,справедливыедляВСЕХперегруженныхоператоров,определенныхспомощьюключевогословаoperator.Вслучаесоператорамисдвумяоперандами(такимикак=или+)находящийсясправаоперанд объявляется как параметр функции оператора, заданной в конструкторе. Так,выражениеа=bобъявляетсякакa.operator=(b);Чтопроизойдет,еслиизменитьпорядокприсвоения,каквследующемпримере:1:CountertheCtr(5);2:inttheShort=theCtr;3:cout<<"theShort:"<<theShort<<endl;Вновь компилятор покажет сообщение об ошибке.
Хотя сейчас компилятор уже знает, каксоздать временный объект Counter для принятия значения типа int, но он не знает, какосуществитьобратныйпроцесс.Операторыпреобразованиятипов Чтобы разрешить эту и подобные ей проблемы, в C++ есть специальные операторыпреобразования типов, которые можно добавить в пользовательский класс. В результатепоявится возможность явного преобразования типа пользовательского класса к любому избазовых типов данных языка программирования. Реализация этой возможности показана влистинге10.18.Толькооднозамечание:воператорахпреобразованийнезадаетсятипвозврата.Даже если их работа напоминает возврат функции, в действительности они возвращаютпреобразованноезначение.Листинг10.18.ПреобразованияданныхтипаCounterвтипunsignedshort()1:#include<iostream.h>2:3:classCounter4:{5:public:6:Counter();7:Counter(intval);8:~Counter(){}9:intGetItsVal()const{returnitsVal;}10:voidSetItsVal(intx){itsVal=x;}11:operatorunsignedshort();12:private:13:intitsVal;14:15:};16:17:Counter::Counter():18:itsVal(0)19:{}20:21:Counter::Counter(intval):22:itsVal(val)23:{}24:25:Counter::operatorunsignedshort()26:{27:return(int(itsVal));28:}29:30:intmain()31:{32:Counterctr(5);33:inttheShort=ctr;34:cout<<"theShort:"<<theShort<<endl;35:return0;36:}Результат:theShort:5Анализ:Встроке11объявляетсяоператорпреобразованиятипа.Обратитевнимание,чтовнемнеуказантипвозврата.Функцияоператорапреобразованиявыполняетсявстроках25—28.Встроке27возвращаетсязначениеобъектаitsVal,преобразованноевтипint.Теперькомпиляторзнает,какприсвоитьобъектуклассазначениетипаintикаквозвратитьизобъектаклассатекущеезначение,чтобыприсвоитьеговнешнейпеременнойтипаint.РезюмеСегодня вы научились перегружать функции-члены пользовательского класса.
Вы такжеузнали,какпередаватьвфункциизначения,заданныепоумолчанию,ивкакихслучаяхвместозначенийпоумолчаниюлучшеиспользоватьперегруженныефункции.Перегрузка конструкторов класса позволяет более гибко управлять классами и создаватьновые классы, содержащие объекты других классов. Лучше всего инициализацию объектовклассаосуществлятьвовремяинициализацииконструктора,вместотогочтобыделатьэтовтелеконструктора.Конструктор-копировщик и оператор присваивания по умолчанию предоставляютсякомпилятором, если в классе эти объекты не были созданы пользователем. Но прииспользовании копировщика и оператора присваивания, заданных по умолчанию,осуществляется только поверхностное копирование данных.