Современные технологии программирования
Конспект лекций
назад | содержание | вперед

Лекция 5. Классы Delphi

Содержание

Введение *

Изучаемые вопросы: *

Изучаемые понятия: *

Тезисы *

Особенности ООП - модели Object Pascal *

Уровни представления программы *

Основные понятия ООП *

Объекты и их жизненный цикл *

Инкапсуляция. Свойства. *

Контрольные вопросы *

Источники дополнительных сведений *

 

Введение

  1. Изучаемые вопросы:
  2. Особенности объектно-ориентированного программирования (ООП) - модели Object Pascal

    Класс – тип данных, шаблон для создания объектов

    Объект – структурный элемент приложения

    Уровни представления программы: логический, языка программирования, физический

    Описание классов

    Объекты и их жизненный цикл

    Объект на физическом уровне

  3. Изучаемые понятия:
  4. Класс

    Объект

    Поле

    Метод

    Свойство

    Self – ссылка на текущий объект в методе

    RTTI класса

    Класс Tobject

    Конструктор

    Деструктор

    Указатель на объект

  5. Тезисы

Класс - тип, объект (экземпляр класса), переменная типа (указатель на объект, ссылка на объект), метод, поле, свойство. Self. Класс Tobject – предок по умолчанию. Отличие метода от подпрограммы. Опережающее объявление класса. RTTI класса. Память под объект класса. Объекты и их жизненный цикл. Объект - динамическая переменная. Переменная типа класс – указатель. Конструктор (Create). Конструктор – метод класса. Деструктор (Destroy). Метод Free. Директива inherited. Создание и уничтожение компонентов формы. Создание и уничтожение форм. Примеры.

 

Особенности ООП - модели Object Pascal

Уровни представления программы

Программу можно рассматривать на трёх уровнях:

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

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

На физическом уровне рассматривается организация объектов в памяти, организация и вызов методов.

Основные понятия ООП

Классом называется особый тип записи, который может иметь в своём составе поля, методы и свойства. Такой тип также будем называть объектным типом. Ниже приводится описание класса TFrac, помещённое в модуль UFrac. В модуле размещают описание одного или нескольких логически связанных классов.

//--------------------------------------------------------------

unit UFrac;

interface

Uses SysUtils,Dialogs;

type

TFrac = class

FN,FD: Real;

constructor Create(Nr: Real = 1;Dr: Real = 1);

function Add(b: TFrac): TFrac;

function GetFrac: String;

end;

implementation

//--------------------------------------------------------------

constructor TFrac.Create(Nr,Dr: Real);

begin

FN:= Int(Nr);

FD:= Int(Dr);

end;

//--------------------------------------------------------------

function TFrac.add;

var c,e: Real;{c+d}

begin

c:= FN * b.FD + FD * b.FN;

e:= FD * b.FD;

result:= TFrac.Create(c,e);

end;

//--------------------------------------------------------------

function TFrac.GetFrac;

begin

Result:= FloatToStr(FN) + '/' + FloatToStr(FD);

end;

end.

//--------------------------------------------------------------

В примере описан класс TFrac, имеющий поля FN (числитель), FD (знаменатель) методы Add (сложить), GetFrac (представить в виде строки), Create (конструктор).

Где допустимо описание классов? Классы могут быть описаны:

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

//--------------------------------------------------------------

Type

TFClass = class;

TSClass = class(TObject)

First: TFClass;

End;

TFClass = class

end;

//--------------------------------------------------------------

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

//--------------------------------------------------------------

program PExample;

uses SysUtils, UFrac;

Var

FracObj: TFrac;

//--------------------------------------------------------------

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

На уровне физической памяти объект состоит из полей, которые описаны в его объектовом типе и содержат данные, уникальные для каждого объекта класса. Кроме того, в состав объекта по умолчанию добавляется поле, для хранения указателя на структуру RTTI класса (указатель на класс). Ниже на рисунке показано распределение памяти под FracObj. В RTTI каждого класса имеется указатель на RTTI родительского класса.


Рисунок 1. Организация объектов в памяти.

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

