вторник, 10 мая 2011 г.

Структурированные типы

Перевод из справочной системы Delphi

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


По умолчанию для улучшения производительности значения в структурированном типе выравниваются по словам или двойным словам. Когда вы объявляете структурированный тип, для сжатия данных вы можете указать зарезервированное слово packed. Например, type TNumbers = packed array [1..100] of Real;

Использование зарезервированного слово ухудшает производительность и, в случае с массивом символов, влияет на совместимость.



Множества

Множество – это набор значений одного порядкового типа. Значения не имеют определенного порядка. Одно и тоже значение не может быть включено во множество дважды.

Диапазон значений множества является множеством значений определенного порядкового типа, который называется базовым. То есть возможные значения элементов множества являются подмножествами базового типа (включая пустое множество). Базовый тип может иметь не более, чем 256 значений и их порядковые номера должны быть в пределах от 0 до 255. Любая конструкция вида:

set of baseType

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

type
  TSomeInts = 1..250;
  TIntSet = set of TSomeInts;

Созадет множество с именем TIntSet, значения которого являются набором целых чисел с диапазоном значений от 1 до 250. Вы также можете объявить аналогичный тип, указав:

type TIntSet = set of 1..250;

Объявив тип таким образом, вы можете создать множество:
var Set1, Set2: TIntSet;
  ...
Set1 := [1, 3, 5, 7, 9];
Set2 := [2, 4, 6, 8, 10]

Вы также можете воспользоваться конструкцией set of ... construction непосредственно при объявлении переменных:

var MySet: set of 'a'..'z';
  ...
MySet := ['a','b','c'];

Дополнительные примеры объявления множеств:
set of Byte
set of (Club, Diamond, Heart, Spade)
set of Char;

Оператор in проверяет членство элемента во множестве:
if 'a' in MySet then ... { делаем что-то } ;

Каждое значение типа множество может содержать пустое множество, которое обозначается как [].



Массивы

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



Статические массивы


Статические массивы объявляются конструкцией вида:
array[indexType1, ..., indexTypen] of baseType;

где каждое значение indexType находится в диапазоне 2GB. Поскольку значения indexType индексируют массив, количество элементов в массиве ограничевается произведением значений indexType. На практике значения indexType являются подмножествами типа integer.

В самом простом случае, для одномерного массива существует единственное значение indexType. То есть:

var MyArray: array [1..100] of Char;

объявляет переменную с именем MyArray которая хранит 100 символьных значений. При объявлении такого массива, обращение MyArray[3] обозначает третий символ в массиве MyArray. Если вы создаете статический массив, но не присваиваете значений всем его элементам, для неиспользованных элементов резервируется память и они заполняются случайными данными. Этим они схожи с неинициализированными переменными.

Многомерный массив – это массив массивов. То есть, объявление:

type TMatrix = array[1..10] of array[1..50] of Real;

эквивалентно:
type TMatrix = array[1..10, 1..50] of Real;

При любом объявлении TMatrix, он представляет собой массив, содержащий 500 значений типа real. Переменная MyMatrix типа TMatrix может быть проиндексирована следующим образом: MyMatrix[2,45]; или так: MyMatrix[2][45]. Аналогично:

packed array[Boolean, 1..10, TShoeSize] of Integer;
эквивалентно:
packed array[Boolean] of packed array[1..10] of 
  packed array[TShoeSize] of Integer;

Стандартные функции Low и High работают с идентификаторами типов массивов и переменными. Они возвращают нижние и верхние границы диапазона первого индекса массива. Стандартная функция Length возвращает количество элементов в первом размерении массива.

Одномерный запакованный (packed) статический массив, содержащий значения типа Char называется запакованной строкой (packed string). Запакованная строка совместима со строковыми типами и другими запакованными строками (содержащими такое же количество элементов).

Массив вида array[0..x] of Char называется индексированным от 0 символьным массивом (zero-based character array). Индексированный от нуля символьный массив применяется для хранения строк, заканчивающихся нулевым символом, которые в свою очередь совместимы со значениями типа PChar.



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

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

array of baseType 

Например:
var MyFlexibleArray: array of Real;

объявляет одномерный динамический массив вещественных чисел. Объявление не выделяет память для MyFlexibleArray. Для создания массива в памяти следует вызвать SetLength. Например, для предыдущего объявления:

SetLength(MyFlexibleArray, 20);

Выделяет память для 20 вещественных чисел, индексируемых от 0 до 19. Альтернативным способом выделения памяти для динамического массива является вызов конструктора массива:

type
  TMyFlexibleArray = array of Integer;

begin
  MyFlexibleArray := TMyFlexibleArray.Create(1, 2, 3 {...});
end;

который выделит память для трех элементов и присвоит им данные значения.

Динамические массивы всегда индексируются типом integer, всегда начиная с 0. Переменные типа динамический массив являются указателями и функционируют при помощи механизма подсчета ссылок, используемого для хранения длинных строк. Для освобождения памяти, выделенной под динамический массив, присвойте значение nil переменной массива или передайте ее в качестве параметра процедуре Finalize. Любой из этих способов удаляет массив при условии, что на него нет больше ссылок. Динамические массивы автоматически удаляются, когда счетчик ссылок становится равным нулю. Динамические массивы с нулевой длиной имеют значение nil. Не применяйте оператор разыменования (^) к переменным типа динамический массив и не передавайте их в качестве параметров в процедуры New и Dispose.

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

var
  A, B: array of Integer;
  begin
    SetLength(A, 1);
    A[0] := 1;
    B := A;
    B[0] := 2;
  end;

Значение A[0] будет 2. (Если бы A и B были статическими массивами, значение A[0] было бы равно 1.)
Присваивание значения элементу динамического массива по индексу (например, MyFlexibleArray[2] := 7) не выполняет перераспределения памяти для массива. Ошибки доступа по индексу не выявляются при компиляции.

Для того, чтобы сделать независимую копию динамического массива, необходимо воспользоваться глобальной функцией Copy:

var
  A, B: array of Integer;
begin
  SetLength(A, 1);
  A[0] := 1;
  B := Copy(A);
  B[0] := 2; { B[0] <> A[0] }
end;

При сравнении двух переменных типа динамический массив, сравниваются указатели, а не значения элементов массива. Таким образом, после выполнения кода:

var
  A, B: array of Integer;
begin
   SetLength(A, 1);
   SetLength(B, 1);
   A[0] := 2;
   B[0] := 2;
end;

A = B возвращает False , но A[0] = B[0] возвращает True.

Для уменьшения размера динамического массива можно воспользоваться процедурой SetLength, или передать переменную массива в функцию Copy и присвоить полученный результат переменной массива. (Процедура SetLength обычно работает быстрее.) Например, если A – это динамический массив, то A := SetLength(A, 0, 20) удаляет все элементы массива, оставляя только первые 20.

После выделения памяти для динамического массива вы можете передавать его в качестве параметра в стандартные функции Length, High, и Low. Length возвращает количество элементов в массиве, High возвращает максимальный индекс в массиве (то есть, Length - 1), а Low возвращает 0. В случае, когда длина массива равна нулю, High возвращает -1 (при этом High < Low).

Замечание: В некоторых объявлениях функций и процедур, параметры типа массив представляются как array of baseType, без указания индексирования.

Например:
function CheckStrings(A: array of string): Boolean;

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



Многомерные диамические массивы

Для объявления многомерного динамического массива следует пользоваться итеративными конструкциями вида array of .... Например:

type TMessageGrid = array of array of string;
var Msgs: TMessageGrid;

объявляет двумерный массив строк. Для создания такого массива слудует вызвать SetLength с двумя целочисленными аргументами. Например, если I и J – это целочисленные переменные, то инструкция:

SetLength(Msgs,I,J);

Выделяет память для массива с размерениями I и J, а Msgs[0,0] - выполняет доступ к элементу массива.

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

var Ints: array of array of Integer;
SetLength(Ints,10);

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

SetLength(Ints[2], 5);

Устанавливает размер пять элементов для третьего столбца в массиве Ints. С этого момента (даже в том случае, если для остальных столбцов память не была выделена) вы можете присваивать значения элементам в третьем столбце. Например:

Ints[2,4] := 6. 

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

var
  A : array of array of string;
  I, J : Integer;
begin
  SetLength(A, 10);
  for I := Low(A) to High(A) do
  begin
    SetLength(A[I], I);
    for J := Low(A[I]) to High(A[I]) do
      A[I,J] := IntToStr(I) + ',' + IntToStr(J) + ' ';
    end;
  end;


Типы массивов и инструкции присваивания

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

var
  Int1: array[1..10] of Integer;
  Int2: array[1..10] of Integer;
      ...
  Int1 := Int2;

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

var Int1, Int2: array[1..10] of Integer;
или:
type IntArray = array[1..10] of Integer;
var
   Int1: IntArray;
   Int2: IntArray;


Записи (традиционные)

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

type recordTypeName = record
  fieldList1: type1;
   ...
  fieldListn: typen;
end

где recordTypeName – это допустимый идентификатор, каждый type определяет тип, а каждый fieldList может быть допустимым идентификатором или списком идентификаторов, разделенных запятыми. Последняя точка с запятой (;) не является обязательной.


Например, следующее объявление создает тип записи c именем TDateRec.
type
  TDateRec = record
    Year: Integer;
    Month: (Jan, Feb, Mar, Apr, May, Jun,
            Jul, Aug, Sep, Oct, Nov, Dec);
    Day: 1..31;
  end;

Каждое значение типа TDateRec содержит три поля: целочисленное значение с именем Year, значение поддиапазонного типа с именем Month, и еще одно целочисленное поле с именем Day и диапазоном значений от 1 до 31. Идентификаторы Year, Month и Day являются обозначениями полей для TDateRec и действуют как переменные. Объявление типа TDateRec, однако не выделяет память для полей Year, Month и Day. Память выделяется при объявлении переменной:

var Record1, Record2: TDateRec;

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

Record1.Year := 1904;
Record1.Month := Jun;
Record1.Day := 16;

Аналогичные операции можно выполнить при помощи инструкции with:
with Record1 do
begin
  Year := 1904;
  Month := Jun;
  Day := 16;
end;

вы можете копировать значения полей Record1 в Record2:
Record2 := Record1;

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

var S: record
  Name: string;
  Age: Integer;
end;

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



Вариантные части в записях

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

Для объявления типа записи с вариантной частью следует пользоваться следующим синтаксисом:

type recordTypeName = record
  fieldList1: type1;
   ...
  fieldListn: typen;
case tag: ordinalType of
  constantList1: (variant1);
   ...
  constantListn: (variantn);
end;

Первая часть этого объявления – до зарезервированного слова case – не отличается от обычного объявления типа запись. Оставшаяся часть объявления – от case до необязательной точки с запятой (;) – называется вариантной частью. В вариантной части:

  • tag – необязателен и может быть любым допустимым идентификатором. Если вы не указываете tag, - не следует указывать и двоеточие (:) после него.
  • ordinalType определяет порядковый тип.
  • Каждый constantList – это константа, определяющая значение типа ordinalType или список таких констант, разделяемых запятыми. В списке констант значения могут быть представлены только однократно.
  • Каждый variant – это отделяемый точкой с запятой (;) список объявлений, схожий с конструкцией fieldList: type в основной части объявления типа запись. То есть вариант имеет вид:
fieldList1: type1;
  ...
fieldListn: typen;

где каждый fieldList это допустимый идентификатор или список идентификаторов, разделяемых запятыми. Каждый type определяет тип. Заключительная точка с запятой (;) является необязательной. Type не должен быть длинной строкой, динамическим массивом, типом variant или interface. Кроме того, он не может быть структурированным типом, содержащим длинные строки, динамические массивы, типы variant или interface, но он может быть указателем на эти типы.

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

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

type
  TEmployee = record
  FirstName, LastName: string[40];
  BirthDate: TDate;
  case Salaried: Boolean of
    True: (AnnualSalary: Currency);
    False: (HourlyWage: Currency);
end;

Идея здесь в том, что каждый сотрудник (employee) может иметь либо ежегодный оклад, либо почасовую оплату (но не одновременно). Таким образом, когда вы создаете экземпляр TEmployee, нет причины для выделения памяти для обоих полей. В этом случае единственное различие между вариантами существует в именах полей, но поля могут также иметь и различные типы. Рассмотрим более сложные примеры:

type
  TPerson = record
  FirstName, LastName: string[40];
  BirthDate: TDate;
  case Citizen: Boolean of
    True: (Birthplace: string[40]);
    False: (Country: string[20];
            EntryPort: string[20];
            EntryDate, ExitDate: TDate);
  end;

type
  TShapeList = (Rectangle, Triangle, Circle, 
     Ellipse, Other);
  TFigure = record
    case TShapeList of
      Rectangle: (Height, Width: Real);
      Triangle: (Side1, Side2, Angle: Real);
      Circle: (Radius: Real);
      Ellipse, Other: ();
  end;

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

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



Записи (усовершенствованные)

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

type
  TMyRecord = record
    type
      TInnerColorType = Integer;
    var
      Red: Integer;
    class var
      Blue: Integer;
    procedure printRed();
    constructor Create(val: Integer);
    property RedProperty: 
       TInnerColorType read Red write Red;
    class property BlueProp: 
       TInnerColorType read Blue write Blue;
end;

constructor TMyRecord.Create(val: Integer);
begin
  Red := val;
end;

procedure TMyRecord.printRed;
begin
  writeln('Red: ', Red);
end;

Хоть записи и могут использовать многое из функциональности классов, есть несколько важных различий между записями и классами:

  • Записи не поддерживают наследование.
  • Записи могут содержать вариантные части, классы – нет.
  • Записи – это типы-значения, то есть они копируются при присваивании, передаются по значению в подпрограммы и размещаются в стеке, за исключением тех случаев, когда они объявлены глобально или для них явным образом выделяется память при помощи функций New и Dispose. Классы – это типы-ссылки, то есть они не могут копироваться при присваивании, они передаются в подпрограммы по ссылке и размещаются в куче.
  • Записи на платформе Win32 позволяют выполнять перегрузку операторов; классы - нет.
  • Записи создаются автоматически, при помощи конструктора, не имеющего аргументов, а классы должны создаваться явным образом. Поскольку по умолчанию конструкторы записей не имеют аргументов, любой пользовательский конструктор для записи должен иметь по крайней мере один (или более) параметр.
  • Записи не могут иметь деструкторов.
  • Виртуальные методы (объявляются при помощи зарезервированных слов virtual, dynamic и message) не могут быть использованы в записях.
  • В отличие от классов записи на платформе Win32 не могут реализовывать интерфейсы.


Файловые типы (Win32).

Файловые типы, которые доступны только для платформы Win32, являются последовательностями элементов одинакового типа. Стандартные подпрограммы ввода/вывода используют предопределенные типы TextFile и Text, которые представляют собой файл, содержащий символы и организованный в строки.


Для объявления файлового типа следует использовать синтаксис:
type fileTypeName = file of type

где fileTypeName – любой допустимый идентификатор, а type – это любой тип фиксированной длины. Запрещены указатели – явные или неявные, то есть файл не может содержать динамических массивов, длинных строк, классов, объектов, указателей, вариантных типов, других файлов или любые структурированные типы, содержащие вышеуказанные типы. Пример:

type
   PhoneEntry = record
     FirstName, LastName: string[20];
     PhoneNumber: string[15];
     Listed: Boolean;
   end;
   PhoneList = file of PhoneEntry;

Объявляет файловый тип для записи имен и телефонных номеров.
Вы можете также использовать конструкцию file of ... непосредственно при объявлении переменной. Например:

var List1: file of PhoneEntry;

Слово файл без указания типа определяет нетипизированный файл:

var DataFile: file;


Комментариев нет:

Отправить комментарий