Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
6 Пользовательские типы данных(массивы, записи,....doc
Скачиваний:
2
Добавлен:
07.07.2019
Размер:
137.22 Кб
Скачать

Константы

Константы – это такие же, как переменные, ячейки оперативной памяти, значение которых нельзя изменить. То есть, вы однажды указали значение константы, и в дальнейшем обращаетесь к ней, как к обычной переменной, но присвоить ей другое значение уже не сможете. Константы удобно использовать, когда вы будете неоднократно обращаться к одному и тому же значению. Тип константы указывать не нужно, компилятор сам подбирает для нее подходящий тип. Константы описываются в разделе const, который всегда должен описываться перед разделом var:

procedure MyProc;

const

pi = 3.14;

MessageError = 'Текст сообщения об ошибке';

var

a : Integer;

b : String;

begin

...

end;

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

В процессе расчета вам приходится умножать это значение на количество минимальных зарплат. Допустим, некто Сидоров имеет оклад, равный 5-ти минимальным зарплатам. В момент расчета вы умножаете 5 не на сумму минимальной зарплаты, а на константу MinZarPlat, которая и содержит нужное значение. А если через месяц или год сумма минимальной зарплаты изменится, то вам останется только заменить значение вашей константы на новое, не меняя при этом остального кода. Удобно, не правда ли?

Пройденный материал на практике

Создадим немного глупый, но использующий все новые возможности проект. Создайте новый проект, переименуйте форму в fMain, а в свойстве Caption укажите "Случайные числа". Сохраните проект в новую папку. Проект назовите RandomNum.

Установите на форму Memo, удалите из него весь текст. Ниже – кнопку с названием "Генерировать".

Рис. 9.2.  Окончательный вид формы

В обработчике нажатий на кнопку впишем такой текст:

procedure TfMain.Button1Click(Sender: TObject);

const

MaxValue = 1000; //записываем максимальный размер случайных чисел

var

a : array [1..100] of Integer; //массив целых чисел из 100 элементов

i : integer; //счетчик для for

s : String;

begin

//вначале очистим Memo:

Memo1.Clear;

//Заполняем случайными числами от 0 до MaxValue массив a:

for i := 1 to 100 do

a[i] := Random(MaxValue);

//данные из массива добавляем сначала в строковую переменную, затем в Memo:

s := '';

for i := 1 to 100 do

s := s + IntToStr(a[i]) + ', ';

Memo1.Lines.Add(s);

end;

В результате выполнения этого кода мы получим строку s, в которой через запятую будут перечислены 100 случайных чисел от 0 до 1000.

Типы данных, определяемые программистом

До этого момента в программах использовались стандартные типы данных: integer, Real, Char, string и Boolean. Вместе с тем, язык Delphi позволяет программисту определить свой собственный тип данных, а затем данные этого типа использовать в программе.

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

  • перечисляемому;

  • интервальному;

  • составному типу данных (записи).

Перечисляемый тип

Определить перечисляемый тип — это значит перечислить все значения, которые может принимать переменная, относящаяся к данному типу.

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

Тип =(Значение1, Значение2, ... Значение i)

где:

  • тип — имя перечисляемого типа данных;

  • Значение i — символьная константа, определяющая одно из значений, которое может принимать переменная типа Тип.

Примеры:

TDayOfWeek = (MON,TUE,WED,THU,FRI,SAT,SUN);

TColor = (Red,Yellow,Green);

Примечание

Согласно принятому в Delphi соглашению, имена типов должны начинаться с буквы Т (от слова Туре — тип).

После объявления типа можно объявить переменную, относящуюся к этому типу, например:

type

TDayOfWeek = (MON,TUE,WED,THU, FRI,SAT,SUN) ;

var

ThisDay, LastDay: TDayOfWeek;

Помимо указания значений, которые может принимать переменная, описание типа задает, как значения соотносятся друг с другом. Считается, что самый левый элемент списка значений является минимальным, а самый правый — максимальным. Для элементов типа DayOfWeek справедливо:

MON < TUE < WED < THU < FRI < SAT < SUN

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

if (Day = SAT) OR (Day = SUN) then

begin

{ действия, если день — суббота или воскресенье }

end;

Приведенную инструкцию можно записать и так:

if Day > FRI then begin

{ действия, если день — суббота или воскресенье }

end;

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

Во время компиляции Delphi проверяет соответствие типа переменной типу выражения, которое присваивается переменной. Если тип выражения не может быть приведен к типу переменной, то выводится сообщение об ошибке.

Например, в фрагменте программы

type

TDayOfWeek = (MON, TUE, WED, THU, FRI, SAT, SUN) ;

ThisDay: TDayOfWeek;

begin

ThisDay:=1;

if ThisDay = 6 then

begin

{ блок инструкций }

end;

инструкция ThisDay:= i; ошибочна, т. к. переменная ThisDay принадлежит к определенному программистом перечисляемому типу TDayOfWeek, а константа, значение которой ей присваивается, принадлежит к целому типу (integer). В условии инструкции if тоже ошибка.

Можно утверждать, что объявление перечисляемого типа — это сокращенная форма записи объявления именованных констант. Например, приведенное выше объявление типа TDayOfWeek равносильно следующему объявлению:

const

MON=0; TUE=1; WED=2; THU=3; FRI=4; SAT=5; SUN=6;

Интервальный тип

Интервальный тип является отрезком или частью другого типа, называемого базовым. В качестве базового обычно используют целый тип данных (integer).

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

Тип = НижняяГраница..ВерхняяГраница;

где:

  • тип — имя объявляемого интервального типа данных;

  • НижняяГраница — наименьшее значение, которое может принимать переменная объявляемого типа;

  • верхняяГраница — наибольшее значение, которое может принимать переменная объявляемого типа.

Примеры:

TIndex = 0 .. 100; TRusChar = 'А' .. 'я';

В объявлении интервального типа можно использовать именованные константы. В следующем примере в объявлении интервального типа TIndex использована именованная константа HBOUND:

const

HBOUND=100;

type

TIndex=l..HBOUND;

Интервальный тип удобно использовать при объявлении массивов, например, так:

type

TIndex =1 .. 100;

var

tab1 : array[TIndex] of integer; i:TIndex;

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

type

TMonth = (Jan, Feb, Mar, Apr, May, Jun,

Jul, Aug, Sep, Oct, Nov, Dec);

TSammer = Jun.. Aug;

Запись

В практике программирования довольно часто приходится иметь дело с данными, которые естественным образом состоят из других данных. Например, сведения об учащемся содержат фамилию, имя, отчество, число, месяц и год рождения, домашний адрес и другие данные. Для представления подобной информации в языке Delphi используется структура, которая носит название запись (record).

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

Итак, запись — это структура данных, состоящая из отдельных именованных компонентов разного типа, называемых полями.

Объявление записи

Как любой тип, создаваемый программистом, тип "запись" должен быть объявлен в разделе type. В общем виде объявление типа "запись" выглядит так:

Имя = record

Поле_1 : Тип_1; Поле_2 : Тип_2; Поле_К : Тип_К; end;

где:

  • Имя — имя типа "запись";

  • record — зарезервированное слово языка Delphi, означающее, что далее следует объявление компонентов (полей) записи;

  • поле_i и тил_i — имя и тип i-го компонента (поля) записи, где i=1, ..., k;

  • end — зарезервированное слово языка Delphi, означающее, что список полей закончен.

Примеры объявлений:

type

TPerson = record

f_name: string[20];

l_name: string[20];

day: integer;

month: integer;

year: integer;

address: string[50]; end;

TDate = record

day: integer; month: integer; year: integer;

end;

После объявления типа записи можно объявить переменную-запись (или просто запись), например:

var

student : TPerson; birthday : TDate;

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

ShowMessage('Имя: ', student.f_name + #13 + 'Адрес: ', student.address);

выводит на экран содержимое полей f_name (имя) и address (адрес) переменной-записи student.

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

student: record

f_name:string[20];

l_name:string[20];

day:integer;

month:integer;

year:integer;

address:string[50];

end;

Инструкция with

Инструкция with позволяет использовать в тексте программы имена полей без указания имени переменной-записи. В общем виде инструкция with выглядит следующим образом:

with Имя do

begin

( инструкции программы } end;

где:

  • имя — имя переменной-записи;

  • with — зарезервированное слово языка Delphi, означающее, что далее, до слова end, при обращении к полям записи имя, имя записи можно не указывать.

Например, если в программе объявлена запись

student:record // информация о студенте

f_name: string[30]; // фамилия

l_name: string[20]; // имя

address: string[50}; // адрес

end;

и данные о студенте находятся в полях Edit1, Edit2 и Edit3 диалогового окна, то вместо инструкций

student.f_name := Editl.text;

student.l_name := Edit2.text;

student.address := Edit3.text;

можно записать:

with student do begin

f_name := Edit1.text; l_name := Edit2.text; address := Edit3.text;

end;

Указатели и динамическая память Динамическая память

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

Указатели

Оперативная память ПК представляет собой совокупность ячеек для хранения информации — байтов, каждый из которых имеет собственный номер. Эти номера называются адресами, они позволяют обращаться к любому байту памяти. Delphi предоставляет в распоряжение программиста гибкое средство управления дина­мической памятью — так называемые указатели. Указатель — это переменная, которая в качестве своего значения содержит адрес байта памяти. С помощью указа­телей можно размещать в динамической памяти любой из известных в Delphi типов данных. Лишь некоторые из них (Byte, Char, ShortInt, Boolean) занимают во внутреннем представлении один байт; остальные — несколько смежных. Поэтому на самом деле указатель адресует лишь первый байт данных.

Как правило, указатель связывается с некоторым типом данных. Такие указатели будем называть типизированными. Для объявления типизированного указа­теля используется значок ^, который помещается перед соответствующим типом, например:

var

p1 : ^Integer;

р2 : ^Real;

type

PerconPointer = "PerconRecord;

PerconRecord = record

Name : String;

Job : String;

Next : PerconPointer

end;

Обратите внимание: при объявлении типа PerconPointer мы сослались на тип PerconRecord, который предварительно в программе объявлен не был. Как уже отмечалось, в Delphi последовательно проводится в жизнь принцип, в соот­ветствии с которым перед использованием какого-либо идентификатора он дол­жен быть описан. Исключение сделано только для указателей, которые могут ссы­латься на еще не объявленный тип данных.

В Delphi можно объявлять указатель и не связывать его при этом с каким-либо конкретным типом данных. Для этого служит стандартный тип Pointer, напри­мер:

var

р: Pointer;

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

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

var

pl1,pl2: ^Integer;

pR: ^Real;

p: Pointer;

В этом случае показанное ниже присваивание вполне допустимо: pl1 := р!2;

В то же время следующее присваивание запрещено, поскольку рl1 и pR указы­вают на разные типы данных:

pl1 := pR;

Это ограничение, однако, не распространяется на нетипизированные указате­ли, поэтому мы могли бы записать:

Р := pR;

Pl1 := р;

тем самым достичь нужного результата.

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

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

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

var

pI,pj: ^Integer;

pR: ^Real;

begin

New(pI);

New(pR);

end;

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

pJ^ := 2; // В область памяти pJ помещено значение 2

pR^ := 2*pi; // В область памяти pR помещено значение 6.28

Таким образом, значение, которое адресует указатель, то есть собственно дан­ные, размещенные в куче, обозначаются значком ^сразу за указателем. Если за указателем нет значка ^, то имеется в виду адрес, по которому размещены данные. Имеет смысл еще раз задуматься над только что сказанным: значением любого указателя является адрес, а чтобы показать, что речь идет не об адресе, а о тех дан­ных, которые размещены по этому адресу, за указателем ставится значок ^ (иногда об этом говорят как о разыменовании указателя).

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

pR^ := Sqr(pR^) + I^ - 17;

Разумеется, совершенно недопустим следующий оператор, так как указателю pR нельзя присвоить значение вещественного выражения:

pR := Sqr(pR^) + I^ - 17;

Точно так же недопустим оператор:

pR^ := Sqr(pR);

Причина в том, что значением указателя pR является адрес и его (в отличие от того значения, которое размещено по этому адресу) нельзя возводить в квадрат. Ошибочным будет и следующее присваивание, так как вещественным данным, на которые указывает pR, нельзя присвоить значение указателя (адрес):

pR^ := pJ;

Динамическую память можно не только забирать из кучи, но и возвращать об­ратно. Для этого используется процедура Dispose. Например, показанные ниже операторы вернут в кучу память, которая ранее была закреплена за указателями рJ и pR (см. выше).

Dispose(pJ); Dispose(pR);

Замечу, что процедура Dispose (pPtr) не изменяет значения указателя pPtr, а лишь возвращает в кучу память, ранее связанную с этим указателем. Однако

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

const

pR: ^Real = NIL;

begin

if pR = NIL then

New(pR);

Dispose(pR);

pR := NIL;

end;

Никакие другие операции сравнения над указателями не разрешены.

Приведенный выше фрагмент иллюстрирует предпочтительный способ объяв­ления указателя в виде типизированной константы с одновременным присваива­нием ему значения NIL. Следует учесть, что начальное значение указателя (при его объявлении в разделе переменных) может быть произвольным. Использова­ние указателей, которым не присвоено значение процедурой New или другим спо­собом, не контролируется Delphi и вызовет исключение.

Как уже отмечалось, параметром процедуры New может быть только типизиро­ванный указатель. Для работы с нетипизированными указателями используются процедуры GetMem и FreeMem:

GetMem(P, Size); // Резервирование памяти

FreeMem(P, Size); // Освобождение памяти

Здесь Р — нетипизированный указатель; Size — размер в байтах требуемой или освобождаемой части кучи.

СОВЕТ Использование процедур GetMem и FreeMem, как и вообще вся работа с дина­мической памятью, требует особой осторожности и тщательного соблюдения простого правила: освобождать нужно ровно столько памяти, сколько ее было зарезервировано, и именно с того адреса, с которого она была зарезервирована.

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

Таблица . Средства Delphi для работы с памятью

Подпрограмма

Описание

function Addr(X): Pointer;

Возвращает адрес аргумента X. Аналогичный результат возвращает операция @

procedure Dispose(var P: Pointer);

Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за типизированным указателем Р

procedure FreeMem(var P: Pointer; Size: Integer);

Возвращает в кучу фрагмент динамической памяти, который ранее был зарезервирован за нетипизированным указателем Р

procedure GetMem(var P: Pointer ; Size: Integer);

Резервирует за нетипизированным указателем Р фрагмент динамической памяти требуемого размера Size

procedure New (var P: Pointer) ;

Резервирует фрагмент кучи для размещения переменной и помещает в типизированный указатель Р адрес первого байта

function SizeOf(X): Integer;

Возвращает длину в байтах внутреннего представления указанного объекта X

Windows имеет собственные средства работы с памятью. За более полной ин­формацией обращайтесь к справочной службе в файле WIN32.HLP или WIN32S.HLP.