Class — Ключевое слово Delphi


Содержание

Лекция 14. ООП (Классы. — глава из учебника [3])

3.5.1 Объявление класса

Класс — это тип данных, определяемый пользователем. То, что в Delphi имеется множество предопределенных классов, не противоречит этому определению -ведь разработчики Delphi тоже пользователи Object Pascal.

Класс должен быть объявлен до того, как будет объявлена хотя бы одна переменная этого класса. Т.е. класс не может объявляться внутри объявления переменной.

В любом вашем Делфи-приложении вы можете увидеть строки:

Это объявление класса TForml вашей формы и объявление переменной Forml -объекта этого класса.

В общем случае синтаксис объявления класса следующий:

Имя класса может быть любым допустимым идентификатором. Но принято идентификаторы большинства классов начинать с символа «Т». Имя класса — родителя может не указываться. Тогда предполагается, что данный класс является непосредственным наследником TObject — наиболее общего из предопределенных классов. Таким образом, эквивалентны следующие объявления: В приведенном ранее объявлении класса формы TForml видно, что его родительским классом является класс TForm.

Класс наследует поля, методы, свойства, события от своих предков и может отменять какие-то из этих элементов класса или вводить новые. Доступ к объявляемым элементам класса определяется тем, в каком разделе они объявлены.

Раздел public (открытый) предназначен для объявлений, которые доступны для внешнего использования. Это открытый интерфейс класса. Раздел published (публикуемый) содержит открытые свойства, которые появляются в процессе проектирования на странице свойств Инспектора Объектов и которые, следовательно, пользователь может устанавливать в процессе проектирования. Раздел private (закрытый), содержит объявления полей, процедур и функций, используемых только внутри данного класса. Раздел protected (защищенный) содержит объявления, доступные только для потомков объявляемого класса. Как и в случае закрытых элементов, можно скрыть детали реализации защищенных элементов от конечного пользователя. Однако в отличие от закрытых, защищенные элементы остаются доступны для программистов, которые захотят производить от этого класса производные объекты, причем не требуется, чтобы производные объекты объявлялись в этом же модуле.

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

В приведенном ранее объявлении класса формы вы можете видеть строку

Это объявление объекта (поля) Buttonl типа (класса) TButton.

Имеется одно очень существенное отличие объявления поля от обычного объявления переменной: в объявлении поля не разрешается его инициализация каким-то значением. Автоматически проводится стандартная инициализация: порядковым типам в качестве начального значения задается 0, указателям — nil, строки задаются пустыми. При необходимости задания других начальных значений используются конструкторы, описанные далее в разд. 3.5.3.

Объявления методов в простейшем случае также не отличаются от обычных объявлений процедур и функций.

3.5.2 Свойства

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

Свойство объявляется оператором вида:

Если в разделах read или write этого объявления записано имя поля, значит предполагается прямое чтение или запись данных (т.е. обмен данными непосредственно с полем).

Если в разделе read записано имя метода чтения, то чтение будет осуществляться только функцией с этим именем. Функция чтения — это функция без параметра, возвращающее значение того типа, который объявлен для свойства. Имя функции чтения принято начинать с префикса Get, после которого следует имя свойства.

Если в разделе write записано имя метода записи, то запись будет осуществляться только процедурой с этим именем. Процедура записи — это процедура с одним параметром того типа, который объявлен для свойства. Имя процедуры записи принято начинать с префикса Set, после которого следует имя свойства.

Если раздел write отсутствует в объявлении свойства, значит это свойство только для чтения и пользователь не может задавать его значение.

Директивы запоминания определяют, как надо сохранять значения свойств при сохранении пользователем файла формы .dfm. Чаще всего используется директива default — значение по умолчанию.
Она не задает начальные условия. Это дело конструктора. Директива просто говорит, что если пользователь в процессе проектирования не изменил значение свойства но умолчанию, то сохранять значение свойства не надо.

Приведем пример. Мы уже не раз использовали объекты, содержащие сведения о сотруднике некоторой организации. Но раньше мы реализовывали эти объекты в виде записей (см. разд. 3.3). А теперь рассмотрим более общий подход — реализацию класса подобных объектов.

Начните новый проект. Объявление класса, который мы хотим создать, можно поместить непосредственно в файл модуля. Но если вы хотите создать класс, который будете использовать в различных проектах, лучше оформить его в виде отдельного модуля unit, сохранить в каталоге своей библиотеки или библиотеки Delphi, и подключать в дальнейшем к различным проектам с помощью предложения uses. Если вы забыли, как создается отдельный модуль, не связанный с формой, и как он сохраняется в библиотеке, посмотрите все это в разд. 2.8.7.4. Мы выберем именно этот вариант. Так что выполните команду File | New | Unit (в некоторых более старых версиях Delphi — File | New и на странице New выберите пиктограмму Unit). Сохраните сразу этот ваш модуль в библиотеке под именем, например, MyClasses. А в модуль формы введите оператор, ссылающийся на этот модуль:

Теперь займемся созданием класса в модуле My > Вглядимся в приведенный код. Интерфейсный раздел модуля interface начинается с предложения uses. Заранее включать это предложение в модуль не требуется. Но по мере написания кода вы будете встречаться с сообщениями компилятора о неизвестных ему идентификаторах функций, типов и т.п. Столкнувшись с таким сообщением, надо посмотреть во встроенной справке Delphi или в справке [3], в каком модуле объявлена соответствующая функция или класс. И включить этот модуль в приложение uses.

Теперь обратимся к объявлению класса. Объявленный класс TPerson наследует непосредственно классу TObject, поскольку родительский класс не указан. В закрытом разделе класса private объявлен ряд полей. Поле FName предполагается использовать для фамилии, имени и отчества человека. Поля FDepl, FDep2, FDep3 будут использоваться под указание места работы или учебы. Поле FYear будет хранить год рождения, поле FSex — указание пола: символ «м» или «ж». Поле FAttr будет хранить какую-то характеристику: штатный — нештатный, отличник или нет и т.п. Поле FComment предназначено для каких-то текстовых комментариев. В частности, в нем можно хранить свойство Text многострочного окна редактирования Memo или RichEdit. Так что это может быть развернутая характеристика человека, правда, без форматирования.

В открытом разделе класса public объявлены свойства, соответствующие всем полям. Чтение всех свойств осуществляется непосредственно из полей. Запись во всех свойствах, кроме Sex, осуществляется тоже непосредственно в поля. А для поля Sex указана в объявлении свойства процедура записи SetSex, поскольку надо следить, чтобы по ошибке в это поле не записали символ, отличный от «м» и «ж». Соответственно в защищенном разделе класса protected содержится объявление этой процедуры. Как говорилось ранее, она должна принимать единственный параметр типа, совпадающего с типом свойства.

В раздел модуля implementation введена реализация процедуры записи SetSex. Ее заголовок повторяет объявление, но перед именем процедуры вводится ссылка на класс TPerson, к которому она относится. Не забывайте давать такие ссылки для методов класса. Иначе получите сообщение компилятора об ошибке; Unsatisfied forward or external declaration: TPerson.SetSex — нереализованная ранее объявленная или внешняя функция TPerson.SetSex.

Тело процедуры SetSex в особых комментариях не нуждается. В нем проверяется допустимость символа, переданного в процедуру через параметр Value. Если символ допустимый, то его значение заносится в поле FSex. Это поле, как и другие закрытые поля, открыто для методов данного класса. При ошибочном символе пользователю выдается сообщение об ошибке. Правда, лучше было бы в этом случае сгенерировать исключение, но пока мы не обсуждали, как это можно делать.

Вы создали класс в вашем модуле MyClasses. Давайте посмотрим, как можно использовать объекты нашего класса. Создайте в модуле формы Unit1 вашего приложения тест класса TPerson. Введите в модуль операторы:

uses Classl;
var Pers: TPerson;

Они обеспечивают связь с модулем, описывающим класс, и объявляют переменную Pers, через которую вы будете связываться с объектом класса. Но так же, как при работе с другими объектами и записями, объявление этой переменной еще не создает сам объект. Это указатель на объект, и его значение равно nil.

Создание объекта вашего класса TPerson должно осуществляться вызовом его конструктора Create. Так что создайте обработчик события OnCreate вашей формы, и вставьте в него оператор:

Вот теперь объект создан, и переменная Pers указывает на него. Чтобы не забыть очистить память от этого объекта при завершении работы приложения, сразу создайте обработчик события OnDestroy формы, и вставьте в него оператор:

Почему ваш объект воспринимает методы Create и Free? Ведь вы их не объявляли в классе TPerson! Это работает механизм наследования. В классе TObject, являющемся родительским для TPerson, методы Create и Free имеются. А поскольку вы их не перегружали, то ваш класс наследует их.

Теперь перенесите на форму четыре окна Edit, окно Memo и две кнопки. Пусть первая кнопка с надписью Запись заносит в объект Pers данные из окон редактирования: ‘ фамилию с именем и отчеством, пол, подразделение, в котором работает или обучается человек, год рождения, характеристику из окна Memo. Обработчик щелчка на ней может иметь вид:

А вторая кнопка с надписью Чтение пусть осуществляет чтение информации из объекта в окна редактирования. Обработчик щелчка на ней может иметь вид:

Выполните ваше приложение. Занесите в окна редактирования какую-то подходящую информацию и щелкните на кнопке Запись. А потом сотрите тексты всех окон редактирования и щелкните на кнопке Чтение. Информация в окнах должна восстановиться. Проверьте также реакцию на неверный символ, задающий пол.

Классы и объекты

22.01.2011, 09:10

Классы и объекты
Добрый день всем.Хотел узнать кое что,надеюсь вопрос будет соответствовать название темы:) На.

Классы и объекты
Создать класс Date для работы с датами в формате «год.месяц.день». Дата представляется структурой с.

Классы и объекты
Не могу разобраться с классами для курсовой. Нужно сделать мобов. Каждый должен иметь timer и.

Объекты и классы
Всем привет! Может кто-то нормальным русским языком объяснить, что такое классы и объекты, как.

Объекты и классы
Доброго всем! Возникла проблема с задачей. помогите пожалуйста разобраться. Нужно нарисовать.

22.01.2011, 09:16 2 18.04.2011, 23:58 3

Может вам это поможет.

Тема 2.2.41. Классы и объекты

1. Основные понятия
2. Составляющие класса
3. Объявление класса

1. Основные понятия

Классами в Object Pascal называются специальные типы, которые содержат поля, методы и свойства. Как и любой другой тип, класс служит лишь образцом для создания конкретных экземпляров реализации, которые называются объектами. Понятие класса разработчиками Object Pascal было заимствованно из языка Си++.
Всякий экземпляр класса называется объектом. По структуре и способу доступа класс в некоторой степени напоминает запись.
Важным отличием классов от других типов является то, что объекты класса всегда распределяются в куче, поэтому объект-переменная фактически представляет собой лишь указатель на динамическую область памяти. Однако в отличие от других указателей при ссылке на содержимое объекта запрещается использовать символ «^» за именем объекта:
Пример описания:

Таким образом, по умолчанию непосредственным родилем любого пользовательского класса считается класс TObject.
Принцип наследования приводит к созданию ветвящегося дерева классов, постепенно разрастающегося при перемещении от TObject к его потомкам. Каждый потомок дополняет возможности своего родителя новыми и передает их своим потомкам.
Полиморфизм – это свойство классов решать схожие по смыслу проблемы разными способами. В рамках Object Pascal поведенческие свойства класса определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках класса, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перекрыть его в потомке, т. е. объявить в потомке одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющие разную алгоритмическую основу и, следовательно, придающие объектам разные свойства. Это и называется полиморфизмом объектов.
В Object Pascal полиморфизм достигается не только описанным выше механизмом наследования и перекрытия методов родителя, но и их виртуализацией, позволяющей родительским методам обращаться к методам своих потомков.

2. Составляющие класса

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

