Операции над указателями




Программирование с использованием динамической памяти

До настоящего времени мы имели дело с переменными, которые размещаются в памяти согласно вполне определенным правилам. Так, память под глобальные переменные программы выделяется в процессе компиляции, и эти переменные существуют в течение всего времени работы программы. Для размещения локальных переменных, описанных в подпрограмме, память отводится при вызове подпрограммы. При выходе из подпрограммы эта память освобождается, а сами переменные прекращают свое существование, т.е. распределение памяти во всех случаях осуществляется полностью автоматически. Переменные, память под которые выделяются описанным образом, называют статическими. Под эту категорию подпадают все переменные, объявленные в области описаний программных блоков.

Delphi Pascal предоставляет возможность создания новых переменных во время работы программы, сообразуясь с потребностями решаемой задачи, и уничтожения их, когда надобность в них отпадает. Переменные, созданием и уничтожением которых может явно управлять программист, называют динамическими. Динамическая переменная не указывается явно в описаниях переменных, и у нее нет имени. Для доступа к динамическим переменным предназначены переменные специального типа данных, называемого ссылочным, или типом-указателем.

Указатели и операции над ними

Наименьшей адресуемой единицей памяти компьютера, построенного на базе процессоров Intel и их аналогов, является байт (8 бит). Оперативная память представляет собой последовательность нумерованных байтов по 8 бит в каждом. Для обращения к конкретному байту необходимо знать его номер, который называют его физическим адресом.

В Delphi Pascal для работы с адресами используется специальный тип данных – ссылочный тип, или указатель. Данные этого типа – целые числа размером 32 бита. Различают указатели двух типов: типизированные и нетипизированные.

Типизированные указатели содержат адреса, по которым в памяти размещаются данные определенных типов. Используя эти указатели с данными указанных типов, можно выполнять операции, предусмотренные базовым типом.

Например,

Type tpi=^integer; {объявлен тип «указатель на целое»}

Var p:tpi; {объявлена переменная этого типа}

или без предварительного объявления типа:

Var p:^integer; {объявлена переменная типа «указатель на целое»}

Нетипизированные указатели хранят просто адреса, которые не связаны с данными конкретных типов, поэтому их удобно использовать для динамического размещения данных, структура и тип которых меняются в ходе выполнения программы. Для их объявления используют зарезервированное слово pointer. Например:

Var pp:pointer;

Связь указателя с объектом схематично можно изобразить следующим образом
(рис. 1):

 

 


Рис. 1

На этой схеме звездочкой изображено значение указателя р, а стрелка, исходящая из этой звездочки, отражает тот факт, что значением указателя является ссылка на объект, посредством которой он и доступен в программе.

Указатели – единственное исключение из общего правила, согласно которому, все ресурсы перед использованием должны быть описаны. Для них допускаются описания вида:

Type tt=^person; {тип person еще не определен}

person=record {определение типа person}

name:string;

next:tt;

end;

Для указателей, которые не хранят никаких адресов, введена константа «нулевой адрес» с именем nil. Константу nil можно присваивать указателю любого типа. Например:

Const q:^real=nil;

Следует различать понятия «пустой указатель» и «неопределенный указатель». Пустой указатель получается, если указателю присвоено значение nil, неопределенный указатель возникает в случае использования указателя, значение которого еще не определено.

Операции над указателями

Над значениями указателей возможны следующие операции.

Присваивание. При выполнении этой операции указателю присваивается значение другого указателя или nil. Допускается присваивать указателю только значение того же или неопределенного типа, например:

Var p1,p2:^integer; p3:^real; p:pointer;

{допустимые операции}

p1:=p2;

p:=p3;

p1:=p;

p1:=nil;

p:=nil;

{недопустимые операции}

p3:=p2;

p1:=p3;

Получение адреса. Это унарная операция, которая строится из знака операции – символа @ и одного операнда – переменной любого типа. Например:

Var i:integer; p:^integer;

p:=@i; {указатель p будет содержать адрес переменной i }

Это можно наглядно представить в виде схемы (рис. 2):

 

 

Рис. 2

Операция взятия указателя допустима для любых переменных, в том числе массивов, полей записи и т.д. Например, если есть описание

Var A:array[1..10] of integer; p:^integer;

то конструкция @A[i] имеет смысл «указателя на i-ое целое в массиве А» и также может участвовать в присваивании:

