От Delphi 4 к Delphi 5 часть 31

От Delphi 4 к Delphi 5
Массивы
Массив представляет собой структуру данных, позволяющую хранить под одним именем совокупность данных любого, но только одного какого-то типа. Он характеризуется своим именем, типом хранимых элементов, размером (числом хранимых элементов), нумерацией элементов и размерностью. Различают массивы статические, размеры которых устанавливаются в момент их объявления, и динамические, размеры которых могут изменяться во время выполнения. Таким образом, массив — структура сохраняемых в памяти данных, состоящих из последовательности значений, относящихся к одному типу. Элементы массива расположены последовательно в непрерывной области памяти. Массивы могут быть одно-, двух-, трех- и, в общем случае, многомерными. В функции или процедуры можно передавать в качестве параметров как массивы фиксированного размера, так и открытые массивы, размер которых неизвестен. Кроме того, можно передавать открытые массивы констант, содержащие значения различных типов.
Описание массива производится следующим образом:
<имя типа> = array [<список индексных типов> ] of <тип> ;
где <имя типа> — представляет собой идентификатор;
array, of – зарезервированные слова, обозначающие массив, из;
[] — квадратные скобки, которые обрамляют список в соответствии с синтаксисом;
<список индексных типов> — список из одного или нескольких индексных типов, разделенных запятыми;
<тип> — любой тип языка Object Pascal.
В качестве индексных типов можно использовать любые порядковые типы, кроме LongWord и Int64.
Определить переменную как массив можно непосредственно при описании этой переменной, без предварительного описания типа массива.
var
d : array[1..100] of Real;
массив вещественного типа.
Обычно в качестве индексного типа используется тип-диапазон, в котором задаются границы изменения индексов.

Одномерные массивы
Одномерный массив — это, по существу, набор одинаковых элементов, расположенных в одну строку. Элементы относятся к одному типу, но могут, конечно, отличаться по значению (рисунок 1).
Синтаксис объявления типов одномерных массивов следующий:
type
ИмяТипаМассива = array [ОпределениеИндекса] of ТипЭлемента;
например:
type
Month = array[1..12] of Integer;
Parameters = array[Boolean] of Byte;
Типы массивов определяются внутри type-блока. После знака равенства стоит зарезервированное слово array, за ним в квадратных скобках следует определение порядковых номеров элементов массива, затем следует второе зарезервированное слово of, после которого указывается тип элементов — так называемый базовый тип массива.
В качестве базового может использоваться любой объявленный тип, но все элементы массива должны быть одного типа.
Определение индекса массива указывает диапазон значений индекса, это или явное объявление порядкового типа, или использование идентификатора ограниченного типа. Обычно описание индекса прямо определяет диапазон величин порядкового типа, указывая начальное и конечное значения. Например, следующее объявление определяет массив из 100 элементов целого типа с индексом, принимающим значения от 1 до 100:
type
WholeType = array [1..100] of Integer;
Выражение 1..100 в этом объявлении прямо указывает диапазон целых чисел от 1 до 100. Вы можете использовать идентификатор ограниченного типа прямо внутри определения индекса, вместо явного указания диапазона. Например, следующее описание использует диапазон целых чисел для определения массива из 32 элементов вещественного типа:
type
Time = 1..32;
GeneralTime = array[Time] of Real;
Вы можете также использовать перечислимый, а также любой другой порядковый тип во всей его полноте для определения диапазона изменения индекса.
Например, следующее объявление использует предварительно определенный тип-перечисление, для создания массива из 12 элементов вещественного типа:
type
Months = (January, February, March, April, May, June, July, August, September, October, November, December);
Month = array [Months] of Real ;
Это объявление типа массива Month использует весь диапазон перечислимого типа Months для описания индекса. Описание Mont соответствует массиву из 12 элементов вещественного типа, которые доступны посредством индексов типа Months. Аналогично, CharCodeTable = array[Char] of Byte; определяет массив из 256 элементов, выстроенных по значениям всех возможных символов, пронумерованных от 0 до 255. Рассмотрим пример использование одномерного массива. Запишите следующий программный код:
implementation
{$R *.DFM}
type
Test = array [1..12] of Integer;
procedure TForm1.Button1 Click(Sender: TObject);
var
VTest: Test;
begin
{определим значение для первого элемента массива}
VTest[1]:=5;
{проверим содержимое первого элемента массива}
Edit1.Text:=IntToStr(VTest[1]);
end;
end.
Итак, массив VTest состоит из фиксированного количества компонентов, а именно из 12.
Важной для понимания массивов является концепция индекса, или номера элемента массива. Доступ к отдельному элементу массива возможен с использованием индексного выражения в квадратных скобках. Например, VTest[1] обозначает обращение к элементу массива с номером 1.
Вы можете размещать любой из встроенных типов данных Object Pascal в массив. Массив, таким образом, просто совокупность значений. Например, скажем, вы хотите сохранить массив целых чисел, который включает в себя пять целочисленных значений. Вы можете объявить массив следующим образом:
var
MyArray : array[0..4] of Integer;
В этом случае транслятор распределяет память для массива следующим образом: поскольку каждое целое число требует 4 байта памяти хранения, полный массив будет содержать 20 байт в памяти. Теперь, когда вы объявили массив, можете заполнять его значениями с использованием операторов с индексом ([]) следующим образом:
MyArray[0] := -200;
MyArray[1] := -100;
MyArray[2] := 0;
MyArray[3] := 100;
MyArray[4] := 200;
После этого в программе можно обращаться к индивидуальным элементам массива, производя необходимые действия, например:
X:= MyArray[3] + MyArray[4]; результат равен 300.
Двумерные массивы
Двумерный массив, или матрица, это расширение концепции одномерного массива или вектора. Вы можете считать его упорядоченным списком одномерных массивов одинакового размера и типа, образующих столбцы (или строки) матрицы. Или, другими словами, вы можете рассматривать его как вектор, каждый элемент которого тоже является вектором. В самом деле, ведь для описания типа элементов может быть использован любой объявленный тип — в том числе другой тип массива. Иными словами, матрица состоит из прямоугольной таблицы строк и столбцов элементов одинакового типа — базового типа массива.
Синтаксис объявления типов двумерных массивов следующий:
type
ИмяТипаМассива = array [ОпределениеИндекса1,ОпределениеИндекса2] of БазовыйТип ;
Например:
type
Monts = (January, February, March);
Month= array[1..3, January .. March] of Real; (рисунок 2).
Test = array[1..5,Boolean] of Byte;
Двумерные массивы объявляются так же, как и одномерные, за исключением того, что в квадратных скобках после зарезервированного слова array стоят два разделенных запятыми определения индексов, относящихся к двум размерностям массива. Каждое определение индекса независимо от другого индекса. Типы первого и второго индексов могут различаться. Например, следующий двумерный массив MoneyFlow определен как матрица 3х12 элементов типа Longint:
type
FlowTypa = (Expense, Income, Transfer) ;
MonthType = (January, February, March, April, May, June, July, August, September, October, November, December);
MoneyFlow = array[FlowType,MonthType] of Longint;
Это описание массива MoneyFlow использует два типа-перечисления FlowТуре и MothType для описания индексов двух измерений массива.
Рассмотрим пример использования двумерного массива.
1. Запустите Delphi.
2. Сохраните файл модуля под именем TDimArray_pas, а файл проекта под именем TDimArray.dpr.
3. Поместите на форму компонент StringGrid, две кнопки Button, три компонента Label, три компонента Edit для ввода значений. Программный код с пояснениями приведен ниже. Данное приложение использует двумерный массив, значения которого отображаются в компоненте StringGrid1. Вы можете вводить новые значения, для этого компонента используя окна текстовых редакторов Edit1, Edit2, Edit3, определяя при этом номер строки, номер столбца и вводимое значение. На рисунке 3 показан результат работы приложения.
implementation