В контексте программы свойство ведет себя как обычное поле. Разница между полем и свойством заключается в том, что при обращении к свойству автоматически подключается соответствующий метод: при передачи свойству нового значения – метод, описанный после write; если же свойство передает свое значение – метод read. Если нет необходимости в специальных действиях при чтении или записи свойства, вместо имени соответствующего метода можно указывать имя поля.Если необходимо, чтобы поле было доступно только для чтения или только для записи, следует опустить соответственно часть write или read. Вообще говоря, свойство может и не связываться с полем. Фактически оно описывает один или два метода, кото рые осуществляют некоторые действия над данными того же типа, что и свойство.

3. Объявление класса

Любой вновь создаваемый класс может содержать секции (разделы), определяемые зарезервированными словами published (декларированные), private (личные), protected (защищенные), public (доступные) и automated (автоматизированные). Внутри каждой секции вначале определяются поля, а затем – методы и свойства.
Секции определяют области видимости элементов описания класса. Секция public не накладывает ограничений на область видимости перечисляемых в ней полей, методов и свойств – их можно вызывать в любом другом модуле программы. Секция published также не ограничивает область видимости, однако в ней перечисля ются свойства, которые должны быть доступны не только на этапе исполнения, но и на этапе конструирования программы (т. е. в окне Инспектора Объектов). Секция published используется только при разработке нестандартных компонентов. Среда Delphi помещает описания компонентов, вставленных в форму, в специальную секцию без названия, которая располагается сразу за заголовком класса и продолжается до первой объявленной секции. Эта секция – published. В нее не следует помещать собственные элементы описания класса или удалять из нее элементы, вставленные средой. Секция private сужает область видимости до минимума: личные элементы описания доступны только внутри методов данного класса и подпрограммах, находящихся в том же модуле, где описан класс. Элемент, объявленный в секции private, становится недоступным даже ближайшим потомкам класса, если они размещаются в других модулях. Секция protected доступна только методам самого класса, а также любым его потомкам независимо от того, находятся ли они в том же модуле или нет. Наконец, секция automated используется только для объявления свойств и методов, которые будут добавлены к так называемому интерфейсу OLE-объектов Автоматизации; область видимости членов этой секции не ограничена.
В Object Pascal разрешается сколько угодно раз объявлять любую секцию, причем порядок следования секций не имеет значения. Любая секция может быть пустой.
Класс может объявляться только в интерфейсной области модуля или в самом начале области реализации. Нельзя определять классы в разделе описаний подпрограмм.

Поля, свойства и методы

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

При объявлении имен полей принято к названию добавлять заглавную букву F . Например FSomeField.

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

Для получения и передачи данных в классе применяются свойства. Для объявления свойств в классе используется зарезервированное слово property .

Свойства представляют собой атрибуты, которые составляют индивидуальность объекта и помогают описать его. Например, обычная кнопка в окне приложения обладает такими свойствами, как цвет, размеры, положение. Для экземпляра класса «кнопка» значения этих атрибутов задаются при помощи свойств — специальных переменных, определяемых ключевым словом property . Цвет может задаваться свойством Color , размеры — свойствами Width и Height и т. д.

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

function GetColor: TSomeType;

procedure SetColor(ANewValue: TSomeType);

property AColor: TSomeType read GetColor write SetColor;

В данном примере доступ к значению свойства AColor осуществляется через вызовы методов GetColor и SetColor . Однако в обращении к этим методам в явном виде нет необходимости: достаточно написать:

и компилятор самостоятельно оттранслирует обращение к свойству AColor в вызовы методов Getcolor или Setcolor . Tо есть внешне свойство выглядит в точности как обычное поле, но за всяким обращением к нему могут стоять нужные вам действия. Например, если у вас есть объект, представляющий собой квадрат на экране, и его свойству «цвет» вы присваиваете значение «белый», то произойдет немедленная перерисовка, приводящая реальный цвет на экране в соответствие со значением свойства. Выполнение этой операции осуществляется методом, который связан с установкой значения свойства «цвет».

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

function Correct(AValue: Integer):boolean;

procedure SetValue(NewValue: Integer);

property AValue: Integer read FValue write SetValue;

procedure TPropObject.SetValue(NewValue: Integer);

if (NewValueoFValue) and Correct(NewValue) then EValue := NewValue;

В этом примере чтение значения свойства AValue означает просто чтение поля rvalue . Зато при присвоении значения внутри SetValue вызывается сразу два метода.

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

property AProperty: TSomeType read GetValue;

В этом примере вне объекта значение свойства можно лишь прочитать; попытка присвоить свойству AProperty значение вызовет ошибку компиляции.

Для присвоения свойству значения по умолчанию используется ключевое слово default :

property Visible: boolean read FVisible write SetVisible default True;

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

Свойство может быть и векторным; в этом случае оно внешне выглядит как массив:

property APoints[Index : Integer]:TPoint read GetPoint write SetPoint;

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

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

Аналогично, метод, помещающий значения в такое свойство, должен первым параметром иметь индекс, а вторым — переменную нужного типа (которая может быть передана как по ссылке, так и по значению):

procedure SetPoint(Index:Integer; NewPoint:TPoint);

У векторных свойств есть еще одна важная особенность. Некоторые классы в Delphi (списки т-List, наборы строк TStrings ) «построены» вокруг основного векторного свойства (см. гл. 7). Основной метод такого класса дает доступ к некоторому массиву, а все остальные методы являются как бы вспомогательными. Специально для облегчения работы в этом случае векторное свойство может быть описано с ключевым словом default :

property Strings[Index: Integer]: string read Get write Put; default;

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

AMyObject[2] := ‘Second’; (второй способ>

Будьте внимательны, применяя зарезервированное слово default , — как мы увидели, для обычных и векторных свойств оно употребляется в разных случаях и с различным синтаксисом.

О роли свойств в Delphi красноречиво говорит следующий факт: у всех имеющихся в распоряжении программиста стандартных классов 100% полей недоступны и заменены базирующимися на них свойствами. Рекомендуем при разработке собственных классов придерживаться этого же правила.

Внимательный читатель обратил внимание, что при объяснении терминов «поле» и «свойство» мы использовали понятие метода, и наверняка понял его общий смысл. Итак, методом называется объявленная в классе функция или процедура, которая используется для работы с полями и свойствами класса. Согласно принципам ООП (см. разд. «Инкапсуляция» далее в этой главе), обращаться к свойствам класса можно только через его методы. От обычных процедур и функций методы отличаются тем, что им при вызове передается указатель на тот объект, который их вызвал. Поэтому обрабатываться будут данные именно того объекта, который вызвал метод. На некоторых особенностях использования методов мы остановимся ниже.

НОВОСТИ ФОРУМА
Рыцари теории эфира
01.10.2020 — 05:20: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]69vJGqDENq4[/Youtube][/center]
[center]14:36[/center]
Osievskii Global News
29 сент. Отправлено 05:20, 01.10.2020 г.’ target=_top>Просвещение от Вячеслава Осиевского — Карим_Хайдаров.
30.09.2020 — 12:51: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Ok]376309070[/Ok][/center]
[center]11:03[/center] Отправлено 12:51, 30.09.2020 г.’ target=_top>Просвещение от Дэйвида Дюка — Карим_Хайдаров.
30.09.2020 — 11:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]VVQv1EzDTtY[/Youtube][/center]
[center]10:43[/center]

интервью Раввина Борода https://cursorinfo.co.il/all-news/rav.
мой телеграмм https://t.me/peshekhonovandrei
мой твиттер https://twitter.com/Andrey54708595
мой инстаграм https://www.instagram.com/andreipeshekhonow/

[b]Мой комментарий:
Андрей спрашивает: Краснодарская синагога — это что, военный объект?
— Да, военный, потому что имеет разрешение от Росатома на манипуляции с радиоактивными веществами, а также иными веществами, опасными в отношении массового поражения. Именно это было выявлено группой краснодарцев во главе с Мариной Мелиховой.

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

Class — Ключевое слово Delphi

На этом шаге мы напомним общие понятия о классах и рассмотрим описание классов в Delphi .

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

Экземпляр типа класс принято называть объектом . Объекты класса всегда распределяются в куче в отличие от экземпляров объектового типа. Итак, класс — это описание, объект — то, что создано в соответствии с этим описанием .

Тип класс — это структура данных, состоящая из полей, методов, свойств . Поля содержат данные определенного типа. Методы — это функции и процедуры, описанные внутри класса и предназначенные для операций над его полями. Свойства — это специальный механизм классов, регулирующий доступ к полям. Свойство описывает один или два метода, которые осуществляют некоторые действия над данными того же типа, что и свойство. Например, обычная кнопка в окне приложения обладает такими свойствами, как цвет, размеры, положение. Для экземпляра класса «кнопка» значения этих свойств представлены специальными переменными, определяемыми ключевым словом Property . Цвет может задаваться свойством Color , размеры — свойствами Width и Height и т.д.

Так как свойство обеспечивает обмен данными с внешней средой, то для доступа к его значению используются специальные методы класса. Например:

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

В данном примере доступ к значению свойства MyColor осуществляется через вызовы методов GetColor и SetColor . К методам в явном виде не обращаются, достаточно записать:

и компилятор оттранслирует эти операторы в вызовы соответствующих методов: записи ( SetColor ) и чтения ( GetColor ).

Особым видом свойств являются события . В Object Pascal событие — свойство процедурного типа, предназначенное для создания пользовательской реакции на то или иное входное воздействие:

FOnMyEvent1, FOnMyEvent2 — поля, содержащие адреса некоторых методов. Присвоить такому свойству значение — значит, указать объекту адрес метода, который будет вызываться в момент наступления события . Такие методы называют обработчиками событий. Таким образом, обработчик события — фрагмент программы, который выполняется в ответ на определенное изменение в программе или в Windows .

Каждый новый класс в Delphi должен быть объявлен глобально. Для этого используется зарезервированное слово Class . Объявление определяет функциональные возможности класса. Объявление классов в модуле производится в разделе объявления типов. Пример объявления класса и объекта в Delphi :

В объявлении типа определен новый класс — TForm1 , наследуемый от класса TForm , содержащегося в библиотеке визуальных компонентов. На это указывает зарезервированное слово Class . Данный тип содержит указатели на компоненты, которые были помещены на форму:

  • один компонент Label1 — объект типа TLabel (иначе говоря, экземпляр класса TLabel ),
  • один компонент Edit1 — объект типа TEdit (экземпляр класса TEdit ) и
  • два экземпляра класса TBitBtn .

Область видимости идентификатора компонента, объявленного в описании класса, простирается от его объявления до конца определения класса, а также распространяется на все потомки этого класса и на все блоки реализации методов класса. Область видимости идентификатора компонента зависит от раздела, в котором объявлен этот идентификатор.

В объявлениях типов класса имеются разделы частных ( private ), общих ( public ), защищенных ( protected ) и опубликованных ( published ) объявлений.

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

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

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

Свойства, расположенные в разделе опубликованных объявлений , отображаются в Инспекторе объектов (Object Inspector) .

На следующем шаге мы поговорим об иерархии классов .

КЛАССЫ И ОБЪЕКТЫ Delphi

Для реализации интерфейса Delphi использует библиотеку классов, которая содержит большое количество разнообразных классов, поддерживающих форму и различные компоненты формы (командные кнопки, поля редактирования и т. д.).

Классыи Объекты Delphi имеют следующую иерархию (см. диаграмму): Самый первый класс (основной), который не имеет предков, а сам является предком для всех остальных – TObject.

TObject.Этот класс не имеет полей и свойств, но включает в себя методы самого общего назначения, обеспечивающие весь жизненный цикл любых объектов – от их создания до уничтожения.

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

Класс TComponent умеет взаимодействовать со средой разработчика и передает это умение потомкам всем компонентам Delphi.

Класс TСontrol умеет взаимодействовать со средой и умеет обслуживать видимые на экране изображения,

а его потомок может создавать Windows-окна и т. д.

Если выразить наследование в виде иерархической структуры, то для созданных элементов управления Button1, Button2 и CheckBox1 получим следующую диаграмму:

Например, элемент управления CheckBox1 (т.е. класс CheckBox1) может наследовать все или некоторые свойства и методы, вышестоящих по иерархии классов, а никакие черты от класса TButton – наследовать не может. Другими словами можно сказать, что CheckBox1 произведен (derived) непосредственно от класса-предка TCheckBox, а также от TObject, TPersistent, TComponent, TControl и TWinControl.

Классы бывают абстрактными , это те классы ,что не имеют объектов. например TObject Абстрактные методы – методы, которые не содержат исполняемых операторов и служат заготовками для аналогичных методов в объектах – потомках. Абстрактные методы должны перекрываться в потомках.

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

Type

TForm1 = class(TForm)

Private

Public

end;

Var

Это описание класса исходной, пустой формы приложения и объявление объекта — формы приложения.

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

Помимо классов визуальных компонентов в библиотеку классов входят и классы так называемых невизуальных (невидимых) компонентов, которые обеспечивают создание соответствующих объектов и доступ к их методам и свойствам. Типичным примером невизуального компонента является таймер (тип TTimer) и компоненты доступа и управления базами данных. Существует еще множество других классов,

ПОЛИМОРФИЗМ

Полиморфизмнеразрывно связан с наследованием

Полиморфизм– возможность замещения методов класса-родителя одноименными методами класса-потомка.

Другими словами, — это свойство классов решать сходные по смыслу задачи разными способами.

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

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

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

Способность использования одного и того же метода сообразно своему классу называется полиморфизмом.

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

Класс TEmpioyee должен иметь свой собственный конструктор, обеспечивающий инициализацию класса-родителя и своих полей. Вот пример реализации конструктора класса TEmployee:

constructorTEmpioyee.Create(Name:Tname;Dep:integer);

Begin

inheritedCreate(Name);//inherited (англ.- унаследованный)

end;

В приведенном примере директивой inherited (англ.- унаследованный ) вызывается конструктор родительского класса. После этого присваивается значение полю класса-потомка.

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

Var engineer :TEmployee;

engineer := TEmployee.Create(‘Сидоров’,413);

engineer.address := ‘ул.Блохина, д.8, кв.10’;

engineer.age := 23;

if engineer.dep=413 then …

Type

Person=class

name: string;

constructor Create(nm: string; ag: integer);

Begin

end;

procedure Print;

Begin

writeln(‘Имя: ‘,name,’ Возраст: ‘,age);

end;

end;

Student=class(Person)

course, group: integer; новые поля

constructor Create(nm: string; ag,c,gr: integer);

begin //директивой inherited (англ.- унаследованный ) вызывается конструктор родительского //класса. После этого присваивается значение полю класса-потомка.

inherited Create(nm,ag); //перекрытие метода родительского


course:=c; group:=gr; //и добавление новых действий в методе

end;

procedure Print;

Begin

inherited Print; //перекрытие метода родительского

writeln(‘Курс: ‘,course,’ Группа: ‘,group);// и добавление новых действий в методе

end;

end;

Здесь метод Print производного класса Student вызывает вначале метод Print, унаследованный от базового класса Person, с помощью конструкции inherited Print. Аналогично конструктор Create класса Student вызывает вначале конструктор Create базового класса Person, также используя служебное слово inherited. Следует обратить внимание, что конструктор базового класса вызывается в этом случае как процедура, а не как функция, при этом создания нового объекта не происходит.

Основы создания компонент в среде Delphi

Delphi , Компоненты и Классы , Создание компонент

Начнем с самого простого — с создания объявления класса. Для этого в меню «Component» выбираем пункт «New Component…». В появившемся окне, нужно задать следующие значения: Ancestor Type (класс родитель создаваемого класса), Class Name (имя создаваемого класса), Palette Page (название закладки палитры компонентов, на которую будет установлен данный компонент; если такой закладки не существует, то она будет создана), Unit File Name (имя модуля, в котором будет размещено описание класса).

Итак, создадим потомок TСustomControl’а — TMyClass, в результате чего получим следующие объявление класса:

Ключевое слово class указывает на то, что мы создаем новый класс TMyClass, порождая его от родителя — TCustomControl.

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

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

В соответствии с концепцией объектно-ориентированного программирования (инкапсуляцией) в Delphi существуют четыре директивы видимости, предназначенные для сокрытия полей и методов внутри объекта. Итак, первая из них — private. Это сама строгая из всех директив видимости. Поля и методы, объявленные в этой секции, не будут видны во всех классах-потомках. Вторая, менее строгая — protected. Она предназначена для объявления тех полей и методов, которые должны быть видны в классах-потомках. Следующая директива — public, предназначена для объявления элементов, которые будут видны программе или модулю, если они имеют доступ к модулю, в котором объявлен класс. И, наконец, последняя директива — published. Она предназначена для объявления тех свойств, которые должны быть видны в инспекторе объектов на этапе проектирования (design-time).

Вообще, поля — это обычные переменные, инкапсулированные внутри объекта. Объявляются они точно так же, как и в обычном Паскале (смотри пример ниже, FStr — поле типа String). Также полями могут быть и объекты.

Это одна из самых главных частей объекта. Методы обеспечивают своеобразный интерфейс между внутренней (инкапсулированной) частью объекта и внешней (доступной вне объекта). Объявляются так же, как функции и процедуры в обычном Паскале. Вот простой пример:

Реализация методов производится в том же модуле, где и объявление класса, в секции implementation, следующим образом:

Этот пример также хорошо иллюстрирует сущность инкапсуляции. Класс TMyClass инкапсулирует поле FStr и реализацию методов записи — SetStr и чтения — GetStr этого поля.

Для перекрытия методов класса-предка в классе-потомке используется специальная директива override. При перекрывании метода надо учитывать следующее: метод, объявленный в классе-предке в секции private, перекрыть в потомке нельзя; нельзя также перекрывать методы, объявляя их в потомке с меньшей видимостью, чем в классе-предке; при перекрытии методов обработки сообщений директива override не используется. Для того чтобы в теле перекрывающего метода обратиться к методу предка, используется директива inherited. Пример:

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

Методы могут быть виртуальными и динамическими. Они различаются способом решения классической проблемы «время — ресурс, ресурс — время» (в данном случае ресурс — это объем оперативной памяти).

Разница между ними заключается в структуре соответствующих таблиц методов VMT — Virtual Metod Table (таблица виртуальных методов) и DMT — Dynamic Metod Table (таблица динамических методов). Диспетчеризация виртуальных методов происходит быстрее, чем динамических, но с другой стороны, таблица виртуальных методов (VMT) занимает больше места в памяти, чем таблица динамических (DMT). Выбор остается за программистом.

Для определения типа метода (виртуальный или динамический) используются две директивы — virtual и dynamic соответственно. Для указания типа метода после его объявления через точку с запятой указывается одна из этих директив. Вот простой пример:

Также стоит сказать и об абстрактных методах — они определяются путем добавления после объявления метода директивы abstract. Такое объявление метода делает ненужным и даже неправомочным реализацию данного метода в текущем классе. Вызов такого метода также будет считаться неправомочным. Такие методы в основном предназначены для перекрытия их в потомках. Пример объявления абстрактного метода:

Методы обработки сообщений

Еще одна разновидность методов — это методы обработки сообщений (message-handling metods). Эти методы специально предназначены для обработки сообщений Windows. На них накладываются некоторые ограничения: они обязательно должны быть процедурами, должны иметь один параметр — переменную; при прикрытии таких методов директива override не применяется; также при перекрытии необходимо вызывать метод предка. Объявляются такие методы с помощью директивы message. Вот пример объявления и реализации метода обработки сообщений:

Константа после директивы message определяет сообщение, которое будет обрабатываться этим методом (в данном случае метод будет вызываться каждый раз, когда происходит перемещение курсора мыши над данным объектом). Тип единственного параметра процедуры задает, в каком формате методу будут переданы данные сообщения (например, в этом случае Msg.XPos и Msg.YPos — текущие координаты курсора мыши в области данного компонента). Описание констант и соответствующих типов помещено в модуле Messages.

Классовые методы (или методы класса)

Существует еще одно важное расширение методов — классовые методы. Это методы, которые можно вызывать, не создавая экземпляра объекта (например: TMyClass.MyClassMetod). В теле классового метода нельзя обращаться ни к одному методу или полю данного класса, но можно вызывать конструкторы и другие классовые методы. Обычно такие методы используются для предоставления информации о классе, версии реализации и т. д. Определяются такие методы путем добавления директивы class перед объявлением метода. Вот пример классового метода и его реализации:

Перерегружаемые (overload) методы

И напоследок, стоит обсудить перегружаемые методы. Они предоставляют возможность более гибко взаимодействовать с объектом. Это достигается за счет того, что может существовать несколько различных методов с одним идентификатором, но различными параметрами. Объявляются они с использованием специальной директивы — overload.

В данном примере, при вызове пользователем метода SetX, в зависимости от типа передаваемого параметра будет вызван соответствующий метод (для параметра типа String — первый, для Integer — второй).

Пример вызова первого варианта метода:

Пример вызова второго варианта метода:

Удобства, предоставляемые данной возможностью, очевидны, например, в данном случае пользователю не нужно самому конвертировать Integer в String.

Конструкторы и деструкторы

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

Свойства — это второе по важности (после методов) средство обеспечения взаимодействия объекта с окружающим миром. Через свойства обеспечивается доступ к полям объекта, только свойства могут быть видны в инспекторе объектов (Object Inspector) Delphi.

Объявление свойства, чтение и запись.

Вот один из вариантов объявления свойства:

Свойства объявляются с использованием директивы property, за ней следует имя свойства и тип (как объявление переменной в обычном Паскале), затем директива read и указание «источника» чтения (это либо поле такого же типа, как свойство, либо функция без параметров, возвращающая значение того же типа), далее следует директива write с указанием «приемника» записи (поле с тем же типом, что и свойство, либо процедура с одним параметром, того же типа). В нашем случае чтение осуществляется напрямую из поля, а запись через процедуру. Здесь есть одна хитрость. Соответствующее поле не обязательно должно существовать или иметь тот же тип, что и свойство. Это позволяет снизить избыточность хранимой информации и уменьшить расход памяти. Распространенным примером такого подхода является предоставление информации о размере какого-либо массива, например так:

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

property StrCount: Integer read GetStrCount; — свойство только для чтения (read-only)
property StrCount: Integer write SetStrCount; — свойство только для записи (write-only).

Применение методов для чтения и записи зачастую обусловлено необходимостью в так называемом побочном эффекте. Это ситуация, когда при изменении значения свойства необходимо произвести еще какие-либо изменения. Так, например, при изменения свойства Height или Width для любого визуального объекта тут же происходит его перерисовка, и он меняет свои размеры. Методы записи также часто используются для проверки присваемого свойству значения на допустимость.

Теперь разберемся, как организовать свойство, предоставляющее доступ непосредственно к элементам массива — строкам для предыдущего примера. Для этого обязательно чтение и запись производить через соответствующий метод, добавляя в его описание один параметр — индекс, и задать тип индекса для самого свойства. На практике это выглядит так:

Тип индекса может быть и другим, например строкой.

Для многомерных массивов все точно так же, только вместо одного индекса (и в объявлении методов чтения и записи, и в описании свойства) нужно указывать необходимое число индексов, например — для двумерного массива:

К таким свойствам следует обращаться, как к массивам, например для экземпляра Test класса TMyClass — Test.Str[5].

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

Как видно из примера, если для свойства определен индекс, совершенно необязательно для чтения или записи использовать метод, поддерживающий индексирование. Методом чтения и записи в качестве индекса (параметр Index, если у них таковой имеется) для каждого свойства будет передаваться то значение, которое указано после директивы index. Ну а в теле метода по полученному значению индекса выясняем, к какому из свойств было осуществлено обращение, и предпринимаем соответствующие действия.

Свойства совсем необязательно объявлять в public или published секциях. Очень часто наоборот бывает удобно объявить свойство в секции protected для последующего переобъявления его в классах-потомках. Но не все так хорошо — нельзя понизить видимость, т. е. если свойство объявлено в классе-предке в секции public, то переобъявив его в потомке в секции protected, невидимым его сделать не получится.

Переобъявляются свойства очень просто, возьмем наш пример с массивом строк:

Эта директива применяется для задания значения по умолчанию для свойств. На практике выглядит так:

Тип свойства должен совпадать с типом значения, указываемого после default’а.

С этой директивой связано одно распространенное заблуждение — многие считают, что с ее помощью можно инициировать значения свойств. Это неверно. Дело в том, что все значения свойств, установленные программистом для определенного объекта, в инспекторе объектов сохраняются в файл формы (.dfm), который потом компилируется вместе с кодом. При запуске приложения автоматически создаются объекты, заданные на этапе проектирования, и для них загружаются и устанавливаются значения свойств. Это не выгодно, во-первых, потому, что увеличивает размер откомпилированного exe файла, а во-вторых, на все это тратится изрядная доля времени при запуске приложения.

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

Для отмены значения по умолчанию, определенного в классе-предке, нужно переобъявить свойство с использованием директивы nodefaul, например:

Для изменения значения по умолчанию нужно преобъявить свойство и с помощью директивы default указать новое значение по умолчанию, например:

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

На такое свойство можно ссылаться без указания его имени, например для экземпляра MyClass класса TMyClass — MyClass[10] равносильно MyClass.Str[10].

Эта директива предназначена для жесткого определения — сохранять значение свойства в файле формы или нет. Она добавляется в конец определения свойства, и после нее должно идти либо булево (boolean — FALSE или TRUE) значение, либо функция, возвращающая булево значение, либо поле булевого типа. Если булево значение ИСТИНА (TRUE), то сохранение значения свойства происходит, если ЛОЖЬ (FALSE) — нет. Этот механизм может быть полезен, если существует какая-либо избыточность полей и соответствующих им свойств. Возможно совместное использование директив default и stored. Пример (взят из модуля Controls):

События — это единственный для объекта способ спровоцировать какое-либо внешнее действие в ответ на изменение его состояния.

Событие как свойство

В Delphi все события реализуются как свойства определенного типа. Этот тип должен быть предварительно определен. Вот определение самого распространенного в Delphi свойства:

Как видно, это обычная процедура (также может быть использована и функция) с добавлением директивы of object.

Само объявление же события будет выглядеть так:

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

Перед вызовом события обязательно нужно проверить, существует ли обработчик этого события — это выполняется с помощью функции Assigned. Если этого не сделать, то при отсутствии обработчика получим ошибку времени выполнения, доступа к памяти. Self — внутри каждого объекта — это указатель на самого себя, который мы и передаем в качестве параметра Sender.

Общепринятые правила именования

Напоследок стоит сказать об общепринятых правилах именования классов и их составляющих:

  • из названия должно быть понятно, для чего предназначен данный класс, метод, поле или свойство;
  • название классов принято называть с T, от type;
  • к названию иногда добавляют инициалы разработчика компонента или часть названия фирмы. Для Васи Иванова это может быть — TviComponent;
  • имена полей принято начинать с F, от Field (поле, область);
  • свойства принято называть точно так же, как и поля (если свойству в соответствие ставится поле), опуская F. Поле FStr — свойство Str;
  • имена методов чтения и записи принято образовывать путем добавления перед именем свойства Get и Set, соответственно. Свойство Str — метод чтения GetStr, метод записи SetStr;
  • именование полей и методов чтения и записи для событий подчиняются тем же правилам, что и для свойств;
  • имена событий принято начинать с On: OnClick, OnMouseMove.

Для того чтобы установить созданный класс в палитру компонентов, в меню «Component» выбираем пункт «Install Component…». В появившемся окне нужно задать следующие значения: Unit file name (имя модуля, в котором содержится описание устанавливаемого класса), Search path (пути через «;» по которым Delphi будет искать указанный модуль), Package file name (путь и имя файла пакета, в который будет добавлен устанавливаемый вами модуль с описанием класса).

Статья Основы создания компонент в среде Delphi раздела Компоненты и Классы Создание компонент может быть полезна для разработчиков на Delphi и FreePascal.

Комментарии и вопросы

:: 2012-11-15 14:05:00 :: re:Основы создания компонент в среде Delphi

В описании перегружаемых методов вместо overload везде написано override.

:: 2012-11-15 14:13:33 :: re:Основы создания компонент в среде Delphi

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

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

Программирование на языке Delphi

Глава 3. Объектно-ориентированное программирование (ООП)


Авторы: А.Н. Вальвачев
К.А. Сурков
Д.А. Сурков
Ю.М. Четырько

Опубликовано: 19.11.2005
Исправлено: 10.12.2020
Версия текста: 1.0

Объекты — это крупнейшее достижение в современной технологии программирования. Они позволили строить программу не из чудовищных по сложности процедур и функций, а из кирпичиков-объектов, заранее наделенных нужными свойствами. Самое приятное в объктах то, что их внутренняя сложность скрыта от программиста, который просто пользуется готовым строительным материалом.

Сейчас преимущества использования объектов очевидны для всех. Однако так было не всегда. Сначала старая гвардия не поняла и не приняла объекты, поэтому они почти 20 лет потихоньку развивались в различных языках, первым из которых была Simula 67. Постепенно объектно-ориентированный подход нашел себе место и в более мощных языках, таких как C++, Delphi и множестве других языков. Блестящим примером реализации объектов была библиотека Turbo Vision, предназначенная для построения пользовательского интерфейса программ в операционной системе MS-DOS.

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

3.1. Краеугольные камни ООП


3.1.1. Формула объекта

Авторы надеются, что читатель помнит кое-что из главы 2 и такие понятия как тип данных, процедура, функция, запись для него не в новинку. Это прекрасно. Так вот, в конце 60-х годов кому-то пришло в голову объединить эти понятия, и то, что получилось, назвать объектом. Рассмотрение данных в неразрывной связи с методами их обработки позволило вывести формулу объекта :

Объект = Данные + Операции

На основании этой формулы была разработана методология объектно-ориентированного программирования (ООП).

3.1.2. Природа объекта

Об объектах можно думать как о полезных существах, которые «живут» в вашей программе и коллективно решают некоторую прикладную задачу. Вы, как Демиург, лепите этих существ, распределяете между ними обязанности и устанавливаете правила их взаимодействия.

В общем случае каждый объект «помнит» необходимую информацию, «умеет» выполнять некоторый набор действий и характеризуется набором свойств. То, что объект «помнит», хранится в его полях . То, что объект «умеет делать», реализуется в виде его внутренних процедур и функций, называемых методами . Свойства объектов аналогичны свойствам, которые мы наблюдаем у обычных предметов. Значения свойств можно устанавливать и читать. Программно свойства реализуются через поля и методы.

Например, объект «кнопка» имеет свойство «цвет». Значение цвета кнопка запоминает в одном из своих полей. При изменении значения свойства «цвет» вызывается метод, который перерисовывает кнопку.

Кстати, этот пример позволяет сделать важный вывод: свойства имеют первостепенное значение для программиста, использующего объект. Чтобы понять суть и назначение объекта вы обязательно должны знать его свойства, иногда — методы, очень редко — поля (объект и сам знает, что с ними делать).

3.1.3. Объекты и компоненты

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

Компоненты в среде Delphi — это особые объекты, которые являются строительными кирпичиками визуальной среды разработки и приспособлены к визуальной установке свойств. Чтобы превратить объект в компонент, первый разрабатывается по определенным правилам, а затем помещается в палитру компонентов. Конструируя приложение, вы берете компоненты из Палитры Компонентов, располагаете на форме и устанавливаете их свойства в окне Инспектора Объектов. Внешне все выглядит просто, но чтобы достичь такой простоты, потребовалось создать механизмы, обеспечивающие функционирование объектов-компонентов уже на этапе проектирования приложения! Все это было придумано и блестяще реализовано в среде Delphi. Таким образом, компонентный подход значительно упростил создание приложений с графическим пользовательским интерфейсом и дал толчок развитию новой индустрии компонентов.

В данной главе мы рассмотрим лишь вопросы создания и использования объектов. Чуть позже мы научим вас превращать объекты в компоненты (см. главу 13).

3.1.4. Классы объектов

Каждый объект всегда принадлежит некоторому классу объектов. Класс объектов — это обобщенное (абстрактное) описание множества однотипных объектов. Объекты являются конкретными представителями своего класса, их принято называть экземплярами класса . Например, класс СОБАКИ — понятие абстрактное, а экземпляр этого класса МОЙ ПЕС БОБИК — понятие конкретное.

3.1.5. Три кита ООП

Весь мир ООП держится на трех китах: инкапсуляции, наследовании и полиморфизме. Для начала о них надо иметь только самое общее представление.

Объединение данных и операций в одну сущность — объект — тесно связано с понятием инкапсуляции, которое означает сокрытие внутреннего устройства. Инкапсуляция делает объекты похожими на маленькие программные модули, в которых скрыты внутренние данные и у которых имеется интерфейс использования в виде подпрограмм. Переход от понятий «структура данных» и «алгоритм» к понятию «объект» значительно повысил ясность и надежность программ.

Второй кит ООП — наследование . Этот простой принцип означает, что если вы хотите создать новый класс объектов, который расширяет возможности уже существующего класса, то нет необходимости в переписывании заново всех полей, методов и свойств. Вы объявляете, что новый класс является потомком (или дочерним классом ) имеющегося класса объектов, называемого предком (или родительским классом ), и добавляете к нему новые поля, методы и свойства. Процесс порождения новых классов на основе других классов называется наследованием . Новые классы объектов имеют как унаследованные признаки, так и, возможно, новые. Например, класс СОБАКИ унаследовал многие свойства своих предков — ВОЛКОВ.

Третий кит — это полиморфизм . Он означает, что в производных классах вы можете изменять работу уже существующих в базовом классе методов. При этом весь программный код, управляющий объектами родительского класса, пригоден для управления объектами дочернего класса без всякой модификации. Например, вы можете породить новый класс кнопок с рельефной надписью, переопределив метод рисования кнопки. Новую кнопку можно «подсунуть» вместо стандартной в какую-нибудь подпрограмму, вызывающую рисование кнопки. При этом подпрограмма «думает», что работает со стандартной кнопкой, но на самом деле кнопка принадлежит производному классу кнопок и отображается в новом стиле. Пока достаточно самого поверхностного понимания всех приведенных выше понятий, ниже мы рассмотрим их подробнее и покажем, как они реализованы в среде Delphi.

3.2. Классы

Для поддержки ООП в язык Delphi введены объектные типы данных , с помощью которых одновременно описываются данные и операции над ними. Объектные типы данных называют классами , а их экземпляры — объектами .

Классы объектов определяются в секции type глобального блока. Описание класса начинается с ключевого слова class и заканчивается ключевым словом end . По форме объявления классы похожи на обычные записи, но помимо полей данных могут содержать объявления пользовательских процедур и функций. Такие процедуры и функции обобщенно называют методами , они предназначены для выполнения над объектами различных операций. Приведем пример объявления класса, который предназначен для чтения текстового файла в формате «delimited text» (файл в таком формате представляет собой последовательность строк; каждая строка состоит из значений, которые отделены друг от друга символом-разделителем):

Класс содержит поля (FileVar, Items, Delimiter) и методы (PutItem, SetActive, ParseLine, NextLine, GetEndOfFile). Заголовки методов, (всегда) следующие за списком полей, играют роль упреждающих (forward) описаний. Программный код методов пишется отдельно от определения класса и будет приведен позже.

Класс обычно описывает сущность, моделируемую в программе. Например, класс TDelimitedReader представляет собой «читатель» текстового файла с разбором считываемых строк на элементы (подстроки), которые отделены друг от друга некоторым символом, называемым разделителем.

Класс содержит несколько полей:

  • FileVar — файловая переменная, необходимая для доступа к файлу;
  • Delimiter — символ, который служит разделителем элементов;
  • Items — массив элементов, полученных разбором последней считанной строки;

