Обучающий курс. 24. Записи (часть 1)

Источник: delphi

Введение

В прошлом уроке мы познакомились со множествами - более сложной структурой данных нежели хранение значения одной переменной. Сегодня речь пойдёт о записях.

Запись (англ. record) - это сложный тип данных, позволяющие объединить данные разных типов. Запись можно назвать наиболее общим сложным типом данных. Название "запись" появилось из тех соображений, что данные разного типа можно встретить в таблицах: в каждой строке записаны сразу несколько разных значений. Таким образом, одна запись соответствует одной строке данных: она имеет несколько полей, каждое из которых хранит своё значение.

Пример

Допустим, мы хотим хранить информацию о людях, заполнивших анкету на поступление на работу. Нас интересуют: ФИО человека, возраст, образование (среднее/высшее), владение компьютером, владение иностранными языками. Все эти данные мы можем объединить в запись. Дальнейшее повествование будет основываться на этом примере.

Описание записи

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

type
  имя_типа_записи = record
    {поля записи}

  end;

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

Подходите ответственно к выбору типов данных для полей записи. Если при описании переменной она хранится в единственном экземпляре, то записи, как правило, используются для хранения некоторого набора данных (таблицы с несколькими строками, образно говоря). Это значит, что каждое из полей будет храниться в памяти столько раз, сколько строк данных у вас будет. Если в таблице 1000 строк, то объём занимаемой памяти будет немалым, да и скорость работы с данными может заметно уменьшиться из-за выбора избыточных типов данных.

Для нашего примера:

type
  TPerson = record
    Name: String;
    Age: Byte;
    Education,PC: Boolean;
    Foreign: set of TForeignLanguages;    
  end;

Разберём поля по порядку. Для ФИО вполне подходит текстовая строка типа String. 255 символов нам хватит сполна. Для хранения возраста целесообразно выбрать тип данных Byte (число от 0 до 255). Не думаю, что на работу будут устраиваться люди, возраст которых превышает 255 :-) Использовать тип Integer в данном случае нецелесообразно - мало того, что у него максимальное значение превышает 32.000, да ещё и отрицательные числа поддерживаются. Для поля "образование" выбран логический тип данных. Условимся, что True - это высшее образование, False - высшего нет (т.е. среднее). PC - владение компьютером, здесь всё понятно. Для хранения иностранных языков здесь используется множество. Ведь человек может знать несколько языков, не так ли? Описание типа данных, на основе которого построено множество, следующее:

type
  TForeignLanguages = (flEnglish, flGerman, flFrench);

Как видно, это перечислимый тип данных с тремя возможными значениями. Если вам мало английского, немецкого и французского, можете добавить в список и другие. Так как свойство Foreign нашей записи - множество, то мы сможем легко хранить информацию, например, о том, что человек знает и английский, и немецкий. Удобно, не правда ли? Если человек не знает никаких иностранных языков, множество будет пустое. Конечно, можно было бы добавить в список что-то вроде flNone для указания отсутствия иностранных языков, но зачем усложнять себе жизнь?

Итак, наша запись готова. Чтобы работать с ней в программе, разместить её следует в глобальном разделе type.

Примеры других записей

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

TPoint - запись с двумя полями - X и Y - служит для хранения координат точки. Описана эта запись следующим образом:

TPoint = record

  X: Longint;
  Y: Longint;
end;

TRect - позволяет хранить данные о прямоугольной области. Свойства Left, Top, Right и Bottom отвечают за координаты сторон прямоугольника, а координаты TopLeft и BottomRight типа TPoint - указывают на левую верхнюю и правую нижнюю вершины соответственно (эти вершины позволяют однозначно определить прямоугольник на плоскости, стороны которого параллельны осям координат). Однако в один момент времени используется только один набор из этих свойств - либо 4 координаты, либо 2 точки. О том, как это делается, мы поговорим позже.

Ещё один пример - хранение даты. Дату можно разбить на число, месяц и год, и хранить каждое значение отдельно:

TDate = record
  Day: 1..31;
  Month: 1..12;
  Year: 1900..2100;

end;

Работа с записями

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

имя_переменной.название_поля

Т.е. имя переменной или объекта и его поле разделяются точкой.

Чтобы работать с записью, нужно определить переменную, тип которой - наша запись. До этого мы всего лишь описали нашу запись, но на самом деле никаких значений ещё нигде нет. Описание происходит стандартным образом:

var Person: TPerson;

Имя переменной вы, естественно, можете дать любое, просто логично называть всё своими именами.

Вводим анкеты - интерфейс

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

  • ФИО - TEdit (Edit1)
  • Возраст - TEdit (Edit2)
  • Высшее образование - TCheckBox (CheckBox1)
  • Владение компьютером - TCheckBox (CheckBox2)
  • Владение иностранными языками - TCheckListBox (CheckListBox1)

Компонент TCheckListBox расположен на странице Additional. Это список, аналогичный TListBox, но в каждой строке есть TCheckBox, т.е. строка либо отмечена, либо нет. В свойство Items сразу занесите наши 3 языка, вводя каждый на отдельной строке.
Чтобы было удобно вводить возраст, сделаем вот что. На странице Win32 есть компонент TUpDown - это две кнопки ("вверх-вниз" или "влево-вправо"). Если в свойстве Associate выбрать наше поле ввода Edit2, то кнопки автоматически разместятся рядом с полем и нажатие на них будет автоматически изменять число в поле. В само поле лучше изначально вписать какой-нибудь "реальный" возраст, чтобы не пришлось долго щёлкать, добираясь до нужного значения. Например, можно вписать 25.
В поле Edit1 никакого текста быть не должно. Все галочки в остальных компонентах убраны. Таково изначальное состояние нашего интерфейса.

