246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 46
Текст из файла (страница 46)
Например, назначение конструктораRectangleсостоитвсозданииобъектапрямоугольник.Дозапускаконструкторапрямоугольниквпрограмме отсутствует. Существует только зарезервированная для него область памяти. Позавершении выполнения конструктора в программе появляется готовый для использованияобъект.Конструкторы,какивседругиефункции,можноперегружать.Перегрузкаконструкторов—мощноесредствоповышенияэффективностиигибкостипрограммы.Например, рассматриваемый нами объект Rectangle может иметь два конструктора.
Впервомзадаетсяширинаидлинапрямоугольника,авторойнеимеетпараметровидляустановкиразмеровиспользуетзначенияпоумолчанию.Этаидеяреализованавлистинге10.3.Листинг10.3.Перегрузкаканструктора1://Листинг10.3.2://Перегрузкаконструктора3:4:#include<iostream.h>5:6:classRectangle7:{8:public:9:Rectangle();10:Rectangle(intwidth,intlength);11:~Rectangle(){}12:intGetWidth()const{returnitsWidth;}13:intGetLength()const{returnitsLength;}14:private:15:intitsWidth;16:intitsLength;17:};18:19:Rectangle::Rectangle()20:{21:itsWidth=5;22:itsLength=10;23:}24:25:Rectangle::Rectangle(intwidth,intlength)26:{27:itsWidth=width;28:itsLength=length;29:}30:31:intmain()32:{33:RectangleRect1;34:cout<<"Rect1width:"<<Rect1.GetWidth()<<endl;35:cout<<"Rect1length:"<<Rect1.GetLength()<<endl;36:37:intaWidth,aLength;38:cout<<"Enterawidth:";39:cin>>aWidth;40:cout<<"\nEnteralength:";41:cin>>aLength;42:43:RectangleRect2(aWidth,aLength);44:cout<<"\nRect2width:"<<Rect2.GetWidth()<<endl;45:cout<<"Rect2length:"<<Rect2.GetLength()<<endl;46:return0;47:}Результат:Rect1width:5Rect1length:10Enterawidth:20Enteralength:50Rect2width:20Rect2length:50Анализ: Класс Rectangle объявляется в строках с 6 по 17.
В классе представлены дваконструктора:одиниспользуетзначенияпоумолчанию(строка9),авторойпринимаетзначениядвух целочисленных параметров (строка 10). В строке 33 прямоугольный объект создается сиспользованием первого конструктора. Значения размеров прямоугольника, принятые поумолчанию, выводятся на экран в строках 34 и 35.
Строки программы с 37 по 41 выводят наэкран предложения пользователю ввести собственные значения ширины и длиныпрямоугольника. В строке 43 вызывается второй конструктор, использующий два параметра столько что установленными значениями. И наконец, значения размеров прямоугольника,установленныепользователем,выводятсянаэкранвстроках44и45.Как и при использовании других перегруженных функций, компилятор выбирает нужноеобъявлениеконструктора,основываясьначислеитипепараметров.ИнициализацияобъектовДосихпорпеременные-членыобъектовзадавалисьпрямовтелеконструктора.Выполнениеконструкторапроисходитвдваэтапа:инициализацияивыполнениетелаконструктора.Большинство переменных может быть задано на любом из этих этапов: как во времяинициализации,такивовремявыполненияконструктора.Нологическиправильнее,азачастуюиэффективнее,инициализироватьпеременные-членывовремяинициализацииконструктора.Вследующемпримерепоказанаинициализацияпеременных-членов:CAT()://имяконструктораисписокпараметровitsAge(5),//инициализацияспискаitsWeigth(8){}//телоконструктораПосле скобки закрытия списка параметров конструктора ставится двоеточие.
Затемперечисляются имена переменных-членов. Пара круглых скобок со значением за именемпеременной используется для инициализации этой переменной. Если инициализируется сразунесколько переменных, то они должны быть отделены запятыми. В листинге 10.4 показанаинициализация переменных конструкторов, взятых из листинга 10.3. В данном примереинициализацияпеременныхиспользуетсявместоприсвоенияимзначенийвтелеконструктора.Листинг10.4.Фрагментпрограммногокодасинициализациейпеременных-членов1:Rectangle::Rectangle():2:itsWidth(5),3:itsLength(10)4:{5:}6:7:Rectangle::Rectangle(intwidth,intlength):8:itsWidth(width),9:itsLength(length)10:{11:}Результат:ОтсутствуетАнализ:Некоторыепеременныеможнотолькоинициализироватьинельзяприсваиватьимзначения:например,вслучаеиспользованияссылокиконстант.Безусловно,переменной-членуможноприсвоитьзначениепрямовтелеконструктора,нодляупрощенияпрограммылучшеповозможности устанавливать значения переменных-членов на этапе инициализацииконструктора.Конструктор-копировщикПомимо конструктора и деструктора, компилятор по умолчанию предоставляет такжеконструктор-копировщик,которыйвызываетсявсякийраз,когданужносоздатькопиюобъекта.Когда объект передается как значение либо в функцию, либо из функции в виде возврата,всегда создается его временная копия.
Если в программе обрабатывается объект, созданныйпользователем,тодлявыполненияэтихоперацийвызываетсяконструктор-копировщиккласса,какбылопоказанонапредыдущемзанятиивлистинге9.6.Всекопировщикипринимаюттолькоодинпараметр—ссылкунаобъектвтомжеклассе.Разумно будет сделать эту ссылку константной, так как конструктор не должен изменятьпередаваемыйвнегообъект.Например:CAT(constCAT&theCat);В данном случае конструктор CAT принимает константную ссылку на объект класса CAT.Цельиспользованияконструктора-копировщикасостоитвсозданиикопииобъектаtheCat.Копировщик, заданный компилятором по умолчанию, просто копирует все переменныечлены из указанного в параметре объекта в переменные-члены нового объекта.
Такоекопирование называется поверхностным; и, хотя оно подходит для большинства случаев, могутвозникнуть серьезные проблемы, если переменные-члены окажутся указателями на ячейкидинамическойпамяти.Поверхностное копирование создает несколько переменных-членов в разных объектах,которые ссылаются на одни и те же ячейки памяти. Глубинное копирование переписываетзначенияпеременныхпоновымадресам.Например, класс CAT содержит переменную-член theCat, которая указывает на ячейку вобласти динамической памяти, где сохранено некоторое целочисленное значение.
КопировщикпоумолчаниюскопируетпеременнуюtheCatизстарогоклассаCATвпеременнуюtheCatвновомклассеCAT.Приэтомобаобъектабудутуказыватьнаоднуитужеячейкупамяти(рис.10.1).Рис.10.1.Использованиекопировщика,заданногопоумолчаниюПроблемы могут возникнуть, если программа выйдет за область видимости одного изклассов CAT. Как уже отмечалось при изучении указателей, назначение деструктора состоит втом, чтобы очищать память от ненужных объектов.
Если деструктор исходного класса CATочистит свои ячейки памяти, а объекты нового класса CAT все так же будут ссылаться на этиячейки, то над программной нависнет смертельная опасность. Эта проблемапроиллюстрировананарис.10.2.Рис.10.2ВозникновениеошибочногоуказателяЧтобы предупредить возникновение подобных проблем, нужно вместо копировщика поумолчанию создать и использовать собственный копировщик, который будет осуществлятьглубинное копирование с перемещением значений переменных-членов в новые адреса памяти.Этотпроцесспоказанвлистинге10.5Листинг10.5.Конструктор-копировщик1://Листинг10.5.2://Конструктор-копировщик3:4:#include<iostream.h>5:6:classCAT7:{8:public:9:CAT();//конструкторпоумолчанию10:CAT(constCAT&);//конструктор-копировщик11:~CAT();//деструктор12:intGetAge()const{return*itsAge;}13:intGetWeight()const{return*itsWeight;}14:voidSetAge(intage){*itsAge=age;}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:CAT::CAT(constCAT&rhs)30:{31:itsAge=newint;32:itsWeight=newint;33:*itsAge=rhs.GetAge();//открытыйметоддоступа34:*itsWeight=*(rhs.itsWeight);//закрытыйметоддоступа35:}36:37:CAT::~CAT()38:{39:deleteitsAge;40:itsAge=0;41:deleteitsWeight;42:itsWeight=0;43:}44:45:intmain()46:{47:CATfrisky;48:cout<<"frisky'sage:"<<frisky.GetAge()<<endl;49:cout<<"Settingfriskyto6...\n";50:frisky.SetAge(6);51:cout<<"Creatingbootsfromfrisky\n";52:CATboots(frisky);53:cout<<"frisky'sage:"<<frisky.GetAge()<<endl;54:cout<<"boots'age;"<<boots.GetAge()<<endl;55:cout<<"settingfriskyto7...\n";56:frisky.SetAge(7);57:cout<<"frisky'sage:"<<frisky.GetAge()<<endl;58:cout<<"boot'sage:"<<boots.GetAge()<<endl;59:return0;60:}Результат:frisky'sage:5Settingfriskyto6...Creatingbootsfromfriskyfrisky'sage:6boots'age:6settingfriskyto7...frisky'sage:7boots'age:6Анализ:Встрокахпрограммыс6по19объявляетсяклассCAT.Обратитевнимание,чтовстроке9объявляетсяконструкторпоумолчанию,австроке10—конструктор-копировщик.Встроках17и18объявляетсядвепеременные-члены,представляющиесобойуказателинацелочисленные значения.
В реальной жизни трудно вообразить, для чего может понадобитьсясоздание переменных-членов как указателей на целочисленные значения. Но в данном случаетакие переменные являются отличными объектами для демонстрации методов управленияпеременными-членами,сохраненнымивдинамическойобластипамяти.Конструктор по умолчанию в строках с 21 по 27 выделяет для переменных областидинамическойпамятииинициализируетэтипеременные.Работакопировщиканачинаетсясостроки29.Обратитевнимание,чтовкопировщикезаданпараметр rhs.
Использовать в параметрах копировщиков символику rhs, что означает right-handside (стоящий справа), — общепринятая практика. Если вы посмотрите на строки 33 и 34, тоувидите,чтоввыраженияхприсваиванияименапараметровкопировщикарасполагаютсясправаотоператораприсваивания(знакаравенства).Вот как работает копировщик. Строки 31 и 32 выделяют свободные ячейки в областидинамической памяти. Затем, в строках 33 и 34 в новые ячейки переписываются значения изсуществующегоклассаCAT.Параметр rhs соответствует объекту классу CAT, который передается в копировщик в видеконстантной ссылки.
Как объект класса CAT, rhs содержит все переменные- члены любогодругогоклассаCAT.Любой объект класса CAT может открыть доступ к закрытым переменным-членам длялюбого другого объекта класса CAT. В то же время для внешних обращений всегда лучшесоздавать открытые члены, где это только возможно. Функция-член rhs.GetAge() возвращаетзначение,сохраненноевпеременной-членеitsAge,адрескоторойпредставленвrhs.Процедуры, осуществляемые программой, продемонстрированы на рис. 10.3.