Освобождение памяти pascal abc

Обновлено: 04.07.2024

Программирование. Динамические списки Pascal-Паскаль

  • Скачено бесплатно: 18554
  • Куплено: 414
    ->Программирование. Динамические списки Pascal-Паскаль

Динамические структуры данных

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

Классификация структур данных

Используемые в программировании данные можно разделить на две большие группы:

Данные статической структуры – это данные, взаиморасположение и взаимосвязи элементов которых всегда остаются постоянными.

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

Данные динамической структуры:

К данным динамической структуры относят файлы, несвязанные и связанные динамические данные.

Заметим, что файлы в данной классификации отнесены к динамическим структурам данных. Это сделано исходя из вышеприведенного определения. Хотя удаление и вставка элементов в середину файла не допускается, зато длина файла в процессе работы программы может изменяться – увеличиваться или уменьшаться до нуля. А это уже динамическое свойство файла как структуры данных.

Статические и динамические переменные в Паскале

В Паскале одной из задач описания типов является то, чтобы зафиксировать на время выполнения программы размер значений, а, следовательно, и размер выделяемой области памяти для них. Описанные таким образом переменные называются статическими.

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

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

Предположим, например, что у вас есть программа, требующая массива в 400 строк по 100 символов каждая. Для этого массива требуется примерно 40К, что меньше максимума в 64К. Если остальные ваши переменные помещаются в оставшиеся 24К, массив такого объема проблемы не представляет. Но что если вам нужно два таких массива? Это потребовало бы 80К, и 64К сегмента данных не хватит. Другим общим примером является сортировка. Обычно когда вы сортируете большой объем данных, то делаете копию массива, сортируете копию, а затем записываете отсортированные данные обратно в исходный массив. Это сохраняет целостность ваших данных, но требует также наличия во время сортировки двух копий данных.

С другой стороны объем памяти компьютера достаточно велик для успешного решения задач с большой размерностью данных. Выходом из положения может служить использование так называемой динамической памяти.

Динамическая память (ДП) – это оперативная память ПК, предоставляемая программе при ее работе, за вычетом сегмента данных (64 Кб), стека (16 Кб) и собственно тела программы. Размер динамической памяти можно варьировать. По умолчанию ДП – вся доступная память ПК.

ДП – это фактически единственная возможность обработки массивов данных большой размерности. Многие практические задачи трудно или невозможно решить без использования ДП. Например, при разработке САПР статическое распределение памяти невозможно, т.к. размерность математических моделей в разных проектах может значительно различаться.

И статические и динамические переменные вызываются по их адресам. Без адреса не получить доступа к нужной ячейке памяти, но при использовании статических переменных, адрес непосредственно не указывается. Обращение осуществляется по имени. Компилятор размещает переменные в памяти и подставляет нужные адреса в коды команд.

Адресация динамических переменных осуществляется через указатели. Их значения определяют адрес объекта.

Для работы с динамическими переменными в программе должны быть выполнены следующие действия:

  • Выделение памяти под динамическую переменную;
  • Инициализация указателя;
  • Освобождение памяти после использования динамической переменной.

Программист должен сам резервировать место, определять значение указателей, освобождать ДП.

Вместо любой статической переменной можно использовать динамическую, но без реальной необходимости этого делать не стоит.

Указатели

Для работы с динамическими программными объектами в Паскале предусмотрен ссылочный тип или тип указателей. В переменной ссылочного типа хранится ссылка на программный объект (адрес объекта).

Указатель – это переменная, которая в качестве своего значения содержит адрес байта памяти.

Объявление указателей

Указатель, связанный с некоторым определенным типом данных, называют типизированным указателем. Его описание имеет вид:

Пример фрагмента программы объявления указателя

Type A= array [1..100] of integer;
TA= ^ A ;
Var
P1: ^ integer;
P2: ^ real;

Указатель, не связанный с каким-либо конкретным типом данных, называется нетипизированным указателем. Для описания нетипизированного указателя в Паскале существует стандартный тип pointer. Описание такого указателя имеет вид:

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

Значения указателей – адреса переменных в памяти. Адрес занимает четыре байта и хранится в виде двух слов, одно из которых определяет сегмент, второе – смещение.

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

Пример фрагмента программы объявления указателя различных типов

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

Выделение и освобождение динамической памяти

Вся ДП рассматривается как сплошной массив байтов, который называется кучей.

Расположение кучи в памяти ПК.

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

  • Heaporg – начало кучи;
  • Heapend – конец кучи;
  • Heapptr – текущая граница незанятой ДП.

Выделение памяти под динамическую переменную осуществляется процедурой:

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

Пример фрагмента программы объявления указателя различных типов

Графически действие процедуры new можно изобразить так:

Освобождение динамической памяти осуществляется процедурой:

Следует помнить, что повторное применение процедуры dispose к свободному указателю может привести к ошибке.

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

Любые действия над указателем в программе располагаются между процедурами new и dispose.

При использовании динамически распределяемых переменных часто возникает общая проблема, называемая утечкой динамической памяти. Утечка памяти – это ситуация, когда пространство выделяется в динамически распределяемой памяти и затем теряется – по каким-то причинам ваш указатель не указывает больше на распределенную область, так что вы не можете освободить пространство. Общей причиной утечек памяти является переприсваивание динамических переменных без освобождения предыдущих. Простейшим случаем является следующий:

Пример фрагмента программы

var IntPointer :^ Integer;
begin
New (IntPointer);
New (IntPointer);
end.

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

Присваивание значений указателю

Для инициализации указателей существует несколько возможностей.

  • процедура new отводит блок памяти в области динамических переменных и сохраняет адрес этой области в указателе;
  • специальная операция @ ориентирует переменную-указатель на область памяти, содержащую уже существующую переменную. Эту операцию можно применять для ориентации на статическую и динамическую переменную.
    Например,

type A=array[0..99] of char;
var X: array [0..49] of integer;
pA:^ A;
Объявлены переменные разных типов: массив из 50 целых чисел и указатель на массив символов. Чтобы указатель pA указывал на массив X, надо присвоить ему адрес X
  • Существует единственная константа ссылочного типа nil, которая обозначает «пустой» адрес. Ее можно присваивать любому указателю.
  • Переменной-указателю можно присвоить значение другого указателя того же типа. Используя указательный тип pointer как промежуточный, можно присвоить значение одного указателя другому при несовпадении типов.

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

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

Еще раз повторим правила присваивания указателей:

  • любому указателю можно присвоить стандартную константу nil, которая означает, что указатель не ссылается на какую-либо конкретную ячейку памяти;
  • указатели стандартного типа pointer совместимы с указателями любого типа;
  • указателю на конкретный тип данных можно присвоить только значение указателя того же или стандартного типа данных.

Указатели можно сравнивать на равенство и неравенство, например:

В Паскале определены стандартные функции для работы с указателями:

  • addr( x) – тип результата pointer, возвращает адрес x (аналогично операции @), где x – имя переменной или подпрограммы;
  • seg( x) – тип результата word, возвращает адрес сегмента для x;
  • ofs( x) – тип результата word, возвращает смещение для x;
  • ptr( seg, ofs) – тип результата pointer, по заданному сегменту и смещению формирует адрес типа pointer.

Присваивание значений динамическим переменным

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

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

Недопустимо использовать выражения, подобные следующим:

Адрес --->R:= sqr( R^) + I^ -17 <---вещественное выражение.
Вещественная переменная --->R^:= sqr( R) <---аргумент – адрес.

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

Динамические структуры

Линейные списки (однонаправленные цепочки).

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

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

Лишь на самый первый элемент списка (голову) имеется отдельный указатель. Последний элемент списка никуда не указывает.

Описание списка

Type ukazat= ^ S;
S= record
Inf: integer;
Next: ukazat;
End;

В Паскале существует основное правило: перед использованием какого-либо объекта он должен быть описан. Исключение сделано лишь для указателей, которые могут ссылаться на еще не объявленный тип.

Формирование списка

Чтобы список существовал, надо определить указатель на его начало.

Пример описания списка

Type ukazat= ^S;
S= record
Inf: integer;
Next: ukazat;
End;

Создадим первый элемент списка:

Продолжим формирование списка. Для этого нужно добавить элемент либо в конец списка, либо в голову.

А) Добавим элемент в голову списка. Для этого необходимо выполнить последовательность действий:

  • получить память для нового элемента;
  • поместить туда информацию;
  • присоединить элемент к голове списка.

Б)Добавление элемента в конец списка. Для этого введем вспомогательную переменную, которая будет хранить адрес последнего элемента. Пусть это будет указатель с именем hv (хвост).

Просмотр списка

Удаление элемента из списка

А)Удаление первого элемента. Для этого во вспомогательном указателе запомним первый элемент, указатель на голову списка переключим на следующий элемент списка и освободим область динамической памяти, на которую указывает вспомогательный указатель.

Б) Удаление элемента из середины списка. Для этого нужно знать адреса удаляемого элемента и элемента, стоящего перед ним. Допустим, что digit – это значение удаляемого элемента.

x:= u;
while ( x<> nil) and ( x^. inf<> digit) do
begin
dx:= x;
x:= x^.next;
end;
dx:= x^.next:
dispose(x);

В)Удаление из конца списка. Для этого нужно найти предпоследний элемент.

x:= u; dx:= u;
while x^.next<> nil do
begin
dx:= x; x:= x^.next;
end;
dx^.next:= nil;
dispose(x);

Прохождение списка. Важно уметь перебирать элементы списка, выполняя над ними какую-либо операцию. Пусть необходимо найти сумму элементов списка.

summa:= 0;
x:= u;
while x<> nil do
begin
summa:= summa+ x^.inf;
x:= x^.next;
end;

Динамические объекты сложной структуры

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

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

Type ukazat= ^S;
S= record
Inf: integer;
Next: ukazat;
Pred: ukazat;
End;

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

Наличие в каждом звене двунаправленного списка ссылки как на следующее, так и на предыдущее звено позволяет от каждого звена двигаться по списку в любом направлении. По аналогии с однонаправленным списком здесь есть заглавное звено. В поле Pred этого звена фигурирует пустая ссылка nil, свидетельствующая, что у заглавного звена нет предыдущего (так же, как у последнего нет следующего).

В программировании двунаправленные списки часто обобщают следующим образом: в качестве значения поля Next последнего звена принимают ссылку на заглавное звено, а в качестве значения поля Pred заглавного звена – ссылку на последнее звено:

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

Существуют различные методы использования динамических списков:

  • Стек – особый вид списка, обращение к которому идет только через указатель на первый элемент. Если в стек нужно добавить элемент, то он добавляется впереди первого элемента, при этом указатель на начало стека переключается на новый элемент. Алгоритм работы со стеком характеризуется правилом: «последним пришел – первым вышел».
  • Очередь– это вид списка, имеющего два указателя на первый и последний элемент цепочки. Новые элементы записываются вслед за последним, а выборка элементов идет с первого. Этот алгоритм типа «первым пришел – первым вышел».
  • Возможно организовать списки с произвольным доступом к элементам. В этом случае необходим дополнительный указатель на текущий элемент.

Программирование

Исходники Pascal (127)

Справочник

Справочник по паскалю: директивы, функции, процедуры, операторы и модули по алфавиту

§30. Динамическая память.

Динамическая память, указатели.

Все переменные, которые мы до сих пор создавали, хранятся в том же участке памяти, который отводится под память программы. Если нам понадобиться переменная, требующая большой объём памяти, то её необходимо хранить уже отдельно от программы, т.к. под программу, как правило, отводится не так много места.

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

Объявляется указатель так же как и обычная переменная в разделе объявления переменных или в теле программы следующим образом:

var pi: ^ integer ;

Как видно из кода отличие составляет только символ «^» перед типом. Так же стоит сказать, что перед именем указателя, как правило, но не обязательно, ставится префикс p (от слова pointer-указатель).

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

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

В последней строчке примера находится не понятная строка. Здесь стоит сделать небольшое отступление. Каждая ячейка любой компьютерной памяти имеет свой порядковый номер. Именно этот номер и является её адресом. Если в компьютере 4 Гб оперативной памяти, то соответственно последняя ячейка будет иметь адрес, который можно обозначить десятизначным десятеричным числом. Т.е. если выражать адреса десятеричными числами, то придётся оперировать со слишком большим количеством цифр. Понятно, что это неудобно. Поэтому, как правило, все адреса выражаются шестнадцатеричными числами. Они несколько короче десятеричных. О том, что такое десятеричное или шестнадцатеричное число, надеюсь, вы знаете. Так вот в последней строке примера как раз находится шестнадцатеричное число, которое означает адрес выделенного кусочка памяти. О том, что это именно шестнадцатеричное число, а не какое-либо другое, говорит символ «$», стоящий перед числом. На том, что обозначают буквы в таких числах, здесь останавливаться не будем.

Двигаемся дальше. Для того, что бы освободить указатель, и соответственно очистить оперативную память, используется процедура dispose:

Мы научились выделять и освобождать кусочки оперативной памяти, теперь осталось научиться записывать туда данные и считывать их оттуда. Для этого используется так называемая операция разыменования. В коде программы для разыменования указателя необходимо после его имени поставить символ «^»:

Как видно работа с разыменованным указателем аналогична работе с обычной переменной.

Операция разименования к такому указателю не применима.

Значение одного указателя можно присвоить другому. Так же указатели можно сравнивать друг с другом на равенство (=) или (<>), для того, что бы определить указывают ли они на один объект. Далее пример:

if p1=p2 then writeln( 'Указатели указывают на одну и ту же переменную' );

Указатели указывают на одну и ту же переменную

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

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

procedure P(p1,p2:^ integer );

p1^:=Random( 1 , 100 );

p2^:=Random( 1 , 100 );

var i1,i2: integer ;

writeln( 'i1=' ,i1, ' i2=' ,i2);

procedure P( var ii1,ii2: integer );

ii1:=Random( 1 , 100 );

ii2:=Random( 1 , 100 );

var i1,i2: integer ;

writeln( 'i1=' ,i1, ' i2=' ,i2);

С таким подходом вы уже хорошо знакомы. И в своих программах будете поступать именно так. Тем не менее мне хотелось что б вы знали и устаревший принцип, потому что в дальнейшем вам возможно придётся работать с какой-нибудь старой версией языка программирования.

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

Однако, если вам будет очень необходимо освободить память, то можете присвоить переменной указателю на объект константу nil. В таком случае память, занятая под объект освободиться, но объект при этом уничтожен не будет. Указатель, в свою очередь, не перестанет указывать на эту память, соответственно и на объект. И пока на место этой памяти не запишется что-либо другое, можно будет пользоваться объектом. Поэтому может создаться иллюзия, что после присвоения указателю на объект константы nil, ничего не произошло. Однако если вы будете продолжать пользоваться объектом через этот же указатель, и если на его место будет записано, что-либо новое, то возникнет ошибка.

Динамическим список называется потому, что его размер может меняться в течение работы программы, и потому, что заранее не известен даже его максимальный размер. В списке могут быть данные какого-либо одного и того же типа. Например, создадим список целых чисел типа integer.

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

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


Теперь осталось только реализовать этот принцип в коде программы. Так как всё современное программирование не безосновательно построено на принципах ООП, то и список предлагаю оформить в виде класса TSpisok. Это даст возможность без особого труда создавать сколь угодно много объектов-списков, а так же можно будет использовать методы этого класса для работы с ними.

Далее представлено описание получившегося класса TSpisok и код программы, проверяющей работу этого класса:

Эта глава объясняет динамическое управление памятью в Pascal. Язык программирования Pascal предоставляет несколько функций для выделения памяти и управления.

Выделение памяти динамически

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

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

Паскаль предоставляет новую процедуру для создания переменных указателя.

Теперь, если вам нужно определить указатель с определенным количеством байтов, чтобы он мог ссылаться на него позже, вы должны использовать функцию getmem или процедуру getmem , которая имеет следующий синтаксис:

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

Изменение размера и освобождение памяти

Когда ваша программа выходит, операционная система автоматически освобождает всю память, выделенную вашей программой, но в качестве хорошей практики, когда вам больше не нужна память, вы должны освободить эту память.

Ниже приведен пример использования подпрограмм ReAllocMem и freemem:

Функции управления памятью

function Addr (X: TAnytype): указатель;

Возвращает адрес переменной

Назначенная функция (P: указатель): Boolean;

Проверяет правильность указателя

функция CompareByte (const buf1; const buf2; len: SizeInt): SizeInt;

Сравнивает 2 буфера памяти байт на байт

функция CompareChar (const buf1; const buf2; len: SizeInt): SizeInt;

Сравнивает 2 буфера памяти байт на байт

функция CompareDWord (const buf1; const buf2; len: SizeInt): SizeInt;

Сравнивает 2 буфера памяти байт на байт

функция CompareWord (const buf1; const buf2; len: SizeInt): SizeInt;

Обычно в языке Паскаль используются статические массивы вида:

var mas: array [1..10] of integer;

Рассмотрим работу с динамическим массивом.

Объявляется динамический массив в теле программы:

begin . var a: array of integer;

Или объявление с инициализацией:

память в динамическом массиве


А выделение памяти и размер такого массива задается уже по ходу программы:

var a: array of integer; var n:=readInteger; a:=new integer[n];

var a: array of integer; a:=new integer[readInteger];

var a: array of integer; var n:=readInteger; SetLength(a,n); // устанавливаем размер массива а

Ссылочная объектная модель: память выделяется служебным словом NEW

var a := new integer[5];

Организация памяти для массива a

Организация памяти для массива a

Инициализация, присваивание и вывод

Возможна инициализация динамического массива при описании:

var a: array of integer := (1,2,3);

Новые способы заполнения массива (заполнители):

var a:=Arr(1,2,3);// по правой части - integer

var a:=ArrFill(5,2); // 2 2 2 2 2

Ввод с клавиатуры:

var a:=ReadArrInteger(5); var a:=ReadArrReal(5);

Заполнение случайными числами:

var a:=new integer[10]; a:=arrRandomInteger(10);

Или с разделителем между элементами:

Переприсваивание:

var a: array of integer := (1,2,3); var b:=a; // [1,2,3]

ссылочная объектная модель


Но!
Если теперь переприсвоить значение элементов массива b , то изменится и массив a :

var a: array of integer := (1,2,3); var b:=a; b[2]:=1; print(a); //[1,2,1]

Для того, чтобы избежать данной ситуации, необходимо создать массив b , как копию массива a :

var a: array of integer := (1,2,3); var b:=Copy(a); b[2]:=1; print(a); //[1,2,3]

Очистка динамического массива

Если в программе случайно создается повторно один и тот же массив:

var a: array of integer; a:=new integer[4]; . a:=new integer[5];

Но можно очистить память и принудительно:

В процессе очистки выполнение программы и всех процессов приостанавливается. По этой причине сборщик мусора в системах реального времени не используется.

Работа с элементами массива

for var i:=0 to High(a) do print(a[i]);;

foreach var x in a do print(x);

Примеры работы с динамическими массивами

Пример: поиск элемента в массиве (выдавать индекс искомого)

Выполнение:
Выполним сначала БЕЗ использования обобщенной функции:

function IndexOf(a:array of integer;x:integer):integer; begin result:=-1; for var i:=0 to High(a) do if a[i]=x then begin result:=i; break end end; begin var a:=Arr(1,2,3,4,5); print(IndexOf(a,5)) end.

А теперь, выполним с использованием обобщенной функции:

function IndexOf<T>(a:array of T;x:T):integer; begin . end; begin var a:=Arr(1,2,3,4,5); print(IndexOf(a,5)) end.

При вызове обобщенной функции компиляция будет в два этапа:

  1. Автовыведение типа Т, сравнение с реальными цифрами, и т.к. числа целые, то Т определится как integer.
  2. Берется тело функции и заменяется Т на integer (инстанцирование функции с конкретным типом)

Методы для работы с массивами

var a:=Arr(1,2,3,4,5); reverse(a); // [5,4,3,2,1]

var a:=Arr(2,3,1,4,5); //[1,2,3,4,5] sort(a);

Сдвиги

сдвиги в динамическом массиве

Пример: дан массив, надо сдвинуть его элементы:

Стандартное выполнение:

var a:=Arr(1,2,3,4,5,6); var x:=a[0]; for var i:=0 to 4 do a[i]:=a[i+1]; a[5]:=x; print(a)

сдвиги в массивах

Если сдвиг вправо:

Срезы

  • Срезы доступны только на чтение, присваивать им значения нельзя.
  • Тип среза такой же, как у массива.
  • Срезы работают с массивами, строками и со списками.

var a:=Arr(1,2,3,4,5,6); print(a[1:5]) // [2,3,4,5]

println(a[:5]); // [1,2,3,4,5] - с начала до 5-го не включая println(a[1:]); // [2,3,4,5,6] - до конца println(a[::2]); // [1,3,5] - третий параметр - это шаг

срезы в массивах


Т.о. выполнение при помощи срезов выглядит так:
Перестановка:

var a:=Arr(1,2,3,4,5,6); var k:=a.Length-1; // 6 - 1 a:=a[k:]+a[:k]; print(a) // [6,1,2,3,4,5]

Т.е. создан еще массив уже со сдвигом.

Удаление и вставка элементов массива. Списки

Решение задач

Простые задачи

Пример: Напишите функцию SumArray , возвращающую сумму элементов заданного динамического массива. Выведите длину и сумму элементов массива

Выполнение:

function SumArray(var a: array of integer): integer:=a.Sum; begin var a:=new integer[10]; a:=arrRandomInteger(10); foreach var x in a do print(x); println('длина массива = ',a.Length,' сумма = ',ArraySum(a)) end.

Выполнение:
Дополните код:

procedure PrintArr( a: array of integer; delim:string:='; '); begin foreach var . in . do print(x,delim); end; begin var a:=new integer[10]; a:=arrRandomInteger(10); . end.

Задание 2: Заполнить массив (b) первыми N ( N ≥ 0 ) положительными нечётными числами массива a. Необходимо реализовать два различных решения задачи:

1

function MakeOddArrFunc (a: array of integer; n:integer):array of integer; begin var b:= new integer[n]; var j:=0; for var i:=0 to . do if . then begin . ;. ;end; SetLength(. ); result:=b; end; procedure MakeOddArrProc (a: array of integer;var b: array of integer; n:integer); begin b:= new integer[n]; var j:=0; for var i:=0 to . do if . then begin . ;. ;end; SetLength(. ); end; begin var a:=arrRandomInteger(20); var b: array of integer; var n:=readInteger('введите N'); println('исходный массив ',a); println('результат работы с функцией ',MakeOddArrFunc(a,n)); MakeOddArrProc(a,b,n); print('результат работы с процедурой ',b) end.

Задание 3: Заполнить целочисленный массив первыми N ( N ≥ 0 ) числами Фибоначчи. Достаточно одной из реализаций: функции или процедуры

function MakeFibArr (n : integer):array of integer; begin var c:= new integer[n]; c[0]:=. ;c[1]:=. ; for var i:=2 to n-1 do . ; result:=c; end; begin var n:= readinteger; . ; end.

Пример: Описать функцию MakeRandomRealArr с тремя параметрами: целым числом N ( N ≥ 0 ), вещественными параметрами a и b ( a ).
Функция должна возвращать массив (тип которого либо RealArr либо array of real ) из N случайных вещественных элементов в диапазоне a..b .
Продемонстрируйте заполнение и вывод на экран массива

Выполнение:

function MakeRandomRealArr (n : integer;a,b:real):array of real; begin var c:= ArrRandomReal(n,a,b); result:=c; end; begin var n:= readinteger; var a:=readReal; var b:=readReal; println('результат работы с функцией ',MakeRandomRealArr(n,a,b)); end.

Задание 5: Дан непустой массив вещественных чисел. Найти его наименьший элемент (функция FindMin ) Пример: Дан целочисленный массив. Обнулить все его элементы
с нечётными индексами (процедура SetToZeroOddIndexes ).
Условный оператор не использовать

Выполнение:

procedure SetToZeroOddIndexes(a: array of integer); begin var j:=0; while j<=a.Length-1 do begin a[j]:=0;j+=2; end; println('результирующий массив: ',a); end; begin var a:=arrRandomInteger(10); println('исходный массив: ',a); SetToZeroOddIndexes(a); end.

Задание: Дан целочисленный массив, содержащий не менее трёх элементов. Найти значение первого локального минимума (функция FirstLocMin ).
Пояснение: локальным минимумом считается тот элемент, который меньше каждого из своих соседей. Считать, что локальный минимум в массиве есть. Первый и последний элемент в качестве локальных минимумов не рассматривать. Задание: Дан массив целых чисел, содержащий не менее трёх элементов. Найти значение и номер последнего локального максимума (процедура с двумя выходными параметрами)

Задачи на срезы

Задание srez1: Дан массив размера N . Вывести его элементы в обратном порядке Пример: Дан массив A размера N и целое число K ( 1 ). Вывести его элементы с порядковыми номерами, кратными K :

Условный оператор не использовать.

Выполнение:

var n:=ReadInteger; var a:=ReadArrReal(n); var k:=ReadInteger; a[k-1 : : k].Print;

Задание srez2: Дан массив A размера N ( N - четное число). Вывести его элементы с четными номерами в порядке возрастания номеров:

Условный оператор не использовать.

Задание srez3: Дан массив A размера N ( N - нечетное число). Вывести его элементы с нечетными номерами в порядке убывания номеров:

Условный оператор не использовать.

Пример: Дан массив A размера N . Вывести сначала его элементы с четными номерами (в порядке возрастания номеров), а затем - элементы с нечетными номерами (также в порядке возрастания номеров):

Условный оператор не использовать

Выполнение:

var n:=ReadInteger; var a:=ReadArrReal(n); var srez:=a[1::2]+a[::2]; Print(srez);

Задание srez4: Дан массив A размера N . Вывести сначала его элементы с нечетными номерами (в порядке возрастания номеров), а затем - элементы с четными номерами (в порядке убывания номеров):

Условный оператор не использовать

Пример: Дан массив размера N и целые числа K и L ( 1 ). Найти среднее арифметическое элементов массива с номерами от K до L включительно

Выполнение:

var n:=ReadInteger; var a:=ReadArrReal(n); var k:=ReadInteger; var l:=ReadInteger; var srez:=a[k-1:l].Average; print(srez);

Читайте также: