Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Часть 2-Основы программирования (Delphi)

.pdf
Скачиваний:
65
Добавлен:
09.06.2015
Размер:
1.93 Mб
Скачать

Тип дата-время

Тип дата-время определяется стандартным идентификатором TDateTime и предназначен для одновременного хранения и даты, и времени. Во внутреннем представлении он занимает 8 байт и подобно Currency представляет собой вещественное число с фиксированной дробной частью: в целой части числа хранится дата, в дробной - время. Дата определяется как количество суток, прошедших с 30 декабря 1899 года, а время - как часть суток, прошедших с 0 часов. Так что значение 36444,837 соответствует дате 11.10.1999 и времени 20:05. Количество суток может быть и отрицательным, однако значения меньшие -693594 (соответствует дате 00.00.0000 от Рождества Христова) игнорируются функциями преобразования даты к строковому типу.

Над данными типа TDateTime определены те же операции, что и над вещественными числами, а в выражениях этого типа могут участвовать константы и переменные целого и вещественного типов.

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

Function Date: TDateTime;

 

Возвращает текущую дату

 

 

 

Function Time: TDateTime;

 

Возвращает текущее время

 

 

 

Function Now: TDateTime;

 

Возвращает текущую дату и время

 

 

Function DateToStr(D: TDateTime): String;

Преобразует дату в строку символов

 

 

Function TimeToStr(T: TDateTime): String;

Преобразует время в строку

 

 

 

Function

DateTimeToStr(D:

TDateTime):

Преобразует дату и время в строку

String;

 

 

символов

 

 

 

 

В Delphi включен модуль DateUtils (dateutils.pas), который существенно расширяет перечисленный набор подпрограмм для работы с типом дата-время: сравнение дат, времен, подсчет количества часов, недель, месяцев между двумя датами, определение дня недели, номера дня в месяце, в году и т. д. Например, многие

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

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

Label1.Caption := DateToStr(Date + 21);

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

Time + StrToTime('1:30') или Time+1.5/24

С помощью показанного ниже обработчика Button1Click можно ввести в строке ввода любое вещественное число и посмотреть его интерпретацию как значение типа TDateTime:

procedure TForm1.Button1Click(Sender: TObject) ; var k: Real;

begin

k := StrToFloat(Edit1.Text); Label1.Caption := DateTimeToStr(k);

end;

Глава . ТИПЫ ДАННЫХ (продолжение)

o СТРУКТУРИРОВАННЫЕ ТИПЫ

Массивы

Записи

Множества

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

Обобщенная структура в определенном выше смысле без учета ее представления в памяти ЭВМ называется абстрактной или логической. Чаще применяется термин “логическая” и мы тоже будем этот термин применять для структуры данных с точки зрения пользователя или программиста. ЭВМ может обрабатывать только те структуры, которые тем или иным образом представлены в машинной памяти. Такая структура данных называется физической структурой, структурой хранения, внутренней структурой или структурой памяти. Т.о. и физическая структура данных отражает способ представления данных в памяти ЭВМ , между логической и физической структурой существует различие, которое зависит от особенностей структуры и особенностей организации памяти ЭВМ, в которой структура должна быть отражена. Вследствие этого различия в ЭВМ должны существовать процедуры, осуществляющие отображение логической структуры в физическую и наоборот. Эти процедуры осуществляют доступ к физическим структурам и выполнение над ними различных операций. Например, доступ к элементу двумерного массива на логическом уровне реализуется указанием номеров строки и столбца. На физическом уровне к элементу массива доступ осуществляется с помощью функции адресации, которая при известном начальном адресе массива в машиной памяти преобразует номер строки и столбца в адрес памяти соответствующего элемента массива.

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

Логическое представление не зависит от языка программирования и ЭВМ, а физическое представление зависит от трансляторов и ЭВМ. Так, двумерный массив в Фортране и Паскаль представляется одинаково, а физическое представление в одной и той же ЭВМ в этих языках разное.

Прежде чем приступить к изучению конкретных структур данных, дадим им общую классификацию.

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

с произвольным распределением – (односвязные, двусвязные, ассоциативные списки). Рассмотрим структуры данных подробнее в рамках их классификации по изменчивости. Статические структуры данных характеризуются тем, что для их размещения в памяти ЭВМ выделяется фиксированный объем памяти.

СТРУКТУРИРОВАННЫЕ ТИПЫ