Класс также содержит ряд методов (процедур и функций):

  • PutItem — помещает элемент в массив Items по индексу Index; если индекс превышает верхнюю границу массива, то размер массива автоматически увеличивается;
  • SetActive — открывает или закрывает файл, из которого производится чтение строк;
  • ParseLine — осуществляет разбор строки: выделяет элементы из строки и помещает их в массив Items; возвращает количество выделенных элементов;
  • NextLine — считывает очередную строку из файла и с помощью метода ParseLine осуществляет ее разбор; в случае успешного чтения очередной строки функция возвращает значение True, а иначе — значение False (достигнут конец файла);
  • GetEndOfFile — возвращает булевское значение, показывающее, достигнут ли конец файла.

Обратите внимание, что приведенное выше описание является ничем иным, как декларацией интерфейса для работы с объектами класса TDelimitedReader. Реализация методов PutItem, SetActive, ParseLine, NextLine и GetEndOfFile на данный момент отсутствует, однако для создания и использования экземпляров класса она пока и не нужна.

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

3.3. Объекты

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

При работе с обычными типами данных этого объявления было бы достаточно для получения экземпляра типа. Однако объекты в среде Delphi являются динамическими данными, т.е. распределяются в динамической памяти. Поэтому переменная Reader — это просто ссылка на экземпляр ( объект в памяти), которого физически еще не существует. Чтобы сконструировать объект (выделить память для экземпляра) класса TDelimitedReader и связать с ним переменную Reader, нужно в тексте программы поместить следующий оператор:

Create — это так называемый конструктор объекта; он всегда присутствует в классе и служит для создания и инициализации экземпляров. При создании объекта в памяти выделяется место только для его полей. Методы, как и обычные процедуры и функции, помещаются в область кода программы; они умеют работать с любыми экземплярами своего класса и не дублируются в памяти.

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

Кроме того, как и при работе с записями, допустимо использование оператора with , например:

Если объект становится ненужным, он должен быть удален вызовом специального метода Destroy, например:

Destroy — это так называемый деструктор объекта; он присутствует в классе наряду с конструктором и служит для удаления объекта из динамической памяти. После вызова деструктора переменная Reader становится несвязанной и не должна использоваться для доступа к полям и методам уже несуществующего объекта. Чтобы отличать в программе связанные объектные переменные от несвязанных, последние следует инициализировать значением nil . Например, в следующем фрагменте обращение к деструктору Destroy выполняется только в том случае, если объект реально существует:

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

После уничтожения объекта переменная Reader сохраняет свое значение, продолжая ссылаться на место в памяти, где объекта уже нет. Если эту переменную предполагается еще использовать, то желательно присвоить ей значение nil , чтобы программа могла проверить, существует объект или нет. Таким образом, наиболее правильная последовательность действий при уничтожении объекта должна быть следующая:

С помощью стандартной процедуры FreeAndNil это можно сделать проще и элегантнее:

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

Объекты могут выступать в программе не только в качестве переменных, но также элементов массивов, полей записей, параметров процедур и функций. Кроме того, они могут служить полями других объектов. Во всех этих случаях программист фактически оперирует указателями на экземпляры объектов в динамической памяти. Следовательно, объекты изначально приспособлены для создания сложных динамических структур данных, таких как списки и деревья. Указатели на объекты для этого не нужны.

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

Первое объявление класса TDelimitedReader называется упреждающим (от англ. forward). Оно необходимо для того, чтобы компилятор нормально воспринял объявление поля Owner в классе TDelimitedReader.

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

3.4. Конструкторы и деструкторы

Особой разновидностью методов являются конструкторы и деструкторы . Напомним, что конструкторы создают, а деструкторы разрушают объекты. Создание объекта включает выделение памяти под экземпляр и инициализацию его полей, а разрушение — очистку полей и освобождение памяти. Действия по инициализации и очистке полей специфичны для каждого конкретного класса объектов. По этой причине язык Delphi позволяет переопределить стандартный конструктор Create и стандартный деструктор Destroy для выполнения любых полезных действий. Можно даже определить несколько конструкторов и деструкторов (имена им назначает сам программист), чтобы обеспечить различные процедуры создания и разрушения объектов.

Объявление конструкторов и деструкторов похоже на объявление обычных методов с той лишь разницей, что вместо зарезервированных слов function и procedure используются слова constructor и destructor . Для нашего класса TDelimitedReader потребуется конструктор, которому в качестве параметра будет передаваться имя обрабатываемого файла и разделитель элементов:

Приведем их возможную реализацию:

Если объект содержит встроенные объекты или другие динамические данные, то конструктор — это как раз то место, где их нужно создавать.

Конструктор применяется к классу или к объекту. Если он применяется к классу,

то выполняется следующая последовательность действий:

  • в динамической памяти выделяется место для нового объекта;
  • выделенная память заполняется нулями. В результате все числовые поля и поля порядкового типа приобретают нулевые значения, строковые поля становятся пустыми, а поля, содержащие указатели и объекты получают значение nil ;
  • затем выполняются заданные программистом действия конструктора;
  • ссылка на созданный объект возвращается в качестве значения конструктора. Тип возвращаемого значения совпадает с типом класса, использованного при вызове (в нашем примере это тип TDelimitedReader).

Если конструктор применяется к объекту,

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

Деструктор уничтожает объект, к которому применяется:

  • выполняется заданный программистом код завершения;
  • освобождается занимаемая объектом динамическая память.

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

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

3.5. Методы

Процедуры и функции, предназначенные для выполнения над объектами действий, называются методами . Предварительное объявление методов выполняется при описании класса в секции interface модуля, а их программный код записывается в секции implementation . Однако в отличие от обычных процедур и функций заголовки методов должны иметь уточненные имена, т.е. содержать наименование класса. Приведем возможную реализацию одного из методов в классе TDelimitedReader:

Обратите внимание, что внутри методов обращения к полям и другим методам выполняются как к обычным переменным и подпрограммам без уточнения экземпляра объекта. Такое упрощение достигается путем использования в пределах метода псевдопеременной Self (стандартный идентификатор). Физически Self представляет собой дополнительный неявный параметр, передаваемый в метод при вызове. Этот параметр и указывает экземпляр объекта, к которому данный метод применяется. Чтобы пояснить сказанное, перепишем метод SetActive, представив его в виде обычной процедуры:

Согласитесь, что метод SetActive выглядит лаконичнее процедуры TDelimitedReader_SetActive.

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

Если выполнить метод SetActive,

то обрабатываемый файл будет открыт. При этом неявный параметр Self будет содержать значение переменной Reader. Такой вызов реализуется обычными средствами процедурного программирования приблизительно так:

3.6. Свойства


3.6.1. Понятие свойства

Помимо полей и методов в объектах существуют свойства . При работе с объектом свойства выглядят как поля: они принимают значения и участвуют в выражениях. Но в отличие от полей свойства не занимают места в памяти, а операции их чтения и записи ассоциируются с обычными полями или методами. Это позволяет создавать необходимые сопутствующие эффекты при обращении к свойствам. Например, в объекте Reader присваивание свойству Active значения True вызовет открытие файла, а присваивание значения False — закрытие файла. Создание сопутствующего эффекта (открытие или закрытие файла) достигается тем, что за присваиванием свойству значения стоит вызов метода.

Объявление свойства выполняется с помощью зарезервированного слова property , например:

Ключевые слова read и write называются спецификаторами доступа. После слова read указывается поле или метод, к которому происходит обращение при чтении (получении) значения свойства, а после слова write — поле или метод, к которому происходит обращение при записи (установке) значения свойства. Например, чтение свойства Active означает чтение поля FActive, а установка свойства — вызов метода SetActive. Чтобы имена свойств не совпадали с именами полей, последние принято писать с буквы F (от англ. field). Мы в дальнейшем также будем пользоваться этим соглашением. Начнем с того, что переименуем поля класса TDelimitedReader: поле FileVar переименуем в FFile, Items — в FItems, а поле Delimiter — в FDelimiter.

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

Если один из спецификаторов доступа опущен, то значение свойства можно либо только читать (задан спецификатор read ), либо только записывать (задан спецификатор write ). В следующем примере объявлено свойство, значение которого можно только читать.

Здесь свойство ItemCount показывает количество элементов в массиве FItems. Поскольку оно определяется в результате чтения и разбора очередной строки файла, пользователю объекта разрешено лишь узнавать количество элементов.

В отличие от полей свойства не имеют адреса в памяти, поэтому к ним запрещено применять операцию @. Как следствие, их нельзя передавать в var — и out -параметрах процедур и функций.

Технология объектно-ориентированного программирования в среде Delphi предписывает избегать прямого обращения к полям, создавая вместо этого соответствующие свойства. Это упорядочивает работу с объектами, изолируя их данные от непосредственной модификации. В будущем внутренняя структура класса, которая иногда является достаточно сложной, может быть изменена с целью повышения эффективности работы программы. При этом потребуется переработать только методы чтения и записи значений свойств; внешний интерфейс класса не изменится.

3.6.2. Методы получения и установки значений свойств

Методы получения (чтения) и установки (записи) значений свойств подчиняются определенным правилам. Метод чтения свойства — это всегда функция, возвращающая значение того же типа, что и тип свойства. Метод записи свойства — это обязательно процедура, принимающая параметр того же типа, что и тип свойства. В остальных отношениях это обычные методы объекта. Примерами методов чтения и записи свойств являются методы GetItemCount и SetActive в классе TDelimitedReader:

Использование методов для получения и установки свойств позволяет проверить корректность значения свойства, сделать дополнительные вычисления, установить значения зависимых полей и т.д. Например, в методе SetActive вполне целесообразно осуществить проверку состояния файла (открыт или закрыт), чтобы избежать его повторного открытия или закрытия:

Наличие свойства Active позволяет нам отказаться от использования методов Open и Close, традиционных при работе с файлами. Согласитесь, что открывать и закрывать файл с помощью свойства Active гораздо удобнее и естественнее. Одновременно с этим свойство Active можно использовать и для проверки состояния файла (открыт или нет). Таким образом, для осуществления трех действий требуется всего лишь одно свойство! Это делает использование Ваших классов другими программистами более простым, поскольку им легче запомнить одно понятие Active, чем, например, три метода: Open, Close и IsOpen.

Значение свойства может не храниться, а вычисляться при каждом обращении к свойству. Примером является свойство ItemCount, значение которого вычисляется как Length(FItems).

3.6.3. Свойства-массивы

Кроме обычных свойств в объектах существуют свойства-массивы (array properties). Свойство-массив — это индексированное множество значений. Например, в классе TDelimitedReader множество элементов, выделенных из считанной строки, удобно представить в виде свойства-массива:

Элементы массива Items можно только читать, поскольку класс TDelimitedReader предназначен только для чтения данных из файла.

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

Основная выгода от применения свойства-массива — возможность выполнения итераций с помощью цикла for , например:

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

Свойства-массивы имеют два важных отличия от обычных массивов:

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


операции над свойством-массивом в целом запрещены; разрешены операции только с его элементами.

3.6.4. Свойство-массив как основное свойство объекта

Свойство-массив можно сделать основным свойством объектов данного класса. Для этого в описание свойства добавляется слово default :

Такое объявление свойства Items позволяет рассматривать сам объект класса TDelimitedReader как массив и опускать имя свойства-массива при обращении к нему из программы, например:

Следует помнить, что только свойства-массивы могут быть основными свойствами объектов; для обычных свойств это недопустимо.

3.6.5. Методы, обслуживающие несколько свойств

Один и тот же метод может использоваться для получения (установки) значений нескольких свойств одного типа. В этом случае каждому свойству назначается целочисленный индекс, который передается в метод чтения (записи) первым параметром.

В следующем примере уже известный Вам метод GetItem обслуживает три свойства: FirstName, LastName и Phone:

