Паскаль - Классы
Вы видели, что объекты Pascal демонстрируют некоторые характеристики объектно-ориентированной парадигмы. Они реализуют инкапсуляцию, скрытие данных и наследование, но также имеют ограничения. Например, объекты Паскаля не участвуют в полиморфизме. Таким образом, классы широко используются для реализации правильного объектно-ориентированного поведения в программе, особенно в программном обеспечении на основе графического интерфейса.
Класс определяется почти так же, как объект, но является указателем на объект, а не на сам объект. Технически это означает, что класс выделяется в куче программы, а объект - в стеке. Другими словами, когда вы объявляете переменную типа объекта, она будет занимать столько места в стеке, сколько размер объекта, но когда вы объявляете переменную типа класса, она всегда будет иметь размер указателя в стеке. Фактические данные класса будут в куче.
Определение классов Паскаля
Класс объявляется так же, как объект, с использованием объявления типа. Общая форма объявления класса следующая:
type class-identifier = class
private
field1 : field-type;
field2 : field-type;
...
public
constructor create();
procedure proc1;
function f1(): function-type;
end;
var classvar : class-identifier;
Стоит отметить следующие важные моменты -
Определения классов должны входить только в часть программы с объявлением типа.
Класс определяется с помощью class ключевое слово.
Поля - это элементы данных, которые существуют в каждом экземпляре класса.
Методы объявляются в определении класса.
Есть предопределенный конструктор под названием Createв классе Root. Каждый абстрактный класс и каждый конкретный класс является потомком Root, поэтому все классы имеют по крайней мере один конструктор.
Существует предопределенный деструктор под названием Destroyв классе Root. Каждый абстрактный класс и каждый конкретный класс является потомком Root, поэтому все классы имеют хотя бы один деструктор.
Давайте определим класс Rectangle, который имеет два члена данных целочисленного типа - длину и ширину, а также некоторые функции-члены для управления этими членами данных и процедуру для рисования прямоугольника.
type
Rectangle = class
private
length, width: integer;
public
constructor create(l, w: integer);
procedure setlength(l: integer);
function getlength(): integer;
procedure setwidth(w: integer);
function getwidth(): integer;
procedure draw;
end;
Давайте напишем полную программу, которая создаст экземпляр класса прямоугольника и нарисует прямоугольник. Это тот же пример, который мы использовали при обсуждении объектов Паскаля. Вы обнаружите, что обе программы почти одинаковы, за следующими исключениями:
Вам нужно будет включить директиву {$ mode objfpc} для использования классов.
Вам потребуется включить директиву {$ m +} для использования конструкторов.
Создание экземпляра класса отличается от создания экземпляра объекта. Только объявление переменной не создает места для экземпляра, вы будете использовать конструктор create для выделения памяти.
Вот полный пример -
{$mode objfpc} // directive to be used for defining classes
{$m+} // directive to be used for using constructor
program exClass;
type
Rectangle = class
private
length, width: integer;
public
constructor create(l, w: integer);
procedure setlength(l: integer);
function getlength(): integer;
procedure setwidth(w: integer);
function getwidth(): integer;
procedure draw;
end;
var
r1: Rectangle;
constructor Rectangle.create(l, w: integer);
begin
length := l;
width := w;
end;
procedure Rectangle.setlength(l: integer);
begin
length := l;
end;
procedure Rectangle.setwidth(w: integer);
begin
width :=w;
end;
function Rectangle.getlength(): integer;
begin
getlength := length;
end;
function Rectangle.getwidth(): integer;
begin
getwidth := width;
end;
procedure Rectangle.draw;
var
i, j: integer;
begin
for i:= 1 to length do
begin
for j:= 1 to width do
write(' * ');
writeln;
end;
end;
begin
r1:= Rectangle.create(3, 7);
writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
r1.draw;
r1.setlength(4);
r1.setwidth(6);
writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
r1.draw;
end.
Когда приведенный выше код компилируется и выполняется, он дает следующий результат:
Draw Rectangle: 3 by 7
* * * * * * *
* * * * * * *
* * * * * * *
Draw Rectangle: 4 by 6
* * * * * *
* * * * * *
* * * * * *
* * * * * *
Видимость членов класса
Видимость указывает на доступность учащихся. Члены класса Pascal имеют пять типов видимости -
Старший Нет | Видимость и доступность |
---|---|
1 |
Public Эти участники всегда доступны. |
2 |
Private К этим членам можно получить доступ только в модуле или модуле, который содержит определение класса. К ним можно получить доступ изнутри методов класса или извне. |
3 | Strict Private Доступ к этим членам можно получить только из методов самого класса. Другие классы или дочерние классы в том же модуле не могут получить к ним доступ. |
4 |
Protected Это то же самое, что и private, за исключением того, что эти члены доступны для потомков, даже если они реализованы в других модулях. |
5 | Published Это то же самое, что и Public, но компилятор генерирует информацию о типе, которая необходима для автоматической потоковой передачи этих классов, если компилятор находится в состоянии {$ M +}. Поля, определенные в опубликованном разделе, должны относиться к типу класса. |
Конструкторы и деструкторы для классов Паскаля
Конструкторы - это специальные методы, которые вызываются автоматически при создании объекта. Таким образом, мы в полной мере пользуемся этим поведением, инициализируя многие вещи с помощью функций-конструкторов.
Паскаль предоставляет специальную функцию create () для определения конструктора. Вы можете передать в функцию-конструктор любое количество аргументов.
В следующем примере будет создан один конструктор для класса с именем Books, который инициализирует цену и название книги во время создания объекта.
program classExample;
{$MODE OBJFPC} //directive to be used for creating classes
{$M+} //directive that allows class constructors and destructors
type
Books = Class
private
title : String;
price: real;
public
constructor Create(t : String; p: real); //default constructor
procedure setTitle(t : String); //sets title for a book
function getTitle() : String; //retrieves title
procedure setPrice(p : real); //sets price for a book
function getPrice() : real; //retrieves price
procedure Display(); // display details of a book
end;
var
physics, chemistry, maths: Books;
//default constructor
constructor Books.Create(t : String; p: real);
begin
title := t;
price := p;
end;
procedure Books.setTitle(t : String); //sets title for a book
begin
title := t;
end;
function Books.getTitle() : String; //retrieves title
begin
getTitle := title;
end;
procedure Books.setPrice(p : real); //sets price for a book
begin
price := p;
end;
function Books.getPrice() : real; //retrieves price
begin
getPrice:= price;
end;
procedure Books.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price:5:2);
end;
begin
physics := Books.Create('Physics for High School', 10);
chemistry := Books.Create('Advanced Chemistry', 15);
maths := Books.Create('Algebra', 7);
physics.Display;
chemistry.Display;
maths.Display;
end.
Когда приведенный выше код компилируется и выполняется, он дает следующий результат:
Title: Physics for High School
Price: 10
Title: Advanced Chemistry
Price: 15
Title: Algebra
Price: 7
Как и неявный конструктор с именем create, существует также неявный метод деструктора destroy, с помощью которого вы можете освободить все ресурсы, используемые в классе.
Наследование
Определения классов Pascal могут необязательно наследоваться от определения родительского класса. Синтаксис следующий -
type
childClas-identifier = class(baseClass-identifier)
< members >
end;
В следующем примере представлен класс романов, который наследует класс Books и добавляет дополнительные функции в зависимости от требований.
program inheritanceExample;
{$MODE OBJFPC} //directive to be used for creating classes
{$M+} //directive that allows class constructors and destructors
type
Books = Class
protected
title : String;
price: real;
public
constructor Create(t : String; p: real); //default constructor
procedure setTitle(t : String); //sets title for a book
function getTitle() : String; //retrieves title
procedure setPrice(p : real); //sets price for a book
function getPrice() : real; //retrieves price
procedure Display(); virtual; // display details of a book
end;
(* Creating a derived class *)
type
Novels = Class(Books)
private
author: String;
public
constructor Create(t: String); overload;
constructor Create(a: String; t: String; p: real); overload;
procedure setAuthor(a: String); // sets author for a book
function getAuthor(): String; // retrieves author name
procedure Display(); override;
end;
var
n1, n2: Novels;
//default constructor
constructor Books.Create(t : String; p: real);
begin
title := t;
price := p;
end;
procedure Books.setTitle(t : String); //sets title for a book
begin
title := t;
end;
function Books.getTitle() : String; //retrieves title
begin
getTitle := title;
end;
procedure Books.setPrice(p : real); //sets price for a book
begin
price := p;
end;
function Books.getPrice() : real; //retrieves price
begin
getPrice:= price;
end;
procedure Books.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price);
end;
(* Now the derived class methods *)
constructor Novels.Create(t: String);
begin
inherited Create(t, 0.0);
author:= ' ';
end;
constructor Novels.Create(a: String; t: String; p: real);
begin
inherited Create(t, p);
author:= a;
end;
procedure Novels.setAuthor(a : String); //sets author for a book
begin
author := a;
end;
function Novels.getAuthor() : String; //retrieves author
begin
getAuthor := author;
end;
procedure Novels.Display();
begin
writeln('Title: ', title);
writeln('Price: ', price:5:2);
writeln('Author: ', author);
end;
begin
n1 := Novels.Create('Gone with the Wind');
n2 := Novels.Create('Ayn Rand','Atlas Shrugged', 467.75);
n1.setAuthor('Margaret Mitchell');
n1.setPrice(375.99);
n1.Display;
n2.Display;
end.
Когда приведенный выше код компилируется и выполняется, он дает следующий результат:
Title: Gone with the Wind
Price: 375.99
Author: Margaret Mitchell
Title: Atlas Shrugged
Price: 467.75
Author: Ayn Rand
Стоит отметить следующие важные моменты -
Члены класса "Книги" имеют protected видимость.
Класс Novels имеет два конструктора, поэтому overload Оператор используется для перегрузки функции.
Заявлена процедура отображения книг. virtual, так что тот же метод из класса Novels может override Это.
Конструктор Novels.Create вызывает конструктор базового класса с помощью inherited ключевое слово.
Интерфейсы
Интерфейсы определены для предоставления разработчикам общего имени функции. Различные разработчики могут реализовать эти интерфейсы в соответствии со своими требованиями. Можно сказать, интерфейсы - это скелеты, которые реализуют разработчики. Ниже приведен пример интерфейса -
type
Mail = Interface
Procedure SendMail;
Procedure GetMail;
end;
Report = Class(TInterfacedObject, Mail)
Procedure SendMail;
Procedure GetMail;
end;
Обратите внимание: когда класс реализует интерфейс, он должен реализовывать все методы интерфейса. Если метод интерфейса не реализован, компилятор выдаст ошибку.
Абстрактные классы
Абстрактный класс - это класс, который нельзя создать, а только унаследовать. Абстрактный класс указывается включением абстрактного символа слова в определение класса, например:
type
Shape = ABSTRACT CLASS (Root)
Procedure draw; ABSTRACT;
...
end;
При наследовании от абстрактного класса все методы, отмеченные как абстрактные в объявлении родительского класса, должны быть определены потомком; кроме того, эти методы должны быть определены с одинаковой видимостью.
Статическое ключевое слово
Объявление членов или методов класса статическими делает их доступными без необходимости создания экземпляра класса. К члену, объявленному как статический, нельзя получить доступ с помощью созданного объекта класса (хотя статический метод может). Следующий пример иллюстрирует концепцию -
program StaticExample;
{$mode objfpc}
{$static on}
type
myclass=class
num : integer;static;
end;
var
n1, n2 : myclass;
begin
n1:= myclass.create;
n2:= myclass.create;
n1.num := 12;
writeln(n2.num);
n2.num := 31;
writeln(n1.num);
writeln(myclass.num);
myclass.num := myclass.num + 20;
writeln(n1.num);
writeln(n2.num);
end.
Когда приведенный выше код компилируется и выполняется, он дает следующий результат:
12
31
31
51
51
Вы должны использовать директиву {$ static on} для использования статических членов.