В отличие от обычных переменных в ссылочных находятся не сами данные, а только адреса ячеек, в которых эти данные хранятся. Поэтому под ссылочные переменные выделяется одинаковое (4 байта) количество памяти независимо от типа данных, на которые указатель ссылается.

Память под данные выделяется только после использования оператора New, который имеет две формы — процедуры и функции. Изначально указатель ссылается в неопределенное место памяти, что может привести к грубым ошибкам при попытке доступа к данным, на которые он указывает. Ссылочной переменной любого типа может быть присвоено значение nil, означающее, что он никуда не указывает. Попытка доступа к данным через такой указатель также вызовет ошибку, однако перед вызовом указатель может быть проверен на то, не равен ли он nil.

Рассмотрим следующий пример:

procedure PtrDemo1;

var p1,p2:^Integer;

a, b:Integer;

begin

a:=1;

b:=2;

New(p1); {в "куче"(heap) динамически выделена ячейка — область под

"безымянную" переменную типа Integer, указатель p1 настроен

на эту ячейку}

p1^:=a+5; {в эту ячейку записывается значение 6}

p2:=p1; {на эту ячейку теперь указывают и p1, и p2, в ней - число 6}

New(p1); {динамически выделяется еще одна ячейка, p1 переключается на нее,

а p2 остался настроен на прежнюю}

b:=p2^-1; {переменной b присваивается значение, содержащееся в первой

ячейке, уменьшенное на 1,то есть 5}

{значение p1^не определено, p2^= 6}

dispose(p1); {высвобождаем память, выделенную под ячейку2}

dispose(p2); {высвобождаем память, выделенную под ячейку1}

НЕ нашли? Не то? Что вы ищете?

end;

В данном примере видно, что указатель можно использовать не только для динамического выделения памяти, как это происходит с p1 , но и для сохранения программой связи с выделенной ячейкой, для чего используется p2. Если бы мы вовремя не настроили p2 на первую динамически выделенную ячейку памяти, при втором вызове New(p1) мы бы потеряли к ней доступ, указатель p1 "перещелкнулся" бы на вторую динамически выделенную ячейку, и образовывался бы так называемый "мусор" (garbage) в памяти: область, помеченная операционной системой как занятая задачей, но самой задаче уже недоступная.

Следующий тонкий момент при работе с указателями — высвобождение динамически выделенной памяти. К окончанию работы программы вся динамически выделенная память должна быть "возвращена обратно". Это делается с помощью оператора dispose, который "уничтожает" (т. е. высвобождает) ячейку, на которую ссылается указатель. Сам указатель при этом не уничтожается и может быть в дальнейшем использован!

Часто встречающаяся в программах ошибка — использование указателя доступа к данным после того, как ячейка на которую он ссылался, высвобождена. Этот случай аналогичен попытке использования неинициализированных (т. е. "не настроенных" на конкретную ячейку) указателей и часто кончается "зависанием" компьютера, такие указатели часто называют "висящими" ("dangling pointer").

Пример работы с указателями:

program PtrDemo2;

type tpInt=^Integer; {тип "ссылочный на Integer"}

var p1,p2:tpInt;

...

begin

...

New(p1); {выделили ячейку1 и настроили на нее p1}

...

New(p2); {выделили ячейку2 и настроили на нее p2}

...

dispose(p2); {высвободили ячейку2, но p2 "жив"}

p2:=p1; {настроили p2 на ячейку1}

New(p1); {выделили ячейку3 и настроили на нее p1}

...

dispose(p1); {высвободили ячейку3}

...

dispose(p2); {высвободили ячейку1}

end.

Следует помнить, что каждому New (вызванному, к примеру, с указателем p1) должен соответствовать свой dispose(вызванный, к примеру, с p2), и чтобы указатели p1 и p2 указывали бы на одну и ту же ячейку. Попытка лишний раз сделать dispose, т. е. повторно высвободить один и тот же участок памяти, обычно сразу заканчивается аварийной остановкой программы. Иногда указатели используются для получения ссылки на переменную, процедуру или функццию. Для этого в Object Pascal имеется функция addr (вместо нее можно использовать символ @ при соответствующей установке опции компилятора).

2.12. Процедуры и функции. Формальные и фактические параметры. Передача параметров по ссылке, по значению и через указатель.

Процедуры по своей структуре напоминают независимую программу, вложенную в основную программу. Главное их отличие в том, что они, как правило, имеют параметры, передаваемые из внешнего программного блока кода внутрь процедуры и/или обратно. Кроме того, что после окончания описания процедуры становится не точка, а точка с запятой. Процедуры можно использовать в других процедурах или в поле программы. Допускается вложение одной процедуры в другую. Возможная степень вложения зависит от реализации компилятора PASCAL и, как правило, бывает не менее7.

program factor1;

procedure factorial(var x:extended; n:integer); {заголовок

процедуры}

var i:integer; {раздел описания переменных}

begin

x:=1; {тело процедуры}

for i:=1 to n do x:=x*i; {тело процедуры}

end;

