Obiekty dynamiczne

Transkrypt

Obiekty dynamiczne
Podstawy Programowania – semestr drugi
Wykład dwudziesty pierwszy
1.
Obiekty dynamiczne
Podobnie jak zmienne innych typów obiekty mogą być tworzone w sposób dynamiczny. Ponieważ korzystając z tego typu obiektów musimy posługiwać się wskaźnikami,
więc takie rozwiązanie ułatwia stosowanie metod polimorficznych. W tworzeniu i usuwaniu obiektów dynamicznych ważną rolę odgrywają znane już nam konstruktory
1
oraz metody nazywane destruktorami. Konstruktory podobnie jak w przypadku obiektów statycznych wiążą obiekt z tablicą metod wirtualnych klasy tego obiektu oraz
mogą dokonywać inicjalizacji pól nowo utworzonego obiektu. Zmienia się jednak sposób wywołania tej metody. Obiekt dynamiczny jest tworzony na stercie przy pomocy
2
procedury new . Pierwszym parametrem tej procedury jest wskaźnik na tworzony obiekt, a drugim konstruktor:
new(wskaznikNaObiekt, konstruktor);
Tak wywołana procedura new zachowuje się tak jak procedura getmem. Można również stworzyć obiekt stosując „tradycyjne” wywołanie new i dopiero po jego
utworzeniu wywołać dla niego konstruktor. W ciele konstruktora można umieścić wywołanie procedury fail. Ta procedura powinna być wywoływana wówczas, gdy po
3
stworzeniu obiektu dynamicznego nie powiedzie się inicjalizacja jego pól . W takiej sytuacji posługiwanie się tym obiektem byłoby niebezpieczne i należy go usunąć.
Czyni to właśnie procedura fail, dodatkowo powodując zakończenie działania konstruktora. Jeśli konstruktor jest wywoływany tak jak inne metody, to fail spowoduje,
że będzie on działał jak funkcja, która zwróci wartość true jeśli poprawnie się wykona lub false w przeciwnym wypadku. Podobnie jak w przypadku obiektów
statycznych konstruktor nie może być wirtualny.
Destruktor jest metodą, która wywoływana jest zawsze podczas zwalniania pamięci, w której znajduje się obiekt. Zazwyczaj metoda ta nie jest wywoływana wprost, lecz
jest przekazywana jako drugi parametr procedury dispose, która działa wówczas jak procedura freemem. Zadaniem destruktora przy takim sposobie jego wywołania jest
przekazanie procedurze dispose rozmiaru zwalnianego obiektu, który pobiera ze związanej z obiektem tablicy metod wirtualnych. Jeśli zwalniany obiekt nie zawiera
żadnych pól wskaźnikowych, to destruktor może być metodą pustą. W przeciwnym przypadku ta metoda powinna zawierać instrukcje, które zwalniają pamięć
przydzieloną na zmienne dynamiczne, na które wskazują pola wskaźnikowe obiektu. Dodatkowo, jeśli pola te są obecne w obiektach klasy pochodnej, a nie są w
obiektach klasy bazowej, to destruktor klasy pochodnej musi być destruktorem wirtualnym, gdyż tylko w takim wypadku mamy pewność, że zostanie wywołana
właściwa metoda usuwająca obiekt. Jeśli natomiast obiekt klasy bazowej zawierał pola wskaźnikowe, to jego wywołanie powinno znaleźć się jako ostatnia instrukcja
wykonywana w destruktorze klasy pochodnej. Destruktor może być również wywołany tak jak pozostałe metody.
Dla obiektów dynamicznych, podobnie jak dla obiektów statycznych, działa mechanizm rzutowania w górę, tzn. wskaźnikiem klasy bazowej możemy wskazywać
zarówno obiekt tej klasy bazowej, jak i obiekty dowolnych klas dziedziczących po tej klasie. Jedynym odstępstwem od tej reguły jest przypadek, kiedy wskaźnik na
obiekt przekazujemy do funkcji lub procedury za pomocą parametru i jest to przekazanie przez zmienną. (Zupełnie inaczej wygląda sytuacja, kiedy przekazywany jest
sam obiekt, o czym była mowa na poprzednim wykładzie.) Wówczas typy parametru formalnego i faktycznego musi się zgadzać, w przeciwnym przypadku kompilator
zgłosi błąd. Takiej sytuacji możemy uniknąć dokonując rzutowania na właściwy typ w momencie podstawiania parametru.
Oto przykład programu, który korzysta z obiektów dynamicznych:
Program rysuje na ekranie w trybie graficznym trzy figury
geometryczne: okrąg, kwadrat i trójkąt, w pewnych
odstępach czasu, a następnie wymazuje te figury, również w
pewnych odstępach czasu i wypisuje na ekran słowo
„KONIEC”. W głównym programie jest tworzony obiekt
1 program Figury;
2 uses graph,zarzadca;
3 var
„zarządzający” wyświetlaniem figur na ekranie. Klasa tego
obiektu została zdefiniowana w module „zarzadca”
(zarządca). Zawiera ona tablicę, której elementami są
wskaźniki typu PShape i cztery metody: prywatną rysuj,
4 gd,gm:integer;
5 za:Zarzad;
6 mem:longint;
która posiada jeden parametr typu PShape, motodę inicuj,
która tworzy obiekty klas Cir (okrąg), Square (kwadrat) oraz
7 begin
Triangle (trójkąt) i zapisuje ich adresy w tablicy Zbior.
Wszystkie wymienione klasy dziedziczą po klasie Shape, a
8 gd:=detect;
PShape jest wskaźnikiem tej klasy. Wyżej wymienione
9 initgraph(gd,gm,'c:\tp\bgi');
obiekty tworzone są za pomocą procedury new, którą
wywołano w opisany wcześniej sposób (wiersze 30, 31, 32 w
kodzie modułu zarzadca). Metoda prywatna rysuj wywołuje
metodę draw dla każdego obiektu, którego adres zostanie jej
10 if graphresult<>grok then exit;
11 mem:=memavail;
przekazany przez parametr. Metoda draw jest, jak się
12 za.inicjuj;
później przekonamy, metodą wirtualną. Metoda publiczna
narysuj wywołuje metodę rysuj dla każdego elementu tablicy
13 za.narysuj;
zbior. Metoda usun usuwa wszystkie obiekt, których adresy
14 za.usun;
są przechowywane w w/w tablicy, wywołując dla nich
procedurę dispose, w sposób, który został opisany wcześniej
15 setcolor(green);
(wiersz 44 w kodzie modułu zarzadca). Moduł Figura
zawiera definicję klasy Shape. Zawiera ona trzy metody:
16 outtextxy(320,240,'KONIEC');
konstruktor inicjuj,
i
destruktor erase.
18 readln;
Oprócz konstruktora wszystkie pozostałe metody są
wirtualne. Dzięki temu można będzie wywołać metodę draw,
właściwą dla danego obiektu, która będzie wiedziała w jaki
sposób go narysować oraz destruktor erase właściwy dla
20 end.
2
3
draw
Wszystkie te metody są abstrakcyjne, a więc również klasa
Shape jest abstrakcyjna i nie należy tworzyć jej obiektów.
19 closegraph;
1
metodę
17 if mem-memavail<>0 then outtextxy(0,0,'Błąd zarządzania pamięcią!');
W klasie obiektu można stworzyć kilka osobnych konstruktorów, które mogą inicjalizować obiekty w różny sposób. Należy pamiętać o tym, aby te metody miały różne
nazwy. W Object Pascalu dla środowiska Turbo Pascal tylko metody dziedziczone mogą być przeciążane (to oznacza, że nie można mieć w obrębie jednej klasy dwóch
metod o takich samych nazwach).
Która może również być wywołana jako funkcja.
To może mieć miejsce w przypadku inicjalizacji pól obiektu, które są wskaźnikami i najczęstszą przyczyną takiej sytuacji jest brak wolnego miejsca w pamięci
operacyjnej.
1
Podstawy Programowania – semestr drugi
danego obiektu, który będzie wiedział jak go wymazać z
ekranu. Warto zwrócić uwagę na to, jak deklaruje się i
definiuje destruktor. Używamy do tego celu słowa
kluczowego destructor (wiersze 8 i 23 omawianego
modułu). W modułach Kwadrat, Okrag (Okrąg)
i
1 unit zarzadca;
2
3 interface
Trojkat
4
(Trójkąt)
zdefiniowane
są
klasy
Square
(kwadrat), Cir (okrąg) i Triangle (trójkąt), dziedziczące
5 uses Figura,Okrag,Kwadrat,Trojkat,crt;
po klasie Shape. Każda z tych klas przykrywa metody
6
rodzaje figur. Definicje tych klas pokazują jakie może
być potencjalne zastosowanie destruktorów. Całość
programu ilustruje również w jaki sposób można
połączyć ze sobą kompozycję, dziedziczenie i
polimorfizm.
draw i erase tak aby, rysowały i wymazywały określone
7 type
8
9
PShape = ^Shape;
10
ZbiorFigur = array [1..3] of PShape;
11
Zarzad=object
12
13
private
14
zbior:ZbiorFigur;
15
procedure rysuj(f:PShape);
16
public
17
procedure inicjuj;
18
procedure narysuj;
19
procedure usun;
20
end;
21
22 implementation
23
24 procedure Zarzad.inicjuj;
25
var
26
o:^Cir;
27
k:^Square;
28
29
t:^Triangle;
begin
30
new(o,init);
31
new(k,init);
32
new(t,init);
33
zbior[1]:=o;
34
zbior[2]:=k;
35
zbior[3]:=t;
36
end;
37
38
procedure Zarzad.usun;
39
var
40
41
i:byte;
begin
42
for i:=low(zbior) to high(zbior) do
43
begin
44
dispose(zbior[i],erase);
45
delay(1000);
2
Podstawy Programowania – semestr drugi
46
47
end;
end;
48
49
procedure Zarzad.narysuj;
50
var
51
52
i:byte;
begin
53
for i:=low(zbior) to high(zbior) do
54
begin
55
rysuj(zbior[i]);
56
delay(1000);
57
58
end;
end;
59
60
procedure Zarzad.rysuj(f:PShape);
61
begin
62
63
f^.draw;
end;
64
65 end.
1 unit Figura;
2 interface
3 uses objects;
4 type
5
Shape = object
6
constructor init;
7
procedure draw; virtual;
8
destructor erase; virtual;
9
end;
10
11 implementation
12
13
constructor Shape.init;
14
begin
15
16
abstract;
end;
17
18
procedure Shape.draw;
19
begin
20
21
abstract;
end;
22
23
destructor Shape.erase;
24
begin
25
abstract;
3
Podstawy Programowania – semestr drugi
26
end;
27 end.
1 unit Kwadrat;
2
3 interface
4
5 uses figura,graph;
6
7 type
8
Square = object(Shape)
9
constructor init;
10
procedure draw; virtual;
11
destructor erase; virtual;
12
end;
13
14 implementation
15
16
constructor Square.init;
17
begin
18
setcolor(green);
19
end;
20
21
procedure Square.draw;
22
begin
23
rectangle(400,300,440,340);
24
end;
25
26
destructor Square.erase;
27
begin
28
setcolor(black);
29
draw;
30
end;
31
32 end.
1 unit Okrag;
2
3 interface
4
5 uses figura,graph;
6
7 type
8
9
10
Cir = object(Shape)
constructor init;
procedure draw; virtual;
4
Podstawy Programowania – semestr drugi
11
12
destructor erase; virtual;
end;
13
14 implementation
15
16 constructor Cir.init;
17 begin
18
setcolor(green);
19 end;
20
21 procedure Cir.draw;
22 begin
23
circle(320,240,50);
24 end;
25
26 destructor Cir.erase;
27 begin
28
setcolor(black);
29
draw;
30 end;
31
32 end.
1 unit Trojkat;
2 interface
3 uses graph,figura;
4 type
5
6
7
Triangle = object(Shape)
private
8
coordinates:array[1..4] of PointType;
9
number:integer;
10
public
11
constructor init;
12
procedure draw; virtual;
13
destructor erase; virtual;
14
end;
15
16 implementation
17
18
constructor Triangle.init;
19
begin
20
number:=4;
21
coordinates[1].x := 50;
22
coordinates[1].y := 100;
23
coordinates[2].x := 100;
5
Podstawy Programowania – semestr drugi
24
coordinates[2].y := 100;
25
coordinates[3].x := 150;
26
coordinates[3].y := 150;
27
coordinates[4].x := 50;
28
coordinates[4].y := 100;
29
setcolor(green);
30
end;
31
32
procedure Triangle.draw;
33
begin
34
drawpoly(number,coordinates);
35
end;
36
37
destructor Triangle.erase;
38
begin
39
setcolor(black);
40
draw;
41
end;
42
43 end.
Na poprzednich wykładach poruszany był problem rzutowania w dół. Ten typ rzutowania jest niezbędny, jeśli wskazujemy na obiekt klasy pochodnej wskaźnikiem
klasy bazowej i chcemy dla niego wywołać metodę, która została zdefiniowana w jego klasie. W pewnych sytuacjach możemy bezpośrednio zastosować rzutowanie w dół,
jednakże dosyć często nie możemy stwierdzić od razu, czy to na co wskazujemy jest rzeczywiście obiektem klasy na którą chcemy rzutować. Jeśli się pomylimy, może to
spowodować awaryjne zakończenie programu. Aby temu zapobiec musimy mieć jakiś mechanizm rozpoznawania typu obiektu w czasie wykonania programu (ang. runtime type identification). W Pascalu taką rolę pełni funkcja TypeOf. Oto program ilustrujący sposób jej użycia:
1 program SklepObiektowy;
Interesujący nas fragment kodu znajduje się w module Skl.
2 uses skl,crt;
Instrukcja warunkowa umieszczona w wierszu 59 sprawdza,
czy obiekt na który wskazuje wskaźnik magazyn[i] jest
3 var s:Sklep;
obiektem klasy Ksiazka (Książka). Jeśli tak, to w wierszu 61
4 begin
następuje rzutowanie na tę klasę wskaźnika i wywołanie
metody ustawWydanie, zdefiniowanej w klasie Ksiazka. W
5 s.inicjuj;
przeciwnym wypadku przyjmuje się, że obiekt jest obiektem
klasy Czasopismo (innej możliwości nie ma), dokonuje się
rzutowania na tę klasę i wywołuje się metodę ustawNaklad
6 s.drukuj;
7 s.zmien(4);
(ustaw Nakład), która została zdefiniowana w tej klasie.
Należy zwrócić uwagę, że aby wykonać rzutowanie obiektu
na jego właściwą klasę, najpierw należy przepisać jego adres
do wskaźnika typu pointer, a potem dopiero wykonać
8 s.drukuj;
9 s.usun;
rzutowanie.
10 end.
1 unit Skl;
2 interface
3 uses lit,crt;
4 type
5
6
7
8
Sklep = object
private
magazyn : array [1..4] of PLiteratura;
public
9
procedure inicjuj;
10
procedure zmien(i:byte);
11
procedure drukuj;
6
Podstawy Programowania – semestr drugi
12
13
procedure usun;
end;
14
15 implementation
16
17
procedure Sklep.inicjuj;
18
var
19
ks:PKsiazka;
20
cz:PCzasopismo;
21
begin
22
randomize;
23
new(ks,inicjuj('Przeminęło z wiatrem'));
24
magazyn[1]:=ks;
25
new(ks,inicjuj('Ogniem i mieczem'));
26
magazyn[2]:=ks;
27
new(cz,inicjuj('Polityka'));
28
magazyn[3]:=cz;
29
new(cz,inicjuj('Wprost'));
30
magazyn[4]:=cz;
31
end;
32
33
procedure Sklep.drukuj;
34
var
35
36
i:byte;
begin
37
clrscr;
38
for i:=1 to 4 do magazyn[i]^.drukuj;
39
40
readln;
end;
41
42
procedure Sklep.zmien(i:byte);
43
var
44
naklad:integer;
45
cena:real;
46
wydanie:byte;
47
tytul:string;
48
p:pointer;
49
begin
50
if i>4 then exit;
51
write('Podaj tytuł: ');
52
readln(tytul);
53
magazyn[i]^.ustawTytul(tytul);
54
write('Podaj cenę: ');
55
readln(cena);
56
magazyn[i]^.ustawCene(cena);
7
Podstawy Programowania – semestr drugi
57
if TypeOf(magazyn[i]^)=TypeOf(Ksiazka) then begin
58
p:=magazyn[i];
59
write('Podaj wydanie: ');
60
readln(wydanie);
61
Ksiazka(p^).ustawWydanie(wydanie);
62
end
63
else begin
64
write('Podaj nakład: ');
65
readln(naklad);
66
p:=magazyn[i];
67
Czasopismo(p^).ustawNaklad(naklad);
68
end;
69
end;
70
71
procedure Sklep.usun;
72
var i:byte;
73
begin
74
for i:=1 to 4 do dispose(magazyn[i],usun);
75
end;
76 end.
1 unit lit;
2 interface
3
4 type
5
PLiteratura = ^Literatura;
6
Literatura = object
7
private
8
tytul:string;
9
cena:real;
10
public
11
constructor inicjuj(const tyt:string);
12
procedure ustawCene(c:real);
13
function podajCene:real;
14
procedure ustawTytul(const t:string);
15
function podajTytul:string;
16
procedure drukuj; virtual;
17
destructor usun;
18
end;
19
20
PKsiazka = ^Ksiazka;
21
Ksiazka = object(Literatura)
22
23
24
25
private
wydanie:byte;
public
constructor inicjuj(const tyt:string);
8
Podstawy Programowania – semestr drugi
26
procedure ustawWydanie(nw:byte);
27
function podajWydanie:byte;
27
function podajWydanie:byte;
28
procedure drukuj; virtual;
29
end;
30
31
PCzasopismo = ^Czasopismo;
32
Czasopismo = object(Literatura)
33
private
34
naklad:integer;
35
public
36
constructor inicjuj(const tyt:string);
37
procedure ustawNaklad(n:longint);
38
function podajNaklad:longint;
39
procedure drukuj; virtual;
40
end;
41
42 implementation
43
44
procedure Literatura.ustawCene(c:real);
45
begin
46
47
cena:=c;
end;
48
49
function Literatura.podajCene:real;
50
begin
51
52
podajCene:=cena;
end;
53
54
procedure Literatura.ustawTytul(const t:string);
55
begin
56
57
tytul:=t;
end;
58
59
function Literatura.podajTytul:string;
60
begin
61
62
podajTytul:=tytul;
end;
63
64
procedure Literatura.drukuj;
65
begin
66
writeln('Tytuł: ', podajTytul);
67
writeln('Cena: ', podajCene:5:2);
68
end;
69
9
Podstawy Programowania – semestr drugi
70
constructor Literatura.inicjuj(const tyt:string);
71
begin
72
73
74
ustawTytul(tyt);
ustawCene(random(100)+random);
end;
75
76
destructor Literatura.usun;
77
begin
78
end;
79
80
procedure Ksiazka.ustawWydanie(nw:byte);
81
begin
82
83
wydanie:=nw;
end;
84
85
function Ksiazka.podajWydanie:byte;
86
begin
87
88
podajWydanie:=wydanie;
end;
89
90
constructor Ksiazka.inicjuj(const tyt:string);
91
begin
92
inherited inicjuj(tyt);
93
ustawWydanie(random(10)+1);
94
end;
95
96
procedure Ksiazka.drukuj;
97
begin
98
inherited drukuj;
99
writeln('Wydanie: ', podajWydanie);
100
101
writeln;
end;
102
103
procedure Czasopismo.ustawNaklad(n:longint);
104
begin
105
106
naklad:=n;
end;
107
108
function Czasopismo.podajNaklad:longint;
109
begin
110
111
podajNaklad:=naklad;
end;
112
113
constructor Czasopismo.inicjuj(const tyt:string);
114
begin
10
Podstawy Programowania – semestr drugi
115
inherited inicjuj(tyt);
116
ustawNaklad(random(10000));
117
end;
118
119
procedure Czasopismo.drukuj;
120
begin
121
inherited drukuj;
122
writeln('Nakład: ', podajNaklad);
123
writeln;
124
end;
125 end.
11