246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 35
Текст из файла (страница 35)
С помощьюоперации разыменования значение, записанное по адресу, хранящемуся в указателе pAge,выводится на экран (строка 13). Как видим, полученный результат совпадает со значениемпеременной myAge. В строке 15 переменной, адрес которой записан в pAge, присваиваетсязначение 7. После выполнения такой операции переменная myAge будет содержать значение 7.Убедитьсявэтомможнопослевыводаэтихзначений(строки16,17).В строке 19 значение myAge опять изменяется. Теперь этой переменной присваиваетсячисло 9.
Затем в строках 20 и 21 мы обращаемся к этому значению непосредственно (черезпеременную)ипутемразыменованияуказателянанее.Использованиеадреса,хранящегосявуказателеПриработесуказателямивбольшинствеслучаевнеприходитсяиметьделосозначениямиадресов, записанных в указателях. В предыдущих разделах отмечалось, что после присвоенияуказателю адреса переменной значением указателя будет именно этот адрес. Почему бы непроверить это утверждение? Для этого можно воспользоваться программой, приведенной влистинге8.3.Листинг8.3.Чтожезаписановуказателе?1://Листинг8.3.Чтожехранитсявуказателе?2:3:#include<iostream.h>4:5:6:intmain()7:{8:unsignedshortintmyAge=5,yourAge=10;9:unsignedshortint*pAge=&myAge;//Указатель10:cout<<"myAge:\t"<<myAge<<"\tyourAge:\t"<<yourAge<<"\n";11:cout<<"&myAge:\t"<<&myAge<<"\t&yourAge;\t"<<&yourAge<<"\n";12:cout<<"pAge;\t"<<pAge<<"\n";13:cout<<"*pAge:\t"<<*pAge<<"\n";14:pAge=&yourAge;//переприсвоениеуказателя15:cout<<"myAge:\t"<<myAge<<"\tyourAge;\t"<<yourAge<<"\n";16:cout<<"&myAge:\t"<<&myAge<<"\t&yourAge:\t"<<&yourAge<<"\n";17:cout<<"pAge:\t"<<pAge<<"\n";18:cout<<"*pAge:\t"<<*pAge<<"\n";19:cout<<"&pAge:\t"<<&pAge<<"\n";20:return0;21:}Результат:myAge:5yourAge:10&myAge:0x355C&yourAge:0x355EpAge:0x355C*pAge:5myAge:5yourAge:10&myAge:0x355C&yourAge:0x355EpAge:0x355E*pAge:10&pAge:0x355A(Ваширезультатымогутотличатьсяотприведенных.)Анализ: В строке 8 объявляются две переменные типа unsigned short — myAge и yourAge.Далее, в строке 9, объявляется указатель на этот тип (pAge).
Этому указателю присваиваетсяадреспеременнойmyAge.В строках 10 и 11 значения и адреса переменных pAge и myAge выводятся на экран.ОбращениекзначениюпеременнойmyAgeпутемразыменованияуказателяpAgeвыполняетсявстроке 13. Перед тем как перейти к дальнейшему изучению материала, подумайте, все ли вампонятно в рассмотренном примере. Еще раз проанализируйте текст программы и результат еевыполнения.Встроке14указателюpAgeприсваиваетсяадреспеременнойyourAge.Послеэтогонаэкранвыводятсяновыезначенияиадресапеременных.Проанализироваврезультатпрограммы,можноубедиться,чтоуказательpAgeдействительносодержитадреспеременнойyoutAge,аспомощьюразыменованияэтогоуказателяможнополучитьеезначение.Строка19выводитнаэкранзначениеадресауказателяpAge.Каклюбаядругаяпеременная,указатель также имеет адрес, значение которого может храниться в другом указателе.
Охранениивуказателеадресадругогоуказателяречьпойдетнесколькопозже.Рекомендуется:Используйте оператор разыменовывания (*) для получения доступа кданным,сохраненнымпоадресу,содержащемусявуказателе.Инициализируйтеуказательнулевымзначениемприобъявлении,еслизаранеенеизвестно,дляуказаниянакакуюпеременнуюонбудетиспользоваться.Помните о разнице между адресом в указателе и значением переменной, на которуюссылаетсяэтотуказатель.ИспользованиеуказателейЧтобы объявить указатель, запишите вначале тип переменной или объекта, на которыйбудет ссылаться этот указатель, затем поместите символ звездочки (*), а за ним — имяновогоуказателя,например:unsignedshortint*pPointer=0;Чтобы присвоить указателю адрес переменной, установите перед именем переменнойоператорадреса(&),каквследующемпримере:unsignedshortinttheVariable=5;unsignedshortint*pPointer=&theVariable;Чтобы разыменовать указатель, установите перед его именем операторразыменовывания(*):unsignedshortinttheValue=*pPointerДлячегонужныуказателиВпредыдущихразделахмыдетальнорассмотрелипроцедуруприсвоенияуказателюадресадругойпеременной.Однаконапрактикетакоеиспользованиеуказателейвстречаетсядостаточноредко.
К тому же, зачем задействовать еще и указатель, если значение уже хранится в другойпеременной? Рассмотренные выше примеры приведены только для демонстрации механизмаработы указателей. Теперь, после описания синтаксиса, используемого в C++ для работы суказателями, можно переходить к более профессиональному их применению.
Наиболее частоуказателиприменяютсявследующихслучаях:•дляразмещенияданныхвсвободныхобластяхпамятиидоступакним;•длядоступакпеременнымифункциямклассов;•дляпередачипараметроввфункциипоссылке.Оставшаяся часть главы посвящена динамическому управлению данными и операциям спеременнымиифункциямиклассов.ПамятьстековаяидинамическираспределяемаяЕсливыпомните,назанятии5приводилисьусловноеразделениепамятинапятьобластей:•областьглобальныхпеременных;•свободная,илидинамическираспределяемаяпамять;•регистроваяпамять(регистры);•сегментыпрограммы;•стековаяпамять.Локальные переменные и параметры функций размещаются в стековой памяти.Программный код хранится в сегментах, глобальные переменные — в области глобальныхпеременных.
Регистровая память предназначена для хранения внутренних служебных данныхпрограммы, таких как адрес вершины стека или адрес команды. Остальная часть памятисоставляеттакназываемуюсвободнуюпамять—областьпамяти,динамическираспределяемуюмеждуразличнымиобъектами.Особенностьюлокальныхпеременныхявляетсято,чтопослевыходаизфункции,вкоторойони были объявлены, память, выделенная для их хранения, освобождается, а значенияпеременныхуничтожаются.Глобальныепеременныепозволяютчастичнорешитьэтупроблемуценойнеограниченногодоступа к ним из любой точки программы, что значительно усложняет восприятие текстапрограммы.Использованиединамическойпамятиполностьюрешаетобепроблемы.Чтобы понять, что же такое динамическая память, попытайтесь представить областьпамяти,разделеннуюнамножествопронумерованныхячеек,вкоторыхзаписанаинформация.Вотличиеотстекапеременных,ячейкамсвободнойпамятинельзяприсвоитьимя.Доступкнимосуществляетсяпосредствомуказателя,хранящегоадреснужнойячейки.Чтобы лучше понять изложенное выше, рассмотрим пример.
Допустим, вам дали номертелефонаслужбызаказовтоварапопочте.Придядомой,вызанеслиэтотномервпамятьвашеготелефона, а листок бумаги, на котором он был записан, выбросили. Нажимая на кнопкутелефона, вы соединяетесь со службой заказа. Для вас не имеет значения номер и адрес этойслужбы,посколькувыужеполучилидоступкинтересующейвасинформации.Службазаказоввданном случае является моделью динамической памяти. Вы не знаете, где именно находитсянужная вам информация, но знаете, как ее получить.
Для обращения к значению используетсяего адрес, роль которого играет телефонный номер. Причем помнить адрес (или номер) необязательно—достаточнолишьзаписатьегозначениевуказатель(илителефон).Послеэтого,используяуказатель,можноизвлечьнужноезначение,даженезнаяместоегорасположения.Что касается стека переменных, то по завершении работы функции он очищается. Врезультате все локальные переменные оказываются вне области видимости и их значенияуничтожаются. В отличие от стека, динамическая память не очищается до завершения работыпрограммы,поэтомувтакомслучаеосвобождениемпамятидолжензаниматьсяпрограммист.Важнымпреимуществомдинамическойпамятиявляетсято,чтовыделеннаявнейоблаятьпамяти не может использоваться до тех пор, пока явно не будет освобождена.
Поэтому, еслипамять в динамической области выделяется во время работы функции, ее можно будетиспользоватьдажепослезавершенияработы.Еще одним преимуществом динамического выделения памяти перед использованиемглобальныхпеременныхявляетсято,чтодоступкданнымможнополучитьтолькоизфункций,вкоторых есть доступ к указателю, хранящему нужный адрес. Такой способ доступа позволяетжестко контролировать характер манипулирования данными, а также избегать нежелательногоилислучайногоихизменения.Для работы с данными описанным способом прежде всего нужно создать указатель наячейкидинамическойобластипамяти.Отом,какэтосделать,читайтевследующемразделе.ОператорnewДля выделения памяти в области динамического распределения используется ключевоеслово new.
После new следует указать тип объекта, который будет размещаться в памяти. Этонеобходимо для определения размера области памяти, требуемой для хранения объекта.Написав, например, new unsigned short int, мы выделим два байта памяти, а строка new longдинамическивыделитчетыребайта.Вкачестверезультатаоператорnewвозвращаетадресвыделенногофрагментапамяти.Этотадрес должен присваиваться указателю. Например, для выделения памяти в областидинамическогообменапеременнойтипаunsignedshortможноиспользоватьтакуюзапись:unsignedshortint*pPointer;pPointer=newunsignedshortint;Иливыполнитьтежедействия,новоднойсороке:unsignedshortint*pPointer=newunsignedshortint;В каждом случае указатель pPointer будет указывать на ячейку памяти в областидинамического обмена, содержащую значение типа unsigned short.
Теперь pPointer можноиспользовать как любой другой указатель на переменную этого типа. Чтобы занести ввыделеннуюобластьпамятикакое-нибудьзначение,напишитетакуюстроку:*pPointer=72;Эта строка означает следующее: "записать число 72 в память по адресу, хранящемуся вpPointer".Ввиду того что память является ограниченным ресурсом, попытка выделения памятиоператоромnewможетоказатьсянеудачной.Вэтомслучаевозникнетисключительнаяситуация,котораярассматриваетсяназанятии20.ОператорdeleteКогда память, выделенная под переменную, больше не нужна, ее следует освободить.Делаетсяэтоспомощьюоператораdelete,послекоторогозаписываетсяимяуказателя.Операторdelete освобождает область памяти, определенную указателем.