Скачать 127.11 Kb.
|
Конспект по теме: «Указатели и динамические переменные» Учитель информатики Батракова Л.В. Ранее мы изучали статические типы данных, память под которые выделялась во время компиляции программы, она не должна была превышать 64 килобайта и количество таких данных не изменялось во время выполнения программы. Однако существует ряд задач, где статические структуры неэффективны. В языке Паскаль имеются средства создания динамических структур данных, которые позволяют во время выполнения программы:
Определение: Динамические переменные - это переменные, память под которые выделяется во время выполнения программы. Для получения ясного представления о динамических переменных надо рассмотреть структуру памяти во время выполнения программы на языке Паскаль: Основным механизмом для организации динамических данных является выделение в специальной области памяти, называемой « heap-областью» или «кучей», непрерывного участка подходящего размера и сохранения адреса начала этого участка в специальной переменной, называемой, ссылочной переменной или ссылкой или указателем. Определение: Ссылочная переменная (указатель) – переменная, предназначенная для хранения адреса переменной, расположенной в динамической области памяти (heap – области или «куче»). Сам указатель располагается в статической памяти. Ссылочная переменная Динамическая переменная (указатель) Каждый указатель размещается в сегменте данных или в стеке (если он объявлен в подпрограмме) и занимает там 4 байта (2 слова: номер сегмента и смещение *). Это дополнительные “накладные расходы’ памяти. Поэтому обычные переменные очень редко создают и уничтожают динамически, оставляя эту возможность для больших совокупностей данных. Чем больше размер динамической переменной, тем меньше доля накладных расходов. Например, при хранении в динамической памяти массивов больших размеров лишние 4 байта, затраченные на указатель, несущественны. *Пояснение: Адресуемое пространство памяти организовано сегментами: последовательными блоками памяти по 64 Кб каждый. Если известен сегмент, то дальнейшее уточнение места объекта в памяти происходит по смещению, т.е. номеру байта от начала сегмента. Любая ячейка памяти определяется парой чисел сегмент: смещение. Описание ссылочной переменной (указателя) Формат описания ссылочного типа данных: Type <тип указателя> = ^ <идентификатор базового типа>, то есть указатель связан с некоторым типом данных. Такие указатели называются типизированными. Базовый тип может быть любым, кроме файлового. Пример описания переменных ссылочного типа: Type DinMas=array[1..10000] of real; p1=^integer; p2=^real; P3=^DinMas; Var A,B,C:p1; X,Y,Z:p2; M:p3; P:^char; T:^integer; Cсылочные переменные A, B, C,T указывают на динамические объекты целого типа, X,Y,Z - вещественного, P – символьного, а M - на массив. Значением ссылочной переменной является адрес в динамически выделенной памяти, где хранится объект этого типа. Однако в Турбо Паскале можно объявлять указатель и не связывать его с конкретным типом данных. Такой указатель называется нетипизированным. Для его объявления служит стандартный тип pointer. Структура и тип таких данных могут меняться во время выполнения программы. Для обращения к ссылочной переменной используют запись “ A^ ”, что означает: ”идти по адресу, хранящемуся в A”. Рассмотрим на примере отличие ссылочной переменной от статической. Пусть дано описание: Var J:^integer; I:integer; После выполнения операций: J ^:=15; I:=15; Операции, определенные над ссылочной переменной (указателем)
p:=nil;
p:=q;
p:=@x; p:= addr(х); В языке Pascal есть специальная операция получения указателя на переменную (или процедуру) — она обозначается как @. Имеется также эквивалентная ей функция addr.
Два значения ссылочного типа равны, если они оба есть nil, либо указывают на один и тот же динамический объект. Во всех остальных случаях имеет место неравенство. Рассмотрим пример. Пусть в программе описаны следующие указатели: Var q, p: ^Integer; Если: p:=Nil; q:= Nil; то условие p=q будет истинным. Можно указатели использовать в условных операторах: if p=q then… или if p=nil then... {проверка указателя на пустоту} Процедуры и функции, определенные над ссылочными переменными:
Внимание! В PascalABC используются только первые четыре процедуры (new, Dispose, Getmem, Freemem). При работе с указателями обязательны два этапа:
Динамические переменные Под динамически размещаемую переменную во время выполнения программы, память выделяется с помощью вызова процедуры New(P); Р - указатель. Данный вызов выполняет два следующих действия: •обращается к операционной системе для выделения области памяти, необходимой для размещения переменной базового типа; •устанавливает начальный адрес этой области памяти в качестве значения переменной-указателя. Вызов New можно выполнять в любом месте программы любое число раз, в том числе – циклически. Это позволяет при выполнении программы динамически создавать любое необходимое число переменных базового типа. После выделения области памяти в неё можно записать необходимое значение, используя ссылочную переменную и символ ^ после ее имени – это называется операцией разыменования. Определение: Разыменованием ссылки называется получение объекта, на который указывает эта ссылка. Комбинация имени переменной-указателя и символа ^ превращает указатель в переменную базового типа. С такой переменной можно выполнять все те операции, которые разрешены для базового типа. Например: Type Mas1 = Array[1..100] Of Integer; Var P1 : ^Integer; P2 : ^String; Pm : ^Mas1; Здесь P1 — указатель на динамическую величину целого типа; P2 — указатель на динамическую величину строкового типа; Pm — указатель на динамический массив, тип которого задан в разделе Type. Память под динамическую величину, связанную с указателем, выделяется в результате выполнения стандартной процедуры NEW: NEW(P1); NEW(P2); NEW(Pm); После их выполнения в динамической памяти оказывается выделенным место под три величины (две скалярные и один массив), которые имеют идентификаторы: P1^, P2^, Pm^ Дальнейшая работа с динамическими переменными происходит точно так же, как со статическими переменными соответствующих типов. Им можно присваивать значения, их можно использовать в качестве операндов в выражениях, параметров подпрограмм и пр. Например, если переменной P1^ нужно присвоить число 25, переменной P2^ присвоить значение символа "Write", а массив Pm^ заполнить по порядку целыми числами от 1 до 100, то это делается так: P1^:= 25; P2^:= 'Write'; For I:= 1 To 100 Do Pm^[I]:= I; Надо понимать разницу между ссылочной переменной (переменной –указатель) и динамической переменной. Покажем это на примере: Var p,q:^integer; …… new(p); new (q); p^:=3; q^:=58; p:=q; q:=nil; Если вместо оператора p:=q написать оператор p^:=q^ , то изменится значение динамической переменной, на которую указывает указатель p. Если динамическая величина теряет свой указатель, то она становится "мусором". В программировании под этим словом понимают информацию, которая занимает память, но уже не нужна. В рассмотренном выше примере, динамическая величина, равная 3, потеряла свой указатель и стала недоступной. Однако место в памяти она занимает. Это и есть пример возникновения "мусора". На схеме показано, что произошло в результате выполнения оператора p := q. В Паскале имеется стандартная процедура, позволяющая освобождать память от данных, потребность в которых отпала. Ее формат: DISPOSE(<указатель>); Например, если динамическая переменная p^ больше не нужна, то оператор DISPOSE(P) удалит ее из памяти. После этого значение указателя p становится неопределенным (НЕ путать с ПУСТЫМ указателем!) и ее нельзя использовать до тех пор, пока она снова не будет инициализирована с помощью вызова New или инструкции присваивания. Особенно существенным становится эффект экономии памяти при удалении больших массивов. Внимание! Распределение и освобождение памяти для нетипизированных указателей (pointer) производится с помощью специальных стандартных функций GetMem и FreeMem. Рассмотрим предыдущий пример с использованием процедуры DISPOSE(P): Var p,q:^integer; …… new(p); new (q); p^:=3; q^:=58; despose(p); p :=q; q :=nil; Таким образом, стандартные процедуры new и dispose позволяют динамически порождать объекты и уничтожать их, что дает возможность использовать память более эффективно. Процедурой despose(p)надо пользоваться аккуратно. Использование вызова despose(p) может приводить к весьма неприятной ситуации, связанной с появлением так называемых “висячих” указателей: если два указателями адресовали одну и ту же область памяти (а это встречается очень часто), то вызов despose(p) с одной из них оставляет в другой переменной адрес уже освобожденной области памяти и повторное использование этой переменной приведет либо к неверному результату, либо к аварийному завершению программы. Н апример: Var p,q:^integer; …… new(p); new (q); p^:=3; q:=p; despose(p); Использование Dispose для освобождения выделенной динамической памяти не является обязательным, после выхода из программы динамическая память освобождается автоматически, но частое использование процедуры выделения памяти без ее освобождения может привести к состоянию невозможности дальнейшего выделения памяти и к остановке программы. Примеры программ с использованием указателей и динамических переменных Пример 1. Program DemoPointer; var p1,p2,p3:^Integer; begin p1:=NIL; p2:=NIL; p3:=NIL; New(p1); New(p2); New(p3); p1^:=2; p2^:=4; p3^:=p1^+Sqr(p2^); writeln('p1^=',p1^:3,' p2^=',p2^:3,' p3^=',p3^:3); p1:=p2; writeln('p1^=',p1^:3,' p2^=',p2^:3) end. В результате выполнения программы на экран дисплея будут выведены результаты: p1^= 2 p2^= 4 p3^= 18 p1^= 4 p2^= 4 Пример 2. В динамическом массиве найти максимальный элемент. type tm=array[1..10000] of integer; var a:^tm; n,i,max:integer; begin writeln('input n'); readln(n); randomize; new(a); for i:=1 to n do a^[i]:=random(100); max:=a^[i]; for i:=2 to n do if a^[i]>max then max:=a^[i]; writeln('max=',max); dispose(a); end. Пример 3. Создать массив указателей и найти в нем минимальное значение. var a:array[1..100] of ^real; i,n:integer; min:real; begin writeln('input n'); readln(n); randomize; for i:=1 to n do begin new(a[i]); a[i]^:=random(10)-random; writeln(a[i]^:6:2) end; min:=a[i]^; for i:=2 to n do if a[i]^ writeln('min=',min:6:2); for i:=1 to n do dispose(a[i]); end. Пример 4. Дан текстовый файл размером не более 64 Кб, содержащий действительные числа, по одному в каждой строке. Переписать содержимое файла в массив, разместив его в динамически распределяемой памяти. Вычислить среднее значение элементов массива. Очистить динамическую память. Создать целый массив размером 10000, заполнить его случайными целыми числами в диапазоне от –100 до 100 и вычислить его среднее значение. Program Srednee; Const NMax = 10000; Type Diapazon = 1..NMax; MasInt = Array[Diapazon] Of Integer; MasReal = Array[Diapazon] Of Real; Var PInt : ^MasInt; PReal : ^MasReal; I, Midint, N : longInt; MidReal : Real; T : Text; S : string; Begin Write('Введите имя файла: '); ReadLn(S); Assign(T, S); Reset(T); MidReal := 0; MidInt := 0; Randomize; NEW(PReal); {Выделение памяти под вещественный массив} {Ввод и суммирование вещественного массива} I:=1; While Not Eof (T) Do Begin ReadLn(T, PReal^[I]); MidReal := MidReal + PReal^[I]; I:=I+1; End; DISPOSE(PReal); {Удаление вещественного массива} N:=I; Close(T); NEW(PInt); {Выделение памяти под целый массив} {Вычисление и суммирование целого массива} For I := 1 To N Do Begin PInt^[I] := -100 + Random(201); MidInt := MidInt + PInt^[I] End; {Вывод средних значений} WriteLn('среднее целое равно: ', MidInt Div N); WriteLn('среднее вещественное равно: ', (MidReal / N) : 10 : 6) End. Заключение
Здесь вы можете проверить себя по материалу этой темы. Контрольные вопросы
Задание на дом:
2. Для хранения двумерного массива создать в динамической памяти одномерный массив указателей на столбцы. В задаче требуется найти столбцы матрицы, в которых находятся минимальный и максимальный элементы матрицы и если это разные столбцы, то поменять их местами. |