Тема 3. Класи на Object Pascal
Основні поняття
Клас
Об’єкт
Екземпляр класу
Поле
Метод
Властивість
Конструктор
Деструктор
Опис класів на Object Pascal
Тип даних – статичний опис сукупності динамічних об’єктів, які об’єднані певними операціями. В ООП кожен тип базується на певному класі. Клас – це тип даних з частковою або повною реалізацією операцій.
type <ім’я класу> = class (<ім’я батьківського класу>)
private
{поля, методи}
protected
{поля, методи, властивості}
public
{поля, методи, властивості}
published
{поля, методи, властивості}
end;
Ім’я класу – будь-який ідентифікатор; як правило, починається з великої літери Т. Ім’я надкласу може не вказуватись, тоді вважається, що клас є безпосереднім нащадком класу TObject. В ObjectPascal клас TObject є базовим класом для усіх об’єктів.
В описі класу визначають поля і методи.
Поля – це певні характеристики об’єктів класу (описують як змінні).
Методи – це операції над об’єктами класу (описують як процедури та функції).
Наступні директиви керують правами доступу до елементів класу.
Private (особистий, закритий) містить поля та методи, які використовуються лише об’єктами цього класу.
Protected (захищений) містить поля та методи, доступні також для всіх нащадків цього класу.
Public (відкритий) – поля та методи, а також властивості, доступні для зовнішнього використання, у тому числі з об’єктів інших класів.
Published (для публікації) містить поля, методи, які є відкритими, а також властивості які будуть доступні під час проектування у інспекторі об’єктів Delphi (для перегляду і зміни).
Якщо директиви не вказані, то за замовчуванням вважається, що встановлено директиву Published.
Приклад класу: TPoint – точки на площині
type
TPoint = class
X, Y: Real; // координати в декартовій системі
function Ro: Real; // координати
function Fi: Real; // в полярній системі
function Distance (p: TPoint): Real;
// відстані між двома точками;
procedure Translate (dx, dy: Real);
// переміщення;
procedure Rotate (p:TPoint; f: Real);
// обертання точки на певний кут навколо іншої точки (центру обертання);
procedure Scale (k: Real);
// масштабування
end;
В цьому описі:
X, Y – поля класу;
Ro, Fi, Distance, Translate, Rotate, Scale – методи класу.
Можливим є опис, де X та Y будуть методами, а Ro та Fi – полями:
// …
Ro, Fi: Real; // координати в полярній системі
function X: Real; // координати
function Y: Real; // в декартовій системі
У Object Pascal опис класу разом з полями та прототипами методів включається у відкриту частину програмного модуля, що називається інтерфейсом (англ. interface). Реалізація методів включається до закритої частини модуля, що називається реалізацією (англ. implementation).
implementation
function TPoint.Ro: Real;
begin
Ro:=sqrt(sqr(X)+sqr(Y));
// або
// Result:=sqrt(sqr(X)+sqr(Y));
end;
function TPoint.Fi: Real; // в полярній системі
begin
Fi:= ArcTan(Y/X);
end;
function TPoint.Distance (p: TPoint): Real;
begin
Distance:=sqrt(sqr(X-p.X)+sqr(Y-p.Y));
end;
procedure TPoint.Translate (dx, dy: Real);
begin
X:=X+dx;
Y:=Y+dy;
end;
procedure TPoint.Rotate (p:TPoint; f: Real);
var x1, y1: real;
begin
x1:=(X-p.X)*cos(f)-(Y-p.Y)*sin(f)+p.X;
y1:=(X-p.X)*sin(f)+(Y-p.Y)*cos(f)+p.Y;
X:=x1; Y:=y1;
end;
procedure TPoint.Scale (k: Real);
begin
X:=k*X;
Y:=k*Y;
end;
Опис об’єктів на Object Pascal. Створення об’єктів
Об’єкт – це екземпляр певного класу. Об’єкт описується як змінна, тип змінної – це клас об’єкта.
Приклад
var P1, P2: TPoint;
Назва об’єкта є вказівником на цей об’єкт. При описі змінних-об’єктів в пам’яті виділяється місце лише під вказівник (4-байтну адресу). Початкове значення цієї адреси дорівнює nil (невизначений вказівник).
Таким чином, об’єкти в Object Pascal є динамічними змінними, тобто створюються під час роботи програми. Але, при роботі з об’єктами, на відміну від вказівників, розадресація вказівника (^), процедури New та Dispose не використовуються.
Для створення об’єктів використовують спеціальний метод – конструктор, який позначається ключовим словом constructor. Як правило, цей метод має назву Create.
Приклад:
type TPoint= class
// …
constructor Create;
// …
end;
var P1, P2: TPoint;
constructor TPoint.Create;
Begin
X:=0;
Y:=0;
End;
begin
P1:= TPoint.Create;
P2:= TPoint.Create;
//…
end;
В даній реалізації конструктор викликається без параметрів.
Можна реалізувати конструктор у вигляді:
constructor TPoint.Create (a, b: Real);
begin
X:=a;
Y:=b;
end;
Тоді викликати конструктор можна, наприклад:
P1:=TPoint.Create (1, 2);
Всередині методів можна використовувати змінну Self, яка позначає об’єкт, для якого викликається даний метод. Зокрема, для розв’язання колізій (протиріч) між назвами полів і локальних змінних та параметрів методів можна вказати цю змінну перед назвами полів, наприклад:
Self.X:= X;
Self.Y:= Y;
Якщо конструктор реалізовано, але у ньому немає присвоєння початкових значень полям об’єкта, то відбувається ініціалізація за замовчуванням. Числові змінні ініціалізуються значеннями 0, символьні – нуль-символом (#0), логічні змінні – значенням false, вказівники – значенням nil.
Таким чином, на відміну від вказівників процедура new для створення об’єктів не використовується, якщо існує конструктор. Але змінна, яка позначає об’єкт в операціях використовується подібно до вказівників.
Ініціалізація об’єктів, звертання до полів та методів. Властивості
При написанні програми доступ до полів (для читання) не відрізняється від доступу до метода без аргументів (функції) – принцип однорідного доступу. Наприклад:
X1:=P1.X; //звертання до поля;
Ro1:=P1.Ro; //звертання до метода.
Об’єкт, для якого викликається метод, називається також одержувачем цього методу (об’єкт Р1 є одержувачем методу Ro);
Якщо потрібно змінити поле об’єкта після створення, то можна використати оператор присвоєння, наприклад
P1.X:=6; // безпосередня зміна значення поля
У багатьох випадках пряма зміна поля може бути некоректною. Наприклад, для об’єктів D1, D2 класу TDate (календарні дати) з полями Year, Month, Day:
D1.Month:= 13; D2.Day:= 32; // не коректно!
Тому, як правило, значення полів класу можуть змінювати лише методи даного класу (або дочірніх класів). Для коректного доступу до полів класу звертаються через властивості (англ. property):
property <ім’я властивості>: <тип>
read <ім’я поля або метода для читання>
write <ім’я поля або метода для запису>
default <значення за замовчуванням>;
Метод для читання – це функція без параметрів, яка повертає значення з типом властивості (наприклад GetMonth, GetDay). Також цю функцію називають селектором.
Метод для запису – це процедура, яка змінює значення певного поля при певних умовах (SetMonth, SetDay). Також ця процедура називається модифікатором. Її параметром є змінна з типом поля.
Поля та методи для читання або запису оголошуються перед описом властивості, в описі лише вказуються їх назви.
Значення за замовчуванням – це певне значення властивості, яке задається при створенні об’єкта (для полів Month та Day може бути рівним 1).
Приклад:
type TDate = class
private
aDay, aMonth, aYear: integer
protected
procedure SetDay (value: integer);
// …
published
property Day: integer read aDay
write SetDay
default 1;
end;
procedure TDay.SetDay (value: integer);
begin
if value in [1..31]
aDay:= value;
end;
Властивості – це ще один спосіб керувати доступом до полів класу (поряд з директивами private, protected, public, published).
Присвоєння об’єктів
У оператора присвоєння можливі дві семантики:
- семантика копіювання;
- семантика вказівників.
У першому випадку (для всіх змінних, крім об’єктів та вказівників) вміст правої частини виразу копіюється у вміст лівої частини (створюється копія у пам’яті).
У другому випадку (для об’єктів та вказівників) ліва частина буде вказувати на вміст правої частини (копія у пам’яті не створюється).
Приклад:
var P1, P2: TPoint;
P1:= TPoint.Create (3, 4); //P1.X=3; P1.Y=4;
P2:= TPoint.Create (5, 7); //P2.X=5; P2.Y=7;
P1:=P2; //P1.X=5; P1.Y=7;
P2.X:=10; //P2.X=10; P1.X=10;
В результаті присвоєння об’єкт P1 буде вказувати на вміст об’єкту P2. Попередній вміст об’єкту P1 став недосяжним.
Для створення копії об’єкта, як правило, описується і реалізується спеціальний метод Assign, який робить присвоєння кожного поля окремо за новим вказівником.
P1.Assign(P2); // P1:=P2
Перевірка на рівність
При порівняні двох змінних, аналогічно до присвоєння є дві семантики: копіювання і вказівників.
Для чисел та символьних змінних умовою рівності є однакове побітове представлення в пам’яті (семантика копіювання).
Для змінних складених типів використовується рівність елементів. Можлива ситуація, коли: а = b, але b ≠ а (коли змінна b містить більше елементів, ніж змінна а).
Приклад:
а – точка у двовимірному просторі, b – точка у тривимірному просторі.
Для об’єктів рівність досягається, коли існує рівність вказівників (семантика вказівників).
If P1=P2 then
{…}
Альтернативна перевірка на рівність (для об’єктів з різними вказівниками) може бути реалізована за допомогою спеціального метода Equals.
If P1.Equals(P2) then
{…}
Знищення об’єктів
Об’єкти – динамічні змінні, які створюються та знищуються під час роботи програми (run-time), тому знищення об’єкту передбачає звільнення пам’яті. З цією метою використовують спеціальний метод – деструктор (позначається ключовим словом destructor). Як правило, цей метод має назву Destroy.
Окрім деструктора в описі класу звичайно вводиться метод Free, який перевіряє чи змінна Self має значення nil. Для непустого об’єкта викликається деструктор.
Приклад.
procedure TPoint.Free;
begin
if Self<>nil then Destroy;
end;
При цьому автоматичного встановлення значення об’єктної змінної в nil не відбувається.
Керування пам’яттю
Для звільнення пам’яті, яка виділяється під об’єкти, які більше не потрібні, можливі 3 способи:
- Ручне звільнення (засобами мов програмування);
- “Автоматичне прибирання сміття” (англ. аutomatic garbage collection). Під “сміттям” розуміють об’єкти, вміст яких став недосяжним, наприклад, внаслідок присвоєння. Для різних операційних систем розроблені спеціальні процедури, які відстежують ці об’єкти з певними часовими інтервалом і знищують.
- Кожному об’єкту при створенні призначається власник (інший об’єкт), який керує процесом звільнення пам’яті. (Наприклад, форма є власником всіх об’єктів, що знаходяться на ній).
Типові помилки при звільненні пам’яті
- Спроба використати пам’ять, яка ще не виділена;
- Виділена пам’ять ніколи не звільнюється;
- Пам’ять звільняюється двома незалежними процедурами;
- Спроба використати вже звільнену пам’ять.