Осталось добавить кнопку, которая будет выполнять все действия.

Общий вид формы у меня получился таким:

Окно добавления анкет

Теперь перейдём к программной части.

Вводим анкеты - программирование

Итак, перейдём к программированию нашей кнопки "Добавить".

Чтобы использовать нашу запись, создадим переменную соответствующего типа:

var Person: TPerson;

Теперь у нас есть один экземпляр нашей записи и в него можно смело записывать данные. Никаких трудностей возникнуть не должно:

procedure TForm1.Button1Click(Sender: TObject);
var Person: TPerson;
begin
  Person.Name:=Edit1.Text;
  Person.Age:=UpDown1.Position;
  Person.Education:=CheckBox1.Checked;
  Person.PC:=CheckBox2.Checked;
  Person.Foreign:=[];
  if CheckListBox1.Checked[0] then

    Person.Foreign:=Person.Foreign + [flEnglish];
  if CheckListBox1.Checked[1] then

    Person.Foreign:=Person.Foreign + [flGerman];
  if CheckListBox1.Checked[2] then

    Person.Foreign:=Person.Foreign + [flFrench];
end;

Ну и конечно комментарии.
Возраст в данном случае лучше взять из этих самых "кнопок-стрелочек" - там есть соответствующее свойство Position. А почему не из Edit2, спросите вы? А вот почему. Во-первых, если брать значение из Edit, нужно преобразовывать его в число функцией StrToInt(). Но это не самое страшное. А вот второе, что придётся делать - контролировать это значение. В Edit запросто можно ввести отрицательное число, да или вообще буквы. Тогда нужно ещё и введёную строку проверять - а число ли это вообще? Зато, имея TUpDown, этих проблем нет. Даже если ввести в Edit буквы, UpDown сохранит своё текущее значение и никаких проблем не будет. Кстати, у UpDown1 можно задать свойство Max - максимальное значение. Изначально там стоит 100. Если не хотите принимать на работу, скажем, людей старше 60, впишите туда 60.
Теперь что касается иностранных языков. Для начала множество лучше сделать пустым, мало ли что? А дальше проверяются строки из списка. Узнать, выбрана строка или нет, можно с помощью свойства Checked, указав в квадратных скобках номер (индекс) строки, которые начинаются с нуля. Если строка выбрана, добавляем во множество соответствующий элемент. Если ни одна строка не выбрана, множество останется пустым.

Оптимизация

Вроде у нас всё хорошо, да вот об оптимизации не стоит забывать. В данном случае получится скорее не оптимизация, а сокращение кода. Помните про оператор with? Да-да, тот самый, что позволяет вынести имя объекта за скобку? Вот его и применим, вынеся Person вперёд:

procedure TForm1.Button1Click(Sender: TObject);

var Person: TPerson;
begin
  with Person do
  begin
    Name:=Edit1.Text;
    Age:=UpDown1.Position;
    Education:=CheckBox1.Checked;
    PC:=CheckBox2.Checked;
    Foreign:=[];
    if CheckListBox1.Checked[0] then

      Foreign:=Foreign + [flEnglish];
    if CheckListBox1.Checked[1] then
      Foreign:=Foreign + [flGerman];
    if CheckListBox1.Checked[2] then

      Foreign:=Foreign + [flFrench];
  end;
end;

Уже лучше, согласитесь? Но всё равно не идеально. В частности, строки проверки иностранных языков выглядят как-то нелепо - они мало отличаются друг от друга. К тому же, что делать, если в списке будет 100 языков? 100 раз писать одно и то же? Нет, конечно. Единственная загвоздка в данном случае может возникнуть только в том, как преобразовать номер строки (0, 1, 2) в значение типа данных (flEnglish, flGerman, flFrench)? Эти значения ведь нельзя указать в виде строки... А решение простое. Если вспомним, что перечислимые типы данных упорядочены, а каждому "текстовому" значению просто ставится в соответствие число, начиная с нуля, проблема мигом решится. Итак, нам нужно просто привести типы данных, а именно - число к типу TForeignLanguages. Дальше элементарно - заводим цикл по строкам и "на лету" формируем требуемое множество. Итак, окончательный вариант:

procedure TForm1.Button1Click(Sender: TObject);

var Person: TPerson; I: Byte;
begin
  with Person do
  begin
    Name:=Edit1.Text;
    Age:=UpDown1.Position;
    Education:=CheckBox1.Checked;
    PC:=CheckBox2.Checked;
    Foreign:=[];
    for I := 0 to CheckListBox1.Items.Count-1 do

      if CheckListBox1.Checked[I] then
        Foreign:=Foreign + [TForeignLanguages(I)];
  end;

end;

Красивый и при этом самый короткий код.

Заключение

Данные мы получили и занесли их в память. А что с ними делать дальше? Наверное, сохранить их где-то для постоянного хранения. Например, в файле. О том, как это сделать - в следующий раз.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=22656