В данном посте разбираюсь с обобщенными типами данных, руководствуясь книгой Д. Осипова “Delphi. Программирование для Windows. OS X, iOS и Android”. Цель – отработать на практике возможности обобщенных типов.
Зачем нужны обобщенные типы?
Насколько я понял, в том числе для того, чтобы можно было использовать один и тот же алгоритм для разных типов данных. Скажем, есть у нас алгоритм сравнения или сортировки или сложения или ещё чего угодно, но, например, написан он для целочисленных значений.
Допустим, он корректно работает и с другими типами данных. Раньше надо было бы его переписывать на другие типы данных, а сейчас достаточно указать в обобщенном типе данных, что тип данных в данном конкретном случае будет другой.
Где используются?
1 |
system.generics.defaults,System.Generics.Collections |
Generic types в записях
Объявили тип
1 2 3 4 5 |
type TGenericRec<AnyTypeName1,AnyTypeName2,AnyTypeName3>=record A:AnyTypeName1; B:AnyTypeName2; C:AnyTypeName3; D:integer; |
Объявили переменную
1 2 3 |
var GRec1:TGenericRec<integer,integer,double>; GRec2:TGenericRec<string,integer,double>; |
Используем переменные
1 2 |
GRec1.A := 12; GRec2.A = 'hello'; |
Пример простой и наглядный. Как видно, один и тот же тип используем по-разному. Переменной этого типа Grec1.A присвоили значение 12, а переменной Grec2.A – значение ‘hello’;
Удобно с точки зрения написания одного алгоритма под разные виды данных.
Generyc types в процедурах и функциях
В книге взят пример с функции максимума 2-х чисел. Прикол в том, что нельзя напрямую сравнивать значения обобщенных типов так как они заранее не известны. Тем не менее, для реализации функции в обобщенном виде используются специальные интерфейсы, например IComparer. Итак, для начала объявляется тип с обобщенным параметром
1 2 3 4 |
uses system.sysutils, system.generics.defaults; type TGetMax =class class function GetMax<T>(X,Y:T):T; end; |
Далее пишется реализация данного типа
1 2 3 4 5 6 7 8 9 10 |
class function TGetMAx.GetMax(X, Y: T): T; var Comparer:Icomparer; begin Comparer:=TComparer.Default; if Comparer.Compare(X,Y) > 0 then Result := X else Result := Y; end; |
Далее, можем “повесить” вот такой обработчик на кнопку
1 2 3 4 5 6 7 8 9 10 |
procedure TForm2.Button1Click(Sender: TObject); var A: integer; B: string; begin A := TGetMax.GetMax(10,20); Showmessage(inttostr(A)); B := TGetMax.GetMax('A','B'); Showmessage(B); end; |
Видим, что использовали 2 разных типа и один алгоритм. Единственное, что не очень удобно – нельзя все сделать напрямую. То есть нужен вспомогательный интерфейс – компаратор.
Generic types в классах
Создаем класс
1 2 3 4 5 |
type TMyGenericClass = class ft: T; procedure SetT(Value:T); function GetT: T; end; |
Пишем реализацию методов
1 2 3 4 5 6 7 8 9 |
function TMyGenericClass.GetT: T; begin Result := ft; end; procedure TMyGenericClass.SetT(Value: T); begin ft:=Value; end; |
“Вешаем” обработчик на кнопку
1 2 3 4 5 6 7 8 9 10 |
procedure TForm.ButtonClick(Sender: TObject); var GenericObject: TMyGenericClass; // конкретизируем begin GenericObject := TMyGenericClass.Create; GenericObject.SetT(99); Showmessage('Записал значение '+inttostr(GenericObject.ft)); GenericObject.GetT; Showmessage('Прочитал значение '+inttostr(GenericObject.ft)); end; |
Generic types в массивах
// На примере массива целых чисел. На форме кнопка и листбокс
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TForm2.Button1Click(Sender: TObject); const Size=100; var i: integer; begin for i := 0 to Size-1 do begin A[i]:=Random(100); listbox1.Items.Add(inttostr(A[i])) ; end; TArray.Sort(A); listbox1.Clear; for i := 0 to Size-1 do listbox1.Items.Add(inttostr(A[i])); end; |
Generic types в объектах
Из книги Д.Осипова, взял и реализовал небольшой пример
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//Объявляем лист объектов var List:TobjectList; //Создаем лист объектов procedure TForm2.FormCreate(Sender: TObject); begin List:=TobjectList.create; end; //Добавляем объекты через таймер procedure TForm2.Timer1Timer(Sender: TObject); var Circle: TCircle; i:integer; begin Randomize; Circle:=Tcircle.Create(nil); List.Add(Circle); Circle.Parent:=Form2; Circle.Position.Y:=0; Circle.Position.X:=Random end; //Через другой таймер смещаем шарики procedure TForm2.Timer2Timer(Sender: TObject); var i:integer; begin //Смещаем шарики for i := 0 to List.Count-1 do with List.Items[i] do Position.Y:=Position.Y+5; end; |
Результат
В словарях
Для тренировки создал простейшую программу на основе TDictionary<>;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// Объявили словарь в глобальных переменных var D: TDictionary<integer,string>; //Создали словарь procedure TForm2.FormCreate(Sender: TObject); begin D:=TDictionary<integer,string>.create; end; //Добавили слово var s:string; begin D.Add(d.Count,edit1.Text); //d.TryGetValue(d.Count-1,s); // << можно так... // экспортировать значение в s listbox1.Items.Add(d.Items[d.Count-1]); // но имхо проще так //s:=''; end; //Удалили слово procedure TForm2.Button2Click(Sender: TObject); var i:integer; begin d.Remove(listbox1.ItemIndex); listbox1.Clear; for I := 0 to d.Count-1 do listbox1.Items.Add(d.Items[i]) end; |
Особенность TDictionary в том, что ключ должен быть уникальным для каждой записи иначе сгенерируется ошибка. Мне сначала показалось, что TDictionary мало чем отличается по функционалу от TStringlist, и, в принципе, в своем примере я показал очень малую, куцую часть его возможностей, но не тут то было, чуть позже, после того как я написал пост, я обнаружил очень классную статью на оф. сайте embarcadero. Там TDictionary используется по аналогии с записью. Объявляется класс с полями, который потом используется в качестве обобщенного параметра. И в поля уже пишется все, что нужно.