Тема 9 Часть 1.ppt
- Количество слайдов: 111
Тема 9. Динамические структуры данных Часть 1 © С. В. Кухта, 2010
2 Содержание 1. 2. 3. 4. 5. Адресация оперативной памяти MS DOS Указатели и операции над ними Управление динамической памятью Работа с динамическими массивами Динамические структуры данных: списки, стеки, очереди, деки 6. Деревья и графы 7. Примеры решения задач обработки динамических структур данных © С. В. Кухта, 2010
3 1. Адресация оперативной памяти MS DOS © С. В. Кухта, 2010
4 До настоящего момента мы имели дело с переменными, которые размещаются в памяти согласно вполне определенным правилам. Ø Под глобальные переменные программы: ü память выделяется в процессе компиляции, ü и эти переменные существуют в течение всего времени работы программы. Ø Для локальных переменных, описанных в подпрограмме: Ø память отводится при вызове подпрограммы, Ø при выходе из нее эта память освобождается, Ø а сами переменные прекращают свое существование. Иными словами, распределение памяти во всех случаях осуществляется полностью автоматически. © С. В. Кухта, 2010
Статические данные Переменные, память под которые выделяется описанным образом, называют статическими. Под эту категорию подпадают все переменные, объявленные в области описаний программных блоков. © С. В. Кухта, 2010 5
6 Статические данные var x, y: integer; z: float; A: array[1. . 10] of float; str: string; • переменная (массив) имеет имя, по которому к ней можно обращаться • размер заранее известен (задается при написании программы) • память выделяется при объявлении • размер нельзя увеличить во время работы программы © С. В. Кухта, 2010
Динамические данные Однако Паскаль предоставляет возможность создавать новые переменные во время работы программы, сообразуясь с потребностями решаемой задачи, и уничтожать их, когда надобность в них отпадает. Переменные, созданием и уничтожением которых может явно управлять программист, называют динамическими. Для более полного понимания механизма работы с динамическими переменными следует сначала разобраться в механизме адресации оперативной памяти MS DOS. © С. В. Кухта, 2010 7
8 Динамические данные • размер заранее неизвестен, определяется во время работы программы • память выделяется во время работы программы • нет имени? Проблема: как обращаться к данным, если нет имени? Решение: использовать адрес в памяти Следующая проблема: в каких переменных могут храниться адреса? как работать с адресами? © С. В. Кухта, 2010
Структура адреса в оперативной памяти Наименьшей адресуемой единицей памяти персонального компьютера, построенного на базе микропроцессоров фирмы Intel и их аналогов, является байт. Таким образом, память представляет собой последовательность нумерованных байтов. Для обращения к конкретному байту необходимо знать его номер, который называют его физическим адресом. Память принято делить на Ø слова, Ø двойные слова Ø и параграфы. Слово имеет длину 2 байта, двойное слово – 4 байта, а параграф – 16 байт. © С. В. Кухта, 2010 9
Структура адреса в оперативной памяти При работе с памятью используется адресация по схеме «база + смещение» : При этом адрес конкретного байта М определяется как адрес некоторого заданного байта Аб (адрес базы) + расстояние до требуемого байта Асм (смещение). © С. В. Кухта, 2010 10
Структура адреса в оперативной памяти 11 В микропроцессорах фирмы Intel (начиная с i 8086) в качестве адреса базы используют адреса, кратные 16. Четыре последних бита (в двоичной системе счисления) такого адреса равны 0, и их не хранят, а аппаратно добавляют при вычислении физического адреса. Непрерывный участок памяти, имеющий длину не более 64 КБ и начинающийся с адреса, кратного 16 (0, 16, 32, …), называют сегментом. Адрес начала сегмента принимают за базу для всего сегмента. Адрес базы сегмента без последних четырех бит называют сегментным. © С. В. Кухта, 2010
Структура адреса в оперативной памяти Сегментный адрес и смещение имеют размер по 16 бит (слово). Физический адрес, получаемый при их сложении с учетом отброшенных четырех бит (рис. ), имеет размер 20 бит и может адресовать память объемом 220 байт или 1 МБ. Максимальное смещение равно 216 -1, что соответствует 64 КБ памяти. Т. о. , относительно одной базы можно адресовать не более 64 КБ памяти, что и ограничивает размер сегмента. © С. В. Кухта, 2010 12
Структура адреса в оперативной памяти 13 Современные модели микропроцессоров используют адреса большей длины с отличающейся схемой получения физического адреса, что учитывается версиями языка Паскаль, предназначенным для работы «под Windows» , но принцип адресации по схеме «база+смещение» используется и там. © С. В. Кухта, 2010
Виды сегментов 14 Программа и данные хранятся в памяти фрагментами, каждый из которых расположен в своем сегменте. Различают три вида сегментов: Ø кодов, Ø данных Ø и стека. В сегментах кодов хранится собственно программа. В сегментах данных размещаются глобальные переменные и константы. Сегмент стека интенсивно используется в процессе выполнения программы: ü при вызове подпрограмм в стек записывается адрес возврата, ü в нем размещаются локальные переменные, ü копии параметров-значений, адреса параметров© С. В. Кухта, 2010 переменных и параметров-констант и т. п.
15 Виды сегментов В процессе работы сегментные адреса хранятся в специальных сегментных регистрах: ü CS – адрес базы сегмента кодов; ü DS – адрес базы сегмента данных; ü SS – адрес базы сегмента стека. Доступ к конкретным участкам сегмента осуществляется через соответствующие смещения. © С. В. Кухта, 2010
Структура записи адреса в память При записи адреса в память отдельно сохраняются сегментный адрес и смещение: В языке Паскаль для работы с адресами используется специальный тип данных – указатель. Данные этого типа включают два поля типа word и хранят соответственно сегментный адрес и смещение. © С. В. Кухта, 2010 16
17 2. Указатели и операции над ними © С. В. Кухта, 2010
18 Различают указатели двух типов: Ø типизированные Ø и нетипизированные. Типизированные указатели содержат адреса, по которым в памяти размещаются данные определенных типов. Используя эти указатели с данными указанных типов, можно выполнять операции, предусмотренные базовым типом. Нетипизированные указатели хранят просто адреса, которые не связаны с данными конкретных типов, т. е. они могут хранить адрес переменной любого типа. © С. В. Кухта, 2010
19 Описание указателей При описании типизированного указателя необходимо сообщить компилятору, адреса переменных какого типа он может хранить: var <имя указателя>: ^ <тип адресуемой переменной>; Описание нетипизированного указателя: var <имя указателя>: pointer; © С. В. Кухта, 2010
20 Указатели Указатель – это переменная, в которую можно записывать адрес другой переменной (или блока памяти). Объявление: указатель var p. C: ^ ^char; // адрес символа p. I: ^integer; // адрес целой переменной p. R: ^real; // адрес вещ. переменной s: ^array[1. . 10] of byte; p: pointer; // нетипизиров. указатель © С. В. Кухта, 2010
21 Указатели – единственное исключение из общего правила, согласно которому все ресурсы перед использованием должны быть описаны. Для них допускаются описания вида: Туре рр = ^реrsоn; { тип person еще не определен! } реrsоn = record { определение типа person } name: string: next: рр; end; © С. В. Кухта, 2010
22 Указатели Type { правильные объявления типов} P 1=^word; { указатель на данные типа word } P 2=^char; { указатель на данные типа char } P 4=array[1. . 10] of ^real; {указатель на массив указателей, ссылающихся на данные типа real} © С. В. Кухта, 2010
23 Указатели Type { неправильные объявления типов} в таких P 5=^array[1. . 10] of real; случаях надо прежде описать P 6=^string[25]; идентификатор типа, а P 7=^record затем использовать его Field 1 : string [15]; в других описаниях Field 2 : real; end; Type mas = array[1. . 10] of real; st = string[25]; rec = record field 1 : string [15]; field 2 : real; end; var p 5 : ^mas; p 6 © С. В. Кухта, 2010 : ^rec; : ^st; p 7
24 Указатели Для указателей, которые не хранят никаких адресов, введена константа «нулевой адрес» с именем nil. Константу nil можно присваивать указателю любого типа. © С. В. Кухта, 2010
25 Типизированные указатели Как записать адрес: var m: integer; // целая переменная p. I: ^integer; // указатель A: array[1. . 2] of integer; // массив. . . p. I : = @ m; @ // адрес переменной m p. I : = @ A[1]; // адрес элемента массива A[1] p. I : = nil ; // нулевой адрес ячейки © С. В. Кухта, 2010
26 Обращение к данным через указатель program qq; var m, n: integer; p. I: ^integer; begin «вытащить» m : = 4; значение по адресу p. I : = @m; writeln('Адрес m = ', p. I); // вывод адреса ^ writeln('m = ', p. I^); // вывод значения n : = 4*(7 - p. I^); // n = 4*(7 - 4) = 12 p. I^ : = 4*(n - m); // m = 4*(12 – 4) = 32 end. © С. В. Кухта, 2010
27 Обращение к данным (массивы) program qq; var i: integer; A: array[1. . 4] of integer; p. I: ^integer; begin for i: =1 to 4 do A[i] : = i; p. I : = @A[1]; // адрес A[1] while ( p. I^ <= 4 ) // while( A[i] <= 4 ) do begin p. I^ : = p. I^ * 2; // A[i] : = A[i]*2; p. I : = p. I + 1; // к следующему элементу end; Не работает в end. Pascal. ABC. NET! переместиться к следующему элементу = изменить адрес на sizeof(integer) © С. В. Кухта, 2010 !
28 Что надо знать об указателях Ø указатель – это переменная, в которой можно хранить адрес другой переменной; Ø при объявлении указателя надо указать тип переменных, на которых он будет указывать, а перед типом поставить знак ^ ; Ø знак @ перед именем переменной обозначает ее адрес; Ø запись p^ обозначает значение ячейки, на которую указывает указатель p; Ø nil – это нулевой указатель, он никуда не указывает Ø при изменении значения указателя на n он в самом деле сдвигается к n-ому следующему числу данного типа (для указателей на целые числа – на n*sizeof(integer) байт). Нельзя использовать указатель, который указывает © С. В. Кухта, 2010 неизвестно куда (будет сбой или зависание)!
29 Пример обращения к указателям Type Sym=^char; Zap=record Field 1, field 2: real; End; m=array[0. . 9] of word; Var Ch : sym; Rec : ^zap; Mas : ^m; . . . Ch^: ='*'; {обращение к динамической переменной типа char, запись в эту область символа звездочка} . . . Readln (rec^. Field 1); {обращение к полю Field 1 динамической записи, ввод в него данных с клавиатуры } . . . Writeln (Mas[5]^); {обращение к элементу Mas[5] динамич. массива, вывод на экран значения указанного элемента} . . . © С. В. Кухта, 2010
30 Следует отметить, что обращение к переменным типа Pointer (указателям, которые не указывают ни на какой определенный тип и совместимы со всеми другими типами указателей) приводит к ошибке. Например: Var p: pointer; . . . p^: =1; {ошибка!} © С. В. Кухта, 2010
31 3. Операции над указателями © С. В. Кухта, 2010
32 Над значениями указателей возможны следующие операции. © С. В. Кухта, 2010
33 Присваивание При выполнении этой операции указателю присваивается значение другого указателя или nil. Допускается присваивать указателю только значение того же или неопределенного типа. Например: Var р1, р2: ^integer; р3: ^real; р: pointer; {допустимые операции} p 1: =p 2; р: =р3; р1: =р; p 1: =nil; р: =nil; {недопустимые операции} р3: =р2; p 1: =p 3; © С. В. Кухта, 2010
34 Получение адреса Это унарная операция, которая строится из знака операции – символа @ (коммерческое а) и одного операнда – переменной любого типа. Результат операции – указатель типа pointer, который можно присвоить любому указателю. Например: Var i: integer; pi: ^integer; . . . pi: =@i; { указатель pi будет содержать адрес переменной i } © С. В. Кухта, 2010
35 Доступ к данным по указателю Операция доступа к данным по указателю (операция разыменования) необходима, чтобы получить доступ к переменной по указателю, необходимо после переменной – типизированного указателя поставить знак «^» . Полученное значение имеет тип, совпадающий с базовым типом указателя. Нетипизированные указатели разыменовывать нельзя. Например: j: =pi^; {переменной j присваивается значение целого, pi^: =pi^+2; расположенного по адресу pi } {целое значение, расположенное по адресу pi, увеличивается на 2 } © С. В. Кухта, 2010
36 В таблице показано, как выполняются операции с указателями. Фрагмент программы Const i: integer=1; Var pi: ^integer; Результат операции Описание операции Создается инициализированная переменная i и указатель на целое pi pi: =@i; Указателю pi присваивается адрес переменной i © С. В. Кухта, 2010
37 Фрагмент программы pi^: =pi^+2; pi: =nil; Результат операции Описание операции Значение, адрес которого находится в pi, увеличивается на 2 Запись в pi константы «нулевой адрес» © С. В. Кухта, 2010
38 Операции отношения Из всех возможных операций отношения допускаются только операции проверки Ø равенства (=) Ø и неравенства (< >). Эти операции проверяют соответственно равенство и неравенство адресов. Например: sign: = pl=p 2; { переменная sign логического типа получает if p 1 <> значение true или false в зависимости от значений указателей } nil then. . . { проверка адреса } © С. В. Кухта, 2010
39 Указатель на указатель Поскольку в качестве базового типа типизированного указателя может быть использован любой тип, допустимо определять «указатель на указатель» . Например, для реализации схемы, изображенной на рисунке, переменную ppi описать и инициализировать следующим образом: Const i: integer=1; Var pi: ^integer; ppi: ^pi; . . . pi: =@i; ppi: =@pi; . . . Для получения значения переменной i необходимо дважды применить операцию разыменования. В нашем случае ppi^^ имеет © С. В. Кухта, 2010 и равно 1. тип integer
40 Функции, работающие с указателями Функция ADDR(x): pointer возвращает адрес объекта х, в качестве которого может быть указано имя переменной, функции, процедуры. Выполняет те же действия, что и операция «@» . © С. В. Кухта, 2010
41 Функции, работающие с указателями Функция SEG(x): word возвращает сегментный адрес указанного объекта. Функция OFS(x): word возвращает смещение указанного объекта. © С. В. Кухта, 2010
42 Функции, работающие с указателями Функция CSEG(x): word возвращает текущее значение сегментного регистра CS – сегментный адрес сегмента кодов. Функция DSEG: word возвращает текущее значение сегментного регистра DS – сегментный адрес сегмента данных. © С. В. Кухта, 2010
43 Функции, работающие с указателями Функция PTR(seg, ofs: word): pointer возвращает значение указателя по заданным сегментному адресу seg и смещению ofs. © С. В. Кухта, 2010
Преобразование типов данных с использованием типизированных указателей Как отмечалось ранее, типизированный указатель связывается с некоторым типом данных и адресует вполне определенную область памяти, соответствующую длине внутреннего представления своего типа. Если указателям разного типа присвоить один и тот же адрес, то каждый из них будет рассматривать содержимое области в соответствии с внутренним представлением своего типа. © С. В. Кухта, 2010 44
Преобразование типов данных с использованием типизированных указателей Эта особенность указателей позволяет использовать их для неявного преобразования типа. Необходимо помнить, что для присвоения разнотипным указателям одного и того же адреса следует использовать нетипизированные указатели, либо задавать абсолютное значение требуемого адреса. Контроль корректности значений, полученных в результате выполненных действий, системой не осуществляется, а ложится целиком на программиста. © С. В. Кухта, 2010 45
Преобразование типов данных с использованием типизированных указателей Var L: longint; {длинное целое число} Р 1: ^аrrаy[1. . 4] of byte; {указатель на область длиной 4 байта} k: byte; Begin L: =123456789; P 1: =@L; {возвращает нетипизированный указатель} k: =Р 1^[1]: {младший байт внутреннего представления числа L, младший потому, что числа в памяти для данного типа компьютеров хранятся с младшего байта} © С. В. Кухта, 2010 46
47 4. Управление динамической памятью © С. В. Кухта, 2010
48 Куча Определяемые в примерах предыдущего параграфа указатели для наглядности содержали адреса статически размещенных переменных. Однако основное назначение указателей – адресация динамических переменных. Такие переменные располагаются в свободной области, называемой динамической памятью или «кучей» . Эта область расположена после программы, и ее объем составляет около 200. . . 300 к. Б. © С. В. Кухта, 2010
49 Куча Соответственно, чем больше объем программы, тем меньше размер свободной области памяти. Значения стандартных переменных, используемых для управления динамической областью: Heap. Org – указатель на начало динамической области; Heap. End – указатель на конец динамической области; Heap. Ptr – указатель на текущее значение границы свободной динамической области. © С. В. Кухта, 2010
50 Куча Заказать и освободить память требуемого объема из динамической области можно, используя специальные процедуры и функции. © С. В. Кухта, 2010
Процедуры и функции, работающие с указателями Процедура New (Var <типизированный указатель>) возвращает адрес выделенного участка памяти через параметр-переменную. Размер участка памяти определяется базовым типом указателя. Например: Var pi: ^integer; . . . New(pi); {теперь pi содержит адрес двух байт, выделенных из динамической памяти под размещение переменной целого типа} © С. В. Кухта, 2010 51
Процедуры и функции, работающие с указателями Функция New (<тип типизированного указателя>) : pointer Возвращает адрес выделенного участка памяти. Размер участка памяти также определяется базовым типом указателя. Например: Туре tpi: ^integer; Var pi: tpi; . . . pi: = New(tpi); {pi также содержит адрес двух байт, выделенных из динамической памяти под размещение переменной целого типа} © С. В. Кухта, 2010 52
Процедуры и функции, работающие с указателями Для размещения изученных нами типов переменных можно использовать как процедуру New, так и функцию New. Процедура New ищет в незанятой памяти подходящий по размеру кусок и, "застолбив" это место для безымянной динамической переменной, записывает в типизированный указатель адрес выделенного участка. Поэтому часто говорят, что процедура New создает динамическую переменную. © С. В. Кухта, 2010 53
Процедуры и функции, работающие с указателями Размер выделяемого "куска памяти" напрямую зависит от типа указателя. Например, если переменная p была описана Ø как указатель на integer-переменную, то процедура new(p) выделит два байта; Ø под real-переменную необходимо выделить четыре байта и т. д. © С. В. Кухта, 2010 54
Процедуры и функции, работающие с указателями Процедура Dispose (<типизированный указатель>) освобождает память по адресу, хранящемуся в указателе. Например: Dispose(pi); При попытке применить процедуру к указателю, имеющему значение nil, или освободить уже освобожденную память фиксируется ошибка и выдается соответствующее сообщение. © С. В. Кухта, 2010 55
Процедуры и функции, работающие с указателями В результате освобождения памяти при помощи процедуры dispose значение указателя, хранившего адрес освобожденной области, становится неопределенным. Во избежание проблем его лучше сразу же "обнулить" или присвоить другое значение: dispose(p); p: = nil; © С. В. Кухта, 2010 56
Процедуры и функции, работающие с указателями Серия последовательных обращений к New и Dispose обычно приводит к фрагментации памяти – память разбивается на небольшие фрагменты с чередованием свободных и занятых участков. В результате может возникнуть ситуация: ü свободной памяти для размещения новой переменной достаточно, ü но она не может быть размещена из-за отсутствия непрерывного участка требуемого размера. Для уменьшения явления фрагментации используют специальные процедуры. © С. В. Кухта, 2010 57
Пример 1 работы с разнотипными динамическими переменными в куче Type St 1=string[7]; St 2=string[3]; Var i, i 1, i 2, i 3, i 4: ^integer; R^: real; S 1: ^st 1; S 2: ^st 2; Begin New(i); i 1^: =1; New(i 2); i 2^: =2; New(i 3); i 3^: =3; New(i 4); i 4^: =4; (*1*) Disроsе (i 2); {освобождается второе размещение} New (i); {память нужного размера (2 байта) выделяется на 1 -м свободном месте от начала кучи, достаточном для размещения данной переменной; в этом примере - это участок, который занимала переменная i 2^, ее адрес остался в указателе i 2 } © С. В. Кухта, 2010 58
Пример 1 работы с разнотипными динамическими переменными в куче i^: =5; (*2*) Dispose(i 3); {освобождается третье размещение} New(r); {память под переменную типа real выделяется в вершине кучи, т. к. размер дырки с адресом i 3 (2 байта) мал для размещения переменной типа real, для которой необходимо 6 байт } r^: =6; (*3*) Writeln (r^); { вывод: 6. 00000 e+00} End. © С. В. Кухта, 2010 59
Пример 2 работы с разнотипными динамическими переменными в куче {В следующем примере используется массив указателей } Uses crt; Var r: array [1. . 10] of ^real; i: 1. . 10; Begin Randomize; For i: =1 to 10 do begin New(r[i]); r[i]^: =random; {генерация случайных вещественных чисел в диапазоне 0 <= r[i]^ < 1} Writeln(r[i]^); {вывод случайных чисел} end; End. © С. В. Кухта, 2010 60
Процедуры и функции, работающие с указателями Процедура Mark (Var p: pointer) запоминает значение Heap. Ptr – указателя на текущее значение границы свободной динамической области – в указателе р, полученном в качестве параметра. © С. В. Кухта, 2010 61
Процедуры и функции, работающие с указателями Процедура Release (Var p: pointer) освобождает весь фрагмент памяти, начиная с адреса р, зафиксированного в указателе процедуры Mark. Например: . . . new(pl); new(p 2); mark(p); new(p 3); new(p 4); release(p); . . . Совместное использование процедур Dispose и Release недопустимо, т. к. Release разрушает список освобожденных фрагментов, создаваемый при © выполнении Dispose. С. В. Кухта, 2010 62
Процедуры и функции, работающие с указателями Динамическую память можно выделять фрагментами, указывая их размер. Процедура Get. Mem (Var p: pointer; size: word) запрашивает у системы память размера, указанного в параметре size (запрашиваемый объем не должен превышать 64 КБ), и помещает адрес выделенного системой фрагмента в переменную типа pointer с именем р (нетипизрованный указатель). Как правило, данный способ выделения памяти используется, если требуется память под размещение буферов, формат которых программисту не известен. © С. В. Кухта, 2010 63
Процедуры и функции, работающие с указателями Функция Size. Of(x): word возвращает длину указанного объекта х в байтах. © С. В. Кухта, 2010 64
Процедуры и функции, работающие с указателями Процедура Free. Mem (p: pointer; size: word) освободит в памяти столько байтов (начиная с указанного в переменной p адреса), сколько задано в переменной size, т. е. освобождает область памяти, выделенную процедурой Get. Mem. © С. В. Кухта, 2010 65
66 Обработка ошибок при выделении памяти Однако каким бы способом не запрашивалась память, может возникнуть ситуация, когда оставшаяся свободная память меньше требуемой, и память выделить невозможно. В этом случае система по умолчанию выдает сообщение об ошибке выполнения и аварийно завершает задачу. Избежать подобной ситуации можно несколькими способами. Рассмотрим способ, который заключается в предварительной проверке наличия свободной памяти требуемого размера. © С. В. Кухта, 2010
67 Обработка ошибок при выделении памяти Рассмотрим 1 -й способ, который заключается в предварительной проверке наличия свободной памяти требуемого размера. Функция Maxavail: longint возвращает длину максимального непрерывного участка памяти. Функция Memavail: longint возвращает размер всей свободной памяти – сумму длин всех свободных фрагментов. © С. В. Кухта, 2010
68 Обработка ошибок при выделении памяти 2 -й способ базируется на возможности перехвата системной обработки ошибки выделения памяти. Для этого необходимо определить свою подпрограмму обработки ошибки, в которой вместо признака ошибки распределения динамической памяти 0, установленного по умолчанию, необходимо задать Heapfunc: =1. © С. В. Кухта, 2010
69 Обработка ошибок при выделении памяти Например: Function Heap. Func(size: word): integer; far; begin Heap. Func: =7; end; В программе необходимо определить адрес подпрограммы обработки ошибки Неар. Еrrоr, указав собственную программу Heap. Func: Heap. Error: = @Heap. Func; . . . © С. В. Кухта, 2010
70 Обработка ошибок при выделении памяти Использование такой подпрограммы приведет к тому, что процедуры New и Get. Mem при исчерпании памяти вернут указатели, установленные в nil, и выполнение программы не будет прервано. Действия по обработке возникшей ситуации выполняет программист, который должен проверить указатели после возврата из процедур выделения памяти. © С. В. Кухта, 2010
71 Пример. Разработать программу для определения суммы элементов двумерного массива большой размерности (n 10000, m 10000, n m 50000). Для размещения массива n m вещественных чисел (real) потребуется 6 n m = 300000 байт памяти, что превышает 64 Кб, следовательно, использовать стандартный тип «массив» нельзя. Если создать массив указателей размерности n m, то потребуется 4 n m = 200000 байт памяти, что также превышает 64 Кб. © С. В. Кухта, 2010
72 Пример. Решением задачи является реализация массива в виде статически размещенного массива указателей ptrstr на n элементов (по числу строк), каждый указатель которого хранит адрес динамически размещенного массива – строки матрицы. © С. В. Кухта, 2010
73 Пример. Тогда одного сегмента достаточно для размещения около 64000/4 =16000 указателей на строки матрицы. Так как указатель может адресовать целый сегмент, то в каждой строке можно разместить 64000/6 = 10677 элементов типа real, т. е. даже больше, чем требуется по условию задачи. Однако конкретный размер массива, который можно разместить, определяется размером доступной динамической памяти (кучи). Поэтому в программе осуществляется контроль выделения памяти методом «перехвата» системной ошибки с помощью собственной функции обработки ошибок. © С. В. Кухта, 2010
74 Пример Наибольшую сложность в данной программе представляет определение адреса элемента матрицы с индексами (i, j). Этот адрес складывается из сегментного адреса, хранящегося в указателе – Seg(ptrstr[i]), и смещения, состоящего из смещения начала динамического массива, которое хранится в том же указателе – Ofs(ptrstr[i]), и смещения на j– 1 элемент внутри динамически размещенного массива – (j– 1) Size. Of(real). Эти вычисления в программе целесообразно реализовать в виде подпрограммы-функции, которая возвращает результат типа real. © С. В. Кухта, 2010
75 Пример Program ex_large_mas; Const nn=16000; {максимальное количество строк} Var i, j, n, m: word; s: real; ptrstr: array[1. . nn] of pointer; {статический массив указателей на строки матрицы} Туре tpreal=^real; {функция формирования адреса элемента матрицы} Function Addr. R(I, j : word) : tpreal; begin Addr. R: =Ptr(Seg(ptrstr[i]^), Ofs(ptrstr[i]^)+(j-1)*Size. Of(real)) end; © С. В. Кухта, 2010
76 Пример {собственная функция обработки ошибок} function heapfunc(size: word): integer; far; begin heapfunc: =1; end; {основная программа} begin Randomize; {подключаем собственную функцию обработки ошибок} heaperror: =@heapfunc; Write. Ln(Введите n, m'); Read. Ln(n, m); © С. В. Кухта, 2010
77 Пример for i: =1 to n do begin {запрашиваем память под строку матрицы} Get. Mem(ptrstr[i], m*sizeof(real)); if ptrstr[i]=nil then begin {если памяти не хватает, то завершаем программу} Write. Ln('Не хватает памяти под матрицу'); for j: =1 to i-1 do {освобождаем уже выделенную память} Free. Mem(ptrstr[j], m*sizeof(real)); Halt(2); end; for j: =1 to m do Addr. R(i, j)^: =Random; {если память есть, то заполняем строку случайными числами} end; © С. В. Кухта, 2010
78 Пример s: =0; for i: =1 to n do for j: =1 to m do s: =s + Addr. R(i, j)^; Write. Ln('3 нaчeнue суммы =', s: 15: 10); Write. Ln('Сpeднee значение =', s/(n*m): 15: 10); for i: =1 to n do {освобождаем использованную память} Free. Mem(ptrstr[i], m*Size. Of(real)); End. © С. В. Кухта, 2010
79 5. Динамические массивы © С. В. Кухта, 2010
80 При работе с массивами практически всегда возникает задача настройки программы на фактическое количество элементов массива. В зависимости от применяемых средств решение этой задачи бывает различным. © С. В. Кухта, 2010
81 Первый вариант – использование констант для задания размерности массива. Program first; const n : integer = 10; { либо n = 10; } var a : array [1. . n] of real; i : integer; begin for i: =1 to n do readln (a[i]); { и далее все циклы работы с массивом используют n} Такой способ требует перекомпиляции программы при каждом изменении числа обрабатываемых элементов. © С. В. Кухта, 2010
82 Второй вариант – программист планирует некоторое условно максимальное (теоретическое) количество элементов, которое и используется при объявлении массива. При выполнении программа запрашивает у пользователя фактическое количество элементов массива, которое должно быть не более теоретического. На это значение и настраиваются все циклы работы с массивом. © С. В. Кухта, 2010
83 Program second; var a : array [1. . 25] of real; i, nf : integer; begin writeln('введите фактич. число элементов массива <= 25 '); readln(nf); for i: =1 to nf do readln(a[i]); { и далее все циклы работы с массивом используют nf} Этот вариант более гибок и технологичен по сравнению с предыдущим, т. к. не требуется постоянная перекомпиляция программы, но очень нерационально расходуется память, ведь ее объем для массива всегда выделяется по указанному максимуму. Используется же © С. В. Кухта, 2010 только часть ее.
84 Вариант третий – в нужный момент времени надо выделить динамическую память в требуемом объеме, а после того, как она станет не нужна, освободить ее. © С. В. Кухта, 2010
85 Program dynam_memory; type mas = array[1. . 2] of <требуемый_тип_элемента>; ms = ^mas; var a : ms; i, nf : integer; begin writeln('введите фактич. число элементов массива'); readln(nf); getmem (a, sizeof(<требуемый_тип_элемента>) *nf); for i: =1 to nf do readln(a^[i]); { и далее все циклы работы с массивом используют nf}. . . freemem(a, sizeof(<требуемый_тип_элемента>) *nf); end. © С. В. Кухта, 2010
86 Где нужны динамические массивы? Задача. Ввести размер массива, затем – элементы массива. Отсортировать массив и вывести на экран. Проблема: размер массива заранее неизвестен. Пути решения: 1) выделить память «с запасом» ; 2) выделять память тогда, когда размер стал известен. Алгоритм: 1) ввести размер массива; 2) выделить память ; выделить память 3) ввести элементы массива; 4) отсортировать и вывести на экран; 5) удалить массив © С. В. Кухта, 2010
87 Использование указателей какой-то массив целых чисел program qq; type int. Array = array[1. . 1] of integer; var A: ^int. Array; i, N: integer; begin writeln('Размер массива>'); выделить память readln(N); Get. Mem(pointer(A), N*sizeof(integer)); for i : = 1 to N do readln(A[i]); работаем так же, . . . { сортировка } как с обычным массивом! for i : = 1 to N do writeln(A[i]); Free. Mem(pointer(A)); освободить память end. © С. В. Кухта, 2010
88 Использование указателей • для выделения памяти используют процедуру Get. Mem( указатель, размер в байтах ); • указатель должен быть приведен к типу pointer – указатель без типа, просто адрес какого-то байта в памяти; • с динамическим массивом можно работать так же, как и с обычным (статическим); • для освобождения блока памяти нужно применить процедуру Free. Mem: Free. Mem ( указатель ); © С. В. Кухта, 2010
89 Пример 1 Рассмотрим пример использования динамического одномерного массива, который используется как двумерный массив. После ввода реальной размерности массива и выделения памяти для обращения к элементу двумерного массива адрес его рассчитывается, исходя из фактической длины строки и положения элемента в строке (при заполнении матрицы по строкам). Требуется найти максимальный элемент в матрице и его координаты. © С. В. Кухта, 2010
90 Пример 1 Uses crt; type t 1=array[1. . 1] of integer; var a: ^t 1; n, m, i, j, k, p: integer; max: integer; begin write('n='); readln (n); write('m='); readln (m); getmem (a, sizeof(integer)*n*m); for i: =1 to n*m do read(a^[i]); max: =a^[1]; k: =1; p: =1; for i: =1 to n do for j: =1 to m do if a^[(i-1)*m+j] > max then begin max: =a^[(i-1)*m+j]; k: =i; p: =j end; write('строка=', k: 2, ' столбец=', p: 2); freemem(a, 2*n*m); © С. В. Кухта, 2010 end.
91 Пример 2 В следующем примере для хранения двумерного массива используется одномерный массив указателей на столбцы. В задаче требуется найти столбцы матрицы, в которых находятся минимальный и максимальный элементы матрицы и если это разные столбцы, то поменять их местами. © С. В. Кухта, 2010
92 Пример 2 Uses crt; Type Vk=^t 1; T 1=array[1. . 1] of integer; Mt=^t 2; T 2=array[1. . 1] of vk; Var a: mt; m, n, i, j, k, l: integer; max, min: integer; r: pointer; Begin Readln (n, m); {выделение памяти под указатели столбцов матрицы} Getmem(a, sizeof(pointer)*m); {выделение памяти под элементы столбцов} for j: =1 to m do Getmem (a^[j], sizeof(integer)*n); © С. В. Кухта, 2010
93 Пример 2 For i: =1 to n do For j: =1 to m do Read(a^[j]^[i]); For i: =1 to n do begin For j: =1 to m do Write (a^[j]^[i]: 4); Writeln End; max: =a^[1]; k: =1; min: =max; l: =1; For j: =1 to m do For i: =1 to n do if a^[j]^[i]
94 Пример 2 {для обмена столбцов достаточно поменять указатели на столбцы} if k<>l then begin r: =a^[k]; a^[k]: =a^[l]; a^[l]: =r end; For i: =1 to n do begin For j: =1 to m do Write(a^[j]^[i]: 3, ' '); Writeln end; For i: =1 to m do Freemem (a^[i], n*sizeof(integer)); Freemem (a, m*sizeof(pointer)) End. © С. В. Кухта, 2010
95 Ошибки при работе с памятью Запись в «чужую» область памяти: память не была выделена, а массив используется. Что делать: так не делать. Выход за границы массива: обращение к элементу массива с неправильным номером, при записи портятся данные в «чужой» памяти. Что делать: если позволяет транслятор, включать проверку выхода за границы массива. Указатель удаляется второй раз: структура памяти нарушена, может быть все, что угодно. Что делать : в удаленный указатель лучше записывать nil, ошибка выявится быстрее. Утечка памяти: ненужная память не освобождается. Что делать : убирайте «мусор» © С. В. Кухта, 2010 (в среде. NET есть сборщик мусора!)
96 6. Динамические записи © С. В. Кухта, 2010
97 Структуры (в Паскале – записи) Свойства: Задача: объединить эти данные в единое • автор (строка) целое • название (строка) • год издания (целое число) • количество страниц (целое число) Структура (запись) – это тип данных, который может включать в себя несколько полей – элементов разных типов (в том числе и другие структуры). Размещение в памяти автор название год издания количество страниц 40 символов 80 символов целое © С. В. Кухта, 2010
98 Одна запись Объявление (выделение памяти): название запись поля var Book: record author: string[40]; title: string[80]; year: integer; pages: integer; end; // // автор, строка название, строка год издания, целое кол-во страниц, целое ! Обращение к полям: Для обращения к полю записи используется точка! readln(Book. author); // ввод readln(Book. title); Book. year : = 1998; // присваивание if Book. pages > 200 then // сравнение writeln(Book. author, '. ', Book. title); // вывод © С. В. Кухта, 2010
99 Массив записей Books[1] . . . author title Books[10] year Объявление (выделение памяти): const N = 10; var a. Books: array[1. . N] of record author: string[40]; title: string[80]; year: integer; pages: integer; end; © С. В. Кухта, 2010 pages
100 Массив записей Обращение к полям: for i: =1 to N do begin readln(a. Books[i]. author); readln(a. Books[i]. title); . . . end; for i: =1 to N do if a. Books[i]. pages > 200 then writeln(a. Books[i]. author, '. ', a. Books[i]. title); ! a. Books[i]. author – обращение к полю author записи a. Books[i] © С. В. Кухта, 2010
101 Новый тип данных – запись Объявление типа: type TBook = record author: string[40]; title: string[80]; year: integer; pages : integer; end; ! // // Память не выделяется! автор, строка название, строка год издания, целое кол-во страниц, целое TBook – Type Book ( «тип книга» ) – удобно! Объявление переменных и массивов: const N = 10; var Book: TBook; // одна запись a. Books: array[1. . N] of TBook; © С. В. Кухта, 2010 // массив
102 Записи в процедурах и функциях Процедура: procedure Show. Author ( b: TBook ); begin writeln ( b. author ); end; Функция: function Is. Old( b: TBook ): boolean; begin Is. Old : = b. year < 1900; Память не end; ! Основная программа: Book. author : = 'А. С. Пушкин'; Show. Author ( Book ); Book. year : = 1800; writeln( Is. Old(Book) ); © С. В. Кухта, 2010 выделяется!
103 Файлы записей Объявление указателя на файл: var F: file of TBook; Запись в файл: Assign(F, 'books. dat'); { связать с указателем } Rewrite(F); { открыть файл для запись } writeln(F, Book); { запись } for i: =1 to 5 do writeln(a. Book[i]); { запись } Close(F); { закрыть файл } © С. В. Кухта, 2010
104 Чтение из файла Известное число записей: Assign(F, 'books. dat'); { связать с указателем } Reset(F); { открыть для чтения } Read(F, Book); { чтение } for i: =1 to 5 do Read(F, a. Book[i]); { чтение } Close(F); { закрыть файл } «Пока не кончатся» : пока не дошли до конца файла F EOF = end of file count : = 0; while not eof(F) do begin count : = count + 1; { счетчик } Read(F, a. Book[count]); { чтение } end; ? В чем может быть проблема! © С. В. Кухта, 2010
105 Пример программы Задача: в файле books. dat записаны данные о книгах в виде массива структур типа TBook (не более 100). Установить для всех 2008 год издания и записать обратно в тот же файл. полное описание type TBook … ; Tbook структуры const MAX = 100; var a. Books: array[1. . MAX] of TBook; i, N: integer; F: file of TBook; begin { прочитать записи из файла, N - количество } for i: =1 to N do a. Books[i]. year : = 2008; { сохранить в файле } end. © С. В. Кухта, 2010
106 Пример программы Чтение «пока не кончатся» : чтобы не выйти за пределы массива Assign(f, 'books. dat'); Reset(f); N : = 0; while not eof(F) and (N < MAX) do begin N : = N + 1; read(F, a. Books[N]); end; Сlose(f); Сохранение: Assign(f, 'books. dat'); { можно без этого } Rewrite(f); for i: =1 to N do write(F, a. Books[i]); Close(f); © С. В. Кухта, 2010
107 Выделение памяти под запись переменнаяуказатель на TBook var p. B: ^TBook; begin выделить память под запись, записать адрес в p. B New(p. B); p. B^. author : = 'А. С. Пушкин'; p. B^. title : = 'Полтава'; p. B^. year : = 1990; ! p. B^. pages : = 129; Dispose(p. B); end. освободить память © С. В. Кухта, 2010 Для обращения к полю записи по адресу используется знак ^
108 Сортировка массива записей Ключ (ключевое поле) – это поле записи (или комбинация полей), по которому выполняется сортировка. const N = 100; var a. Books: array[1. . N] of TBook; i, j, N: integer; temp: TBook; { для обмена } begin { заполнить массив a. Books } { отсортировать = переставить } for i: =1 to N do writeln(a. Books[i]. title, a. Books[i]. year: 5); end. © С. В. Кухта, 2010
109 Сортировка массива записей for i: =1 to N-1 do for j: =N-1 downto i do if a. Books[j]. year > a. Books[j+1]. year then begin temp : = a. Books[j]; a. Books[j] : = a. Books[j+1]; a. Books[j+1] : = temp; end; ? Какой ключ сортировки? ? ? Какой метод сортировки? © С. В. Кухта, 2010 Что плохо?
110 Сортировка массива записей Проблема: как избежать копирования записи при сортировке? Решение: использовать вспомогательный массив указателей, при сортировке переставлять указатели. До сортировки: После сортировки: p[1] 5 p[5] 5 p[2] 1 p[1] 1 p[3] 3 p[4] 2 p[2] 2 Вывод результата: for i: =1 to N do p[i]^ writeln(p[i]^. title, p[i]^. year: 5); © С. В. Кухта, 2010 p[5] 4 p[4] 4
111 Реализация в программе type PBook = ^TBook; { новый тип данных } var p: array[1. . N] of PBook; вспомогательные begin указатели { заполнение массива записей} начальная for i: =1 to N do расстановка p[i] : = @a. Books[i]; for i: =1 to N-1 do for j: =N-1 downto i do if p[j]^. year > p[j+1]^. year then begin temp : = p[j]; меняем только p[j] : = p[j+1]; указатели, записи p[j+1] : = temp; остаются на местах end; for i: =1 to N do writeln(p[i]^. title, p[i]^. year: 5); © С. В. Кухта, 2010 end.


