М
И Н И СТ Е РСТ В О О Б РА ЗО В А Н И Я И Н А У К И
Ф
ИВ АНО КАФ
РО
ССИ Й СК О Й
Ф
Е Д Е РА Ц И И
Е Д Е РА Л Ь ...
29 downloads
180 Views
563KB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
М
И Н И СТ Е РСТ В О О Б РА ЗО В А Н И Я И Н А У К И
Ф
ИВ АНО КАФ
РО
ССИ Й СК О Й
Ф
Е Д Е РА Ц И И
Е Д Е РА Л Ь Н О Е А Г Е Н Т СТ В О ПО О Б РА ЗО В А Н И Ю В СК И Й Г О СУ Д А РСТ В Е Н Н Ы Й У Н И В Е РСИ Т Е Т
Е Д РА В Ы ЧИ СЛ И Т Е Л Ь Н О Й И ПРИ К Л А Д Н О Й М А Т Е М А Т И К И
РА ЗРА Б О Т К А К Л А ССО В Н А Я ЗЫ К Е OBJECT PASCAL
М етодическиеуказания к курс у компь ю терны х наук
И в анов о И з д ат ел ь с т в о«И в анов с кий гос уд арс т в енны й унив ерс ит ет » 2007
С о ста вител ь: д оц ент кафед ры в ы чис л ит ел ь ной и прикл ад ной м ат ем ат ики Е . В . Сокол ов
Прив од ят с я прим еры раз работ ки кл ас с ов на яз ы ке Object Pascal, яв л яющ ем с я час т ь ю с ред ы програм м иров ания Borland Delphi. О бс ужд ают с я т акие в опрос ы , как с оз д ание инт ерфейс а, реал из ац ия и ис пол ь з ов ание кл ас с ов -конт ейнеров , нас л ед ов ание кл ас с ов . Пред наз начает с я с туд ент ам 2 курс а м ат ем ат ичес когофакул ь т ет а, обучающ им с я пос пец иал ь нос т и/направ л ению «М ат ем ат ика» и направ л ению «М ат ем ат ика. Ком пь ют ерны е науки» .
П е ча та е тся по ре ш е н ию методиче ской комиссии ма тема тиче ског о фа кул ьтета Ива н овског ог о суда рстве н н ог о ун иве рсите та
Ре це н зе н т канд ид ат физ ико-м ат ем ат ичес ких наук, профес с ор Н . И . Я цкин
Ó И з д ат ел ь с т в о«И в анов с кий гос уд арс т в енны й унив ерс ит ет » , 2007
СО Д Е РЖ А Н И Е В ведение ...................................................................................................... 4 § 1. Б ибл иотеки подпрог рамм ................................................................. 5 Зад ача 1.1 ................................................................................................ 5 § 2. Прос тейш ие кл ассы -контейнеры .................................................. 12 Зад ача 2.1 .............................................................................................. Зад ача 2.2 .............................................................................................. Зад ача 2.3 .............................................................................................. Зад ача 2.4 ..............................................................................................
12 18 25 39
§ 3. У ниверсал ь ны е контейнеры .......................................................... 48 Зад ача 3.1 .............................................................................................. 50 § 4. И ерархии кл ассов ............................................................................. 58 Зад ача 4.1 .............................................................................................. 58 Зад ача 4.2 .............................................................................................. 64 В мес тозакл ю чения ................................................................................. 68 Прил ожение 1. Библ иот ека д л я работ ы с рац ионал ь ны м и чис л ам и ..... 70 Прил ожение 2. Кл ас с Number .................................................................. 73 Прил ожение 3. Кл ас с Rational .................................................................. 75
3
В В Е Д Е НИЕ Н ас тоящ ие м етод ичес кие указ ания прод ол жают пос обие ав тора [*] Вв ед ение в объект но-ориент иров анное програм м иров ание на яз ы ке Object Pascal. И в анов о: И в ГУ, 2005. О ни с од ержат прим еры решения з ад ач, ил л юс т рирующ ие понят ия и м етод ы програм м иров ания, рас с м от ренны е в перв ы х т рех час тях этого пос обия. В перв ом параграфе опис ы в ает с я проц ес с пос т роения библ иот еки под програм м д л я работ ы с ц ел очис л енны м и в екторам и фикс иров анной д л ины . О бс ужд ает с я раз работ ка инт ерфейс а библ иот еки, ее реал из ац ия, раз м ещ ение в нут ри м од ул я и ис пол ь з ов ание. Второй параграф с од ержит с ерию из чет ы рех з ад ач, в которы х с т роят с я кл ас с ы -конт ейнеры . О ни наз ы в ают с я т ак потом у, что с л ужат обол очкой д л я набора д анны х д ругих т ипов : чис ел , з апис ей, объектов и т. п. Конт ейнеры как нел ь з я л учше д ем онс т рируют ос обеннос т и и преим ущ ес т в а об ъе ктн ог о програм м иров ания. Зад ача 2.1 показ ы в ает, как преобраз ов ать с оз д анную ранее библ иотеку в кл ас с д л я пред с т ав л ения ц ел очис л енны х в екторов . Дал ее пол ученны й кл ас с пос тепенноус л ожняет с я. С начал а ком понент ам и в ектора с т анов ят с я з апис и, пред с т ав л яющ ие рац ионал ь ны е чис л а (з ад ача 2.2), з ат ем — объект ы кл ас с а Rational, с од ержател ь ноим еющ ие тот же с м ы с л (з ад ача 2.3). Н аконец , в з ад аче 2.4 с ним ает с я ограничение на д л ину в ектора. Дл я реал из ац ии пос л ед негокл ас с а ис пол ь з уют с я д инам ичес кие м ас с ив ы , необход им ы е с в ед ения окоторы х прив од ят с я прям ов ход е решения. В т рет ь ем параграфе опис анная с ерия прод ол жает с я з ад ачей пос т роения кл ас с а д л я пред с т ав л ения в екторов , ком понент ы которы х м огут им ет ь произ в ол ь ны й (почт и) т ип. Решение этой з ад ачи уже т ребует прием ов об ъе ктн о-орие н тирова н н ог о програм м иров ания. В час т нос т и, оноис пол ь з ует т акое с пец иал ь ное с ред с т в ояз ы ка Object Pascal, как кл ас с ов ы е с с ы л ки, объяв л ение, в нут ренняя с т рукт ура и ис пол ь з ов ание которы х рас с м ат рив ают с я в начал е параграфа. Н аконец , в чет в ертом параграфе с т роят с я д в а кл ас с а, анал огичны х Rational, объект ы которы х с л ужат д л я пред с т ав л ения ц ел ы х чис ел и ц в етов в форм ат е RGB. Зад ача 4.1 ил л юс т рирует раз работ ку произ в од ны х кл ас с ов и некоторы е в оз никающ ие при этом пробл ем ы . В з ад аче 4.2 указ анны е кл ас с ы ис пол ь з уют с я д л я того, чтобы в пол ном объем е прод ем онс т риров ат ь в оз м ожнос т и конт ейнера из з ад ачи 3.1, а т акже ис пол ь з ов ание кл ас с ов ы х с с ы л ок д л я обобщ енногопрограм м иров ания. 4
1. Б И Б Л И О Т Е К И ПО Д ПРО Г РА М М Э тот параграф опис ы в ает проц ес с раз работ ки библ иот ек под програм м и яв л яет с я в с пом огат ел ь ны м . Пос т роенная в нем библ иот ека буд ет ис пол ь з ов ана в с л ед ующ ем параграфе д л я с оз д ания кл ас с а, им еющ егоанал огичную функц ионал ь нос т ь . Задача 1.1. Требует с я раз работ ат ь библ иот еку проц ед ур и функц ий, м анипул ирующ их ц ел очис л енны м и в екторам и фикс иров анной раз м ернос т и n. Библ иот ека д ол жна с од ержат ь под програм м ы , в ы пол няющ ие с л ед ующ ие д ейс т в ия: 1) в ы чис л ение с ум м ы д в ух в екторов , 2) в ы чис л ение раз нос т и д в ух в екторов , 3) в ы чис л ение с кал ярногопроиз в ед ения д в ух в екторов , 4) прибав л ение од ногов ектора к д ругом у, 5) в ы чит ание од ногов ектора из д ругого, 6) прис в аив ание од ногов ектора д ругом у, 7) с рав нение д в ух в екторов на рав енс т в о, 8) в ы чис л ение д л ины в ектора (как эл ем ент а n-м ерного ев кл ид ов а прос т ранс т в а), 9) преобраз ов ание в ектора в с т роков ую форм у д л я в ы в од а на экран ил и в файл . Библ иот еку с л ед ует оформ ит ь в в ид е м од ул я. Требует с я т акже с оз д ат ь програм м у, ис пол ь з ующ ую д анны й м од ул ь и д ем онс т рирующ ую работ у егоот кры т ы х под програм м . Схема реш ения. 1. О бъяв л ение нов ы х т ипов д анны х и конс т ант, которы е буд ут ис пол ь з ов ат ь с я под програм м ам и библ иот еки. 2. О бъяв л ение под програм м , т. е. опред ел ение их им ен, с пис ков парам ет ров и т ипов в оз в ращ аем ы х з начений. 3. Реал из ац ия под програм м . 4. Раз м ещ ение напис анногокод а в нут ри м од ул я. 5. С оз д ание програм м ы , д ем онс т рирующ ей работ у от кры т ы х проц ед ур и функц ий библ иот еки. Реш ение. 1. Прежд е в с его необход им о опред ел ит ь с я с т ипом д анны х, которы й буд ет ис пол ь з ов ат ь с я д л я пред с т ав л ения в екторов . О чев ид но, чтопрос т ы е т ипы яз ы ка Object Pascal д л я этой ц ел и не под ход ят уже хотя бы потом у, чтоз аним ают фикс иров анны й объем пам ят и.
5
Раз м ер же в ектора з ав ис ит от чис л а n и, в ообщ е гов оря, м ожет бы т ь с кол ь угод но бол ь шим . Поэтом у с л ед ует ис пол ь з ов ат ь од ин из с ос т ав ны х т ипов : з апис ь (record), м ас с ив (array) ил и м ножес т в о(set). Пос кол ь ку ком понент ы в ектора м огут им ет ь од инаков ы е з начения, т ип «м ножес т в о» ис кл ючает с я. Запис ь обы чно объед иняет з начения, им еющ ие раз л ичны е с од ержат ел ь ны й с м ы с л , т ип и раз м ер. И х пос л ед ов ат ел ь ны й перебор в ц икл е нев оз м ожен, и это м ожет з ат руд нит ь пос л ед ущ ую реал из ац ию под програм м . Э л ем ент ы м ас с ив а напрот ив с ов ершеннорав ноправ ны и м огут бы т ь л егкоперечис л ены с пом ощ ь ю инд екс иров ания. О чев ид но, чт оэтот в ариант в наибол ь шей с т епени с оот в ет с т в ует с ем ант ике в ектора, поэтом у в качес т в е т ипа д анны х в ы бираем м ас с ив фикс иров анной раз м ернос т и. При этом конс т ант а, опред ел яющ ая раз м ернос т ь , д ол жна бы т ь объяв л ена д оопред ел ения т ипа: const Dim = n; // вместо n необходимо указать конкретное целое число type IVector = array[1..Dim] of Integer;
2. Перейд ем т еперь к объяв л ению проц ед ур и функц ий. Зд ес ь ос нов ную рол ь играет с од ержат ел ь ны й с м ы с л в ы пол няем ы х им и д ейс т в ий. Вы чис л ение с ум м ы и раз нос т и — бинарны е операц ии на м ножес т в е в екторов раз м ернос т и n. Поэтом у с оот в ет с т в ующ ие под програм м ы буд ут функц иям и с д в ум я парам ет рам и т ипа IVector и в оз в ращ аем ы м и з начениям и т ипа IVector: function Plus (v1, v2: IVector): IVector; // вычисление суммы function Minus(v1, v2: IVector): IVector; // вычисление разности
С кал ярное произ в ед ение — т акже функц ия д в ух аргум ентов , яв л яющ ихс я в екторам и, но ее з начение — это чис л о, пол учающ еес я из ком понент в екторов пос л ед ов ат ел ь нос т ь ю операц ий с л ожения и ум ножения. Пос кол ь ку м ы рас с м ат рив аем в екторы с ц ел очис л енны м и ком понент ам и, рез ул ь т ат в ы пол нения д анной пос л ед ов ат ел ь нос т и с нов а буд ет ц ел ы м чис л ом : function ScalarProduct(v1, v2: IVector): Integer;
Под програм м ы 4–6, ос ущ ес т в л яющ ие прибав л ение, в ы чит ание и прис в аив ание, отл ичают с я от в ы шеприв ед енны х в перв ую очеред ь т ем , чтоиз м еняют од ин из с в оих аргум ентов . С л ед ов ат ел ь но, этот аргум ент д ол жен бы т ь объяв л ен как перед ав аем ы й пос с ы л ке (var-парам ет р). Кром е того, в яз ы ке Object Pascal оператор прис в аив ания не в оз в ращ ает з начения, поэтом у в нашем с л учае реал из ующ ая егопод програм м а д ол жна бы т ь проц ед урой. Пос кол ь ку прибав л ение и в ы чит ание — это арифм ет ичес кие операторы с прис в аив анием , к ним м огут бы т ь прим енены т е же с оображения. Тем с ам ы м , пол учаем с л ед ующ ие объяв л ения: 6
procedure Add (var v1, v2: IVector); // прибавление procedure Subtract(var v1, v2: IVector); // вычитание procedure Assign (var v1, v2: IVector); // присваивание
О перац ию с рав нения на рав енс т в ом ожнорас с м ат рив ать как функц ию д в ух в екторов , в оз в ращ ающ ую бул ев с кое з начение. О т с юд а нем ед л еннов ы т екает ее объяв л ение: function AreEqual(v1, v2: IVector): Boolean;
Н аконец , опред ел ение д л ины в ектора и преобраз ов ание егов с т року — это, очев ид но, функц ии од ногоаргум ент а. В перв ом с л учае з начением функц ии буд ет чис л о, причем не обяз ат ел ь но ц ел ое, т ак как д л я егов ы чис л ения ис пол ь з ует с я операц ия из в л ечения кв ад рат ногокорня: function GetLength(v: IVector): Real;
Вторая функц ия, ес т ес т в енно, в оз в ращ ает с т року, с од ержащ ую рез ул ь т ат преобраз ов ания: function AsString(v: IVector): String;
3. И т ак, в с е проц ед уры и функц ии объяв л ены и м ы м ожем прис т упит ь к их реал из ац ии, которая, в прочем , с ов ершеннот рив иал ь на. Пример1.1.1. Реал из ац ия под програм м з ад ачи 1.1. function Plus(v1, v2: IVector): IVector; var i: Integer; begin for i:=1 to Dim do result[i]:=v1[i]+v2[i]; end; function Minus(v1, v2: IVector): IVector; var i: Integer; begin for i:=1 to Dim do result[i]:=v1[i]-v2[i]; end; procedure Add(var v1, v2: IVector); var i: Integer; begin for i:=1 to Dim do v1[i]:=v1[i]+v2[i]; end; procedure Subtract(var v1, v2: IVector); var i: Integer; begin for i:=1 to Dim do v1[i]:=v1[i]-v2[i]; end; function ScalarProduct(v1, v2: IVector): Integer; var i: Integer; begin result:=0; for i:=1 to Dim do result:=result+v1[i]*v2[i]; end; function GetLength(v: IVector): Real; var i: Integer; begin result:=0; for i:=1 to Dim do result:=result+sqr(v[i]); result:=sqrt(result); end;
7
function AreEqual(v1, v2: IVector): Boolean; var i: Integer; begin result:=true; for i:=1 to Dim do if v1[i]<>v2[i] then result:=false; end; procedure Assign(var v1, v2: IVector); var i: Integer; begin for i:=1 to Dim do v1[i]:=v2[i]; end; function AsString(v: IVector): String; var i: Integer; begin result:='('; for i:=1 to Dim do begin result:=result+IntToStr(v[i]); if i
Под програм м ы Plus, Minus, Add и Subtract в ц икл е в ы пол няют поком понент ны е операц ии с л ожения ил и в ы чит ания. Раз ниц а л ишь в том , чтофункц ии Plus и Minus с охраняют рез ул ь т ат в перем енной result, в оз в ращ аем ой в качес т в е их з начения, а проц ед уры Add и Subtract — в перв ом аргум ент е v1. Т ак как т ип IVector — этом ас с ив , то к ком понент ам перем енны х д анногот ипа м ы м ожем обращ ат ь с я обы чны м образ ом — с пом ощ ь ю инд екс иров ания. Пос кол ь ку |v| = v ×v , неуд ив ит ел ь но, чтореал из ац ии функц ий ScalarProduct и GetLength очень похожи. В обоих перем енная result иниц иал из ирует с я нул ем и з ат ем в ц икл е накапл ив ает с ум м у поком понент ны х произ в ед ений. В функц ии GetLength из пол ученногоз начения т ребует с я д опол нит ел ь ноиз в л ечь кв ад рат ны й корень . Крит ерий рав енс т в а д в ух в екторов м ожно с форм ул иров ат ь с л ед ующ им образ ом : в екторы не рав ны , ес л и хот я бы д л я од ной пары с оот в ет с т в енны х ком понент эт их в екторов им еет м ес то нерав енс т в о, и рав ны в прот ив ном с л учае. Данное опред ел ение и реал из ует функц ия AreEqual: ес л и хот я бы д л я од ногоi в ы ражение v1[i]<>v2[i] оказ ы в ает с я ис т инны м , в перем енную result з апис ы в ает с я з начение false, ес л и же д л я в с ех i указ анное в ы ражение л ожно, перем енная result с охраняет з начение, пол ученное ею д оначал а в ы пол нения ц икл а, т. е. true. Ф ункц ия AsString прим еняет к кажд ой ком понент е в ектора биб-
8
л иот ечную функц ию IntToStr, которая перев од ит ц ел ое чис л ов д ес ят ичную с ис т ем у с чис л ения и в оз в ращ ает рез ул ьт ат в в ид е ASCII-с т роки, с им в ол ы которой с оот в ет с т в уют ц ифрам д ес ят ичной з апис и. И з пол ученны х т аким образ ом з начений функц ия AsString форм ирует од ну с т року, в с т ав л яя м ежд у ком понент ам и в ектора з апят ы е и з акл ючая в с ю пос л ед ов ат ел ь нос т ь в кругл ы е с кобки. Раз ум еет с я, т акой форм ат не яв л яет с я ед инс т в енно в оз м ожны м . Зам ет им т акже, что д л я ис пол ь з ов ания функц ии IntToStr необход им опод кл ючит ь м од ул ь SysUtils. 4. Теперь пол ученную библ иот еку необход им о оформ ит ь в в ид е м од ул я. Е д инс т в енны й с од ержател ь ны й м ом ент з д ес ь з акл ючает с я в том , чтобы опред ел ит ь , какие эл ем ент ы м од ул я с д ел ат ь от кры т ы м и, а какие з акры т ы м и. В нашем с л учае этот в опрос решает с я од ноз начно: 1) пос кол ь ку в с пом огат ел ь ны х под програм м библ иот ека не с од ержит, в с е ее проц ед уры и функц ии д ол жны бы т ь от кры т ы м и; 2) т ип IVector, необход им ы й д л я объяв л ения под програм м , т акже д ол жен бы т ь от кры т ы м ; 3) конс т ант а, з ад ающ ая раз м ернос т ь в екторов , пред шес т в ует опред ел ению т ипа и, с л ед ов ат ел ь но, з ав ед ом о наход ит с я в раз д ел е инт ерфейс а, т. е. от кры т а. Т аким образ ом , в с е объяв л ения необход им ораз м ес т ит ь в раз д ел е инт ерфейс а, а реал из ац ию под програм м — в раз д ел е реал из ац ии. М од ул ь SysUtils, о котором шл а речь в ы ше, необход им л ишь д л я реал из ац ии функц ии AsString. Поэтом у егол учше под кл ючит ь в раз д ел е реал из ац ии (с м . обс ужд ение этого в опрос а в § 1.3 из [*]). В рез ул ьт ат е м ы пол учаем с л ед ующ ий код . Пример1.1.2. С т рукт ура м од ул я из з ад ачи 1.1. unit UnitIVector; interface const Dim = 5; type IVector = array[1..Dim] of Integer; function function function function procedure procedure procedure function function
Plus (v1, v2: IVector): Minus (v1, v2: IVector): ScalarProduct(v1, v2: IVector): AreEqual (v1, v2: IVector): Add (var v1, v2: IVector); Subtract(var v1, v2: IVector); Assign (var v1, v2: IVector); GetLength(v: IVector): Real; AsString(v: IVector): String;
9
IVector; IVector; Integer; Boolean;
implementation uses SysUtils; // Здесь размещается реализация подпрограмм, // приведенная в примере 1.1.1. end.
5. Пос л ед нее, что нам необход им о с д ел ат ь , это раз работ ат ь д ем онс т рац ионную програм м у. Е е код м ожет в ы гл яд ет ь прим ернот ак. Пример1.1.3. Дем онс т рац ионная програм м а д л я з ад ачи 1.1. program TestIVector; {$APPTYPE CONSOLE} uses UnitIVector in 'UnitIVector.pas'; var v1, v2, v3, v4: IVector; i: Integer; begin for i:=1 to Dim do begin v1[i]:=i+1; v2[i]:=i+2; end; WriteLn('v1: '+AsString(v1)); WriteLn('v2: '+AsString(v2)); WriteLn('GetLength(v1): ', GetLength(v1):5:5); v3:=Plus(v1, v2); WriteLn('v3 after v3:=Plus(v1, v2): '+AsString(v3)); Add(v1, v2); WriteLn('v1 after Add(v1, v2): '+AsString(v1)); if AreEqual(v1, v3) then WriteLn('v1 is equal to v3') else WriteLn('v1 is not equal to v3'); Assign(v4, v2); WriteLn('v4 after Assign(v4, v2): '+AsString(v4)); Subtract(v4, v1); WriteLn('v2 after v4.Subtract(v1): '+AsString(v2)); WriteLn('v4 after v4.Subtract(v1): '+AsString(v4)); WriteLn('ScalarProduct(v2, v4): ', ScalarProduct(v2, v4)); ReadLn; end.
Пос л е под кл ючения м од ул я UnitIVector конс т ант а Dim, т ип IVector и под програм м ы библ иот еки с т анов ят с я д ос т упны т ак же, как ес л и бы они бы л и объяв л ены в с ам ой програм м е. В ос нов ном бл оке пос л е иниц иал из ац ии ком понент в ект оров v1 и v2 над эт им и в ект орам и в ы пол няет с я ряд операц ий, кажд ая из которы х з ав ершает с я в ы в од ом рез ул ь т ат а на экран. С т рока WriteLn('v2 after v4.Subtract(v1): '+AsString(v2));
показ ы в ает, что пос л е прис в аив ания Assign(v4, v2) в екторы v2 и v4 ос 10
т ают с я нез ав ис им ы м и и операц ия над в торы м Subtract(v4, v1) не из м еняет перв ы й. Пос кол ь ку т ип IVector — это обы чны й м ас с ив фикс иров анной раз м ернос т и, никаких с пец иал ь ны х инс т рукц ий по раз м ещ ению перем енны х в пам ят и и их уд ал ению не т ребует с я: в с е это ком пил ятор в ы пол няет ав том ат ичес ки.
11
2. П РО СТ Е Й Ш И Е К Л А ССЫ -К О Н Т Е Й Н Е РЫ Кл ас с наз ы в ают конт ейнером , ес л и егообъект ы с од ержат в нут ри с ебя некоторы й набор од нот ипны х д анны х. К конт ейнерам от нос ят с я кл ас с ы , реал из ующ ие м ас с ив ы , с пис ки, м ножес т в а и д ругие под обны е с т рукт уры . Н ас тоящ ий параграф с од ержит с ерию из чет ы рех з ад ач в оз рас т ающ ей с л ожнос т и, в кажд ой из которы х т ребует с я пос т роит ь кл ас с , пред с т ав л яющ ий в ектор с т ем и ил и ины м и с в ойс т в ам и. Перв ая з ад ача с ущ ес т в енны м образ ом ис пол ь з ует код библ иот еки, с оз д анной в пред ы д ущ ем параграфе. Э т а библ иот ека д ает нам «с кел ет » решения, обл ад ая которы м м ы м ожем л учше ув ид ет ь ос обеннос т и объект ногопрограм м иров ания. Задача2.1. Требует с я раз работ ат ь кл ас с , объект ы которогопред с т ав л яют ц ел очис л енны е в екторы фикс иров анной раз м ернос т и n. Кл ас с д ол жен с од ержат ь от кры т ы е м етод ы , в ы пол няющ ие т е же операц ии, что и под програм м ы библ иот еки из з ад ачи 1.1. Требует с я т акже пом ес т ит ь пол ученны й кл ас с в отд ел ь ны й м од ул ь и с оз д ат ь програм м у, ис пол ь з ующ ую д анны й м од ул ь и д ем онс т рирующ ую работу с объект ам и кл ас с а. Схема реш ения. 1. О пред ел ение им ени нов огокл ас с а. 2. Раз работ ка от кры того инт ерфейс а кл ас с а, пред с т ав л яющ его с обой с пис ок объяв л ений м етод ов , которы е м ожнобуд ет в ы з ы в ат ь из пол ь з ов ат ел ь с кой програм м ы . 3. Раз работ ка з акры того и, при необход им ос т и, з ащ ищ енного инт ерфейс а. Э т а час т ь решения в кл ючает в с ебя в перв ую очеред ь опред ел ение реал из ац ии кл ас с а, т. е. с пис ка пол ей, которы е буд ут хранит ь в с ю необход им ую информ ац ию об объект е. 4. Доработ ка от кры того инт ерфейс а с учет ом ос обеннос т ей реал из ац ии: решение в опрос а онеобход им ос т и переопред ел ения конс т руктора Create и д ес т руктора Destroy. 5. Реал из ац ия м етод ов кл ас с а. 6. Добав л ение к з акры том у инт ерфейс у в с пом огат ел ь ны х м етод ов , ес л и т аков ы е появ ил ис ь в ход е реал из ац ии м етод ов . 7. Раз м ещ ение кл ас с а в нут ри м од ул я. 8. Раз работ ка програм м ы , м анипул ирующ ей объект ам и кл ас с а. О т м ет им ос нов ное отл ичие д анной с хем ы от с хем ы решения з ад ачи 1.1: перед раз работ кой от кры того инт ерфейс а кл ас с а нам необход им оз афикс иров ат ь л ишь наз в ание кл ас с а, в с е ос т ал ь ны е д ет ал и реал и12
з ац ии м огут бы т ь опред ел ены поз д нее и притом не ед инс т в енны м образ ом . Т акая пос л ед ов ат ел ь нос т ь д ейс т в ий нес кол ь ко неприв ы чна, но гарант ирует нез ав ис им ос т ь от кры того инт ерфейс а кл ас с а от конкрет ной его реал из ац ии. Впрочем , как показ ы в ает пункт 4, д анная гарант ия не яв л яет с я с топроц ент ной. Зам ет им , что в з ад аче 1.1 м ы т акже м огл и объяв ит ь в с е под програм м ы , з ад ав л ишь им я т ипа IVector без конкрет ногоопред ел ения. О д накоэтов с е рав ноне с д ел ал обы нез ав ис им ой от д анногоопред ел ения д ем онс т рац ионную програм м у: пред пол ожение о том , что IVector — м ас с ив , ис пол ь з ует с я в ней яв ны м образ ом . Реш ение. 1. Как и в з ад аче 1.1, наз ов ем наш кл ас с IVector. 2. Почт и в ес ь от кры т ы й инт ерфейс нов огокл ас с а м ожет бы т ь л егко пол учен из с пис ка объяв л ений под програм м з ад ачи 1.1. Дл я этого д ос т аточно в с пом нит ь , что л юбом у м етод у неяв но перед ает с я од ин парам ет р — ад рес объект а, д л я которого он в ы з в ан. С т ал о бы т ь , в о в с ех объяв л ениях под програм м библ иот еки перв ы й парам ет р т ипа IVector необход им оуд ал ит ь . В рез ул ьт ат е м ы пол учаем с л ед ующ ее: function function function function procedure procedure procedure function function
Plus (v: IVector): Minus (v: IVector): ScalarProduct(v: IVector): IsEqual (v: IVector): Add (v: IVector); Subtract(v: IVector); Assign (v: IVector); GetLength: Real; AsString: String;
IVector; IVector; Integer; Boolean;
О д нако эт их объяв л ений нед ос т аточно. Н ам необход им ы ещ е и с ред с т в а чт ения/з апис и отд ел ь ны х ком понент в ектора, в ед ь т еперь непос ред с т в енны й д ос т уп к хранящ им с я в з акры т ы х пол ях д анны м из пол ь з ов ат ел ь с кой програм м ы буд ет нев оз м ожен. Поэтом у м ы д обав л яем ещ е д в а м етод а: function GetComp(i: Integer): Integer; procedure SetComp(i: Integer; value: Integer);
Перв ы й из них в оз в ращ ает з начение i-й ком понент ы в ектора, т. е. ос ущ ес т в л яет чт ение, в торой з апис ы в ает в i-ю ком понент у з начение, перед анное через парам ет р value. Раз м ернос т ь в екторов яв л яет с я фикс иров анной и по-прежнем у м ожет бы т ь з ад ана конс т ант ой. К с ожал ению, яз ы к Object Pascal не поз в ол яет с д ел ат ь эт у конс т ант у чл еном кл ас с а, поэтом у она д ол жна бы т ь опред ел ена в не егообы чны м образ ом : 13
const Dim = n; // вместо n необходимо указать конкретное целое число
3. Кажд ы й объект кл ас с а IVector пред с т ав л яет с обой с ам ос тоят ел ь ны й в ектор, ком понент ы которогод ол жны хранит ь с я в пол ях д анного объект а. С оображения, прив ед енны е в решении з ад ачи 1.1, показ ы в ают, чтод л я этой ц ел и д ос т аточноод ногопол я, яв л яющ егос я од ном ерны м м ас с ив ом ц ел ы х чис ел раз м ернос т и Dim. Э топол е наз ов ем comp: comp: array[1..Dim] of Integer;
4. Вв ед енное пол е не яв л яет с я указ ат ел ем , поэтом у при уничтожении объект а з аним аем ая эт им пол ем пам ят ь буд ет пол нос т ь ю ос в обожд ена ком пил ятором . Х отя м ы и не реал из ов ал и м етод ы , но, учит ы в ая с ем ант ику кл ас с а, раз ум нопред пол ожит ь , чтов ход е их работ ы не буд ут з ахв ат ы в ат ь с я никакие рес урс ы . С л ед ов ат ел ь но, переопред ел ят ь унас л ед ов анны й д ес т руктор не нужно. Т ак как яв ное раз м ещ ение пол я в пам ят и и егоиниц иал из ац ия опред ел енны м и з начениям и не т ребуют с я, с обс т в енны й конс т руктор т акже не яв л яет с я необход им ы м . О т м ет им , чтов с е с каз анное в ы ше от нос ит ел ь ноав том ат ичес кого управ л ения в ы д ел ением и ос в обожд ением пам ят и кас ает с я тол ь копол я comp. О бъект ы кл ас с а IVector, как и л юбы е объект ы л юбы х д ругих кл ас с ов , необход им ос оз д ав ат ь и уд ал ят ь яв ны м образ ом . 5. Реал из ац ия м етод ов кл ас с а IVector м ожет бы т ь л егкопол учена из реал из ац ии под програм м з ад ачи 1.1. Дл я этогод ос т аточнол ишь принят ь в ов ним ание с л ед ующ ее: а) при реал из ац ии им я м етод а д ол жно бы т ь кв ал ифиц иров аноим енем кл ас с а; б) ес л и рань ше в с е парам ет ры опис ы в ал ис ь в з агол ов ке под програм м ы , то т еперь од ин из них перед ает с я неяв но и д ос т упен в нут ри м етод а под им енем self; в ) в м ес тов ы ражения v[i] д л я обращ ения к i-й ком понент е в ектора v в нут ри реал из ац ии кл ас с а (и тол ь ко з д ес ь ) с л ед ует ис пол ь з ов ат ь в ы ражение v.comp[i], з ад ающ ее i-й эл ем ент пол я-м ас с ив а comp объект а v; г) ес л и рез ул ьт атом функц ии яв л яет с я в ектор, т. е. объект кл ас с а IVector, егонеобход им о яв ны м образ ом раз м ес т ит ь в пам ят и пут ем в ы з ов а конс т руктора Create. Пос л е в нес ения в с ех из м енений м ы пол учаем с л ед ующ ий код (в с е д обав л ения и из м енения от м ечены под черкив анием ). Пример2.1.1. Реал из ац ия м етод ов кл ас с а IVector. function IVector.Plus(v: IVector): IVector; var i: Integer;
14
begin result:=IVector.Create; for i:=1 to Dim do result.comp[i]:=self.comp[i]+v.comp[i]; end; function IVector.Minus(v: IVector): IVector; var i: Integer; begin result:=IVector.Create; for i:=1 to Dim do result.comp[i]:=self.comp[i]-v.comp[i]; end; procedure IVector.Add(v: IVector); var i: Integer; begin for i:=1 to Dim do self.comp[i]:=self.comp[i]+v.comp[i]; end; procedure IVector.Subtract(v: IVector); var i: Integer; begin for i:=1 to Dim do self.comp[i]:=self.comp[i]-v.comp[i]; end; function IVector.ScalarProduct(v: IVector): Integer; var i: Integer; begin result:=0; for i:=1 to Dim do result:=result+self.comp[i]*v.comp[i]; end; function IVector.GetLength: Real; var i: Integer; begin result:=0; for i:=1 to Dim do result:=result+sqr(self.comp[i]); result:=sqrt(result); end; function IVector.IsEqual(v: IVector): Boolean; var i: Integer; begin result:=true; for i:=1 to Dim do if self.comp[i]<>v.comp[i] then result:=false; end; procedure IVector.Assign(v: IVector); var i: Integer; begin for i:=1 to Dim do self.comp[i]:=v.comp[i]; end;
15
function IVector.AsString: String; var i: Integer; begin result:='('; for i:=1 to Dim do begin result:=result+IntToStr(self.comp[i]); if i
М етод ы GetComp и SetComp м огут бы т ь реал из ов аны д в ум я раз л ичны м и с пос обам и: с конт рол ем инд екс ов и без . В перв ом с л учае при кажд ом в ы з ов е м етод а з начение парам ет ра i пров еряет с я на принад л ежнос т ь от рез ку [1, ..., Dim]. Е с л и инд екс не яв л яет с я д опус т им ы м , в оз бужд ает с я ис кл ючение. Во в тором с л учае никаких пров ерок коррект нос т и инд екс ов не произ в од ит с я. Э тот в ариант обес печив ает бол ее в ы с окую с корос т ь работ ы , но м ожет прив ес т и к неяв ной м од ификац ии пам ят и, чтояв л яет с я в ес ь м а т руд нообнаружив аем ой ошибкой. Реал из ац ия с конт рол ем инд екс ов в ы гл яд ит с л ед ующ им образ ом . Пример2.1.2. Реал из ац ия м етод ов GetComp и SetComp кл ас с а IVector. function IVector.GetComp(i: Integer): Integer; begin if (i>=1) and (i<=Dim) then result:=self.comp[i] else raise Exception.Create('Index is out of bounds'); end; procedure IVector.SetComp(i: Integer; value: Integer); begin if (i>=1) and (i<=Dim) then self.comp[i]:=value else raise Exception.Create('Index is out of bounds'); end;
7. Пос кол ь ку реал из ац ия кл ас с а не пот ребов ал а в в ед ения какихл ибов с пом огат ел ь ны х м етод ов , м ы м ожем з анят ь с я с оз д анием м од ул я. Т ак как кл ас с д ол жен бы т ь д ос т упен з а пред ел ам и д анногом од ул я, его инт ерфейс с л ед ует рас пол ожит ь в раз д ел е инт ерфейс а. Реал из ац ия кл ас с а в с егд а пом ещ ает с я в раз д ел реал из ац ии. Конс т ант а Dim ис пол ь з ует с я в инт ерфейс е кл ас с а и потом у д ол жна бы т ь опред ел ена в ы ше него. Т аким образ ом , м ы пол учаем
16
Пример2.1.3. Раз м ещ ение кл ас с а IVector в нут ри м од ул я. unit UnitIVector; interface const Dim = 5; type IVector = class private comp: array[1..Dim] of Integer; public function Plus (v: IVector): IVector; function Minus (v: IVector): IVector; function ScalarProduct(v: IVector): Integer; function IsEqual (v: IVector): Boolean; procedure Add (v: IVector); procedure Subtract(v: IVector); procedure Assign (v: IVector); function GetLength: Real; function GetComp(i: Integer): Integer; procedure SetComp(i: Integer; value: Integer); function AsString: String; end; implementation uses SysUtils; // Здесь размещается реализация методов, // приведенная в примерах 2.1.1 и 2.1.2. end.
8. Как и реал из ац ия м етод ов , д ем онс т рац ионная програм м а м ожет бы т ь л егкопол учена из програм м ы , д ем онс т рирующ ей работ у библ иот еки в з ад аче 1.1. Дл я этого в пос л ед нюю необход им о в нес т и с л ед ующ ие из м енения: а) ес л и объект кл ас с а в оз в ращ ает с я функц ией в качес т в е з начения, тоот в ет с т в еннос т ь з а егораз м ещ ение в пам ят и нес ет д анная функц ия, в прот ив ном с л учае с оз д анием объект а з аним ает с я с ам а програм м а; б) в с е объект ы , нез ав ис им оот того, как они бы л и с оз д аны , уд ал яют с я програм м ой пут ем яв ногов ы з ов а д ес т рукторов ; в ) обращ ение к ком понент ам в екторов в оз м ожнол ишь пос ред с т в ом в ы з ов а м етод ов GetComp и SetComp, в ы ражения в ид а v.comp[i] нед опус т им ы , т ак как пол е comp яв л яет с я з акры т ы м и опред ел ение кл ас с а наход ит с я в отд ел ь ном м од ул е; г) в ы з ов ы м етод ов отл ичают с я от в ы з ов ов под програм м библ иот еки с инт акс ичес ки, но не с од ержат ел ь но: перв ы й аргум ент — объект, д л я 17
которогов ы з ы в ает с я м етод , — прос тос т ав ит с я перед им енем м етод а и отд ел яет с я от неготочкой. Пол ученны й в рез ул ьт ат е код с од ержит Пример2.1.4. Дем онс т рац ионная програм м а д л я з ад ачи 2.1. program TestIVector; {$APPTYPE CONSOLE} uses UnitIVector in 'UnitIVector.pas'; var v1, v2, v3, v4: IVector; i: Integer; begin v1:=IVector.Create; v2:=IVector.Create; for i:=1 to Dim do begin v1.SetComp(i, i+1); v2.SetComp(i, i+2); end; WriteLn('v1: '+v1.AsString); WriteLn('v2: '+v2.AsString); WriteLn('v1.GetLength: ', v1.GetLength:5:5); v3:=v1.Plus(v2); WriteLn('v3 after v3:=v1.Plus(v2): '+v3.AsString); v1.Add(v2); WriteLn('v1 after v1.Add(v2): '+v1.AsString); if v1.IsEqual(v3) then WriteLn('v1 is equal to v3') else WriteLn('v1 is not equal to v3'); v4:=IVector.Create; v4.Assign(v2); WriteLn('v4 after v4.Assign(v2): '+v4.AsString); v4.Subtract(v1); WriteLn('v2 after v4.Subtract(v1): '+v2.AsString); WriteLn('v4 after v4.Subtract(v1): '+v4.AsString); WriteLn('v2.ScalarProduct(v4): ', v2.ScalarProduct(v4)); v1.Destroy; v2.Destroy; v3.Destroy; v4.Destroy; ReadLn; end.
Как и в ы ше, д обав л енны е и из м ененны е учас т ки код а от м ечены под черкив анием . Задача 2.2 анал огична пред ы д ущ ей, но т еперь ком понент ы в екторов д ол жны бы т ь рац ионал ь ны м и чис л ам и. Пред пол агает с я, что т ип д анны х, пред с т ав л яющ ий рац ионал ь ное чис л о, и под програм м ы д л я работ ы с ним уже опред ел ены и наход ят с я в отд ел ь ном м од ул е. И нт ерфейс этогом од ул я прив од ит с я ниже, реал из ац ия — в прил ожении 1.
unit UnitRational; interface
18
type Rational = record num: Integer; den: Integer; end; function function function function procedure procedure procedure procedure function function
Plus (r1,r2:Rational): Rational; // вычисление суммы Minus(r1,r2:Rational): Rational; // вычисление разности Dot (r1,r2:Rational): Rational; // вычисление произведения Slash(r1,r2:Rational): Rational; // вычисление частного Add (var r1:Rational; r2:Rational); // прибавление Subtract (var r1:Rational; r2:Rational); // вычитание MultiplyBy(var r1:Rational; r2:Rational); // домножение DivideBy (var r1:Rational; r2:Rational); // деление AreEqual(r1,r2:Rational): Boolean; // сравнение на равенство AsString(r: Rational): String; // преобразование к строковой // форме
Реш ение д анной з ад ачи с л ед ует той же с хем е, чтои решение з ад ачи 2.1, поэтом у м ы опишем л ишь егоотл ичия от пред ы д ущ его. 1. Вм ес тоIVector с оз д ав аем ы й кл ас с наз ов ем RVector (от Rational). 2. О чев ид но, чтот акие с в ойс т в а парам ет ров и в оз в ращ аем ы х з начений м етод ов , как «бы т ь в ектором » ил и «бы т ь чис л ом » , а т акже кол ичес т в о парам ет ров и в ид под програм м ы (проц ед ура ил и функц ия) не з ав ис ят от т ипа ком понент в екторов . Поэтом у от кры т ы й инт ерфейс нов ого кл ас с а м ожет бы т ь пол учен из инт ерфейс а кл ас с а IVector пут ем в нес ения в с егод в ух м од ификац ий. Перв ая из них очень прос т а и з акл ючает с я в пов с ем ес т ной з ам ене им ени кл ас с а IVector на RVector. Вторая не с тол ь очев ид на и с в яз ана с из м енением т ипа ком понент ; ее необход им о произ в ес т и д л я в с ех м етод ов , т ип парам ет ра ил и в оз в ращ аем ого з начения которы х з ав ис ит от т ипа ком понент в ектора. И нт ерфейс кл ас с а IVector с од ержит чет ы ре м етод а, уд ов л ет в оряющ их указ анном у ус л ов ию: GetComp, SetComp, ScalarProduct и GetLength. В перв ы х д в ух т ипы , с оот в ет с т в енно, в оз в ращ аем огоз начения и в торого парам ет ра непос ред с тв еннос ов пад ают с т ипом ком понент, поэтом у необход им опрос тоз ам енит ь их с Integer на Rational (т ип парам ет ра i, з ад ающ его ном ер ком понент ы , по-прежнем у ос т ает с я ц ел ы м ). В т рет ь ем в оз в ращ аем ое з начение пол учает с я из ком понент в екторов пос л ед ов ат ел ь нос т ь ю операц ий с л ожения и ум ножения. С л ед ов ат ел ь но, м ы д ол жны убед ит ь с я, чтод анны е операц ии не в ы в од ят з а пред ел ы т ипа. В нашем с л учае это т ак, поэтом у з д ес ь т акже т ребует с я з ам ена Integer на Rational. Н аконец , в чет в ертом в качес т в е з начения в оз в ращ ает с я кв ад рат ны й корень из рац ионал ь ногочис л а. Н ичегоне м еняя, ос т ав л яем т ип Real. 19
В рез ул ьт ат е опис анны х ис прав л ений, м ы пол учаем с л ед ующ ее (как обы чно, в с е м од ификац ии от м ечены под черкив анием ). Пример2.2.1. О т кры т ы й инт ерфейс кл ас с а RVector (в перв ом прибл ижении). function function function function procedure procedure procedure function function procedure function
Plus (v: RVector): RVector; Minus (v: RVector): RVector; ScalarProduct(v: RVector): Rational; IsEqual (v: RVector): Boolean; Add (v: RVector); Subtract(v: RVector); Assign (v: RVector); GetLength: Real; GetComp(i: Integer): Rational; SetComp(i: Integer; value: Rational); AsString: String;
3. Раз ум еет с я, из м енения кос нут с я и в нут ренней реал из ац ии объект ов кл ас с а RVector. О чев ид но, чт о эл ем ент ы м ас с ив а comp т еперь д ол жны им ет ь т ип Rational. 4. С оображения, прив ед енны е в решении пред ы д ущ ей з ад ачи и обос нов ы в ающ ие от каз от переопред ел ения унас л ед ов анного д ес т руктора, ос т ают с я с прав ед л ив ы м и и д л я нов огокл ас с а. В тоже в рем я иниц иал из ац ия эл ем ентов м ас с ив а comp т еперь оказ ы в ает с я необход им ой: нел ь з я д опус т ит ь , чтобы их з нам енат ел и с од ержал и нул ев ы е з начения. Поэтом у м ы д ол жны переопред ел ит ь в кл ас с е RVector с т анд арт ны й конс т руктор Create, д обав ив к от кры том у нит ерфейс у кл ас с а с т року constructor Create;
5. И з м енения, которы е необход им ов нес т и в код м етод ов , с в яз аны с т ем , что с т анд арт ны е операторы и библ иот ечны е функц ии яз ы ка Object Pascal не м огут бы т ь прим енены к перем енны м пол ь з ов ат ел ь с кого т ипа Rational. Поэтом у м ы д ол жны л ибо в с юд у в с т ав ит ь код , в ы пол няющ ий т ребуем ы е д ейс т в ия, наприм ер, з ам енит ь в ы ражение self.comp[i]:=self.comp[i]+v.comp[i];
на self.comp[i].num:=self.comp[i].num*v.comp[i].den +self.comp[i].den*v.comp[i].num; self.comp[i].den:=self.comp[i].den*v.comp[i].den;
л ибов ос пол ь з ов ат ь с я готов ы м и под програм м ам и из м од ул я UnitRational. С огл ас нообщ ей ид ее с окры т ия реал из ац ии в торой в ариант яв л яет с я бо20
л ее пред почт ит ел ь ны м , егод ем онс т рирует Пример2.2.2. Реал из ац ия м етод ов кл ас с а RVector. function RVector.Plus(v: RVector): RVector; var i: Integer; begin result:=RVector.Create; for i:=1 to Dim do result.comp[i]:=UnitRational.Plus(self.comp[i], v.comp[i]); end; function RVector.Minus(v: RVector): RVector; var i: Integer; begin result:=RVector.Create; for i:=1 to Dim do result.comp[i]:=UnitRational.Minus(self.comp[i], v.comp[i]); end; procedure RVector.Add(v: RVector); var i: Integer; begin for i:=1 to Dim do UnitRational.Add(self.comp[i], v.comp[i]); end; procedure RVector.Subtract(v: RVector); var i: Integer; begin for i:=1 to Dim do UnitRational.Subtract(self.comp[i], v.comp[i]); end; function RVector.ScalarProduct(v: RVector): Rational; var i: Integer; begin result.num:=0; result.den:=1; for i:=1 to Dim do UnitRational.Add(result, UnitRational.Dot(self.comp[i], v.comp[i])); end; function RVector.GetLength: Real; var i: Integer; begin result:=0; for i:=1 to Dim do result:=result+sqr(self.comp[i].num/self.comp[i].den); result:=sqrt(result); end; function RVector.IsEqual(v: RVector): Boolean; var i: Integer;
21
begin result:=true; for i:=1 to Dim do if not(AreEqual(self.comp[i], v.comp[i])) then result:=false; end; procedure RVector.Assign(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i]:=v.comp[i]; end; function RVector.GetComp(i: Integer): Rational; begin if (i>=1) and (i<=Dim) then result:=self.comp[i] else raise Exception.Create('Index is out of bounds'); end; procedure RVector.SetComp(i: Integer; value: Rational); begin if (i>=1) and (i<=Dim) then self.comp[i]:=value else raise Exception.Create('Index is out of bounds'); end; function RVector.AsString: String; var i: Integer; begin result:='('; for i:=1 to Dim do begin result:=result+UnitRational.AsString(self.comp[i]); if i
О т м ет им , что реал из ац ии м етод ов ScalarProduct и GetLength т еперь отл ичают с я бол ее с ущ ес т в енно: при опред ел ении д л ины в ектора рац ионал ь ное чис л о уд обнее с раз у перев ес т и в в ещ ес т в енную форм у и уже потом в оз в од ит ь в кв ад рат (хотя в ы чис л ения в рац ионал ь ны х чис л ах с од ним преобраз ов анием в конц е д ают бол ее точны й рез ул ь т ат ). М од ул ь UnitRational не с од ержит проц ед уры , обнул яющ ей рац ионал ь ное 22
чис л о, поэтом у в функц ии ScalarProduct с оот в ет с т в ующ ие д ейс т в ия над перем енной result в ы пол няют с я в ручную. Зам ет им ещ е, чтоим ена бол ь шинс т в а под програм м , оперирующ их рац ионал ь ны м и чис л ам и, с ов пад ают с им енам и м етод ов нашего кл ас с а и д ол жны бы т ь кв ал ифиц иров аны им енем м од ул я. Прим ер 2.2.2 с од ержит и код в нов ь д обав л енного конс т руктора, не т ребующ ий, в прочем , каких-л ибоком м ент ариев . 7. Е д инс т в енное с ущ ес т в енное отл ичие раз м ещ ения кл ас с а RVector в м од ул е — ис пол ь з ов ание м од ул я UnitRational. О пред ел яем ы й в нем т ип Rational фигурирует уже в инт ерфейс е кл ас с а RVector, поэтом у под кл ючение необход им ов ы пол нит ь в раз д ел е инт ерфейс а. Пример2.2.3. Раз м ещ ение кл ас с а RVector в нут ри м од ул я. unit UnitRVector; interface uses UnitRational; const Dim = 5; type RVector = class private comp: array[1..Dim] of Rational; public function Plus (v: RVector): RVector; function Minus (v: RVector): RVector; function ScalarProduct(v: RVector): Rational; function IsEqual (v: RVector): Boolean; procedure Add (v: RVector); procedure Subtract(v: RVector); procedure Assign (v: RVector); function GetLength: Real; function GetComp(i: Integer): Rational; procedure SetComp(i: Integer; value: Rational); function AsString: String; constructor Create; end; implementation uses SysUtils; // теперь, когда функция IntToStr более не используется, // этот модуль необходим лишь для поддержки исключений // Здесь размещается реализация методов, // приведенная в примере 2.2.2. end.
8. Дем онс т рац ионная програм м а т акже почт и не отл ичает с я от 23
пред ы д ущ ей. И з м еняют с я л ишь в ы з ов ы м етод ов , приним ающ их в качес т в е парам ет ров ил и в оз в ращ ающ их в качес т в е з начения рац ионал ь ны е чис л а (SetComp и ScalarProduct), а т акже им ена кл ас с а (IVector ® RVector), програм м ы (TestIVector ® TestRVector) и м од ул я (UnitIVector ® UnitRVector). Дл я д ем онс т рац ии работ ы конс т рукт ора Create с раз у пос л е с оз д ания в ект оров v1 и v2 д обав л ена инс т рукц ия в ы в од а на экран в ект ора v1. Пример2.2.3. Дем онс т рац ионная програм м а д л я з ад ачи 2.2. program TestRVector; {$APPTYPE CONSOLE} uses UnitRVector in 'UnitRVector.pas', UnitRational in 'UnitRational.pas'; // Второй модуль необходим для объявления переменной r // и для вызова функции UnitRational.AsString в строке // WriteLn('v2.ScalarProduct(v4): ', AsString(v2.ScalarProduct(v4))); var v1, v2, v3, v4: RVector; r: Rational; i: Integer; begin v1:=RVector.Create; v2:=RVector.Create; WriteLn('v1: '+v1.AsString); // демонстрация работы Create for i:=1 to Dim do begin r.num:=i; r.den:=i+1; v1.SetComp(i, r); r.num:=i+1; r.den:=i+2; v2.SetComp(i, r); end; WriteLn('v1: '+v1.AsString); WriteLn('v2: '+v2.AsString); WriteLn('v1.GetLength: ', v1.GetLength:5:5); v3:=v1.Plus(v2); WriteLn('v3 after v3:=v1.Plus(v2): '+v3.AsString); v1.Add(v2); WriteLn('v1 after v1.Add(v2): '+v1.AsString); if v1.IsEqual(v3) then WriteLn('v1 is equal to v3') else WriteLn('v1 is not equal to v3'); v4:=RVector.Create; v4.Assign(v2); WriteLn('v4 after v4.Assign(v2): '+v4.AsString); v4.Subtract(v1); WriteLn('v2 after v4.Subtract(v1): '+v2.AsString); WriteLn('v4 after v4.Subtract(v1): '+v4.AsString); WriteLn('v2.ScalarProduct(v4): ', AsString(v2.ScalarProduct(v4))); v1.Destroy; v2.Destroy; v3.Destroy; v4.Destroy; ReadLn; end.
24
Задача 2.3 в пол не ес т ес т в енны м образ ом прод ол жает рас с м ат рив аем ую с ерию. С нов а т ребует с я с оз д ат ь кл ас с д л я пред с т ав л ения в екторов фикс иров анной д л ины с рац ионал ь ны м и ком понент ам и. О д накот еперь ком понент ы эт и д ол жны в с в ою очеред ь бы т ь объект ам и кл ас с а. Как и ранее, д л я пред с т ав л ения рац ионал ь ны х чис ел необход им о ис пол ь з ов ат ь уже с ущ ес т в ующ ий кл ас с Rational, опред ел ение которого наход ит с я в отд ел ь ном м од ул е UnitRational. Дл я решения з ад ачи д ос т аточно з нат ь , что кл ас с Rational унас л ед ов ан от некоего абс т ракт ного кл ас с а Number и им еет с л ед ующ ий от кры т ы й инт ерфейс : function function function function function procedure procedure procedure procedure procedure function constructor constructor procedure procedure function function
Plus (n: Number): Number; Minus (n: Number): Number; Dot (n: Number): Number; Slash (n: Number): Number; IsEqual(n: Number): Boolean; Add (n: Number); Subtract (n: Number); MultiplyBy(n: Number); DivideBy (n: Number); Assign (n: Number); AsString: String; Create; CreateEqualTo(n: Number); SetNum(value: Integer); SetDen(value: Integer); GetNum: Integer; GetDen: Integer;
// // // // // // // // // // // // // // // // //
вычисление суммы вычисление разности вычисление произведения вычисление частного сравнение на равенство прибавление вычитание домножение деление присваивание преобразование в строку конструктор по умолчанию создание копии изменяет числитель изменяет знаменатель возвращает числитель возвращает знаменатель
Реал из ац ия д анногокл ас с а гарант ирует ц ел ос тнос ть объект а и в оз бужд ение ис кл ючения в с л учае, когд а коррект ное в ы пол нение операц ии нев оз м ожно(наприм ер, при д ел ении на нол ь ). Конс т руктор Create с оз д ает объект, пред с т ав л яющ ий нул ев ое чис л о, CreateEqualTo — копию з ад анногообъект а. Бол ее под робны е с в ед ения окл ас с е Rational прив од ят с я в прил ожении 3. О пис ание кл ас с а Number с од ержит с я в прил ожении 2. Реш ение з ад ачи с нов а буд ем ос ущ ес т в л ят ь пут ем пос л ед ов ат ел ь ны х ис прав л ений напис анного ранее код а. Причин, по которы м приход ит с я эт и ис прав л ения произ в од ит ь , д в е. Во-перв ы х, объект ы в отл ичие от з апис ей яв л яют с я указ ат ел ям и, чтос оз д ает ряд д опол нит ел ь ны х пробл ем , т аких, наприм ер, как яв ное раз м ещ ение в пам ят и и уд ал ение из нее. Во-в торы х, в нут ренняя реал из ац ия объект а пол нос т ь ю с кры т а от нас , и ес л и рань ше м ы м огл и в ы бират ь , ис пол ь з ов ат ь л и готов ы е под 25
програм м ы из м од ул я UnitRational ил и опериров ат ь непос ред с т в еннопол ям и з апис и, то т еперь т акой ал ьт ернат ив ы нет : ед инс т в енное, что нам д ос т упно— этоперечис л енны е в ы ше от кры т ы е м етод ы кл ас с а Rational. Впрочем , у нас ес т ь в оз м ожнос т ь из в л екат ь з начения чис л ит ел я и з нам енат ел я пос ред с т в ом м етод ов GetNum/GetDen, в ы пол нят ь над ним и д ейс т в ия, д убл ирующ ие по с ут и работ у д ругих м етод ов кл ас с а Rational (т аких, как Plus, Minus, Add), и з апис ы в ат ь рез ул ьт ат обрат нос пом ощ ь ю SetNum/SetDen. О д нако д анны й под ход не м ожет бы т ь оправ д ан д аже с оображениям и произ в од ит ел ь нос т и. Как и в ы ше, кл ас с наз ов ем RVector. Е гоинт ерфейс и реал из ац ия никак не з ав ис ят от в нут реннегопред с т ав л ения т ипа Rational и, с л ед ов ат ел ь но, ничем не буд ут отл ичат ь с я от прив ед енны х в пред ы д ущ ей з ад аче. Поэтом у м ы м ожем перейт и с раз у к чет в ертом у пункт у с хем ы . 4. Т ак как пол е comp т еперь пред с т ав л яет с обой м ас с ив указ ат ел ей на объект ы , на конс т руктор л ожит с я д опол нит ел ь ная з абот а пораз м ещ ению эт их объектов в пам ят и. Е с т ес т в енно, при уд ал ении в ектора необход им опроиз в ес т и обрат ную операц ию, от куд а в ы т екает необход им ос т ь в з ам ещ ении унас л ед ов анного д ес т руктора. Т аким образ ом , от кры т ы й инт ерфейс кл ас с а RVector д опол няет с я с т рокой destructor Destroy; override;
5. Реал из ац ии конс т руктора и д ес т руктора в ы гл яд ят очень прос то и з акл ючают с я в пос л ед ов ат ел ь ном с оз д ании и уд ал ении в с ех эл ем ентов м ас с ив а comp. Дейс т в ия, в ы пол няв шиес я ранее конс т руктором кл ас с а RVector и с ос тояв шие в з ад ании коррект ны х з начений пол ей эл ем ентов м ас с ив а comp, т еперь берет на с ебя конс т руктор кл ас с а Rational. constructor RVector.Create; var i: Integer; begin inherited Create; for i:=1 to Dim do self.comp[i]:=Rational.Create; end; destructor RVector.Destroy; var i: Integer; begin for i:=1 to Dim do self.comp[i].Destroy; inherited Destroy; end;
Ч тобы пол учит ь реал из ац ию ос т ал ь ны х м етод ов , нам в перв ую очеред ь с л ед ует з ам енит ь в им еющ ем с я код е в ы з ов ы в с ех под програм м 26
м од ул я UnitRational в ы з ов ам и м етод ов кл ас с а Rational. Кром е того, необход им о ис кл ючит ь непос ред с т в енное обращ ение к пол ям num и den в м етод ах ScalarProduct и GetLength. В перв ом из них перем енная result д ол жна бы т ь яв но раз м ещ ена в пам ят и пут ем в ы з ов а конс т руктора, которы й и в ы пол нит ее иниц иал из ац ию нул ем . Вов тором м ожнов ос пол ь з ов ат ь с я м етод ам и GetNum/GetDen. О т м ет им ещ е, чтом етод ы Plus и Minus кл ас с а Rational форм ал ь но в оз в ращ ают с с ы л ку на объект кл ас с а Number (реал ь ноад рес ующ ую объект кл ас с а Rational ил и произ в од ного от него). Поэтом у, прис в аив ая ее эл ем ент у м ас с ив а comp, м ы д ол жны в ы пол нит ь прив ед ение т ипа пос ред с т в ом оператора as. Рез ул ьт ат опис анны х преобраз ов аний с од ержит Пример2.3.1. Перв ая реал из ац ия м етод ов кл ас с а RVector. function RVector.Plus(v: RVector): RVector; var i: Integer; begin result:=RVector.Create; for i:=1 to Dim do result.comp[i]:=self.comp[i].Plus(v.comp[i]) as Rational; end; function RVector.Minus(v: RVector): RVector; var i: Integer; begin result:=RVector.Create; for i:=1 to Dim do result.comp[i]:=self.comp[i].Minus(v.comp[i]) as Rational; end; procedure RVector.Add(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i].Add(v.comp[i]); end; procedure RVector.Subtract(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i].Subtract(v.comp[i]); end; function RVector.ScalarProduct(v: RVector): Rational; var i: Integer; begin result:=Rational.Create; for i:=1 to Dim do result.Add(self.comp[i].Dot(v.comp[i])); end; function RVector.GetLength: Real; var i: Integer;
27
begin result:=0; for i:=1 to Dim do result:=result+sqr(self.comp[i].GetNum/self.comp[i].GetDen); result:=sqrt(result); end; function RVector.IsEqual(v: RVector): Boolean; var i: Integer; begin result:=true; for i:=1 to Dim do if not(self.comp[i].IsEqual(v.comp[i])) then result:=false; end; procedure RVector.Assign(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i]:=v.comp[i]; end; function RVector.GetComp(i: Integer): Rational; begin if (i>=1) and (i<=Dim) then result:=self.comp[i] else raise Exception.Create('Index is out of bounds'); end; procedure RVector.SetComp(i: Integer; value: Rational); begin if (i>=1) and (i<=Dim) then self.comp[i]:=value else raise Exception.Create('Index is out of bounds'); end; function RVector.AsString: String; var i: Integer; begin result:='('; for i:=1 to Dim do begin result:=result+self.comp[i].AsString; if i
8. Пос кол ь ку прив ед енны й код , буд учи с т анд арт ны м образ ом раз м ещ ен в м од ул е, ус пешно ком пил ирует с я, перейд ем к м од ификац ии д ем онс т рац ионной програм м ы . Пробл ем ы з д ес ь т акже с в яз аны ис кл ючит ел ь нос необход им ос т ь ю яв нос оз д ав ат ь и уд ал ят ь перем енны е т ипа Rational, а т акже из м енят ь их з начения тол ь ко через с оот в ет с т в ующ ие от кры т ы е м етод ы . 28
И с прав л ения с л ед ует в нес т и в час т ь код а, гд е произ в од ят с я операц ии с перем енной r. Перед перв ы м ис пол ь з ов анием она д ол жна бы т ь с оз д ана пут ем в ы з ов а конс т руктора, пос л е чего з начения чис л ит ел я и з нам енат ел я м ожнобуд ет ус т анов ит ь , в ы з ы в ая м етод ы SetNum/SetDen. Кром е того, с т рока WriteLn('v2.ScalarProduct(v4): ', AsString(v2.ScalarProduct(v4)));
д ол жна бы т ь з ам енена на p:=v2.ScalarProduct(v4); WriteLn('v2.ScalarProduct(v4): '+p.AsString); p.Destroy;
гд е p — в с пом огат ел ь ная перем енная т ипа Rational. Вариант без в в ед ения этой перем енной: WriteLn('v2.ScalarProduct(v4): '+v2.ScalarProduct(v4).AsString);
ус пешно ком пил ирует с я и работ ает, но прив од ит к ут ечке пам ят и, т ак как объект, с оз д анны й м етод ом ScalarProduct, нигд е не уд ал яет с я. И т ак, д ем онс т рац ионная програм м а в ы гл яд ит с л ед ующ им образ ом . Пример2.3.2. Дем онс т рац ионная програм м а д л я з ад ачи 2.3. program TestRVector; {$APPTYPE CONSOLE} uses UnitRVector in 'UnitRVector.pas', UnitRational in 'UnitRational.pas'; var v1, v2, v3, v4: RVector; r, p: Rational; i: Integer; begin v1:=RVector.Create; v2:=RVector.Create; r:=Rational.Create; for i:=1 to Dim do begin r.SetDen(i+1); v1.SetComp(i, r); r.SetNum(i); r.SetNum(i+1); r.SetDen(i+2); v2.SetComp(i, r); end; WriteLn('v1: '+v1.AsString); WriteLn('v2: '+v2.AsString); WriteLn('v1.GetLength: ', v1.GetLength:5:5); v3:=v1.Plus(v2); WriteLn('v3 after v3:=v1.Plus(v2): '+v3.AsString); v1.Add(v2); WriteLn('v1 after v1.Add(v2): '+v1.AsString); if v1.IsEqual(v3) then WriteLn('v1 is equal to v3') else WriteLn('v1 is not equal to v3'); v4:=RVector.Create; v4.Assign(v2); WriteLn('v4 after v4.Assign(v2): '+v4.AsString); v4.Subtract(v1); WriteLn('v2 after v4.Subtract(v1): '+v2.AsString);
29
WriteLn('v4 after v4.Subtract(v1): '+v4.AsString); p:=v2.ScalarProduct(v4); WriteLn('v2.ScalarProduct(v4): '+p.AsString); p.Destroy; r.Destroy; v1.Destroy; v2.Destroy; v3.Destroy; v4.Destroy; ReadLn; end.
Ув ы , хотя ком пил яц ия и проход ит ус пешно, в ы пол нение програм м ы з ав ершает с я крахом в м ом ент в ы з ов а д ес т руктора объект а v1. Е с л и з аком м ент иров ат ь с т року, гд е произ в од ит с я уд ал ение в екторов , том ожноз ам ет ит ь , чтоперв ы й же в ы в од на экран с од ержим огов ектора v1 д ает с ов с ем не тот рез ул ьт ат, кот оры й ожид ает с я. Вм ес тонабора (1/2, 2/3, 3/4, 4/5, 5/6) м ы пол учаем в ектор, в с е ком понент ы которогорав ны 6/7. Дл я объяс нения этого факт а необход им о в с пом нит ь , что яв ны е с оз д ание и уд ал ение — не ед инс т в енны е пробл ем ы , с в яз анны е с т ем , что объектны е перем енны е яв л яют с я указ ат ел ям и. О ператоры прис в аив ания и с рав нения на рав енс т в о, прим еняем ы е к т аким перем енны м , м анипул ируют не с ам им и объект ам и, а их ад рес ам и. Э топов ед ение принц ипиал ь но отл ичает с яот операц ий над з апис ям и, с которы м и м ы им ел и д ел оранее. В нашем конкрет ном с л учае пробл ем а в ы з в ана м етод ом SetComp, произ в од ящ им прос тое с ов м ещ ение ад рес ов . В рез ул ьт ат е пос л е в ы пол нения ц икл а в начал е д ем онс т рац ионной програм м ы в с е ком понент ы в екторов v1 и v2 с с ы л ают с я на од ин и тот же реал ь ны й объект, ад рес уем ы й ещ е и перем енной r. Пол я этого объект а на пос л ед ней ит ерац ии ц икл а пол учают з начения 6 и 7, чем и объяс няет с я набл юд аем ы й в ы в од на экран. Н аконец , при уд ал ении в ектора v1 д ес т рукт ор пы т ает с я нес кол ь ко раз (по чис л у ком понент ) уд ал ит ь д анны й объект, что, ес т ес т в енно, прив од ит к в оз бужд ению ис кл ючения. Дл я начал а попы т аем с я ис прав ит ь с ит уац ию, с л егка м од ифиц иров ав д ем онс т рац ионную програм м у. Зам еним с т роки в ее начал е v1:=RVector.Create; v2:=RVector.Create; r:=Rational.Create; for i:=1 to Dim do begin r.SetNum(i); r.SetDen(i+1); v1.SetComp(i, r); r.SetNum(i+1); r.SetDen(i+2); v2.SetComp(i, r); end;
на v1:=RVector.Create; v2:=RVector.Create; for i:=1 to Dim do begin r:=Rational.Create; r.SetNum(i); r.SetDen(i+1); v1.SetComp(i, r); r:=Rational.Create; r.SetNum(i+1); r.SetDen(i+2); v2.SetComp(i, r); end;
30
Теперь нов ы й объект с оз д ает с я перед кажд ы м в ы з ов ом м етод а SetComp, и, с л ед ов ат ел ь но, в с е ком понент ы в екторов наход ят с я в раз -
ны х обл ас тях пам ят и. О д наков оз никает нов ая пробл ем а. М етод Assign кл ас с а RVector т акже произ в од ит с ов м ещ ение ад рес ов объектов , поэтом у пос л е инс т рукц ии v4.Assign(v2) с оот в ет с т в енны е ком понент ы в екторов v2 и v4 с с ы л ают с я на од нои тоже м ес тов пам ят и. В рез ул ьт ат е в ы чит ание v4.Subtract(v1) прив од ит к неяв ной м од ификац ии в ектора v2, чтои д ем онс т рирует в ос ь м ая с т рока т екс т а, в ы в од им ого програм м ой: v2 after v4.Subtract(v1): (-1/2, -2/3, -3/4, -4/5, -5/6)
(в от д л я чегобы л а нужна инс т рукц ия WriteLn('v2 after v4.Subtract(v1): '+v2.AsString);
д обав л енная в д ем онс т рац ионную програм м у ещ е в з ад аче 1.1). С л ожнос т и в оз никают и при уд ал ении в екторов : д ес т руктор объект а v2 ос в обожд ает в с ю пам ят ь , на которую с с ы л ают с я эл ем ент ы м ас с ив а comp объект а v4. Поэтом у коррект ное з ав ершение програм м ы в оз м ожнол ишь в том с л учае, когд а д ес т руктор д л я в ектора v4 не в ы з ы в ает с я. Е с л и пов ед ение м етод а SetComp ещ е м ожнос чит ат ь д опус т им ы м и учит ы в ат ь при напис ании код а, том етод Assign работ ает яв нонев ерно. С т ал обы т ь , м ы д ол жны в ернут ь с я к реал из ац ии кл ас с а RVector и бол ее прис т ал ь ноиз учит ь операц ии прис в аив ания, в ы пол няем ы е егом етод ам и. Прежд е в с его, в нес ем из м енения в м етод ы GetComp, SetComp и Assign. В пос л ед них д в ух из них в м ес тооператора прис в аив ания м ы м ожем ис пол ь з ов ат ь м етод Assign кл ас с а Rational, ос ущ ес т в л яющ ий коррект ное копиров ание объект а. Пример2.3.3. Н ов ая реал из ац ия м етод ов SetComp и Assign. procedure RVector.SetComp(i: Integer; value: Rational); begin if (i>=1) and (i<=Dim) then self.comp[i].Assign(value) else raise Exception.Create('Index is out of bounds'); end; procedure RVector.Assign(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i].Assign(v.comp[i]); end;
Работу м етод а GetComp с л ед ует из м енит ь т аким образ ом , чтобы 31
он с оз д ав ал нов ы й объект, з начение которогос ов пад ал обы с оз начением self.comp[i], и им енно его в оз в ращ ал в качес т в е рез ул ь т ат а. Как нел ь з я л учше д л я решения этой з ад ачи под ход ит конс т руктор CreateEqualTo кл ас с а Rational.
Пример2.3.4. Н ов ая реал из ац ия м етод а GetComp. function RVector.GetComp(i: Integer): Rational; begin if (i>=1) and (i<=Dim) then result:=Rational.CreateEqualTo(self.comp[i]) else raise Exception.Create('Index is out of bounds'); end;
О каз ы в ает с я д ал ее, чтокром е GetComp, SetComp и Assign с ов м ещ ение ад рес ов объектов произ в од ят т акже м етод ы Plus и Minus. Н о в отл ичие от перв ы х т рех эт и м етод ы не «эконом ят » пам ят ь , т иражируя ад рес а на од ин и тот же объект, а наоборот, перерас ход уют ее, д опус кая ут ечку. Кол ичес т в ообъектов кл ас с а Rational, с оз д ав аем ы х в проц ес с е работ ы кажд огоиз них, в д в ое прев ы шает необход им ое: перв ы е Dim объектов раз м ещ ает в пам ят и конс т руктор Create кл ас с а RVector, в торы е Dim объектов появ л яют с я в ов рем я в ы пол нения ц икл а как рез ул ьт ат в ы з ов ов м етод а Plus кл ас с а Rational. О ператор прис в аив ания result.comp[i]:=self.comp[i].Plus(v.comp[i]) as Rational;
под м еняет ад рес а объектов в м ас с ив е result.comp, т еряя при этом с с ы л ки на пам ят ь , в ы д ел енную конс т руктором RVector.Create. С ам ы й прос той с пос об из бав л ения от л ишних объектов з акл ючает с я в о в в ед ении в с пом огат ел ь ной перем енной д л я в рем енного хранения ад рес а объект а, с оз д ав аем ого м етод ом Plus кл ас с а Rational, и ис пол ь з ов ании м етод а Assign тогоже кл ас с а в м ес тооператора прис в аив ания. В рез ул ьт ат е д л я м етод а RVector.Plus м ы пол учаем с л ед ующ ий код . Пример2.3.5. Вторая реал из ац ия м етод а Plus. function RVector.Plus(v: RVector): RVector; var i: Integer; temp: Rational; begin result:=RVector.Create; for i:=1 to Dim do begin temp:=self.comp[i].Plus(v.comp[i]) as Rational; result.comp[i].Assign(temp);
32
temp.Destroy; end; end;
И с прав л ения д л я м етод а Minus анал огичны . К с ожал ению, прив ед енное решение характ ериз ует с я крайней из бы точнос т ь ю в ы пол няем ы х д ейс т в ий. Вм ес тотого, чтобы од ин раз с оз д ат ь объект кл ас с а Rational и в ы чис л ит ь з начения егопол ей, м ы раз м ещ аем в пам ят и д в а объект а, пол я перв ого(с оз д ав аем огоконс т руктором RVector.Create) иниц иал из ируем чис л ам и 0 и 1, пол я в торого в ы чис л яем , пос л е чего м етод ом Assign произ в од им копиров ание в ы чис л енны х з начений в перв ы й объект и уд ал яем в торой. Н ес кол ь ко бол ее эффект ив ная реал из ац ия ос нов ана на ис пол ь з ов ании в м ес том етод а Plus кл ас с а Rational м етод а Add. Пример2.3.6. Трет ь я реал из ац ия м етод а Plus. function RVector.Plus(v: RVector): RVector; var i: Integer; begin result:=RVector.Create; for i:=1 to Dim do begin result.comp[i].Assign(self.comp[i]); result.comp[i].Add(v.comp[i]); end; end;
С начал а м етод ом Rational.Assign м ы копируем з начение объект а self.comp[i] в уже с оз д анны й объект result.comp[i], а з ат ем прибав л яем к пос л ед нем у v.comp[i]. Тогоже эффект а м ожнод обит ь с я, ис пол ь з уя в м ес том етод ов кл ас с а Rational од ноим енны е м етод ы кл ас с а RVector. Пример2.3.7. Ч ет в ерт ая реал из ац ия м етод а Plus. function RVector.Plus(v: RVector): RVector; var i: Integer; begin result:=RVector.Create; result.Assign(self); result.Add(v); end;
О тл ичие д анной реал из ац ии л ишь в поряд ке в ы пол нения д ейс т в ий: в с е
33
ком понент ы т екущ егов ектора, ад рес уем огоуказ ат ел ем self, копируют с я в ком понент ы в ектора result, и л ишь пос л е этого к ним прибав л яют с я ком понент ы в ектора v. И в том , и в д ругом с л учае, од нако, пол нос т ь ю ус т ранит ь из бы точны е д ейс т в ия не уд ает с я. Л ишним по-прежнем у яв л яет с я копиров ание т екущ егов ектора в ов нов ь с оз д анны й, и из бав ит ь с я от негом ожно, тол ь коиз м енив с ам у проц ед уру с оз д ания в ектора. Зд ес ь в оз м ожны по крайней м ере д в а решения. Перв ое — д обав ит ь к кл ас с у RVector конс т руктор, анал огичны й Rational.CreateEqualTo и с оз д ающ ий копию з ад анногов ектора. Второе — реал из ов ат ь в оз м ожнос т ь с оз д ания объект а кл ас с а RVector без раз м ещ ения в пам ят и его ком понент и иниц иал из ац ии эл ем ентов м ас с ив а comp их ад рес ам и. При этом указ анная в оз м ожнос т ь не д ол жна пред ос т ав л ят ь с я ни пол ь з ов ат ел ю кл ас с а, ни кл ас с у-нас л ед нику, пос кол ь ку они не им еют д ос т упа к пол ю comp и не с м огут прис в оит ь его эл ем ент ам реал ь ны е з начения (с т арая в ерс ия м етод а SetComp поз в ол ял а этос д ел ат ь , ном ы решил и от нее от каз ат ь с я). Т аким образ ом , конс т руктор, с оз д ающ ий в ектор опис анны м с пос обом , д ол жен бы т ь объяв л ен з акры т ы м . Реал из уем в торой в ариант. Н ов ы й конс т руктор буд ет наз ы в ат ь с я CreateEmpty и им ет ь с л ед ующ ий т рив иал ь ны й код . Пример2.3.8. Реал из ац ия конс т руктора CreateEmpty. constructor RVector.CreateEmpty; begin inherited Create; end;
М етод ы Plus и Minus т еперь в оз в ращ ают с я практ ичес ки к с в оем у перв оначал ь ном у в ид у. Пример2.3.9. О кончат ел ь ная реал из ац ия м етод ов Plus и Minus. function RVector.Plus(v: RVector): RVector; var i: Integer; begin result:=RVector.CreateEmpty; for i:=1 to Dim do result.comp[i]:=self.comp[i].Plus(v.comp[i]) as Rational; end; function RVector.Minus(v: RVector): RVector; var i: Integer;
34
begin result:=RVector.CreateEmpty; for i:=1 to Dim do result.comp[i]:=self.comp[i].Minus(v.comp[i]) as Rational; end;
Пос кол ь ку конс т руктор CreateEmpty не раз м ещ ает в пам ят и ком понент ы в ектора result, оператор прис в аив ания д ел ает им енно т о, что нужно — ад рес объект а, с оз д анногом етод ом Rational.Plus ил и Rational.Minus з апис ы в ает с я в i-й эл ем ент м ас с ив а result.comp, д о этого ничего не с од ержав ший. Ут ечки пам ят и при этом нет. Пос л ед нее, чтом ы д ол жны с д ел ат ь , — этод обав ит ь к з акры том у инт ерфейс у кл ас с а RVector объяв л ение конс т руктора CreateEmpty. О т м ет им , что это д ейс т в ие под пад ает под пункт 6 общ ей с хем ы решения, прив ед енной в з ад аче 2.1. О кончат ел ь ны й код м од ул я UnitRVector с ов с ем и ис прав л ениям и с од ержит Пример2.3.10. Вторая в ерс ия м од ул я UnitRVector. unit UnitRVector; interface uses UnitRational; const Dim = 5; type RVector = class private comp: array[1..Dim] of Rational; constructor CreateEmpty; public function Plus (v: RVector): RVector; function Minus (v: RVector): RVector; function ScalarProduct(v: RVector): Rational; function IsEqual (v: RVector): Boolean; procedure Add (v: RVector); procedure Subtract(v: RVector); procedure Assign (v: RVector); function GetLength: Real; function GetComp(i: Integer): Rational; procedure SetComp(i: Integer; value: Rational); function AsString: String; constructor Create; destructor Destroy; override; end; implementation uses SysUtils;
35
function RVector.Plus(v: RVector): RVector; var i: Integer; begin result:=RVector.CreateEmpty; for i:=1 to Dim do result.comp[i]:=self.comp[i].Plus(v.comp[i]) as Rational; end; function RVector.Minus(v: RVector): RVector; var i: Integer; begin result:=RVector.CreateEmpty; for i:=1 to Dim do result.comp[i]:=self.comp[i].Minus(v.comp[i]) as Rational; end; procedure RVector.Add(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i].Add(v.comp[i]); end; procedure RVector.Subtract(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i].Subtract(v.comp[i]); end; function RVector.ScalarProduct(v: RVector): Rational; var i: Integer; begin result:=Rational.Create; for i:=1 to Dim do result.Add(self.comp[i].Dot(v.comp[i])); end; function RVector.GetLength: Real; var i: Integer; begin result:=0; for i:=1 to Dim do result:=result+sqr(self.comp[i].GetNum/self.comp[i].GetDen); result:=sqrt(result); end; function RVector.IsEqual(v: RVector): Boolean; var i: Integer; begin result:=true; for i:=1 to Dim do if not(self.comp[i].IsEqual(v.comp[i])) then result:=false; end; procedure RVector.Assign(v: RVector); var i: Integer; begin for i:=1 to Dim do self.comp[i].Assign(v.comp[i]); end; function RVector.GetComp(i: Integer): Rational;
36
begin if (i>=1) and (i<=Dim) then result:=Rational.CreateEqualTo(self.comp[i]) else raise Exception.Create('Index is out of bounds'); end; procedure RVector.SetComp(i: Integer; value: Rational); begin if (i>=1) and (i<=Dim) then self.comp[i].Assign(value) else raise Exception.Create('Index is out of bounds'); end; function RVector.AsString: String; var i: Integer; begin result:='('; for i:=1 to Dim do begin result:=result+self.comp[i].AsString; if i
Теперь д ем онс т рац ионная програм м а из прим ера 2.3.2 ус пешно работ ает и в ы д ает коррект ны й рез ул ьт ат. Н оус покаив ат ь с я рано: ут ечке пам ят и под в ержен ещ е од ин м етод — ScalarProduct. Ч тобы в этом убед ит ь с я, д ос т аточнопод с чит ат ь кол ичес т в ообъектов кл ас с а Rational, с оз д ав аем ы х в проц ес с е егоработ ы . Перв ы й из них раз м ещ ает с я в пам ят и инс т рукц ией result:=Rational.Create;
37
Зат ем на кажд ой ит ерац ии ц икл а появ л яет с я ещ е од ин — рез ул ьт ат работ ы м етод а Dot, — ад рес которого ис пол ь з ует с я в качес т в е аргум ент а м етод а Add и пос л е этого т еряет с я. В итоге в м ес то од ного объект а м ы пол учаем Dim+1. Зд ес ь , к с ожал ению, обойт ис ь без с оз д ания в с пом огат ел ь ны х объектов не уд ас т с я. Поэтом у в с е, что м ы м ожем с д ел ат ь , — это в в ес т и в с пом огат ел ь ную перем енную д л я хранения их ад рес ов . Пример2.3.11. Ул учшенная реал из ац ия м етод а ScalarProduct. Верс ия 1. function RVector.ScalarProduct(v: RVector): Rational; var i: Integer; temp: Rational; begin result:=Rational.Create; for i:=1 to Dim do begin temp:=self.comp[i].Dot(v.comp[i]) as Rational; result.Add(temp); temp.Destroy; end; end;
Прив ед ение т ипа с пом ощ ь ю оператора as необход им о в в ид у того, что м етод Dot форм ал ь но в оз в ращ ает с с ы л ку на объект кл ас с а Number, а перем енная temp им еет т ип Rational. М ожно прив ес т и и д ругую в ерс ию той же функц ии, в которой в с пом огат ел ь ная перем енная с оз д ает с я и уд ал яет с я в с его од ин раз , но при этом на кажд ой инт ерац ии ц икл а д опол нит ел ь но ос ущ ес т в л яет с я прис в аив ание м етод ом Assign. Н а в опрос отом , яв л яет с я л и д анны й в ариант бол ее эффект ив ны м , т руд нод ат ь од ноз начны й от в ет. Пример2.3.12. Ул учшенная реал из ац ия м етод а ScalarProduct. Верс ия 2. function RVector.ScalarProduct(v: RVector): Rational; var i: Integer; temp: Rational; begin result:=Rational.Create; temp:=Rational.Create; for i:=1 to Dim do begin temp.Assign(self.comp[i]); // дополнительное присваивание temp.MultiplyBy(v.comp[i]); result.Add(temp); end; temp.Destroy; end;
38
И , наконец , ещ е од ин м етод м ожет с т ат ь ис точником т еперь уже пот енц иал ь ной ут ечки пам ят и. Речь ид ет оконс т рукторе Create. Вс пом ним , чтов Object Pascal конс т руктор м ожет бы т ь в ы з в ан не тол ь код л я с оз д ания нов огообъект а, нои д л я переиниц иал из ац ии с ущ ес т в ующ его. Н аш конс т рукт ор Create при этом прос тораз м ес т ит з анов о в с е ком понент ы в ектора, напрочь з абы в об уже с ущ ес т в ующ их объект ах. Ч тобы ис кл ючит ь т акую в оз м ожнос т ь , д обав им к код у конс т руктора пров ерку на рав енс т в о указ ат ел я нул ю (т. е. в д анном с л учае nil). Е с л и ком понент а с ущ ес т в ует, ее нужноне раз м ещ ат ь в пам ят и з анов о, а л ишь переиниц иал из иров ать пов торны м в ы з ов ом конс т руктора Rational.Create. Пример2.3.13. О кончат ел ь ная реал из ац ия конс т руктора Create. constructor RVector.Create; var i: Integer; begin inherited Create; for i:=1 to Dim do if self.comp[i]<>nil then self.comp[i].Create else self.comp[i]:=Rational.Create; end;
В з акл ючение решения д анной з ад ачи от м ет им , чтоприв ед енны й в ы ше код д ем онс т рирует объект ное, но не объект но-ориент иров анное програм м иров ание. Х отя форм ал ь ноэл ем ент ы м ас с ив а comp и парам ет ры м етод ов кл ас с а RVector им еют т ип Rational, в д ейс т в ит ел ь нос т и они м огут с од ержат ь ад рес а объектов , принад л ежащ их л юбы м кл ас с ам , произ в од ны м от Rational. В этом с л учае и рез ул ьт ат, с кажем , операц ии ScalarProduct т акже не обяз ан принад л ежат ь кл ас с у Rational. С л ед ов ат ел ь но, м ы не м ожем с оз д ат ь перем енную result в ы з ов ом конс т руктора, яв но указ ав им я д анногокл ас с а. Бол ее под робное обс ужд ение эт их в опрос ов отл ожим д оз ад ачи 3.1. Задача 2.4 яв л яет с я обобщ ением пред ы д ущ ей. Требует с я из м енит ь кл ас с RVector т ак, чтобы егообъект ы пред с т ав л ял и в екторы произ в ол ь ной раз м ернос т и и эт а раз м ернос т ь м огл а из м енят ь с я пол ь з ов ат ел ем кл ас с а. Реш ение. 1. Ч тобы под черкнут ь ос нов ное отл ичие д анногокл ас с а от пред ы д ущ его, наз ов ем егоDynamicRVector. 2. Пос кол ь ку раз м ернос т ь в ектора с огл ас ноус л ов ию з ад ачи м ожет 39
из м енят ь с я в проц ес с е с ущ ес т в ов ания объект а, к от кры т ом у инт ерфейс у кл ас с а необход им од обав ит ь м етод ы , поз в ол яющ ие опред ел ят ь и м од ифиц иров ат ь ее. О чев ид но, раз м ернос т ь характ ериз ует с я од ним ц ел ы м чис л ом , поэтом у д анны е м ет од ы буд ут им ет ь с л ед ующ ие объяв л ения: function GetDim: Integer; // определение размерности procedure SetDim(value: Integer); // изменение размерности
3. Яс но, что д л я хранения ад рес ов ком понент в ектора т еперь необход им а с т рукт ура д анны х перем енного раз м ера. Принц ипы пос т роения т аких с т рукт ур хорошо из в ес т ны , од нако з д ес ь м ы не буд ем в ручную с оз д ав ат ь под ход ящ ую конс т рукц ию, а в ос пол ь з уем с я готов ы м т ипом Object Pascal — дин а миче ским ма ссивом (dynamic array), — которы й как нел ь з я л учше с оот в ет с т в ует нашим ц ел ям . Дл я коррект ногоис пол ь з ов ания д инам ичес ких м ас с ив ов нам необход им ы некоторы е с в ед ения об их в нут ренней с т рукт уре. О бъяв л ение перем енной, пред с т ав л яющ ей с обой (од ном ерны й) д инам ичес кий м ас с ив , в ы гл яд ит с л ед ующ им образ ом : ИмяПеременной: array of ИмяТипа;
гд е И м я Типа опред ел яет т ип эл ем ентов м ас с ив а. Данная перем енная, как и объект ная, в д ейс т в ит ел ь нос т и яв л яет с я указ ат ел ем с о в с ем и в ы т екающ им и от с юд а пос л ед с т в иям и. И з начал ь но, з начение указ ат ел я рав ноnil. Дл я реал ь ногораз м ещ ения в пам ят и и уд ал ения из нее эл ем ент ов м ас с ив а ис пол ь з ует с я проц ед ура SetLength, приним ающ ая д в а парам ет ра: им я перем еннойм ас с ив а и д л ину. Е с л и нов ая д л ина бол ь ше с т арой, к м ас с ив у д обав л яют с я эл ем ент ы (з начения уже им еющ ихс я при эт ом с охраняют с я), ес л и м ень ше — м ас с ив ус екает с я д о указ анногораз м ера. В час т нос т и, д л я пол ного уд ал ения м ас с ив а из пам ят и с л ед ует ус т анов ит ь его д л ину рав ной нул ю. Текущ ую д л ину м ас с ив а в оз в ращ ает функц ия Length, приним ающ ая в качес т в е ед инс т в енногопарам ет ра им я перем енной-м ас с ив а. О бращ ение к эл ем ент ам м ас с ив а ос ущ ес т в л яет с я обы чны м образ ом с пом ощ ь ю оператора инд екс иров ания. При этом инд екс ы в с егд а яв л яют с я ц ел ы м и чис л ам и и начинают с я с нул я. Т аким образ ом , м ас с ив a с ос тоит из эл ем ентов a[0], a[1], … , a[Length(a)-1]. Вы ход инд екс а з а пред ел ы д опус т им огоот рез ка прив од ит к ошибке в ов рем я в ы пол нения. Вернем с я т еперь к нашем у кл ас с у и опред ел им пол е comp с л ед ующ им образ ом : comp: array of Rational;
Пос кол ь ку д л ину этого м ас с ив а в с егд а м ожно опред ел ит ь с пом ощ ь ю 40
функц ии Length, в в од ит ь с пец иал ь ное пол е д л я хранения раз м ернос т и в ектора не т ребует с я. О чев ид но, чтоконс т руктор и д ес т руктор нов ом у кл ас с у по-прежнем у необход им ы и ничегов их объяв л ениях ис прав л ят ь не нужно. Поэтом у м ы переход им с раз у к реал из ац ии м етод ов , которая прет ерпит бол ь шие из м енения. 5. Прежд е в с егонам необход им ореал из ов ат ь в нов ь д обав л енны е м етод ы GetDim и SetDim. Как уже бы л о от м ечено, опред ел ит ь раз м ернос т ь в ектора очень л егко: она рав на д л ине м ас с ив а comp. Поэтом у м ы пол учаем с л ед ующ ий код . Пример2.4.1. Реал из ац ия м етод а GetDim. function DynamicRVector.GetDim: Integer; begin result:=Length(self.comp); end;
С из м енением раз м ернос т и д ел ообс тоит с л ожнее. Во-перв ы х, м ы д ол жны отд ел ь но рас с м ат рив ат ь с л учаи ее ув ел ичения и ум ень шения. Во-в торы х, кром е м анипул яц ий с м ас с ив ом comp необход им оуправ л ят ь и с оот в ет с т в ующ им и ком понент ам и в ектора — объект ам и кл ас с а Rational. В перв ом с л учае с л ед ует раз м ещ ат ь в пам ят и нов ы е объект ы , в о в тором — уд ал ят ь л ишние, причем раз м ещ ение нужнопроиз в ес т и пос л е ув ел ичения м ас с ив а, чтобы бы л о куд а з апис ы в ат ь ад рес а объектов , а уд ал ение — наоборот, д оум ень шения, иначе неот куд а буд ет эт и ад рес а брат ь . Добал яя к с каз анном у з ащ ит у от некоррект ны х (м ень ше нул я) и в ы рожд енны х (рав ны х т екущ ей) з начений раз м ернос т и, м ы пол учаем с л ед ующ ее. Пример2.4.2. Реал из ац ия м етод а SetDim. procedure DynamicRVector.SetDim(value: Integer); var i, OldDim: Integer; begin // случай некорректного значения if value<0 then raise Exception.Create('Invalid value'); // случай уменьшения размерности if value
41
// случай увеличения размерности else if value>Length(self.comp) then begin // OldDim – индекс первого из вновь добавленных элементов OldDim:=Length(self.comp); // увеличение длины массива SetLength(self.comp, value); // размещение в памяти новых компонент for i:=OldDim to value-1 do self.comp[i]:=Rational.Create; end; // вырожденный случай value=Length(self.comp) // исключен последним условием end;
Таким образ ом , проц ед ура SetDim факт ичес ки берет на с ебя функц ии и конс т руктора, и д ес т руктора. Поэтом у их код с т анов ит с я з начит ел ь нопрощ е. Пример2.4.3. Реал из ац ия конс т руктора Create и д ес т руктора. constructor DynamicRVector.Create; begin inherited Create; self.SetDim(0); end; destructor DynamicRVector.Destroy; begin self.SetDim(0); inherited Destroy; end;
Бы т ь м ожет, кажет с я нес кол ь кос т ранны м , чтоконс т руктор и д ес т руктор в ы пол няют практ ичес ки од ни и т е же д ейс т в ия. О д нако, учит ы в ая от с ут с т в ие у конс т руктора парам ет ра, с пос обного принят ь в ел ичину раз м ернос т и в ектора, т акое егопов ед ение пред с т ав л яет с я наибол ее ес т ес т в енны м . Дал ь нейшие м од ификац ии оказ ы в ают с я в ес ь м а од нот ипны м и. Во-перв ы х, в ов с ех ц икл ах необход им оис прав ит ь границ ы из м енения парам ет ра, прив ед я их в с оот в ет с т в ие с д опус т им ы м д иапаз оном инд екс ов м ас с ив а comp. А нал огичны е ис прав л ения с л ед ует в нес т и в код м етод ов GetComp и SetComp, гд е т акже пров еряет с я коррект нос т ь инд екс а. При этом , в ы чит ая из з начения парам ет ра ед иниц у, м ы л егком ожем обес печит ь т ранс л яц ию д иапаз она [0, GetDim-1] в бол ее прив ы чны й пол ь з ов ат ел ю [1, GetDim] (т ем с ам ы м , м ы пол учаем ещ е од ин прим ер пол ез нос т и конц епц ии с окры т ия реал из ац ии).
42
Во-в торы х, м етод ы , оперирующ ие д в ум я в екторам и (Plus, Minus, Add, Subtract, ScalarProduct, Assign), с с од ержат ел ь ной т очки з рения м огут прим енят ь с я л ишь к в екторам од инаков ой раз м ернос т и. Поэтом у к ним с л ед ует д обав ит ь с оот в ет с т в ующ ие пров ерки, в оз бужд ающ ие при необход им ос т и ис кл ючение. О с обы е прав ил а нужны л ишь д л я м етод а IsEqual, гд е нерав енс т в ораз м ернос т ей д опус т им ои прос тооз начает нерав енс т в ов екторов . Зам ет им т акже, что м етод ам Plus и Minus необход им а в оз м ожнос т ь с оз д ав ат ь «пус той» в ектор (т. е. без объектов -ком понент ) з ад анной раз м ернос т и. Ранее д л я этой ц ел и нам и бы л в в ед ен з акры т ы й конс т руктор CreateEmpty. М од ифиц ируем его, д обав ив к з агол ов ку од ин парам ет р ц ел огот ипа и из м енив реал из ац ию с оот в ет с т в ующ им образ ом . Рез ул ьт ат в с ех ис прав л ений с од ержит Пример2.4.4. Реал из ац ия м етод ов кл ас с а DynamicRVector. function DynamicRVector.Plus(v: DynamicRVector): DynamicRVector; var i: Integer; begin if Length(self.comp)=Length(v.comp) then begin result:=DynamicRVector.CreateEmpty(Length(self.comp)); for i:=0 to Length(self.comp)-1 do result.comp[i]:=self.comp[i].Plus(v.comp[i]) as Rational; end else raise Exception.Create('Invalid Operation'); end; function DynamicRVector.Minus(v: DynamicRVector): DynamicRVector; var i: Integer; begin if Length(self.comp)=Length(v.comp) then begin result:=DynamicRVector.CreateEmpty(Length(self.comp)); for i:=0 to Length(self.comp)-1 do result.comp[i]:=self.comp[i].Minus(v.comp[i]) as Rational; end else raise Exception.Create('Invalid Operation'); end; procedure DynamicRVector.Add(v: DynamicRVector); var i: Integer; begin if Length(self.comp)=Length(v.comp) then for i:=0 to Length(self.comp)-1 do self.comp[i].Add(v.comp[i]) else raise Exception.Create('Invalid Operation'); end;
43
procedure DynamicRVector.Subtract(v: DynamicRVector); var i: Integer; begin if Length(self.comp)=Length(v.comp) then for i:=0 to Length(self.comp)-1 do self.comp[i].Subtract(v.comp[i]) else raise Exception.Create('Invalid Operation'); end; function DynamicRVector.ScalarProduct(v: DynamicRVector): Rational; var i: Integer; temp: Rational; begin if Length(self.comp)=Length(v.comp) then begin result:=Rational.Create; for i:=0 to Length(self.comp)-1 do begin temp:=self.comp[i].Dot(v.comp[i]) as Rational; result.Add(temp); temp.Destroy; end; end else raise Exception.Create('Invalid Operation'); end; function DynamicRVector.GetLength: Real; var i: Integer; begin result:=0; for i:=0 to Length(self.comp)-1 do result:=result+sqr(self.comp[i].GetNum/self.comp[i].GetDen); result:=sqrt(result); end; function DynamicRVector.IsEqual(v: DynamicRVector): Boolean; var i: Integer; begin if Length(self.comp)=Length(v.comp) then begin result:=true; for i:=0 to Length(self.comp)-1 do if not(self.comp[i].IsEqual(v.comp[i])) then result:=false; end else result:=false; // исключение не возбуждается end; procedure DynamicRVector.Assign(v: DynamicRVector); var i: Integer; begin if Length(self.comp)=Length(v.comp) then for i:=0 to Length(self.comp)-1 do self.comp[i].Assign(v.comp[i]) else raise Exception.Create('Invalid Operation'); end;
44
function DynamicRVector.GetComp(i: Integer): Rational; begin if (i>=1) and (i<=Length(self.comp)) then result:=Rational.CreateEqualTo(self.comp[i-1]) else raise Exception.Create('Index is out of bounds'); end; procedure DynamicRVector.SetComp(i: Integer; value: Rational); begin if (i>=1) and (i<=Length(self.comp)) then self.comp[i-1].Assign(value) else raise Exception.Create('Index is out of bounds'); end; function DynamicRVector.AsString: String; var i: Integer; begin result:='('; for i:=0 to Length(self.comp)-1 do begin result:=result+self.comp[i].AsString; if i
7. Е д инс т в енны м з ам ет ны м отл ичием м од ул я, с од ержащ егокл ас с DynamicRVector, от рас с м от ренны х ранее яв л яет с я от с ут с т в ие объяв л ения конс т ант ы Dim. Теперь уже в с я информ ац ия, опис ы в ающ ая объект,
с од ержит с я в нем с ам ом . Пример2.4.5. Раз м ещ ение кл ас с а DynamicRVector в нут ри м од ул я. unit UnitDynamicRVector; interface uses UnitRational; type DynamicRVector = class private comp: array of Rational; constructor CreateEmpty(d: Integer); public function Plus (v: DynamicRVector): DynamicRVector; function Minus (v: DynamicRVector): DynamicRVector; function ScalarProduct(v: DynamicRVector): Rational;
45
function procedure procedure procedure function function procedure function procedure function constructor destructor end;
IsEqual (v: DynamicRVector): Boolean; Add (v: DynamicRVector); Subtract(v: DynamicRVector); Assign (v: DynamicRVector); GetLength: Real; GetComp(i: Integer): Rational; SetComp(i: Integer; value: Rational); GetDim: Integer; SetDim(value: Integer); AsString: String; Create; Destroy; override;
implementation uses SysUtils; // Здесь размещается реализация методов, // приведенная в примерах 2.4.1 - 2.4.4. end.
8. Теперь , с оз д ав ая в ектор, м ы д ол жны не тол ь ков ы з ы в ат ь конс т руктор кл ас с а DynamicRVector, но и обяз ат ел ь но з ад ав ат ь его раз м ернос т ь с пом ощ ь ю м етод а SetDim. Кром е того, необход им о с л ед ит ь з а т ем , чтобы в ектор, перед ав аем ы й в качес т в е парам ет ра каком у-л ибом етод у нашегокл ас с а, им ел т у же раз м ернос т ь , чтои в ектор, д л я которого д анны й м етод в ы з в ан. Т аков а пл ат а з а унив ерс ал ь нос т ь решения. М од ифиц иров анны й с оот в ет с т в ующ им образ ом код програм м ы с д опол нит ел ь ной д ем онс т рац ией коррект нос т и работ ы проц ед уры SetDim при ум ень шении раз м ернос т и с од ержит Пример2.4.6. Дем онс т рац ионная програм м а д л я кл ас с а DynamicRVector. program TestDynamicRVector; {$APPTYPE CONSOLE} uses UnitDynamicRVector in 'UnitDynamicRVector.pas', UnitRational in 'UnitRational.pas'; var v1, v2, v3, v4: DynamicRVector; r, p: Rational; i: Integer; begin v1:=DynamicRVector.Create; v2:=DynamicRVector.Create; r:=Rational.Create; v1.SetDim(9); // сначала создаются векторы различной размерности for i:=1 to 9 do begin r.SetNum(i); r.SetDen(i+1); v1.SetComp(i, r); end;
46
v2.SetDim(7); for i:=1 to 7 do begin r.SetNum(i+1); r.SetDen(i+2); v2.SetComp(i, r); end; WriteLn('v1: '+v1.AsString); WriteLn('v2: '+v2.AsString); WriteLn('v1.GetLength: ', v1.GetLength:5:5); v1.SetDim(5); v2.SetDim(5); // затем размерность выравнивается WriteLn('v1 after v1.SetDim(5): '+v1.AsString); // демонстрация WriteLn('v2 after v2.SetDim(5): '+v2.AsString); // корректности v3:=v1.Plus(v2); WriteLn('v3 after v3:=v1.Plus(v2): '+v3.AsString); v1.Add(v2); WriteLn('v1 after v1.Add(v2): '+v1.AsString); if v1.IsEqual(v3) then WriteLn('v1 is equal to v3') else WriteLn('v1 is not equal to v3'); v4:=DynamicRVector.Create; v4.SetDim(5); v4.Assign(v2); WriteLn('v4 after v4.Assign(v2): '+v4.AsString); v4.Subtract(v1); WriteLn('v2 after v4.Subtract(v1): '+v2.AsString); WriteLn('v4 after v4.Subtract(v1): '+v4.AsString); p:=v2.ScalarProduct(v4); WriteLn('v2.ScalarProduct(v4): '+p.AsString); p.Destroy; r.Destroy; v1.Destroy; v2.Destroy; v3.Destroy; v4.Destroy; ReadLn; end.
47
3. У Н И В Е РСА Л ЬН Ы Е К О Н Т Е Й Н Е РЫ В этом параграфе нашей ц ел ь ю буд ет с оз д ание кл ас с а д л я пред с т ав л ения в екторов , ком понент ы которы х м огут принад л ежат ь л юбы м кл ас с ам , унас л ед ов анны м от Number. Е с т ес т в енно, что опират ь с я м ы буд ем на с оз д анны й в пред ы д ущ ей з ад аче кл ас с DynamicRVector. О чев ид нот акже, чтокод этогокл ас с а нужнобуд ет из м енит ь т аким образ ом , чтобы в нем не ос т ал ос ь никаких упом инаний орац ионал ь ны х чис л ах. И з д ес ь в оз никает од ин принц ипиал ь ны й в опрос : чем з ам енит ь инс т рукц ию Rational.Create в м етод ах ScalarProduct и SetDim? Бол ее точнопробл ем а форм ул ирует с я с л ед ующ им образ ом : необход им о с оз д ат ь экз ем пл яр кл ас с а, произ в од ного от Number, причем какогоим енно, с т анет из в ес т но тол ь ко в о в рем я в ы пол нения програм м ы . Бол ее того, ис ход нов ектор не с од ержит ни од ной ком понент ы , которую м ожнобы л обы ис пол ь з ов ат ь в качес т в е образ ц а д л я кл ониров ания. Поэтом у д л я хранения информ ац ии от ипе ком понент нужноис пол ь з ов ат ь какой-тод ругой с пос об. В яз ы ке Object Pascal опис анная пробл ем а м ожет бы т ь решена с пом ощ ь ю кл а ссовых ссыл ок (class references). Т ип с с ы л ки на кл ас с опред ел яет с я с л ед ующ им образ ом : ИмяТипаСсылкиНаКласс = class of ИмяКласса
гд е И м я Типа С с ы л киН а Кл а с с — им я нов огот ипа, И м я Кл а с с а — им я л юбогокл ас с а. Пос л е этого перем енны е, яв л яющ иес я с с ы л кам и на указ анны й кл ас с , объяв л яют с я обы чны м образ ом : ИмяСсылкиНаКласс: ИмяТипаСсылкиНаКласс
(ис пол ь з ов ат ь в ы ражение class of И м я Кл а с с а прям ов объяв л ении перем енной нел ь з я). С точки з рения в нут реннегопред с т ав л ения с с ы л ка на кл ас с пред с т ав л яет с обой указ ат ел ь на т абл иц у в ирт уал ь ны х м етод ов д анного кл ас с а. О д наков ы з в ат ь с ее пом ощ ь ю обы чны й в ирт уал ь ны й м етод нев оз м ожно, пос кол ь ку ем у необход им о перед ат ь ад рес конкрет ного объект а. Кл ас с ов ы е с с ы л ки прим еняют с я л ишь д л я в ы з ов а конс т рукторов и с пец иал ь ны х кл а ссовых ме тодов (class methods), которы м объект кл ас с а не нужен. С инт акс ис в ы з ов а т акой же, как и при ис пол ь з ов ании обы чны х объект ны х с с ы л ок: ИмяСсылкиНаКласс.ИмяКонструктораИлиКлассовогоМетода(Параметры)
Х отя кл ас с ов ы е с с ы л ки и яв л яют с я указ ат ел ям и, с т рукт уры д анны х, которы е они ад рес уют, никогд а не м еняют с я и ис пол ь з уют с я тол ь ко 48
д л я чт ения. Поэтом у операт ор прис в аив ания, прим еняем ы й к с с ы л кам и в ы пол няющ ий копиров ание ад рес ов , не порожд ает з д ес ь обы чны х д л я указ ат ел ей пробл ем . Точнот ак же оператор с рав нения на рав енс т в од л я кл ас с ов ы х с с ы л ок д ает в пол не коррект ны й рез ул ьт ат, в ед ь т абл иц а в ирт уал ь ны х м етод ов кл ас с а в с егд а ед инс т в енна и потом у д в е с с ы л ки ад рес уют од ин и тот же кл ас с тогд а и тол ь котогд а, когд а они с од ержат од инаков ы е указ ат ел и. И м я л юбого кл ас с а м ожет рас с м ат рив ат ь с я как конс т ант а с оот в ет с т в ующ его с с ы л очного т ипа. В час т нос т и, прис в аив ание кл ас с ов ой с с ы л ке ад рес а конкрет ногокл ас с а в ы пол няет с я инс т рукц ией ИмяСсылкиНаКласс:=ИмяКласса
(операц ия @ в з ят ия ад рес а не ис пол ь з ует с я). Пол им орфиз м при в ы з ов е м етод а с пом ощ ь ю кл ас с ов ой с с ы л ки обес печив ает с я рас ширенны м и прав ил ам и с ов м ес т им ос т и в от ношении операц ии прис в аив ания: с с ы л ке на кл ас с м ожет бы т ь прис в оен ад рес л юбогокл ас с а, произ в од ногоот того, на которы й она форм ал ь нос с ы л ает с я. Н априм ер, с с ы л ке cn на кл ас с Number, объяв л енной с л ед ующ им образ ом : type ClassNumber = class of Number; var cn: ClassNumber;
с пом ощ ь ю инс т рукц ии cn = Rational;
м ожет бы т ь прис в оен ад рес т абл иц ы в ирт уал ь ны х м етод ов кл ас с а Rational. Пос л е этогов ы з ов с пом ощ ь ю д анной с с ы л ки в ирт уал ь ногоконс т руктора Create: cn.Create;
прив ед ет к с оз д анию объект а кл ас с а Rational, а не Number. Е с л и бы конс т руктор Create не бы л объяв л ен в ирт уал ь ны м , ис пол ь з ов ал с я бы т ип из форм ал ь ногообъяв л ения с с ы л ки, т. е. Number. М ы в ид им , т аким образ ом , чт од л я решения с форм ул иров анной в ы ше пробл ем ы д ос т ат очнов кажд ом объект е-в ект оре с с ам огос оз д ания хранит ь с с ы л ку на кл ас с , котором у д ол жны принад л ежат ь егоком понент ы . С форм ул ируем т еперь бол ее точноочеред ную з ад ачу.
49
Задача 3.1. Требует с я раз работ ат ь кл ас с , объект ы которогопред с т ав л яют в екторы произ в ол ь ной раз м ернос т и. Кл ас с д ол жен с од ержат ь от кры т ы е м етод ы , в ы пол няющ ие т е же операц ии, что и под програм м ы библ иот еки из з ад ачи 1.1, з а ис кл ючением опред ел ения д л ины в ектора. При этом ком понент ы в ект оров м огут принад л ежат ь л юбом у фикс иров анном у кл ас с у, произ в од ном у от Number. Как обы чно, т ребует с я пом ес т ит ь пол ученны й кл ас с в отд ел ь ны й м од ул ь и с оз д ат ь програм м у, ис пол ь з ующ ую д анны й м од ул ь и д ем онс т рирующ ую работ у с объект ам и кл ас с а. Реш ение. 1. Н ов ы й кл ас с ес т ес т в енноназ в ат ь DynamicVector . 2. О т кры т ы й инт ерфейс эт ого кл ас с а пол учает с я из инт ерфейс а кл ас с а DynamicRVector очень л егко: пут ем з ам ены в с ех ид ент ификаторов Rational на Number и DynamicRVector на DynamicVector . Появ ит с я, в прочем , и кое-чтонов ое: объект кл ас с а с од ержит с в ед ения от ипе егоком понент и у пол ь з ов ат ел я д ол жна бы т ь в оз м ожнос т ь эт и д анны е уз нав ат ь и м од ифиц иров ат ь . О д накоиз м енение т ипа ком понент уже с ущ ес т в ующ егов ектора в ряд л и необход им о. Поэтом у в м ес тообы чной пары м етод ов д л я чт ения/з апис и м ы д обав им к кл ас с у м етод GetCompType, в оз в ращ ающ ий т ребуем ую информ ац ию, и конс т руктор CreateOfType, поз в ол яющ ий опред ел ят ь ее при с оз д ании ил и переиниц иал из ац ии объект а. Поус л ов ию з ад ачи ком понент ы в ектора м огут принад л ежат ь л юбом у кл ас с у, произ в од ном у от Number. С л ед ов ат ел ь но, т ип в оз в ращ аем огоз начения м етод а GetCompType и ед инс т в енны й парам ет р конс т руктора CreateOfType д ол жны им ет ь т ип «с с ы л ка на кл ас с Number» , (он уже опред ел ен в м од ул е UnitNumber и наз ы в ает с я ClassNumber). И т ак, м ы пол учаем Пример3.1.1. О т кры т ы й инт ерфейс кл ас с а DynamicVector. function function function function procedure procedure procedure function procedure function procedure
Plus (v: DynamicVector): DynamicVector; Minus (v: DynamicVector): DynamicVector; ScalarProduct(v: DynamicVector): Number; IsEqual (v: DynamicVector): Boolean; Add (v: DynamicVector); Subtract(v: DynamicVector); Assign (v: DynamicVector); GetComp(i: Integer): Number; SetComp(i: Integer; value: Number); GetDim: Integer; SetDim(value: Integer);
50
function function constructor constructor destructor
GetCompType: ClassNumber; AsString: String; Create; CreateOfType(ct: ClassNumber); Destroy; override;
Зам ет им , чтоед инс т в енны м с пос обом с охранения м етод а в ы чис л ения д л ины в ектора бы л обы из м енение егоз агол ов ка на function GetLength: Number;
пос кол ь ку неопред ел еннос т ь т ипа ком понент в ектора оз начает неопред ел еннос т ь в оз в ращ аем огоз начения д анногом етод а. Н отогд а к кл ас с у Number с л ед ов ал обы д обав ит ь абс т ракт ны й м етод в ы чис л ения кв ад рат ногокорня из чис л а, з ам ещ аем ы й в кажд ом егонас л ед нике. Т ак как корень из рац ионал ь ного чис л а яв л яет с я, в ообщ е гов оря, чис л ом в ещ ес т в енны м , нам пот ребов ал ос ь бы т акже с оз д ат ь потом ка кл ас с а Number д л я пред с т ав л ения в ещ ес т в енны х чис ел , а с л ед ом з а ним и ком пл екс ны х (ес л и тол ь ком ы с чит аем , чт окорень из от риц ат ел ь ногочис л а с ущ ес т в ует ). С оот в ет с т в ующ ий код не с л ишком с л ожен, но в ес ь м а объем ен, поэтом у реал из ац ия нам еченной с хем ы ос т ав л яет с я чит ат ел ю в качес т в е упражнения. 3. О бъяв л ение пол я comp т акже с л ед ует ис прав ит ь , з ам енив Rational на Number: comp: array of Number;
Кром е того, м ы д ол жны д обав ит ь к кл ас с у пол е д л я хранения с с ы л ки на т ип ком понент. Н аз ов ем этопол е comptype: comptype: ClassNumber;
5. Пос кол ь ку конс т рукторы и д ес т руктор уже бы л и объяв л ены в пункт е 2, обрат им с я к реал из ац ии м етод ов . Как обы чно, м ы прос тоис прав им код , в з ят ы й из пред ы д ущ ей з ад ачи. Кром е т рив иал ь ногоприв ед ения з агол ов ков м етод ов в с оот в ет с т в ие с инт ерфейс ом , из м енения необход им о буд ет в нес т и в т е учас т ки код а, гд е произ в од ит с я с оз д ание нов ы х объектов . Т ак, в м етод ах ScalarProduct, GetComp и SetDim в ы ражение Rational.Create с л ед ует з ам енит ь на self.comptype.Create. В рез ул ьт ат е эт и м етод ы в с егд а буд ут с оз д ав ат ь объект ы тогоже т ипа, чтои ком понент ы в ектора. Кром е того, в м етод е ScalarProduct необход им о из м енит ь кл ас с в с пом огат ел ь ной перем енной temp с Rational на Number и уд ал ит ь ненужное т еперь прив ед ение рез ул ьт ат а м етод а Dot к т ипу Rational (з ам ет им , в прочем , что и в м етод е кл ас с а DynamicRVector перем енная temp м огл а принад л ежат ь кл ас с у
51
Number, т ип Rational бы л ис пол ь з ов ан ис кл ючит ел ь нод л я нагл яд нос т и). Т акже л ишним и оказ ы в ают с я прив ед ения т ипа в м етод ах Plus и Minus.
Переход я к реал из ац ии конс т рукторов , обс уд им прежд е в с егов опрос окоррект нос т и объект а и с охранении егоц ел ос т нос т и. О чев ид но, чтона протяжении в с ей жиз ни объект а егопол е comptype д ол жнос од ержат ь с с ы л ку на некоторы й конкрет ны й кл ас с . Н икакогораз ум ногоз начения поум ол чанию з д ес ь пред л ожит ь нев оз м ожно, т ак как кл ас с Number яв л яет с я абс т ракт ны м , а ничем бол ее под ход ящ им м ы не рас пол агаем . С л ед ов ат ел ь но, конс т руктор Create, не приним ающ ий ни од ного парам ет ра, м ожно ис пол ь з ов ат ь тол ь ко д л я переиниц иал из ац ии объект а, ноне д л я егос оз д ания «с нул я» . Внут ри т ел а конс т руктора понят ь , с какой ц ел ь ю он бы л в ы з в ан, м ожно как раз по с од ержим ом у пол я comptype: егоначал ь ное з начение рав ноnil. Е д инс т в енны м с пос обом с оз д ания в ектора с т анов ит с я, т аким образ ом , конс т руктор CreateOfType. К с ожал ению, ус т анов ит ь , яв л яет с я л и его парам ет р с с ы л кой на конкрет ны й ил и абс т ракт ны й кл ас с , нел ь з я. О шибка прояв ит с я л ишь при в ы з ов е нез ам ещ енногоабс т ракт ногом етод а д л я некоторогообъект а-ком понент ы в ектора. Конс т руктору CreateEmpty, ис пол ь з уем ом у м етод ам и Plus и Minus, т акже уд обно с раз у перед ав ат ь т ип ком понент. Дл я этого к з агол ов ку д анногоконс т руктора необход им од обав ит ь парам ет р т ипа ClassNumber. Пол ожим , что з начение т ипа, перед ав аем ое указ анны м и м етод ам и при в ы з ов е, д ол жнобы т ь т ем же, чтои у в ектора, д л я которогоони в ы з в аны . Зам ет им д ал ее, чтов с е ком понент ы од ногов ектора в с егд а обяз аны им ет ь од ин и тот же т ип, с с ы л ку на которы й с од ержит пол е comptype (точнее, принад л ежат ь нас л ед никам кл ас с а, ад рес уем ого эт им пол ем ). Кром е того, т ип ком понент в ектора, перед анного в качес т в е аргум ент а од ном у из м етод ов кл ас с а DynamicVector, д ол жен бы т ь т аким , чтобы рез ул ь т ат в ы пол нения д анногом етод а оказ ал с я опред ел ен. В д ейс т в ит ел ь нос т и, пров ерка эт их ус л ов ий неяв нопроиз в од ит с я м ет од ам и кл ас с а (ил и кл ас с ов ), кот ором у принад л ежат ком понент ы в ект ора. Проц ед ура SetComp, с пец иал ь нопред наз наченная д л я из м енения ком понент ы , в ы з ы в ает д л я этой ц ел и м етод Assign д анной ком понент ы , перед ав ая ем у в качес т в е парам ет ра прис в аив аем ое з начение. Дал ь нейшее пов ед ение опред ел яет с я реал из ац ией указ анногом етод а (наприм ер, м етод Rational.Assign в ы з ов ет ис кл ючение, ес л и з начение парам ет ра не принад л ежит кл ас с у Rational ил и од ном у из его потом ков ), нов л юбом с л учае т ип ком понент ы ос т ает с я без из м енения. А нал огичны м образ ом пос тупают м етод ы Add, Subtract и Assign, 52
ис пол ь з ующ ие од ноим енны е м етод ы ком понент д л я их (ком понент ) м од ификац ии. Зд ес ь неиз м еннос т ь т ипа и, как с л ед с т в ие, ц ел ос т нос т ь объект а т акже очев ид ны . Н аконец , в оз м ожнос т ь в ы пол нения т ребуем ы х д ейс т в ий м етод ам и Plus, Minus и ScalarProduct пол нос т ь ю опред ел яет с я в ы з ы в аем ы м и им и м етод ам и кл ас с а ком понент Plus, Minus и Dot, с оот в ет с т в енно. Гарантиров ат ь , что пос л ед ние в оз в ращ ают объект им енно того кл ас с а, с с ы л ку на которы й уже с од ержит пол е result.comptype, иниц иал из иров анное конс т руктором CreateEmpty, нел ь з я. Поэтом у м ы д ол жны пров ерит ь , что кажд ы й объект result.comp[i] д ейс т в ит ел ь но принад л ежит указ анном у кл ас с у. С д ел ат ь этом ожнос пом ощ ь ю оператора is, в качес т в е аргум ент а которогод опус кает с я ис пол ь з ов ат ь не тол ь ко им я кл ас с а, но и кл ас с ов ую с с ы л ку: if not(result.comp[i] is result.comptype) then ...
Е д инс т в енное, чтом ы д ос их пор не обс уд ил и — этов нов ь объяв л енны й м етод GetCompType. О д нако, егореал из ац ия т рив иал ь на и з акл ючает с я в в оз в рат е з начения пол я comptype. Т аким образ ом , м ы пол учаем Пример3.1.2. Реал из ац ия м етод ов кл ас с а DynamicVector. function DynamicVector.Plus(v: DynamicVector): DynamicVector; var i: Integer; begin if Length(self.comp)=Length(v.comp) then begin result:=DynamicVector.CreateEmpty(self.comptype, Length(self.comp)); for i:=0 to Length(self.comp)-1 do begin result.comp[i]:=self.comp[i].Plus(v.comp[i]); // приведение типа удалено if not(result.comp[i] is result.comptype) then raise Exception.Create('Invalid Operation'); // результат работы метода Plus должен иметь требуемый тип end; end else raise Exception.Create('Invalid Operation'); end; function DynamicVector.Minus(v: DynamicVector): DynamicVector; var i: Integer; begin if Length(self.comp)=Length(v.comp) then begin result:=DynamicVector.CreateEmpty(self.comptype, Length(self.comp)); for i:=0 to Length(self.comp)-1 do begin result.comp[i]:=self.comp[i].Minus(v.comp[i]); // приведение типа удалено
53
if not(result.comp[i] is result.comptype) then raise Exception.Create('Invalid Operation'); // результат работы метода Minus должен иметь требуемый тип end; end else raise Exception.Create('Invalid Operation'); end; procedure DynamicVector.Add(v: DynamicVector); var i: Integer; begin if Length(self.comp)=Length(v.comp) then for i:=0 to Length(self.comp)-1 do self.comp[i].Add(v.comp[i]) else raise Exception.Create('Invalid Operation'); end; procedure DynamicVector.Subtract(v: DynamicVector); var i: Integer; begin if Length(self.comp)=Length(v.comp) then for i:=0 to Length(self.comp)-1 do self.comp[i].Subtract(v.comp[i]) else raise Exception.Create('Invalid Operation'); end; function DynamicVector.ScalarProduct(v: DynamicVector): Number; var i: Integer; temp: Number; begin if Length(self.comp)=Length(v.comp) then begin result:=self.comptype.Create; for i:=0 to Length(self.comp)-1 do begin temp:=self.comp[i].Dot(v.comp[i]); // приведение типа удалено result.Add(temp); temp.Destroy; end; end else raise Exception.Create('Invalid Operation'); end; function DynamicVector.IsEqual(v: DynamicVector): Boolean; var i: Integer; begin if Length(self.comp)=Length(v.comp) then begin result:=true; for i:=0 to Length(self.comp)-1 do if not(self.comp[i].IsEqual(v.comp[i])) then result:=false; end else result:=false; end;
54
procedure DynamicVector.Assign(v: DynamicVector); var i: Integer; begin if Length(self.comp)=Length(v.comp) then for i:=0 to Length(self.comp)-1 do self.comp[i].Assign(v.comp[i]) else raise Exception.Create('Invalid Operation'); end; function DynamicVector.AsString: String; var i: Integer; begin result:='('; for i:=0 to Length(self.comp)-1 do begin result:=result+self.comp[i].AsString; if i=1) and (i<=Length(self.comp)) then result:=self.comptype.CreateEqualTo(self.comp[i-1]) else raise Exception.Create('Index is out of bounds'); end; procedure DynamicVector.SetComp(i: Integer; value: Number); begin if (i>=1) and (i<=Length(self.comp)) then self.comp[i-1].Assign(value) else raise Exception.Create('Index is out of bounds'); end; function DynamicVector.GetDim: Integer; begin result:=Length(self.comp); end; procedure DynamicVector.SetDim(value: Integer); var i, OldDim: Integer; begin if value<0 then raise Exception.Create('Invalid value'); if valueLength(self.comp) then begin OldDim:=Length(self.comp); SetLength(self.comp, value); for i:=OldDim to value-1 do self.comp[i]:=self.comptype.Create; end; end;
55
function DynamicVector.GetCompType: ClassNumber; begin result:=self.comptype; end; constructor DynamicVector.CreateEmpty(ct: ClassNumber; d: Integer); begin inherited Create; self.comptype:=ct; SetLength(self.comp, d); end; constructor DynamicVector.Create; begin if self.comptype=nil then raise Exception.Create('Invalid Operation'); inherited Create; self.SetDim(0); end; constructor DynamicVector.CreateOfType(ct: ClassNumber); begin inherited Create; self.comptype:=ct; self.SetDim(0); end; destructor DynamicVector.Destroy; begin self.SetDim(0); inherited Destroy; end;
7-8. М од ул ь , с од ержащ ий кл ас с DynamicVector, отл ичает с я от UnitDynamicRVector л ишь наз в анием (UnitDynamicVector) и им енем ис пол ь з уем огов раз д ел е инт ерфейс а м од ул я (UnitNumber). Поэтом у прив од ит ь егокод м ы не буд ем . О тл ичия д ем онс т рац ионной програм м ы т акже нез начит ел ь ны . Как уже бы л оот м ечено, нов ы й объект -в ектор м ы м ожем с оз д ат ь тол ь ко с пом ощ ь ю конс т руктора CreateOfType, перед ав ем у в качес т в е парам ет ра им я т ипа ком понент. Пока м ы рас пол агаем л ишь од ним конкрет ны м нас л ед ником кл ас с а Number — т ипом Rational, егои буд ем ис пол ь з ов ат ь . Е щ е од ноис прав л ение с в яз анос из м енением т ипа в оз в ращ аем ого з начения м етод а ScalarProduct с Rational на Number. Теперь перем енную p, которой оно прис в аив ает с я, уд обно с д ел ат ь форм ал ь но принад л ежащ ей кл ас с у Number (как и в с л учае с в рем енной перем енной temp в м етод е ScalarProduct, объяв ит ь p т аков ой м ожно бы л о и в програм м е из пред ы д ущ ей з ад ачи). Н од л я этогонеобход им од опол нит ел ь нопод кл ючит ь м од ул ь UnitNumber. 56
И т ак, м ы им еем Пример3.1.3. Дем онс т рац ионная програм м а д л я кл ас с а DynamicVector. program TestDynamicVector; {$APPTYPE CONSOLE} uses UnitDynamicVector in 'UnitDynamicVector.pas', UnitNumber in 'UnitNumber.pas', UnitRational in 'UnitRational.pas'; var v1, v2, v3, v4: DynamicVector; r: Rational; p: Number; i: Integer; begin v1:=DynamicVector.CreateOfType(Rational); v2:=DynamicVector.CreateOfType(Rational); r:=Rational.Create; v1.SetDim(9); for i:=1 to 9 do begin r.SetNum(i); r.SetDen(i+1); v1.SetComp(i, r); end; v2.SetDim(7); for i:=1 to 7 do begin r.SetNum(i+1); r.SetDen(i+2); v2.SetComp(i, r); end; WriteLn('v1: '+v1.AsString); WriteLn('v2: '+v2.AsString); // вызов метода GetLength удален v1.SetDim(5); v2.SetDim(5); WriteLn('v1 after v1.SetDim(5): '+v1.AsString); WriteLn('v2 after v2.SetDim(5): '+v2.AsString); v3:=v1.Plus(v2); WriteLn('v3 after v3:=v1.Plus(v2): '+v3.AsString); v1.Add(v2); WriteLn('v1 after v1.Add(v2): '+v1.AsString); if v1.IsEqual(v3) then WriteLn('v1 is equal to v3') else WriteLn('v1 is not equal to v3'); v4:=DynamicVector.CreateOfType(Rational); v4.SetDim(5); v4.Assign(v2); WriteLn('v4 after v4.Assign(v2): '+v4.AsString); v4.Subtract(v1); WriteLn('v2 after v4.Subtract(v1): '+v2.AsString); WriteLn('v4 after v4.Subtract(v1): '+v4.AsString); p:=v2.ScalarProduct(v4); WriteLn('v2.ScalarProduct(v4): '+p.AsString); p.Destroy; r.Destroy; v1.Destroy; v2.Destroy; v3.Destroy; v4.Destroy; ReadLn; end.
57
4. И Е РА РХ И И К Л А ССО В Ч т обы проил л юс т риров ат ь в оз м ожнос т и кл ас с а DynamicVector, од ного л ишь кл ас с а Rational нед ос т ат очно. Поэт ом у с л ед ующ ей нашей ц ел ь ю буд ет раз работ ка ещ е нес кол ь ких кл ас с ов , произ в од ны х от Number. Задача 4.1. Требует с я пос т роит ь д в а нас л ед ника кл ас с а Number, раз м ес т ив их в отд ел ь ны х м од ул ях. О бъект ы перв ого кл ас с а д ол жны пред с т ав л ят ь ц ел ы е чис л а, в торого — ц в ет а в форм ат е RGB. Кажд ы й ц в ет з ад ает с я т ройкой в ещ ес т в енны х чис ел из от рез ка [0, 1], опред ел яющ их крас ную (red), з ел еную (green) и с инюю (blue) его с ос т ав л яющ ие. А рифм ет ичес кие операц ии над ц в ет ам и ос ущ ес т в л яют с я с нас ы щ ением : ес л и з начение какой-л ибо ц в етов ой ком понент ы оказ ы в ает с я м ень ше 0, оноз ам еняет с я на 0, ес л и бол ь ше 1 — на 1. С пис ок операц ий, которы е д ол жны бы т ь реал из ов аны д л я кажд ого кл ас с а, опред ел яет с я кл ас с ом Number. Схема реш ения д анной з ад ачи в ц ел ом т а же, чтои пред ы д ущ их. Н о ес л и рань ше от кры т ы й инт ерфейс кл ас с а раз рабат ы в ал с я «с нул я» , ис ход я из набора операц ий, которы м и д ол жен обл ад ат ь этот кл ас с , то т еперь он в з начит ел ь ной с т епени опред ел яет с я от кры т ы м инт ерфейс ом кл ас с а-пред ка. Реш ение. 1. Н ов ы е кл ас с ы наз ов ем Whole и RGBColor. 2. С огл ас ноус л ов ию нам необход им ореал из ов ат ь л ишь т е операц ии, объяв л ения которы х уже с од ержит кл ас с Number. Э тоод нов рем еннообл егчает и ус л ожняет з ад ачу. С од ной с тороны , не нужнод ум ат ь над т ем , как объяв ит ь тот ил и иной м етод — этоуже с д ел анов кл ас с е-пред ке. С д ругой — унас л ед ов анное объяв л ение м ожет бы т ь не с ов с ем т аким , каким м ы с д ел ал и бы его, раз рабат ы в ая кл ас с с нул я. И з -з а этого реал из ац ия д анногом етод а м ожет оказ ат ь с я нес кол ь кос л ожнее. С т роя кон кре тн ый кл ас с , м ы обяз аны реал из ов ат ь в с е абс т ракт ны е м етод ы баз ов ого. Кл ас с Number с од ержит 7 т аких м етод ов : function procedure procedure procedure procedure procedure function
IsEqual(n: Number): Boolean; Add (n: Number); Subtract (n: Number); MultiplyBy(n: Number); DivideBy (n: Number); Assign (n: Number); AsString: String;
58
virtual; virtual; virtual; virtual; virtual; virtual; virtual;
abstract; abstract; abstract; abstract; abstract; abstract; abstract;
Вс е они д ол жны бы т ь з ам ещ ены в кл ас с ах-потом ках, поэтом у д обав л яем к их от кры т ы м инт ерфейс ам объяв л ения: function procedure procedure procedure procedure procedure function
IsEqual(n: Number): Boolean; Add (n: Number); Subtract (n: Number); MultiplyBy(n: Number); DivideBy (n: Number); Assign (n: Number); AsString: String;
override; override; override; override; override; override; override;
О чев ид но, т акже, чтоу нас д ол жна бы т ь в оз м ожнос т ь опред ел ят ь и из м енят ь з начение, пред с т ав л яем ое объектом . Кл ас с у Whole д л я этого д ос т аточнод в ух м етод ов : function GetValue: Integer; virtual; // определение значения procedure SetValue(val: Integer); virtual; // изменение значения
Ч токас ает с я кл ас с а RGBColor, тоз д ес ь в оз м ожны д в а в ариант а. С од ной с тороны , ц в ет — этов ектор д л ины 3, поэтом у, как и в з ад ачах 2.1– 3.1, д л я чт ения-з апис и ком понент м ожно прим енят ь л ишь д в а м етод а, перед ав ая им в качес т в е парам ет ра ном ер ком понент ы . С д ругой с тороны , в с е ц в етов ы е ком понент ы им еют с в ои наз в ания и пол ь з ов ат ел ю бы л о бы уд обнее опериров ат ь им енно им и, а не чис л ов ы м и инд екс ам и. Поэтом у м ы д обав им к кл ас с у RGBColor т ри пары м етод ов , поод ной д л я кажд ой ком понент ы : procedure procedure procedure function function function
SetRed (value: Real); virtual; // SetGreen(value: Real); virtual; // изменение значений SetBlue (value: Real); virtual; // GetRed: Real; virtual; // GetGreen: Real; virtual; // определение значений GetBlue: Real; virtual; //
Пос кол ь ку т еперь м ы раз рабат ы в аем не од ин кл ас с , а иерархию, в с е в в ед енны е м етод ы объяв л яем в ирт уал ь ны м и на с л учай в оз м ожного нас л ед ов ания от наших кл ас с ов . 3. Дл я хранения ц ел огочис л а д обав им к кл ас с у Whole од нопол е с оот в ет с т в ующ егот ипа: value: Integer;
Кл ас с RGBColor с нов а д опус кает д в а в ариант а: с в ое пол е д л я кажд ой ц в етов ой ком понент ы ил и м ас с ив из т рех в ещ ес т в енны х чис ел . Зд ес ь уже необход им оз ад ум ат ь с я не об уд обс т в е пол ь з ов ат ел я (ем у пол я в с е рав но нед ос т упны ), а о прос тот е реал из ац ии. Вс е арифм ет ичес кие операц ии в ы пол няют с я над ц в ет ам и как над в екторам и поод ним и т ем же прав ил ам д л я в с ех ком понент. С л ед ов ат ел ь но, им еет с м ы с л ис пол ь 59
з ов ат ь ц икл ы , а они прим еним ы л ишь к м ас с ив ам . Т аким образ ом , ос т анав л ив аем с я на этом в ариант е: comp: array[1..3] of Real;
4. Рас с ужд ения, прив ед енны е в этом пункт е решения з ад ачи 2.1, в пол не год ят с я и д л я обоих наших кл ас с ов . С л ед ов ат ел ь но, з ам ещ ат ь унас л ед ов анны е конс т руктор и д ес т руктор не нужно. 5. Реал из ац ия м етод ов в ес ь м а прос т а и л егком ожет бы т ь в ы пол нена по анал огии с кл ас с ом Rational. Е д инс т в енная с л ожнос т ь в ы з в ана т ем , чтопарам ет ры з ам ещ ающ их м етод ов форм ал ь ноим еют т ип Number, а не Whole ил и RGBColor, как в с л учае раз работ ки кл ас с ов «с нул я» . Поэтом у м ы в ы нужд ены произ в од ит ь д опол нит ел ь ную пров ерку т ипа. Е с л и объект д ейс т в ит ел ь нопринад л ежит кл ас с у Whole ил и RGBColor, д л я д ос т упа к егос од ержим ом у пот ребует с я яв ное прив ед ение т ипа с пом ощ ь ю оператора as, в прот ив ном с л учае прид ет с я в оз буд ит ь ис кл ючение. Как и ранее, нес кол ь коиное пов ед ение им еет м етод IsEqual. Е с л и реал ь ны е т ипы парам ет ра и т екущ его объект а не с ов пад ают, рез ул ьт ат этого м етод а опред ел ен и рав ен false. С л ед ует з ам ет ит ь , чтоопис анная с ис т ем а прав ил яв л яет с я наибол ее прос той и д ал еко не ед инс т в енной. Во-перв ы х, л юбое ц ел ое чис л о м ожно рас с м ат рив ат ь как рац ионал ь ное. Во-в торы х, произ в ол ь ное в ещ ес т в енное чис л оиз от рез ка [0, 1] з ад ает от т енок с ерогоц в ет а, в с е ком понент ы которого рав ны д анном у чис л у. Т аким образ ом , рез ул ьт ат той ил и иной операц ии м ожно в ы чис л ит ь и тогд а, когд а т екущ ий объект и парам ет р м етод а принад л ежат раз ны м кл ас с ам . При этом с л ед ует учес т ь , что некоторы е операц ии по с ут и с в оей ком м ут ат ив ны , т ак что в ы пол няющ ие их м етод ы в раз л ичны х кл ас с ах д ол жны бы т ь с огл ас ов аны м ежд у с обой. Реал из ац ия д анной с хем ы не яв л яет с я с ейчас необход им ой и ос т ав л яет с я чит ат ел ю в качес т в е упражнения. Воз в ращ аяс ь к перечис л ению ос обеннос тей м етод ов , отм ет им с л ед ующ ее. 1) Дл я ц ел ы х чис ел д ел ение в ы пол ним о, л ишь ес л и д ел им ое крат но д ел ит ел ю. Как обы чно, час т ичны й характ ер этой операц ии м ожно в ы раз ит ь с пом ощ ь ю пров ерки на в оз м ожнос т ь в ы пол нения д ейс т в ия и в оз бужд ения ис кл ючения. 2) А рифм ет ичес кие операц ии над ц в ет ам и отл ичают с я от в екторны х тол ь кот ем , чтокажд ая ком понент а рез ул ь т ат а д ол жна бы т ь упаков ана в от рез ок [0, 1]. Дл я в ы пол нения нас ы щ ения в ектора уд обно ис пол ь з ов ат ь в с пом огат ел ь ны й м етод : procedure Clamp;
60
3) Х отя м ы и в в ел и д л я уд обс т в а пол ь з ов ат ел я т ри пары м етод ов д л я чт ения-з апис и ц в етов ы х ком понент, их реал из ац ию м ожно в ы пол нит ь с пом ощ ь ю пары в с пом огат ел ь ны х м етод ов , приним ающ их в качес т в е парам ет ра ном ер ком понент ы : function GetComp(i: Integer): Real; // чтение значения procedure SetComp(i: Integer; value: Real); // изменение значения
Три прив ед енны х м етод а м огут оказ ат ь с я пол ез ны м и при раз работ ке нас л ед ников кл ас с а RGBColor, поэтом у их раз ум но объяв ит ь з ащ ищ енны м и и в ирт уал ь ны м и. О пис анную реал из ац ию с од ержат прим еры 4.1.1 и 4.1.2. Пример4.1.1. Реал из ац ия м етод ов кл ас с а Whole. procedure Whole.Add(n: Number); begin if n is Whole // типы текущего объекта и параметра должны совпадать then self.value:=self.value+(n as Whole).value else raise Exception.Create('Invalid operation'); end; procedure Whole.Subtract(n: Number); begin if n is Whole then self.value:=self.value-(n as Whole).value else raise Exception.Create('Invalid operation'); end; procedure Whole.MultiplyBy(n: Number); begin if n is Whole then self.value:=self.value*(n as Whole).value else raise Exception.Create('Invalid operation'); end; procedure Whole.DivideBy(n: Number); begin if (n is Whole) and ((self.value mod (n as Whole).value)=0) // self.value должно быть кратно n.value then self.value:=self.value div (n as Whole).value else raise Exception.Create('Invalid operation'); end; procedure Whole.Assign(n: Number); begin if n is Whole then self.value:=(n as Whole).value else raise Exception.Create('Invalid operation'); end;
61
function Whole.IsEqual(n: Number): Boolean; begin if (n is Whole) and (self.value=(n as Whole).value) then result:=true else result:=false; end; function Whole.AsString: String; begin result:=IntToStr(self.value); end; procedure Whole.SetValue(val: Integer); begin self.value:=val; end; function Whole.GetValue: Integer; begin result:=self.value; end;
Пример4.1.2. Реал из ац ия м етод ов кл ас с а RGBColor. function RGBColor.IsEqual(n: Number): Boolean; var i: Integer; begin if n is RGBColor // типы текущего объекта и параметра должны then begin // совпадать; вычисления осуществляются так же, result:=true; // как и для векторов for i:=1 to 3 do if self.comp[i]<>(n as RGBColor).comp[i] then result:=false; end else result:=false; end; procedure RGBColor.Add(n: Number); var i: Integer; begin if n is RGBColor then for i:=1 to 3 do self.comp[i]:=self.comp[i]+(n as RGBColor).comp[i] else raise Exception.Create('Invalid operation'); self.Clamp; // насыщение результата end; procedure RGBColor.Subtract(n: Number); var i: Integer; begin if n is RGBColor then for i:=1 to 3 do self.comp[i]:=self.comp[i]-(n as RGBColor).comp[i] else raise Exception.Create('Invalid operation'); self.Clamp; end;
62
procedure RGBColor.MultiplyBy(n: Number); var i: Integer; begin if n is RGBColor then for i:=1 to 3 do self.comp[i]:=self.comp[i]*(n as RGBColor).comp[i] else raise Exception.Create('Invalid operation'); self.Clamp; end; procedure RGBColor.DivideBy(n: Number); var i: Integer; begin if n is RGBColor then for i:=1 to 3 do self.comp[i]:=self.comp[i]/(n as RGBColor).comp[i] else raise Exception.Create('Invalid operation'); self.Clamp; end; procedure RGBColor.Assign(n: Number); var i: Integer; begin if n is RGBColor then for i:=1 to 3 do self.comp[i]:=(n as RGBColor).comp[i] else raise Exception.Create('Invalid operation'); end; function RGBColor.AsString: String; begin result:='Red: '+FloatToStrF(self.comp[1],ffFixed,1,4)+ ', Green: '+FloatToStrF(self.comp[2],ffFixed,1,4)+ ', Blue: '+FloatToStrF(self.comp[3],ffFixed,1,4); end; function RGBColor.GetComp(i: Integer): Real; begin result:=self.comp[i]; end; procedure RGBColor.SetComp(i: Integer; value: Real); begin if (value>=0) and (value<=1) // значение должно принадлежать then self.comp[i]:=value // отрезку [0,1] else raise Exception.Create('Illegal value'); end; procedure RGBColor.SetRed(value: Real); // реализации отличаются begin self.SetComp(1, value); end; // лишь номером компоненты procedure RGBColor.SetGreen(value: Real); begin self.SetComp(2, value); end;
63
procedure RGBColor.SetBlue(value: Real); begin self.SetComp(3, value); end; function RGBColor.GetRed: Real; begin result:=self.GetComp(1); end; function RGBColor.GetGreen: Real; begin result:=self.GetComp(2); end; function RGBColor.GetBlue: Real; begin result:=self.GetComp(3); end; procedure RGBColor.Clamp; var i: Integer; begin for i:=1 to 3 do begin if self.comp[i]<0 then self.comp[i]:=0; if self.comp[i]>1 then self.comp[i]:=1; end; end;
Раз м ещ ение кл ас с ов Whole и RGBColor в м од ул ях в ы пол няет с я в точнос т и т ак же, как и в з ад ачах 2.1–3.1, поэтом у с оот в ет с т в ующ ий код м ы прив од ит ь не буд ем . Задача 4.2. Требует с я с оз д ат ь програм м у, д ем онс т рирующ ую работ у с объект ам и кл ас с ов Whole, Rational и RGBColor, а т акже преобраз ов ат ь програм м у из з ад ачи 3.1 т ак, чтобы она м анипул иров ал а в екторам и с ком понент ам и в с ех т рех т ипов . При этом в обеих програм м ах д ейс т в ия, не з ав ис ящ ие от конкрет ногот ипа ком понент, не д ол жны пов торят ь с я. Реш ение. Вы пол нит ь т ребов ание от с ут с т в ия д убл ирующ егос я код а м ожно, в ы д ел ив его час т ь , не з ав ис ящ ую от т ипа, в от д ел ь ную проц ед уру. В качес т в е парам ет ра эт а проц ед ура д ол жна приним ат ь л ибо уже с оз д анны е объект ы , т ип и з начения которы х опред ел ены , л ибо кл ас с ов ую с с ы л ку, указ ы в ающ ую на т ип, которы й необход им о прот ес т иров ат ь . Н ачнем с м од ификац ии програм м ы из з ад ачи 3.1. О чев ид но, что конкрет ны й т ип ком понент необход им л ишь д л я с оз д ания в екторов и з апол нения их з начениям и. Яв ное упом инание т ипа при с оз д ании в ектора v4 м ожноз ам енит ь з начением , в оз в ращ аем ы м м етод ом GetCompType д л я в ектора v2. В рез ул ь т ат е м ы пол учаем с л ед ующ ее.
64
Пример4.2.1. М од ификац ия програм м ы из з ад ачи 3.1. program TestDynamicVector; {$APPTYPE CONSOLE} uses UnitDynamicVector in 'UnitDynamicVector.pas', UnitNumber in 'UnitNumber.pas', UnitWhole in 'UnitWhole.pas', UnitRational in 'UnitRational.pas', UnitRGBColor in 'UnitRGBColor.pas'; procedure Test(v1, v2: DynamicVector); var v3, v4: DynamicVector; p: Number; begin WriteLn('v1: '+v1.AsString); WriteLn('v2: '+v2.AsString); v1.SetDim(5); WriteLn('v1 after v1.SetDim(5): '+v1.AsString); v2.SetDim(5); WriteLn('v2 after v2.SetDim(5): '+v2.AsString); v3:=v1.Plus(v2); WriteLn('v3 after v3:=v1.Plus(v2): '+v3.AsString); v1.Add(v2); WriteLn('v1 after v1.Add(v2): '+v1.AsString); if v1.IsEqual(v3) then WriteLn('v1 is equal to v3') else WriteLn('v1 is not equal to v3'); v4:=DynamicVector.CreateOfType(v2.GetCompType); v4.SetDim(5); v4.Assign(v2); WriteLn('v4 after v4.Assign(v2): '+v4.AsString); v4.Subtract(v1); WriteLn('v2 after v4.Subtract(v1): '+v2.AsString); WriteLn('v4 after v4.Subtract(v1): '+v4.AsString); p:=v2.ScalarProduct(v4); WriteLn('v2.ScalarProduct(v4): '+p.AsString); p.Destroy; v3.Destroy; v4.Destroy; end; var v1, v2: DynamicVector; w: Whole; r: Rational; c: RGBColor; i: Integer; begin v1:=DynamicVector.CreateOfType(Whole); v1.SetDim(9); v2:=DynamicVector.CreateOfType(Whole); v2.SetDim(7); w:=Whole.Create; for i:=1 to 9 do begin w.SetValue(i); v1.SetComp(i, w); end; for i:=1 to 7 do begin w.SetValue(i+1); v2.SetComp(i, w); end; Test(v1, v2); w.Destroy; v1.Destroy; v2.Destroy; WriteLn; v1:=DynamicVector.CreateOfType(Rational); v1.SetDim(9); v2:=DynamicVector.CreateOfType(Rational); v2.SetDim(7); r:=Rational.Create; for i:=1 to 9 do begin r.SetNum(i); r.SetDen(i+1); v1.SetComp(i, r); end;
65
for i:=1 to 7 do begin r.SetNum(i+1); r.SetDen(i+2); v2.SetComp(i, r); end; Test(v1, v2); r.Destroy; v1.Destroy; v2.Destroy; WriteLn; v1:=DynamicVector.CreateOfType(RGBColor); v1.SetDim(9); v2:=DynamicVector.CreateOfType(RGBColor); v2.SetDim(7); c:=RGBColor.Create; for i:=1 to 9 do begin c.SetRed(i/10); c.SetGreen((i+1)/10); c.SetBlue((i-1)/10); v1.SetComp(i, c); end; for i:=1 to 7 do begin c.SetRed((i+1)/10); c.SetGreen((i+2)/10); c.SetBlue((i+3)/10); v2.SetComp(i, c); end; Test(v1, v2); c.Destroy; v1.Destroy; v2.Destroy; ReadLn; end.
Под обная же с хем а м ожет бы т ь ис пол ь з ов ана и при пос т роении програм м ы , д ем онс т рирующ ей работ у м етод ов в с ех т рех кл ас с ов . Н о з д ес ь в оз никает небол ь шая пробл ем а с с оз д анием нов ы х объектов в нут ри т ес т ирующ ей проц ед уры . В с ам ом д ел е, в прим ере 4.2.1 м ы пол ь з ов ал ис ь т ем , чтокажд ы й в ектор с од ержит пол е с информ ац ией о т ипе с в оих ком понент. Кл ас с Number т акогопол я не им еет, од накобы л обы с т ранно, ес л и бы , обл ад ая объектом , м ы не м огл и уз нат ь егот ип. И д ейс т в ит ел ь но, т акая в оз м ожнос т ь им еет с я: она реал из ует с я м ет од ом ClassType кл ас с а TObject, от кот орого неяв но унас л ед ов ан Number. Э тот м етод в оз в ращ ает з начение т ипа class of TObject, которое м ожет с од ержат ь с с ы л ку на л юбой кл ас с . Ч тобы ис пол ь з ов ат ь ее д л я с оз д ания объект а при пом ощ и конс т руктора, в в ед енного в некотором кл ас с е C, м ожно(и нужно) в ы пол нит ь прив ед ение д анной с с ы л ки к т ипу class of C (раз ум еет с я, т акое прив ед ение буд ет работ ат ь прав ил ь но, л ишь ес л и с с ы л ка на с ам ом д ел е с од ержит ад рес кл ас с а C ил и од ногоиз его потом ков ). В нашем с л учае прив ед ение с с ы л ки к т ипу ClassNumber необход им од л я в ы з ов а не тол ь коконс т руктора CreateEqualTo, нои Create. Без этого прив ед ения буд ет ис пол ь з ов ан конс т руктор TObject.Create, не яв л яющ ийс я в ирт уал ь ны м и потом у с оз д ающ ий объект кл ас с а TObject, а не Whole, Rational, RGBColor и т. д . И т ак, в торая д ем онс т рац ионная програм м а буд ет в ы гл яд ет ь т ак. 66
Пример4.2.2. Програм м а, д ем онс т рирующ ая работ у с объект ам и кл ас с ов , произ в од ны х от Number. program TestNumbers; {$APPTYPE CONSOLE} uses UnitWhole in 'UnitWhole.pas', UnitRational in 'UnitRational.pas', UnitRGBColor in 'UnitRGBColor.pas', UnitNumber in 'UnitNumber.pas'; procedure Test(n1: Number); var n2, n3, n4: Number; begin WriteLn('n1: '+n1.AsString); n2:=ClassNumber(n1.ClassType).CreateEqualTo(n1); WriteLn('n2: '+n2.AsString); n3:=n1.Plus(n2); WriteLn('n3 after n3:=n1.Plus(n2): '+n3.AsString); n4:=ClassNumber(n1.ClassType).Create; n4.Assign(n3); WriteLn('n4 after n4.Assign(n3): '+n4.AsString); if n3.IsEqual(n4) then WriteLn('n3 is equal to n4') else WriteLn('n3 is not equal to n4'); n4.MultiplyBy(n2); WriteLn('n4 after n4.MultiplyBy(n2): '+n4.AsString); n2.Destroy; n3.Destroy; n4.Destroy; end; var w: Whole; r: Rational; c: RGBColor; begin w:=Whole.Create; w.SetValue(3); r:=Rational.Create; r.SetNum(2); r.SetDen(7); c:=RGBColor.Create; c.SetRed(0.2); c.SetGreen(0.8); c.SetBlue(0.6); Test(w); w.Destroy; WriteLn; Test(r); r.Destroy; WriteLn; Test(c); c.Destroy; ReadLn; end.
67
В М Е СТ О ЗА К Л Ю ЧЕ Н И Я Прив ед енная в нас т оящ их м етод ичес ких указ аниях с ерия з ад ач, с л ед уя ос нов ной ид ее пос обия [*], с т ав ил а с в оей ц ел ь ю показ ат ь , как, им ея опред ел енны й опы т проц ед урного програм м иров ания, ис пол ь з ов ат ь его д л я с оз д ания кл ас с ов и обобщ енны х ал горит м ов . Р аз ум еет с я, раз работ ку нов огокл ас с а с ов с ем необяз ат ел ь нокажд ы й раз начинат ь с пос т роения с оот в ет с т в ующ ей библ иот еки под програм м . С пос обнос т ь опред ел ят ь инт ерфейс кл ас с а прям о поформ ул иров ке з ад ачи и в ы пол нят ь его реал из ац ию без проц ед урного анал ога появ л яет с я д ос т аточно бы с т ро. В то же в рем я рас с м от ренны е з д ес ь з ад ачи в ес ь м а прос т ы , и ум ение их решат ь — эт ол ишь перв ы й шаг в ос в оении объект ной т ехнол огии. Ч ит ат ел ь , в ероят но, уже з ам ет ил опред ел енны е анал огии м ежд у инт ерфейс ам и кл ас с ов DynamicVector и Number. О ба они с од ержат м етод ы Plus, Minus, Add, Subtract, IsEqual и Assign, объяв л енны е с хожим образ ом . Поэтом у в оз никает в опрос , нел ь з я л и с д ел ат ь перв ы й из указ анны х кл ас с ов произ в од ны м от в торого. Н а перв ы й в з гл яд эт а ид ея прот ив оречит прав ил ам нас л ед ов ания. Кл ас с Number пред с т ав л яет абс т ракт ное пол е, а в екторы образ уют л ишь группу от нос ит ел ь нос л ожения, не яв л яющ уюс я егопод т ипом . О д нако, пред пол агая, что ал гебраичес кие операц ии д опус кают час т ичное опред ел ение, м ы м ожем рас с м ат рив ат ь л юбую группу ил и кол ь ц окак пол е. Т акой под ход уже бы л ис пол ь з ов ан нам и в параграфе 4 при пос т роении кл ас с а Whole. Е д инс т в енны й м инус з д ес ь в том , что нев оз м ожнос т ь в ы пол нения операц ии обнаружив ает с я л ишь в ов рем я в ы пол нения програм м ы , а не в ов рем я ком пил яц ии. Пл юс ом же яв л яет с я бóл ь шая унив ерс ал ь нос т ь с оз д анного код а. Кром е того, у нас появ л яет с я принц ипиал ь ная в оз м ожнос т ь пос т роения с кол ь угод нос л ожны х с т рукт ур д анны х: м ногочл енов от нес кол ь ких перем енны х, м ат риц над кол ь ц ам и м ногочл енов и т. п. Впрочем , д л я реал из ац ии указ анной в оз м ожнос т и необход им о с начал а раз работ ат ь с оот в ет с т в ующ ие кл ас с ы -конт ейнеры . С д ел ат ь это м ожно нес кол ь ким и с пос обам и. Перв ы й и с ам ы й прос той — начинат ь кажд ы й нов ы й кл ас с практ ичес ки с чис того л ис т а, нас л ед уя прям о от кл ас с а Number. Н ет руд но пред с каз ат ь , од нако, что уже пос л е с оз д ания д в ух-т рех кл ас с ов в их реал из ац иях с т анет з ам ет ноз начит ел ь ное д убл иров ание код а. С с ам огоначал а эт и пов торы м ожнопопы т ат ь с я ис кл ючит ь , ес л и в качес т в е баз ов ого кл ас с а в з ят ь не Number, а DynamicVector. Н о з д ес ь 68
в оз никнет д ругая пробл ем а. Данны й кл ас с раз рабат ы в ал с я нам и без пред пол ожения отом , чтоот негобуд ет унас л ед ов ан, с кажем , кл ас с д л я пред с т ав л ения м ногочл енов , и это с каз ал ос ь и на его реал из ац ии, и на инт ерфейс е. Н априм ер, м ы не с м ожем ис кл ючит ь из от кры того инт ерфейс а нас л ед ника м етод ScalarProduct, им еющ ий с м ы с л тол ь код л я в екторов . Н аз в ания некоторы х м етод ов , т аких как GetComp и SetComp, т акже не в пол не под ход ят произ в од ны м кл ас с ам (у м ат риц и м ногочл енов — эл ем ент ы и коэффиц иент ы , а не ком понент ы ). Т аким образ ом , наибол ее прав ил ь ны м яв л яет с я в в ед ение унив ерс ал ь ного пред ка д л я кл ас с ов в екторов , м ногочл енов и м ат риц , з аним ающ егопром ежуточное пол ожение м ежд у ним и и кл ас с ом Number. О н д ол жен с од ной с тороны с од ержат ь в ес ь код , общ ий д л я его нас л ед ников , а с д ругой — не д обав л ят ь в от кры т ы й инт ерфейс иерархии м етод ы , которы е бы прот ив оречил и с од ержат ел ь ном у с м ы с л у хотя бы од ногоиз произ в од ны х кл ас с ов . Пос т роение опис анной иерархии кл ас с ов пред с т ав л яет с обой прим ер бол ее с л ожной з ад ачи, ес т ес т в енны м образ ом прод ол жающ ей прив ед енную с ерию. Дл я чит ат ел я, ос в оив шего пред ы д ущ ий м ат ериал , ее решение буд ет непл охим упражнением . При этом с тоит з ам ет ит ь , что напис ат ь код с раз у «набел о» з д ес ь в ряд л и уд ас т с я, с корее в с егопот ребует с я нес кол ь ко пос л ед ов ат ел ь ны х под гонок и прит ирок кл ас с ов д руг код ругу. Н ов л юбом с л учае бол ее под робное обс ужд ение д анной т ем ы в ы ход ит з а рам ки нас тоящ егоиз д ания.
69
П РИ Л О Ж Е Н И Е 1. Б И Б Л И О Т Е К А Д Л Я РА Б О Т Ы СРА Ц И О Н А Л ЬН Ы М И ЧИ СЛ А М И
Прив од им ая з д ес ь библ иот ека яв л яет с я нес кол ь ко рас ширенной в ерс ией м од ул я, опис анногов перв ой час т и пос обия [*]. Н ов ы м и яв л яют с я функц ии: AreEqual, произ в од ящ ая с рав нение д в ух рац ионал ь ны х чис ел на рав енс т в о, и AsString, преобраз ующ ая рац ионал ь ное чис л о к с т роков ой форм е д л я пос л ед ующ егов ы в од а на экран. ПримерП.1.1. Код библ иот еки д л я работ ы с рац ионал ь ны м и чис л ам и. unit UnitRational; interface type Rational = record num: Integer; den: Integer; end; function function function function procedure procedure procedure procedure function function
Plus (r1,r2:Rational): Rational; // вычисление суммы Minus(r1,r2:Rational): Rational; // вычисление разности Dot (r1,r2:Rational): Rational; // вычисление произведения Slash(r1,r2:Rational): Rational; // вычисление частного Add (var r1:Rational; r2:Rational); // прибавление Subtract (var r1:Rational; r2:Rational); // вычитание MultiplyBy(var r1:Rational; r2:Rational); // домножение DivideBy (var r1:Rational; r2:Rational); // деление AreEqual(r1,r2:Rational): Boolean; // сравнение на равенство AsString(r: Rational): String; // преобразование к строковой // форме
implementation uses SysUtils; procedure Normalize(var r:Rational); { Процедура приводит рациональное число r, передаваемое в качестве параметра, к нормальной форме: числитель - целое число, знаменатель - натуральное. } begin if r.den<0 then begin r.num:=-r.num; r.den:=-r.den; end; end; procedure Reduce(var r:Rational); { Процедура сокращает числитель и знаменатель числа r на их наибольший общий делитель. }
70
var a,b:Integer; begin // используется алгоритм Евклида поиска НОД a:=Abs(r.num); b:=r.den; while a<>0 do if a>=b then a:=a-b else b:=b-a; // по завершении цикла b=НОД(r.num,r.den) r.num:=r.num div b; r.den:=r.den div b; end; { Определения подпрограмм, объявленных в разделе интерфейса. } function Plus(r1,r2:Rational): Rational; var res: Rational; begin res.num:=r1.num*r2.den+r1.den*r2.num; res.den:=r1.den*r2.den; Normalize(res); Reduce(res); result:=res; end; function Minus(r1,r2:Rational): Rational; var res: Rational; begin res.num:=r1.num*r2.den-r1.den*r2.num; res.den:=r1.den*r2.den; Normalize(res); Reduce(res); result:=res; end; function Dot(r1,r2:Rational): Rational; var res: Rational; begin res.num:=r1.num*r2.num; res.den:=r1.den*r2.den; Normalize(res); Reduce(res); result:=res; end; function Slash(r1,r2:Rational): Rational; var res: Rational; begin if r2.num=0 then raise Exception.Create('Division by zero'); { Если числитель второго аргумента равен нулю, возбуждается исключение с сообщением "Деление на ноль". } res.num:=r1.num*r2.den; res.den:=r1.den*r2.num; Normalize(res); Reduce(res); result:=res; end;
71
procedure Add(var r1:Rational; r2:Rational); var _num,_den:Integer; begin _num:=r1.num*r2.den+r1.den*r2.num; _den:=r1.den*r2.den; r1.num:=_num; r1.den:=_den; Normalize(r1); Reduce(r1); end; procedure Subtract(var r1:Rational; r2:Rational); var _num,_den:Integer; begin _num:=r1.num*r2.den-r1.den*r2.num; _den:=r1.den*r2.den; r1.num:=_num; r1.den:=_den; Normalize(r1); Reduce(r1); end; procedure MultiplyBy(var r1:Rational; r2:Rational); var _num,_den:Integer; begin _num:=r1.num*r2.num; _den:=r1.den*r2.den; r1.num:=_num; r1.den:=_den; Normalize(r1); Reduce(r1); end; procedure DivideBy(var r1:Rational; r2:Rational); var _num,_den:Integer; begin if r2.num=0 then raise Exception.Create('Division by zero'); _num:=r1.num*r2.den; _den:=r1.den*r2.num; r1.num:=_num; r1.den:=_den; Normalize(r1); Reduce(r1); end; function AreEqual(r1,r2:Rational): Boolean; begin if r1.num*r2.den=r2.num*r1.den then result:=true else result:=false; end; function AsString(r: Rational): String; begin result:=IntToStr(r.num)+'/'+IntToStr(r.den); end; end.
72
П РИ Л О Ж Е Н И Е 2. К Л А ССNUMBER Прив ед енное опред ел ение кл ас с а Number д ов ол ь но с ил ь но отл ичает с я от ис пол ь з уем огов т рет ь ей час т и пос обия [*]. Во-перв ы х, он с од ержит т ри нов ы х абс т ракт ны х м ет од а: IsEqual, Assign и AsString. В произ в од ны х кл ас с ах их реал из ац ии д ол жны в ы пол нят ь , с оот в ет с т в енно, с рав нение д в ух объект ов на рав енс т в о, прис в аив ание од ногообъект а д ругом у и преобраз ов ание объект а к с т роков ой форм е. Во-в торы х, д обав л ено д в а конс т руктора: Create и CreateEqualTo д л я с оз д ания и кл ониров ания объектов . О ба они объяв л ены в ирт уал ь ны м и с т ем , чтобы их м ожнобы л оис пол ь з ов ат ь с ов м ес т нос кл ас с ов ы м и с с ы л кам и д л я с оз д ания объектов , т ип которы х неиз в ес т ен в о в рем я раз работ ки. Допол нит ел ь нок кл ас с у Number в в ед енои им я ClassNumber д л я с с ы л очногот ипа class of Number. Н аконец , м етод ы Plus, Minus, Dot, Slash и конс т рукт ор CreateEqualTo реал из ов аны унив ерс ал ь ны м образ ом с ис пол ь з ов анием д ругих м етод ов тогоже кл ас с а. Т аким образ ом , т еперь они с ам и пред с т ав л яют с обой обобщ енны е ал горит м ы . В качес т в е прим ера опишем бол ее под робноработ у м етод а Plus. М ет од ClassType , унас л ед ов анны й от TObject, в оз в ращ ает с с ы л ку на кл ас с т екущ егообъект а (наз ов ем егоSelfClass), кот оры й з ав ед ом о яв л яет с я пот ом ком Number. Поэт ом у прив ед ение т ипа ClassNumber(self.ClassType) без опас но и поз в ол яет в ы з в ат ь конс т рукт ор CreateEqualTo им енноиз кл ас с а SelfClass. В рез ул ь т ат е с оз д ает с я копия т екущ егообъект а, к которой с пом ощ ь ю м етод а Add прибав л яет с я объект n. Пос кол ь ку в кл ас с е Number д анны й м етод объяв л ен в ирт уал ь ны м , в ы з ы в ает с я проц ед ура, з ам ещ ающ ая егов кл ас с е SelfClass. Т аким образ ом , операц ия с л ожения в ы пол няет с я поправ ил ам , опред ел енны м в реал из ац ии кл ас с а т екущ егообъект а. ПримерП.2.1. Код кл ас с а Number. unit UnitNumber; interface type Number = class public function Plus function Minus function Dot
(n: Number): Number; (n: Number): Number; (n: Number): Number;
73
virtual; virtual; virtual;
function function procedure procedure procedure procedure procedure function constructor constructor end; ClassNumber =
Slash (n: Number): Number; IsEqual(n: Number): Boolean; Add (n: Number); Subtract (n: Number); MultiplyBy(n: Number); DivideBy (n: Number); Assign (n: Number); AsString: String; Create; CreateEqualTo(n: Number);
virtual; virtual; virtual; virtual; virtual; virtual; virtual; virtual; virtual; virtual;
abstract; abstract; abstract; abstract; abstract; abstract; abstract;
class of Number;
implementation function Number.Plus(n: Number): Number; begin result:=ClassNumber(self.ClassType).CreateEqualTo(self); result.Add(n); end; function Number.Minus(n: Number): Number; begin result:=ClassNumber(self.ClassType).CreateEqualTo(self); result.Subtract(n); end; function Number.Dot(n: Number): Number; begin result:=ClassNumber(self.ClassType).CreateEqualTo(self); result.MultiplyBy(n); end; function Number.Slash(n: Number): Number; begin result:=ClassNumber(self.ClassType).CreateEqualTo(self); result.DivideBy(n); end; constructor Number.Create; begin inherited Create; end; constructor Number.CreateEqualTo(n: Number); begin inherited Create; self.Assign(n); end; end.
74
П РИ Л О Ж Е Н И Е 3. К Л А ССRATIONAL Кл ас с Rational отл ичает с я от од ноим енногокл ас с а из т рет ь ей час т и пос обия [*] з начит ел ь ном енее, чем Number. Н ов ы м и яв л яют с я м етод ы IsEqual, Assign и AsString, з ам ещ ающ ие с оот в ет с т в ующ ие абс т ракт ны е м етод ы кл ас с а Number. М етод ы Plus, Minus, Dot и Slash, напрот ив , от с ут с т в уют (т. е. не переопред ел яют с я). И х реал из ац ия в кл ас с е Number нас в пол не ус т раив ает. ПримерП.3.1. Код кл ас с а Rational. unit UnitRational; interface uses UnitNumber; type Rational = class(Number) private num: Integer; den: Integer; protected procedure Normalize; procedure Reduce; public function IsEqual(n: Number): Boolean; procedure Add (n: Number); procedure Subtract (n: Number); procedure MultiplyBy(n: Number); procedure DivideBy (n: Number); procedure Assign (n: Number); function AsString: String; constructor Create; procedure SetNum(value: Integer); procedure SetDen(value: Integer); function GetNum: Integer; function GetDen: Integer; end;
virtual; virtual; override; override; override; override; override; override; override; override; virtual; virtual; virtual; virtual;
implementation uses SysUtils; procedure Rational.Normalize; { Процедура приводит текущий объект к нормальной форме. } begin if self.den<0 then begin self.num:=-self.num; self.den:=-self.den; end; end;
75
procedure Rational.Reduce; { Процедура сокращает числитель и знаменатель текщуего объекта на их наибольший общий делитель. } var a,b: Integer; begin // используется алгоритм Евклида поиска НОД a:=Abs(self.num); b:=self.den; while a<>0 do if a>=b then a:=a-b else b:=b-a; // по завершении цикла b=НОД(r.num,r.den) self.num:=self.num div b; self.den:=self.den div b; end; procedure Rational.Add(n: Number); var _num,_den: Integer; begin if n is Rational then begin _num:=self.num*(n as Rational).den+self.den*(n as Rational).num; _den:=self.den*(n as Rational).den; self.num:=_num; self.den:=_den; self.Normalize; self.Reduce; end else raise Exception.Create('Invalid operation'); end; procedure Rational.Subtract(n: Number); var _num,_den: Integer; begin if n is Rational then begin _num:=self.num*(n as Rational).den-self.den*(n as Rational).num; _den:=self.den*(n as Rational).den; self.num:=_num; self.den:=_den; self.Normalize; self.Reduce; end else raise Exception.Create('Invalid operation'); end; procedure Rational.MultiplyBy(n: Number); var _num,_den: Integer; begin if n is Rational then begin _num:=self.num*(n as Rational).num; _den:=self.den*(n as Rational).den; self.num:=_num; self.den:=_den; self.Normalize; self.Reduce; end else raise Exception.Create('Invalid operation'); end;
76
procedure Rational.DivideBy(n: Number); var _num,_den: Integer; begin if n is Rational then begin if (n as Rational).num=0 then raise Exception.Create('Division by zero'); _num:=self.num*(n as Rational).den; _den:=self.den*(n as Rational).num; self.num:=_num; self.den:=_den; self.Normalize; self.Reduce; end else raise Exception.Create('Invalid operation'); end; procedure Rational.Assign(n: Number); begin if (n is Rational) then begin self.num:=(n as Rational).num; self.den:=(n as Rational).den; end else raise Exception.Create('Invalid operation'); end; function Rational.IsEqual(n: Number): Boolean; begin if (n is Rational) and (self.num*(n as Rational).den=self.den*(n as Rational).num) then result:=true else result:=false; end; function Rational.AsString: String; begin result:=IntToStr(self.num)+'/'+IntToStr(self.den); end; constructor Rational.Create; begin self.num:=0; self.den:=1; end; function Rational.GetNum: Integer; begin result:=self.num; end; function Rational.GetDen: Integer; begin result:=self.den; end; procedure Rational.SetNum(value:Integer); begin self.num:=value; end; procedure Rational.SetDen(value:Integer); begin if value>0 then self.den:=value else raise Exception.Create('Illegal value of denominator'); end; end.
77