Любой из структурированных типов (а в Object Pascal их четыре: массивы, записи, множества и файлы) характеризуется множественностью образующих этот тип элементов. Каждый элемент, в свою очередь, может принадлежать структурированному типу, что позволяет говорить о возможной вложенности типов. В Object Pascal допускается произвольная глубина вложенности типов, однако суммарная длина любого из них во внутреннем представлении не должна превышать 2 Гбайт.

Массивы

Массивы в Object Pascal во многом схожи с аналогичными типами данных в других языках программирования. Отличительная особенность массивов заключается в том, что все их компоненты суть данные одного типа (возможно, структурированного). Эти компоненты можно легко упорядочить и обеспечить доступ к любому из них простым указанием его порядкового номера.

Описание типа массива задается следующим образом: <имя типа> = array [ <сп. инд. типов> ] of <тип>;

Здесь <имя типа> - правильный идентификатор; array, of - зарезервированные слова (массив, из); <сп. инд. типов> - список из одного или нескольких индексных типов, разделенных запятыми; квадратные скобки, обрамляющие список, - требование синтаксиса; <тип> - любой тип Object Pascal.

В качестве индексных типов в Object Pascal можно использовать любые порядковые типы, имеющие мощность не более 2 Гбайт (т. е. кроме LongWord и Int64). Обычно в качестве индексного типа используется тип-диапазон, в котором задаются границы изменения индексов. Например,

type

digit = array [0..9] of Char; vector = array [byte] of Single;

Определить переменную как массив можно и непосредственно при описании этой переменной, без предварительного описания типа массива, например:

var a,b : array [1..10] of Real;

Так как <тип>, идущий в описании массива за словом of, - любой тип Object Pascal, то он может быть, в частности, и другим массивом, например:

type

mas = array [0..5] of array [-2..2] of array [Char] of Byte;

Такую запись можно заменить более компактной: type

mas = array [0..5,-2..2,char] of Byte;

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

при переходе от младших адресов к старшим наиболее быстро меняется самый правый индекс массива.

Если, например, var

a: array [1..2,1..2] of Byte; begin

а[1,1] := 1; а[2,1] := 2; а[1,2] := 3; а[2,2] := 4;

end;

то в памяти последовательно друг за другом будут расположены байты со значениями 1, 3, 2, 4.

В Object Pascal можно одним оператором присваивания передать все элементы одного массива другому массиву того же типа, например:

var

a,b : array [1..5] of Single; begin

а := b;

end;

После этого присваивания все пять элементов массива а получат те же значения, что и в массиве b. Заметим, что объявление

var

a:array [1..5] of Single;

b:array [1..5] of Single;

создаст разные типы массивов, поэтому оператор а := b; вызовет сообщение об ошибке.

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

if а = b then ...

Сравнить два массива можно поэлементно, например: var

a, b : array [1..5] of Single; i : Byte;

begin

...

for i := 1 to 5 do

if a[i] <> b[i] then ...

end;

Динамические массивы

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

var

A: array of Integer;

В: array of array of Char;

C:array of array of array of Real;

Вэтом примере динамический массив A имеет одно измерение, массив B - два

имассив C - три измерения. Распределение памяти и указание границ индексов по каждому измерению динамических массивов осуществляется в ходе выполнения

программы путем инициации массива с помощью функции SetLength. В ходе выполнения такого оператора:

SetLength(А,3);

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

Фактически идентификатор динамического массива ссылается на указатель, содержащий адрес первого байта памяти, выделенной для размещения массива. Для освобождения этой памяти достаточно присвоить идентификатору значение Nil (другим способом является использование процедуры Finalize):

var

А,В: array of Integer; begin

//Распределяем память

SetLength(A,10) ; SetLength(B,20) ;

//Используем массивы

...

//Освобождаем память

А:= NIL;

Finalize(В); end;

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

В многомерных массивах сначала устанавливается длина его первого измерения, затем второго, третьего и т. д. Например:

var

A: array of array of Integer;//Двумерный динамический массив

begin

//Устанавливаем длину первого измерения (количество столбцов)

SetLength(A,3) ;

//Задаем длину каждого столбца

SetLength(A[0],3) ;

SetLength(A[l],3) ;

SetLength(A[2],3) ; end;

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

SetLength(A,3) ;

//Задаем длину каждого столбца

SetLength(A[0],3) ;

SetLength(A[l],4) ;