p:=@A[i];

Поскольку одномерные массивы содержат последовательные элементы одного типа (т.е. если элемент расположен по адресу Р, тогда следующий элемент расположен по адресу Р + Size (element), то можно это использовать для прохода по массиву в цикле). Для этого надо начать с адреса первого элемента массива, провести с ним необходимые преобразования, а на следующей итерации увеличить указатель так, чтобы он стал указывать на второй элемент и т.д. Для изменения значения указателя можно использовать процедуру увеличения значения переменной порядкового типа inc. Например, Inc (p);

Ссылочные типы можно образовывать от любых типов, поэтому допустимо определение вида «указатель на указатель». Так переменная pp из следующего описания:

Type tpi=^integer;

Var i:integer; p:tpi; pp:^tpi;

в качестве своих возможных значений имеет множество указателей, ссылающих на указатели, которые в свою очередь, ссылаются на целые значения.

p:=@i; pp:=@p;

Связи , возникшие в результате выполнения указанных операторов присваивания, можно изобразить следующей схемой (рис.3):

 

Рис. 3

Доступ к данным по указателю (операция разыменования). Чтобы получить доступ к переменной по указателю, необходимо после переменной – типизированного указателя поставить символ ^. Полученное значение имеет тип, совпадающий с базовым типом указателя. Нетипизированные указатели разыменовывать нельзя. Например:

Var j:integer; p:^integer;

……………

j:=p^; {переменной j присваивается значение целого, расположенного по адресу p }

p^:= p^ + 2; {целое значение, расположенное по адресу p увеличивается на 2}

Еще один пример:

Var n:integer; p:^integer;

……………………….

n:=5; p:=@n; {указатель p содержит адрес переменной n }

p^:=p^+2; {значение переменной, расположенной по адресу p увеличивается на 2}

Writeln(p^:3,n:3); {на экран выведено: 7 7}

…..

Для пояснения результатов присваивания значений ссылочным переменным и переменным с указателем рассмотрим пример.

Var p,d:^integer;

……..

p^:=3; d^:=58; p:=d; d:=nil; …………….

Для наглядности результаты, получаемые при выполнении этих операторов, представлены в виде схем (рис. 4).

 
 

 


Рис. 4

В данном примере после выполнения оператора присваивания p:=d на динамический объект со значением 3 не указывает ни одна ссылка, т.е он стал недоступен для программы. В результате выполнения этого же оператора присваивания не образуется какой-либо новый динамический объект со значением 58, переменная p, будет ссылаться на тот же уже существующий динамический объект, на который ссылается и переменная d.

Важно четко понимать разницу между ссылкой, т.е. значением ссылочной переменной (указателем), и значением динамического объекта, т.е. значением соответствующей переменной с указателем.

Если в предыдущем примере вместо оператора p:=d выполнить оператор p^:=d^, то будет получен следующий результат (рис. 5):

                 
   
 
   
 
     

 


Рис. 5

В случае «указателя на указатель» возможно многократное разыменование. Например, для переменной pp, описанной выше как имеющей тип «указатель на указатель на целое» (рис. 3), допустима конструкция pp^^, которая имеет статус целой переменной.

Разыменование считается некорректным, если указатель имеет значение nil. В этом случае не существует переменной, на которую ссылается указатель, поэтому операторы вида:

p:=nil;

p^:=2;

являются недопустимыми.

Следует иметь в виду, что указателю нельзя присвоить значение динамической переменной, а значение динамической переменной нельзя присвоить указателю. Например, если переменные Р1 и Р2 описаны как ^integer, то приведенные ниже операторы присваивания являются ошибочными:

P1:=5*P2^; P1^:=P2;

Операции отношения. Допускаются только операции проверки равенства (=) и неравенства (<>). Например:

Sign:= p1=p2 {переменная логического типа sign получает значение true или false

в зависимости от значений указателей}

или

If p <> nil then …{проверка адреса}

Функции, работающие с указателями. Для работы с указателями предусмотрены стандартные функции, облегчающие и упрощающие выполнение часто встречающихся операций:

1. Функция Add(var X:Type):pointer – возвращает адрес объекта Х, в качестве которого может быть указано имя переменной, функции, процедуры. Выполняет те же действия, что и операция @.

2. Функция Prt(Adress:Integer):Pointer – преобразует целое число в указатель.



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2017-04-04 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: