| « Поставить закладку » « Сделать стартовой » | |||
|
|||
| Статьи:: Delphi :: Delphi и .NET :: Delphi.NET: перегрузка операторов
Delphi.NET: перегрузка операторовОписание возможностей по переопределению операторов в Delphi.NETАвтор: Игорь Мельников (imelnikov@topsbi.ru)
|
| Символ оператора | Сигнатура метода | Категория |
|---|---|---|
| Неявное преобразование | 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; | Бинарный |
Интересно отметить, что такие часто используемые функции, как 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. Правильное применение этой мощной возможности позволить сделать Ваш код более читабельным и облегчит его понимание другими разработчиками и дальнейшее его сопровождение.
Весь код приведенных примеров вы можете загрузить по данной ссылке.
Инструменты Internet Explorer 8 Beta 2 для разработчиков.
Google Developer Day 2008 в Москве.
Дата проведения: 28 октября 2008 г.; Место проведения: Амбер Плаза, Москва, Россия. Конференция для веб-разработчиков и разработчиков мобильных приложений в Москве. Узнайте, как наилучшим образом использовать инструменты разработки и API от Google, чтобы создавать социальные, мобильные и картографические приложения, как использовать AJAX/JavaScript инструменты и библиотеки от Google и многое другое из первых уст.
ТОП 10 самых раздражающих факторов для программиста.
Совсем недавно наткнулся в интернете на забавный "хит-парад" наиболее раздражающих вещей для программиста. Поскольку он был на английском — решил перевести текст и несколько адаптировать к нашим реалиям…
Windows Server 7, 8 и 9
jQuery для JavaScript-программистов
Инновационный веб-броузер Google Chrome стартует уже сегодня
Windows 7: подход к производительности системы
Trac + Subversion @ Ubuntu: Revisited
[g]Vim в режиме Python: Рекомпиляция в Windows
Java + JSON. Пути к дружбе
Драйвер SQL Server 2005 для PHP
Типы данных в MySQL (сжатый справочник для PHP программиста)
PHP класс для работы с Яндекс.XML
Ошибки начинающих PHP разработчиков
Наследование шаблонов в Smarty
Особенности хранения сессий PHP в memcached
Internet Explorer 8 beta 2
9 правил для начинающего Ajax-разработчика
ExtJS 2.2 - полная поддержка Firefox 3, новые виджеты и другие нововведения| Кто на сайте |
Realcoding.NET
© 2003-2008 |
Контакты |
Реклама на сайте
|