{$R *.DFM}
{описание массива}
type
Monts = (January, February, March);
Month= array[1..3, January .. March] of String;
{определение массива}
var
Monthc: Month;
procedure TForm1.FormCreate(Sender: TObject);
begin
{определение названий фиксированных строк компонета StringGrid1}
StringGrid1.Cells[0,0]:='Массив Monthc';
StringGrid1.Cells[1,0]:='Столбец 1, January';
StringGrid1.Cells[2,0]:='Столбец 2, January';
StringGrid1.Cells[3,0]:='Столбец 3, January';
StringGrid1.Cells[0,1]:='Строка 1, January';
StringGrid1.Cells[0,2]:='Строка 2, February';
StringGrid1.Cells[0,3]:='Строка 3, March';
end;
procedure TForm1.Button1 Click(Sender: TObject);
begin
{определение значений массива}
Monthc[1,January]:= '22';
Monthc[2,January]:= '45';
Monthc[3,January]:= '17';
Monthc[1,February]:= '55';
Monthc[2,February]:= '66';
Monthc[3,February]:= '87';
Monthc[1,March]:= '16';
Monthc[2,March]:= '99';
Monthc[3,March]:= '23';
{определение значений ячеек в соответствии со значениями элементов массива}
StringGrid1.Cells[1,1]:= Monthc[1,January];
StringGrid1.Cells[2,1]:= Monthc[2,January];
StringGrid1.Cells[3,1]:= Monthc[3,January];
StringGrid1.Cells[1,2]:= Monthc[1,February];
StringGrid1.Cells[2,2]:= Monthc[2,February];
StringGrid1.Cells[3,2]:= Monthc[3,February];
StringGrid1.Cells[1,3]:= Monthc[1,March];
StringGrid1.Cells[2,3]:= Monthc[2,March];
StringGrid1.Cells[3,3]:= Monthc[3,March];
end;

procedure TForm1.Button2 Click(Sender: TObject);
var
D,E:Integer;
C:String;
begin
{ввод новых значений для ячеек компонента StringGrid1}
D:= StrToInt(Edit1.Text);
E:= StrToInt(Edit2.Text);
C:=StrLower(Pchar(Edit3.Text));
StringGrid1.Cells[D,E]:= C;
end;

end.
Для массивов определены две встроенные функции Low и High. Они получают в качестве своего аргумента имя массива. Функция Low возвращает нижнюю, а High — верхнюю границу этого массива. Функции Low и High чаще всего используются для указания начального и конечного значений в операторе цикла for.
Рассмотрим следующий программный код, в котором используются эти функции:
implementation

{$R *.DFM}

function Sum (var X: array of Double): Double;
var
I: Word;
S: Real;
begin
S := 0; {Замечание: индекс массива всегда отсчитывается от нуля}
for I := 0 to High(X) do S := S + X[I];
Sum := S;
end;

procedure TForm1.Button1 Click(Sender: TObject);
var
List1: array[0..3] of Double;
List2: array[5..17] of Double;
X: Word;
S, TempStr: string;
begin
for X := Low(List1) to High(List1) do
List1[X] := X * 3.4;
for X := Low(List2) to High(List2) do
List2[X] := X * 0.0123;
Str(Sum(List1):4:2, S);
S := 'Сумма чисел в массиве List1: ' + S + #13#10;
S := S + 'Сумма чисел в массиве List2: ';
Str(Sum(List2):4:2, TempStr);
S := S + TempStr;
MessageDlg(S, mtInformation, [mbOk], 0);

end;
end.
На рисунке 4 показан результат использования функций Low и High в массиве.
В операциях с многомерными массивами циклы for вкладываются друг в друга. Например, для инициализации элементов таблицы, объявленной как
var
Square: array[1..5, 1..5] of Double;
Необходимо использовать два вложенных цикла for и две целые переменные Col и Row для параметров этих циклов:
var
Col, Row:Integer;
begin
for Col:=1 to 5 do
for Row := 1 to 5 do
Square [Col,Row] :=2;
end;
end.

