Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Ключевые слова is и as




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

Параметры по умолчанию

 

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

Рассмотрим пример подпрограммы с параметром по умолчанию:

 

// Объявление процедуры с параметром

// EraseFirst, который по умолчанию имеет значение false.

procedure Redraw(EraseFirst: Boolean = False);

// описание процедуры

procedure Redraw(EraseFirst: Boolean);

begin

if (EraseFirst) then begin

{ код стирания изображения }

end;

{ код рисования изображения }

end;

 

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

 

Redraw;

Redraw(False);

 

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

 

{ Объявление функции воспроизведения звукового файла}

function PlayWaveFile(Name: string; Loop: Boolean = False;

Loops: Integer = 10): Integer;

 

{ Вызовы попрограммы PlayWaveFile }

R:= PlayWaveFile('chime.wav'); { не надо повторять звук }

R:= PlayWaveFile('ding.wav', True); { проиграть 10 раз }

R:= PlayWaveFile('bell.wave',True, 5); { проиграть 5 раз }

 

 


 

Глава 3

 

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

 

Что такое класс?

Анатомия класса

Уровни видимости

Конструктор

Деструктор

Поля данных

Методы

Указатель Self

Процедура класса

Пример класса

Перекрытие методов

 

Файл PASCAL3.DOC Версия 17/03/2000-03-17


 

В этой главе рассматривается важнейшее понятие объектно–ориентированного программирования – классы. Классы можно считать ядром Object Pascal. Классы так же являются ядром библиотеки визуальных компонент Delphi (Visual Component Library, VCL), которую вы будете ипользовать при создании настоящих Windows – приложений.

В данной главе объясняется сущность понятия «класс» и как классы используются при программировании.

 

Что такое класс?

 

Класс – это абстрактное понятие, сравнимое с понятием категория в обычном его смысле.

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

 

• возможностью контроля доступа к полям и методам

• конструктором

• деструктором

• полями

• методами (процедурами и функциями)

• специальным указателем Self

 

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

Анатомия класса

 

Любой класс сначала должен быть объявлен. Объявления классов размещают в секции type модуля. Объявление класса содержит ссылку на предка и перечень полей и методов. Object Pascal предоставляет возможность управления доступом к полям и методам класса при использовании его в программе. Управление доступом реализуют специальные директивы, устанавливающие тот или иноц уровень видимости идентификаторов, которыми обозначены поля и методы класса.

 

Область видимости идентификаторов. Предусмотрены четыре уровня видимости идентификаторов класса:

 

• Private (частный, скрытый);

• Public (открытый, публичный);

• Protected (защищенный);

• Published (опубликованный).

 

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

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

Практически в любом классе есть поля и методы, которые выполняют исключительно служебные функции, знать и пользоваться которыми программисту – пользователю нельзя, ибо в противном случае нарушится логика его поведения. Такие поля и методы образуют скрытую (private) часть класса.

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

 

Новый термин: Абстракция данных – это изоляция пользователя от внутренних особенностей их реализации.

 

 

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

Например, когда вы садитесь в автомобиль и поворачиваете ключ зажигания, вам не обязательно знать как все это работает. Действительно, важно знать лишь правила безопасного управления автомобилем. В рамках данной аналогии руль, педали, переключатель скоростей, спидометр и т.д. представляют «пользовательский» интерфейс между водителем и автомобилем. Водитель знает как пользоваться элементами интерфейса чтобы ехать в нужном направлении. Двигатель, трансмиссия, тормозная и электрическая системы автомобиля как бы «невидимы». Двигатель находится там, заглядывать куда имеет смысл лишь квалифицированному механику, да и то, если «что–то не так». Для водителя двигатель – это лишь необходимый компонент автомобиля, а вникать в то как он работает – излишне. Предстваим себе, сколько аварийных ситуаций возникало бы на дорогах, если бы водители должны были постоянно заботиться о том, поступает ли бензин в карбюратор, достаточно ли масла в коробке передач и в двигателе, хорошо ли работают впускные и выпускные клапаны системы газораспределения, достаточное ли напряжение подводится к свечам зажигания и магнитоле? Да кому все это нужно! Класс скрывает подробности своей реализации в private полях и методах, ограждая пользователя от явлений и процессов, которые происходят «под капотом».

Таким образом, внутренее устройство класса есть его собственность (private означает скрытый, частный, собственный), а интерфейс класса открыт для пользователя (public означает открытый, гласный).

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

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

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

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

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

