Главная » Просмотр файлов » Основы Object Pascal

Основы Object Pascal (551739), страница 9

Файл №551739 Основы Object Pascal (Основы Object Pascal) 9 страницаОсновы Object Pascal (551739) страница 92015-11-14СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 9)

где e и M – заданные вещественные значения, а неизвестной является вещественная переменная E. Для решения этого уравнения применяется метод последовательных приближений (итераций) по схеме

Ek+1 = M + e sin(Ek), k=0,1,2,. . .

с начальным приближением E0 = M. Итерации выполняют до тех пор, пока не будет достигнута заданная точность, например Ek+1Ek 10-9.

function SolveEquation_While(const e,M : real):real;

{ Решение уравнения Кеплера в цикле while }

const

Eps = 1e-9;

var

Eold,Enew : real;

Stop : boolean;

begin

Eold := M;

Stop := false;

while not Stop do

begin

Enew := M + e * Sin(Eold);

Stop := Abs(Eold-Enew)<=Eps;

Eold := Enew;

end;

Result := Enew;

end;

Синтаксис цикла while имеет вид

while cond_expr do

begin

statements;

end;

Тело цикла (statements) выполняется итеративно пока условие цикла cond_expr есть True. Исходное состояние условия цикла необходимо обеспечить до входа в него. В теле цикла должны присутствовать операторы, так или иначе влияющие на состояние условия цикла. В противном случае получим бесконечный цикл (зависание системы).

Когда условное выражение cond_expr становится ложным, цикл прекращается.

Если тело цикла состоит из единственного оператора, ключевые слова begin и end необязательны.

Цикл repeat

Цикл repeat почти идентичен циклу while. Отличие состоит в том что здесь условие цикла проверяется в точке окончания, т.е. после каждого выполнения операторов тела цикла.

В качестве примера рассмотрим предыдущее упражнение, но теперь вместо while применим repeat:

procedure TForm1.Button1Click(Sender: TObject);

var

I : Integer;

begin

I := 5;

Memo1.Clear;

repeat

Memo1.Lines.Add('До перехвата '+

IntToStr(I) + ' сек.');

Dec(I);

until I = -1;

Memo1.Lines.Add('Цель с радара исчезла');

end;

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

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

function SolveEquation_Repeat(const e,M : real):real;

{ Решение уравнения Кеплера в цикле repeat }

const

Eps = 1e-9;

var

Eold,Enew : real;

Stop : boolean;

begin

Eold := M;

Stop := false;

repeat

Enew := M + e * Sin(Eold);

Stop := Abs(Eold-Enew)<=Eps;

Eold := Enew;

until Stop;

Result := Enew;

end;

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

Синтакис цикла repeat имеет следующий вид

repeat

statements;

until cond_expr;

Цикл repeat повторяет выполнение блока операторов, обозначенных как statements пока условие cond_expr есть False. Исходное состояние цикла необходимо подготовить до входа в него. Среди операторов statements должен быть такой, который модифицирует условие цикла. В противном случае возможен бесконечный цикл и зависание системы. Повторение блока statements прекращается в точке until при cond_expr=true.

Управление циклами

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

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

var

Done : Boolean;

Error : Boolean;

begin

Done := False;

{Начало цикла}

while not Done do

begin

{Здесь находится безусловно выполняемый код }

Error := SomeFunction; {формируем условие}

if Error then Continue; {пропуск до конца если Error=true}

{ Здесь находятся операторы, которые не надо выполнять

при Error=true }

end; {конец цикла}

end;

Процедура Break прерывает выполнение цикла независимо от текущего его состояния.

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

var

MyArray : array [0..99] of Integer;

Index : Integer;

SearchNumber : Integer;

I : Integer;

Diagnostics : string;

const

sCaption = ’Заданное число ’;

begin

FillArray; { Процедура инициализации массива }

Index := -1;

{Ввод искомого числа из компонента Edit1}

SearchNumber := StrToInt(Edit1.Text);

Diagnostics := sCaption + Edit1.Text + ’ ’;

for I := 0 to High(MyArray) do

begin

if MyArray[I] = SearchNumber then

begin

Index := I;

Break;

end;

end;

if Index <> -1 then

Label1.Caption:= Diagnostics +

’- содержит элемент номер ’+

IntToStr(Index)

else

Label1.Caption := Diagnostics + ’- не найдено. ’;

end;

Процедуры Continue и Break можно вызывать только в теле цикла for, while или repeat. На вызов любой из этих процедур вне цикла компилятор отреагирует сообщением об ошибке:

Error: BREAK or CONTINUE outside of loop.

Оператор goto

Оператор goto передает управление в точку программы, помеченную идентификатором специального вида – меткой.

Употребление в программах на Object Pascal операторов перехода считается делом недостойным и теоретически избыточным. Передачи управления делают программу непонятной и трудно модифицируемой.

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

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

Следующий фрагмент кода иллюстрирует применение оператора перехода при решении известной задачи:

procedure TForm1.Button1Click(Sender: TObject);

var

I: Integer;

label

MyLabel;

begin

Memo1.Clear;

I := 0;

MyLabel:

Inc(I);

Memo1.Lines.Add(IntToStr(I));

if I < 5 then goto MyLabel;

end;

Упражнение. Напишите программу, которая при нажатии на кнопку формы заполняет массив из 100 элементов случайными числами (используйте функцию Random), вводит целое число, заданное пользователем (поместите на форму компонент Edit), ищет заданное число в массиве и выводит сообщение о результате поиска. Как образец, используйте фрагмент кода, приведенный выше.

