246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 37
Текст из файла (страница 37)
Для набора этого оператора используется непрерывная последовательность двухсимволов: тире и знака "больше". В C++ эти символы рассматриваются как один оператор.Листинг 8.6 иллюстрирует пример обращения к переменным и функциям класса, экземпляркоторогоразмещенвобластидинамическогообмена.Листинг8.6.Доступкданнымобъектавобластидинамическогообмена1://Листинг8.6.2://Доступкданнымобъектавобластидинамическогообмена3:4:#include<iostream.h>5:6:classSimpleCat7:{8:public:9:SimpleCat(){itsAge=2;}10:~SimpleCat(){}11:intGetAge()const{returnitsAge;>12:voidSetAge(intage){itsAge=age;}13:private:14:intitsAge;15:};16:17:intmain()18:{19:SimpleCat*Frisky=newSimpleCat;20:cout<<"Frisky"<<Frisky->GetAge()<<"yearsold\n";21:Frisky->SetAge(5);22:cout<<"Frisky"<<Frisky->GetAge()<<"yearsold\n";23:deleteFrisky;24:return0;25:}Результат:Frisky2yearsoldFrisky5yearsoldАнализ: В строке 19 в области динамического обмена выделяется память для храненияэкземпляра класса SimpleCat.
Конструктор, вызываемый по умолчанию, присваивает новомуобъекту возраст два года. Это значение получено как результат выполнения функции-членаGetAge(),котораявызываетсявстроке20.Посколькумыимеемделосуказателемнаобъект,длявызовафункциииспользуетсяоператоркосвенногообращениякчленукласса(->).Встроке21дляустановкиновогозначениявозраставызываетсяметодSetAge(),аповторныйвызовфункцииGetAge()(строка22)позволяетполучитьэтозначение.ДинамическоеразмещениечленовклассаВкачествечленовклассамогутвыступатьиуказателинаобъекты,размещенныевобластидинамического обмена. В таких случаях выделение памяти для хранения этих объектовосуществляется в конструкторе или в одном из методов класса. Освобождение памятипроисходит,какправило,вдеструкторе(листинг8.7.).Листинг8.7.Указателикакчленыкласса1://Листинг8.7.2://Указателикакчленыкласса3:4:#include<iostream.h>5:6:classSimpleCat7:{8:public:9:SimpleCat();10:~SimpleCat();11:intGetAge()const{return*itsAge;}12:voidSetAge(intage){*itsAge=age;}13:14:intGetWeight()const{return*itsWeight;}15:voidsetWeight(intweight){*itsWeight=weight;}16:17:private:18:int*itsAge:19:int*itsWeight;20:};21:22:SimpleCat::SimpleCat()23:{24:itsAge=newint(2);25:itsWeight=newint(5);26:}27:28:SimpleCat::~SimpleCat()29:{30:deleteitsAge;31:deleteitsWeight;32:}33:34:intmain()35:{36:SimpleCat*Frisky=newSimpleCat;37:cout<<"Frisky"<<Frisky->GetAge()<<"yearsold\n";38:Frisky->SetAge(5);39:cout<<"Frisky"<<Frisky->GetAge()<<"yearsold\n";40:deleteFrisky;41:return0;42:}Результат:Frisky2yearsoldFrisky5yearsoldАнализ:Объявляемкласс,переменными-членамикоторогоявляютсядвауказателянатипint.Вконструкторекласса(строки22—26)выделяетсяпамятьдляхраненияэтихпеременных,азатемимприсваиваютсяначальныезначения.Выделенная под переменные-члены память освобождается в деструкторе (строки 28—32).Послеосвобожденияпамятивдеструктореприсваиватьуказателямнулевыезначениянеимеетсмысла,посколькууничтожаетсяисамэкземпляркласса.Такаяситуацияявляетсяоднимизтехслучаев,когдапослеосвобожденияпамятиуказателюможнонеприсваиватьзначение0.Привыполнениифункции,изкоторойосуществляетсяобращениекпеременнымкласса(вданном случае main()), вы можете и не знать, каким образом выполняется это обращение.
Вылишь вызываете соответствующие методы класса (GetAge() и SetAge()), а все операции спамятьювыполняютсявнутреннимимеханизмамикласса.При уничтожении объекта Frisky (строка 40) вызывается деструктор класса SimpleCat. Вдеструкторепамять,выделеннаяподчленыкласса,освобождается.Еслиодинизчленовклассаявляетсяобъектомдругогоопределенногопользователемкласса,происходитвызовдеструктораэтогокласса.Вопросыиответы:Еслияобъявляюобъекгкласса,хранящийсявстеке,аэтотобъект,всвоюочередь,имеетпеременные-члены,хранящиесявобластидинамическогообмена,токакиечастиобъектабудртнаходитьсявстеке,акакие—вобластидинамическогообмена?#include<iostream.h>classSimpleCat{public:SimpleCat();~SimpleCat();intGetAge()const{return*itsAge;}//другиеметодыprivate:int*itsAge;int*itsWeight;};SimpleCat::SimpleCat(){itsAge=newint(2);itsWeight=newint(5);}SimpleCat::~SimpleCat(){deleteitsAge;deleteitsWeight;}intmain(){SimpleCatFrisky;cout<<"Friskyis"<<Frisky.GetAge()<<"yearsold\n";return0;}В стеке будет находиться локальная переменная Frisky.
Эта переменная содержитдвауказателя, каждый из которых занимает по четыре байта стековой памяти для храненияадресов целочисленных значений, размещенных в области динамического обмена. Такимобразом, объект Frisky займет восемь байтов стековой памяти и восемь— в областидинамическогообмена.Конечно,дляданногопримерадинамическоеразмещениевпамятипеременных-членовнеобязательно. Однако в реальных программах такой способ хранения данных может оказатьсядостаточноэффективным.Важночеткопоставитьзадачу,которуюнеобходиморешить.Помните,чтолюбаяпрограмманачинается с проектирования.
Допустим, например, что требуется создать класс, членомкоторого является объект другого класса, причем второй объект может создаваться еще довозникновения первого и оставаться после его уничтожения. В этом случае доступ ко второмуобъектудолженосуществлятьсятолькопоссыпке,т.е.сиспользованиемуказателя.Допустим, первым объектом является окно, а вторым — документ.
Вполне понятно, чтоокно должно иметь доступ к документу. С другой стороны, продолжительность существованиядокумента никак не контролируется окном. Поэтому для окна важно хранить лишь ссылку наэтотдокумент.Обиспользованииссылокречьидетназатянии9.УказательthisКаждый метод класса имеет скрытый параметр — указатель this. Этот указатель содержитадрес текущего объекта.
Рассмотренные в предыдущем разделе функции GetAge() и SetAge()такжесодержатэтотпараметр.Влистинге8.8приведенпримериспользованияуказателяthisвявномвиде.Листинг8.8.Указательthis1://Листинг8.8.2://Указательthis3:4:#include<iostream.h>5:6:classRectangle7:{8:public:9:Rectangle();10:~Rectangle();11:voidSetLength(intlength){this->itsLength=length;}12:intGetLength()const{returnthis->itsLength;}13:14:voidSetWidth(intwidth){itsWidth=width;}15:intGetWidth()const{returnitsWidth;}16:17:intitsLength18:intitsWidth;20:};21:22:Rectangle::Rectangle()23:{24:itsWidth=5;25:itsLength=10;26:}27:Rectangle::~Rectangle()28:{}29:30:intmain()31:{32:RectangletheRect;33:cout<<"theRectis"<<theRect.GetLength()<<"meterslong.\n";34:cout<<"theRectis"<<theRect.GetWidth()<<"meterswide.\n";35:theRect.SetLength(20);36:theRect.SetWidth(10);37:cout<<"theRectis"<<theRect.GetLength()<<"meterslong.\n";38:cout<<"theRectis"<<theRect.GetWidth()<<"meterswide.\n";39:return0;40:}Результат:theRectis10meterslong.theRects5meterswide.theRectis20meterslong.theRectis10meterswide.Анализ: В функциях SetLength() и GetLength() при обращении к переменным классаRectangle указатель this используется в явном виде.
В функциях SetWidth() и GetWidth() такоеобращение осуществляется неявно. Несмотря на различие в синтаксисе, оба вариантаидентичны.На самом деле роль указателя this намного важнее, чем это может показаться. Посколькуthis является указателем, он содержит адрес текущего объекта и в этой роли может оказатьсядостаточномощныминструментом.Приобсуждениипроблемыперегрузкиоператоров(занятие10)будетприведенонесколькореальных примеров использования указателя this.
В данный момент вам необходимо понимать,чтоthis—этоуказатель,хранящийадресобъекта,вкоторомониспользуется.Памятьдляуказателяthisневыделятсяинеосвобождаетсяпрограммно.Этузадачуберетнасебякомпилятор.Блуждающие,дикиеилизависшиеуказателиБлуждающие указатели являются достаточно распространенной ошибкой программистов,обнаружить которую довольно сложно. Блуждающий (либо, как его еще называют, дикий илизависший)указательвозникает,еслипослеудаленияобъектаоператоромdeleteэтомууказателюне присвоить значение 0.
При попытке использовать такой указатель в дальнейшем результатможет оказаться непредсказуемым. В лучшем случае программа завершится сообщением обошибке.Возникаетситуация,подобнаяследующей.Почтоваяслужбапереехалавновыйофис,авывсе еще продолжаете звонить по ее старому номеру телефона.
Если этот номер будет простоотключен,этонеприведетниккакимнегативнымпоследствиям.Атеперьпредставьтесебе,чтоэтотномеротданкакому-товоенномузаводу...Одним словом, будьте осторожны при использовании указателей, для которых вызывалсяоператорdelete.Указательпо-прежнемубудетсодержатьадресобластипамяти,однакопоэтомуадресу уже могут находиться другие данные. В этом случае обращение по указанному адресуможет привести к аварийному завершению программы.
Или, что еще хуже, программа можетпродолжать работать, а через несколько минут "зависнет". Такая ситуация получила название"мины замедленного действия" и является достаточно серьезной проблемой при написаниипрограмм. Поэтому во избежание неприятностей после освобождения указателя присваивайтеемузначение0.Примервозникновенияблуждающегоуказателяпоказанвлистинге8.9.Листинг8.9.Примервозникновенияблуждающегоуказателя1://Листинг8.9.2://Примервозникновенияблуждающегоуказателя3:typedefunsignedshortintUSHORT;4:#include<iostream.h>5:6:intmain()7:{8:USHORT*pInt=newUSHORT;9:*pInt=10;10:cout<<"*pInt;"<<*pInt<<endl;11:deletepInt;12:13:long*pLong=newlong;14:*pLong=90000;15:cout<<"*pLong:"<<*pLong<<endl;16:17:*pInt=20;//Присвоениеосвобожденномууказателю18:19:cout<<"*pInt:"<<*pInt<<endl;20:cout<<"*pLong:"<<*pLong<<endl;21:deletepLong;22:return0;23:}Результат:*pInt:10*pLong:90000*pInt:20*pLong:65556(Ваширезультатымогутотличатьсяотприведенных.)Анализ:Встроке8переменнаяpIntобъявляетсякакуказательнатипUSH0RT.Выделяетсяпамять для хранения этого типа данных.
В строке 9 по адресу в этом указателе записываетсязначение 10, а в строке 10 оно выводится на экран. Затем память, выделенная для pInt,освобождается оператором delete. После этого указатель оказывается зависшим, илиблуждающим.В сроке 13 объявляется новый указатель (pLong) и ему присваивается адрес выделеннойоператоромnewобластипамяти.Встроке14поадресувуказателезаписываетсячисло90000,австроке15этозначениевыводитсянаэкран.В строке 20 по адресу, занесенному в pInt, записывается значение 20. Однако, вследствиетого что выделенная для этого указателя память была освобождена, такая операция являетсянекорректной.Последствиятакогоприсваиваниямогутоказатьсянепредсказуемыми.Встроке19новоезначениеpIntвыводитсянаэкран.Этозначение,какиожидалось,равно20.Встроке20выводитсязначениеуказателяpLong.Кудивлению,обнаруживаем,чтооноравно65556.Возникаетдвавопроса.1.КакмоглоизменитьсязначениеpLong,еслимыегодаженеиспользовали?2.Кудаделосьчисло20,присвоенноевстроке17?Каквы,наверное,догадались,этидвавопросасвязаны.Приприсвоениивстроке17число20записываетсявобластьпамяти,накоторуюдоэтогоуказывалpInt.Но,таккакпамятьбылаосвобождена в строке 11, компилятор использовал эту область для записи других данных.