Обращения к свойствам FirstName, LastName и Phone заменяются компилятором на вызовы одного и того же метода GetItem, но с разными значениями параметра Index:

Обратите внимание, что метод GetItem обслуживает как свойство-массив Items, так и свойства FirstName, LastName и Phone. Удобно, не правда ли!

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

3.7. Наследование


3.7.1. Понятие наследования

Классы инкапсулируют (т.е. включают в себя) поля, методы и свойства; это их первая черта. Следующая не менее важная черта классов — способность наследовать поля, методы и свойства других классов. Чтобы пояснить сущность наследования обратимся к примеру с читателем текстовых файлов в формате «delimited text».

Класс TDelimitedReader описывает объекты для чтения из текстового файла элементов, разделенных некоторым символом. Он не пригоден для чтения элементов, хранящихся в другом формате, например в формате с фиксированным количеством символов для каждого элемента. Для этого необходим другой класс:

Поля, свойства и методы класса TFixedReader практически полностью аналогичны тем, что определены в классе TDelimitedReader. Отличие состоит в отсутствии свойства Delimiter, наличии поля FItemWidths (для хранения размеров элементов), другой реализации метода ParseLine и немного отличающемся конструкторе. Если в будущем появится класс для чтения элементов из файла еще одного формата (например, зашифрованного текста), то придется снова определять общие для всех классов поля, методы и свойства. Чтобы избавиться от дублирования общих атрибутов (полей, свойств и методов) при определении новых классов, воспользуемся механизмом наследования. Прежде всего, выделим в отдельный класс TTextReader общие атрибуты всех классов, предназначенных для чтения элементов из текстовых файлов. Реализация методов TTextReader, кроме метода ParseLine, полностью идентична реализации TDelimitedReader, приведенной в предыдущем разделе.

При реализации класса TTextReader ничего не известно о том, как хранятся элементы в считываемых строках, поэтому метод ParseLine ничего не делает. Очевидно, что создавать объекты класса TTextReader не имеет смысла. Для чего тогда нужен класс TTextReader? Ответ: чтобы на его основе определить ( породить ) два других класса — TDelimitedReader и TFixedReader, предназначенных для чтения данных в конкретных форматах:

Классы TDelimitedReader и TFixedReader определены как наследники TTextReader (об этом говорит имя в скобках после слова class ). Они автоматически включают в себя все описания, сделанные в классе TTextReader и добавляют к ним некоторые новые. В результате формируется дерево классов , показанное на рисунке 3.1 (оно всегда рисуется перевернутым).

Рисунок 3.1. Дерево классов

Класс, который наследует атрибуты другого класса, называется порожденным классом или потомком . Соответственно класс, от которого происходит наследование, выступает в роли базового, или предка . В нашем примере класс TDelimitedReader является прямым потомком класса TTextReader. Если от TDelimitedReader породить новый класс, то он тоже будет потомком класса TTextReader, но уже не прямым.

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

3.7.2. Прародитель всех классов

В языке Delphi существует предопределенный класс TObject, который служит неявным предком тех классов, для которых предок не указан. Это означает, что объявление

Класс TObject выступает корнем любой иерархии классов. Он содержит ряд методов, которые по наследству передаются всем остальным классам. Среди них конструктор Create, деструктор Destroy, метод Free и некоторые другие методы.

Таким образом, полное дерево классов для чтения элементов из текстового файла в различных форматах выглядит так, как показано на рисунке 3.2.

Рисунок 3.2. Полное дерево классов

Поскольку класс TObject является предком для всех других классов (в том числе и для ваших собственных), то не лишним будет кратко ознакомиться с его методами:

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

Краткое описание методов в классе TObject:

  • Create — стандартный конструктор.
  • Free — уничтожает объект: вызывает стандартный деструктор Destroy, если значение псевдопеременной Self не равно nil .
  • InitInstance (Instance: Pointer): TObject — при создании объекта инициализирует нулями выделенную память. На практике нет необходимости вызывать этот метод явно.
  • CleanupInstance — освобождает память, занимаемую полями с типом string, Variant, динамический массив и интерфейс. На практике нет необходимости вызывать этот метод явно.
  • ClassType : TClass — возвращает описатель класса (метакласс).
  • ClassName : ShortString — возвращает имя класса.
  • ClassNameIs (const Name: string): Boolean — проверяет, является ли заданная строка именем класса.
  • ClassParent : TClass — возвращает описатель базового класса.
  • ClassInfo : Pointer — возвращает указатель на соответствующую классу таблицу RTTI (от англ. Runtime Type Information). Таблица RTTI используется для проверки типов данных на этапе выполнения программы.
  • InstanceSize : Longint — возвращает количество байт, необходимых для хранения в памяти одного объекта соответствующего класса. Заметим, что значение, возвращаемое этим методом и значение, возвращаемое функцией SizeOf при передаче ей в качестве аргумента объектной переменной — это разные значения. Функция SizeOf всегда возвращает значение 4 (SizeOf(Pointer)), поскольку объектная переменная — это ни что иное, как ссылка на данные объекта в памяти. Значение InstanceSize — это размер этих данных, а не размер объектной переменной.
  • InheritsFrom (AClass: TClass): Boolean — проверяет, является ли класс AClass базовым классом.
  • MethodAddress (const Name: ShortString): Pointer — возвращает адрес published-метода, имя которого задается параметром Name.
  • MethodName (Address: Pointer): ShortString — возвращает имя published-метода по заданному адресу.
  • FieldAddress (const Name: ShortString): Pointer — возвращает адрес published-поля, имя которого задается параметром Name.
  • GetInterface (const IID: TGUID; out Obj): Boolean — возвращает ссылку на интерфейс через параметр Obj; идентификатор интерфейса задается параметром IID. (Интерфейсы рассмотрены в главе 6)
  • GetInterfaceEntry (const IID: TGUID): PInterfaceEntry — возвращает информацию об интерфейсе, который реализуется классом. Идентификатор интерфейса задается параметром IID.
  • GetInterfaceTable : PInterfaceTable — возвращает указатель на таблицу с информацией обо всех интерфейсах, реализуемых классом.
  • AfterConstruction — автоматически вызывается после создания объекта. Метод не предназначен для явного вызова из программы. Используется для того, чтобы выполнить определенные действия уже после создания объекта (для этого его необходимо переопределить в производных классах).
  • BeforeDestruction — автоматически вызывается перед уничтожением объекта. Метод не предназначен для явного вызова из программы. Используется для того, чтобы выполнить определенные действия непосредственно перед уничтожением объекта (для этого его необходимо переопределить в производных классах).
  • Dispatch (var Message) — служит для вызова методов, объявленных с ключевым словом message .
  • DefaultHandler (var Message) — вызывается методом Dispatch в том случае, если метод, соответствующий сообщению Message, не был найден.
  • NewInstance : TObject — вызывается при создании объекта для выделения динамической памяти, чтобы разместить в ней данные объекта. Метод вызывается автоматически, поэтому нет необходимости вызывать его явно.
  • FreeInstance — вызывается при уничтожении объекта для освобождения занятой объектом динамической памяти. Метод вызывается автоматически, поэтому нет необходимости вызывать его явно.
  • Destroy — стандартный деструктор.

3.7.3. Перекрытие атрибутов в наследниках

В механизме наследования можно условно выделить три основных момента:

  • наследование полей;
  • наследование свойств;
  • наследование методов.

Любой порожденный класс наследует от родительского все поля данных, поэтому классы TDelimitedReader и TFixedReader автоматически содержат поля FFile, FActive и FItems, объявленные в классе TTextReader. Доступ к полям предка осуществляется по имени, как если бы они были определены в потомке. В потомках можно определять новые поля, но их имена должны отличаться от имен полей предка.

Наследование свойств и методов имеет свои особенности.

Свойство базового класса можно перекрыть (от англ. override) в производном классе, например чтобы добавить ему новый атрибут доступа или связать с другим полем или методом.

Метод базового класса тоже можно перекрыть в производном классе, например чтобы изменить логику его работы. Обратимся к классам TDelimitedReader и TFixedReader. В них методы PutItem, GetItem, SetActive и GetEndOfFile унаследованы от TTextReader, поскольку логика их работы не зависит от того, в каком формате хранятся данные в файле. А вот метод ParseLine перекрыт, так как способ разбора строк зависит от формата данных:

В классах TDelimitedReader и TFixedReader перекрыт еще и конструктор Create. Это необходимо для инициализации специфических полей этих классов (поля FDelimiter в классе TDelimitedReader и поля FItemWidths в классе TFixedReader):

Как видно из примера, в наследнике можно вызвать перекрытый метод предка, указав перед именем метода зарезервированное слово inherited . Когда метод предка полностью совпадает с методом потомка по формату заголовка, то можно использовать более короткую запись. Воспользуемся ей и перепишем деструктор в классе TTextReader правильно:

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

3.7.4. Совместимость объектов различных классов

Для классов, связанных отношением наследования, вводится новое правило совместимости типов. Вместо объекта базового класса можно подставить объект любого производного класса. Обратное неверно. Например, переменной типа TTextReader можно присвоить значение переменной типа TDelimitedReader:

Объектная переменная Reader формально имеет тип TTextReader, а фактически связана с экземпляром класса TDelimitedReader.

Правило совместимости классов чаще всего применяется при передаче объектов в параметрах процедур и функций. Например, если процедура работает с объектом класса TTextReader, то вместо него можно передать объект класса TDelimitedReader или TFixedReader.

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

3.7.5. Контроль и преобразование типов

Поскольку реальный экземпляр объекта может оказаться наследником класса, указанного при описании объектной переменной или параметра, бывает необходимо проверить, к какому классу принадлежит объект на самом деле. Чтобы программист мог выполнять такого рода проверки, каждый объект хранит информацию о своем классе. В языке Delphi существуют операторы is и as , с помощью которых выполняется соответственно проверка на тип (type checking) и преобразование к типу (type casting).

Например, чтобы выяснить, принадлежит ли некоторый объект Obj к классу TTextReader или его наследнику, следует использовать оператор is :

Для преобразования объекта к нужному типу используется оператор as , например

Стоит отметить, что для объектов применим и обычный способ приведения типа:

Вариант с оператором as лучше, поскольку безопасен. Он генерирует ошибку (точнее исключительную ситуацию; об исключительных ситуациях мы расскажем в главе 4) при выполнении программы (run-time error), если реальный экземпляр объекта Obj не совместим с классом TTextReader. Забегая вперед, скажем, что ошибку приведения типа можно обработать и таким образом избежать досрочного завершения программы.

3.8. Виртуальные методы


3.8.1. Понятие виртуального метода

Все методы, которые до сих пор рассматривались, имеют одну общую черту — все они статические . При обращении к статическому методу компилятор точно знает класс, которому данный метод принадлежит. Поэтому, например, обращение к статическому методу ParseLine в методе NextLine (принадлежащем классу TTextReader) компилируется в вызов TTextReader.ParseLine:

В результате метод NextLine работает неправильно в наследниках класса TTextReader, так как внутри него вызов перекрытого метода ParseLine не происходит. Конечно, в классах TDelimitedReader и TFixedReader можно продублировать все методы и свойства, которые прямо или косвенно вызывают ParseLine, но при этом теряются преимущества наследования, и мы возвращаемся к тому, что необходимо описать два класса, в которых большая часть кода идентична. ООП предлагает изящное решение этой проблемы — метод ParseLine всего-навсего объявляется виртуальным :

Объявление виртуального метода в базовом классе выполняется с помощью ключевого слова virtual , а его перекрытие в производных классах — с помощью ключевого слова override . Перекрытый метод должен иметь точно такой же формат (список параметров, а для функций еще и тип возвращаемого значения), что и перекрываемый:

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

Работа виртуальных методов основана на механизме позднего связывания (late binding). В отличие от раннего связывания (early binding), характерного для статических методов, позднее связывание основано на вычислении адреса вызываемого метода при выполнении программы. Адрес метода вычисляется по хранящемуся в каждом объекте описателю класса.

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

3.8.2. Механизм вызова виртуальных методов

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