SetLength(A[2],5) ;

В многомерных динамических массивах каждый элемент любого из N-1 измерений (N - количество измерений) представляет собой динамический массив и, следовательно, нуждается в инициации. Вот как, например, можно инициировать вещественный кубический массив 3x3х3:

var

A: array of array of array of Real; i, j: Integer;

begin

SetLength(A,3) ; for i := 0 to 2 do begin

SetLength(A[i],3) ;

for j := 0 to 2 do SetLength(A[i,j],3) ; end;

end;

Записи

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

Структура объявления типа записи такова: <имя типа> = record <сп. полей> end;

Здесь <имя типа> - правильный идентификатор; record, end - зарезервированные слова (запись, конец); <сп. полей> - список полей; представляет собой последовательность разделов записи, между которыми ставится точка с запятой.

Каждый раздел записи состоит из одного или нескольких идентификаторов полей, отделяемых друг от друга запятыми. За идентификатором (идентификаторами) ставится двоеточие и описание типа поля (полей), например:

type

BirthDay = record Day, Month: Byte; Year : Word

end;

var a, b : BirthDay;

В этом примере тип BirthDay (день рождения) есть запись с полями Day, Month и Year (день, месяц и год); переменные a и b содержат записи типа BirthDay.

Как и в массиве, значения переменных типа записи можно присваивать другим переменным того же типа, например

а := b;

К каждому из компонентов записи можно получить доступ, если использовать составное имя, т. е. указать имя переменной, затем точку и имя поля:

a.day := 27; b.year := 1939;

Для вложенных полей приходится продолжать уточнения:

type

BirthDay = record Day, Month: Byte; Year: Word

end;

var

с: record

Name : String;

Bd : BirthDay end;

begin

if c.Bd.Year = 1989 then ...

end;

Чтобы упростить доступ к полям записи, используется оператор присоединения with:

with <переменная> do <оператор>;

Здесь with, do - зарезервированные слова (с, делать); <переменная> - имя переменной типа запись, за которой, возможно, следует список вложенных полей; <оператор> - любой оператор Object Pascal. Например:

with с, Bd do Month := 9;

или with с do with Bd do Month := 9;

Это эквивалентно

с.Bd.Month := 9;

Object Pascal разрешает использовать записи с так называемыми вариантными полями (вариантные записи), например:

type

Forma = record Name: String; case byte of

0:(BirthPlace: String[40]);

1:(Country : String[20]; EntryPort : String[20]; EntryDate : 1..31;

ExitDate : 1..31)

end;

В этом примере тип Forma определяет запись с одним фиксированным полем Name и вариантной частью, которая задается предложением case ... of. Вариантная часть состоит из нескольких вариантов (в примере - из двух вариантов: 0 и 1). Каждый вариант определяется константой выбора, за которой следуют двоеточие и список полей, заключенный в круглые скобки. В любой записи может быть только одна вариантная часть, и, если она есть, она должна располагаться за всеми фиксированными полями.

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

Предложение case ... of, открывающее вариантную часть, внешне похоже на соответствующий оператор выбора, но на самом деле лишь играет роль своеобразного служебного слова, обозначающего начало вариантной части. Именно

поэтому в конце вариантной части не следует ставить end как пару к case ... of. (Поскольку вариантная часть - всегда последняя в записи, за ней все же стоит end, но лишь как пара к record). Ключ выбора в предложении case ... of фактически игнорируется компилятором: единственное требование, предъявляемое к нему в Object Pascal, состоит в том, чтобы ключ определял некоторый стандартный или предварительно объявленный порядковый тип.

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

Множества

Множества - это наборы однотипных логически связанных друг с другом объектов. Характер связей между объектами лишь подразумевается программистом и никак не контролируется Object Pascal. Количество элементов, входящих в множество, может меняться в пределах от 0 до 256 (множество, не содержащее элементов, называется пустым). Именно непостоянством количества своих элементов множества отличаются от массивов и записей.

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

Описание типа множества имеет вид: <имя типа> = set of <базовый тип>;

Здесь <имя типа> - правильный идентификатор; set, of - зарезервированные слова (множество, из); <базовый тип> - базовый тип элементов множества, в качестве которого может использоваться любой порядковый тип, кроме Word, (Smallint???), Integer, Longint, Int64.

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

Пример определения и задания множеств: type

digitChar = set of '0'..'9'; digit = set of 0..9;