Любой класс объявляется ключевым словом class.

Рассмотрим в качестве примера как может выглядеть объявление класса «автомобиль»:

 

 

TVehicle = class

private

CurrentGear: Integer;//Текущая передача

Started: Boolean; // Движемся или стоим

Speed: Integer; // Текущая скорость

procedure StartElectricalSystem;// Включить зажигание

procedure StartEngine; // Запустить двигатель

protected

procedure StartupProcedure;

public

HaveKey: Boolean; // Ключ в замке зажигания?

Start: Boolean; // Поехали?

procedure SetGear(Gear: Integer);//Какая передача?

procedure Accelerate(Acceleration: Integer);//Газуем

procedure Brake(Factor: Integer);// Тормозим

procedure Turn(Direction: Integer);//Надо свернуть

procedure ShutDown;// Выключить

end;

 

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

 

Конструктор. Все классы Object Pascal имеют специальный метод, который называется конструктором.

 

Новый термин: Конструктор – это метод, который создает экземпляр данного класса – объект.

 

 

Конструктор инициализирует поля, выделяет память для динамических переменных. Иными словами, конструктор должен подготовить объект к работе. На первый взгляд, класс TVehicle не имеет конструктора. Это не так. Класс TVehicle по умолчанию является наследником класса TObject. Класс TObject играет ключевую роль в Object Pascal. Так, все создаваемые нами классы, являются его прямыми или косвенными наследниками. Класс TObject имеет конструктор, деструктор и ряд полезных методов, которые при наследовании автоматически переносятся в новый класс. Конструктор класса TObject имеет имя Create, поэтому его и надо вызвать чтобы создать объект класса TVehicle.

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

 

TVehicle = class

private

CurrentGear: Integer;

Started: Boolean;

Speed: Integer;

procedure StartElectricalSystem;

procedure StartEngine;

protected

procedure StartupProcedure;

public

HaveKey: Boolean;

Start: Boolean;

procedure SetGear(Gear: Integer);

procedure Accelerate(Acceleration: Integer);

procedure Break(Factor: Integer);

procedure Turn(Direction: Integer);

procedure ShutDown;

constructor Create; { конструктор }

end;

 

Конструктор – это процедура специального вида. Ниже мы увидим, что конструкторы относятся к категории «классовых методов». Конструктор не может возвращать значение функции.

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

 

TVehicle = class

{ Предыдущие строки пропущены }

constructor Create;

constructor CreateModel(Model: string);

end;

 

В данном примере объявлены два конструктора с именами Create и CreateModel.

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

 

TVehicle = class

{ Предыдущие строки пропущены }

constructor Create; overload;

constructor Create(AOwner: TObject); overload;

end;

 

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

 

TMyRect = class

private

Left: Integer;

Top: Integer;

Right: Integer;

Bottom: Integer;

public

function GetWidth: Integer;

function GetHeight: Integer;

procedure SetRect(ALeft, ATop, ARight, ABottom: Integer);

constructor Create;

constructor CreateVal(ALeft,ATop,ARight,ABottom: Integer);

end;

 

Определения конструкторов при этом принимают вид:

 

constructor TMyRect.Create;

begin

inherited Create;

Left:= 0;

Top:= 0;

Right:= 0;

Bottom:= 0;

end;

constructor TMyRect.CreateVal(ALeft, ATop, ARight, ABottom:

Integer);

begin

inherited Create;

Left:= ALeft;

Top:= ATop;

Right:= ARight;

Bottom:= ABottom;

end;

 

Обратим внимание на заголовок метода при его описании. Так, он в точности соответствует объявлению метода, но имени предшествует квалификатор – имя класса.

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

Обратите внимание на ключевое слово inherited в конструкторах. Его назначение рассматривается ниже в разделе «Наследование».

Следующий фрагмент кода демонстрирует использование различных конструкторов для создания двух объектов Rect1 и Rect2. Первый из них создается конструктором Create и поэтому все его поля имеют нулевые значения. Второй объект при создании получит указанные значения полей:

 

var

Rect1: TMyRect;

Rect2: TMyRect;

begin

Rect1:= TMyRect.Create;

Rect2:= TMyRect.CreateVal(0, 0, 100, 100);

end;

 

Отметим, что оба экземпляра класса TMyRect размещаются в динамической памяти, поскольку все объекты – экземпляры классов Object Pascal по умолчанию являются динамическими переменными. Поэтому Rect1 и Rect2 являются типизированными указателями на на класс TMyRect.

 

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

 

 

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

 

 