var x, y,z:extended; {раздел описания переменных программы}

i, k,l:integer; {раздел описания переменных программы}

begin {начало тела программы}

i:=7; k:=2; l:=100;

factorial(x, i);

factorial(y,(k+l)/2-5*i);

{factorial (z+1,l); — так нельзя!}

factorial(z, l); {так можно}

end. {конец тела программы}

Иногда (как в данном случае) гораздо удобнее использовать процедуру-функцию:

program factor2;

function factorial(n:integer):extended; {заголовок функции с

формальным параметром n}

var i:integer; {локальная переменная функции factorial}

x:extended; {локальная переменная функции factorial}

begin

x:=1; {тело функции}

for i:=1 to n do x:=x*i; {тело функции}

factorial:=x;

end;

var x, y,z:extended; {глобальные переменные программы}

i, k,l:integer; {глобальные переменные программы}

{эти x и y совсем не то, что локальные в factorial}

begin

i:=7; k:=2; l:=100;

x:=factorial(i); {вызов1}

y:=factorial((k+l)/2-5*i); {вызов2}

z:=factorial(l); {вызов3}

end.

При описании процедуры (или функции) переменные, описанные в заголовке процедуры — это формальные параметры (в примере fact2 это n для функции factorial). В теле программы при вызове процедуры на соответствующее место надо подставлять фактические (это строка i в вызове1, (k+l)/2-5*i в вызове2, l в вызове3). Если перед формальным параметром стоит зарезервированное слово var — это передача параметра "по ссылке". В этом случае вместо формального параметра подставляется фактическая переменная соответствующего типа, и если в процессе работы процедуры (или функции) должно меняться значение формального параметра, то реально меняется значение этой переменной. После окончания работы процедуры эта переменная сохраняет изменившееся значение. Т. о. через такой параметр можно передавать значения как "внутрь" процедуры, так и "наружу" из нее. В ряде современных языков программирования такой способ передачи параметров описывают как сочетание in ("внутрь") и out ("наружу"). Смысл термина "передача параметра по имени" следующий: при работе процедуры вместо имени формального параметра подставляется имя фактического, и именно с ним проделывается все то, что описано в процедуре.

Второй способ передачи параметров в процедуру или функцию — "по значению", если перед формальным параметром не стоит зарезервированное слово var. В этом случае в программе в качестве фактического параметра можно писать произвольное выражение соответствующего типа (совместимого по присваиванию). Для формального параметра при вызове процедуры создается временная переменная данного типа. Ей присваивается значение выражения, вычисляемое в процессе присваивания (вот почему говорят о передаче параметра "по значению"). Затем с этой переменной выполняются все действия, описанные в процедуре для формального параметра. Перед завершением процедуры временная переменная (ее называют локальной) автоматически уничтожается, поэтому с ее помощью ничего нельзя передать "наружу". Другими словами — это передача параметра типа in. В некоторых других языках программирования (например, ADA), возможна передача параметра типа out. При этом в процедуре такой параметр может стоять только в левой части присваивания, и его нельзя использовать в выражениях в правой части присваивания или в фактических параметрах процедур.

Передача параметра через указатель – это явная передача адреса ячейки. Единственное отличие от передачи по ссылке заключается в том, что для доступа к ячейке внутри подпрограммы требуется ставить символ ^ после имени указателя.

2.13. Локальные и глобальные переменные. Побочный эффект функции. Вложенность процедур и функций. Правила видимости.

В приведенных выше примерах часть переменных описана в блоке описания переменных программы. Такие переменные называются глобальными. Они существуют в любой момент выполнения программы. Другая часть переменных описана внутри процедуры или функции. Это — локальные переменные. В отличие от глобальных они создаются не в момент запуска программы, а в момент каждого очередного вызова процедуры, и после окончания работы процедуры уничтожаются. (Для повышения эффективности работы часть процедур и локальных переменных не уничтожается полностью, а хранится в стеке. Это дает возможность быстро вызывать их. Однако с точки зрения программиста это совершенно не меняет ситуации с их "существованием" и правилами видимости). Поэтому эти переменные и называются локальными: они существуют только внутри соответствующей процедуры. "Снаружи" процедуры локальные переменные этой процедуры не имеют смысла, и попытка вызова такой переменной приведет к сообщению компилятором об ошибке. Говорят, что локальная переменная невидима вне своей процедуры. С другой стороны, глобальные переменные видимы в любом месте программы т. е. их можно вызывать и внутри процедур и функций. Поэтому их называют глобальными переменными. Глобальные переменные иногда используются для передачи данных внутрь процедуры не через список параметров в заголовке процедуры, а "напрямую". Как правило этот путь нельзя считать хорошим, т. к. обычно он не соответствует принципам структурного программирования. С другой стороны, в ряде случаев бывает целесообразно менять значение некоторых нелокальных переменных в процессе работы процедуры, т. е. получать так называемый побочный эффект работы процедуры. Этот способ часто используется в объектном программировании для приведения в соответствие значений нескольких взаимно зависимых параметров.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16