var

sl,s2,s3 : digitChar; s4,s5,s6 : digit;

begin

s1 = ['1', '2', '3'];

s2 = ['3', '2', '1'];

s3 = ['2', '3'];

s4 = [0..3, 6];

s5 = [4, 5];

s6 = [3..9];

end;

В этом примере множества s1 и s2 эквивалентны, а множество s3 включено в s2 и s1. Над множествами определены следующие операции:

* пересечение множеств; результат содержит элементы, общие для обоих множеств; например,

s4*s6 содержит [3], s4*s5 - пустое множество;

+ объединение множеств; результат содержит элементы первого множества, дополненные недостающими элементами из второго множества:

s4+s5 содержит [0,1,2,3,4,5,6]; s5+s6 содержит [3, 4, 5, 6, 7, 8, 9];

разность множеств; результат содержит элементы из первого множества, которые не принадлежат второму:

s6-s5 содержит [3,6,7,8,9];

s4-s5 содержит [0,1, 2, 3, 6];

= проверка эквивалентности; возвращает True, если оба множества эквивалентны;

<> проверка неэквивалентности; возвращает True, если оба множества неэквивалентны;

<= проверка вхождения; возвращает True, если первое множество включено во второе;

>= проверка вхождения; возвращает True, если второе множество включено в первое;

in проверка принадлежности; в этой бинарной операции первый элемент - выражение, а второй - множество одного и того же типа; возвращает True, если выражение имеет значение, принадлежащее множеству:

3 in s6 возвращает True;

2*2 in s1 возвращает False.

Дополнительно к этим операциям можно использовать две процедуры. Include(s, i) - включает новый элемент во множество. Здесь s - множество,

состоящее из элементов базового типа TSetBase; i - элемент типа TSetBase, который необходимо включить во множество.

Exclude(s, i) - исключает элемент из множества. Параметры обращения - такие же, как у процедуры Include.

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

Замечание. Внутреннее устройство множества таково, что каждому его элементу ставится в соответствие один двоичный разряд (один бит); если элемент включен во множество, соответствующий разряд имеет значение 1, в противном случае - 0. В то же время минимальной единицей памяти является один байт, содержащий 8 бит, поэтому компилятор выделит множествам по одному байту, даже если они описаны как содержащие меньшее число элементов, и в результате мощность каждого из них станет равна 8 элементам. Максимальная мощность множества - 256 элементов. Для таких множеств компилятор выделяет по 16 (32???) смежных байт.

Пример. Реализуем алгоритм выделения из N натуральных чисел всех простых чисел. [Простыми называются целые числа, которые не делятся без остатка на любые другие целые числа, кроме 1 и самого себя. К простым

относятся 1, 2, 3, 5, 7, 11, 13 и т. д.] В его основе лежит прием, известный под названием “решето Эратосфена”. В соответствии с этим алгоритмом вначале формируется множество BeginSet, состоящее из всех целых чисел в диапазоне от 2 до N. В множество PrimerSet (оно будет содержать искомые простые числа) помещается 1. Затем циклически повторяются следующие действия:

взять из BeginSet первое входящее в него число Next и поместить его в

PrimerSet;

удалить из BeginSet число Next и все другие числа, кратные ему, т.е. 2*Next, 3*Next и т. д.

Цикл повторяется до тех пор, пока множество BeginSet не станет пустым.

Эту программу нельзя использовать для произвольного N, так как в любом множестве не может быть больше 256 элементов.

procedure TForm1.Button1Click(Sender: TObject); const

N = 255; // Количество элементов исходного множества

type

SetOfNumber = set of 1..N;

var

Next, kr_Next, i: Word; // Вспомогательные переменные

BeginSet, PrimerSet: SetOfNumber; S: String;

begin

BeginSet := [2..N]; // Создаем исходное множество PrimerSet:= [1]; // Первое простое число

Next := 2; // Следующее простое число

while BeginSet <> [ ] do begin

Include(PrimerSet, Next); kr_Next := Next;

// Цикл удаления непростых чисел

while kr_Next <= N do begin

Exclude(BeginSet, kr_Next); kr_Next := kr_Next + Next end;

repeat

Inc(Next)

until (Next in BeginSet) or (Next > N)

end;

// Вывод результата

S := '1';

for i := 2 to N do

if i in PrimerSet then S := s+', '+IntToStr(i); Label1.Caption := s

end;