246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 36
Текст из файла (страница 36)
Необходимо помнить, чтоуказатель, в отличие от области памяти, на которую он указывает, является локальнойпеременной. Поэтому после выхода из функции, в которой он был объявлен, этот указательстанетнедоступным.Однакообластьпамяти,выделеннаяоператоромnew,накоторуюсослалсяуказатель, при этом не освобождается.
В результате часть памяти окажется недоступной.Программисты называют такую ситуацию утечкой памяти. Такое название полностьюсоответствует действительности, поскольку до завершения работы программы эту памятьиспользоватьнельзя,онакакбы"вытекает"извашегокомпьютера.Чтобыосвободитьвыделеннуюпамять,используйтеключевоесловоdelete,например:deletepPointer;Насамомделеприэтомпроисходитнеудалениеуказателя,аосвобождениеобластипамятипо адресу, записанному в нем.
При освобождении выделенной памяти с самим указателемничего не происходит и ему можно присвоить другой адрес. Листинг 8.4 показывает, каквыделитьпамятьдлядинамическойпеременной,использоватьее,азатемосвободить.Предупреждение:Когда оператор delete применяется к указателю, происходитосвобождение области динамической памяти, на которую этот указатель ссылается.Повторное применение оператора delete к этому же указателю приведет к зависаниюпрограммы. Рекомендуется при освобождении области динамической памяти присваиватьсвязанному с ней указателю нулевое значение. Вызов оператора delete для нулевого указателяпройдетсовершеннобезболезненнодляпрограммы,например:Animal*pDog=newAnimal;deletepDog;//освобождениединамическойпамятиpDog=0//присвоениеуказателюнулевогозначения//...deletepDog;//бессмысленная,носовершеннобезвреднаястрокаЛистинг8.4.Выделение,использованиеиосвобождениединамическойпамяти1;//Листинг8,4,2;//Выделение,использованиеиосвобождениединамическойпамяти3;4:#include<iostream.h>5:intmain()6:{7:intlocalVariable=5;8:int*pLocal=&localVariable;9:int*pHeap=newint;10:рНеар=7;11:cout<<"localVariable:"<<localVariable<<"\n";12:cout<<"*pLocal:"<<*pLocal<<"\n";13:cout<<"*pHeap;"<<*pHeap<<"\n";14:deleteрНеар;15:рНеар=newint;16:*pHeap=9;17:cout<<"*pHeap:"<<*pHeap<<"\n";18:deleteрНеар;19:return0;20:}Результат:localVariable:5*pLocal:5*pHeap:7*pHeap:9Анализ: В строке 7 объявляется и инициализируется локальная переменная localVariable.Затем объявляется указатель, которому присваивается адрес этой переменной (строка 8).
Встроке9выделяетсяпамятьдляпеременнойтипаintиадресвыделеннойобластипомещаетсявуказательрНеар.Записавпоадресу,содержащемусяврНеар,значение7,можноудостоверитьсяв том, что память была выделена корректно (строка 10). Если бы память под переменную небылавыделена,топривыполненииэтойстрокипоявилосьбысообщениеобошибке.Чтобы не перегружать примеры излишней информацией, мы опускаем всякого родапроверки.Однаковоизбежаниеаварийногозавершенияпрограммыприрешенииреальныхзадачтакойконтрольобязательнодолженвыполняться.Встроке10,послевыделенияпамяти,поадресувуказателезаписываетсязначение7.Затемв строках 11 и 12 значения локальной переменной и указателя pLocal выводятся на экран.Вполне понятно, почему эти значения равны.
Далее, в строке 13, выводится значение,записанное по адресу, хранящемуся в указателе рНеар. Таким образом, подтверждается, чтозначение,присвоенноевстроке10,действительнодоступнодляиспользования.Освобождение области динамической памяти, выделенной в строке 9, осуществляетсяоператором delete в строке 14. Освобожденная память становится доступной для дальнейшегоиспользования, и ее связь с указателем разрывается.
После этого указатель рНеар можетиспользоваться для хранения нового адреса. В строках 15 и 16 выполняется повторноевыделение памяти и запись значения по соответствующему адресу. Затем в строке 17 этозначениевыводитсянаэкран,послечегопамятьосвобождается.Вообще говоря, строка 18 не является обязательной, так как после завершения работыпрограммы вся выделенная в ней память автоматически освобождается. Однако явноеосвобождение памяти считается как бы правилом хорошего тона в программировании.
Крометого,этоможетоказатьсяполезнымприредактированиипрограммы.ЧтотакоеутечкапамятиПри невнимательной работе с указателями может возникнуть эффект так называемойутечки памяти. Это происходит, если указателю присваивается новое значение, а память, накоторуюонссылался,неосвобождается.Нижепоказанпримертакойситуации.1:unsignedshortint*pPointer=newunsignedshortint;2:*pPointer=72;3:deletepPointer;4:pPointer=newunsignedshortint;5:*pPointer=84;В строке 1 объявляется указатель и выделяется память для хранения переменной типаunsignedshortint.Вследующейстрокеввыделеннуюобластьзаписываетсязначение72.Затемвстроке3указателюприсваиваетсяадресдругойобластипамяти,вкоторуюзаписываетсячисло84(строка4).Послевыполнениятакихоперацийпамять,содержащаязначение72,оказываетсянедоступной,посколькууказателюнаэтуобластьбылоприсвоеноновоезначение.Врезультатеневозможно ни использовать, ни освободить зарезервированную память до завершенияпрограммы.Правильнеебылобынаписатьследующее:1:unsignedshortint*pPointer=newunsignedshortint;2:*pPointer=72;3:pPointer=newunsignedshortint;4:*pPointer=84;Вэтомслучаепамять,выделеннаяподпеременную,корректноосвобождается(строка3).Примечание:Каждыйраз,когдавпрограммеиспользуетсяоператорnew,занимдолженследовать оператор delete.
Очень важно следить, какой указатель ссылается на выделеннуюобластьдинамическойпамяти,ивовремяосвобождатьее.РазмещениеобъектоввобластидинамическойпамятиАналогично созданию указателя на целочисленный тип, можно динамически размещать впамяти любые объекты. Например, если вы объявили объект класса Cat, для манипулированияэтим объектом можно создать указатель, в котором будет храниться его адрес, — ситуация,абсолютно аналогичная размещению переменных в стеке. Синтаксис этой операции такой же,какидляцелочисленныхтипов:Cat*pCat=newCat;Вданномслучаеиспользованиеоператораnewвызываетконструкторклассапоумолчанию,т.е. конструктор, использующийся без параметров.
Важно помнить, что при создании объектакласса конструктор вызывается всегда, независимо от того, размещается объект в стеке или вобластидинамическогообмена.УдалениеобъектовПри использовании оператора delete, за которым следует идентификатор указателя наобъект,вызываетсядеструкторсоответствующегокласса.Этопроисходитещедоосвобожденияпамяти и возвращения ее в область динамического обмена. В деструкторе, как правило,освобождаетсявсяпамять,занимаемаяобъектомкласса.Примердинамическогоразмещенияиудаленияобъектовпоказанвлистинге8.5.Листинг8.5.Размещениеиудалениеобъектоввобластидинамическогообмена1://Листинг8.5.2://Размещениеиудалениеобъектоввобластидинамическогообмена3:4:#include<iostream.h>5:6:classSimpleCat7:{8:public:9:SimpleCat();10:~SimpleCat();11:private:12:intitsAge;13:};14:15:SimpleCat::SimpleCat()16:{17:cout<<"Constructorcalled.\n";18:itsAge=1;19:}20:21:SimpleCat::~SimpleCat()22:{23:cout<<"Destructorcalled.\n";24:}25:26:intmain()27:{28:cout<<"SimpleCatFrisky...\n";29:SimpleCatFrisky;30:cout<<"SimpleCat*pRags=newSimpleCat...\n";31:SimpleCat*pRags=newSimpleCat;32:cout<<"deletepRags...\n";33:deletepRags;34:cout<<"Exiting,watchFriskygo...\n";35:return0;36:}Результат:SimpleCatFrisky...Constructorcalled.SimpleCat*pRags=newSimpleCat..Constructorcalled.deletepRags...Destructorcalled.Exiting,watchFriskygo...Destructorcalled.Анализ: В строках 6—13 приведено описанИе простейшего класса SimpleCat.
Описаниеконструктора класса находится в строке 9, а его тело — в строках 15-19. Деструктор описан встроке10,еготело—встроках21-24.В строке 29 создается экземпляр описанного класса, который размешается в стеке. ПриэтомпроисходитнеявныйвызовконструктораклассаSimpleCat.Второйобъектклассасоздаетсявстроке31.ДляегохранениядинамическивыделяетсяпамятьиадресзаписываетсявуказательpRags.Вэтомслучаетакжевызываетсяконструктор.ДеструкторклассаSimpleCatвызываетсявстроке33какрезультатпримененияоператораdeleteкуказателюpRags.Привыходеизфункциипеременная Frisky оказывается за пределами области видимости и для нее также вызываетсядеструктор.ДоступкчленамклассаДля локальных переменных, являющихся объектами класса, доступ к членам классаосуществляетсяспомощьюоператорапрямогодоступа(.).Дляэкземпляровкласса,созданныхдинамически, оператор прямого доступа применяется для объектов, полученныхразыменованиемуказателя.Например,длявызовафункции-членаGetAgeнужнонаписать:(*pRags).GetAge();Скобки указывают на то, что оператор разыменования должен выполняться еще до вызовафункцииGetAge().Такаяконструкцияможетоказатьсядостаточногромоздкой.Решитьэтупроблемупозволяетспециальный оператор косвенного обращения к члену класса, по написанию напоминающийстрелку (->).