Оператор case

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

Рассмотрим следующий пример оператора case:

{

Вычисляем размер штрафа Fine за превышение скорости. Размер штрафа пропорционален величине превышения, выраженной в км/час (AmountOverSpeedLimit). Если скорость превышена более чем на 30 км/час водитель лишается прав на срок JailTime.

}

case AmountOverSpeedLimit of

0 : Fine := 0;

10 : Fine := 20;

15 : Fine := 50;

20,

25,

30 : Fine := AmountOverSpeedLimit * 10;

else

begin

Fine := GoToCourt;

JailTime := GetSentence;

end;

end; // case

Оператор case состоит из нескольких частей. Вначале определяется выражение–селектор. В нашем примере селектором является переменная AmountOverSpeedLimit.

Затем следуют определения ветвей, каждая из которых соответствует либо конкретному значению селектора, либо серии возможных значений селектора. Так, если значение AmountOverSpeedLimit равно 0, будет выбрана ветвь 0:. Переменная Fine получит значение 0.

Если значение AmountOverSpeedLimit равно 10, переменная Fine получит значение 20, и т.д. Как только нужная ветвь найдена и выполнен связанный с ней оператор, оставшиеся случаи игнорируются.

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

Fine := AmountOverSpeedLimit * 10;.

Вместо перечисления значений здесь можно было бы написать и так

20..30: Fine := AmountOverSpeedLimit * 10;.

т.е. указать случай как отрезок значений селектора.

Наконец, в составе case мы видим оператор else. Блок кода, расположенный после ключевого слова else, будет выполнен если значение селектора не подпадает ни под один из случаев.

Наличие else – ветви в составе оператора case необязятельно.

ПРАВИЛО. Селектором в операторе case может быть выражение любого упорядоченного типа (Integer, Word, Byte, Char, Cardinal и т.п.).

Ветви оператора case взаимно исключают друг друга – может быть выполнена только одна из них.

Строковый или вещественный типы селекторов недопустимы:

case SomeStringVariable of

'One' : { code }

'Two' : { code }

end;

Синтаксис оператора case имеет вид

case selector of

value_1: statements_1;

value_2: statements_2;

.

.

.

value_n: statements_n;

else

else_statements;

end;

Оператор case выполняет один из блоков кода, выбирая его по значению выражения–селектора (selector). Тип выражения селектора относится к одному из упорядоченных типов Object Pascal.

Блок кода statements_1 выполняется при условии что значение selector равно (соответствует) value_1, блок кода statements_2 выполняется при условии что значение selector равно (соответствует) value_2 и т.д.

Если значение селектора не соответствует ни одной из ветвей value_1...value_n выполняется блок else_statements.

Блок else не обязателен.

Область видимости идентификаторов

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

Обычно область действия идентификатора начинается там, где он объявлен, а заканчивается там где это объявление теряет силу.

Большинство переменных являются локальными. Их область видимости – блок кода, в котором они объявлены. Рассмотрим программу на листинге 2.1. (На этом листинге показан модуль, текст которого сгенерирован Delphi.)

Листинг 2.1. SCOPEU.PAS.

01: unit ScopeU;

02:

03: interface

04:

05: uses

06: Windows, Messages, SysUtils, Classes, Graphics,

Controls, Forms, Dialogs, StdCtrls;

08:

09: type

10: TForm1 = class(TForm)

11: Button1: TButton;

12: Memo1: TMemo;

13: procedure Button1Click(Sender: TObject);

14: procedure FormCreate(Sender: TObject);

15: private

16: { Private declarations }

17: public

18: { Public declarations }

19: end;

20:

21: var

22: Form1: TForm1;

23:

24: implementation

25:

26: var

27: X : Integer;

28

29: {$R *.DFM}

30:

31: procedure TForm1.Button1Click(Sender: TObject);

32: var

33: X : Integer;

34:

35: procedure Test;

36: var

37: X : Integer;

38: begin

39: X := 300;

40: {Здесь переменная X имеет значение 300. }

41: Memo1.Lines.Add(`Локальная функция X: ` + IntToStr(X));

42: end;

43:

44: begin

45: X := 100;

46: Memo1.Lines.Clear;

47: { Локальная переменная X имеет значение 100. }

48: Memo1.Lines.Add(`Локальная X: ` + IntToStr(X));

49: { Глобальная переменная X имеет значение 200. }

50: Memo1.Lines.Add(`Глобальная X: ` + IntToStr(ScopeU.X));

51: { Вызываем процедуру Test. }

52: Test;

53: end;

54:

55: procedure TForm1.FormCreate(Sender: TObject);

56: begin

57: { Инициализируем переменную модуля X. }

58: X := 200;

59: end;

60:

61: end.

Прежде всего отметим то, что переменная X объявлена в трех местах этой программы. Она объявлена в строке 27 в разделе реализации, затем в строке 33 (метод Button1Click), наконец – в строке 37 (в локальной процедуре с именем Test). Если объявление переменной встречается дважды, компилятор фиксирует ошибку «Identifier redeclared: 'X'», а компиляция прекращается. Но эта программа прекрасно работает. Почему? Потому, что каждая из переменных с именем X в этом листинге имеет свою облась действия.

