Академический Документы
Профессиональный Документы
Культура Документы
Записи
Запись – это структура данных, состоящая из фиксированного числа
компонентов, называемых полями записи. В отличие от массива, компоненты
(поля) записи могут быт различного типа. Чтобы можно было ссылаться на
тот или иной компонент записи, поля именуются.
Структура объявления записи такова:
<имя типа>= record <список полей> end;
Здесь <имя типа> – правильный идентификатор;
record, end – зарезервированные слова;
<список полей> – список полей; представляет собой последовательность
разделов записи, между которыми ставится точка с запятой.
Каждый раздел записи состоит из одного или нескольких
идентификаторов полей, отделяемых друг от друга запятыми. За
идентификатором (идентификаторами) ставится двоеточие и описание типа
поля (полей), например:
Type
Birthday=record
day, month: byte;
year : word;
end;
var
a,b: Birthday;
...
a.day:=27;
b.year:=1939;
var
c: record
name : string;
bd : Birthday;
end;
...
if c.bd.year=1939 then ...
Оператор WITH
Если в программе содержится большое число обращений к
компонентам записи (полям), то указание имени записи и имени поля с
помощью сочленяющей точки будет громоздким . Для упрощения вводится
оператор WITH - присоединение. Его еще называют оператором над
записями.
Общая форма
80
gl.price := 200;
with gl do
begin
price := 200;
number := 10
end;
program store;
const Max_n = 100;
type
str20 = string [20];
goods = record
name : str20;
price : real;
number : integer;
end;
var stock : array[l..Max_n] of goods;
i, j, len : integer;
name : str20;
found : boolean;
count : integer;
begin
write('Введите количество товаров: ');
readln(count);
for i:=1 to count do begin
writeln(‘Введите в отдельных строках наимен., цену и кол-во’);
81
with stock[i] do
readln(name, price, number);
…
end.
82
sg:real;
begin
writeln('сколько студентов в группе?');
read(n);
for i:=1 to n do
with ved[i] do
begin
writeln('введите фамилию студента');
read(fam);
writeln('введите оценки');
read(fiz,mat,pr)
end;
sg:=0;
for i:=1 to n do
with ved[i] do
begin
ss:=(fiz+mat+pr)/3; {вычисление среднего балла
студента}
sg:=sg+ss;
end;
sg:=sg/n;{вычисление среднего балла группы}
writeln('ведомость группы');
write('! фамилия ! физика ! матем ! прогр !');
writeln('! cp. балл !')
for i:=1 to n do
with ved[i] do
begin
write('!',fam:10,'!',fiz:10,'!',mat:10,'!',pr:10);
writeln('!',ss:10:2,'!');
end;
writeln('средний балл в группе =',sg);
end.
Иногда бывает необходимо иметь в программе несколько родственных,
но не совсем идентичных записей. Такая необходимость возникает,
например, для программы, которая обрабатывает информацию о человеке и
83
тогда, в зависимости от значения поля sex (мужской или женский),
появляются поля:
время прохождения очередных военных сборов;
род войск, в которых проходил военный сбор;
или же:
любимые цветы.
Для таких случаев в Турбо-Паскале предусмотрены записи с
вариантами. Такие записи содержат фиксированную и вариантную часть,
которая начинается с ключевого слова case. Рассмотрим пример:
type personsex=(male,female);
person = record
name,secondname,surname : string[20];
birthday : date;
case sex : personsex of
male : ( army1 : date;
army2 : string[20]);
female : (flower : srting[20]);
end;
Следует отметить, что вариантная часть всегда располагается после
фиксированной части, а отводимая память вычисляется по самому большому
варианту, т.е. различные варианты одной записи как бы "накладываются"
друг на друга.
84
программируете на Паскале, но раньше не работали с указателями. Она
охватывает следующие темы:
− Зачем и когда используются указатели.
− Что такое указатель.
− Как использовать указатели.
− Эффективная работа с указателями.
85
Когда вы описываете в Turbo Pascal глобальные переменные,
компилятор выделяет для них память в области, которая называется
сегментом данных. Сегмент данных имеет максимальный размер 64К. Это
означает, что общий объем всех ваших глобальных переменных не может
превышать 64К. Для многих программ этот предел значения не имеет, но в
некоторых случаях вам может потребоваться больший объем.
Примечание: Локальные переменные не помещаются в сегмент данных
и в пределе 64К не учитываются.
Предположим, например, что у вас есть программа, требующая массива
в 400 строк по 100 символов каждая. Для этого массива требуется примерно
40К, что меньше максимума в 64К. Если остальные ваши переменные
помещаются в оставшиеся 24К, массив такого объема проблемы не
представляет.
Но что если вам нужно два таких массива? Это потребовало бы 80К, и
64К сегмента данных не хватит. Чтобы работать с большими объемами
данных, вам нужно использовать динамически распределяемую область
памяти. Ваша программа может выделить в динамически распределяемой
области 80К, поддерживая указатель в виде ссылку на адрес данных.
Указатель занимает в сегменте данных только 4 килобайта.
Что такое динамически распределяемая область памяти?
Динамически распределяемая область памяти - это вся память, которую
ваша операционная система делает доступной для программы и которая не
используется ее кодом, сегментом данных и стеком. Объемом
распределяемой динамической памяти вы можете управлять с помощью
директивы компилятора $M.
Обычно в Turbo Pascal вы можете зарезервировать память в
динамически распределяемой области, получить к ней доступ через
указатель, а затем снова освободить память. Подробности о распределении
памяти в динамически распределяемой области вы можете найти ниже в
разделе "Как использовать указатели?".
86
Работа с данными неизвестного размера
Некоторые элементы данных Turbo Pascal (в частности, строки и
массивы) требуют задания размеров во время компиляции, даже если при
выполнении программы вам не потребуется вся выделенная память. Простым
примером может быть программа, считывающая вводимую пользователем
строку, например, имя пользователь. Чтобы записать имя в обычной
строковой переменной, вам потребовалось бы зарезервировать достаточно
памяти для максимальной возможной строки, даже если набранное имя
содержит всего несколько букв. Если вы распределяете переменные в
динамически распределяемой области памяти во время выполнения, то
можете выделить точно столько байт, сколько необходимо для фактической
строки данных.
Это тривиальный пример, но в приложении, содержащем сотни и тысячи
таких элементов данных (таких как множественные окна или считываемые из
файлов списки) выделение точного объема пространства может вместо
ситуации нехватки памяти привести к успешному выполнению.
87
данные обратно в исходный массив. Это сохраняет целостность ваших
данных, но требует также наличия во время сортировки двух копий данных.
Если вы хотите распределить сортируемый массив в динамически
распределяемой памяти, то можете отсортировать его и скопировать обратно
в оригинал, а затем уничтожить сортируемый массив, освободив память для
других нужд.
Связанные списки
Одним из общих случаев использования указателей является соединение
связанных списков записи. Во многих простых приложениях типа баз данных
вы можете размещать записи данных в массивах или типизированных
файлах, но иногда требуется что-то более гибкое, чем массив, который имеет
фиксированный размер. Распределяя динамические записи, так что каждая
запись имеет поле, указывающее на следующую запись, вы можете
построить список, содержащий столько элементов, сколько вам требуется.
88
Что такое указатель?
Указатель - это какой-либо адрес в памяти вашего компьютера. Это
может быть адрес переменной, записи данных, либо процедуры или функции.
Обычно вам не важно, где расположен элемент в памяти. Вы можете просто
ссылаться на него по имени, и Turbo Pascal знает, где его нужно искать.
Именно это происходит, когда вы описываете переменную. Например,
если программа включает в себя следующий код, то вы указываете
компилятору на необходимость зарезервировать область в памяти, на
которую будете ссылаться по имени SomeNumber.
Ссылочный тип
Чтобы хранить указатели, вам требуется переменная-указатель, а для
создания переменной-указателя вам необходим ссылочный тип (или тип
"указатель"). Простейшим ссылочным типом является стандартный тип с
именем Pointer. Переменная типа Pointer - это общий (нетипизированный)
указатель, то есть, просто адрес. Он не содержит информации о том, на что
он указывает.
Таким образом, чтобы использовать тот же пример SomeNumber, вы
можете присвоить его адрес переменной-указателю:
var
SomeNumber: Integer;
89
SomeAddress: Pointer;
begin
SomeNumber := 17; {присвоить SomeNumber значение}
SomeAddress := @SomeNumber; {присвоить SomeAddress адрес}
SomeAddress := Addr(SomeNumber); {другой способ получения
адреса}
end.
Типизированные указатели
Обычно вы определяете ссылочные типы, которые указывают на
конкретный вид элемента, например, целое значение или запись данных. Как
вы далее увидите, можно извлечь преимущество из того факта, что указателю
известно, на что он указывает. Чтобы определить типизированный указатель,
вы можете описать новый тип, определенный символом каре (^), за которым
следуют один или более идентификаторов. Например, чтобы определить
указатель на Integer, вы можете сделать следующее:
var
90
X: ^Integer:
Y: PInteger;
Разыменование указателей
До сих пор мы видели, как можно присваивать указателям значения, но
если вы не можете получить значения обратно, польза от этого невелика.
Разыменовав типизированный указатель, вы можете интерпретировать так,
как если бы это была переменная типа, на которую он указывает. Чтобы
разыменовать указатель, поместите символ каре (^) после идентификатора
указателя.
Ниже показаны некоторые примеры разыменования указателя:
var
SomeNumber: Integer; { присвоить SomeNumber 17 }
SomeAddress:=@SomeNumber;{SomeAddress указывает на SomeNumber}
Writeln(SomeNumber); { напечатать 17 }
Writeln(SomeAddress);
{ не допускается; указатели печатать нельзя }
Writeln(SomeAddress^); { напечатать 17 }
AnotherAddress:=SomeAddress;{ также указывает на SomeNumber }
AnotehrAddress^ := 99; { новое значение для SomeNumber }
Writeln(SomeNumber); { напечатать 99 }
end.
91
Как использовать указатели?
Теперь вы получили достаточно хорошее представление о том, в каких
ситуациях вы можете использовать указатели, и можно рассмотреть их
фактическое применение. В данном разделе охватываются следующие темы:
− Распределение динамических переменных.
− Освобождение выделенной для динамических переменных
памяти.
− Распределение и освобождение выделенных объемов памяти.
− Проверка доступного в динамически распределяемой области
пространства.
Turbo Pascal предусматривает две пары процедур для выделения и
освобождения памяти, распределяемой для динамических переменных. Чаще
всего используются процедуры New и Dispose, которые отвечают
большинству потребностей. Процедуры GetMem и FreeMem выполняют те
же функции, но на более низком уровне.
var
IntPointer: ^Integer;
StringPointer: ^String;
92
begin
New(IntPointer); { выделяет в динамически распреде
ляемой области два байта }
New(StringPointer); { выделяет в динамически распреде-
. ляемой области 256 байт }
.
.
end.
IntPointer := New(PInteger);
93
SomeProcedure(New(PointerType));
Dispose(StringPointer);
Dispose(IntPointer);
94
Динамическое выделение памяти для строки
Пусть, например, у вас есть прикладная программа, которая считывает
1000 строк из файла и записывает их в динамическую память. Вы не знаете,
насколько длинной будет каждая из этих строк, поэтому вам потребуется
описать строковый тип такого размера, который будет соответствовать
максимальной возможной строке. Если предположить, что не все строки
имеют максимальную длину, то у вас будет бесполезно использоваться
память.
Чтобы решить эту проблему, вы можете считать каждую строку в буфер,
затем выделить столько памяти, сколько требуется для фактических данных в
строке. Пример этого показан ниже:
var
ReadBuffer: String;
LinewRead: array[1..1000] of PString;
TheFile: Text;
LineNumber: Integer;
begin
Assign(TheFile, 'FOO.TXT');
Reset(TheFile);
for LineNumber := 1 to 1000 do
begin
Readln(ReadBuffer);
GetMem(LinesRead[LineNumber], Length(ReadBuffer) + 1);
LinesRead[LineNumber]^ := ReadBuffer;
end;
end.
95
Вместо выделения для строк 256К (256 символов на строку 1000 раз) вы
выделили 4К (4 байта на указатель 1000 раз), плюс объем, фактически
занимаемый текстом.
type
PCheck = ^ TCheck;
TCheck = record
Amount: Real;
96
Mounth: 1..12;
Day: 1..31;
Year: 1990..2000;
Payee: string[39];
end.
Каждая запись типа TCheck занимает 50 байт, поэтому, если у вас есть
переменная ThisCheck типа PCheck, вы можете распределить динамическую
запись следующим образом:
GetMem(ThisGheck, 50);
FreeMem(ThisCheck, 50);
GetMem(ThisCheck, SizeOf(TCheck));
.
.
.
FreeMem(ThisCheck, SizeOf(TCheck));
97
Это не только обеспечивает, что вы выделяете и освобождаете один и
тот же объем, но гарантирует, что при изменении размера типа ваша
программа все равно будет выделять нужную память.
98
Разыменование неинициализированных указателей
Одним из общих источников ошибок при работе с указателями является
разыменование указателя, который еще не был инициализирован. Как и в
случае других переменных Паскаля, значение переменной-указателя не будет
определено, пока вы не присвоите ей значение, так что она сможет указывать
на какой-то адрес в памяти.
Перед использованием указателей им всегда нужно присваивать
значения. Если вы разыменовываете указатель, которому еще не присвоено
значение, то считанные из него данные могут представлять собой случайные
биты, а присваивание значения указываемому элементу может затереть
другие данные, вашу программу или даже операционную систему. Это
звучит несколько пугающе, но при определенной дисциплине такие вещи
легко отслеживаются.
99
.
.
{ найти элемент, возвращая указатель на него или
nil,
если элемент не найден }
end;
begin
ItemPointer := nil; { начнем в предположении
nil }
ItemPointer := FindItem; { вызвать
функцию }
if ItemPointer <> nil then ... { для надежности разымено-
вания ItemPointer }
end.
begin
New(IntPointer);
New(IntPointer);
100
end.
begin
New(IntPointer);
.
.
.
Dispose(IntPointer);
IntPointer := nil;
.
.
.
if IntPointer = nil then New(IntPointer);
end.
101
Лекция 14 Параметры процедур и функций
Параметры-массивы и параметры-строки
Может сложиться впечатление, что объявление переменных в списке
формальных параметров подпрограммы ничем не отличается от объявления
их в разделе описания переменных. Действительно, в обоих случаях много
общего, но есть одно существенное различие: типом любого параметра в
списке формальных параметров может быть только стандартный или ранее
объявленный тип. Поэтому нельзя, например, объявить следующую
процедуру:
type
atype = array [1..10] of real;
PROCEDURE S (a : atype);
type
intype = string[15];
outype = string[30];
FUNCTION St (s : intype) : outype;
103
104