В отличие от полей, методы являются общими для всех объектов класса. Методы – это процедуры или функции, описанные внутри класса и предназначенные для операций над полями. В описании класса как типа данных методы представлены своими заголовками. Полное описание методов осуществляется в разделе подпрограмм. Если класс описан в разделе типов раздела интерфейса (interface) модуля, то полное описание методов приводится в разделе описания подпрограмм раздела реализации (implementation) модуля. В процессе выполнения приложения класс представлен специальной структурой данных RTTI (run time type information) – информация о типе времени выполнения. В RTTI содержатся данные доступные приложению во время выполнения. В частности, там хранится имя класса, размер объекта, адреса памяти, по которым размещены в памяти коды методов.

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

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

Объекты и их жизненный цикл

Объекты в Delphi могут быть только динамическими, т. е. создаются в свободной памяти (куче). Описание в программе переменной типа класс не приводит к автоматическому распределению памяти под объект класса при запуске приложения. Переменная типа класс, например FracObj: TFrac, на самом деле, является указателем. Переменная FracObj используется для хранения указателя на объект её класса. За создание экземпляров класса и освобождение занимаемой ими памяти отвечает программист.

Использование объекта как указателя имеет одну особенность. При использовании указателя для доступа к данным, на которые он указывает, необходимо применять операцию разыменования “^”. Но это правило не распространяется на объекты. То есть вместо

FracObj^.FN:= 5;

Следует писать

FracObj.FN:= 5;

Новый экземпляр объекта создаётся конструктором, а уничтожается специальным методом – деструктором:

FracObj:= TFrac.Create;{Создание объекта}

FracObj.Destroy; {Удаление объекта}

Конструктор является классовым методом, поэтому может быть вызван до создания объекта. Для его вызова используется идентификатор типа. В Object Pascal у класса может быть несколько конструкторов. Принято называть конструктор Create. Для уничтожения объектов (освобождения памяти из под объектов) используют деструкторы. Типичное название деструктора – Destroy. В документации рекомендуется использовать для уничтожения экземпляра объекта метод Free, который первоначально проверяет указатель (не равен ли он nil) и затем уж вызывает Destroy.

Поскольку каждый класс по умолчанию является потомком класса Tobject, класс TMyClass унаследует от него методы: Create, Destroy, Free, описанные в классе Tobject.

Tobject = class

Constructor Create;

Destructor Destroy; virtual;

Procedure Free;

end;

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

Constructor TMyClass.Create;

Begin

inherited Create;

End;

Директива inherited позволяет вызвать одноименный метод прямого предка.

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

FracObj:= TFrac.Create;{Создание объекта и инициализация полей значениями по умолчанию}

FracObj.Create(7,9);//Инициализация полей значениями 7 и 9.

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

Destructor TMyClass.Destroy;

Begin

inherited Destroy;

End;

 

Пример 1. Работ с объектами класса TFrac в режиме консольного приложения.

program PFrac;

{$APPTYPE CONSOLE}

uses

SysUtils,

UFrac in 'UFrac.pas';

var a,b,c: TFrac;

begin

a:= TFrac.Create(-1,2);

writeln(a.getFrac);// -1/2

b:= TFrac.Create(3,4);// 3/4

writeln(b.getFrac);

c:= a.Add(b);

writeln(c.getFrac);// 2/8

c.Free;

a.FN:= -3;

a.FD:= 4;

c:= a.Add(b);

writeln(c.getFrac);// 0/16

readln;

end.

В Пример 1. Работ с объектами класса TFrac в режиме консольного приложения. выполняются следующие действия:

При работе с визуальными компонентами конструкторы и деструкторы вызываются редко. Поскольку за создание и уничтожение компонента отвечает компонент-владелец (owner). Для большинства компонентов это форма, на которую они помещены. За создание и уничтожение форм отвечает объект Application. Создавать объекты явно приходится только в том случае, если они создаются во время работы приложения (динамически).

Пример 2. Приложение из одной формы.

//--------------------------Модуль формы------------------------

unit UExample1;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, StdCtrls;

type

TForm1 = class(TForm)

Button1: TButton;

procedure Button1Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);

begin

//Close;

//Form1.Close;

Application.Terminate;

end;

end.

//---------------Головная программа (source)--------------------

program PExample1;

uses

Forms,

UExample1 in 'UExample1.pas' {Form1};

{$R *.res}

begin

Application.Initialize;

Application.CreateForm(TForm1, Form1);

Application.Run;

end.

Каждый компонент, помещаемый на форму, становится полем класса формы. Каждый обработчик события для компонента становится методом формы. За создание и уничтожение формы отвечает объект Application. При закрытии формы в приложении, состоящем из одной формы, закрывается всё приложение.

Головная программа содержит три оператора:

  1. первый – вызов метода Initialize объектом Application (этот объект создаётся автоматически при запуске приложения и управляет работой приложения), который выполняет действия по инициализации приложения;
  2. второй – вызов метода CreateForm(TForm1, Form1) объектом Application, который создаёт форму (объект Form1 класса TForm1), регистрирует её в объекте-владельце Application и возвращает указатель на созданный объект формы через переменную Form1;
  3. третий – вызов метода Run объектом Application, который запускает бесконечный цикл обработки событий приложения.

    1. Инкапсуляция. Свойства.

ОПП основано на трёх принципах:

  1. инкапсуляция,
  2. наследование,
  3. полиморфизм.

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

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

//--------------------------------------------------------------

Type

//--------------------------------------------------------------

TFrac = class

FN,FD: Real;

function GetFrac: String;

procedure SetFrac(newn: String);

property Frac: String read GetFrac write SetFrac;

end;

//--------------------------------------------------------------

function TFrac.GetFrac;

var s: String;

begin

s:= FloatToStr(FN) + '/' + FloatToStr(FD);

Result:= s

end;

//--------------------------------------------------------------

procedure TFrac.SetFrac;

var

n: Integer;

z: String;

begin

try

n:= System.Pos('/',newn);

FN:= StrToInt(System.Copy(newn,1,(n - 1)));

Delete(newn,1,n);

FD:= StrToInt(System.Copy(newn,1,Length(newn)));

except

on EConvertError do ShowMessage('Ошибка ввода')

end;

end;

//--------------------------------------------------------------

В данном примере свойство Frac позволяет нам осуществлять запись и чтение простой дроби в формате строки. Причём с помощью свойства мы обрабатываем сразу оба поля объекта. Доступ к значению свойства Frac осуществляется через вызовы методов GetFrac, SetFrac. Однако для обращения к этим методам в явном виде нет необходимости достаточно написать:

//--------------------------------------------------------------

Var

Obj: TFrac;

S: String;

Obj.TFrac:= ‘1/2’;

S:= Obj.Frac;

//--------------------------------------------------------------

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

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

//--------------------------------------------------------------

Type

TPropClass = class

FValue: TSomeType;

Procedure DoSomeThing;

function Correct(AValue: Integer): Boolean;

Procedure SetValue(NewValue: Integer);

Property AValue: Integer read FValue write SetValue;

End;

//--------------------------------------------------------------

procedure TPropClass.SetValue(NewValue: Integer);

begin

if (NewValue <> FValue) and Correct(NewValue)

then AValue:= NewValue;

DoSomeThing;

end;

//--------------------------------------------------------------

В этом примере чтение значения свойства AValue просто означает чтение поля FValue. Зато при присвоении ему значения внутри SetValue вызывается сразу два метода. Тип свойства и тип поля, на которое опирается свойство, могут быть разными.

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

//--------------------------------------------------------------

Type

TPropClass = class

FValue: Integer;

Property AValue: Integer read FValue;

End;

//--------------------------------------------------------------

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

//--------------------------------------------------------------

Type

TPropClass = class

Property AValue: Integer write SetValue;

End;

//--------------------------------------------------------------

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

//--------------------------------------------------------------

Property APoint[index: Integer]: TPoint read GetPoint write SetPoint;

//--------------------------------------------------------------

На самом деле поле, на которое опирается это свойство, может и не являться массивом.

Для векторного свойства необходимо описать не только тип элементов массива, но также имя и тип индекса. И после read и после write в этом случае должны стоять имена методов – использование имени поля типа массив недопустимо.

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

//--------------------------------------------------------------

function GetPoint (index: Integer): TPoint;

//--------------------------------------------------------------

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

//--------------------------------------------------------------

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

//--------------------------------------------------------------

У векторных свойств есть ещё одна особенность. Некоторые классы могут иметь несколько векторных свойств. Основное свойство такого класса даёт доступ к некоторому массиву, а все остальные свойства являются вспомогательными. Специально для упрощения работы вектрное свойство может быть описано как default:

//--------------------------------------------------------------

Type

TMyClass = class

Property Strings[index: Integer]: String read Get write Put; default;

//--------------------------------------------------------------

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

//--------------------------------------------------------------

Var AMyObject: TMyClass;

Begin

AMyObject.Strings[1]:= ‘First’;

AMyObject[2]:= ‘Second’;

end.

//--------------------------------------------------------------

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

Пример 3. Работа с объектом простая дробьь через простое свойство

Класс простые дроби. Свойство позволяет читать и писать значение дроби в формате строки. Поля “числитель – FN” и “знаменатель - FD” и методы для чтения (getFrac) и записи (setFrac) помещены в раздел с уровнем доступа private (закрытый). Свойство Frac помещено в раздел с уровнем доступа public (открытый).

//--------------------------------------------------------------

program PFrac;

{$APPTYPE CONSOLE}

uses

SysUtils,

UFrac in 'UFrac.pas';

var a,b,c: TFrac;

begin

a:= TFrac.Create(-1,2);

writeln(a.Frac);// -1/2 вывод дроби через свойство в формате строки

b:= TFrac.Create(3,4);

c:= a.Add(b); // ¾ вывод дроби через свойство в формате строки

writeln(b.Frac);

writeln(c.Frac);// 2/8 вывод дроби через свойство в формате строки

c.Free;

a.Frac:= '-3/4';// ввод дроби через свойство в формате строки

c:= a.Add(b);

writeln(c.Frac);// 0/16 вывод дроби через свойство в формате строки

readln;

end.

//--------------------------------------------------------------

unit UFrac;

interface

Uses SysUtils,Dialogs;

type

TFrac = class

private

FN,FD: Real;

function GetFrac: String;

procedure SetFrac(newn: String);

public

constructor Create(Nr: Real = 1;Dr: Real = 1);

function Add(b: TFrac): TFrac;

property Frac: String read GetFrac write SetFrac;

end;

implementation

//--------------------------------------------------------------

constructor TFrac.Create(Nr,Dr: Real);

begin

FN:= Int(Nr);

FD:= Int(Dr);

end;

//--------------------------------------------------------------

function TFrac.add;

var c,e: Real;{c+d}

begin

c:= FN * b.FD + FD * b.FN;

e:= FD * b.FD;

result:= TFrac.Create(c,e);

end;

//--------------------------------------------------------------

function TFrac.GetFrac;

var s: String;

begin

s:= FloatToStr(FN) + '/' + FloatToStr(FD);

Result:= s

end;

//--------------------------------------------------------------

procedure TFrac.SetFrac;

var

n: Integer;

z: String;

begin

try

n:= System.Pos('/',newn);

FN:= StrToInt(System.Copy(newn,1,(n - 1)));

Delete(newn,1,n);

FD:= StrToInt(System.Copy(newn,1,Length(newn)));

except

on EConvertError do ShowMessage('Ошибка ввода')

end;

end;

//--------------------------------------------------------------

end.

 

Контрольные вопросы

  1. Что такое класс?
  2. Где можно помещать описание класса?
  3. Как описывается класс?
  4. Каков порядок описания полей, методов и свойств в классе?
  5. Что такое поле?
  6. Что такое метод?
  7. Что такое свойство?
  8. Как создаются объекты?
  9. Как уничтожаются объекты?
  10. Что содержит переменная объектового типа?
  11. Как распределяется память под объект
  12. Что такое RTTI класса?
  13. Что такое инкапсуляция?
  14. Какой класс является общим предком по умолчанию?
  15. Для чего используется директива inherited?
  16. Для чего используется директива self?
  17. В чём отличие метода от подпрограммы?

 

Источники дополнительных сведений

 

наверх


назад | содержание | вперед