Компонент Splitter
В библиотеке Delphi имеется специальный компонент Splitter, который позволяет легко осуществить перестроение размеров панелей пользователем. Его свойства Beveled и ResizeStyle определяют вид разделителя, а свойство MinSize ограничивает минимальный размер панелей по обе стороны от разделителя.
Иногда изменение размеров различных панелей оказывается недостаточно для создания удобного пространства для работы пользователя. Даже при увеличении окна на весь экран какая-то панель приложения может оказаться перегруженной информацией, а другая относительно пустой. В этих случаях полезно использовать возможности перемещения границы, разделяющей различные панели, изменяя их относительные размеры.
Компонент Splitter со страницы Additional позволяет легко осуществить это. При работе с ним необходимо соблюдать определенную последовательность проектирования.
Если вы хотите установить Splitter между двумя панелями, первая из которых будет выровнена к какому-то краю клиентской области, а вторая займет всю клиентскую область, то сначала надо выровнять первую панель, например, влево. Для этого поместите на форму компонент Panel, установите для него свойство Align равным alLeft.
Затем поместите на форму компонент Splitter и оставьте установленное по умолчанию для него свойство Align равным alLeft. В этом случае Splitter прижмется к правому краю первой панели. Оставьте для этого компонента свойство Beveled равное False. Поместите на форму второй компонент Panel. После этого можно задать выравнивание на всю оставшуюся площадь клиентской области, свойство Align = alClient. В результате компонент Splitter окажется зажатым между двумя панелями и при запуске приложения он позволит пользователю изменять положение соответствующей границы между этими панелями. Поместите еще один компонент Splitter на форму, установите для него значение Align = alBottom, а свойство Beveled равное True. И еще одну панель поместите на форму со значением Align = alBottom.
На рисунке 5 показано размещение компонентов.
Подобные разделители Splitter можно разместить между всеми панелями приложения, дав пользователю полную свободу изменять топологию окна, с которым он работает.
Компонент Splitter имеет событие OnMoved, которое наступает после конца перемещения границы. В обработчике этого события надо предусмотреть, если необходимо, упорядочение размещения компонентов на панелях, размеры которых изменились: переместить какие-то метки, изменить размеры компонентов и т.д.
Свойство ResizeStyle компонента Splitter определяет поведение разделителя при перемещении его пользователем.
type TResizeStyle = (rsNone, rsLine, rsUpdate, rsPattern);
property ResizeStyle: TResizeStyle;
Поэкспериментируйте с ним, чтобы увидеть различие в режимах перемещения разделителя. По умолчанию свойство Splitter равно rsPattern. Это означает, что пока пользователь тянет курсором мыши границу, сам разделитель не перемещается и панели тоже остаются прежних размеров. Перемещается только шаблон линии, указывая место намечаемого перемещения границы. Лишь после того, как пользователь отпустит кнопку мыши, разделитель переместится, и панели изменят свои размеры. Практически такая же картина наблюдается, если установить ResizeStyle = rsLine. При ResizeStyle = rsUpdate в процессе перетаскивания границы пользователем разделитель тоже перемещается, и размеры панелей все время меняются. Это, может быть, удобно, если пользователь хочет установить размер панели таким, чтобы на ней была видна какая-то конкретная область. Но так как процесс перетаскивания в этом случае сопровождается постоянной перерисовкой панелей, наблюдается неприятное мерцание изображения. Так что этот режим можно рекомендовать только в очень редких случаях. Если установить ResizeStyle = rsNone, то в процессе перетаскивания границы не перемещается ни сама граница, ни изображающая ее линия, при перемещении разделителя мышью нет видимого перемещения, только после отпускания кнопки мыши разделитель перемещается.
Свойство MinSize компонента Splitter устанавливает минимальный размер в пикселях обеих панелей, между которыми зажат разделитель.
type NaturalNumber = 1..High(Integer);
property MinSize: NaturalNumber;
Задание такого минимального размера необходимо, чтобы при перемещениях границы панель не сжалась до нулевого размера или до такой величины, при которой на ней исчезли бы какие-то необходимые для работы элементы управления. К сожалению, в версиях Delphi ниже 5 свойство MinSize не всегда работает верно.
В Delphi 5 введено новое свойство компонента Splitter — AutoSnap.
property AutoSnap: Boolean;
Если оно установлено в True (по умолчанию), то при перемещении границы возможны те же неприятности, что в младших версиях Delphi. Но если установить AutoSnap в False, то перемещение границы панелей сверх пределов, при которых размер одной из панелей станет меньше MinSize, просто блокируется. Так что можно рекомендовать всегда устанавливать AutoSnap в False.
Впрочем, и это не решает всех задач, связанных с перемещением границ панелей. Дело в том, что свойство MinSize относится к обеим панелям, граница между которыми перемещается, а в ряде случаев желательно раздельно установить различные минимальные размеры одной и другой панели. Это проще сделать, задав в панелях соответствующие значения свойства Constraints.
property Constraints: TSizeConstraints;
Рассмотренные методы изменения размеров панелей имеют недостаток: при чрезмерном уменьшении размеров окна какие-то компоненты могут исчезать из поля зрения. Иногда и увеличение приводит к нехорошим результатам. Таким образом, необходимы средства, которые бы вносили ограничения на изменения окон.
Свойство Constraints является таковым средством, оно появилось, начиная с Delphi 4. Оно позволяет задавать ограничения на допустимые изменения размеров.
Свойство имеет четыре значения: MaxHeight, MaxWidth, MinHeight, MinWidth. По умолчанию эти свойства имеют нулевые значения, т.е. ограничения отсутствуют. Для того чтобы компоненты не пропадали на форме из-за изменения ее размеров, вы можете задать ограничения.
Рассмотрим пример использования компонента Splitter. Он показывает, как использовать разделитель, чтобы иметь окно на форме изменяемого размера. Все объекты на форме созданы динамически с использованием события OnCreate.
Чтобы выполнять этот пример, добавьте Filectrl к предложению uses раздела interface формы.
Замечание: при создании разделителя динамически во время выполнения программы важно установить его позицию в соответствующую сторону управления, которая изменяет размеры.
Ниже представлен программный код:
procedure TForm1.FormCreate(Sender: TObject);
var
Split: TSplitter;
Files: TFileListBox;
Dirs: TDirectoryListBox;
begin
{добавим список каталога к форме}
Dirs := TDirectoryListBox. Create(Form1);
{ выстройте в линию слева от формы}
Dirs.Parent := Form1;
Dirs.Align := alLeft;
Dirs.Width := Form1.ClientWidth div 3 ;
{Теперь добавьте разделитель, чтобы отделить область окна каталога от области окна файла}
Split := TSplitter.Create (Form1);
Split.Parent := Form1;
{Удостоверьтесь, что разделитель — справа от списка каталога}
Split.Left := Dirs.Left + Dirs.Width + 1;
Split.Align := Dirs.Align;
{это то же самое выравнивание линии, как и для каталога}
{Каждая область окна должна составлять, по крайней мере, одну четверть ширины формы}
Split.MinSize := Form1.ClientWidth div 4;
{создайте последнюю область окна — список файла}
Files := TFileListBox.Create (Form1);
Files.Parent := Form1;
Files.Align := alClient;
Dirs.FileList := Files;
{Свяжите список файла со списком каталога}
end;
end.
На рисунке 6 показан результат работы программы.

Владимир Скуратов

(c) компьютерная газета


Компьютерная газета. Статья была опубликована в номере 43 за 2001 год в рубрике программирование :: delphi

©1997-2024 Компьютерная газета