else
if) // else if) for)
kde výraz musí nabývat logické hodnoty. Větev else je nepovinná. Příklad použití: (# d: @directory do 'adresar'->d.name; (if d.empty then 'Prazdny'->putLine else 'Neprazdny'->putLine if) #)
Tento příklad využívá vzoru directory definovaného ve stejnojmenné knihovně. Přiřazením textu do atributu name objektu vytvořeného podle tohoto vzoru zadáme cestu k adresáři, který nás zajímá a pak pomocí atributu empty můžeme zjistit, zda tento adresář obsahuje nějaká položky. Poznamenejme, že u tohoto příkladu dojde v případě neexistence zkoumaného adresáře k vyvolání implicitní výjimky (kterou bychom mohli snadno předefinovat), která ukončí provádění programu vypsáním chybového hlášení. Použití (běžného) podmíněného příkazu by vyžadovalo přidání // true na konec výrazu v podmínce. 3.2 Podmíněný příkaz Podmíněný příkaz v jazyce BETA umožňuje podmínku s více alternativami (v jiných jazycích např. switch, nebo case) a jeho obecná syntaxe vypadá takto: (if
Symbol // vyjadřuje rovnost. Větev then (řádka uvozená symbolem //) může být jedna nebo více. Nejdříve se vyhodnotí výrazl a výsledek se porovnává s výsledky výrazů uvedených v jednotlivých then větvích. Jestliže platí rovnost ve více případech, vybere se libovolná ze splněných větví a provedou se příslušné příkazy. Pokud neplatí rovnost v žádném případě, spustí se příkazy z větve else, pokud existuje, neboť ta je nepovinná. Příklad použití: 20
(# do (if noOfArguments //2 then 'Jeden argument: '->putText 2->arguments->putLine //3 then 'Dva argumenty...'->putLine else 'Pouziti: argumentl [argument2]'->putLine if) #)
Takto lze jednoduše zpracovávat příkazovou řádku programu. Slouží k tomu vzory definované v základní knihovně betaenv, noOfArguments, který vrací počet parametrů na příkazové řádce (včetně názvu programu) a arguments, pomocí něhož lze tyto parametry získat (parametr číslo jedna je název programu). Pokud je program spuštěn s jedním parametrem, vypíše se příslušně text následovaný zadaným parametrem, pokud je spuštěn se dvěma parametry, vypíše se o tom zpráva. V jiných případech (bez parametru, více než dva parametry) se použije větev else. Vzor putText má stejnou funkci jako putLine (výpis textu na obrazovku), ale nepřidává ukončení řádky. Rozhodnutí na základě logické hodnoty lze provést například takto (případ false může být ekvivalentně řešen pomocí větve else): (if x>0 // true then ... // false then ... if)
Zajímavější je použití pro větvení na základě několika logických výrazů, například: (if true // x<0 then ... // x=0 then ... // x>0 then ... if)
3.3 Cyklus for Řídící struktura pro iterace se v BETĚ nazývá příkaz for (for imperative) a má následující strukturu: (for proměnná ):
kde proměnná je název řídící proměnné cyklu, která je viditelná pouze uvnitř cyklu, nelze ji přiřazovat a je typu integer. Nejprve je vyhodnocen výraz, který určuje kolikrát jsou spuštěny příkazy,přičemž řídící proměnná cyklu se postupně zvyšuje o jedničku počínaje hodnotou 1. Jednoduchý příklad, který sečte všechna čísla od 1 do 1000: (# domaciUloha: @integer do (for i: 1000 repeat domaciUloha+i -> domaciUloha for); domaciUloha -> putInt
21
#)
Pokud nepotřebujeme řídící proměnnou cyklu, lze použít jednoduchý cyklus for, který má stejnou syntaxi, pouze je vypuštěna proměnná a dvojtečka. Pak jsou prostě příkazy provedeny tolikrát, kolikrát udává výraz. Tedy například: (# do (for 10 repeat 'Ahoj' -> putLine for); #)
vypíše desetkrát "ahoj" . Důležitá je v tomto kontextu myšlenka, že většina nejrůznějších řídících struktur (jako například cyklus for s určením spodní hranice řídící proměnné, apod.) může být vytvořena pomocí vzorů (tak jak tomu skutečně v základních knihovnách je a jak ještě uvidíme) a proto má BETA předdefinováno jen několik málo řídících struktur. 3.4 Návěští a příkazy leave a restart Návěští může být přiřazeno příkazu takto: jméno:
Návěští jméno je dostupné pouze uvnitř pojmenované konstrukce. Vykonávání pojmenovaného příkazu (může to být popisovač objektu obsahující skupinu příkazů) může být ukončeno pomocí příkazů leave nebo restart. První z nich, leave, způsobí skok za konec příkazu, jehož jméno je uvedeno za příkazem leave. Naopak vykonání příkazu restart způsobí opakované vykonávání příslušného (jménem za příkazem restart daného) příkazu. Protože příkazy návěští jsou úzce spojeny s repeticí, názorný příklad bude uveden v následující kapitole.
Příklady: Příklad 8: ORIGIN '~beta/basiclib/betaenv' ---- program: descriptor ---(# (* Multiplication Table * * Objectives: * - use the FOR imperative * - introduce a parameterized procedure *************************************************) tab: (# N: @integer; enter N do (for N repeat ' '->put for) #); do '\n\t** Multiplication Table ** \n\n'->puttext; 4->tab; (for i: 9 repeat
22
i->putint; 2->tab; for); newline; newline; (for i: 9 repeat i->Putint; 3->tab; (for j: 9 repeat i*j->putint ; 2->tab; for); newline for) #)
Příklad 9: ORIGIN '~beta/basiclib/betaenv' ---- program: descriptor ---(# (* Multiplication Table 2 * * Objectives: * - use the IF and FOR imperatives * - use procedures & parameters *************************************************) tab: (# N: @integer; enter N do (for N repeat ' '->Put for) #); Outint: (# N,W, ND: @integer; enter (N,W) do (if true //N<10 then 1->ND //(N>=10) and (N<100) then 2->ND else 3->ND if); W-ND->tab; N->putint; #); do '\n\t** Multiplication Table ** \n\n'->puttext; 4->tab; (for i: 10 repeat (i,3)->Outint; for); newline; newline; (for i: 10 repeat (i,4)->Outint; (for j: 10 repeat (i*j,4)->Outint; for); newline for) #)
23
Příklad 10: ORIGIN '~beta/basiclib/betaenv' ---- program: descriptor ---(# (* Multiplication Table 2 * * Objectives: * - use the IF and FOR imperatives * - use procedures & parameters *************************************************) tab: (# N: @integer; enter N do (for N repeat ' '->Put for) #); Outint: (# N,W: @integer; enter (N,W) do N->screen.putint(# format:: (# do W->width#)#); #); do '\n\t** Multiplication Table ** \n\n'->puttext; 4->tab; (for i: 10 repeat (i,4)->Outint; for); newline; newline; (for i: 10 repeat (i,4)->Outint; (for j: 10 repeat (i*j,4)->Outint; for); newline for) #)
Jednoduchý podmíněný příkaz, podmíněný příkaz, příkaz cyklu, návěští a příkazy leave a restart.
V tomto modulu jsme vysvětlili základní příkazy pro selekci a pro iteraci. Jednoduchý podmíněný příkaz, obecný podmíněný příkaz (switch, case), příkaz cyklu.
24
K jakému příkazu (z jazyka Pascal, C) byste přirovnali podmíněný příkaz? S jakým krokem pracuje příkaz cyklu for? V jakém intervalu pracuje příkaz cyklu for?
Jak byste realizovali průchod cyklem for od největší hodnoty po jedničku?
3.1 Jak převedete níže uvedený podmíněný příkaz na jednoduchý podmíněný příkaz? (if x>0 // true ‘ x je kladne ‘->puttext // false ‘ x není kladne ‘-> puttext if) 3.2 Co provádí daný příkaz (p je integer proměnná nastavená na celé číslo): a: [0]@integer; do p->a.new; (for i:p repeat a[p-i+1]->putint for)
3.1 Řešení: (if x>0 then … else … if) vypisované texty jsou stejné. 3.2 Tiskne pole a od nejvyššího prvku p po nejnižší (1).
Jak vidíte, základní řídící příkazy jsou poměrně jednoduché. Pomocí dědičnosti je možné dále rozvíjet příkaz iterace (iterace s krokem, iterace od jiného počátku než od jedničky). Příkazy while a until realizujeme pomocí virtuálních procedur.
25
4. Repetice Datová struktura podobná polím v ostatních programovacích jazycích se v BETĚ nazývá repetition. Tento termín byl zvolen pro odlišení od jiných programovacích jazyků, neboť datový typ repetition v sobě zahrnuje kromě konceptu pole (lineární posloupnost prvků téhož typu) také jisté operace (především pro dynamickou změnu velikosti datové struktury). Proto bude nadále používán jako český ekvivalent termínu repetition termín repetice. Deklarace R:[10]@P kde R je jméno repetice statických odkazů prvků popsaných vzorem P, který má velikost 10 prvků (na místě konstanty 10 může být libovolný výraz typu integer). Jednotlivé prvky lze odkazovat takto R[1], R[2], ... R[R.range] kde R.range je atribut, který má každá datová struktura repetice a pomocí kterého lze získat její velikost (počet prvků). Prvky jsou vždy číslovány od jedné. Stejně tak lze deklarovat repetici dynamických odkazů D:[10]^P kde každý prvek je odkaz na jednu instanci vzoru P. Zde tedy nebudou na rozdíl od předchozí repetice statických odkazů při jejím vytváření vytvořeny instance vzoru P, ale jen dynamické odkazy (inicializované na NULL). Příklad definice vzoru reprezentujícího jednoduchý zásobník pro čísla: Stack: (# Data: [10] @integer; Top: @integer; Push: (# e:@integer enter e do Top+1->Top; e->Data[Top] #) ; Pop: (# e: @integer do Data[Top]->e; Top-1->Top exit e #) ; #)
Pro uložení dat v zásobníku slouží repetice Data, proměnná Top reprezentuje aktuální vrchol zásobníku a vnořené vzory Push a Pop základní operace se zásobníkem. Využívá se implicitní inicializace proměnné Top na nulu, díky čemuž není nutno zásobník inicializovat. Postačí jej vytvořit jako statický odkaz a pak již ho lze používat. V následujícím příkladu S: @Stack do (for i: 10 repeat i->S.Push for); (for i: 10 repeat S.Pop->PutInt; newLine for)
26
jsou čísla vložená postupně (vzestupně) do zásobníku prvním cyklem druhým cyklem vybírána v opačném pořadí a tištěna na obrazovku (vzor newLine provede odřádkování). Při pokusu o přístup k prvku repetice s indexem mimo rozsah 1 ... range dojde k vyvolání standardní výjimky, která ukončí běh programu s chybovým hlášením. To zde nastane, pokud •
se pokusíme do zásobníku vložit více elementů, než na kolik je dimenzován (konstanta 10 v deklaraci repetice Data)
•
se pokusíme vybrat ze zásobníku data aniž bychom tam předtím příslušně počet elementů vložili
Výskyt výjimky z prvního důvodu eliminujeme v následujícím odstavci pomocí dynamické změny velikosti repetice. 4.1 Dynamická změna velikosti Datová struktura repetice je pole s dynamicky měnitelnou délkou. Velikost repetice udávaná při deklaraci se chápe pouze jako počáteční velikost. Možnost dynamické změny velikosti je důležitá z hlediska použití datové struktury pro přímou implementaci prostředků pro ukládání dat, neboť z hlediska analýzy většina takových problémů vede na určité úrovni návrhu k potřebě dynamicky měnit velikost datových struktur. Například u našeho příkladu, zásobníku, může být výhodné, pokud při pokusu o vložení prvku bude zásobník v případě potřeby zvětšen namísto generování výjimky. Pak bude pro uživatele takového zásobníku zcela lhostejné, jakou (počáteční) hodnotu velikosti datové struktury zvolil ten, kdo jej implementoval. Úprava definice zásobníku pro dosažení tohoto výsledku je poměrně jednoduchá (týká se pouze vzoru Push, jinak se nic nemění): Push: (# e: @integer enter e do (if (Top+1->Top)>Data.range then 5 -> Data.extend if); e->Data[Top] #);
Každá datová struktura repetice má atribut extend, který slouží ke zvětšení rozsahu. Pokud máme repetici Data o deseti prvcích (Data.range=10) a provedeme přiřazení 5 -> Data.extend
pak se rozsah zvěší o přiřazenou hodnotu, tedy na Data.range=15, přičemž hodnoty prvků Data[1],Data[2],...,Data[10] zůstanou zachovány a nově budou k dispozici prvky Data[11],Data[12],...,Data[15], které dostanou počáteční hodnotu nula. Jinou možností je vytvořit zcela novou datovou strukturu, pomocí atributu new, například 20 -> Data.new
vytvoří novou datovou strukturu repetice o rozsahu daném přiřazenou hodnotou (zde tedy 20 prvků), přičemž původní prvky budou nedosažitelné a nové prvky dostanou počáteční hodnotu nula. Pro datovou strukturu repetice je definováno vzájemné přiřazení. Předpokládejme deklarace:
27
R1: [7) @integer; R2: [13] @integer pak přiřazení R1 -> R2 má následující význam: Rl.range -> R2.new; (for i: Rl.range repeat Ri[i]->R2[i] for)
tedy obsah repetice je překopírován s tím, že rozsah je náležitě přizpůsoben. 4.2 Řezy Pro datovou strukturu repetice je definován řez (repetition slice), pomocí kterého je možno vybrat část prvků repetice. Například R1[3:5] -> R2
zkopíruje do R2 pouze ty prvky R1, které mají index 3 až 5. Obecné přiřazení R2[el:e2] -> R1
kde e1 a e2 mohou být libovolné výrazy typu integer je interpretováno jako e2-e1+1 -> Rl.new; (for i: Rl.range repeat R2[el+i-1] -> R1[i] for)
4.3 Vzor text Vzor text slouží k reprezentaci řetězců znaků, neboli posloupnosti objektů typu char. Instanci vzoru text může být přiřazována konstanta například takto: (# do #)
T: @text 'retezec' -> T
Vzor text nepatří mezi základní vzory, proto může být použit k vytváření dynamických odkazů a jako nadvzor. Je definován v základní knihovně basiclib a má poměrně mnoho atributů sloužících k manipulaci s textem. Nyní uvedeme základní příklad na použití repetice. ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# P: [12] @integer; Q: [0] @integer; k,m: @integer do (for i: P.range repeat i+3->P[i] for); 'Vypis pole P: \n'->puttext; (for i: P.range repeat 'P('->puttext; i->putint; '): '->puttext; P[i]->putint; ' '->put for); newline; Nav: (for i: P.range repeat (if P[i] = 7 then i->k; leave Nav if) for); (if k = 0 then 'cislo 7 nenalezeno v repetici P '->puttext else
28
'Cislo 7 nalezeno na pozici: '->puttext; k->putint; newline if); 1->m; ccc: (if (m <= P.range) and (P[m] <> 7) then m+1->m; restart ccc else m->k if); (if k = 0 then 'cislo 7 nenalezeno v repetici P '->puttext else 'Cislo 7 nalezeno na pozici: '->puttext; k->putint; newline if); P->Q; 'Vypis pole Q: \n'->puttext; (for i: Q.range repeat 'Q('->puttext; i->putint; '): '->puttext; Q[i]->putint; ' '->put for) #)
V příkladu máme dvě návěští a to návěští Nav a ccc. V obou případech hledáme na jaké pozici (číslo indexu) je umístěno v repecici číslo 7. Využíváme toho, že na začátku má každá integer proměnná hodnotu nula. Je-li tedy po operaci k rovno nule, číslo 7 v repetici není. Repetice Q má po přiřazení stejný obsah jako repetice P, díky výše popsaným vlastnostem repetice.
Příklady: Příklad 11: ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# Vektor: (# p: [0] @integer; top: @integer; init: (# k: @integer enter k do k->p.new #); print: (# do (for i: p.range repeat '\nprvek: '->puttext; i->putint; ' hodnota: '->puttext; p[i]->putint for) #); insert: (# j: @integer enter j do (if (top+1 <= p.range) then top+1->top; j->p[top] else '\nOverFlow'->puttext if) #); size: (# exit p.range #)
29
#); zn: (# do (if keyboard.peek = ascii.newline then keyboard.get if) #); tab: (# x: @integer enter x do (for x repeat ' '->put for) #); A,B: @Vektor; k: @integer do 10->A.init; 1->B.init; '\nVkladani: \n'->puttext; (for i: A.size repeat '\nprvek: '->puttext; i->putint; 2->tab; getint->k; zn; k->A.insert for); A.print; A.p->B.p; A.top->B.top; 'B print\n'->puttext; B.print #)
Na repetici nemůžeme mít přímou referenci. Například kdybychom chtěli předat adresu repetice do nějaké metody, přímo to nejde. Musíme k tomu využít bezprostředně nadřazený vzor. Použití takové konstrukce je uvedeno v následujícím příkladě. Příklad 12: ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# numRep: (# r: [9] @integer; print: (# do '\nVypis prvku pole\n'->puttext; (for i: r.range repeat '\nprvek: '->puttext; i->putint; ' hodnota: '->puttext; r[i]->putint for) #) #); uprava: (# nr: ^numRep enter nr[] do (for i: nr.r.range repeat (if nr.r[i] < 0 then 0->nr.r[i] if) for) #); number: @numRep; k: @integer do - 6->k; (for i: number.r.range repeat k->number.r[i]; 2+k->k for); number.print; number[]->uprava; '\nTisk po uprave:\n'->puttext; number.print #)
V tomto příkladu je uveden klasický případ použití registru s tím, že prvky repetice jsou celá
30
čísla. Příklad 13: ORIGIN '~beta/basiclib/betaenv'; INCLUDE '~beta/basiclib/math' '~beta/basiclib/numberio'; -- program: Descriptor -(# Register: (# Table: [100] @integer; Top: @integer; Init: (# do 0->Top #); Has: (# Key: @integer; Result: @boolean enter Key do False->Result; Search: (for inx: Top repeat (if ((Table[inx] = Key)->Result) = True then leave Search if) for) exit Result #); Insert: (# New: @integer enter New do (if (New->Has) = False then (if (Top+1 <= Table.Range) then top+1->top; New->Table[Top] else ' Owerflow'->putline if) if) #); Remove: (# Key: @integer enter Key do Search: (for inx: Top repeat (if Table[inx] = key then (for i: Top-inx repeat Table[inx+i]->Table[inx+i-1] for); Top-1->Top; leave Search if) for) #); Print: (# do (for i: Top repeat '\nPrvek: '->puttext; i->putint; ' Hodnota: '->puttext; Table[i]->putint for) #) #);
31
R: @Register do R.Init; (for inx: 6 repeat inx*inx->R.Insert for); R.print; 54->R.insert; 55->R.insert; R.print; 4->R.remove; 36->R.remove; newline; 22->R.insert #)
V následujícím příkladu jsou prvky repetice jednoduché objekty záznamu. Příklad 14: ORIGIN '~beta/basiclib/betaenv'; INCLUDE '~beta/basiclib/math' '~beta/basiclib/numberio' '~beta/basiclib/textUtils'; -- program: Descriptor -(# Register: (# Table: [12] ^Record; Top: @integer; Init: (# do 0->Top #); Has: (# R1: ^Record; Result: @boolean enter R1[] do False->Result; Search: (for inx: Top repeat (if ((Table[inx].num = R1.num)->Result) = True then leave Search if) for) exit Result #); Insert: (# New: ^Record enter New[] do (if (New[]->Has) = False then (if (Top+1 <= Table.Range) then top+1->top; New[]->Table[Top][]; else ' Owerflow'->putline if) else 'zaznam jiz existuje zapis neproveden '->puttext if) #); Remove: (# key: @integer enter key do Search:
32
(for inx: Top repeat (if Table[inx].num = key then (for i: Top-inx repeat Table[inx+i][]->Table[inx+i-1][] for); Top-1->Top; leave Search if) for) #); Print: (# do N1: (for i: Top repeat (if Table[i][] <> none then '\nPrvek: '->puttext; i->putint; Table[i].tisk else leave N1 if) for) #); Get: (# i: @integer; rx: ^Record enter i do Table[i][]->rx[] exit rx[] #); getTop: (# exit top #) #); Record: (# num: @integer; name: @text; tisk: (# do '\nCislo: '->puttext; num->putint; ' jmeno: '->puttext; name[]->puttext #) enter (num,name) #); R: @Register; sum: @integer; rc: ^Record do R.Init; &record[]->rc[]; (1,'Karel')->rc; rc[]->R.insert; &record[]->rc[]; (2,'Jana')->rc; rc[]->R.insert; &record[]->rc[]; (33,'Jindra')->rc; rc[]->R.insert; &record[]->rc[]; (12,'Franta')->rc; rc[]->R.insert;
33
&record[]->rc[]; (36,'Franta')->rc; rc[]->R.insert; R.print #) Příklad 15: ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# Zasobnik: (# Top: @integer; A: [20] @integer; push: (# e: @integer enter e do (if Top < A.range then Top+1->Top; e->A[top] else '\nZasobnik je plny'->puttext; newline if) #); pop: (# e: @integer do (if Top > 0 then A[top]->e; top-1->top else 'Zasobnik je prazdny'->puttext; newline if) exit e #); Init: (# do 0->Top #); isEmpty: (# res: @boolean do (0 = Top)->res exit res #); size: (# exit top #); peek: (# e: @integer do A[top]->e exit e #); tisk: (# do '\nprvek: '->puttext; 2->tab; 'hodnota\n'->puttext; (for i: top repeat i->putint; 8->tab; A[top-i+1]->putint; newline for) #); tab: (# n: @integer enter n do (for i: n repeat ' '->put for) #) #); S1: @Zasobnik do S1.init; S1.isEmpty; 12->S1.push; 33->S1.push; 44->S1.push; S1.peek->putint; S1.tisk #) Příklad 16: ORIGIN '~beta/basiclib/betaenv';
34
-- program: Descriptor -(# fronta: (# a: [5] @integer; pocet,in,out: @integer; init: (# do 1->in; a.range->out; 0->pocet #); zapis: (# prvek: @integer enter prvek do (if pocet >= a.range then '\nFronta je plna - zapis neproveden'->puttext else prvek->a[in]; pocet+1->pocet; (in mod a.range)+1->in if) #); cteni: (# prvek: @integer do (if pocet <= 0 then '\nFronta je prazdna - cteni neprovedeno'->puttext else pocet-1->pocet; (out mod a.range)+1->out; a[out]->prvek if) exit prvek #); velikost: (# exit pocet #); prazdna: (# exit (pocet <= 0) #); plna: (# exit (pocet >= a.range) #); kapacita: (# exit a.range #) #); f1,f2: @fronta; do f1.init; f2.init; (for i: f1.kapacita repeat i+3->f1.zapis for); 5->f1.zapis; f1.cteni->putint; ' '->put; f1.cteni->putint; ' '->put; f1.cteni->putint; ' '->put; ' '->put; - 12->f1.zapis; - 14->f1.zapis; f1.cteni->putint; ' '->put; f1.cteni->putint; ' '->put; f1.cteni->putint; ' '->put; f1.cteni->putint; '\nVelikost fronty: '->puttext; f1.velikost->putint #)
35
Repetice.
Základní metody pro práci s repeticí jsou: range – vrací deklarovaný rozsah repetice; new – ruší původní prvky repetice a vytvoří novou repetici se zadaným rozsahem prvků; extend – rozšiřuje původní repetici o zadaný počet prvků. Návěští a příkazy leave a restart. Návěští leave – opouští danou iteraci; návěští restart – pokračuje v iteraci od uvedeného návěští.
Jaké jsou předdefinované metody pro práci s repeticí? Jak se dá pomocí repetice realizovat datová struktura fronta?
Co má společného a v čem se liší repetice a pole?
4.1 Jak předáme adresu repetice do dané metody? 4.2 Co se stane v příkladu btcv4\registrB.bet pokud nebudeme vytvářet nové instance záznamů (tučně vyznačené řádky vynecháme)? R.Init; &record[]->rc[]; (1,'Karel')->rc; rc[]->R.insert; &record[]->rc[]; (2,'Jana')->rc; rc[]->R.insert; &record[]->rc[];
36
4.1 Řešení je uvedeno v následujícím příkladě: ORIGIN '~beta/basiclib/betaenv'; INCLUDE '~beta/basiclib/numberio'; -- program: Descriptor -(# test: (# pole: [9] @integer #); prumer: (# vstup: ^test; vysledek: @real enter vstup[] do (for i: vstup.pole.range repeat vysledek+vstup.pole[i]->vysledek for); vysledek / vstup.pole.range->vysledek exit vysledek #); mujTest: @test do (for i: mujTest.pole.range repeat i+5->mujTest.pole[i] for); 'Prumer pole je: '->puttext; mujtest[]->prumer->putreal #)
4.2 Každý prvek registru bude ukazovat pouze na jeden (první) záznam.
Repetice se hlavně používá ve statických datových strukturách, viz např. příklady k tomuto modulu. Po jejich zvládnutí budete schopni si sami vytvořit například frontu, nebo zásobník.
37
5. Podtřídy – dědičnost Objekty mohou dědit strukturu lokálních dat (ne obsah) a možné operace s nimi, od jimi nadřazených objektů. Vzniká tak hierarchie objektů, přičemž nejobecnější objekty mají jen relativně málo společných vlastností (lokální data a operace nad nimi). Objekty, které jsou v hierarchii níže, mohou být postupně odlišovány od obecných. Příklad hierarchie dědění vidíme na obrázku 5.1. Obratlovci lokalita, rychlost barva
Savci
Ptáci létání
Papoušek slovník
Orel
Pes poslušnost
Velryba
Obr. 5.1 Hierarchie dědění Savec je objekt nejvyšší v uvedené hierarchii. Jeho vlastnosti (lokalita, rychlost, barva) jsou společné pro objekty, které leží v hierarchii níže. V našem obrázku platí, že každý objekt má pouze jednoho předka. Ve všeobecnosti to neplatí a existují násobné dědičnosti (multiple inheritance). Programovací jazyk BETA patří do rodiny objektově orientovaných programovacích jazyků a podobně jako v jiných objektově orientovaných jazycích, tak i v BETĚ lze vytvářet hierarchie vzorů(pokrývající jako zobecnění hierarchie tříd) sloužící k reprezentaci klasifikačních hierarchií. V této souvislosti budeme používat termín podvzor (subpattern) vyjadřující, že se jedná o vzor, který je specializací jiného vzoru. Pro takovýto mechanismus se obvykle v objektově orientovaných jazycích používá termín dědičnost. Vzpomeňme si nyní na vzor planeta, který by mohl vypadat takto: teleso
planeta
hvezda
Obr. 5.2: Znázornění příkladu jednoduché hierarchie vzorů planeta: (# velikost, hmotnost: @integer; atmosfera: ... #)
a vezměme vzor hvezda, který by mohl být deklarován takto:
38
hvezda: (# velikost, hmotnost, svitivost: @integer #)
Takto definované vzory nám mohou sloužit pro reprezentaci příslušných pojmů a jejich vlastností, ale jak známo z objektově orientovaných metod návrhu a implementace počítačových systémů (viz například [2]), je výhodné zavést jistou klasifikační hierarchii těchto vzorů, využívající a podchycující jejich společné vlastnosti a vzájemné vztahy. Za tímto účelem zavedeme zastřešující objekt shrnující společné vlastnosti všech objektů naší jednoduché hierarchie (viz obr.1.1): teleso: (# velikost, hmotnost: @integer #)
Vzor planeta pak lze zavést jako specializaci (podvzor) vzoru teleso: planeta: teleso (# atmosfera: ... #)
Použitím vzoru teleso jako prefixu popisovače objektu pro vzor planeta dosáhneme toho, že popisovač objektu pro vzor planeta zdědí všechny atributy vzoru teleso. Takto objekty deklarované jako instance vzoru planeta budou obsahovat atributy velikost, hmotnost a atmosfera. Vzor hvezda deklarujeme takto: hvezda: teleso (# svitivost: @integer #)
Vzory planeta a hvezda jsou podvzory vzoru teleso, zatímco ten je pro ně tzv. nadvzorem (superpattern). Tato terminologie je odrazem pohledu na klasifikační hierarchie z hlediska jejich významu pro generované instance. Definujeme-li vzor hvezda jako podvzor vzoru teleso, popisujeme dodatečné vlastnosti, v tomto případě je to svitivost. Lze říci, že množina všech instancí vzoru hvezda je podmnožinou množiny všech instancí vzoru teleso. Obecně přidáním nových vlastností k vlastnostem nadvzoru zmenšíme množinu všech možných instancí podvzoru a tato množina je podmnožinou množiny všech možných instancí příslušného nadvzoru. 5.1 Abstraktní nadvzory - kvalifikace odkazů Jak již bylo řečeno dříve, dynamické odkazy v BETĚ jsou typovány (kvalifikovány) na určitý vzor, který určuje množinu objektů, na kterou se lze odkazovat. Z tohoto hlediska mají klasifikační hierarchie značně význam, neboť odkaz typovaný na nějaký nadvzor může odkazovat na libovolný objekt z množiny instancí všech jeho podvzorů. Vzory jako teleso bývají nazývány abstraktní nadvzory, což vyjadřuje, že neslouží pro vytváření instancí, ale pouze jako nadvzory. Mezi jejich využití patří tak‚ vytváření univerzálních odkazů. Například: R: ^teleso
může odkazovat na libovolnou instanci vzorů planeta a hvezda (pokud by takové instance existovaly, může samozřejmě odkazovat také na instance vzoru teleso) a všech jejich případných podvzorů. Kvalifikace dynamického odkazu také určuje, které atributy mohou být odkazovány vzdáleném přístupem. Například: R.velikost
39
je legální a bude odkazovat na příslušný atribut, ať už R odkazuje na instanci jakéhokoli podvzoru vzoru teleso, neboť každý takový podvzor je rozšířením vzoru teleso a obsahuje tudíž zaručeně všechny jeho atributy. Legální jsou tedy vzdálené přístupy právě k těm atributům, které jsou atributy vzoru, jímž je příslušná dynamická proměnná kvalifikována. Mějme nyní deklaraci: P: ^planeta neboli dynamický odkaz P, který může odkazovat na instance vzoru planeta. Pak je možné provést přiřazení P[] -> R[]
neboť množina objektů, na které může odkazovat P je podmnožinou množiny objektů, na které může odkazovat R. Zatímco přiřazení R[] -> P[]
je možné provést pouze v případě, že R odkazuje instanci, kterou může odkazovat i P, v našem případě instanci vzoru planeta. V tomto případě však nelze o legálnosti přiřazení rozhodnout při překladu. Proto je takovéto přiřazení při překladu pokládáno za správné, avšak překladač sem umístí kontrolu, která případně způsobí chybu za běhu programu (tak je zajištěna správnost vzdálených přístupů k atributům, neboť do dynamického odkazu nelze přiřadit odkaz na objekt, který nemá příslušné atributy). Testování příslušnosti objektů ke vzorům lze provádět také explicitně. Například kontrola před přiřazením z minulého odstavce by mohla vypadat takto: (if R##=planeta## then R[] -> P[] else ... if)
Výraz R## odkazuje na vzor objektu na který ukazuje R a výraz planeta## znamená vzor planeta. Poznamenejme, že test by ve skutečnosti vypadal spíše R## <= planeta##, neboť obecně lze přiřazovat do proměnné P odkazy nejen na instance vzoru na který je kvalifikována, ale i všech jeho podvzorů. 5.2 Využití singulárních objektů Analogicky výše uvedeným informacím o Singulárních objektech lze definovat Singulární objekt také pomocí popisovače objektu s prefixem, tj. s využitím specializace: Zeme: planeta (# zivot: ... #)
Zeme takto má všechny vlastnosti společné planetám a jednu, život, navíc. 5.3 Vzor object O vzoru object jsme se již také zmínili: je to nejobecnější abstraktní nadvzor. Navíc, vzor object je také implicitní nadvzor všech vzorů, které nemají nadvzor definován, neboli pokud uvedeme popisovač objektu bez prefixu, je to interpretováno jako popisovač objektu s prefixem object.
40
Takto jsou všechny vzory podvzory vzoru object. 5.4 Procedurální aspekty podvzorů Výkonná část vzorů může být také děděna a specializována od nadvzorů k podvzorům. Mějme P1: (# do 'P1> '->putText; inner P1; '
kde příkaz inner P1 slouží k řízení specializace akcí mechanismem uspořádaného přidávání dalších akcí - určuje místo, do kterého bude umístěn kód případných podvzorů specializujících vzor P1. Pro instance vzoru P1 má tedy příkaz inner P1 prázdný význam. Avšak vzor P2: P1(# do 'P2'->putText #)
takto definuje pro svoje instance výkonnou část odpovídající (# do 'P1> '->putText; 'P2'->putText; '
(neboli výkonná část) byla zděděna z nadvzoru a na místo definované v nadvzoru příkazem inner přidány akce z výkonné části příslušného vzoru. Obecně vykonávání akcí instance určitého vzoru začíná ve výkonné části jeho nejvyššího nadvzoru (vzato do důsledku, je to vždy vzor object, který obsahuje ve své výkonné části pouze příkaz inner) a postupně (v místech určených umístěním příkazu inner) přechází do výkonných částí jednotlivých podvzorů, až do daného vzoru a pak stejným způsobem (tentokrát zdola nahoru) vykonává zbytky jednotlivých výkonných částí (uvedené za jednotlivými příkazy inner). Běžně se používá zkrácená verze příkazu inner, prostě bez uvedení vzoru, což znamená, že se týká bezprostředně obklopujícího popisovače objektu. Se způsobem dědění a rozšiřování atributů objektů od nadvzorů k podvzorům jsme se již seznámili. V této souvislosti však je třeba ještě říci, že všechny atributy deklarované v nějakém vzoru jsou viditelné ve všech jeho podvzorech. Mechanismus specializace akcí se dá v kombinaci se Singulárními objekty použít pro vytváření řídících struktur. Nejjednodušší příklad, který nám zároveň ukáže použití příkazů leave a restart, je tento: cycle: (# do inner; restart cycle #) Vzor cycle lze jako nekonečnou smyčku (Singulární objekt reprezentující nekonečnou smyčku) použít jednoduše takto: do cycle (# do 'a' -> putText #)
Takovou smyčku lze opustit příkazem leave cycle. Je jasné, že uvnitř popisovače objektu popisujícího Singulární objekt nemá příkaz inner smysl, neboť takový popisovač objektu nemůže mít podvzor, který by dal příkazu inner smysl. Častým využitím výše naznačené techniky jsou mnohé iterátory definované v různých datových strukturách. V tuto chvíli uveďme jednoduchý příklad, který vypíše obsah adresáře pomocí atributu scanEntries vzoru directory (se kterým jsme se již setkali): (# d: @directory do 'adresar'->d.name;
41
d.scanEntries(# do found.name -> putLine #) #)
Objekt vytvořený podle vzoru scanEntries prochází postupně všechny položky příslušného adresáře, odkaz na ně přichystá do proměnné s určitým názvem a strukturou (found), načež provede příkaz inner, neboli spustí příkazy uvedené v popisovači Singulárního objektu, jež je jeho potomkem. Mnoho dalších případů využití Singulárních výkonných objektů lze najít v knihovně containers, kde jsou definovány základní datové struktury jako například zásobník, fronta, seznam, tabulka s rozptýlenými hesly (hash), .... Ve složitějších případech se často využívají vlastnosti virtuálních vzorů o kterých pojednává samostatná kapitola, ve které lze také nalézt další příklady použití. 5.5 Vstupní a výstupní část podvzoru Mějme: P1: (# ... enter a .. exit z #) P2: P1 (# ... enter (b,c) ... exit (x,y) #)
pak vzor P2 má vstupní část (a,b,c) a výstupní část (z,x,y), neboli vstupní část je spojením vstupní části nadvzoru a vstupní části příslušného vzoru. Totéž platí o výstupních částech. Pro objasnění dědičnosti si uvedeme dva příklady. V prvním příkladě máme uvedenou dědičnost tříd, kdy každá třída má svoji výkonnou část. Po prostudování napište, jaká posloupnost znaků se zobrazí na obrazovce po spuštění programu? ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# Prvni: (# do 'a'->put; INNER ; 'f'->put #); Druhy: Prvni (# do 'b'->put; INNER ; 'g'->put #); Treti: Druhy (# do 'c'->put; INNER ; 'h'->put #); Ctvrty: Treti (# do 'd'->put; INNER Ctvrty; 'i'->put #); Paty: Ctvrty (# do 'e'->put; 'j'->put #); x: @paty do newline; x #)
Jaká posloupnost znaků se zobrazí na obrazovce po vytvoření instance y a jejímu spuštění? y: @Treti; Ve druhém příkladě nejdříve nakreslete deklarovanou strukturu tříd. ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# Record: (# key: @integer #); Book: Record (# Author: @text; title: @text #); Person: Record (# name: @text; sex: @text #); Employee: Person (# Salary: @integer; Position: @text #); Student: Person (# Status: @text #); R1,R2: ^Record; S1,S2: ^Student; B1,B2: ^Book; P1: ^Person; st: @text
42
do &Record[]->R1[]; &Record[]->R2[]; &Student[]->S1[]; &Student[]->S2[]; &Person[]->P1[]; &Book[]->B1[]; &Book[]->B2[]; 12->S1.key; 'Jan'->S1.name; 'muz'->S1.sex; 'bakalar'->S1.status; S1[]->R1[]; R1.key->putint; newline; r1[]->S2[]; S2.name[]->puttext; s2.sex[]->puttext #)
V uvedeném příkladě je názorně demonstrováno, že když přiřadíme proměnnou S1 do proměnné R1, v proměnné R1 jsou přístupny pouze atributy proměnné R1 tedy pouze atribut key. Pokud ale opět přiřadíme proměnnou R1 do proměnné S2 dostaneme se zase ke všem atributům které deklaruje třída Student. Odzkoušejte co se stane, budete-li chtít zobrazit atribut name u proměnné R1. Vytvořte následující strukturu tříd a ověřte přístupnost k jednotlivým atributům těchto tříd podle druhého příkladu. Třída Auto má atributy rychlost a spotřeba, třída nakladniAuto, která je podtřídou třídy Auto má atributy nosnost a třída osobniAuto, která je rovněž podtřídou třídy Auto má atributy početOsob. Pomocí dědičnosti se také vytváří na míru požadované iterace, např. průchod repeticí od nejvyššího indexu po nejnižší, nebo procházení repeticí s daným krokem. Tyto možnosti jsou uvedeny v příkladě.
Příklady: Příklad 17: ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# A: [12] @integer; forTo: (# low,high,ic: @integer enter (low,high) do low->ic; kkk: (if ic <= high then INNER forTo; ic+1->ic; restart kkk if) #); downTo:
43
(# low,high,ic: @integer enter (high,low) do high->ic; mm: (if ic >= low then INNER downTo; ic-1->ic; restart mm if) #); forStep: (# min,max,step,ic: @integer enter (min,max,step) do min->ic; loop: (if ic <= max then INNER forStep; ic+step->ic; restart loop if) #); do (for i: 12 repeat i*i->A[i] for); '\nVypis prvku 2-5\n'->puttext; (2,5)->forTo (# do A[ic]->putint; ','->put #); '\nVypis prvku 9-4\n'->puttext; (9,4)->downTo (# do A[ic]->putint; ','->put #); '\nVypis prvku 2-9 s krokem 3\n'->puttext; (2,9,3)->forStep (# do A[ic]->putint; ','->put #) #) Příklad 18: ORIGIN '~beta/basiclib/betaenv'; INCLUDE '~beta/basiclib/math' '~beta/basiclib/numberio' '~beta/basiclib/textUtils'; -- program: Descriptor -(# Register: (# Table: [12] ^Record; Top: @integer; Init: (# do 0->Top #); scan: (# i: @integer; current: ^record; q: @boolean do 1->i; false->q; search: (if i <= top then Table[i][]->current[]; INNER scan; (if q then leave search if); i+1->i; restart search if) #); new: (# rc: ^record; n: ^text do &record[]->rc[]; '\nCislo: '->puttext; getint->rc.num; zn; 'Jmeno: '->puttext; getline->n[]; n->rc.name
44
exit rc[] #); Has: (# R1: ^Record; Result: @boolean enter R1[] do False->Result; scan (# do (if current.num = R1.num then true->result->q if) #) exit Result #); Insert: (# New: ^Record enter New[] do (if (New[]->Has) = False then top+1->top; (if (Top <= Table.Range) then New[]->Table[Top][]; else ' Owerflow'->putline if); else 'zaznam jiz existuje zapis neproveden '->puttext if) #); Remove: scan (# key: @integer enter key do (if current.num = key then (for inx: Top-i repeat Table[i+inx][]->Table[i+inx-1][] for); true->q if) #); Print: scan (# do '\nPrvek: '->puttext; i->putint; current.tisk #); Get: (# i: @integer; rx: ^Record enter i do Table[i][]->rx[] exit rx[] #); getTop: (# exit top #); ForAll: (# Current: ^Record do (for i: Top repeat Table[i][]->Current[]; INNER ForAll for) #) #); Record: (# num: @integer; name: @text; tisk: (# do '\nCislo: '->puttext; THIS(RECORD).num->putint; ' jmeno: '->puttext;
45
name[]->puttext #) enter (num,name) #); forTo: (# low,high,ic: @integer enter (low,high) do low->ic; kkk: (if ic <= high then INNER forTo; ic+1->ic; restart kkk if) #); downTo: (# low,high,ic: @integer enter (high,low) do high->ic; mm: (if ic >= low then INNER downTo; ic-1->ic; restart mm if) #); zn: (# do (if keyboard.peek = ascii.newline then keyboard.get if) #); R,Rnew: @Register; sum: @integer; rc: ^Record do R.Init; R.new->R.insert; R.new->R.insert; R.new->R.insert; R.new->R.insert; R.print; newline; R.ForAll (# do (if 'Renata'->Current.name.equal // true then Current.tisk if) #); R.ForAll (# do (if 'Josef'->Current.name.equal // true then current.tisk; Current[]->Rnew.insert if) #); '\nRnew tisk'->puttext; Rnew.ForAll (# do Current.tisk #); '?'->put; get; (2,3)->forTo (# do ic->R.get->rc[]; rc.tisk #); (1,3)->forTo (# do ic->R.get->rc[]; rc.tisk #); ':'->put; get; (4,2)->downTo (# do ic->R.get->rc[]; rc.num+sum->sum #); '\nsoucet num zaznamu 2 az 4 = '->puttext; sum->putint; get; 0->sum; R.ForAll (# do Current.num+sum->sum #); '\n\tsoucet vsech cisel: '->puttext; sum->putint; 12->R.remove; R.print #)
46
Dědičnost, hierarchie tříd, singulární objekt.
Objekty mohou dědit strukturu lokálních dat (ne obsah) a operace s nimi, od nadřazených objektů. Abstraktní třída slouží pouze k deklaraci, neslouží k vytváření instancí. Třída Object je nejobecnější abstraktní třída v jazyce BETA. Příkaz inner a jeho význam v dědičnosti tříd. Obecně vykonávání akcí instance určitého vzoru začíná ve výkonné části jeho nejvyššího nadvzoru (vzato do důsledku, je to vždy vzor object, který obsahuje ve své výkonné části pouze příkaz inner) a postupně (v místech určených umístěním příkazu inner) přechází do výkonných částí jednotlivých podvzorů, až do daného vzoru a pak stejným způsobem (tentokrát zdola nahoru) vykonává zbytky jednotlivých výkonných částí (uvedené za jednotlivými příkazy inner).
Co dědí objekty od svých nadřazených objektů? Co je to abstraktní třída? Jaká je funkce příkazu inner?
Jak se předávají parametry do/z metody daného objektu a jak se předávají parametry přímo do/z daného objektu?
5.1 Vytvořte s použitím předdefinovaného vzoru cycle smyčku programu, kdy po zadání znaků a,b se zvýší čítač a po zadání znaku c se smyčka opustí. Zadání jiného znaku nemá význam. 5.2 Modifikujte tento program tak, že vypustíte vzor cycle.
47
5.1. ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# ch: @char; pocet: @integer; zn: (# do (if keyboard.peek = ascii.newline then keyboard.get if) #) do 'Zadavejte znaky a A b B, c C konec, jiny znak nema vyznam '->puttext; newline; opakovani: cycle (# do ' znak: '->puttext; get->ch; zn; (if ch // 'a' // 'A' then pocet+1->pocet // 'b' // 'B' then pocet+1->pocet // 'c' // 'C' then leave opakovani if) #); ' Pocet zadanych znaku A a B: '->puttext; pocet->putint
#) 5.2. ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# ch: @char; pocet: @integer; zn: (# do (if keyboard.peek = ascii.newline then keyboard.get if) #) do 'Zadavejte znaky a A b B, c C konec, jiny znak nema vyznam '->puttext; newline; opakovani: (# do ' znak: '->puttext; get->ch; zn; (if ch // 'a' // 'A' then pocet+1->pocet; restart opakovani // 'b' // 'B' then pocet+1->pocet; restart opakovani // 'c' // 'C' then leave opakovani else restart opakovani if)
48
#); ' Pocet zadanych znaku A a B: '->puttext; pocet->putint #)
Hierarchie tříd (dědičnost) patří k základním charakteristikám objektově orientovaného programování. Zaměřte se na pochopení příkazu inner (v prvním příkladě) a na přístup k atributům tříd, ve druhém příkladě. Jejich pochopení je důležité.
49
6. Virtuální procedury (pozdní vazba) Pomocí podvzorů je možno popisovat vlastnosti a strukturu objektů specializací tohoto popisu z jiných vzorů (nadvzorů). Pomocí virtuálních vzorů (virtual patterns) je možno popsat obecné vlastnosti určitého atributu v nadvzoru a tento popis specializovat v podvzorech. Předpokládejme, že potřebujeme pro vzory v naší hierarchii těles definovat atributy popisující akci "vytiskni na obrazovku informace, které jsou o příslušném objektu k dispozici". Bez nároků na propracování detailů zobrazení lze zavést následující definice: teleso: (# velikost, hmotnost: @ integer; tisk:< (# do 'velikost: '->putText; velikost->putInt; newline;‘hmotnost: '->putText; hmotnost->putInt; newline; inner #) #); planeta: teleso (# atmosfera: ... tisk::< (# do atmosfera.tisk; inner #) #); hvezda: teleso (# svitivost: @integer; tisk::< (# do 'svitivost: '->putText; svitivost->putInt; newline; inner #) #);
kde je dosaženo požadované funkce, neboť každý ze vzorů teleso, planeta, hvezda má atribut tisk vypisující příslušné informace, přičemž pokud zavedeme například odkaz R: ^teleso
pak provedení R.tisk
vykoná příslušnou činnost v závislosti na tom, kterého vzoru je instancí objekt jež R právě odkazuje. Způsob, jakým je toho dosaženo popisují následující poznámky: •
Atribut tisk vzoru teleso je příkladem deklarace virtuálního vzoru, což je vyjádřeno pomocí znaku :<.
•
Popisovač objektu uvedený za znakem :< je přímou kvalifikací virtuálního vzoru. Obecně totiž deklarace virtuálního vzoru může být kvalifikována libovolným vzorem, například by zde bylo možno uvést: tisk:< tiskTeleso
kde tiskTeleso je samostatně deklarovaný vzor popisující příslušnou činnost. Pak má terminologie kvalifikace jasně význam, neboť znamená, že virtuální vzor tisk může být dále rozšířené pouze podvzory vzoru tiskTeleso, čemuž říkáme, že je kvalifikován vzorem tiskTeleso.
50
•
Příkaz inner v popisovači objektu funguje stejně jako u podvzorů pro řízení specializace výkonných částí vzorů, určuje, kam bude umístěn rozšiřující kód.
•
V podvzorech vzoru teleso je provedeno rozšíření virtuálního vzoru tisk (pomocí znaku ::<). Popisovač objektu uvedený za tímto znakem je automaticky považován za (anonymní) podvzor (anonymního) vzoru, jímž je popisovač objektu, který byl uveden v deklaraci virtuálního vzoru. Takto popisovač objektu uvedený za znakem ::< popisuje rozšíření (specializaci) virtuálního vzoru. Stejným způsobem lze provést rozšíření virtuálního vzoru kvalifikovaného na nějaký vzor, například tisk::< tiskPlaneta
za předpokladu, že vzor tiskPlaneta je podvzorem vzoru tiskTeleso (platí pokud byl vzor tisk deklarován druhým způsobem, pomocí vzoru tiskTeleso). Virtuální vzor v BETĚ nemůže být předefinován (kompletně nahrazen) jak je možné například v C++, ale může být pouze rozšířen. Tento fakt vychází z principu, že podvzory by měly být v ideálním případě funkčně ekvivalentní svým nadvzorům, pouze více specializovány (rozšířením popisu jejich vlastností a chování). • •
Rozšíření virtuálního vzoru se také nazývá vazba (binding). Příkaz inner je uveden ve virtuálních vzorech tisk vzorů planeta a hvezda pro umožnění jejich další specializace v případných podvzorech.
•
K rozšíření virtuálního vzoru lze použít také finální vazbu (final binding), která používá znaku :: a vyjadřuje totéž co vazba, s tím rozdílem, že zakazuje další rozšíření příslušného virtuálního vzoru. Kdybychom například v deklaraci vzoru planeta použili řádku: tisk:: (# do atmosfera.tisk #)
namísto výše uvedené, znamenalo by to totéž, ale případné podvzory vzoru planeta by nemohly vzor tisk rozšiřovat (proto by zde uvedení příkazu inner nemělo žádný smysl). 6.1 Příklad použití virtuálních vzorů v řídících strukturách V základní knihovně betaenv je definována následující řídící struktura: Loop: (# while:< BooleanValue(# do true->value; INNER #); until:< BooleanValue; whilecondition: @while; untilcondition: @until; do loop: (if whilecondition //true then INNER; (if untilcondition//false then restart loop if) if ) #) ... BooleanValue:(# value: @boolean do INNER exit value #);
která by se dala považovat za zobecnění řídících konstrukcí while a repeat, jak jsou známy z jiných jazyků.
51
V tomto příkladu je také vidět ještě jednu možnost kvalifikace virtuálního vzoru, totiž formou P(#...#), neboli popisovačem objektu specializujícím nějaký vzor P. Nám již známá možnost, přímá kvalifikace pomocí popisovače objektu, je vlastně speciální případ, kdy implicitně P = object. Uvedenou řídící strukturu lze jednoduše používat, například takto loop (# while ::< (# do do příkazy #)
výraz -> value #)
Příklady: Příklad 19: ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# Job: (# name: @text; Value: (# V: @integer do INNER exit V #); income: Value (# do (if tax < 0 then Salary->V else (Salary-Tax)->V if) #); Tax: Value (# do (Salary-Deductible)*45 div 100->V #); Salary:< Value; Deductible:< Value (# do 10000->V; INNER #) #); PermanentJob: Job (# #); NonPermanentJob: Job (# noOfHours: @integer; Salary::< (# do noOfHours*hourlyWage->V #); Deductible::< (# do 3000+V->V; INNER #); hourlyWage:< Value #); Job1: PermanentJob (# Salary::< (# do 35000->V #); Deductible::< (# do 2000+V->V #) #); Job2: PermanentJob (# Salary::< (# do 45000->V #); Deductible::< (# do 2500+V->V #) #); Job3: NonPermanentJob (# hourlyWage::< (# do 80->V #) #); Job4: NonPermanentJob (# hourlyWage::< (# do 85->V #) #); Job5: PermanentJob (# Salary::< (# do 50000->V #); Deductible::< (# do V+1500->V #) #); Registr:
52
(# staff: [12] ^Job; top: @integer; insert: (# e: ^Job enter e[] do (if top < staff.range then top+1->top; e[]->staff[top][] else '\nStaff je full '->puttext if) #); displaySalary: (# do (for i: top repeat '\ntyp prace: '->puttext; staff[i].name[]->puttext; ' plat: '->puttext; staff[i].salary->putint; ' skutecny prijem: '->puttext; staff[i].income->putint for) #) #); J1: ^Job1; J2: ^Job2; J3: ^Job3; J5: ^Job5; R: @Registr do &Job1[]->J1[]; &Job2[]->J2[]; &Job3[]->J3[]; 'Tesar'->J1.name; J1[]->R.insert; 'Ucitel'->J2.name; J2[]->R.insert; 'Programator'->J3.name; 200->J3.noOfHours; J3[]->R.insert; &Job5[]->J5[]; 'Manager'->J5.name; J5[]->R.insert; R.displaySalary; #) Příklad 20: ORIGIN '~beta/basiclib/betaenv'; -- program: Descriptor -(# a,b: [20] @integer; x: @integer; suma,topA,topB: @integer; q: @boolean do '\nuntil\n'->puttext; 0->suma; loop (# until::< (# do (suma > 100)->value #) do '\n Cislo: '->puttext; getint->x;
53
(if topA < a.range then topA+1->topA; x->a[topA]; suma+x->suma; ' suma: '->puttext; suma->putint else '\nPole je zaplnene'->putline if) #); newline; (for i: topA repeat i->putint; ' '->puttext; a[i]->putint; newline for); '?'->put; get; 0->suma; true->q; loop (# while::< (# do '\nCislo: '->puttext; getint->x; (x > 0) and q->value #) do suma+x->suma; '\nsuma: '->puttext; suma->putint; (if topB < (b.range-1) then 1+topB->topB; x->b[topB] else false->q; '\nPole zaplneno'->putline if) #); newline; (for i: topB repeat i->putint; ' '->puttext; b[i]->putint for) #)
Virtuální procedura.
Pomocí virtuálních procedur je možné popsat činnost dané metody v dané třídě a tu pak specializovat v metodě se stejným názvem v příslušné podtřídě (podtřídách). Pomocí virtuálních procedur je realizovaná pozdní vazba zmíněná již v prvním modulu. Příjemce zprávy, dynamická proměnná, může za běhu programu ukazovat na libovolnou instanci svých podtříd. Teprve až za běhu programu je známo, na kterou instanci ze svých podtříd daná dynamická proměnná ukazuje a podle toho se vybere příslušná virtuální metoda. Většina objektově orientovaných jazyků specializaci metod v podtřídách nezná. Pro deklaraci metody v podtřídě používají mechanismus zastiňování (overriding).
54
V čem se liší použití symbolů ::< a :< ? Jak pracují virtuální procedury?
K čemu používáme mechanismus virtuálních procedur?
Vytvořte hierarchii tříd s virtuální metodou tisk (tiskne příslušné atributy třídy) v následující struktuře: Záznam(klíč), Kniha(autor, název), Osoba(jméno, pohlaví), Zaměstnanec(plat, pozice), Student(status). Kniha je podtřídou třídy Záznam, Osoba je podtřídou třídy Záznam, Zaměstnanec je podtřídou třídy Osoba, Student je podtřídou třídy Osoba. Typy atributů: klíč a plat jsou celočíselné, ostatní jsou typu text.
Řešení je v příkladu: http://albert.osu.cz/~hunka v příkladu: virtpat.bet.
V tomto modulu se vracíme k pozdní vazbě. Pozdní vazba se v jazyce BETA realizuje využitím virtuálních procedur. Mechanismus virtuálních procedur se liší od jiných jazyků, protože je spíše zaměřen na další specifikaci metod v podtřídách než na zastiňování (overriding).
55
7. Bloková struktura Kompozice je základním prostředkem pro organizaci objektů a vzorů ve smyslu součástí jiných objektů a konceptů. Existuje řada různých forem kompozice a jedním z nich je lokalizace. Lokalizace je v Betě umožněna díky blokové struktuře, protože popisovač-objektu může být libovolně vnořován. Zajímavým aspektem jazyka BETA je také obecná bloková struktura. Rozšiřuje vyjadřovací schopnost jazyka a dovoluje i značně specifické vyjadřovací konstrukce. Z rozsahových důvodů zde uveďme pouze jeden jednoduchý příklad pro ilustraci. Mějme vzor P: (# X: (# ... #) ... #) a instance a,b: @P;
pak a.X i b.X jsou vzory, ale každý jiný. Použijeme-li tyto vzory pro vytvoření instancí, budou to instance navzájem různých vzorů. Názorně celou záležitost vidíme na následujícím příkladě. Deklarujeme třídu Produkt a uvnitř této třídy je deklarovaná třída Objednávka. Atributy třídy Produkt jsou globální k atributům vnořené třídy Objednavka. V metodě tisk se můžeme odkazovat na atributy deklarované ve třídě Produkt. Metoda tisk třídy objednávka je deklarovaná jako virtuální, aby mohla být dále specializovaná ve svých podtřídách. (# Produkt: (# jmeno: @text; cena: @integer; pocetJednotek: @integer; Objednavka: (# zak: ^Zakaznik; datum: @text; tisk:< (# do '\nJmeno: '->puttext; jmeno[]->puttext; ' Cena: '->puttext; cena->putint; ' PocetJednotek: '->puttext; pocetJednotek->putint; zak.tisk; ' datum: '->puttext; datum[]->puttext; INNER #) enter (zak[],datum) #) enter (jmeno,cena,pocetJednotek) #); Zakaznik: (# jmeno: @text; mesto: @text; tisk: (#
56
do '\nZakaznik jmeno: '->puttext; jmeno[]->puttext; ' mesto : '->puttext; mesto[]->puttext #) enter (jmeno,mesto) #); z1,z2: ^Zakaznik; P1,P2: @Produkt; o1,o2: @P1.objednavka; o3,o4: @P2.objednavka; x1: ^Produkt.objednavka do &Zakaznik[]->z1[]; &Zakaznik[]->z2[]; ('Karel','Havirov')->z1; ('Jan','Ostrava')->z2; (z1[],'brezen/99')->o1; ('chleba',18,200)->P1; o1.tisk; (z2[],'zari/77')->o3; o3.tisk; ('kolo',8500,40)->P2; o3.tisk; (z2[],'duben/98')->o2; o2.tisk; o2[]->x1[]; x1.tisk; o3[]->x1[]; x1.tisk; #)
Deklarujme si dva objekty třídy Produkt P1, P2. V rámci těchto objektů (instancí) si deklarujme dvě objednávky o1, o2, jako instance vnořené třídy P1.objednavka. Podobně si deklarujeme i dvě další instance a sice o3 a o4. Zatímco instance o1 a o2 budou mít společné atributy třídy Produkt a to jmeno, cena a pocetJednotek, budou mít odlišný atribut datum. Atribut zákazník je ve třídě objednávka deklarovaný pomocí dynamické proměnné, a proto záleží pouze na tom jakou proměnnou zde přidělíme. Stejným způsobem pracují i instance o3 a o4. Pokud bychom chtěli přiřazovat do nějaké proměnné libovolnou instanci vnořené třídy objednavka, musíme ji deklarovat jako dynamickou proměnnou viz x1. Jak je z příkladu patrné, do této proměnné můžeme uložit libovolnou instanci od vnořené třídy objednávka. Platí zde to co platilo v dříve uvedeném jednoduchém příkladě, že instance O1 a O2 resp. O3 a O4 nejsou již instance od jedné třídy, ale mohou mít společné atributy. Právě to může být výhodné pro modelování některých praktických záležitostí.
Příklady: Příklad 21: ORIGIN '~beta/basiclib/betaenv';
57
-- program: Descriptor -(# a,b: [20] @integer; x: @integer; suma,topA,topB: @integer; q: @boolean do '\nuntil\n'->puttext; 0->suma; loop (# until::< (# do (suma > 100)->value #) do '\n Cislo: '->puttext; getint->x; (if topA < a.range then topA+1->topA; x->a[topA]; suma+x->suma; ' suma: '->puttext; suma->putint else '\nPole je zaplnene'->putline if) #); newline; (for i: topA repeat i->putint; ' '->puttext; a[i]->putint; newline for); '?'->put; get; 0->suma; true->q; loop (# while::< (# do '\nCislo: '->puttext; getint->x; (x > 0) and q->value #) do suma+x->suma; '\nsuma: '->puttext; suma->putint; (if topB < (b.range-1) then 1+topB->topB; x->b[topB] else false->q; '\nPole zaplneno'->putline if) #); newline; (for i: topB repeat i->putint; ' '->puttext; b[i]->putint for) #)
Lokalizace, deklarace třídy ve třídě.
58
Kompozice (skládání) je základním prostředkem pro organizaci objektů ve smyslu součástí jiných objektů. Bloková struktura jazyka BETA dovoluje rozšířit kompozici o lokalizaci, kdy je vnořena jedna třída v jiné třídě. Tato konstrukce dovoluje to, že dvě různé instance vnořené třídy mohou sdílet stejné hodnoty atributů vnější třídy.
Jak deklarujeme třídu ve třídě? K čemu se dá využít taková deklarace?
Které atributy jsou v programu btcv9\flight.bet „globální“ vzhledem ke vnořené třídě Flight?
„Globální“ jsou všechny atributy třídy FlightType, tedy: source, destination, arrivalTime, departureTime, flyingTime, noOfSeats.
Bloková struktura – lokalizace má velké použití při modelování reálného světa. Právě tam, kde některé atributy jsou „globální“ a jiné „lokální“. Např. univerzita má jednoho rektora a 3 prorektory společné pro všechny fakulty. Deklarujeme-li třídu fakulta uvnitř třídy univerzita, pak akademičtí funkcionáři univerzity budou „globální“ ve všech fakultách.
59
8. Virtuální vzory tříd (knihovna container) Virtuální vzory jsou používány k popisu společné struktury atributů vzoru v nadvzoru. V BETĚ není technický rozdíl mezi vzorem procedury (deklarací metody) a vzorem třídy (deklarací třídy), je to jednoduše záležitostí jak jsou vytvářeny instance. Instance pro vzory procedur jsou ve výkonné části (action-part) a jsou hned prováděny. Tyto instance se pak stávají nepřístupné, protože žádná reference odkazující se na ně není nikde uložena. Pro vzory tříd jsou vytvářeny pojmenované instance v tom smyslu, že reference je uschována právě v pojmenované proměnné. Vraťme se nyní k definici datové struktury zásobník a pokusme se definovat (generický) zásobník, který umožní vytvářet zásobníky objektů libovolného typu: Stack: (# Data: [10] ^element; Top: @integer; element:< object; Push: (# e: ^element enter e[] do Top+1->Top; e[] ->Data [Top][] #); Pop: (# e: ^element do Data[Top][] ->e[] ; Top-1->Top exit e[] #); ForAll: (# Current: ^element do (for i: Top repeat Data[i][]->Current[]; INNER for) #) #);
Virtuální vzor element určuje typ objektů, které lze ukládat do zásobníku. Protože je kvalifikován vzorem object, může být prvkem zásobníku instance libovolného vzoru. Mezi operace zásobníku byla přidána (pro zásobník zcela netypická) operace ForAll, která dovoluje procházet všechny objekty uložen‚ v zásobníku a provádět s nimi nějaké operace. Tento mechanismus je často využíván v knihovnách pro operace iterativního charakteru, příkladem může být operace scanEntries vzoru directory jež byla použita v předcházejícím textu. V podvzorech vzoru stack můžeme upřesnit typ prvků, které budou ukládány do zásobníku. Definujeme například zásobník pro instance vzoru hvezda: hvezdy: @Stack(# element::< hvezda #)
pak do zásobníku hvezdy lze vkládat pouze objekty vzoru hvezda nebo jeho podvzorů. Pak také lze napsat: hvezdy.ForAll(# Current.tisk #)
60
pro vypsání všech dostupných informací o všech objektech uložených v zásobníku. Protože atribut Current je kvalifikován virtuálním vzorem element, který v objektu hvezdy znamená vzor hvezda, lze přistupovat k atributu tisk. Vzor Stack by mohl být kořenem hierarchie vzorů pro zásobníky různých typů objektů, což by bylo výhodné například pro postupné zavádění dalších operací nad zásobníkem tak, jak se specializují vlastnosti potenciálně ukládatelných objektů. Stejným způsobem si můžeme vytvořit statický seznam, ve smysly, že k jeho vytvoření použijeme repetice, nebo dynamický seznam. Na stejném principu byla vytvořena knihovna containers, která obsahuje nejpoužívanější třídy pro práci se seznamy. Při použití musíme dodržet několik zásad. Do záhlaví musíme dopsát přesně tu knihovnu, kterou používáme. Např. chceme používat třídu list, musíme dopsat: ‘~beta/containers/list’ , chceme – li používat např. arraycontainer, musíme dopsát: ‘~beta/containers/arrayContainer’ . Zapisujeme do nabídky Fragments/Edit … a musíme dát pozor na velká a malá písmena. (Tato nabídka je citlivá na velká a malá písmena). Generický seznam již nemusíme vytvářet, protože ten je již vytvořený v uvedené knihovně. Pokud bychom chtěli upravit předešlý program s tím, že použijeme dynamický seznam (s ukazateli dopředu i dozadu), bude po doplnění knihovny list jeho deklarace následující: hvezdy: @list(# element::< hvezda; push: (# e: ^hvezda enter e[] do e[]-> append #); pop: (# e: ^hvezda do (head).elm[]->e[]; head.delete exit e[] #); tisk:(# do scan (# do current.tisk #)#) Jak je vidět z uvedeného popisu, musíme při využívání generických knihovních tříd používat předem deklarované metody. Proto si nyní uvedeme alespoň základní a nejpoužívanější metody.
název metody append preppend head last delete at
find locate
použití přidává daný prvek na konec seznamu; prvek musí být na vstupu metody jako reference přidává daný prvek na začátek seznamu; prvek musí být na vstupu metody jako reference vrací pozici prvního prvku seznamu; pokud chceme pracovat s prvkem seznamu, musíme použít (head).elm[] vrací pozici posledního prvku seznamu; platí stejná pravidla jako pro metodu head smaže prvek na dané pozici v seznamu; na vstupu musí být pozice prvku, ne prvek vrací pozici prvku v daném seznamu; na vstupu je reference na prvek a na výstupu je reference na pozici; pokud prvek v seznamu není, vrací se NONE prochází celý seznam a vrací první objekt (instanci), která vyhovuje zadané podmínce vrací pozici v seznamu, která první vyhovuje zadané podmínce
61
clear size
ruší všechny prvky seznamu vrací aktuální počet prvků seznamu
Právě na virtuálních vzorech tříd můžeme demonstrovat znovupoužitelnost. Všechny knihovny třídy container jsou abstraktní třídy. Při jejich použití musíme specifikovat prvek seznamu, eventuálně další potřebné metody. V knihovně container je také knihovna sequentialContainer, která mimo jiné obsahuje abstraktní třídy pro deklaraci zásobníku (stack) a fronty (queue).
Příklady: Příklad 22: ORIGIN '~beta/containers/list'; --- program:descriptor --(* This demo program illustrates the usage of the list pattern. The first * part of the demo illustrates inserting and deleting elements from the lists. * * At the end of this file, a copy of the output of this program is given *) (# intList: list(# element:: integerObject #); intList1, intList2: @intList; intList3: ^intList; i: [10]^integerObject; listPosition: ^intList.theCellType; do (* initializing the integerObjects *) (for int:10 repeat &integerObject[]->i[int][]; int->i[int] for); (* initializing intList1 and putting some integerObjects into it *) intList1.init; 'intList1.prepend: '->puttext; (for int:4 repeat i[int][]->intList1.prepend for); intList1.scan(# do current->putint; ','->put #); newline; (* initializing intList3 and putting some integerObjects into it *) intList2.init; 'intList2.append: '->puttext; (for int:4 repeat i[int+3][]->intList2.append for); intList2.scan(# do current->putint; ','->put #); newline; (* illustrates the reverse scanning *) 'intList2.scanReverse: '->puttext; intList2.scanReverse(# do current->putint; ','->put #); newline; (* initializing intList3 *) &intList[]->intList3[]; intList3.init;
62
(* illustrating concatenation of lists *) 'intList1[]->intList2.concatenate->intList3[]: '->puttext; intList1[]->intList2.concatenate->intList3[]; intList3.scan(# do current->putint; ','->put #); newline; (* illustration getting the position of some integerObject in the list, and * replacing the element at that position *) '"10"->("2"->intList3.at).elm[]: '->puttext; i[2][]->intList3.at->listPosition[]; i[10][]->listPosition.elm[]; intList3.scan(# do current->putint; ','->put #); newline; (* illustrates deleting an element from the list *) '6->intList2.at->intList2.delete: '->puttext; i[6][]->intList2.at->intList2.delete; intList2.scan(# do current->putint; ','->put #); newline; i[7][]->intList3.at->listPosition[]; (* illustrating inserting integerObjects before some list position *) '(8,7)->intList3.insertBefore: '->puttext; (i[8][],listPosition[])->intList3.insertBefore; intList3.scan(# do current->putint; ','->put #); newline; (* illustrating deleting integerObjects after some list position *) '7->intList3.deleteAfter: '->puttext; listPosition[]->intList3.deleteAfter; intList3.scan(# do current->putint; ','->put #); newline; (*********** OUTPUT *************** * intList1.prepend: 4,3,2,1, * intList2.append: 4,5,6,7, * intList2.scanReverse: 7,6,5,4, * intList1[]->intList2.concatenate->intList3[]: 4,5,6,7,4,3,2,1, * "10"->("2"->intList3.at).elm[]: 4,5,6,7,4,3,10,1, * 6->intList2.at->intList2.delete: 4,5,7, * (8,7)->intList3.insertBefore: 4,5,6,8,7,4,3,10,1, * 7->intList3.deleteAfter: 4,5,6,8,7,3,10,1, ******************************) #)
Příklad 23: ORIGIN '~beta/basiclib/betaenv'; -- lib: Attributes -Register: (# Content:< Object; Table: [12] ^Content; Top: @integer; Init: (# do 0->Top #); Has: Find (# Result: @boolean; NotFound:: (# do False->Result #) do True->Result; exit Result #); Find: (# subject: ^Content; index: @integer; NotFound:< Object enter Subject[] do
63
1->index; Search: (if (index <= Top) // true then (if Subject[] // Table[index][] then INNER Find; leave Search else 1+index->index; restart search if) else &NotFound if) #); Insert: (# New: ^Content enter New[] do (if (New[]->Has) = False then top+1->top; (if (Top <= Table.Range) // True then New[]->Table[Top][]; else ' Owerflow'->putline if); else 'zaznam jiz existuje zapis neproveden '->puttext if) #); Get: (# i: @integer; rx: ^Content enter i do Table[i][]->rx[] exit rx[] #); scan: (# Current: ^Content do (for i: Top repeat Table[i][]->Current[]; INNER scan for) #) #); RecordRegister: Register (# Content::< Record; Remove: (# num: @integer enter num do Search: (for inx: Top repeat (if Table[inx].num = num then (for i: Top-inx repeat Table[inx+i][]->Table[inx+i-1][] for); Top-1->Top; '\nZaznam odstranen\n'->puttext; leave Search if) for) #); print:< (# do scan (# do Current.tisk #); INNER print #); nazev: (# do '\nRecordRegister ////// \n'->puttext #) #);
64
StudentRegister: RecordRegister (# Content:: Student; UpdateStatus: Find (# Status: @text; NotFound:: (# do '\nAktualizace neprovedena\n'->puttext #) enter Status do Status->Table[index].Status #); nazev: (# do '\nStudentRegister /////// \n'->puttext #) #); Record: (# typRec:< Record; r: ^Record; num: @integer; name: @text; input:< (# rx: ^typRec enter (num,name) do &typRec[]->rx[]; num->rx.num; name->rx.name; INNER input; rx[]->r[] exit r[] #); tisk:< (# do '\nCislo: '->puttext; num->putint; ' jmeno: '->puttext; name[]->puttext; INNER tisk #) #); Student: Record (# typRec::< Student; skola: @text; status: @text; input::< (# sk,st: @text; enter (sk,st) do sk->rx.skola; st->rx.status #); tisk::< (# do ' skola: '->puttext; skola[]->puttext; ' status: '->puttext; status[]->puttext #) #) Příklad 24: ORIGIN '~beta/basiclib/betaenv'; INCLUDE './virtlib'; -- program: Descriptor -(# RR: @RecordRegister; rcx: @Record; RS: @StudentRegister; stud: @Student;
65
std: ^Student do RR.init; RS.init; (1,'Jan')->rcx.input->RR.insert; (2,'Jindrich')->rcx.input->RR.insert; (10,'Alena','OU','magistr')->stud.input->RS.insert; (11,'Eva','VSB','bakalar')->stud.input->RS.insert; RR.nazev; RR.print; newline; RS.nazev; RS.print; 1->RS.get->std[]; (std[],'brbrbrb')->RS.UpdateStatus; newline; RS.print #) Příklad 25: ORIGIN '~beta/basiclib/betaenv'; INCLUDE '~beta/basiclib/textUtils'; -- lib: Attributes -Record: (# Key: @integer; Display:< (# s: ^stream enter s[] do s.newline; '-------------------'->s.putLine; 'Record: Key = '->s.putText; Key->s.putInt; s.newline; INNER #); Insert:< (# rcr:< Record; rec: ^rcr; r: ^Record; do &rcr[]->rec[]; '\nKey :'->puttext; getint->rec.key; zn; INNER insert; rec[]->r[] exit r[] #) #); Person: Record (# Name,Sex: @text; Display::< (* a further binding of Display from Record *) (# do 'Person: Name = '->s.putText; Name[]->s.putLine; ' Sex = '->s.putText; Sex[]->s.putLine; INNER #);
66
Insert::< (# N,S: ^text; rcr::< Person; do 'Person Name: '->puttext; getline->N[]; N->rec.Name; 'Sex: '->puttext; getline->S[]; S->rec.Sex; INNER #) #); Employee: Person (# Salary: @integer; Position: @text; Display::< (# do 'Employee: Salary = '->s.putText; salary->s.putInt; s.newline; ' Position = '->s.putText; Position[]->s.putLine; INNER display #); Insert::< (# P: ^text; rcr::< Employee; do '\nSalary: '->puttext; getint->rec.Salary; zn; 'Position: '->puttext; getline->P[]; P->rec.Position; INNER insert #) #); Student: Person (# Status: @text; Display::< (# do 'Student: Status = '->s.putText; Status[]->s.putLine; INNER #); Insert::< (# S: ^text; rcr::< Student; do 'Status: '->puttext; getline->S[]; S->rec.Status; INNER insert #) #); Book: Record (# Author,Title: @text; Display::< (# do 'Book: Author = '->s.putText; Author[]->s.putLine; ' Title = '->s.putText; Title[]->s.putLine; INNER
67
#); Insert::< (# A,T: ^text; rcr::< Book; do 'Author: '->puttext; getline->A[]; A->rec.Author; 'Title: '->puttext; getline->T[]; T->rec.Title; INNER insert #) #); Register: (# regLst: (# succ: ^regLst; elm: ^record #); head: ^regLst; init: (# do none ->head[] #); scan: (# elm: ^record; p: ^regLst; q: @boolean do head[]->P[]; search: (if (P[] = none ) // false then P.elm[]->elm[]; INNER ; P.succ[]->P[]; (if not q then restart search if) if) #); Display:< (# s: ^stream enter s[] do s.newline; '############ Register Display '->s.putText; INNER ; s.newline; scan (# do s[]->elm.display #); '############ End Register Display #######'->s.putLine #); Has: (# E: ^record; found: @boolean; enter E[] do false->found; search: scan (# do (if E.key // elm.key then true->found; leave search if) #) exit found #); Insert: (# E: ^record; P: ^regLst enter E[] do (if (E[]->Has) // false then ®Lst[]->P[]; head[]->P.succ[]; E[]->P.elm[]; P[]->head[]
68
if) #); Find: (# e1: ^record; k: @integer; enter k do scan (# do (if k // elm.key then elm[]->e1[]; true->q if) #) exit e1[] #) #); zn: (# do (if keyboard.peek = ascii.newline then keyboard.get if) #) Příklad 26: ORIGIN '~beta/basiclib/betaenv'; INCLUDE './reclib1'; -- PROGRAM: Descriptor -(# Greg: @Register; rc1: ^Record; E: @Employee; B: @Book; P: @Person; S: @Student do Greg.init; cLoop: cycle (# do newline; 'Display[d], add[a], find[f], quit[q] ? '->puttext; (if getNonBlank // 'd' // 'D' then screen[]->Greg.display // 'a' // 'A' then 'Student[s], employee[e], person[p], book[b], record[r] ? '->puttext; (if getNonBlank // 's' // 'S' then S.insert->Greg.insert // 'e' // 'E' then E.insert->Greg.insert // 'b' // 'B' then B.insert->Greg.insert // 'p' // 'P' then P.insert->Greg.insert if) // 'f' // 'F' then '\nZadejte klic pro vyhledani: '->puttext; getint->Greg.find->rc1[]; (if rc1[] = none then '\nZaznam nenalezen\n'->puttext else '\nNalezeny zaznam: \n'->puttext; screen[]->rc1.display if) // 'q' // 'Q' then leave cLoop if) #) #)
69
Virtuální vzor třídy, znovupoužitelnost.
Virtuální vzory jsou používány k popisu společné struktury atributů vzoru v nadvzoru. V BETĚ není technický rozdíl mezi vzorem procedury (deklarací metody) a vzorem třídy (deklarací třídy), je to jednoduše záležitostí jak jsou vytvářeny instance. Pomocí virtuálních vzorů tříd můžeme deklarovat seznamy s abstraktním prvkem seznamu. V dalších podtřídách (podvzorech) pak můžeme dále specifikovat prvek seznamu. Uvedené vlastnosti se vyžívá u znovupoužitelnosti. Třídy deklarující seznamy s abstraktním prvkem jsou uloženy v knihovnách a násobně využívány.
K čemu se používá abstraktní třída Object? Co je to generický seznam? Co je to znovupoužitelnost a jak ji realizujeme?
Jaké je použití virtuálního vzoru třídy?
Jak by bylo třeba změnit knihovnu btcv10\reclib1 a eventuálně hlavní program rec1.bet, když bychom místo vlastního dynamického seznamu použili knihovnu list?
Vyřešený příklad je uveden v btcv10\reclist.bet a btcv10\reclistlib.bet.
70
Madsen O., Moller-Pedersen, Nygaard K.: Object oriented programming in the BETA language. Addison Weslay 1993. web: www.mjolner.dk Doplňující materiály na CD – MIA 94-24 Polák J.: Objektově orientované programováni, skripta ČVUT Praha 1992 Polák J., Merunka V.: Objektově orientované pojmy. série článků v Softwarových novinách 1993
1. Vytvořte třídu bod a atributy x,y,z typu integer a doplňte metody: posun, která má na vstupu tři hodnoty, o které se mají jednotlivé souřadnice posunout, metodu tisk, která tiskne názvy atributů a jeho hodnotu a metodu rozdíl. Metoda rozdíl má na vstupu referenci na vstupní bod a vytváří nový bod, jehož souřadnice jsou dány rozdílem vstupního bodu a stávajícího bodu. Výsledný bod vrací jako referenci. 2. Prostudujte podrobně příklad btcv2\obdelnik a vytvořte (s využitím skládání-použito v příkladě obdélníka) třídu bod s atributy x,y a metodami posun a tisk. Dále vytvořte třídu kruh, která má atributy střed (bod) a poloměr (typu integer) s metodami posun a tisk. 3. Vytvořte následující hierarchii tříd: Osoba (atributy jméno, věk), Student (atributy obor), Zaměstnanec (atributy plat, pozice). Atributy věk a plat jsou celočíselné, ostatní atributy jsou textové. U každé třídy vytvořte virtuální metodu tisk, která tiskne odpovídající atributy. K programu přičleňte (include) generickou knihovnu fronty (frontalib), nebo zásobníku (zasobniklib). Ve Vašem programu pak vytvořte instanci od vybrané struktury a specifikujte prvek struktury (Osoba). Vytvořte instance od každé třídy hierarchie tříd, naplňte je hodnotami a uložte do datové struktury. Vytiskněte obsah datové struktury. Datové struktury jsou na adresách: Příklady: zasobniklib.bet, nebo frontalib.bet. Pomůcka viz příklad virtreg.bet a knihovna virtreglib.bet. zasobniklib.bet ORIGIN '~beta/basiclib/betaenv'; -- lib: Attributes -Zasobnik: (# obsah:< object; A: [20] ^obsah; Top: @integer; push: (# e: ^obsah enter e[] do (if Top < A.range then Top+1->Top; e[]->A[top][]
71
else '\nZasobnik je plny'->puttext; newline if) #); pop: (# e: ^obsah do (if Top > 0 then A[top][]->e[]; top-1->top else 'Zasobnik je prazdny'->puttext; newline if) exit e[] #); Init: (# do 0->Top #); isEmpty: (# res: @boolean do (0 = Top)->res exit res #); size: (# exit top #); peek: (# e: ^obsah do A[top][]->e[] exit e #) #) frontalib.bet ORIGIN '~beta/basiclib/betaenv'; -- lib: Attributes -fronta: (# obsah:< object; a: [5] ^obsah; pocet,in,out: @integer; init: (# do 1->in; a.range->out; 0->pocet #); zapis: (# prvek: ^obsah enter prvek[] do (if pocet >= a.range then '\nFronta je plna - zapis neproveden'->puttext else prvek[]->a[in][]; pocet+1->pocet; (in mod a.range)+1->in if) #); cteni: (# prvek: ^obsah do (if pocet <= 0 then '\nFronta je prazdna - cteni neprovedeno'->puttext else pocet-1->pocet; (out mod a.range)+1->out; a[out][]->prvek[] if) exit prvek[] #); velikost: (# exit pocet #); prazdna: (# exit (pocet <= 0) #); plna: (# exit (pocet >= a.range) #); kapacita: (# exit a.range #) #)
72
Znovupoužitelnost je další charakteristika objektově orientovaného přístupu. Právě využitím virtuálního mechanismu tříd docílíme toho, že jsme schopni vytvořit generické seznamy, které můžeme využít vícekrát.
73