Посмотрите на листинг 2.1 внимательней. В строке 37 переменная X объявлена внутри локальной процедуры Test и «видима» только в блоке кода этой процедуры. (Здесь мы несколько забежали вперед, говоря о локальных процедурах и функциях. Необходимые разъяснения будут даны ниже в разделе «Локальные функции и процедуры»). Вне процедуры Test переменная X, которая объявлена в строке 37, не существует. Эта переменная имеет локальную область действия. Точно так же объявление переменной X на строке 33 является локальным по отношению к методу Button1Click. Вне этого метода переменная, объявленная в строке 33, не существует. (Если бы в процедуре Test не было объявления Х в строке 37, то она изменяла бы значение переменной Х, объявленной в строке 33 !!!).

Теперь рассмотрим область действия переменной X, объявленной в разделе implementation. Эта переменная видима из любого места модуля. Раз так, то она «видна» и в методе Button1Click. Но в этом методе переменная Х объявляется дважды. Возникает вопрос, а какое же из трех объявлений переменной Х должно использоваться? Ответ: действует локальное объявление в методе Button1Click, и локальное объявление в процедуре Test. Иными словами, действует то объявление, которое «ближе к телу».

Итак, переменная X, которая объявлена в разделе implementation модуля, доступна всюду в этом модуле. Локальная переменная с таким же именем «главнее» переменной модуля. Но что если внутри метода Button1Click нам понадобится переменная модуля, а не локальная? В этом случае переменную модуля необходимо квалифицировать. Строка 50 листинга 2.1 содержит:

Memo1.Lines.Add(Global X:  + IntToStr(ScopeU.X));

Нетрудно видеть, что квалификатором переменной X является имя модуля (ScopeU). Квалификация переменной именем модуля буквально означает «Дайте мне переменную X модуля, а не локальную переменную X ». Символом присоединения квалификатора является точка (.).

Как было сказано выше, переменная X, объявленная в разделе implementation, доступна в любом месте модуля. Если необходимо чтобы переменная была доступной в других модулях, ее объявление следует разместить в разделе interface. (На листинге 2.1 такой переменной является Form1). Любая переменная, объявленная в разделе interface доступна в любом модуле проекта. Такие переменные часто называют глобальными. Чтобы сделать глобальные переменные, объявленные в некотором модуле, достаточно добавит этот модуль с список uses. Если в двух разных модулях объявлены переменные с одинаковыми именами, для исключения неоднозначности надо применять квалификаторы так, как это показано выше.

ПРИМЕЧАНИЕ. На самом деле, определение глобальной переменной не совсем корректно. Действительно, переменные из раздела интерфейса модуля становятся доступны только после того как соответствующее имя появится в списке uses. Использовать такие переменные автоматически мы не можем. Истинно глобальной является только такая переменная, которая доступна всегда, везде и без ссылок на модули в списке uses. Delphi имеет несколько стандартных глобальных переменных, но мы не можем создавать такие переменные.

Записи

Записи являются еще одним типом структурированных данных, поддерживаемым языком Object Pascal.

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

Пример записи

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

Для объявления записи применяется ключевое слово record:

type

TCDRecord = record

Number : integer; // Порядковый номер CD в коллекции

Title : string; // Наименование

Artist : string; // Исполнитель

Year : integer; // Год выпуска

Recorded: string; // Фирма-производитель

Price : double; // Стоимость

Market : string; // Где куплен

end;

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

ПРИМЕЧАНИЕ. К объявлению записи в этом примере следует относиться только как к примеру. Так, запись с полями типа «длинная строка» не подходит для записи в файл. Если предполагается хранить записи в файлах, все записи должны иметь одинаковый размер ( в байтах). В данном же случае никто не может гарантировать то, что все записи сведений о коллекции CD будут иметь одну и ту же длину. При создании записей, которые предполагается записывать в файлы, поля строкового типа следует объявлять либо как короткие строки, либо как массивы символов (array of Char). Более подробно об этом говорится в разделе «Обработка двоичных данных».

После того как тип записи объявлен, можно работать с переменными – экземплярами:

var

CD : TCDRecord;

Теперь можно присваивать значения полям переменной CD:

CD.Number :=1;

CD.Title :=’Aftermath’;

CD.Artist :=’The Rolling Stones’;

CD.Year := 1986;

CD.Recorded:=’ABCKO Music and Records,Inc’;

CD.Price := 80;

CD.Market := ’Горбушка’;

Здесь мы видим что поле, относящееся к переменной–записи, обозначается идентификатором, состоящим из имени переменной, точки и имени поля.

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

Label1.Caption := CD.Recorded;

Синтаксис оператора record имеет вид

name = record

field_1 : data_type;

field_2 : data_type;

.

.

.

field_n : data_type;

end;

Оператор with

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

CD.Number :=1;

CD.Title :=’Aftermath’;

CD.Artist :=’The Rolling Stones’;

CD.Year := 1986;

CD.Recorded:=’ABCKO Music and Records,Inc’;

CD.Price := 80;

CD.Market := ’Горбушка’;

С помощью оператора with этот код можно несколько упростить:

with CD do

begin

Number :=1;

Title :=’Aftermath’;

Artist :=’The Rolling Stones’;

Year := 1986;

Recorded:=’ABCKO Music and Records,Inc’;

Price := 80;

Market := ’Горбушка’;

end;

Оператор with присоединяет квалификатор (имя записи) по умолчанию к блоку кода. Все, что находится внутри begin – end рассматривается как собственность записи CD.

Массивы записей

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

var

MyCDs : array [0..9] of TCDRecord;

begin

MyCDs[0].Number :=1;

MyCDs[0].Title :=’Aftermath’;

MyCDs[0].Artist :=’The Rolling Stones’;

MyCDs[0].Year := 1986;

MyCDs[0].Recorded:=’ABCKO Music and Records,Inc’;

MyCDs[0].Price := 80;

MyCDs[0].Market := ’Горбушка’;

MyCDs[1].Number := 2;

MyCDs[1].Title := ’Between the buttons’;

MyCDs[1].Artist := ’The Rolling Stones’;

MyCDs[2].Number := 3;

MyCDs[2].Title := ’Willage Green Preservation Society’;

MyCDs[2].Artist := ’The Kinks’;

Label1.Caption := MyCDs[2].Artist;

{ Продолжение . . . }

end;

Это выглядит чуть сложнее, чем в случае массива целого или вещественного типа. Так, индекс следует за именем массива MyCDs, а затем – точка и имя поля.

Включаемый файл

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

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

Листинг2.2. Файл TEST.INC.

const

DefWidth = 500;

DefHeight = 300;

type

TCDRecord = record

Number : integer; // Порядковый номер CD в коллекции

Title : string; // Наименование

Artist : string; // Исполнитель

Year : integer; // Год выпуска

Recorded: string; // Фирма-производитель

Price : double; // Стоимость

Market : string; // Где куплен

end;

Чтобы создать включаемый файл, в главном меню Delphi надо выбрать File | New и затем дважды щелкнуть на пиктограмме Text диалога New Items. В результате откроется чистая страница редактора кода. Введите текст и сохраните файл под любым именем, выбрав в меню File | Save As. . .. Не забудьте при этом задать расширение имени INC, – иначе по умолчанию будет принято расширение TXT.

Чтобы включить внешний файл в текст модуля, необходимо воспользоваться директивой компилятора $I так как это показано в следующем примере:

unit Unit2;

interface

uses

Windows, Messages, SysUtils, Classes,StdCtrls;

{$I Test.inc}

{ ... остальной текст модуля }

Директива $I дает указание компилятору взять исходный текст из файла TEST.INC и заменить им саму директиву. Можно считать, что действие директивы $I подобно копированию фрагмента текста из одного файла в буфер обмена и вставке содержимого буфера в результирующий файл.

Необходимо следить чтобы включаемый файл был синтаксически корректным. В противном случае компилятор зафиксирует ошибку.

Подпрограммы

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

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

Процедуры и функции

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

Функции и процедуры называют подпрограммами. Простейшие подпрограммы не имеют параметров (исходных данных) и не возвращают результат. Другие подпрограммы могут иметь несколько параметров и возвращают результат.

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

Новый термин. Функция – это секция автономного по отношению к главной программе кода, которая выполняет некоторые действия и возвращает результат.

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

Структура объявления функции имеет следующий вид:

// Заголовок функции

function SomeFunction(parameter_list):Type_of_result;

{ Объявления типов данных,констант,переменных}

{ Определения функций и процедур}

begin

{ Код, реализующий алгоритм вычисления результата}

Result := <значение типа Type_of_result>;

end;

Нетрудно видеть, что функция в чем–то сходна с модулем. Так, она может содержать объявления типов, констант, переменных, процедур и функций. Однако, эти объявления имеют локальный характер. Их область действия – блок кода, ограниченный ключевыми словами begin – end.

Список параметров (parameter_list) в заголовке функции перечисляет ее аргументы, но он не обязателен: функция может и не иметь параметров.

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

Result := <значение типа Type_of_result>;

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

Структура объявления процедуры имеет следующий вид:

// Заголовок процедуры

procedure SomeProcedure(parameter_list);

{ Объявления типов данных,констант,переменных}

{ Определения функций и процедур}

begin

{ Код, реализующий алгоритм вычислений}

end;

Упражнение. Рассмотрим пример определения функции. Начните новый проект и выполните следующие действия:

1. Поместите на форму кнопку и надпись.

2. Дважды щелкните на кнопке формы чтобы создать обработчик события OnClick.

3. Поместите курсор редактора кода перед обработчиком события и напечатайте следующий текст:

function Multiply(Num1, Num2 : Integer) : Integer;

begin

Result := Num1 * Num2;

end;

4. Придайте обработчику события OnClick следующий вид:

procedure TForm1.Button1Click(Sender: TObject);

var

X : Integer;

begin

X := Multiply(10, 20);

Label1.Caption := IntToStr(X);

end;

Запустите программу и нажмите кнопку. Надпись отобразит число 200.

Эта программа работает так. В ответ на нажатие кнопки вызывается подпрограмма – обработчик события Button1Click. Обработчик события вызывает функцию Multiply и передает ей два параметра – числа 10 и 20. Результат вычислений запоминается в переменной Х и отображается в надписи.

ПРИМЕЧАНИЕ. Этот пример иллюстрирует применение автономной функции в программе Delphi. Обычно такого рода функции включают в состав класса главной формы приложения. Об этом будет сказано ниже.

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

function Multiply(Num1, Num2 : Integer) : Integer;

begin

Result := Num1 * Num2;

end;

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

К функции Multiply можно обращаться по-разному. Так, ее фактическими аргументами могут быть переменные, константы или выражения:

X := Multiply(2,5); {аргументы – константы }

Y := Multiply(A,B); {аргументы - переменные }

{ аргумент – квадрат переменной, а сама функция есть аргумент другой функции :}

Label1.Caption := IntToStr(Multiply(X, Sqr(Y));

Язык Object Pascal разрешает игнорировать результат функции. Так, абсолютно корректен следующий оператор:

Multiply(X,Y); { результат игнорируется }

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

Объявление и определение подпрограммы

Здесь мы рассмотрим два важных понятия, связанных с подпрограммами – объявление и определение. Так, подпрограммы должны быть всегда определены. Их объявления – не обязательны.

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

Подпрограмму надо объявлять когда она:

  • будет использоваться в других модулях

  • является методом класса.

Новый термин. Определение подпрограммы – это код, реализующий алгоритм процедуры или функции. Определения подпрограмм размещают в секции implementation.

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

unit Unit1;

interface

{ часть исходного кода опущена... }

function Multiply(Num1, Num2 : Integer) : Integer;

implementation

procedure TForm1.Button1Click(Sender: TObject);

var

X : Integer;

begin

X := Multiply(10, 20);

end;

function Multiply(Num1, Num2 : Integer) : Integer;

begin

Result := Num1 * Num2;

end;

end.

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

ПРИМЕЧАНИЕ Если вы объявили функцию, но не дали ее определения, компилятор выдаст ошибку вида

Unsatisfied forward or external declaration: 'Multiply.'

Параметры подпрограмм

Здесь мы определим важное понятие о способе передачи параметров в подпрограммы.

Любой параметр подпрограммы может передаваться ей как

  • значение;

  • переменная;

  • константа.

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

function SquareAndMultiply(Num1, Num2 : Integer) : Integer;

begin

Num1 := Num1 * Num1;

Num2 := Num2 * Num2;

Result := Num1 * Num2;

end;

Код, тестирующий данную функцию, имеет вид:

procedure TForm1.Button1Click(Sender: TObject);

var

X : Integer;

Y : Integer;

Z : Integer;

begin

X := 2;

Y := 3;

Z := SquareAndMultiply(X, Y);

Label1.Caption := IntToStr(Z);

end;

Протестируйте этот код самостоятельно. Два значения передаются в функцию SquareAndMultiply. Оба значения изменяются внутри функции SquareAndMultiply. Однако, при возврате из функции переменные X и Y не изменяются. Если функция использует параметр–значение, компилятор создает его копию и передает ее в функцию. Оригинал параметра в подпрограмме недоступен и поэтому остается неизменным.

Параметры – константы. Параметр–константу нельзя изменять в теле подпрограммы. Ниже дан пример процедуры с параметром–константой:

procedure SaySomething(const S : string);

begin

S := S + `Test';

ShowMessage(S);

end;

В тексте этой подпрограммы имеется ошибка. Компилятор зафиксирует ошибку в первом операторе с сообщением «Left side cannot be assigned to ». Ошибка заключается в том, что формальный параметр S объявлен константой, а значение константы изменить нельзя. Всегда объявляйте константами те параметры, которые в теле подпрограммы изменяться не должны. Кроме того, следует помнить, что вариант procedure SaySomething(const S : string) предпочтительней чем procedure SaySomething(S : string) даже тогда когда параметр не модифицируется. В первом случае компилятор не создает копию строки S и, тем самым, экономит память.

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

procedure Square(var Number : Integer);

begin

Number := Number * Number;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

X : Integer;

begin

X := 20;

Square(X);

Label1.Caption := IntToStr(X);

end;

Параметру–переменной предшествует ключевое слово var.

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

Подпрограмма Square модифицирует свой параметр, умножая его на самого себя. В коде метода Button1Click эта подпрограмма вызывается и ей передается параметр–переменная метода X, которой присвоено значение 20. В результате выполнения кода Square переменная X получит значение 400. Так как параметр передается по наименованию (в нашем случае это переменная X), процедура «имеет право» изменить его первоначальное значение. Всегда используйте параметры–переменные когда процедура или функция должна изменять их значения. Тот факт, что некоторый объект может быть изменен процедурой или функцией, является весьма важным аспектом параметров–переменных.

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

Square(30);

При компиляции такой строки будет зафиксирована ошибка. Действительно, здесь мы имеем явное противоречие: как можно заменить явное значение 20 другим?

Следующий код тоже некорректен:

var

X : Word;

begin

X := 20;

Square(X);

. . . . . . . . . . . .

В данном случае имя X объявлено как переменная типа Word, а формальный параметр процедуры Square объявлен Integer. Компилятор сообщит об этом так: «Types of actual and formal var parameters must be identical » т.е. типы фактических и формальных параметров–переменных должны быть одинаковы.

ПРИМЕЧАНИЕ: Функция может вернуть только одно значение – результат. Используя параметры–переменные можно получить от функции побочные результаты. В точку вызова функции возвращается только один результат, но объекты, которые ей переданы по наименованию, будут изменены.

Локальные процедуры и функции

Локальной функцией или процедурой называется подпрограмма, которая описана внутри другой подпрограммы. Рассмотрим пример:

procedure TForm1.Button1Click(Sender:TObject);

var

X : Integer;

{ Локальная процедура }

procedure Test;

begin

Memo1.Lines.Add(Локальная процедура, X =  + IntToStr(X));

end;

begin

X := 100;

Memo1.Lines.Clear;

Memo1.Lines.Add(Основная процедура, X =  + IntToStr(X));

Test;

end;

Обратите внимание на то, что описание процедуры Test находится в разделе объявления переменных метода Button1Click. Процедура Test называется локальной постольку поскольку ее описанием владеет другая процедура. Локальная процедура доступна только ее владельцу.

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

Основная процедура, X = 100

Локальная процедура, X = 100

Перегрузка подпрограмм

Часто возникает необходимость выполнения одних и тех же действий над данными различных типов. Например, стандартная функция извлечения квадратного корня Sqrt возвращает значение, тип которого совпадает с типом аргумента целого или вещественного. Object Pascal, начиная с Delphi 4, предоставляет разработчикам объявлять одноименные функции и процедуры, принимающие аргументы различных типов. Это называется перегрузкой функций.

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

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

Благодаря перегрузке, объявления соответствующих функций принимают вид:

function Multiply(Num1, Num2 : Integer) : Integer; overload;

function Multiply(Num1, Num2 : Double) : Double; overload;

function Multiply(Num1, Num2 : Word) : Word; overload;

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

implementation

function Multiply(Num1, Num2 : Integer) : Integer; overload;

begin

Result := Num1 * Num2;

end;

function Multiply(Num1, Num2 : Double) : Double; overload

begin

Result := Num1 * Num2;

end;

function Multiply(Num1, Num2 : Word) : Word; overload;

begin

Result := Num1 * Num2;

end;

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

var

X, Y, Z : Double;

begin

X := 1.5;

Y := 10.5;

Z := Multiply(X, Y);

end;

будет вызвана та версия функции Multiply, которая «принимает» параметры типа Double.

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

function DoSomething : Integer; overload;

function DoSomething : Word; overload;

В данной ситуации компилятор выдаст сообщение «Declaration of DoSomething differs from previous declaration ».

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

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

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

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

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

// 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

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

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

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

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

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

Файл 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;

Методы

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

Public – методы и свойства образуют пользовательский интерфейс класса. Только через public – методы и свойства пользователю доступны функциональные возможности класса. Допустим, что нам надо спроектировать класс, инкапсулирующий задачу записи и воспроизведения звуковых файлов. Интерфейс такого класса мог бы включать такие методы как Open, Play, Record, Save, Rewind и т.п.

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

Protected – методы и свойства не доступны в прикладной программе. В этом они сходны с private. Однако, при проектировании новых классов на основе существующих (т.е. при наследовании) такие методы доступны разработчику. Механизм наследования рассматривается ниже.

В Object Pascal существует понятие «классовый метод». В отличие от метода класса, классовый метод – это обычная подпрограмма, объявление которой включено в объявление данного класса. Классовые методы можно рассматривать как те процедуры и функции, которые описывают действия над экземплярами данного и (или) родственных классов, а не над полями данного класса. Так, ни поля, ни методы класса не доступны в классовом методе. Если например, мы создаем класс «матрица», то процедуры действий над матрицами – сложение, умножение и т.п. целесообразно объявить классовыми методами, ибо они выполняют операции над цельными объектами, а не над их частями. Для обращения к классовому методу в программе необходимо указать вместо имени экземпляра (объекта) указать имя класса. Отметим, что аналогичный синтаксис используется для вызова конструктора.

Указатель Self

Новый термин. Все классы имеют скрытое поле с именем Self. Self – это указатель на «свой» экземпляр в памяти.

Поясним смысл Self на примере, показывающем как бы выглядело объявление класса TMyRect , если поле Self объявить явно:

TMyRect = class

private

Self : TMyRect;

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;

Точно так компилятор и «видит» объявление любого класса. На самом деле компилятор сам добавляет поле Self к любому объекту. При создании объекта указатель Self инициализируется автоматически – в него заносится адрес структуры данных класса:

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

{ Теперь Rect и Rect.Self имеют одинаковые значения }

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

Зачем же нам может понадобится поле Self? Вспомним, что каждый объект имеет собственную копию (экземпляр) полей в памяти. В то же время, все объекты одного и того же класса используют один и тот же исполняемый код методов. Иными словами, несколько экземпляров данных разделяют одни и те же методы. Компилятор всегда использует поле Self чтобы однозначно передать в метод те данные (т.е. – поля), которые принадлежат конкретному экземпляру класса – объекту. Рассмотрим, например такую версию описания метода GetWidth:

function TMyRect.GetWidth : Integer;

begin

Result := Right - Left;

end;

Именно так ее «видит» программист. Компилятор, однако интерпретирует этот текст по–своему, а именно:

function TMyRect.GetWidth : Integer;

begin

Result := Self.Right - Self.Left;

end;

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

ПРЕДУПРЕЖДЕНИЕ: Никогда не изменяйте значение поля Self. Его можно использовать как указатель на объект класса в памяти или передавать в качестве фактического параметра другому объекту. Случайное (преднамеренное) изменение поля Self может привести к непредсказуемым последствиям. Это поле предназначено только для чтения.

Хотя Self – это «боец невидимого фронта», его можно эффективно использовать в программах. Например, рассмотрим как можно поместить на форму компонент во время выполнения программы, а не во время разработки. Этот пример иллюстрирует наиболее типичный случай использования указателя Self в программах Delphi.

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

В рамках VCL владельцем (Owner) любой компоненты формы является форма. Но некоторая группа компонент, в свою очередь, может принадлежать и другому компоненту – элементу интерфейса Windows, который называется «родителем» (Parent).

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

Код обработчика соответствующего события мог бы иметь такой вид:

procedure TForm1.Button1Click(Sender: TObject);

var

Button : TButton;

begin

Button := TButton.Create(Self);

Button.Parent := Self;

Button.Left := 20;

Button.Top := 20;

Button.Caption := 'Click Me';

end;

В данном коде Self передается конструктору как фактический параметр (тем самым, свойство Owner новой кнопки будет указывать на форму, которой принадлежит кнопка). Далее свойству Parent созданной кнопки мы также присваиваем указатель на данную форму, поскольку другого группирующего компонента нет.

ПРИМЕЧАНИЕ: Выше было сказано, что классовые методы не имеют доступа к полям и методам класса. Это означает, что классовому методу ( в отличие от методов класса) недоступен указатель Self.

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

Ниже приведен текст модуля, содержащий описание класса TAirplane. Класс TAirplane инкапсулирует задачу управления неким самолетом. Объекты этого класса можно было бы использовать в программе–симуляторе управления движением самолетов различного типа. Управление осуществляется посылкой «пилоту» той или иной команды из числа заранее оговоренных. В данном случае это команды «взлетайте», «садитесь», «измените (курс, высоту, скорость полета)».

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

unit AirplanU;

interface

uses

SysUtils;

const

{ Категории самолетов }

Airliner = 0; // трансконтинентальный авиалайнер

Commuter = 1; // для местных авиалиний

PrivateCraft = 2; // частный самолет

{ Константы состояния }

TakingOff = 0; // Взлетает

Cruising = 1; // В полете

Landing = 2; // Приземляется

OnRamp = 3; // На стоянке

{ Константы типов сообщений }

MsgChange = 0; // Изменить

MsgTakeOff = 1; // Взлететь

MsgLand = 2; // Приземлиться

MsgReport = 3; // Отрапортовать

type

TAirplane = class

private

Name : string; // Идентификатор самолета

Speed : Integer; // Скорость

Altitude : Integer; // Высота

Heading : Integer; // Курс

Status : Integer; // Состояние

Kind : Integer; // Тип

Ceiling : Integer; // Потолок

protected

procedure TakeOff(Dir : Integer); virtual;

procedure Land; virtual;

public

constructor Create(AName:string;AKind:Integer = Airliner);

function SendMessage(Msg:Integer; var Response : string;

Spd : Integer; Dir : Integer; Alt : Integer):Boolean;

function GetStatus(var StatusString:string):Integer;

overload; virtual;

function GetStatus : Integer; overload;

function GetSpeed : Integer;

function GetHeading : Integer;

function GetAltitude : Integer;

function GetName : string;

end;

implementation

constructor TAirplane.Create(AName : string; AKind : Integer);

begin

// Создает экземляр самолета типа AKind

// AName – идентификационный индекс самолета

// По умолчанию самолет находится на стоянке

inherited Create;

Name := AName;

Kind := AKind;

Status := OnRamp;

// Установим потолок (Ceiling) в зависимости от типа c-та

case Kind of

Airliner : Ceiling := 15000; //км

Commuter : Ceiling := 10000; //км

PrivateCraft : Ceiling := 5000; //км

end;

end;

procedure TAirplane.TakeOff(Dir : Integer);

// Реализует команду «Взлететь и лечь на курс Dir»

begin

Heading := Dir;

Status := TakingOff;

end;

procedure TAirplane.Land;

// Реализует команду «Посадка»

begin

Speed := 0;

Heading := 0;

Altitude := 0;

Status := OnRamp;

end;

function TAirplane.SendMessage(Msg:Integer;varResponse:

string; Spd:Integer; Dir:Integer; Alt:Integer):Boolean;

begin

Result := True;

{ Do something based on which command was sent. }

case Msg of

MsgTakeOff :

{ На взлет не имеет смысла, если я уже в воздухе! }

if status <> OnRamp then

begin

Response := Name + ': А я и так уже в воздухе!';

Result := False;

end else

TakeOff(dir);

MsgChange :

begin

{ Отсеивание неверных команд на изменение параметров}

if Spd > 500 then

Response:='Ошибка : Скорость больше 500.';

if Dir > 360 then

Response := Ошибка:Курс больше 360 градусов.';

if Alt < 100 then

Response := Name + ': Разобьюсь!';

if Alt > Ceiling then

Response := Name + ': Не могу забраться так высоко';

if (Spd = 0) and (Dir = 0) and (Alt = 0) then

Response := Name + ': Не понял';

if Response <> '' then

begin

Result := False;

Exit;

end;

{ Невозможно изменить состояние если ЛА на земле }

if status = OnRamp then

begin

Response := Name + ': Я еще на земле.';

Result := False;

end else

begin

Speed := Spd;

Heading := Dir;

Altitude := Alt;

Status := Cruising;

end;

end;

MsgLand :

{ Невозможно приземлиться, если и так на земле }

if status = OnRamp then

begin

Response := Name + ': Я еще на поле.';

Result := False;

end else

Land;

MsgReport :

begin

GetStatus(Response);

Exit;

end;

end;

{ Стандартная реакция, если все идет нормально. }

if Result then

Response := Name + ': Все нормально';

end;

function TAirplane.GetStatus(var StatusString:string):Integer;

begin

StatusString := Format('%s, Высота: %d, Курс: %d, ' +

'Скорость: %d', [Name, Altitude, Heading, Speed]);

Result := Status;

end;

function TAirplane.GetStatus : Integer;

begin

Result := Status;

end;

function TAirplane.GetSpeed : Integer;

begin

Result := Speed;

end;

function TAirplane.GetHeading : Integer;

begin

Result := Heading;

end;

function TAirplane.GetAltitude : Integer;

begin

Result := Altitude;

end;

function TAirplane.GetName : string;

begin

Result := Name;

end;

end.

Анализ объявления и реализации

Численное описание (т.е. данные) класса представлено следующими полями

Имя поля

Тип

Описание

Name

string

Уникальный идентификационный код самолета. В Российской фелерации гражданские самолеты имеют идентификацию вида RA-XXYYYY, где ХХ – код фирмы-разработчика, YYYY – серийный номер. Например, идентификационный код ИЛ-96-300 с серийным номером 7 будет RA-96007.

Speed

Integer

Текущая скорость полета, выраженная в км/час.

Altitude

Integer

Текущая высота полета, выраженная в метрах. Не может быть меньше 50м или больше чем потолок для данного типа самолета.

Heading

Integer

Курс. Может принимать значения от 0 до 360 градусов

Status

Integer

Состояние. Может принимать значение одной из следующих констант TakingOff, Cruising, Landing, OnRamp.

Kind

Integer

Тип самолета. Может принимать значение одной из следующих констант Airliner, Commiter, PrivateCraft

Ceiling

Integer

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

При управлении воздушным движением диспетчер дает «пилоту» команды. Команда может быть выполнена если не противоречит текущему состоянию самолета. Например, нельзя посадить самолет, который еще не взлетел. Факт выполнения команды фиксируется изменением значений полей Speed, Altitude, Heading и Status. Все поля скрытые. В данном случае необходимость сокрытия полей от пользователя очевидна. Действительно, диспетчер, как пользователь объекта рассматриваемого класса, сам не может изменить ни скорость, ни высоту полета самолета. Он это делает через посредника – пилота, который держит «рычаги управления». В рамках нашей абстракции роль пилота будет выполнять специальный метод, который имеет право изменять состояние и параметры движения самолета.

Класс TAirplane имеет 2 скрытых и 8 публичных методов. Сначала рассмотрим функции публичных методов в порядке их появления в объявлении класса.

Конструктор класса TAirplane инициализирует объект как некий самолет типа AKind c уникальным идентификатором AName. Так как первым вызывается конструктор предка (в данном случае предком является класс TObject), то все поля нашего класса очищаются, т.е. получают нулевые значения. По умолчанию (т.е. при создании объекта) считается, что самолет находится на земле. Далее в зависимости от типа самолета устанавливается потолок в метрах.

Метод SendMessage выполняет роль пилота – посредника между диспетчером и самолетом. Его функция – выполнить команду диспетчера, если ее смысл не противоречит текущему состоянию самолета. Данный метод имеет 4 входных параметра, один выходной и результат исполнения функции булевского типа.

Входными параметрами являются:

Msg – код команды диспетчера. Возможные коды команд предопределены константами с именами MsgXXXX в разделе const модуля AirplanU;

Spd – новое значение скорости полета;

Dir – новый курс;

Alt – новый эшелон (высота полета).

Выходной параметр метода – строка Response. Сюда заносится текст «реакции» пилота на команду диспетчера.

Метод с помощью оператора case анализирует код команды и выполняет соответствующие действия. Читателю предлагается разобраться в логике этого метода самостоятельно. Обратим внимание на то что здесь используются два защищенных метода TakeOff и Land, реализующие правила взлета и посадки.

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

«RA–96007 Высота: 10000 Курс: 129 Скорость: 950».

Во втором случае (т.е. когда при обращении к GetStatus параметры не указаны), метод – функция просто вернет текущее значение поля Status.

Смысл остальных методов GetXXXX очевиден.

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

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

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

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

Базовый класс называется родительским или предком. Производный класс называется дочерним или наследником.

Для иллюстрации принципов наследования обратимся к объявлению класса TAirplane и создадим на его основе класс самолетов военного назначения:

TMilitaryPlane = class(TAirplane)

private

TheMission : TMission;

constructor Create(AName:string; AType:Integer);

function GetStatus(varStatusString:string):Integer;override;

protected

procedure TakeOff(Dir:Integer); override;

procedure Land; override;

procedure Attack; virtual;

procedure SetMission; virtual;

end;

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

ПРИМЕЧАНИЕ. Производный (дочерний) класс автоматически получает «в наследство» все поля и методы предка. Можно добавить новые поля и методы т.е. все то, что необходимо для придания классу новых функциональных возможностей. Удалять существующие поля и методы нельзя.

Здесь мы добавили скрытое поле – объект класса TMission. Класс TMission здесь не показан, но в него можно инкапсулировать все, что связано с понятием «боевая задача»: цель, ориентиры, пути подхода и отхода, высоты, курсы и т.д. Тем самым мы показали, что полем класса может быть объект другого (или даже этого же) класса.

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

Сначала рассмотрим механизм виртуальных методов на примере метода TakeOff. Процедура SendMessage родительского класса обращается к методу TakeOff в ответ на команду – сообщение MsgTakeOff. Если дочерний класс TMilitaryPlane не имеет собственного метода TakeOff, то унаследованная процедура SendMessage будет по–прежнему вызывать метод TakeOff родителя. Но в нашем случае класс TMilitaryPlane имеет собственный метод TakeOff, который перекрывает (override) соответствующий виртуальный метод предка. Поэтому процедура SendMessage, несмотря на то что объявлена и описана в родительском классе, будет вызывать тот метод, который фактически принадлежит дочернему классу. В данном случае это будет метод TakeOff класса TMilitaryPlane.

Новый термин. Замещение метода базового класса одноименным методом дочернего класса называется перекрытием метода.

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

Характеристики

Тип файла
Документ
Размер
599,5 Kb
Тип материала
Высшее учебное заведение

Список файлов учебной работы

Свежие статьи
Популярно сейчас
Зачем заказывать выполнение своего задания, если оно уже было выполнено много много раз? Его можно просто купить или даже скачать бесплатно на СтудИзбе. Найдите нужный учебный материал у нас!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
6302
Авторов
на СтудИзбе
313
Средний доход
с одного платного файла
Обучение Подробнее