Все процедурные переменные с адресами виртуальных методов пронумерованы и хранятся в таблице, называемой таблицей виртуальных методов (VMT — от англ. Virtual Method Table). Такая таблица создается одна для каждого класса объектов, и все объекты этого класса хранят на нее ссылку.

Структуру объекта в оперативной памяти поясняет рисунок 3.3:

Рисунок 3.3. Структура объекта TTextReader в оперативной памяти

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

  1. Через объектную переменную выполняется обращение к занятому объектом блоку памяти;
  2. Далее из этого блока извлекается адрес таблицы виртуальных методов (он записан в четырех первых байтах);
  3. На основании порядкового номера виртуального метода извлекается адрес соответствующей подпрограммы;
  4. Вызывается код, находящийся по этому адресу.

Покажем, как можно реализовать косвенный вызов виртуального метода ParseLine (он имеет нулевой номер в таблице виртуальных методов) обычными средствами процедурного программирования:

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

3.8.3. Абстрактные виртуальные методы

При построении иерархии классов часто возникает ситуация, когда работа виртуального метода в базовом классе не известна и наполняется содержанием только в наследниках. Так случилось, например, с методом ParseLine, тело которого в классе TTextReader объявлено пустым. Конечно, тело метода всегда можно сделать пустым или почти пустым (так мы и поступили), но лучше воспользоваться директивой abstract :

Директива abstract записывается после слова virtual и исключает необходимость написания кода виртуального метода для данного класса. Такой метод называется абстрактным , т.е. подразумевает логическое действие, а не конкретный способ его реализации. Абстрактные виртуальные методы часто используются при создании классов-полуфабрикатов. Свою реализацию такие методы получают в законченных наследниках.

3.8.4. Динамические методы

Разновидностью виртуальных методов являются так называемые динамические методы . При их объявлении вместо ключевого слова virtual записывается ключевое слово dynamic , например:

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

По смыслу динамические и виртуальные методы идентичны. Различие состоит только в механизме их вызова. Методы, объявленные с директивой virtual , вызываются максимально быстро, но платой за это является большой размер системных таблиц, с помощью которых определяются их адреса. Размер этих таблиц начинает сказываться с увеличением числа классов в иерархии. Методы, объявленные с директивой dynamic вызываются несколько дольше, но при этом таблицы с адресами методов имеют более компактный вид, что способствует экономии памяти. Таким образом, программисту предоставляются два способа оптимизации объектов: по скорости работы ( virtual ) или по объему памяти ( dynamic ).

3.8.5. Методы обработки сообщений

Специализированной формой динамических методов являются методы обработки сообщений . Они объявляются с помощью ключевого слова message , за которым следует целочисленная константа — номер сообщения . Следующий пример взят из исходных текстов библиотеки VCL:

Метод обработки сообщений имеет формат процедуры и содержит единственный var -параметр. При перекрытии такого метода название метода и имя параметра могут быть любыми, важно лишь, чтобы неизменным остался номер сообщения, используемый для вызова метода. Вызов метода выполняется не по имени, как обычно, а с помощью обращения к специальному методу Dispatch, который имеется в каждом классе (метод Dispatch определен в классе TObject).

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

3.9. Классы в программных модулях

Классы очень удобно собирать в модули. При этом их описание помещается в секцию interface , а код методов — в секцию implementation . Создавая модули классов, нужно придерживаться следующих правил:

  • все классы, предназначенные для использования за пределами модуля, следует определять в секции interface ;
  • описание классов, предназначенных для употребления внутри модуля, следует располагать в секции implementation ;
  • если модуль B использует модуль A, то в модуле B можно определять классы, порожденные от классов модуля A.

Соберем рассмотренные ранее классы TTextReader, TDelimitedReader и TFixedReader в отдельный модуль ReadersUnit:

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

3.10. Разграничение доступа к атрибутам объектов

Программист может разграничить доступ к атрибутам своих объектов для других программистов (и себя самого) с помощью специальных ключевых слов: private , protected , public , published (последнее не используется в модуле ReadersUnit).

  • Private . Все, что объявлено в секции private недоступно за пределами модуля. Секция private позволяет скрыть те поля и методы, которые относятся к так называемым особеностям реализации. Например, в этой секции класса TTextReader объявлены поля FFile, FActive и FItems, а также методы PutItem, SetActive, GetItemCount и GetEndOfFile.
  • Public . Поля, методы и свойства, объявленные в секции public не имеют никаких ограничений на использование, т.е. всегда видны за пределами модуля. Все, что помещается в секцию public , служит для манипуляций с объектами и составляет программный интерфейс класса. Например, в классе TTextReader в эту секцию помещены конструктор Create, метод NextLine, свойства Active, Items, ItemCount.
  • Protected . Поля, методы и свойства, объявленные в секции protected , видны за пределами модуля только потомкам данного класса; остальным частям программы они не видны. Так же как и private , директива protected позволяет скрыть особенности реализации класса, но в отличие от нее разрешает другим программистам порождать новые классы и обращаться к полям, методам и свойствам, которые составляют так называемый интерфейс разработчика. В эту секцию обычно помещаются виртуальные методы. Примером такого метода является ParseLine.
  • Published . Устанавливает правила видимости те же, что и директива public . Особенность состоит в том, что для элементов, помещенных в секцию published , компилятор генерирует информацию о типах этих элементов. Эта информация доступна во время выполнения программы, что позволяет превращать объекты в компоненты визуальной среды разработки. Секцию published разрешено использовать только тогда, когда для самого класса или его предка включена директива компилятора $TYPEINFO .

Перечисленные секции могут чередоваться в объявлении класса в произвольном порядке, однако в пределах секции сначала следует описание полей, а потом методов и свойств. Если в определении класса нет ключевых слов private , protected , public и published , то для обычных классов всем полям, методам и свойствам приписывается атрибут видимости public , а для тех классов, которые порождены от классов библиотеки VCL, — атрибут видимости published .

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

3.11. Указатели на методы объектов

В языке Delphi существуют процедурные типы данных для методов объектов. Внешне объявление процедурного типа для метода отличается от обычного словосочетанием of object , записанным после прототипа процедуры или функции:

Переменная такого типа называется указателем на метод (method pointer). Она занимает в памяти 8 байт и хранит одновременно ссылку на объект и адрес его метода.

Методы объектов, объявленные по приведенному выше шаблону, становятся совместимы по типу со свойством OnReadLine.

Если установить значение свойства OnReadLine:

и переписать метод NextLine,

то объект Form1 через метод HandleLine получит уведомление об очередной считанной строке. Обратите внимание, что вызов метода через указатель происходит лишь в том случае, если указатель не равен nil . Эта проверка выполняется с помощью стандартной функции Assigned, которая возвращает True, если ее аргумент является связанным указателем.

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

3.12. Метаклассы


3.12.1. Ссылки на классы

Язык Delphi позволяет рассматривать классы объектов как своего рода объекты, которыми можно манипулировать в программе. Такая возможность рождает новое понятие — класс класса ; его принято обозначать термином метакласс .

Для поддержки метаклассов введен специальный тип данных — ссылка на класс (class reference). Он описывается с помощью словосочетания class of , например:

Переменная типа TTextReaderClass объявляется в программе обычным образом:

Значениями переменной ClassRef могут быть класс TTextReader и все порожденные от него классы. Допустимы следующие операторы:

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

Переменная типа TClass может ссылаться на любой класс.

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

Физический смысл и взаимосвязь таких понятий, как переменная-объект, экземпляр объекта в памяти, переменная-класс и экземпляр класса в памяти поясняет рисунок 3.4.

Рисунок 3.4. Переменная-объект, экземпляр объекта в памяти, переменная-класс и экземпляр класса в памяти

3.12.2. Методы классов

Метаклассы привели к возникновению нового типа методов — методов класса. Метод класса оперирует не экземпляром объекта, а непосредственно классом. Он объявляется как обычный метод, но перед словом procedure или function записывается зарезервированное слово class , например:

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

Метод ClassName объявлен в классе TObject и возвращает имя класса, к которому применяется. Очевидно, что надуманный метод GetClassName просто дублирует эту функциональность для класса TTextReader и всех его наследников.

Методы класса применимы и к классам, и к объектам. В обоих случаях в параметре Self передается ссылка на класс объекта. Пример:

Методы классов могут быть виртуальными. Например, в классе TObject определен виртуальный метод класса NewInstance. Он служит для распределения памяти под объект и автоматически вызывается конструктором. Его можно перекрыть в своем классе, чтобы обеспечить нестандартный способ выделения памяти для экземпляров. Метод NewInstance должен перекрываться вместе с другим методом FreeInstance, который автоматически вызывается из деструктора и служит для освобождения памяти. Добавим, что размер памяти, требуемый для экземпляра, можно узнать вызовом предопределенного метода класса InstanceSize.

3.12.3. Виртуальные конструкторы

Особая прелесть ссылок на классы проявляется в сочетании с виртуальными конструкторами. Виртуальный конструктор объявляется с ключевым словом virtual . Вызов виртуального конструктора происходит по фактическому значению ссылки на класс, а не по ее формальному типу. Это позволяет создавать объекты, классы которых неизвестны на этапе компиляции. Механизм виртуальных конструкторов применяется в среде Delphi при восстановлении компонентов формы из файла. Восстановление компонента происходит следующим образом. Из файла считывается имя класса. По этому имени отыскивается ссылка на класс (метакласс). У метакласса вызывается виртуальный конструктор, который создает объект нужного класса.

На этом закончим изучение теории объектно-ориентированного программирования и в качестве практики рассмотрим несколько широко используемых инструментальных классов среды Delphi. Разберитесь с их назначением и работой. Это поможет глубже понять ООП и пригодится на будущее.

3.13. Классы общего назначения

Как показывает практика, в большинстве задач приходится использовать однотипные структуры данных: списки, массивы, множества и т.д. От задачи к задаче изменяются только их элементы, а методы работы сохраняются. Например, для любого списка нужны процедуры вставки и удаления элементов. В связи с этим возникает естественное желание решить задачу «в общем виде», т.е. создать универсальные средства для управления основными структурами данных. Эта идея не нова. Она давно пришла в голову разработчикам инструментальных пакетов, которые быстро наплодили множество вспомогательных библиотек. Эти библиотеки содержали классы объектов для работы со списками, коллекциями (динамические массивы с переменным количеством элементов), словарями (коллекции, индексированные строками) и другими «абстрактными» структурами. Для среды Delphi тоже разработаны аналогичные классы объектов. Их большая часть сосредоточена в модуле Classes. Наиболее нужными для вас являются списки строк (TStrings, TStringList) и потоки (TSream, THandleSream, TFileStream, TMemoryStream и TBlobStream). Рассмотрим кратко их назначение и применение.

3.13.1. Классы для представления списка строк

Для работы со списками строк служат классы TStrings и TStringList. Они используются в библиотеке VCL повсеместно и имеют гораздо большую универсальность, чем та, что можно почерпнуть из их названия. Классы TStrings и TStringList служат для представления не просто списка строк, а списка элементов, каждый из которых представляет собой пару строка-объект. Если со строками не ассоциированы объекты, получается обычный список строк.

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

Свойства класса TStrings описаны ниже.

  • Count : Integer — число элементов в списке.
  • Strings [Index: Integer]: string — обеспечивает доступ к массиву строк по индексу. Первая строка имеет индекс, равный 0. Свойство Strings является основным свойством объекта.
  • Objects [Index: Integer]: TObject — обеспечивает доступ к массиву объектов. Свойства Strings и Objects позволяют использовать объект TStrings как хранилище строк и ассоциированных с ними объектов произвольных классов.
  • Text : string — позволяет интерпретировать список строк, как одну большую строку, в которой элементы разделены символами #13#10 (возврат каретки и перевод строки).

