Главная » Free Pascal » Программирование с объектами Free Pascal

0

В этом разделе демонстрируются некоторые идеи объектно-ориентированного программирования, реализованные в режиме Object Pascal extension on, который устанавливается с помощью команды Options      Compiler.

Объект представляет собой сложный тип данных, состоящий из некоторого

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

Для того чтобы пользователь не смог совершить какие-либо несанкциониро- ванные операции над содержимым полей в переменной-объекте, прямой доступ к этим полям обычно блокируют. Для этой цели в описании объекта выделяют раз- дел общедоступных полей и подпрограмм, предваряемый служебным словом pub- lic (публичный, общедоступный), и раздел внутренних данных и служебных под- программ, не доступных для внешнего пользователя. Последний раздел идентифицируется служебным словом private (частный, секретный). Доступ к за- крытым таким образом полям внешний пользователь может получить только через служебные подпрограммы, в которых неправомерное изменение соответствующих значений пресекается. Такое разделение данных и программ на доступные и недос- тупные появилось еще в модулях. Поэтому модули особенно удобны для размеще- ния описания объектов.

Мы продемонстрируем процесс описания объекта на примере внедрения дан- ных типа рациональных дробей и создания библиотеки операций над ними. Анало- гичная работа была проделана ранее в этой главе по созданию модуля Rational. Однако использование технологии объектов позволит более естественно програм- мировать операции над дробями. Если в предыдущем модуле, например, для сло- жения двух дробей мы вызывали функцию Radd:

z:=Radd(x,y);

то привлечение объектов позволит нам ту же операцию записывать в виде:

z:=x+y;

Сохраняем за модулем прежнее название и помещаем в раздел интерфейса за- головочную часть описания объекта (листинг 11.3).

   Листинг 1 1 .3 .  Модуль  Rational с  использованием объектов.  Раздел  INTERFACE      

unit Rational;

// Компилировать в режиме Object Pascal extension on INTERFACE

type

Ratio = object

private

n,d: integer;

public

procedure  Rout(s:string); procedure  Rin(s:string); constructor Init; constructor Init(x:Ratio);

constructor Init(n1,d1:integer);

end;

operator + (x,y:Ratio):Ratio; operator – (x,y:Ratio):Ratio; operator * (x,y:Ratio):Ratio; operator / (x,y:Ratio):Ratio; function DtoR(v:double):Ratio; function RtoD(x:Ratio):Double;

Собственно заголовок описания объекта расположен между служебными сло- вами type и end. Начинается он с имени типа объекта Ratio, сопровождаемого служебным словом object. Далее описаны два целочисленных поля с именами n (от англ. numerator — числитель) и d (от англ. denominator — знаменатель), прямой доступ к которым запрещен. Вслед за служебным словом public расположены пять строк с заголовками специальных подпрограмм, именуемых методами. Если к предыдущим подпрограммам ввода/вывода мы обращались следующим образом:

Rin(‘x=’,x);

Rout(‘z=’,z);

то к заменившим их методам нужно обращаться немного по-другому:

x.Rin(‘x=’);

z.Rout(‘z=’);

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

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

var

x: Ratio;   // объявление переменной-объекта

x.Init;     // инициализация полей в переменной-объекте

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

y.Init(x);   // значения полей x копируются в поля y

В третьем типе конструктора его аргументы в явном виде представляют значе- ния числителя и знаменателя. Дополнительная задача этого конструктора заключа- ется в блокировке попытки задать нулевой знаменатель.

Вслед за заголовком объекта расположены заголовки некоторых подпрограмм, включенных в состав средств, обслуживающих объект. Первые четыре из них, на- чинающиеся со служебного слова operator, — специальные функции, используе- мые для переопределения операций над переменными-объектами. Данные типа ра- циональных дробей не входят в перечень объектов, "знакомых" компилятору. Его следует научить, как надо выполнять ту или иную арифметическую операцию над данными, придуманными пользователем. Именно с этой задачей справляются operator-функции, реализация которых будет представлена в разделе IMPLEMENTATION. Кроме компилятора никто другой к этим функциям обращаться не будет, поэтому для их заголовков придуман особый формат.

Зато две следующие подпрограммы представлены обычными функциями пре- образования данных типа Ratio в вещественные значения (RtoD) и преобразова- ниями вещественных данных в дробно-рациональное представление (DtoR).

Раздел реализации начинается со служебных функций gcd и reduce, уже знако-

мых нам по предыдущей версии модуля Rational. Вслед за ними приведена преж- няя реализация функций конвертирования данных (листинг 11.4).

   Листинг 1 1 .4 .  Модуль  Rational с  использованием объектов.  Раздел  IMPLEMENTATION

IMPLEMENTATION

// Вычисление НОД

function gcd(x,y:integer):integer;

begin

if y=0 then begin Result:=x; exit; end; Result:=gcd(y,x mod y);

end;

// Сокращение дроби

procedure reduce(var x:Ratio); var

n1,d1:integer; begin

n1:=abs(x.n);

if n1=0 then begin x.d:=1; exit; end; d1:=gcd(n1,x.d);

if d1>1 then begin x.n:=x.n div d1; x.d:=x.d div d1;

end;

end;

// Преобразования между Double и Ratio function DtoR(v:double):Ratio;

begin

Result.n:=round(v*9e7); Result.d:=90000000; reduce(Result);

end;

function RtoD(x:Ratio):Double; begin

Result := x.n / x.d;

end;

Пока что ничего нового в содержание предыдущих подпрограмм не внесено. Некоторые нюансы появляются в оформлении конструкторов. Для указания их принадлежности объекту с именем Ratio к имени конструктора добавлен соответ- ствующий префикс (листинг 11.5).

   Листинг 1 1 .5 .  М од ул ь  Rational с  ис пол ьзов а нием о бъ е к тов.  К онструк торы                      

// Конструкторы-инициализаторы объектов типа Ratio.

// Конструктор по умолчанию. constructor Ratio.Init();

begin

n:=0;  d:=1;

end;

// Конструктор копирования constructor Ratio.Init(x:Ratio); begin

n:=x.n; d:=x.d;

end;

// Конструктор по заданным числителю и знаменателю constructor Ratio.Init(n1,d1:integer);

begin

n:=n1;

d:=d1;

if d=0 then begin d:=1; exit;

end; reduce(Self);

end;

Так как задача любого конструктора — заполнить поля переменной-объекта нужными значениями, то ничего особенного в теле двух первых конструкторов нет. В третьем конструкторе предотвращается попытка присвоить знаменателю нулевое значение и появляется некоторое специальное имя Self (дословно — та самая пе- ременная-объект, над которой выполняется инициализация третьего типа). Дело в том, что при произвольно заданных значениях числителя и знаменателя не мешает произвести сокращение дроби, если такая возможность представится. Но для об- ращения к процедуре сокращения необходим аргумент типа Ratio. А среди аргу- ментов конструктора имя инициализируемой переменной отсутствует — при об- ращении к методам это имя выносится из списка параметров. Но компилятор-то знает, к какому объекту должен быть применен вызываемый метод. Именно этому объекту в момент вызова временно приписывается имя Self.

Поскольку подпрограммы ввода/вывода превратились в методы, то при их опи-

сании тоже пришлось приклеить префикс Ratio. Кроме того, после ввода значения дроби может понадобиться ее сокращение, поэтому в процедуре Rin пришлось прибегнуть к услугам переменной Self (листинг 11.6).

   Листинг 1 1 .6 .  М од ул ь  Rational с  ис пол ьзова нием о бъ е к тов.  М е тод ы                                 

// Метод объекта – вывод дроби procedure Ratio.Rout(s:string);

var

k:integer; begin

write(s); if n=0 then

begin writeln(‘0′); exit; end; k:=(n div abs(n))* d;

if k<0 then write(‘-‘); writeln(abs(n),’/’,abs(d));

end;

// Метод объекта – ввод дроби procedure Ratio.Rin(s:string); var

s1,s2:string[20];

i,j:integer; begin

d:=1;

write(s); readln(s1); i:=pos(‘/’,s1);  if i<>0 then begin

s2:=copy(s1,i+1,10); val(s2,d,j);

delete(s1,i,10); end;

if s1[1]=’-‘ then begin delete(s1,1,1);

val(s1,n,j);

n:=-n; end

else val(s1,n,j); reduce(Self);

end;

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

произвести сокращение результата. Единственная особая ситуация может возник- нуть в операции деления, когда числитель делителя равен нулю. В этом случае вы- водится сообщение об ошибке, и после нажатия клавиши <Enter> работа програм- мы завершается (листинг 11.7).

Листинг 11.7. Модуль Rational с использованием объектов. Операции

// Переопределение арифметических операций

operator + (x,y:Ratio):Ratio; begin

Result.n := x.n*y.d + x.d*y.n;

Result.d := x.d*y.d; reduce(Result);

end;

operator – (x,y:Ratio):Ratio; begin

Result.n := x.n*y.d – x.d*y.n;

Result.d := x.d*y.d; reduce(Result);

end;

operator * (x,y:Ratio):Ratio; begin

Result.n := x.n * y.n;

Result.d := x.d * y.d; reduce(Result);

end;

operator / (x,y:Ratio):Ratio; begin

if y.n=0 then begin

writeln(‘Деление на 0′); readln;

halt; end;

Result.n := x.n * y.d; Result.d := x.d * y.n; reduce(Result);

end;

Для тестирования описанного выше модуля использовалась программа из лис- тинга 11.8.

   Листинг 11.8. Те с т                                                                                                                              

program test_R; uses Rational; var

x,y,z:Ratio; v:double=0.75;

begin

x.Rin(‘x=’);

y.Init(1,3);

x.Rout(‘x=’);

y.Rout(‘y=’); z:=x+y; z.Rout(‘x+y=’); z:=x-y; z.Rout(‘x-y=’); z:=x*y; z.Rout(‘x*y=’); z:=x/y; z.Rout(‘x/y=’); z:=DtoR(v); z.Rout(‘0.75=’);

v:=RtoD(y); writeln(‘1/3=’,v:10:8); readln;

end.

Протокол ее работы таков:

Running "c:\fpc\2.2.4\bin\i386-win32\test_r.exe " x=4/6         // значение введено с клавиатуры x=2/3

y=1/3 x+y=1/1 x-y=1/3 x*y=2/9 x/y=2/1 0.75=3/4

1/3=0.33333333

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

быть построены производные классы-дети, которым по наследству передаются данные и методы. В порожденных классах объектов могут добавляться новые дан- ные и создаваться новые методы, переопределяться унаследованные процедуры и функции. Но язык Free Pascal в этом плане не может служить объектом подража- ния. В нем из-за совместимости с языком Object Pascal появилась веточка, порож- денная идеологией объектов. А попытка сохранить совместимость с Delphi потре- бовала нестандартного подхода на базе классов фирмы Borland. Оба эти подхода в какой-то мере похожи, но такой стройной теории, которая была, например, развита в языке C++, здесь не получилось. Вот и приходится, работая в среде FP IDE в од- ном режиме, пользоваться терминологией объектов, а в другом режиме — терми- нологией классов.

Источник: Кетков, Ю. Л., Свободное программное обеспечение. FREE PASCAL для студентов и школьников, Ю. Л. Кетков, А. Ю. Кетков. — СПб.: БХВ-Петербург, 2011. — 384 с.: ил. + CD-ROM — (ИиИКТ)

По теме:

  • Комментарии