Любой класс – наследник TObject владеет стандартным деструктором, имя которого Destroy. Так же как и конструктор, деструктор не возвращает значения функции.

Формально класс может иметь несколько деструкторов. Однако, типичным является использование базового деструктора Destroy или его перкрытие в потомках при необходимости. Для уничтожения объекта (освобождения связанной с ним памяти) следует вызывать метод Free. Этот метод определен в класса TObject. Перед удалением объекта из динамической памяти метод Free вызывает деструктор класса Destroy. Пример:

 

Rect1:= TMyRect.Create;

{ Действия с объектом Rect1 }

{... }

{ Удаляем Rect1. }

Rect1.Free;

 

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

Следующий код демонстрирует модернизированный вариант класса TMyRect (часть кода не приведена для краткости):

 

TMyRect = class

private

Left: Integer;

Top: Integer;

Right: Integer;

Bottom: Integer;

Text: PChar; { Новое поле }

public

function GetWidth: Integer;

function GetHeight: Integer;

procedure SetRect(ALeft, ATop, ARight, ABottom: Integer);

constructor Create;

constructor CreateVal(ALeft,ATop,ARight,ABottom: Integer);

destructor Destroy; override;

end;

 

 

constructor TMyRect.Create;

begin

inherited Create;

{ Выделение памяти для строки с терминальным нулем }

Text:= AllocMem(1024);

end;

 

 

destructor TMyRect.Destroy;

begin

{ Освободить память }

FreeMem(Text);

inherited Destroy;

end;

 

Конструктор модифицированной версии TMyRect выделяет память для строки текста (PChar), адрес которой запоминается в поле Text. Деструктор должен освободить выделенную память перед удалением объекта из памяти.

 

Обратимся к объявлениею деструктора класса TMyRect:

 

destructor Destroy; override;

 

Обратим внимание на ключевое слово override. Оно является директивой компилятору перекрыть одноименный виртуальный метод базового класса. Более подробно особенности перкрытия методов рассматриваются ниже в разделе «Наследование».

 

ПРИМЕЧАНИЕ: В первую очередь, конструктор некоторого класса должен вызвать конструктор предка. Наоборот, наследуемый деструктор вызает деструктор предка в последнюю очередь.

 

Поля данных класса – это просто переменные, «видимые» из всех его методов. Видимость того или иного поля в программе определяется разделом private, public или protected, в котором находится его объявление.

Поля private и protected – поля нельзя изменять из программы, но они доступны в любом из методов класса.

Поля public доступны в любом месте программы, использующей объект. Обратимся к примеру с классом TMyRect. Нетрудно видеть, что у него нет public – полей. При компиляции следующего фрагмента компилятор зафиксирует ошибку с сообщением «Undeclared identifier: 'Left'» (идентификатор 'Left' не объявлен):

 

Rect:= TMyRect.CreateVal(0, 0, 100, 100);

Rect.Left:= 20; { Ошибка! }

 

Если поле Left объявить в разделе public, то ошибки бы не было.

 

ПРИМЕЧАНИЕ: Предшествующее обсуждение скрытых полей данных справедливо в том случае, если объявление класса содержится не там, где используется. Иными словами, если класс объявлен и используется в одном и том же модуле, то скрытые поля имеют «привилегию видимости». Это означает, что разные класса в пределах одного и того же модуля имеют доступ к скрытым полям друг друга.

 

ПРИМЕЧАНИЕ: Одни теоретики ООП утверждают, что все поля класса должны быть скрытыми, а другие говорят что public. Истина, по–видимому, где–то рядом. Так, если изменение значения поля некритично в смысле будущего поведения объекта, то такое поле можно объявить в секции public. Те поля, изменение значения которых требует проведения дополнительных операций, поддерживающих правильное поведение объекта, следует объявлять скрытыми. Любое сомнение между выбором между private и public следует траковать в пользу private поля.

 

Каждый экземпляр класса – объект владеет собственными экземплярами полей класса. Если мы присваивааем значение полю одного объекта, то это никоим образом не затрагивает другой объект этого же класса. Следующий код создает два экземпляра класса TMyRect совершенно одинаковой структуры, но с разными значениями полей, так как они расположены в различных участках памяти.

 

Rect1:= TMyRect.CreateVal(100, 100, 500, 500);

Rect2:= TMyRect.CreateVal(0, 0, 100, 100);

 

Свойства. Для доступа к скрытым полям Object Pascal вводит в рассмотрение понятие «свойство», которому присущи признаки «запись/чтение», «только чтение» или «только запись». Свойство может иметь метод, который должен вызываться при чтении значения, и метод, который должен вызываться при записи значения. Присутствие ни одного из них не обязатально, поскольку свойство может быть явно связано со скрытым полем. Наиболее важным является метод записи значения свойства. Так, в метод записи (т.е. правило присвоения значения полю) может проверить как корректность присвоения, так и выполнить любые сопутствующие действия, связанные с изменением значения данного поля.

Свойство может быть как скалярным, так и многомерным.

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

Рассмотрим как применить скалярные свойства в классе TMyRect.

 

 

TMyRect = class

private

FLeft: Integer;

FTop: Integer;

FRight: Integer;

FBottom: Integer;

FText: PChar; { Новое поле }

function GetWidth: Integer;

function GetHeight: Integer;

procedure SetHeight(Value: integer);

procedure SetWidth(Value: integer);

public

constructor Create;

constructor CreateVal(ALeft,ATop,ARight,ABottom: Integer);

destructor Destroy; override;

procedure SetRect(ALeft, ATop, ARight, ABottom: Integer);

property Height: integer read GetHeight write SetHeight;

property Width: integer read GetWidth write SetWidth;

end;

 

Здесь мы переименовали поля, перенесли функции GetWidth и GetHeight в раздел private, добавили два метода SetHeight и SetWidth, которые отвечают за изменение высоты и ширины прямоугольника, а кроме того – исключили метод SetRect.

Так как теперь методы GetWidth, GetHeight, SetHeight, SetWidth находятся в секции private, программе они недоступны. Но в разделе public появились два свойства:

 

property Height: integer read GetHeight write SetHeight;

property Width: integer read GetWidth write SetWidth;

 

Объявление скалярного свойства содержит ключевое слово property, идентификатор, тип, метод чтения (read GetHeight) и метод записи (write SetHeight). Наличие метода записи или чтения необязательно. Так, допустимым будет свойство, которое напрямую связано с полем.

 

property Left: integer read FLeft write FLeft;

 

Если мы хотим придать свойству статус «только чтение», надо опустить ключевое слово write, например:

 

property Height: integer read GetHeight;

 

В программе скалярное свойство рассматривается как обычное поле, например:

 

var R: TMyRect;

begin

R:= TMyRect.Create;

R.Left:= 10;

R.Right:= 20;

R.Width:= 200;

R.Height:= 200;

.......

if R.Width > 300 then....

.......

R.Free;

end;

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

 

property Left: integer read FLeft write FLeft default 0;

 

Многомерное свойство внешне выглядит как массив. Рассмотрим правила объявления таких свойств на примере классов «вектор» и «матрица».

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

 

type

TMyVector = class

private

FItems: array of real; // динамический массив

FCount: integer; // число элементов

function GetItem(Index:integer):real;

procedure SetItem(Index:integer; Value:real);

public

constructor Create(ACount:integer);

destructor Destroy; override;

property Count:integer read FCount;

property Item[index:integer]:real read GetItem

write SetItem; default;

property Count: integer read FCount;

end;

 

Здесь векторным является свойство Item. Для облегчения работы с объектами этого класса свойство Item отмечено директивой default. Директива default не есть «значение по умолчанию». Разница заключается в том, что во–первых, директиве предшествует символ (;), а во-вторых, ее смысл заключается в том что имя векторного свойства по умолчанию можно опускать. Например:

 

var V: TVector;

........

V:= TVector.Create(6);

V[1]:=0.6;//аналогично

V.Item[1]:=0.6;

 

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

Обявление матричного (т.е. двумерного) свойства проиллюстрируем на следующем примере

 

type TMyMatrix=class

private

FItems: array of array of real;

FRowCount: integer;// число строк матрицы

FColCount: integer; // число столбцов матрицы

function GetItem(ARow,ACol:integer):real;

procedure SetItem(ARow,ACol:integer; Value:real);

public

constructor Create(Rows,Cols:integer);

destructor Destroy; override;

property Rows: integer read FRowCount;

propery Cols: integer read FColCount;

property Item[ARow,ACol:integer]:real read GetItem

write SetItem; default;

end;




Поделиться с друзьями:


Дата добавления: 2015-04-29; Просмотров: 363; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.2 сек.