Главная страница Случайная лекция Мы поможем в написании ваших работ! Порталы: БиологияВойнаГеографияИнформатикаИскусствоИсторияКультураЛингвистикаМатематикаМедицинаОхрана трудаПолитикаПравоПсихологияРелигияТехникаФизикаФилософияЭкономика Мы поможем в написании ваших работ! |
Считывание данных из двумерного массива
program lz; var a:array[1..100,1..100] of integer; i,n,m,j :integer; f :text;
procedure init; begin assign (f,'m2.txt'); reset(f); read(f,n); read(f,m);
for i:=1 to n do for j:=1 to m do read (f,a[i,j]);
close(f); end;
procedure work; begin end;
procedure exi; begin
for i:=1 to n do begin for j:=1 to m do write ('a[',i,',',j,']=',a[i,j],' '); writeln; end; end;
begin init; work; exi; end.
program lz; var a:array[1..100,1..100] of integer; i,n,m,j,c :integer; f :text;
procedure init; begin readln(c);
assign (f,'m4.txt'); reset(f); I:=0; while not eof(f) do begin inc(i); j:=0; while not EOln(f) do begin inc(j); read(f,a[i,j]); end; m:=j; readln (f); end; close(f); n:=i;
end;
procedure work; begin
for i:=1 to n do for j:=1 to m do if c>=a[i,j] then a[i,j]:=0; end;
procedure exi; begin assign (f,'m5.txt'); rewrite (f);
for i:=1 to n do begin for j:=1 to m do write (f,a[i,j],' '); writeln(f); end; writeln (f); close(f); end;
begin init; work; exi; end.
program KKK;
var mas1 :array [1..20,1..20] of longint; i,n,max,indexi,indexj,j,m: longint;
procedure init; begin repeat write ('input n'); readln (n); write ('input m'); readln (m); until n<21; for i:=1 to n do for j:=1 to m do begin write ('input mas1[',i,',',j,']='); readln (mas1 [i,j]); end; end;
procedure work; begin max:=mas1 [1,1]; indexi:=1; indexj:=1; for i:=2 to n do for j:=1 to m do if mas1 [i,j]>max then begin max:=mas1 [i,j]; indexi:=i; indexj:=j; end; end;
procedure exi; begin writeln (max); writeln (indexi,',',indexj); end;
begin init; work; exi; end.
Функции program fs; var i,s,n:integer;
function sumn (n:integer):integer; var i,s:integer; begin s:=0; for i:=1 to n do s:=s+i; sumn:=s; end;
procedure init; begin readln (n); end;
procedure work; begin
s:=sumn(n); end;
procedure exi; begin writeln (s); end;
begin init; work; exi; end.
Сортировка Сортировка линейная program gas; var a:array[1..100] of integer; i,j,n,x:integer;
procedure init; begin write('n '); readln(n); for i:=1 to n do begin write('a[',i,']='); readln(a[i]); end; end;
procedure work; begin for i:=1 to n-1 do for j:=i+1 to n do if a[i]>a[j] then begin x:=a[i]; a[i]:=a[j]; a[j]:=x; end; end;
procedure exi; begin writeln; for i:=1 to n do writeln('a[',i,']=',a[i]) end;
begin init; work; exi; end.
Сортировка методом пузырька program gas; var a:array[1..100] of integer; i,j,n,x:integer; s:boolean; procedure init; begin write('n '); readln(n); for i:=1 to n do begin write('a[',i,']='); readln(a[i]); end; end;
procedure work; begin repeat s:=true; for i:=1 to n-1 do if a[i]<a[i+1] then begin s:=false; x:=a[i]; a[i]:=a[i+1]; a[i+1]:=x; end; until s; end;
procedure exi; begin writeln; for i:=1 to n do writeln('a[',i,']=',a[i]) end;
begin init; work; exi; end.
ВВОДНЫЕ ПОЯСНЕНИЯ И ОПРЕДЕЛЕНИЯ
Модули в Turbo Pascal – это библиотеки определений типов, констант, переменных, процедур и функций. Так же как и подпрограммы, модули могут быть стандартными и нестандартными, или пользовательскими, то есть такими, которые разрабатываются программистами. Как стандартные, так и нестандартные модули устроены совершенно одинаково, поскольку оформляются по единым правилам. Модули подключаются к программе при помощи объявления их имени в директиве компилятора uses. Например, при объявлении:
Программе будут доступны все подпрограммы, переменные, типы и константы, содержащиеся в перечисленных модулях. Модуль System, в котором определены основные константы, типы, переменные и подпрограммы Turbo Pascal, подключается к программе автоматически (по умолчанию). Включение его имени в разделе uses приведет к ошибке на стадии компиляции программы. С модулем Crt мы уже хорошо знакомы, при его подключении становятся доступными описаные в нем константы и подпрограммы, например CtrScr и GoToXY. При помощи модуля Printer и его переменной Lst можно осуществить печать любых сообщений на принтере:
В этом случае принтен выступает в роли файла, но его переменную Lst типа Txt описывать не требуется. Эта переменная после подключения модуля Printer связывается с принтером автоматически, поэтому в применении процедуры Assign не необходимости. Если в роцедуре Writeln (Lst,»Привет») первый параметр опустить, то сообщение будет распечатано на экране дисплея. Об использовании графических подпрограмм модуля Graph будет рассказано в двенадцатом уроке. Сейчас же отметим лишь то, что имена модулей при их объявлении разделяются запятыми, а после последнего модуля должна стоять тоска с запятой! Использование нестандартных модулей позволяет значительно повысить уровень структурированности и стандартизации программ, что приводит к ускорению разработки программ, обеспечивает их понятность и простоту модернизации. Описав в такой библиотеке, например, часто использумые операции при обработке массивов (процедуры ввода, вывода матриц, сортировки массивов и др.), в отдельной программе можно уделить основное внимание лишь решению тех вопросов, которые являются характерными для конкретной задачи и поэтому не реализованы в стандартных и нестандартных модулях. Это не только существенно сокращает время разработки программы, но и уменьшает ее объем. Как уже отмечалось ранее, программы располагаются в специальном сегменте, объем которого, как и всех других сегментов, составляющих оперативную память компьютера, не может превышать 64 Кбайт. Конечно, это достаточно большой объем, но для некоторых программ имеется необходимость в его увеличении. Для достижения этой цели модули оказывают неоценимую услугу. Дело в том, что каждый модуль располагается в отдельном сегменте, поэтому сумма объемов всех сегментов с модулями и программой ограничена лишь объемом оперативной памяти компьютера (640 Кбайт). Мы уверены, что после такого предисловия у читателя уже нет никаких сомнений в полезности применения нестандартных модулей. Возникает лишь непреодолимое желание овладеть искусством их разработки. Для удовлетворения этого желания познакомимся вначале с общей структурой модуля, назначением его разделов и технологией разработки.
СТРУКТУРА МОДУЛЯ Модуль (unit) состоит из следующих частей: - названия или заголовка (например, unit My_Modyl); - интерфейса или раздела объявлений (Interface); - исполнительной части или раздела реализации (Implementation); - раздела или секции инициализаций. Указанная здесь последовательность разделов модуля является обязательной, однако блоки (или только тела), составляющие эти разделы, могу частично или полностью отсутствовать (как и ранее, при описании таких элементов используются квадратные скобки). Далее опишем содержимое разделов модуля.
Заголовок модуля Заголовок модуля состоит из зарезервированного слова unit и уникального идентификатора (не должен повторяться внутри модуля), который может иметь как и любой идентификатор, до 64 символов.
Интерфейс модуля Interface {Все объекты этого раздела, кроме Uses, расположены в сегменте данных} [Uses… {Перечисляются имена модулей, которые используются данным модулем}] [Cons… {Описываются константы, которые должны быть доступны программе или другим модулям, подключившим этот модуль}] [Var… {Описываются переменные, видимые в программе или других модулях, в которых объявлен этот модуль}] [Procedure… {Приводится полный заголовок процедуры с описанием формальных параметров}] [Function… {Приводится полный заголовок функции с описанием формальных параметров}] … {Описываются полные заголовки всех других процдур и функций модуля, доступных программе или другим модулям, подключившим данный модуль}
Исполнительная часть модуля Implementation {Вс объекты, кром Const и Uses расположены в сегменте стека} [Uses… {Перечисляются имена модулей, использованных в данной исполнительной части и не указанных в интерфейсе}] [Label… {Перечисляются имена меток, использованные в подпрограммах данного раздела модуля}] [Const… {Описываются константы, использованные в подпрограммах данного раздела модуля и не объявленные в интерфейсе}] [Var… {Описываются переменные, использование в подпрограммах данного раздела модуля и не объявленные в интерфейсе}] [Procedure… {Приводится сокращенный или полный заголовок роцедуры, объявленный в Interface}] {Тело процедуры} [Function… {Приводится полный или сокращенный заголовок функции, объявленной в Interface}] {Тело функции} Как видите, этот раздел включает в себя описание всех подпрограмм модуля, объявленых в интефейес. В нем могут быть также объявлены типы и переменные, использумые в подпрограмах данного раздела модуля и не описанные в инерфейсе, а также может содержаться перечень локальных меток, применяемых в подпрограммах настоящего раздела. В разделе Implementation могут быть описаны также подпрограммы, участвующие в работе объявленных в интерфейсе процедур и функций, однако сами в нем не объявленные. В программе, подключающей модуль, такие подпрограммы являются недоступными (говорят, что они невидимы в программе). Недоступны для программы также модули, метки, константы, типы и переменные, объявленные в этом разделе. Все эти объекты, кроме const, располагаются в сегменте стека. Значения констант находятся в сегменте данных, однако, в программе они вес же невидимы.
Раздел инициализации Этот раздел позволяет установить связь с теми или иными файлами с помощью процедуры Assign, инициировать какие-то переменные (например, положить S:= 0; P:= 1), константы-переменные (типизированные константы), выполнить какие-то другие одноразовые действия, которые всегда будут исполняться в начала программы, например, печать сообщения на экране при помощи процедуры WriteLn («Привет!»); Раздел начинается словом begin после которого могут стоять исполнительные операторы, а заканчивается словом end. (с точкой).
Если инициализация чего-либо (файлов, переменных, типизированных констант) не требуется, то в разделе помещается лишь слово end. (с точкой). Если в программе и ее модуле описываются переменные с одним и тем же именем, то такая переменная модуля в программе является невидимой. Ее доступность в программе можно обеспечить лишь при помощи квалификатора, который ставится перед такой переменной и отделяется от нее точкой. В качестве квалификатора выступает имя модуля. Например, в теле программы с модулем My_Modyl возможны такие операторы:
Аналогичным образом можно поступить и для обеспечения видимости переменной модуля программы в любой ее подпрограмме, если имена переменной модуля и локальной переменной подпрограммы совпадают. Более того, при помощи квалификатора можно сделать видимой в подпрограмме глобальную переменную. Программы, одноименную локальной переменной подпрограммы:
Если к вашей программе подключен модуль, в интерфейсе которого объявлены другие модули, то все объекты этих модулей в программе окажутся невидимыми. Для обеспечения их доступности в программе все их имена необходимо объявить непосредственно в директиве компилятора uses программы.
Этапы разработки модуля Модуль – это вспомогательный объект, поэтому его нельзя запустить на выполнение командой run (Ctr+F9). Он может только участвовать в построении программ, подпрограмм или других модулей. Процесс разработки модулей имеет два этапа. На первом этапе необходимо в отдельной файле набрать текст модуля. Конечно, удобнее это выполнять в турбосреде. Имя этого файла должно совпадать с первыми восемью символами имени модуля и иметь расширение .pas. Файлы с модулем и программой должны находиться в одной и той же директории. Каждый модуль необходимо поместить в специальную библиотеку, она называется TPU (Turbo Pascal Unit). Для размещения модуля в библиотеке TPU программу следует откомпилировать командой make (F9) или запустить ее на выполнение командой run (Ctr+F9). После этого в текущей директории будет создан файл (модуль) с прежним названием, но с расширением .tru. Файл (модуль) с расширением .pas после этого, в принципе, можно уничтожить, для работы программы он не нужен, но делать это мы настоятельно не рекомендуем, поскольку этот файл может еще понадобиться для последующей модернизации модуля, а также для уточнения его содержимого. Дело в том, что содержимое файлов модуля с расширением .tru представлено не в символах ASCII, как содержимое файлов с расширением .pas, а в машинном коде, поэтому его невозможно изучать и редактировать. Если вы желаете поместить файл модуля с расширением .tru не в текушей, а в заданной директории, то для этого необходимо выполнить следующие команды: F10/Option/Directories/Unit directories {Указать путь к директории в прямоугольнике с таким заголовком} Еще один способ создания файла модуля с расширением .tru (или объектного модуля) состоит в следующем. Для параметра compile/destination при помощи клавиши Enter следует установить значение disk и запустить модуль на выполнение командой Ctr+F9 (Run). При этом осуществится компиляция модуля. Компилятор Turbo Pascal 7.0 распознает в начале текста модуля служебное слово unit и автоматически создаст файл с расширением .tru вместо файла с расширением .exe (как для обычных программ). Сообщение, которое появится при этом на экране дисплея «Can not run unit», просто информирует вас о том, что модуль самостоятельно не выполняется.
Пример использования модуля Проиллюстрируем использование модулей на примере следующей программы. Эта программа в двумерном массиве Mate (n,m) меняет местами максимальный и минимальный элементы. Исходные данные матрицы Matr (n,m) находятся в файле Isx_Dan.pas, резльтаты – в файле Rez_Dan.pas. Процедуры ввода и вывода данных, инициализации файлов, глобальные константы, типы и переменные находятся в объектном модуле Mod_Matr.tru. Обращаем ваше внимание на то, что в этой программе в процедуре Zamena при поиске минимального и максимального значений матрицы использована только переменная для хранения минимального значения, а переменная для заполнения максимального значения не применяется.
Вопросы для самоконтроля - Назовите две основные цели, которые достигаются при использовании модулей. - Перечислите разделы модуля и укажите их назначение. - Можно ли использовать в программе подпрограмму, описанную в исполнительной части ее модуля и не объявленную в интерфейсе? - Можно ли в программе использовать константы, переменные и подпрограммы, описанные в интерфейсе модуля, который подключается к модулю, объявленному в вашей программе? - Как можно использовать в программе переменные, которые в программе и ее модуле объявлены под одним именем?
Задача для программирования Вариант решения задачи Содержимое файла baza.pas Вопросы для самоконтроля - С какими тремя ограничениями связано использование переменных перечисляемого типа? - Почему имена полей всех вложенных друг в друга записей могут совпадать? - Как определить максимальное число полей записей с заданными типами полей? - Зачем используются вариантные поля записи? - В каких случаях применяется вариантная часть записи с пустым полем? - Когда целесообразно прибегать к использованию оператора присоединения With? - Можно ли обрабатывать внутри записи переменные программ, подпрограмм и модулей, имена которых совпадают с именем поля записи? - Что называют базой данных и в чем заключается ее обработка? - Почему описание вложенных записей следует начинать с записи, вложенной последней?
Задачи для программирования Варианты решения задач
ДИНАМИЧЕСКИЕ ПЕРЕМЕННЫЕ Предварительные замечания Все переменные, которые мы использовали до настоящего времени, являются статическими переменными. Необходимая память под такие переменные выделяется компилятором в сегменте данных еще до начала работы программы (в процессе ее компиляции), и при работе программы объем памяти не изменяется. Напомним, что сегмент данных – это непрерывная область оперативной памяти с объемом в 65 536 байт (64 Кбайт). Динамические же переменные могут размещаться в памяти компьютера и убираться из нее в процессе работы программы. Так, например, если введенные динамические переменные уже обработаны и до конца программы использоваться не будут, то их можно убрать из памяти компьютера, а на освободившемся месте разместить другие динамические переменные, необходимые по ходу выполнения решаемой задачи. Именно по этому свойству динамические переменные и получили свое название. Очевидно, что указанное свойство таких переменных весьма полезно при обработке крупных массивов данных. Область памяти, в которой размещаются динамические переменные, называется кучей (heap), максимальный ее объем более чем в 6 раз превышает объем сегмента данных и составляет около 400 Кбайт. Это не означает, что отдельные переменные (запись или массив) могут иметь такой большой размер. Конечно же, нет: ни одна переменная в Turbo Pascal не может превышать объем 65 52- байт. Однако распределить составляющие записи или массива по различным сегментам памяти оказывается вполне возможным. При этом общий объем «распределенной» переменной может существенно превысить объем отдельного сегмента Распределение переменой по разным сегментам кучи особенно полезно, когда ее объем заранее предсказать нельзя. Механизм осуществления такого распределения мы рассмотрим в п. 11.6. Здесь же лишь кратко перечислим уже упомянутые случаи, при которых рекомендуется применять динамические переменные. 1. Необходимы переменные, имеющие большой объем и освобождающие память после их использования. 2. Размер переменной труднопредсказуем. 3. Размер переменной превышает 64 Кбайт.
Распределение оперативной памяти Мы уже отмечали, что объем оперативной памяти Turbo Pascal составляет около 64- кБайт. Эта память делится на сегменты, каждый из которых не превышает 64 Кбайт. В отдельном сегменте размещается код главной программы. Если к программе подключены стандартные и нестандартные модули, то по одному такому сегменту выделяется и для каждого модуля. Корме этого, сегмент памяти обязательно выделяется под стандартный модуль System, автоматически подключаемый к каждой программе. Все перечисленные сегменты носят название сегментов кода, поскольку в них содержатся коды программы или ее модулей. С сегментами данных и стека мы уже хорошо знакомы. Однако еще раз напомним, что в сегменте данных располагаются обычные и типизированные константы, статические переменные, объявленные в главной программе и в интерфейсах ее модулей. При этом отметим, что, независимо от места объявления, обычные и типизированные константы (локальные и глобальные) всегда располагаются в сегменте данных. В сегменте стека хранятся локальные переменные программы и переменные исполнительных частей ее модулей в периоды работы подпрограмм, где эти переменные описаны. Размеры всех сегментов, кроме сегмента стека, определяются в процессе компиляции и во время работы программы не меняются. Размер сегмента стека, как и размер любого другого сегмента, не может превышать 64 Кбайт, по но умолчанию (в случае отсутствия специальной директивы компилятора) его объем устанавливается равным 16 Кбайт. Упомянутые сегменты, используемые при работе программы, располагаются в определенном порядке: вначале сегмент главной программы, затем кодовые сегменты модулей, следом сегмент данных и, наконец, сегмент стека. За сегментом стека следует оверлейный буфер, размер которого может регулироваться программистом его назначение несколько аналогично буферу, используемому при работу с файлами. Оставшаяся свободная область частично или полностью может быть занята динамическими переменными, поэтому она называется динамической памятью. Размеры сегмента стека и динамической памяти могут устанавливаться (регулироваться) с помощью директивы компилятора, имеющей вид: {$М «Размер стека», «Минимальный объем динамической памяти», «Максимальный объем динамической памяти»} Эту директиву применяют в теле программы (после слов BEGIN) перед использованием соответствующих переменных, память в ней измеряется в байтах. Конкретный пример использования такой директивы:
На рис. 11.1 приведена компактная схема распределения оперативной памяти, или карта памяти, Turbo Pascal. В этой схеме указанная последовательность сегментов следует снизу вверх. Это достаточно удобно и оправдывает использование синонима “куча” для динамической памяти, которая располагается на схеме в самом верху и, как всякая куча, заполняется снизу вверх.
Рис. 11.1 Схема распределения оперативной памяти
Согласно этой схеме при отсутсиви стандартных и нестандартных модулей максимальный объем динамической памяти, грубо говоря, может составлять 640-64*3 – 16 + 432 Кбайт. Таким образом, при работе с динамическими переменными вы можете рассчитывать на кучу с максимальным объемом около 400 Кбайт. Как вы знаете, только переменные таких типов, как byte, char, shortint и boolean, занимают во внутреннем представлении машины один байт (одну ячейку), для размещения переменных всех других простых типов требуется уже несколько смежных ячеек. Структурированные переменные, например, такие как массивы и записи. Могут занимать значительно большие объемы памяти. Необходимо заметить, что память под динамические переменные выделяется порциями по 8 байт. Поэтому, например, для переменных типа char и shortint, имеющих размер в один байт, и для real, с объемом в 6 байт, будет выделено по 8 байт. Блоки памяти под переменные больших размеров (массивы, записи и др.) будут кратны 8. Каждый байт памяти компьютера располагается в отдельной ячейке. Каждая ячейка памяти имеет свой адрес, по которому к ней можно обратиться. Поэтому адрес часто еще называют указателем или ссылкой. Ясно, что и первые ячейки всех переменных также имеют свои адреса – ведь они ничем не хуже всех других. При этом адреса ячеек называют адресами соответствующих переменных. Следует помнить, что адреса всех переменных, как статических, так и динамических, располагаются в сегменте данных и поэтому принадлежат к статической памяти. Каждый такой адрес занимает 4 байт и состоит из двух частей – двух шестнадцатиразрядных слов, которые называются сегментом и смещением. Сегмент – это адрес первой ячейки одного из сегментов памяти, объемом в 64 Кбайт. Смещение указывает число ячеек (или байт), на которое текущий адрес отличается от адреса первой ячейки требуемой переменной. Таким образом, адрес переменной – это только адрес первого байта (ячейки) данных. Определить адрес любой переменной, как статической, так и динамической, можно при помощи специальных стандартных функций на рассмотрение которых мы здесь не будем останавливаться. Еще раз отметим, что адреса (или ссылки, указатели) динамических переменных являются статическими переменными и располагаются в сегменте данных, но данные или объекты, на которые они указывают (ссылаются), находятся в куче – динамической памяти. С помощью указателей (этот синоним употребляется более часто) можно разместить в динамической памяти переменные любых типов. Указатель переменной, как уже отмечалось, в этом случае будет ссылаться на первый байт области памяти, выделенной конкретной переменной заданного типа, а следовательно, и размера. В п. 11.3 мы подробно рассмотрим правила использования динамических переменных или переменных с адресом или указателем. Сейчас же более подробно рассмотрим «устройство» кучи при помощи более детальной схемы верхней карты памяти, приведенной на рис. 11.2. Стрелочками обозначены типизированные константы-указатели. Все они описаны в модуле System. Первоначально их значение равно Nil. Nil – это пустая ссылка, то есть адрес ячейки в статической памяти, которая не связана ни с каким объектом в куче, и поэтому объем такой динамической переменной равен нулю. Далее мы подробно рассмотрим, каким образом в эти типизированные константы-указатели записываются адреса сооветствующих ячеек кучи. Сейчас же лишь отметим, что при правильной организации работы программы с помощью этих указателей удобно осуществлять управление состоянием кучи. В принципе, можно для аналогичных целей придумать свои идентификаторы. Однако разумнее применять уже существуцющие, покольку их объявление не требуется (они уже описаны в модуле System), основываются они на двух английских словах, подсказывающих смысл этих переменных. Заметим здесь, что ptr – сокращение служебного слова pointer (стрелка, указатель) – указатель без типа (нетипизированный указатель или ссылка); org – от origin – начало.
Рис. 11.2. Верхняя часть карты памяти
Процедуры New и Dispose Процедура New (Var P : Pointer) отводит место для динамической переменной и присваивает ее адрес ссылке P(New - новый). Для того чтобы эта процедура выделила необходимое место для такой переменной в разделе описания var, необходимо указать тип (фактический размер) переменной (стандартный или пользовательский). С этой целью слева от идентификатора типа ставится знак карата «^». Этот знак фактически указывает размер переменной, которую порождает процедура New. Последующее использование этой же процедуры позволит выделить место для другой динамической переменной, адрес первой ячейки которой будет сдвинут относительно адреса первой ячейки первоначальной переменной на то количество ячеек (байт), которые требуются для размещение первоначальной переменной. Процедура Dispose (Var P : Pointer) уничтожает связь, созданную ранее процедурой New, между указателем Р и объектом, на который он указывает (to dispose - освобождать). Обычно глубокое понимание «устройства» динамических переменных и усвоение правил работы с ними приходит далеко не сразу, и поэтому к некоторым абзацам настоящего раздела вам придется возвращаться несколько раз. Следующий фрагмент программы поясняет использование упомянутых процедур:
В разделе описания переменных место под динамические переменные еще не выделятся, а компилятору лишь сообщается информация о том, что в теле программы переменная заданного типа (размера) может быть выделена. В теле программы процедура New выделяет в куче необходимое количество смежных ячее под заданную динамическую переменную (в примере это Ykazat_1) и адрес первой ячейки этого блока ячеек записывает в ячейках статической памяти, присваивая им имя динамической переменной. Таким образом, динамическая переменная состоит из двух частей: статической переменной – адрес (имя переменной) и объекта, расположенного в куче по указанному адресу. Для обращения к такому объекту знак карата следует ставить после идентификатора динамической переменной. Процедура Dispose (Ykazat_1) возвращает состояние кучи, которое было в ней до использования процедуры New (Ykazat_1). Ячейки объекта переменой Ykazat_1 могут теперь быть использованы для размещения в куче других объявленных переменных: Ykazat_2, Ykazat_3. Очень наглядно обе части динамической переменной представлять в виде прямоугольников, соединенных стрелкой. Такая модель динамической переменной приведена на рис. 11.3. В первом прямоугольнике “хранится” адрес, а во втором – объект динамической переменной. Стрелка как бы связывает конкретные ячейки статической памяти, в которых указан адрес объекта, с ячейками памяти в куче, выделенные под объект по указанному адресу. Рис. 11.3. Модель динамической переменной Прокомментирует работу ледующего фрагмента программы.
Если указатели однотипны, как в вышеприведенном примере переменные А и В, то их значения можно присваивать друг другу (А:= В;). Для таких указателей допустимы операции сравнения (А> В). Если же типы указателей различны, то присваивать их значения друг другу нельзя. Это ограничение реализовано в синтаксисе языка: за его выполнением строго следит компилятор. Поэтому, если даже размеры разных типов одинаковы (например, nyte и char), то компилятор все равно взаимные присваивания между ними не позволит. Однако в Turbo Paskal имеется специальный механизм, который позволяет все же обойти это ограничение. Он основывается на использовании не типизированных указателей pointer. Такие указатели объявляются стандартным образом:
Нетипизированному указателю может быть присвоено значение любого типизированного указателя либо наоборот. Очевидно поэтому, что между нетипизированными указателями допустимы взаимные присваивания, как и для любых однотипных переменных. Нетипизированный указатель представляет собой своеобразный буфер, выделенный в сегменте данных для хранения адреса динамической переменой любого типа. Типизированному и нетипизированному указателю может назначаться адрес, либо Nit. Идентификатор процедуры New может также использоваться в качестве функции, возвращающей указатель заданной переменной.
Ранее отмечалось, что процедура Dispose возвращает состояние кучи, которое было в ней до использования процедуры New, и ячейки, ранее занятые под уничтоженную переменную, могут быть использованы для размещения в куче любых других объявленных переменных. Однако это утверждение не совсем верно. Дело в том, что в освободившемся блоке ячеек могут быть размещены только объекты тех переменных, размеры которых либо равны числу освободившихся ячеек, либо меньше этого числа. В первом случае куча заполняется информацией снизу вверх без пропусков ячеек памяти. Во втором случае, когда размер освободившегося блока памяти больше размера объекта новой динамической переменной, блок ячеек заполняется лишь частично. Конечно, он может быть дополнительно заполнен выведением последующей динамической переменной, если ее размер не будет превышать этот остаток. Однако во всех случаях, когда под новую переменную необходим большой блок ячеек, такой блок выделяется сразу же за блоком памяти объекта последней динамической переменной, если даже он совсем пуст или же заполнен лишь частично. В связи с этим может возникнуть ситуация, при которой программа после ряда последовательных порождений и уничтожений динамических переменных не сможет разместить в куче очередную переменную, размер которой будет превышать размер всех выделяемых и освобождаемых до этого блоков. Таким образом, куча будет фактический пустой, но из-за ее фрагментации в ней уже невозможно будет поместить новую переменную. Конечно, такая ситуация может возникнуть только в отдельных программах. Но само наличие такой возможности является недостатком процедуры New, и это следует принимать во внимание при программировании. Однако в Turbo Pasсal имеется эффективный способ управления кучей, в котором этот недостаток отсутствует. Его мы рассмотрим в п. 11.5.
Процедуры GetMem и FreeMem Процедура GetMem (Var P:Pointer; Razmer : Word) выделяет в куче непрерывный блок ячеек с требуемым размером в Razmer байт и адрес первой ячейки этого блока присваивает указателю Р. Процедура FreeMem освобождает в куче непрерывный блок ячеек с размером в Razmer байт, начиная с адреса, записанного в указателе Р (Get и Free – соответственно выделять и особождать, Mem от Memory - память). Процедура GetMem аналогична процедуре New, а процедура FreeMem – процедуре Dispose. Если в процедурах New и Dispose размер блока под объект динамической переменной определяется автоматически на основе типа выбранной динамической переменной (или, как часто говорят – типа данных, на который ссылается выбранный указатель), то в процедурах GetMem и FreeMem размер такого блока должен контролироваться программистом. Для облегчения этого процесса имеются специальные процедуры и функции. Эти подпрограммы мы широко будем использовать в двенадцатом занятии, поэтому здесь ограничимся лишь следующим замечанием: необходимо четко следить за тем, чтобы объем овобождаемого блока памяти совпадал с объемом памяти, выделяемым при размещении в этом блоке объекта динамической переменной. В противном случае может появиться информационный “мусор” в куче либо часть данных переменной будет бесследно исчезать. Информационный “мусор” наблюдается тогда, когда освобождаемый блок меньше объема объекта уничтожаемой динамической переменной. Исчезновение же данных происходит либо при размещении объекта динамической переменной в блоке недостаточного размера, либо при уничтожении блока, объем которого больше ранее отведенного, поскольку в этом случае могут уничтожиться частично или полностью данные другой перемнной, объект которой примыкал к этому блоку. Сходство упомянутых пар процедур позволяет использовать их одновременно в одной и той же программе. Более того, в паре процедур New-Dispose вместо процедуры New можно применять процедуру GetMem, вместо Dispose – FreeMem и, наоборот – в паре GetMem – FreeMem вместо GetMem - New, а вместо FreeMem – Dispose. К сожалению, при работе пары процедур GetMem и FreeMem также возможна фрагментация кучи.
Управление блоками динамической памяти Искючить фрагментизацию кучи позволяет применение процедур Mark (метит) и Release (освобождать). Процедура Mark (Var P:Pointer) запоминает в указателе Р нижнюю текущую границу свободной памяти кучи – адрес первой свободной ячейки, расположенной сразу же за конечной ячейкой объекта самой последней динамической переменной. Таким образом, в Р запоминается адрес первой ячейки неиспользуемой части кучи. В этом случае динамическая переменная-указатель фактически дублирует функции типизированной константы-указателя HeapPtr и с успехом может быть заменена ею. Процедура Releas (Var P:Pointer) возвращает кучу в состояние, которое было зафиксировано ранее в Р высовом процедуры Mark. Если после вызова процедуры Mark (P) вы порождали (при помощи New и GetMem) целый ряд новых динамических переменных (любых типов), то вызов процедуры Release (P) после таких порождений передвигает нижнюю границу свободного пространства кучи Heap Ptr с позиции (с адреса), которую она занимала после порождения последней динамической переменной, в позицию (в адрес), которая ранее была зафиксирована в указателе Р. При этом все переменные, порожденные после вызова процедуры Mark (P), уничтожаются и все смежные блоки ячеек уничтоженных переменных пригодны для размещения в них любых других динамических переменных без эффекта фрагментации. Если перед порождением первой динамической переменной в тимизированную константу-указатель HeapOrg записать исходное состояние кучи (при помощи Mark (HeapOrg), то вызовом процедуры Release (HeapOrg) можно полностью освободить кучу от всех динамических переменных, которые до такого вызова пророждались, например, вызовом процедуры New. Приведем поясняющий фрагмент программы:
После вызова двух последних процедур приведенного фрагмента программы в указателях Р и PheaPtr будет записан один и тот же адрес – указатель первой ячейки кучи, расположенной за последней ячейкой объекта самой «верхней» динамической переменной Р2. Переменные Р3 и Р4, порожденные после Р2, будут уничтожены и адрес, записанный в Р, окажется адресом первой ячейки неиспользованной памяти кчи: новой нижней границы свободной области кучи. При таком способе управления кучей нельзя удалить, например, переменную Р2, не удалив при этом переменные Р3 и Р4. Это ограничение является определенным недостатком процедур Mark и Release. Для более гибкого использования кучи необходимо применять процедуру Dispose и FreeMem. В одной и той же программе не рекомендуется применение “блочных” процедур Mark и Release совместно с процедурами Dispose и FreeMem. Это обусловлено следующим обстоятельством. При использовании процедур Dispose и FreeMem. Параллельно с освобождением ими блоков памяти объектов конкретных динамических переменных адреса (координаты) освобожденных блоков заносятся в специальный список адресов и объемов свободных блоков, который применяется всякий раз для последующего размещения в куче новых динамических переменных. Процедура же Release (H) не только перемещает указатель на новую нижнюю границу свободной части кучи, но и стирает весь список свободных блоков. Таким образом, если освобожденный процедурой Dispose или FreeMem блок памяти оказался ниже указателя Р, то после применения процедуры Release (Р) до конца работы программы в этом блоке больше не размещаются новые динамические переменные с использованием процедур New или GetMem. Это необходимо всегда помнить, дабы постепенно не заблокировать всю кучу от размещения там каких-либо переменных. В заключение отметим, что хорошим стилем программирования является уничтожение динамических переменных после их обработки (если это даже не требуется для размещения новых динамических переменных) или в конце программы. Это означает, что для каждой процедуры New должны иметься парная ей процедура Dispose, а для каждой процедуры GetMem – процедура FreeMem. Если применяется блочная работа с динамичесекой памятью при помощи подпрограмм Mark и Release, то перед порождением первой динамической переменной необходимо вызвать процедуру Mark (Р), а в конце программы – процедуру Release (Р) для полного освобождения кучи от динамических переменных вашей программы. Нарушение этих рекомендаций не приведет к сбоям в работе программы, а будет лишь свидетельствовать о плохом стиле программирования автора программы.
Обработка переменных с объемом больше 64 Кбайт Мы уже неоднократно отмечали, что сумма размеров всех статических глобальных переменных обычных и типизированных констант не может превышать объем сегмента данных в 64 Кбайт, размер типа переменной не может быть больше 65 520 байт. Например, тип, определяемый ниже, будет забракован компилятором из-за того, что его размер превышает допустимую границу.
Действительно, размер переменной такого типа будет составлять 200*200*6 байт = 240 000 байт = 234 кБайт, что более чем в 3,6 раза превышает размер сегмента данных. В начале настоящего раздела мы отмечали, что переменную такого типа можно разместить в динамической памяти. Но это не совсем верно. Дело в том, что в куче каждая переменная должна обязательно целиком располагаться в отдельном сегменте, ее нельзя разместить в несколько сегментах, пусть даже они представляют собой сплошной блок памяти. Кроме этого, как неоднократно отмечалось, максимальный объем переменной равен 65 520 байт. Вместе с этим, если переменную представить в виде частей, каждая из короых не будет превышать указанный максимальный объем, то в сумме размер такой расчлененной переменной может быть около 400 Кбайт. Разобъем переменную на части следующим образом:
Здесь в сегменте данных задается статический массив на 200 указателей (адресов динамических переменных). Каждый указатель ссылается на объект, расположенный в куче и представляющий собой строку матрицы из 200 элементов типа Real. Размер этого пользовательского типа равен 200*4 = 800 байт. Объем объекта каждой такой динамической переменной (строки матрицы) составляет всего лишь 200*6 = 1200 байт, поэтому такие переменные могут располагаться как в одном и том же, так и в разных сегментах кучи. Общий объем всех таких объектов, как уже упоминалось ранее, равен 234 Кбайт и поэтому вполне может быть размещен в куче. Для размещения и освобождения такой структуры удобнее всего использовать цикл for:
Поскольку объектом каждой динамической переменной является строка матрицы (номера ее элементов – это номера столбцов), то для обращения к этому объекту знак карата необходимо поставить сразу же за квадратными скобками, в которых находится номер строки. Номер столбца элемента строки следует поместить в квадратных скобках за знаком карата. Разрешен только такой синтаксис. Обращение в виде
Является ошибочным. Далее в качестве примера работы с массивом объемом более 64 Кбайт приведем программу расчета (табуляции) функции
Вопросы для самоконтроля - В каких случаях возникает необходимость прибегать к использованию динамических переменных? Чем они отличаются от статических переменных? - Из каких частей состоит динамическая переменная? В каких областях оперативной памяти компьютера находится каждая из них? - Какой объем памяти занимает адрес объекта динамической переменой? Из каких частей он состоит? - Сделайте оценку максимального объема памяти под динамические переменные, который при необходимости были бы использовать в одной из ваших программ. - Переменная не может иметь размер, превышающий 65 520 байт. Можно ли в некоторых случаях устранить такое ограничение? - Какие типы данных могут быть записаны в указатель и объект динамической переменной? - Как вводятся указатели на выбранный тип переменных? - Приведите отличительные признаки обращения к указателю и объекту динамической переменной. - Для каких целей служит тип pointer? - Назовите общие и отличительные черты применения парных процедур New-Dispose и GetMem-FreeMem. Укажите их достоинства и недостатки. - С какой целью применяются процедуры Mark и Release? Укажите достоинства и недостатки их применения. - Почему процедуры Mark и Release нежелательно использовать в одной и той же программе с процедурами Dispose и FreeMem?
Задача для программирования
Вариант решения задачи
Что такое программа? Что такое язык программирования? Назовите известные вам языки? Приведите пример алфавита любого языка программирования. Чем отличается программа-компилятор от программы-интерпретатора? Что такое константа и переменная? Какие типы констант и переменных вы знаете? Какие существуют правила записи арифметических выражений? Какие существуют правила записи логических выражений? Приведите примеры команды ветвления. Приведите примеры команды цикла с параметром. Приведите примеры команды цикла с предусловием. Приведите примеры команды цикла с постусловием. Для чего используются подпрограммы, и какие виды их вы знаете? Какие стандартные математические функции вы знаете? Какие функции обработки строковых переменных вы знаете? Приведите примеры команд ввода, вывода данных. Что включает в себя среда программирования? Что следует понимать под инструментарием программирования? В чем состоит различие между алгоритмическими и объектно-ориентированными языками программирования?
Из базового курса информатики Вам известно, что Алгоритм - набор точных предписаний, однозначно задающих содержание и последовательность выполнения шагов (действий) для решения определенного класса задач. Свойства алгоритма: однозначность, дискретность, сходимость, массовость.
Дата добавления: 2014-11-24; просмотров: 454; Нарушение авторских прав Мы поможем в написании ваших работ! |