Наследники класса TStrings иногда используются для хранения строк вида Имя=Значение, в частности, строк INI-файлов (см. гл. 6). Для удобной работы с такими строками в классе TStrings дополнительно имеются следующие свойства.

  • Names [Index: Integer]: string — обеспечивает доступ к той части строки, в которой содержится имя.
  • Values [const Name: string]: string — обеспечивает доступ к той части строки, в которой содержится значение. Указывая вместо Name ту часть строки, которая находится слева от знака равенства, вы получаете ту часть, что находится справа.

Управление элементами списка осуществляется с помощью следующих методов:

  • Add (const S: string): Integer — добавляет новую строку S в список и возвращает ее позицию. Новая строка добавляется в конец списка.
  • AddObject (const S: string; AObject: TObject): Integer — добавляет в список строку S и ассоциированный с ней объект AObject. Возвращает индекс пары строка-объект.
  • AddStrings (Strings: TStrings) — добавляет группу строк в существующий список.
  • Append (const S: string) — делает то же, что и Add, но не возвращает значения.
  • Clear — удаляет из списка все элементы.
  • Delete (Index: Integer) — удаляет строку и ассоциированный с ней объект. Метод Delete, также как метод Clear не разрушают объектов, т.е. не вызывают у них деструктор. Об этом вы должны позаботиться сами.
  • Equals (Strings: TStrings): Boolean — Возвращает True, если список строк в точности равен тому, что передан в параметре Strings.
  • Exchange (Index1, Index2: Integer) — меняет два элемента местами.
  • GetText : PChar — возвращает все строки списка в виде одной большой нуль-терминированной строки.
  • IndexOf (const S: string): Integer — возвращает позицию строки S в списке. Если заданная строка в списке отсутствует, функция возвращает значение -1.
  • IndexOfName (const Name: string): Integer — возвращает позицию строки, которая имеет вид Имя=Значение и содержит в себе Имя, равное Name.
  • IndexOfObject (AObject: TObject): Integer — возвращает позицию объекта AObject в массиве Objects. Если заданный объект в списке отсутствует, функция возвращает значение -1.
  • Insert (Index: Integer; const S: string) — вставляет в список строку S в позицию Index.
  • InsertObject (Index: Integer; const S: string; AObject: TObject) — вставляет в список строку S и ассоциированный с ней объект AObject в позицию Index.
  • LoadFromFile (const FileName: string) — загружает строки списка из текстового файла.
  • LoadFromStream (Stream: TStream) — загружает строки списка из потока данных (см. ниже).
  • Move (CurIndex, NewIndex: Integer) — изменяет позицию элемента (пары строка-объект) в списке.
  • SaveToFile (const FileName: string) — сохраняет строки списка в текстовом файле.
  • SaveToStream (Stream: TStream) — сохраняет строки списка в потоке данных.
  • SetText (Text: PChar) — загружает строки списка из одной большой нуль-терминированной строки.

Класс TStringList добавляет к TStrings несколько дополнительных свойств и методов, а также два свойства-события для уведомления об изменениях в списке. Они описаны ниже.

Свойства:


  • Duplicates : TDuplicates — определяет, разрешено ли использовать дублированные строки в списке. Свойство может принимать следующие значения: dupIgnore (дубликаты игнорируются), dupAccept (дубликаты разрешены), dupError (дубликаты запрещены, попытка добавить в список дубликат вызывает ошибку).
  • Sorted : Boolean — если имеет значение True, то строки автоматически сортируются в алфавитном порядке.

Методы:


  • Find (const S: string; var Index: Integer): Boolean — выполняет поиск строки S в списке строк. Если строка найдена, Find помещает ее позицию в переменную, переданную в параметре Index, и возвращает True.
  • Sort — сортирует строки в алфавитном порядке.

События:


  • OnChange : TNotifyEvent — указывает на обработчик события, который выполнится при изменении содержимого списка. Событие OnChange генерируется после того, как были сделаны изменения.
  • OnChanging : TNotifyEvent — указывает на обработчик события, который выполнится при изменении содержимого списка. Событие OnChanging генерируется перед тем, как будут сделаны изменения.

Ниже приводится фрагмент программы, демонстрирующий создание списка строк и манипулирование его элементами:

3.13.2. Классы для представления потока данных

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

Класс Описание
TStream Абстрактный поток, от которого наследуются все остальные. Свойства и методы класса TStream образуют базовый интерфейс потоковых объектов.
THandleStream Поток, который хранит свои данные в файле. Для чтения-записи файла используется дескриптор (handle), поэтому поток называется дескрипторным. Дескриптор — это номер открытого файла в операционной системе. Его возвращают низкоуровневые функции создания и открытия файла.
TFileStream Поток, который хранит свои данные в файле. Отличается от ThandleStream тем, что сам открывает (создает) файл по имени, переданному в конструктор.
TMemoryStream Поток, который хранит свои данные в оперативной памяти. Моделирует работу с файлом. Используется для хранения промежуточных результатов, когда файловый поток не подходит из-за низкой скорости передачи данных.
TResourceStream Поток, обеспечивающий доступ к ресурсам в Windows-приложении.
TBlobStream Обеспечивает последовательный доступ к большим полям таблиц в базах данных.
Таблица 3.1. Классы потоков

Потоки широко применяются в библиотеке VCL и наверняка вам понадобятся. Поэтому ниже кратко перечислены их основные общие свойства и методы.

Общие свойства:


  • Position : Longint — текущая позиция чтения-записи.
  • Size : Longint — текущий размер потока в байтах.

Общие методы:


  • CopyFrom (Source: TStream; Count: Longint): Longint — копирует Count байт из потока Source в свой поток.
  • Read (var Buffer; Count: Longint): Longint — читает Count байт из потока в буфер Buffer, продвигает текущую позицию на Count байт вперед и возвращает число прочитанных байт. Если значение функции меньше значения Count, то в результате чтения был достигнут конец потока.
  • ReadBuffer (var Buffer; Count: Longint) — читает из потока Count байт в буфер Buffer и продвигает текущую позицию на Count байт вперед. Если выполняется попытка чтения за концом потока, то генерируется ошибка.
  • Seek (Offset: Longint; Origin: Word): Longint — продвигает текущую позицию в потоке на Offset байт относительно позиции, заданной параметром Origin. Параметр Origin может иметь одно из следующих значений: 0 — смещение задается относительно начала потока; 1 — смещение задается относительно текущей позиции в потоке; 2 — смещение задается относительно конца потока.
  • Write (const Buffer; Count: Longint): Longint — записывает в поток Count байт из буфера Buffer, продвигает текущую позицию на Count байт вперед и возвращает реально записанное количество байт. Если значение функции отличается от значения Count, то при записи была ошибка.
  • WriteBuffer (const Buffer; Count: Longint) — записывает в поток Count байт из буфера Buffer и продвигает текущую позицию на Count байт вперед. Если по какой-либо причине невозможно записать все байты буфера, то генерируется ошибка.

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

3.14. Итоги

Теперь для вас нет секретов в мире ООП. Вы на достаточно серьезном уровне познакомились с объектами и их свойствами; узнали, как объекты создаются, используются и уничтожаются. Если не все удалось запомнить сразу — не беда. Возвращайтесь к материалам главы по мере решения стоящих перед вами задач, и работа с объектами станет простой, естественной и даже приятной. Когда вы достигните понимания того, как работает один объект, то автоматически поймете, как работают все остальные. Теперь мы рассмотрим то, с чем вы встретитесь очень скоро — ошибки программирования.

Class — Ключевое слово Delphi

Класс — это тип данных, определяемый пользователем. То, что в Delphi имеется множество предопределенных классов, не противоречит этому определению -ведь разработчики Delphi тоже пользователи Object Pascal.

Класс должен быть объявлен до того, как будет объявлена хотя бы одна переменная этого класса. Т.е. класс не может объявляться внутри объявления переменной.

В любом вашем Делфи-приложении вы можете увидеть строки:

Это объявление класса TForml вашей формы и объявление переменной Forml -объекта этого класса.

В общем случае синтаксис объявления класса следующий:

Имя класса может быть любым допустимым идентификатором. Но принято идентификаторы большинства классов начинать с символа «Т». Имя класса — родителя может не указываться. Тогда предполагается, что данный класс является непосредственным наследником TObject — наиболее общего из предопределенных классов. Таким образом, эквивалентны следующие объявления:

В приведенном ранее объявлении класса формы TForml видно, что его родительским классом является класс TForm.

Класс наследует поля, методы, свойства, события от своих предков и может отменять какие-то из этих элементов класса или вводить новые. Доступ к объявляемым элементам класса определяется тем, в каком разделе они объявлены.

Раздел public (открытый) предназначен для объявлений, которые доступны для внешнего использования. Это открытый интерфейс класса. Раздел published (публикуемый) содержит открытые свойства, которые появляются в процессе проектирования на странице свойств Инспектора Объектов и которые, следовательно, пользователь может устанавливать в процессе проектирования. Раздел private (закрытый), содержит объявления полей, процедур и функций, используемых только внутри данного класса. Раздел protected (защищенный) содержит объявления, доступные только для потомков объявляемого класса. Как и в случае закрытых элементов, можно скрыть детали реализации защищенных элементов от конечного пользователя. Однако в отличие от закрытых, защищенные элементы остаются доступны для программистов, которые захотят производить от этого класса производные объекты, причем не требуется, чтобы производные объекты объявлялись в этом же модуле.

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

В приведенном ранее объявлении класса формы вы можете видеть строку

Это объявление объекта (поля) Buttonl типа (класса) TButton.

Имеется одно очень существенное отличие объявления поля от обычного объявления переменной: в объявлении поля не разрешается его инициализация каким-то значением. Автоматически проводится стандартная инициализация: порядковым типам в качестве начального значения задается 0, указателям — nil, строки задаются пустыми. При необходимости задания других начальных значений используются конструкторы, описанные далее в 4 части.

Объявления методов в простейшем случае также не отличаются от обычных объявлений процедур и функций.

В следующем уроке мы узнаем о свойствах классов.

Описание класса TObject

TObject является общим предком всех классов в Delphi, корнем иерархии Visual Component Library. Tobject представляет собой абстрактный класс, реализующий только наиболее общие механизмы, универсально разделяемые всеми объектами VCL.

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

Object объявляет конструктор и деструктор (специальные методы, которые создают и разрушают объекты)и реализует информацию о типе и методах обработки сообщений. Поскольку TObject является абстрактным классом, большинство его методов перекрывается в его потомках в рамках VCL. Обычно у вас будет доступ к порожденной реализации Create, конструктору и Free, безопасно вызывающему деструктор Destroy.

Таблица 3-1 отображает методы общецелевого назначения, реализуемые TObject. В общем случае из этих методов разработчикам приложений интересны только три: Create, Destroy и Free.

Таблица 3-1 Методы, реализуемые Tobject

«this» ключевое слово в delphi

Скажем, что у нас есть этот класс:

И я хочу вызвать func1 в коде func2 . Ну, я был программистом на Java, и в этом случае я бы использовал ключевое слово this . Имеет ли Pascal эквивалент для this ключевого слова? Если нет, как я могу достичь такого вызова?

Эквивалент Java this в Delphi является Self . Из документации:

В рамках реализации метода идентификатор Self ссылается на объект, в котором вызывается метод. Например, вот реализация метода TCollection Add в модуле Classes:

Метод Add вызывает метод Create в классе, на который ссылается поле FItemClass, которое всегда является потомком TCollectionItem. TCollectionItem.Create принимает единственный параметр типа TCollection, поэтому Add передает объект экземпляра TCollection, в котором вызывается метод Add. Это показано в следующем коде:

Самость полезна по целому ряду причин. Например, идентификатор участника, объявленный в типе класса, может быть обновлен в блоке одного из методов класса. В этом случае вы можете получить доступ к исходному идентификатору участника как Self.Identifier.

Обратите внимание, однако, что в примере кода в вопросе нет необходимости использовать Self . В этом коде вы можете вызвать func1 из func2 опуская Self .

Пример, приведенный в вышеприведенной отрывке, действительно обеспечивает правильную мотивацию для существования Self

Илон Маск рекомендует:  Zend машина
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL