Суббота, 27.04.2024
Королевство Delphi
Главное меню
Статьи
Наш опрос
Нашли свой исходник?
Всего ответов: 94
Статистика
Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Главная » Статьи » Разные » Delphi.NET

Delphi.NET: перегрузка операторов
Версия текста: 1.0

Введение

Общие правила

Перегрузка унарных операторов

Перегрузка бинарных операторов

Перегрузка операторов сравнения

Перегрузка операторов приведения типа

Расширенные возможности

Создание копирующего конструктора

Перегрузка оператора присваивания

Заключение

Введение

Прочитав название, Вы не ошиблись – в восьмой версии Delphi (Delphi.NET) теперь действительно стала возможна перегрузка операторов. С выходом Delphi.NET в языке Object Pascal появилось много языковых изменений, самых значительных начиная с версии 1.0. Для того чтобы рассмотреть их все, понадобилась бы отдельная книга, поэтому в данной статье мы рассмотрим только одно, на мой взгляд, самое интересное расширение языка – перегрузку операторов.

Возможность автоматического замещения операторов в исходном тексте программы пользовательскими функциями давно знакома программистам пишущим на языках C++ и C#. Теперь эта замечательная возможность доступна и Delphi-программистам.

Общие правила

В Delphi.NET, в отличие от С-подобных языков, таких как C++ и C#, для того, чтобы перегрузить оператор, нужно реализовать функцию с определенной сигнатурой (а не с символом оператора!) – то есть реализовать функцию с определенным именем, числом и типами параметров. В Таблице 1 приведен список операторов, для которых допускается переопределение, и сигнатуры функций, которые для этого нужно реализовать.

Символ оператора Сигнатура метода Категория
Неявное преобразование Implicit(a : type) : resultType; Приведение
Явное преобразование Explicit(a: type) : resultType; Приведение
- Negative(a: type) : resultType; Унарный
+ Positive(a: type): resultType; Унарный
Inc Inc(a: type) : resultType; Унарный
Dec Dec(a: type): resultType Унарный
not LogicalNot(a: type): resultType; Унарный
not BitwiseNot(a: type): resultType; Унарный
Trunc Trunc(a: type): resultType; Унарный
Round Round(a: type): resultType; Унарный
= Equal(a: type; b: type) : Boolean; Сравнение
<> NotEqual(a: type; b: type): Boolean; Сравнение
> GreaterThan(a: type; b: type) Boolean; Сравнение
>= GreaterThanOrEqual(a: type; b: type): resultType; Сравнение
< LessThan(a: type; b: type): resultType; Сравнение
<= LessThanOrEqual(a: type; b: type): resultType; Сравнение
+ Add(a: type; b: type): resultType; Бинарный
- Subtract(a: type; b: type) : resultType; Бинарный
* Multiply(a: type; b: type) : resultType; Бинарный
/ Divide(a: type; b: type) : resultType; Бинарный
div IntDivide(a: type; b: type): resultType; Бинарный
Mod Modulus(a: type; b: type): resultType; Бинарный
shl ShiftLeft(a: type; b: type): resultType; Бинарный
shr ShiftRight(a: type; b: type): resultType; Бинарный
and LogicalAnd(a: type; b: type): resultType; Бинарный
Or LogicalOr(a: type; b: type): resultType; Бинарный
xor LogicalXor(a: type; b: type): resultType; Бинарный
and BitwiseAnd(a: type; b: type): resultType; Бинарный
Or BitwiseOr(a: type; b: type): resultType; Бинарный
xor BitwiseXor(a: type; b: type): resultType; Бинарный

Таблица 1.Список операторов, для которых в Delphi.NET возможна перегрузка

Интересно отметить, что такие часто используемые функции, как Round и Trunc являются в Delphi.NET операторами, то есть по форме вызова ничем не отличаются от функций, но могут быть перегружены.

Для того чтобы переопределить оператор для разработанного Вами класса, необходимо объявить, и затем реализовать, метод класса (class method) с определенной сигнатурой (колонка 2 Таблицы 1). При объявлении и реализации данного метода необходимо указывать ключевое слово operator.

Пример

type
TMyClass = class
{Оператор арифметического сложения двух объектов типа TMyClass}
class operator Add(a, b: TMyClass): TMyClass; 
end;
class operator TMyClass.Add(a, b: TMyClass): TMyClass;
begin
{Алгоритм сложения:}
end;

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

 var
 v,v1,v2 : TMyClass;
 begin
 v1 := TMyClass.Create;
 v2 := TMyClass.Create;

 { манипуляции с переменными v1 и v2}

 v := v1 + v2; //вместо "+" компилятор подставляет вызов функции TMyClass.Add
 end;

Далее мы более подробно рассмотрим перегрузку для различных групп операторов

Перегрузка унарных операторов

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

Рассмотрим пример подобного унарного оператора: предположим у нас есть тип: расширенный список строк в виде класса TMyStringList наследованный от класса TStringList библиотеки VCL:

 type

 TMyStringList = class(TStringList)
 
 //Объявление дополнительных полей и методов ...
 end;

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

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

 class operator Negative(a: TMyStringList) : TMyStringList;

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

 program TestNegative;
 {$APPTYPE CONSOLE}

 uses
 Classes;

 type

 TMyStringList = class(TStringList)
 class operator Negative(a: TMyStringList) : TMyStringList;
 end;

 class operator TMyStringList.Negative(a: TMyStringList) : TMyStringList;
 var
 i : integer;
 begin
 Result := TMyStringList.Create;

 for i := 0 to Pred(a.Count) do Result.Add(a[a.Count-1-i]);
 end;

 var
 MyList,
 MyListReverse : TMyStringList;
 begin
 MyList := TMyStringList.Create;
 MyList.Add('Hello');
 MyList.Add('World');

 Writeln(MyList.Text);

 Writeln('After negative operator: ');

 MyListReverse := -MyList; //использование оператора 
 Writeln(MyListReverse.Text);

 MyListReverse.Free;
 MyList.Free;
 end.

Перегрузка бинарных операторов

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

Рассмотрим пример подобного бинарного оператора: для нашего класса TMyStringList определим оператор арифметического сложения со строкой "+”, который будет добавлять эту строку в конец списка.

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

 class operator Add(List : TMyStringLis; str : String) : TMyStringList;

Полный исходный код в виде консольного приложения выглядит следующим образом:

 program TestAdd;
 {$APPTYPE CONSOLE}

 uses
 Classes;

 type

 TMyStringList = class(TStringList)
 class operator Add(List : TMyStringList; str : String) : TMyStringList;
 end;

 class operator TMyStringList.Add(List : TMyStringList; str : String) : TMyStringList;
 begin
 Result := List;
 Result.Add(str);
 end;

 var
 MyList : TMyStringList;
 begin
 MyList := TMyStringList.Create;

 MyList := MyList + 'Hello';
 MyList + 'World!';

 writeln(MyList.Text);

 MyList.Free;
 end.

Теперь добавление новой строки в список сможет выглядеть следующим образом

 MyList := MyList + 'World!'; //Оператор добавления новой строки с списку

Возможно, опытные программисты зададут вопрос: "А что будет если в операторе сложения поменять слагаемые местами”?

 MyList := 'World!' + MyList; // Будет ли вызываться оператор ?

В Delphi.NET подстановка определяется исходя из порядка следования параметров в операторной функции, а поскольку следующий оператор

 class operator Add(str : String; List : TMyStringLis) : TMyStringList;

в нашем классе неопределен, то мы получим ошибку компиляции:

 Error: Incompatible types: 'string' and 'TMyStringList'

Поскольку наш оператор не создает новый объект, то можно обойтись без оператора присваивания и сохранения результата в той же самой переменной:

 MyList + 'World!'; //вызов оператора без сохранения результата !

Конечно, данная конструкция выглядит непривычно для языка Pascal, но является синтаксически верной и компилируется транслятором без ошибок.

Перегрузка операторов сравнения

Операторы сравнения являются бинарными операторами, которые всегда возвращают значение типа Boolean. Использование оператор сравнения возможно в любых выражениях, которые вычисляют логическое значение: в операторе if, в условиях циклов while и repeat и т.д.

В качестве примера, для нашего класса TMyStringList, определим оператор сравнения на равенство "=”, который будет сравнивать два списка на равенство его строк.

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

 class operator Equal(List : TMyStringLis; str : Sringt) : boolean;

Для того чтобы проверить использование данного оператора я разработал небольшое консольное приложение:

 program testEqual;
 {$APPTYPE CONSOLE}

 uses
 Classes;

 type

 TMyStringList = class(TStringList)
 class operator Equal(List1,List2 : TMyStringList) : boolean;
 end;

 class operator TMyStringList.Equal(List1,List2 : TMyStringList) : boolean;
 var
 i : integer;
 begin
 Result := false;

 for i := 0 to Pred(List1.Count) Do 
 if List1[i] <> List2[i] then Exit;

 Result := True;
 end;

 var
 MyList1,
 MyList2 : TMyStringList;
 begin
 MyList1 := TMyStringList.Create;

 MyList1.Add('Hello');

 MyList2 := TMyStringList.Create;
 MyList2.Add('World!');

 if MyList1 = MyList2 then //здесь вызывается наш оператор сравнения на равенство
 Writeln('list is identical')
 else
 Writeln('different list');

 MyList1.Free;
 MyList2.Free;
 end.

Перегрузка операторов приведения типа

Перегрузка операторов преобразования типа является, на мой взгляд, самой востребованной возможностью для Delphi – программистов.

В самом синтаксисе приведения типов изменений в Delphi.NET не произошло: для явного преобразования мы применяем функцию совпадающую c именем типа, для неявного – ничего дополнительно указывать не нужно, как и в предыдущих версиях, тип к которому нужно сделать преобразование определяется из типа выражения.

Например:

 var
 ListBase : TStringList;
 ListChild : TMyStringList;
 s : string;
 begin
 {..создание и работа с переменными ListBase ListChild}
 ListBase := TStringList(ListChild); //явное преобразование ListChild в тип TStringList
 ListBase := ListChild; //неявное преобразование ListChild в тип TStringList

 s := ListChild; // ошибка компиляции: несовпадение типов String и TMyStringList
 end;

Язык Pascal является языком со строгой типизацией, поэтому в версиях Delphi1-Delphi7 нельзя было управлять этим процессом – преобразование типов выполнялось компилятором либо на этапе трансляции, либо на этапе выполнения. Но теперь у нас есть возможность полностью реализовывать и контролировать процесс преобразования типов.

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

 class operator Implicit(a : type) : resultType; 
 class operator Explicit(a: type) : resultType;

Метод Implicit предназначен для определения неявного преобразования типа, а метод Explicit – для явного преобразования.

В качестве примера, для нашего класса расширенного списка строк TMyStringList определим два оператора неявного приведения типа:

Приведение к типу Integer – будет возвращать число элементов в списке

Приведение к типу String – будет возвращать полный текст списка (свойство Text).

Теперь определение класса TMyStringList будет выглядеть следующим образом:

 TMyStringList = class(TStringList)
 class operator Implicit(List : TMyStringList) : String;
 class operator Implicit(List : TMyStringList) : Integer;
 end;

 class operator TMyStringList.Implicit(List : TMyStringList) : Integer;
 begin
 Result := List.Count;
 end;

 class operator TMyStringList.Implicit(List : TMyStringList) : String;
 begin
 Result := List.Text;
 end;

Теперь мы можем использовать экземпляры класса TMyStringList; в любом месте программы, где необходимо значения типов integer и string

 var
 list : TMyStringList;
 s : string;
 i : integet;
 b : boolean;
 begin
 list := TMyStringList.Create;

 s := list; // ошибки нет: вызывается метод Implicit(List : TMyStringList) : String; 
 i := list; // ошибки нет: вызывается метод Implicit(List : TMyStringList) : Integer;
 b := list; //ошибка компиляции: отсутствует оператор Implicit(List : //TMyStringList) : Boolean;
 end.

В приведенном выше примере, для наглядности, преобразование осуществляется к примитивным типам (String, integer, Boolean), в реальных приложениях приведение возможно к произвольному классу. Например, приведение экземпляра класса TAccount (банковский счет) к типу TConractor (контрагент) может возвращать владельца счета, а приведение к типу Real будет возвращать остаток на счету.

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

Расширенные возможности

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

Создание копирующего конструктора

Теперь в Delphi.NET при копировании объектов при помощи оператора присваивания возможно создание полной копии объекта в виде нового экземпляра, а не копирование ссылки.

Идея состоит в определении оператора явного преобразования экземпляра класса к такому же классу.

 TMyStringList = class(TStringList)
 class operator Expplicit(List : TMyStringList) : TMyStringList;
 end;

 //преобразование экземпляра в тот же самый тип: создаем объект и копируем в него //параметр
 class Operator TMyStringList.Expplicit(List : TMyStringList) : TMyStringList;
 begin;
 Result := TMyStringList.Create;
 Result.AddStrings(List);
 end;

Теперь вызов неявного преобразования создает полную копию объекта:

 var
 List1,
 List2 : TMyStringList;
 begin
 List1 := TMyStringList.Create;
 List1.Add('Hello World!');

 List2 := TMyStringList(v_xTest); //Создается новый экземпляр, а не новая ссылка на объект!

 //Теперь у нас есть два экземпляра – нужно оба их разрушить!
 List1.Free;
 List2.Free;
 end.

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

Что же произойдет, если мы переопределим оператор неявного преобразования типа?

 class operator Implicit(List : TMyStringList) : TMyStringList;

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

 var
 List,List1 : TMyStringList
 begin
 {……} 
 List := List1; // Implicit(List : TMyStringList) : TmyStringList не подставляется!
 end;

Перегрузка оператора присваивания

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

Однако в одном случае этого можно добиться: при использования классов которые всегда имеют только один экземпляр. Примером такого экземпляра является переменная Application типа TApplication библиотеки VCL. Может существовать только один экземпляр класса TApplication, и он является глобальной переменной; его создание и уничтожение реализовано в библиотеке VCL и происходит автоматически.

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

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

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

 type

 TLogger = class
 protected
 class var
 FLogger : TLogger; //поле класса (class-field)
 public
 FText : TStringList;
 constructor Create;
 destructor Destroy; override;
 class operator Implicit(Line : String) : TLogger; //оператор преобразования строки к классу TLogger

 strict private
 class var
 class constructor Create; //конструктор класса
 end;

 constructor TLogger.Create;
 begin
 inherited;

 FText := TStringList.Create;
 end;


 destructor TLogger.Destroy;
 begin
 FText.Free;

 inherited;
 end;

 class operator TLogger.Implicit(Line : String) : TLogger;
 begin
 FLogger.FText.Add(Line);

 Result := FLogger;
 end;
 
 class constructor TLogger.Create;
 begin
 FLogger := TLogger.Create;
 end;

Теперь становится возможным операция присвоения строки (string) переменной Logger.

 var
 Logger : TLogger = TLogger.FLogger; //создаем глобальный лог-файл приложения

 begin
 Logger := 'Приложение стартовало';

 {Код работы приложения …}

 Logger := 'Приложение завершило свою работу';

 Logger.Free;

 end.

Заключение

Мы рассмотрели примеры перегрузки операций в Delphi.NET. Правильное применение этой мощной возможности позволить сделать Ваш код более читабельным и облегчит его понимание другими разработчиками и дальнейшее его сопровождение.

Весь код приведенных примеров вы можете загрузить по данной ссылке.

Автор: Игорь Мельников (imelnikov@topsbi.ru)

Получить ссылку на материал

Категория: Delphi.NET | Добавил: Барон (13.12.2011)
Просмотров: 2162 | Теги: .net | Рейтинг: 0.0/0
[ Пожертвования для сайта ] [ Пожаловаться на материал ]

Если вам помог материал сайта кликните по оплаченной рекламе размещенной в центре

Поиск
Категории раздела
Delphi.NET [3]
Kylix Delphi for Linux [9]
Советы Дельферу [6]
Хитрости в Delphi [2]
Обзор Delphi [45]
Инсталлятор [11]
Пользовательский интерфейс [18]
Примеры Delphi [93]
Функции и процедуры [15]
Разные [31]
Королевство Delphi © 2010-2024
Яндекс цитирования