Professional Documents
Culture Documents
PRZYKADOWY ROZDZIA
SPIS TRECI
KATALOG KSIEK
KATALOG ONLINE
Delphi 2005
Autor: Elmar Warken
Tumaczenie: Wojciech Moch
ISBN: 83-7361-993-3
Tytu oryginau: Delphi 2005
Format: B5, stron: 810
TWJ KOSZYK
DODAJ DO KOSZYKA
CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK
CZYTELNIA
FRAGMENTY KSIEK ONLINE
Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl
Spis treci
Przedmowa ....................................................................................... 9
Rozdzia 1. Praca w IDE ................................................................................... 15
1.1. Konstrukcja komponentw ...................................................................................... 15
1.1.1. Elementy sterujce, narzdzia i komponenty ................................................ 16
1.1.2. Formularze i okna ......................................................................................... 18
1.2. Orientacja na zdarzenia ........................................................................................... 19
1.2.1. Zdarzenie na kad okazj ............................................................................ 19
1.2.2. Zdarzenia w Delphi ....................................................................................... 21
1.3. Cykl rozwoju aplikacji ............................................................................................ 23
1.3.1. Cykl rozwoju aplikacji w IDE Delphi ........................................................... 23
1.3.2. Program przykadowy ................................................................................... 24
1.4. IDE i narzdzia wizualne ......................................................................................... 25
1.4.1. Budowa IDE ................................................................................................. 25
1.4.2. Pomoc w IDE i opis jzyka ........................................................................... 29
1.4.3. Projektowanie formularzy ............................................................................. 32
1.4.4. Zarzdzanie plikami ...................................................................................... 36
1.4.5. Inspektor obiektw ........................................................................................ 37
1.5. czenie komponentw z kodem ............................................................................ 45
1.5.1. Wprowadzenie do obsugi zdarze ............................................................... 46
1.5.2. Podstawowe moliwoci procedur obsugi zdarze ...................................... 49
1.5.3. Przegld moduu formularza ......................................................................... 51
1.5.4. Zdarzenia w programie przykadowym ......................................................... 54
1.5.5. Pomoc w edytorze ......................................................................................... 64
1.5.6. czenie zdarze nawigacja, zmiany, usuwanie ...................................... 72
1.5.7. Spojrzenie za kulisy ...................................................................................... 74
1.6. Zarzdzanie projektem ............................................................................................ 78
1.6.1. Pliki projektu ................................................................................................. 78
1.6.2. Zarzdzanie projektem .................................................................................. 81
1.6.3. Przegldarka symboli w projektach i kompilatach .NET .............................. 84
1.6.4. Listy rzeczy do zrobienia ........................................................................... 89
1.7. Debuger ................................................................................................................... 91
1.7.1. Punkty wstrzymania ...................................................................................... 92
1.7.2. Kontrolowanie zmiennych ............................................................................ 95
1.7.3. Wykonywanie kodu ...................................................................................... 98
1.7.4. Oglne okna debugera .................................................................................. 99
Delphi 2005
Spis treci
5
3.2.7. Uprzedzajce deklaracje klas ...................................................................... 312
3.2.8. Zagniedone deklaracje typw .................................................................. 313
3.3. Obiekty w czasie dziaania programu .................................................................... 314
3.3.1. Inicjalizacja obiektw: konstruktory ........................................................... 314
3.3.2. Zwalnianie zasobw i czyszczenie pamici ................................................ 316
3.3.3. Metody wirtualne ........................................................................................ 324
3.3.4. Konwersja typw i informacje o typach ...................................................... 329
3.3.5. Konstruktory wirtualne ............................................................................... 333
3.4. Typy interfejsw .................................................................................................... 335
3.4.1. Czym jest interfejs? ..................................................................................... 335
3.4.2. Implementowanie interfejsu ........................................................................ 339
3.5. Podstawy jzyka Object Pascal ............................................................................. 344
3.5.1. Elementy leksykalne ................................................................................... 345
3.5.2. Instrukcje kompilatora ................................................................................ 347
3.5.3. Typy i zmienne ........................................................................................... 350
3.5.4. Stae i zmienne inicjowane .......................................................................... 351
3.5.5. Obszary widocznoci i zmienne lokalne ..................................................... 353
3.5.6. Atrybuty ...................................................................................................... 355
3.6. Typy ...................................................................................................................... 356
3.6.1. Typy proste ................................................................................................. 356
3.6.2. Operatory i wyraenia ................................................................................. 364
3.6.3. Tablice ........................................................................................................ 367
3.6.4. Rne typy cigw znakw ........................................................................ 370
3.6.5. Typy strukturalne ........................................................................................ 375
3.6.6. Kategorie typw w CLR ............................................................................. 376
3.7. Instrukcje ............................................................................................................... 379
3.8. Procedury i funkcje ............................................................................................... 384
3.8.1. Typy parametrw ........................................................................................ 385
3.8.2. Przecianie metod i parametry standardowe ............................................. 389
3.8.3. Wskaniki metod ........................................................................................ 391
3.9. Wyjtki .................................................................................................................. 392
3.9.1. Wywoywanie wyjtkw ............................................................................. 392
3.9.2. Klasy wyjtkw .......................................................................................... 393
3.9.3. Zabezpieczanie kodu z wykorzystaniem sekcji finally ............................... 394
3.9.4. Obsuga wyjtkw ...................................................................................... 395
3.9.5. Asercja ........................................................................................................ 399
Delphi 2005
4.3.6. Kontrolki TListBox, TListView i TTreeView ............................................ 438
4.3.7. Listy, kolekcje i strumienie ......................................................................... 441
4.3.8. Grafika ........................................................................................................ 445
4.3.9. Mechanizm przecignij-i-upu .................................................................. 452
4.3.10. Wtki ......................................................................................................... 456
4.4. Techniki ponownego wykorzystania formularzy ................................................... 459
4.4.1. Repozytorium obiektw .............................................................................. 460
4.4.2. Dziedziczenie formularzy ........................................................................... 463
4.4.3. Ramki .......................................................................................................... 467
4.5. Przykadowa aplikacja VCL .................................................................................. 471
4.5.1. O programie TreeDesigner .......................................................................... 472
4.5.2. Krtki opis i obsuga programu ................................................................... 474
4.6. Komponenty akcji ................................................................................................. 479
4.6.1. Listy polece z komponentu TActionList ................................................... 480
4.6.2. Akcje standardowe ...................................................................................... 483
4.6.3. Komponenty menedera akcji ..................................................................... 484
4.6.4. Komponent TControlBar ............................................................................ 490
4.6.5. Przykadowy interfejs uytkownika ............................................................ 493
4.7. Przenoszenie aplikacji VCL .................................................................................. 495
4.7.1. Przygotowania ............................................................................................. 496
4.7.2. Dopasowywanie aplikacji do rodowiska .NET .......................................... 499
4.7.3. Wywoania funkcji API i transpozycja danych ........................................... 503
4.7.4. Zmiany w interfejsie biblioteki VCL .......................................................... 509
4.7.5. Operacje na strumieniach ............................................................................ 511
4.8. Aplikacje VCL.NET i rodowisko Win32 ............................................................. 519
4.9. Biblioteki VCL.NET i FCL w ramach jednej aplikacji ......................................... 524
4.9.1. czenie bibliotek FCL i VCL na poziomie klas ........................................ 524
4.9.2. czenie bibliotek FCL i VCL na poziomie formularzy ............................. 528
4.9.3. czenie bibliotek FCL i VCL na poziomie komponentw ........................ 535
Spis treci
7
5.4.4. Podstawowe operacje na danych ................................................................. 642
5.4.5. Kolumny tabeli, czyli pola .......................................................................... 648
5.4.6. Pola trwae i edytor pl ............................................................................... 650
5.4.7. Dane z aktualnego wiersza .......................................................................... 652
5.4.8. Sortowanie, szukanie i filtrowanie .............................................................. 655
5.4.9. Przykadowa aplikacja terminarza .............................................................. 659
Rozdzia 3.
Jzyk Delphi
w rodowisku .NET
W tym rozdziale zajmowa si bdziemy jzykiem programowania Delphi znanym
te pod nazw Object Pascal i jego przeksztacaniem w klasy i kompilaty rodowiska
.NET. Rozdzia ten nie ma by dokumentacj tego jzyka (taka dokumentacja stanowi
cz systemu aktywnej pomocy Delphi; znale j mona w gazi Borland Help/
Delphi 2005 (Common)/Reference/Delphi Language Guide) i w zwizku z tym nie
bd tu opisywa wszystkich jego szczegw. W rozdziale tym bd si stara przedstawi jak najpeniejsze wprowadzenie do jzyka, skierowane do osb przesiadajcych
si z innych narzdzi programistycznych lub z wczeniejszych wersji Delphi. Poza tym
omawia bd powizania istniejce pomidzy Delphi i CLR (ang. Common Language
Runtime wsplne rodowisko uruchomieniowe dla wszystkich aplikacji .NET) oraz
wyjania wszystkie waciwoci jzyka, jakie bd wykorzystywane w ksice (rozdzia ten opisywa bdzie te ograniczenia jzyka w tym zakresie).
Na pocztku rozdziau nie bdziemy zajmowa si drobnymi elementami jzyka, takimi jak typy danych, zmienne i instrukcje, ale od razu przejdziemy do wikszych zagadnie, takich jak kompilaty (podrozdzia 3.1) i model obiektw (podrozdziay 3.2
do 3.4). Od podrozdziau 3.5 przejdziemy do szczegw jzyka, czyli zmiennych,
staych, typw, instrukcji, deklaracji metod i wyjtkw.
Wprowadzenie
Jzyk Object Pascal lub Delphi, jak ostatnio nazywa go firma Borland, jest bezporednim
nastpc jzyka Borland Pascal with Objects, ktry zosta wprowadzony w roku 1989
w pakiecie Turbo Pascal 5.5 (rodowisko programistyczne dla systemu DOS), a krtko
potem w pakiecie Turbo Pascal dla Windows, dziaajcym rwnie w systemie Windows. Od czasu powstania pierwszej wersji Delphi w roku 1995 do jzyka tego dodawano wiele poprawek i rozszerze, ale z kad nastpn wersj jzyka liczba dodatkw cay czas si zmniejszaa, co oznacza, e jzyk ustabilizowa si na wzgldnie
wysokim poziomie.
274
Delphi 2005
punkt 3.6.2).
275
276
Delphi 2005
Kompilaty
Kompilaty (ang. Assemblies) s podstawowym budulcem aplikacji rodowiska .NET.
W wielu przypadkach kompilat jest po prostu bibliotek dynamiczn .dll albo plikiem
wykonywalnym .exe, ale koncepcja kompilatw jest o wiele szersza ni koncepcja
plikw. Jeden kompilat moe rozciga si na wiele plikw, ktre traktowane s jako
jedna cao. Pliki te, traktowane jako jeden kompilat, maj dokadnie te same numery
wersji oraz wspln stref prywatn w rodowisku .NET istnieje szczeglny
atrybut widocznoci o nazwie assembly, umoliwiajcy dostp do identyfikatora tylko
z wntrza danego kompilatu.
Co prawda podziay istniejce w kompilatach wpywaj przede wszystkim na sposb
wykonywania programu, jednak cz z nich dotyczy rwnie jego fizycznej struktury
(kompilaty adowane s jako cakowicie niezalene od siebie skadniki programu,
w zwizku z czym w czasie aktualizacji oprogramowania mog by aktualizowane
niezalenie od siebie). Istnieje te podzia wpywajcy na logiczn struktur programu,
ktry najwiksze znaczenie ma w czasie projektowania aplikacji.
Przestrzenie nazw
Przestrzenie nazw (ang. Namespaces) pozwalaj na dokonanie podziaw w cay czas
rosncych i przez to coraz trudniejszych do ogarnicia bibliotekach klas. Na przykad
klasy przeznaczone do generowania grafiki umieszczone zostay w przestrzeni nazw
System.Drawing, natomiast wszystkie kontrolki znane z aplikacji systemu Windows,
takie jak kontrolki ListBox lub Button, zapisane s w przestrzeni nazw System.Windows.
Forms. Dostpne s te inne kontrolki, przygotowane specjalnie do wykorzystania
w aplikacjach WWW, ktre rwnie nazywaj si ListBox i Button, chocia s to klasy
cakowicie niezalene od klas standardowych kontrolek. Rozrnienie pomidzy tymi
pierwszymi a tymi drugimi kontrolkami umoliwiaj wanie przestrzenie nazw: System.
Windows.Forms.ListBox to kontrolka listy wspdziaajca ze standardowymi aplikacjami
dziaajcymi bezporednio na komputerze uytkownika i na ekranie wywietlana jest
z wykorzystaniem interfejsu GDI+. Z kolei System.Web.UI.WebControls.ListBox to
kontrolka listy wsppracujca z aplikacjami WWW dziaajcymi na serwerach WWW,
a na komputer uytkownika przenoszona w postaci kodu HTML.
Rnorodno koncepcji przestrzeni nazw i kompilatw polega midzy innymi na tym,
e jedna przestrze nazw moe w sobie zawiera wiele kompilatw, a kady z kompilatw moe przechowywa w sobie wiele przestrzeni nazw. Mwic dokadniej,
kompilat nie moe w sobie tak naprawd zawiera przestrzeni nazw, poniewa moe
by ona rozbudowywana przez inne kompilaty. W zwizku z tym naleaoby powiedzie, e kompilat moe przechowywa nazwy pochodzce z wielu przestrzeni nazw,
ktre z kolei mog by rozsiane wrd wielu kompilatw.
277
Takie hierarchiczne zwizki zawierania maj jednak natur wycznie koncepcyjn i maj
suy przede wszystkim ludziom korzystajcym ze rodowiska .NET, umoliwiajc
lepsz orientacj w jego zawartoci. Istnieje na przykad konwencja mwica, e podrzdna
przestrze nazw powinna by zalena od przestrzeni nadrzdnej, ale nie odwrotnie1.
Dla rodowiska CLR zwizki zawierania przestrzeni nazw nie maj adnego znaczenia.
Zawierane przestrzenie nazw mog by zapisywane w osobnych kompilatach, niezalenych od nadrzdnej przestrzeni nazw, a takie kompilaty mog by instalowane, adowane i usuwane cakowicie niezalenie od kompilatw nadrzdnej przestrzeni nazw.
Najczciej jednak podrzdna przestrze nazw wykorzystuje elementy z przestrzeni
nadrzdnej, a odpowiednie kompilaty mog by zaadowane tylko wtedy, gdy dostpne
s te kompilaty nadrzdnej przestrzeni nazw. Ta zaleno nie jest jednak definiowana
przez nazwy przestrzeni nazw, ale przez bezporednie instrukcje nakazujce wczenie
danej przestrzeni nazw (w jzyku C# instrukcja ta nazywa si using, a w Delphi uses).
Wicej na ten temat znale mona w dokumentacji rodowiska .NET, w dokumencie Namespace
Naming Guidelines, ms-help://borland.bds3/cpgenref/html/cpconnamespacenamingguidelines.htm.
278
Delphi 2005
279
280
Delphi 2005
Korzystajc z narzdzia Reflection, mona kontrolowa przestrzenie nazw, jakie zapisywane s w kompilatach tworzonych przez Delphi. Kada z nazw tych przestrzeni
nazw rozkadana jest zgodnie z umieszczonymi w niej kropkami na czci okrelajce
hierarchiczne poziomy, a kademu poziomowi przyporzdkowany jest osobny symbol
folderu. W kompilacie naprawd znajduj si tylko te przestrzenie nazw, przy ktrych
symbolach folderw znajduj si kolejne wzy podrzdne, a przynajmniej jeden symbol
o nazwie Unit.
Na rysunku 3.1 przedstawiono projekt, do ktrego w ramach porwnania doczono
trzy podobne nazwy moduw (prosz przyjrze si zawartoci okna menedera projektu znajdujcego si w prawej dolnej czci rysunku. Przedstawiony projekt zosta
skompilowany do pliku .exe i zaadowany do programu Reflection.
Rysunek 3.1.
Moduy Delphi
wywietlane
w menederze
projektu i powizane
z nimi przestrzenie
nazw wywietlane
w programie
Reflection
281
282
Delphi 2005
Rysunek 3.3.
Wybrane zostay
dwa kompilaty
rodowiska .NET,
ktre zajmuj si
funkcjonowaniem
komponentw
w czasie
projektowania
aplikacji
283
May przykad: W kadej aplikacji tworzonej w Delphi nieodzowna jest jedna z bibliotek
czasu wykonania dostarczanych razem z Delphi (modu System), ktra znajduje si
w pliku borland.delphi.dll. Jeeli bibliotek t dodamy do naszej aplikacji tak jak pokazano na rysunku 3.4, to w efekcie uzyskamy znacznie mniejszy plik wykonywalny
aplikacji. W minimalnej aplikacji Windows-Forms, skadajcej si z jednego pustego
formularza, wielko pliku .exe spada z 21 do 7 kB, a najprostsza aplikacja konsolowa
kompilowana z wykorzystaniem konsolidacji dynamicznej zamyka si w pliku o wielkoci
5 kB. W przypadku aplikacji konsolowych korzystajcych z moduu SysUtils konieczne
jest doczenie do programu referencji biblioteki borland.vcl, co ma zablokowa wczenie tej biblioteki do pliku wykonywalnego.
Rysunek 3.4.
Dynamicznie
konsolidowane
mog by te
biblioteki czasu
wykonania
dostarczane
razem z Delphi
284
Delphi 2005
Kompilaty EXE
Jeeli projekt Delphi zapisywany jest w pliku o rozszerzeniu .dpr, a tekst rdowy
projektu rozpoczyna si od sowa program, to taki projekt moe by nazywany rwnie aplikacj, a Delphi na jego podstawie przygotowywa bdzie plik (kompilat)
wykonywalny, ktrego rozszerzenie bdzie identyczne z rozszerzeniem aplikacji stosowanych w rodowisku Win32 .exe. To rozszerzenie informuje nas te o tym, e
plik ten moe by uruchamiany dokadnie tak samo jak kady inny plik o rozszerzeniu .exe, na przykad dwukrotnym klikniciem w Eksploratorze Windows albo wywoaniem z wiersza polece. Takie dziaanie jest moliwe dlatego, e kady z takich
plikw zawiera w sobie kod maszynowy, ktry moe by uruchomiony w systemach
Win32. Jedynym zadaniem tego kodu jest sprawdzenie obecnoci w systemie rodowiska .NET i CLR, i w przypadku ich znalezienia wywoanie tego rodowiska w celu
uruchomienia waciwej aplikacji.
W IDE Delphi dostpne s wstpnie przygotowane szablony projektw przeznaczone
do budowania rnych aplikacji, takich jak aplikacje konsolowe (ang. Console Application),
aplikacje Windows-Forms (ang. Windows Forms Application) lub aplikacje VCL (ang.
VCL Forms Application; s to pozycje menu File\New\Other).
Kompilaty DLL
Kompilaty DLL tworzone s wtedy, gdy kompilowany jest pakiet (ang. Package)
Delphi. Projekt pakietu zapisywany jest w pliku rdowym o rozszerzeniu .dpk, w ktrym
najwaniejszym sowem jest sowo kluczowe package. Nowy projekt pakietu rodowiska
.NET tworzony jest w IDE Delphi wywoaniem pozycji menu File\New\Other\Delphi
for .NET Projects\Package.
Pakiety rwnie mog mie wasne referencje na inne kompilaty, ale w menederze projektw nie s one nazywane referencjami (ang. References), ale wypisywane s w wle
Required (wymagane), co dobrze wida na rysunku 3.5.
285
testowych wywoa.
286
Delphi 2005
Na rysunku 3.6 zobaczy mona, w jaki sposb przygotowana przed chwil biblioteka
dynamiczna o nazwie FormTestPackage.dll wykorzystywana jest w aplikacji tworzonej w Delphi. Przygotowany zosta nowy projekt aplikacji Windows-Forms, a biblioteka
DLL doczona zostaa do niego w menederze projektu za pomoc pozycji menu
Required/Add reference/Browse. Nastpnie modu z biblioteki zosta wprowadzony
do moduu formularza aplikacji poprzez dopisanie jego nazwy do klauzuli uses (w czasie
uzupeniania listy uses mona skorzysta z pomocy w programowaniu).
Nic wicej nie trzeba program i biblioteka DLL s od razu gotowe do uruchomienia.
Naley tu zauway, e rwnie atwo biblioteki napisane w Delphi mona wykorzystywa w programach tworzonych w jzyku C#. Kroki, jakie naley w tym celu wykona w pakiecie C#-Builder, s niemal identyczne z tymi znanymi z Delphi: utworzy
287
nowy projekt, przygotowan w Delphi bibliotek dopisa do referencji projektu, dowiza przestrze nazw (w C# zamiast sowa uses stosowane jest sowo using) i wywoa formularz. Ten ostatni krok wymaga uycia nieco zmienionej skadni, poniewa z punktu widzenia jzyka C# konstruktory nie tylko nie nazywaj si Create, ale
w ogle nie maj nazwy i wywoywane s przez operator new. Kod takiego wywoania
zapisanego w jzyku C# podaj na listingu 3.5, a wynik dziaania tego kodu zobaczy
mona na rysunku 3.7.
Listing 3.5. Procedura wywoujca formularz pobrany z biblioteki dynamicznej zapisana w jzyku C#
// Na pycie CD: Rozdzial3\Packages\CSharpDLLUser.bdsproj (projekt C#-Builder)
using FormForDll;
...
private void button1_Click(object sender, System.EventArgs e)
{
(new FormForDll.TWinForm2()).ShowDialog();
}
Rysunek 3.7. Formularz skompilowany w Delphi wywietlany jest przez aplikacj napisan w jzyku C#.
Tutaj jeszcze w starym, oddzielnym pakiecie C#-Builder, ktry teraz zintegrowany jest z Delphi 2005
288
Delphi 2005
Rnica midzy pakietami a bibliotekami polega na tym, e pliki .dll typu library
mog by te wywoywane przez aplikacje Win32 pracujce w kodzie niezarzdzanym.
W tym celu kompilator musi generowa, niebezpieczne z punktu widzenia rodowiska
.NET, punkty wejcia uywane w rodowisku Win32, a zatem musimy jawnie zezwoli na stosowanie niebezpiecznego kodu. Procedury, ktre mog by wywoywane przez aplikacje Win32, musz by dodatkowo wypisane w klauzuli exports, tak
jak pokazano na listingu 3.6.
Listing 3.6. Szkielet kodu biblioteki zawierajcej procedur dostpn te w rodowisku Win32
{$UNSAFECODE ON}
library Library1;
...
procedure F1; begin end;
exports
F1; // procedura F1 moe by wywoywana przez aplikacje Win32
end.
Bez bardzo wanych powodw nie naley stosowa dyrektywy kompilatora $UnsafeCode, poniewa przygotowany z jej uyciem kompilat nie moe na przykad zosta
zweryfikowany jako kompilat typowy przez narzdzie wiersza polece PEVerify dostpne w pakiecie .NET SDK. Wynika z tego, e normalne biblioteki dynamiczne rodowiska .NET powinny by generowane w Delphi jako zwyczajne pakiety.
Manifest kompilatw
Kady kompilat rodowiska .NET zawiera w sobie tak zwany manifest, w ktrym zapisane
s midzy innymi nastpujce dane:
kompilaty, jakich oczekuje dany kompilat (s to kompilaty, ktre w menederze
projektu w Delphi wypisywane s w wle Requires lub References). Kady
289
Podpisane kompilaty
Kompilat moe zosta zabezpieczony przed prbami dokonywania w nim pniejszych zmian i innych manipulacji. Zabezpieczenie takie polega na nadaniu mu mocnej
nazwy (ang. Strong name), skadajcej si z pary specjalnie w tym celu przygotowanych kluczy. Jeden z tych kluczy jest kluczem tajnym, przechowywanym na komputerze wytwrcy kompilatu, ktry w czasie kompilowania porednio wpywa na ksztat
danych tekstowych zapisywanych do kompilatu. Drugi klucz publiczny jest o wiele
krtszy i moe by jawnie zapisywany wewntrz kompilatu.
Za pomoc klucza publicznego CLR moe w czasie adowania kompilatu stwierdzi,
czy znajduje si on w oryginalnym stanie. Manipulacja dokonana w kompilacie bdzie
moga by zatajona przed CLR tylko wtedy, gdy podobnej manipulacji poddany zostanie
klucz tajny. Prby wprowadzenia takiej modyfikacji klucza s jednak z gry skazane
na niepowodzenie, poniewa kada aplikacja, ktra dowizuje do siebie kompilaty
290
Delphi 2005
Teraz biblioteka moe zosta ponownie skompilowana, a powstay plik .dll bdzie chroniony przed modyfikacjami przez wprowadzon do biblioteki mocn nazw. Ochron t
mona jeszcze skontrolowa, ponownie wywoujc program sn.exe (wywietlane przez
niego informacje przedstawiam na rysunku 3.8):
sn -Tp FormTestPackage.dll
Rysunek 3.8. Program sn.exe potwierdza, e klucz publiczny zosta przez Delphi dopisany
do biblioteki DLL. Wypisywany jest rwnie skrt klucza
291
W czasie przeprowadzanych przez mnie testw, z tak przygotowanej biblioteki FormTestPackage.dll skorzysta mona byo wycznie w programie tworzonym w C#Builder. Z niewiadomych powodw Delphi zapisywao do manifestu programu nieprawidowy skrt klucza, w zwizku z czym CLR odmawiao zaadowania biblioteki. Przygotowywany przez Delphi manifest mona przejrze za pomoc programu ILDasm.exe,
a wypisany przez niego skrt klucza musi by cakowicie zgodny ze skrtem podawanym przez program sn.exe (odpowiednie dane w tych programach zaznaczone zostay na rysunku 3.9).
Rysunek 3.9. W lewym grnym rogu widoczny jest skrt klucza publicznego, ktry wedug kompilatora C#
powinien znajdowa si w bibliotece. W prawym grnym rogu widoczny jest skrt klucza publicznego,
przy ktrym upiera si Delphi. Poniej wida skrt klucza publicznego rzeczywicie wpisany do biblioteki
Podpisan wersj biblioteki znale mona na pycie CD doczonej do ksiki,
w katalogu Rozdzial3\Signed Assemblies, obok projektw jzyka C# i Delphi korzystajcych z tej biblioteki. Do zainstalowania tego przykadu konieczne jest
rczne wywoanie programu sn -i przed kompilowaniem projektw.
Instalacja kompilatw
Do tej pory korzystalimy tylko z kompilatw dostarczanych przez firmy Microsoft
i Borland albo z wasnych kompilatw, ktre zapisane byy w tym samym katalogu co
plik wykonywalny naszej aplikacji. Oba te warianty przedstawiaj dwa podstawowe
sposoby instalowania bibliotek dynamicznych rodowiska .NET.
W przypadku kompilatw wykorzystywanych przez wiele rnych aplikacji waciwym miejscem instalowania jest globalna skadnica kompilatw GAC. Tutaj znajdziemy kompilaty dostarczane przez firmy Microsoft i Borland, ale take wszystkie te
292
Delphi 2005
293
Instalowanie kompilatw w GAC nie jest jednak wymagane, poniewa CLR poszukuje najpierw kompilatw wykorzystywanych przez aplikacj w katalogu, w ktrym
bya ona uruchamiana. Jeeli w projekcie Delphi dowiemy pewien kompilat poleceniem Add Reference, a kompilat ten nie jest zapisany w skadnicy GAC i nie znajduje si te w katalogu projektu, to Delphi automatycznie skopiuje go do tego katalogu.
Proces kopiowania powtarzany bdzie przy kadej kompilacji projektu, chyba e oryginalny kompilat nie zmieni si od czasu ostatniej kompilacji.
Wszystkie pi rodzajw typw mona definiowa rwnie w programach tworzonych w Delphi, wykorzystujc przy tym sowa kluczowe specyficzne dla Delphi (zostay one wymienione na powyszej licie). Kada z takich definicji typw musi by
w Delphi zapisana w sekcji pliku rdowego opisanej sowem type.
Zawarto moduw
Modu w Delphi skada si z nastpujcych elementw, ktre mog w nim wystpowa
w dowolnej kolejnoci, pod warunkiem, e nie przecz temu adne powizania czce
poszczeglne zadeklarowane elementy:
typy (sekcje pliku rdowego opisane sowem kluczowym type),
stae (sekcje const),
zmienne (sekcje var),
procedury i funkcje (nie ma opisu sekcji, ale kada procedura musi rozpoczyna
si od sowa kluczowego procedure, a kada funkcja od sowa kluczowego
function).
294
Delphi 2005
Budowa moduu
Modu Delphi zbudowany jest tak, jak pokazano na listingu 3.9.
Listing 3.9. Oglna zawarto jednego moduu Delphi
unit ModuleName; {Nazwa moduu nie moe zosta pominita}
interface
[Klauzula uses]
[Deklaracje]
implementation
[Klauzula uses]
[Deklaracje i definicje]
Gwny program moduu zakoczony sowem "end."
Modu moe zawiera w sobie wszystkie elementy jzyka Object Pascal: procedury,
funkcje, zmienne, stae, typy i w szczeglnoci klasy, a zadaniem moduu jest czciowe
lub caociowe udostpnianie tych elementw innym moduom.
Wszystkie obiekty, ktre mog by uywane take przez inne moduy, musz by zadeklarowane w czci interface. W ten sposb wszystkie te identyfikatory uznawane
s za publiczne, czyli widoczne rwnie na zewntrz. W przypadku zmiennych, staych
i typw prostych wystarczajca jest tylko deklaracja, ale w przypadku funkcji i procedur, a take metod rnych klas konieczne jest te dopisanie ich definicji w sekcji
implementation. Wszystkie pozostae deklaracje umieszczone w sekcji implementacji
traktowane s jako prywatne.
295
W przykadzie przedstawionym na listingu 3.10, kompilator mgby wpa w nieskoczon ptl analizowania moduw, jeeli nie rozpoznaby prawidowo bdu w moduach
(instrukcja uses Unit2 nakazaaby mu wczyta dane z moduu Unit2, a znajdujca si
w nim instrukcja uses Unit1 ponownie nakazaaby wczytanie danych z moduu Unit1).
Listing 3.10. Nieprawidowy sposb tworzenia cyklicznych dowiza moduw
{pocztek pliku pierwszego moduu}
unit Unit1; interface uses Unit2;
{pocztek pliku drugiego moduu}
unit Unit2; interface uses Unit1;
W takim wypadku jedna z klauzul uses musi zosta przeniesiona do czci implementacji moduu, na przykad w sposb przedstawiony na listingu 3.11.
Listing 3.11. Waciwy sposb tworzenia cyklicznych dowiza moduw
{pocztek pliku pierwszego moduu}
unit Unit1; uses Unit2;
...
{pocztek pliku drugiego moduu}
unit Unit2;
interface
implementation
uses Unit1;
Jeeli kompilator zajmowa si bdzie analizowaniem moduu Unit1, to moe w ramach tego procesu wczyta interfejs moduu Unit2 i nie natrafi tam na instrukcj odsyajc go z powrotem do moduu Unit1. W trakcie analizowania moduu Unit1 kompilator zupenie nie interesuje si czci implementacji moduu Unit2. Co prawda
interfejs moduu Unit1 jest uzaleniony od moduu Unit2, ale interfejs moduu Unit2
jest cakowicie niezaleny od moduu Unit1. Przedstawione wyej rozwizanie powoduje oczywicie pewne komplikacje, poniewa uniemoliwia stosowanie identyfikatorw z moduu Unit1 w interfejsie moduu Unit2. Oznacza to, e dwa moduy wykorzystujce si wzajemnie musz zosta zaprojektowane tak, eby w interfejsie jednego
z nich nie byy uywane klasy ani typy deklarowane w drugim module.
296
Delphi 2005
297
str := IntToStr(liczba);
// Funkcja IntToStr moe by te wywoywana jako cz moduu SysUtils,
// w takim wypadku wywoanie naley zapisa tak:
str := SysUtils.IntToStr(liczba);
ale rwnie dobrze mona potraktowa liczb jak obiekt i skorzysta z jednej z jego
metod:
str := liczba.ToString;
Pojcie klasy
Jzyk Pascal zawsze by jzykiem bardzo restrykcyjnie pilnujcym typw, w ktrym
kada zmienna musiaa mie okrelony typ, dlatego obiekty w jzyku Object Pascal
(a take w rodowisku .NET) rwnie maj swoje okrelone typy. W przypadku obiektw typ nazywany jest klas obiektu, a kady obiekt, ktrego typ okrela pewna klasa,
jest egzemplarzem lub instancj tej klasy.
Przykadem uatwiajcym rozrnianie klas i ich egzemplarzy (instancji) s formularze przygotowywane w projektancie formularzy. Tworzone w nich klasy okien w czasie
dziaania programu zamieniane s w rzeczywiste okna wywietlane na ekranie.
W tej ksice stosowa bd wyraenie egzemplarz. Rwnie czsto wystpujce
okrelenie instancja powstao najprawdopodobniej w wyniku pierwszych i nie do
koca przemylanych tumacze angielskiego sowa instance. Sowo instancja
w jzyku polskim nie jest dokadnym tumaczeniem sowa angielskiego, ale oznacza rne stopnie w hierarchii instytucji.
298
Delphi 2005
Uzupenianie klas
Po zapisaniu w deklaracji klasy samych nazw, parametrw i typw wartoci zwracanych
przez metody, w dalszej czci pliku rdowego przygotowane musz by implementacje tych metod (czyli ciaa metod z zapisanym wykonywanym w nich kodem),
cakowicie niezalenie od ich deklaracji. W jzyku Object Pascal nie jest moliwe
implementowanie metod bezporednio w deklaracjach klas, co umoliwiaj jzyki C#
i Java. Wszystkie implementacje metod musz by zapisane w czci implementacyjnej
moduu.
Najprostsz metod na uzyskanie szkieletu implementacji wszystkich metod jest wywoanie dostpnej w Delphi funkcji automatycznego uzupeniania klasy. Po umieszczeniu kursora edytora wewntrz przedstawionej wyej deklaracji klasy i naciniciu
kombinacji klawiszy Ctrl+Shift+C, Delphi przygotuje w czci implementacji moduu
szkielety metod przedstawione na listingu 3.14.
Listing 3.14. Implementacje metod przygotowane przez funkcj automatycznego uzupeniania klas
implementation
{ TimerEvent }
constructor TimerEvent.Create;
begin
299
end;
function TimerEvent.ToString: String;
begin
end;
procedure TimerEvent.Trigger;
begin
end;
Konwencje nazewnicze
Delphi stanowi ma pomost pomidzy rnymi wiatami programowania, dlatego nie
znajdziemy w nim adnych z gry narzuconych konwencji nazewniczych, ktre miayby obowizywa we wszystkich klasach zaprogramowanych w Delphi lub wykorzystywanych w tworzonych programach. W poprzednich wersjach Delphi sytuacja
bya jeszcze cakiem przejrzysta, poniewa programici stosowali ogln zasad przygotowan przez firm Borland, mwic, e nazwy klas zaczyna si maj od wielkiej
litery T (podobnie jak i wszystkie inne nazwy typw w jzyku Object Pascal), z ktrej
wynikaj wszystkie nazwy klas obecnych w bibliotece VCL: na przykad TColor,
TForm i TButton.
W rodowisku .NET nie ma takiej tradycji, a klasy nazywaj si tutaj po prostu Color,
Font lub Button. Ze wzgldu na ogromne iloci klas dostpnych w rodowisku .NET,
klasy Delphi z przedrostkiem T znajduj si w mniejszoci. Pozwala to jednak na do
atwe okrelenie pochodzenia danej klasy. Jeeli nie wykorzystujemy adnych dodatkowych klas pochodzcych od firm trzecich, to mona atwo stwierdzi, e klasy z przedrostkiem T przygotowane zostay przez firm Borland, a wszystkie pozostae pochodz
z firmy Microsoft.
W przykadowych programach prezentowanych w tej ksice te dwa schematy nazywania klas stosowane s zamiennie, w zalenoci od tego, czy programowi bliej jest
do biblioteki VCL, czy te do biblioteki klas rodowiska .NET. W ten sposb klasy
prezentowane w rozdziale 1. nie miay w nazwach dodatkowej litery T, ale w rozdziale 4. nazwy klas aplikacji tworzonych w bibliotece VCL.NET bd zaczynay si
od tej litery.
300
Delphi 2005
strict protected Oznacza elementy chronione. Zabezpiecza elementy
301
Dziki zastosowaniu instrukcji with self do wszystkie nazwy zapisane w tym bloku,
ktre s nazwami elementw obiektu self, bd dotyczyy wycznie tego obiektu.
To wszystko oznacza, e przedstawiona wyej instrukcja zmieniajca kolor ta formularza
wewntrz tej procedury wyglda tak:
self.BackColor := Color.Red;
Jak wida, rozrnianie wielu obiektw w czasie dziaania programu obywa si waciwie cakowicie automatycznie. W czasie programowania metod jednej klasy sytuacj t mona sobie wyobrazi tak, e w systemie nie istnieje wiele obiektw tej klasy,
302
Delphi 2005
ale tylko jeden, a my przygotowujemy nie metody klasy, ale metody tego jednego
obiektu. Obiekt, o ktrym tu mwi, nazywa si wanie self. Nie mona jednak nigdy
zapomnie o tym, e w czasie dziaania programu tworzone przez nas metody mog
by wywoywane na rzecz rnych wartoci obiektw zapisanych w parametrze self
(chyba e nasza klasa zostaa celowo tak ograniczona, e na jej podstawie moe by
utworzony tylko jeden obiekt).
Dopki sami nie zadeklarujemy osobnej zmiennej, ktra rwnie bdzie nazywaa si
self, moemy jawnie stosowa identyfikator self, aby w ten sposb pomin na przykad dziaanie innej instrukcji with. W kodzie przedstawionym na listingu 3.19, ze
wzgldu na tak wanie instrukcj with, kompilator uznaje, e identyfikator BackColor
nie jest powizany z obiektem self, ale z obiektem Button1. W takich warunkach,
chcc odwoa si do waciwoci BackColor formularza, musimy jawnie odwoa si
do obiektu self.
Listing 3.19. Instrukcja with moe wymusi jawne odwoanie si do obiektu self
with Button1 do
BackColor := self.BackColor;
{bez instrukcji with mona stosowa taki zapis}
Button1.Color := Color;
3.2.4. Waciwoci
Waciwoci obiektw znosz sprzeczno powstajc pomidzy filozofi programowania zorientowanego obiektowo a chci jak najatwiejszego tworzenia programw.
Jeeli bez stosowania waciwoci chcielibymy jak najprociej zmienia wartoci elementw danych obiektw, to zmuszeni bylibymy do stosowania takiego zapisu:
obiekt.ElementDanych := NowaWartosc;
Taki zapis zadziaaby tylko wtedy, gdyby ElementDanych by zadeklarowany jako publiczny element klasy, co byoby jednak zaprzeczeniem filozofii programowania obiektowego, ktra wymaga, aby takie przypisanie wartoci odbywao si poprzez nastpujce wywoanie metody:
obiekt.SetElementDanych(NowaWartosc);
303
Dyrektywy read i write znajdujce si za deklaracj typu waciwoci (Integer) wskazuj metody stosowane w czasie odczytywania i zapisywania wartoci waciwoci.
Opuszczenie jednej z tych dyrektyw spowoduje, e deklarowana waciwo bdzie moga by tylko odczytywana lub tylko zapisywana.
Metoda odczytujca waciwo musi by funkcj bezparametrow, ktrej typ zwracanej
wartoci zgodny jest z typem waciwoci. Z drugiej strony, metoda zapisujca waciwo musi by procedur pobierajc jeden parametr typu zgodnego z typem waciwoci. Kody metod powizanych z deklarowan wyej waciwoci Width podaj
na listingu 3.20.
Listing 3.20. Kody metod odczytujcych i zapisujcych wartoci waciwoci Width
funktion TPewnaKlasa.GetWidth: Integer;
begin
Result := FWidth;
end;
procedure TPewnaKlasa.SetWidth(NewWidth: Integer);
begin
FWidth := NewWidth;
Invalidate;
end;
304
Delphi 2005
Przykady waciwoci
W tej chwili wska tylko praktyczne przykady waciwoci, jakie mona znale
w innych miejscach tej ksiki:
W klasie RTFAttributes prezentowanej na stronie 171 zadeklarowanych jest
Waciwoci tablicowe
Waciwoci tablicowe na pierwszy rzut oka dziaaj dokadnie tak samo jak zwyczajne
tablice, a dostp do ich elementw mona uzyska na przykad stosujc poniszy zapis:
Colors[0] := System.Drawing.Color.White;
Instrukcja ta przypisuje warto zerowemu elementowi waciwoci Colors. Deklarowanie takiej waciwoci wymaga zadeklarowania nazwy waciwoci i zamknicia
danych o wymiarach tablicy wewntrz nawiasw prostoktnych (na przykad [I: byte]).
Typ poszczeglnych elementw tablicy podawany jest po prostoktnym nawiasie zamykajcym, tak jak na listingu 3.22.
Listing 3.22. Deklarowanie waciwoci tablicowych
// Tablica obiektw typu TColor indeksowana wartociami typu integer
property Colors[i: Integer]: TColor read GetColor write SetColor;
// Trjwymiarowa tablica obiektw
// indeksowana trzema wartociami typu integer
property TrzyWymiary[x, y, z: Integer]: TObject; read Read3D write Set3D;
W deklaracjach metod odczytujcych i zapisujcych waciwoci tablicowe musz pojawi si zmienne indeksowe wymienione w deklaracji wymiarw samej waciwoci.
Dla przedstawionych wyej waciwoci deklaracje tych metod musz wyglda tak
jak na listingu 3.23.
Listing 3.23. Metody zapisujce i odczytujce wartoci waciwoci tablicowych
function GetColor(i: Integer): TColor;
procedure SetColor(i: Integer; val: TColor);
function Get3D(x, y, z: Integer): TObject;
procedure Set3D(x, y, z: Integer; val: TObject);
305
Dla obiektu klasy TList taka deklaracja oznacza na przykad, e cay obiekt tej klasy
mona obsugiwa dokadnie tak samo jak tablic, bez koniecznoci wymieniania konkretnej waciwoci, tak jak w kodzie przedstawionym na listingu 3.25.
Listing 3.25. Traktowanie obiektu listy jako tablicy
var
List: TList;
PierwszyObiekt: TObject;
begin
List := TList.Create(...);
PierwszyObiekt := List[0];
// Inicjowanie listy
Zapisane w powyszym listingu wyraenie List[0] ma dokadnie takie samo znaczenie jak zapis List.Items[0]. Jak mona si domyla, kada klasa moe mie najwyej jedn waciwo standardow tego rodzaju.
Left:
Right:
Top:
Bottom:
Integer
Integer
Integer
Integer
index
index
index
index
1
2
3
4
read
read
read
read
GetCoord;
GetCoord;
GetCoord;
GetCoord;
Wszystkie cztery waciwoci odczytywane s za pomoc tej samej metody, ktra rozrnia te waciwoci korzystajc z indeksu zapisanego w ich deklaracji. Kompilator
automatycznie dodaje waciw warto indeksu do wszystkich wywoa metody odczytujcej (kod tej metody podaj na listingu 3.27).
306
Delphi 2005
Dokadnie tak samo dziaaj procedury zapisujce wartoci do takich waciwoci. One
rwnie wymagaj podania dwch parametrw: indeksu waciwoci i jej nowej wartoci.
W podanym wyej przykadzie nie byy definiowane adne metody zapisujce, poniewa
wartoci czterech przedstawionych waciwoci mona wycznie odczytywa.
307
To wszystko zmienia si jednak diametralnie, gdy przyjrzymy si jeszcze jednej waciwoci Delphi. W jzyku Object Pascal metody statyczne mog by deklarowane jako
wirtualne (dyrektywa virtual), w zwizku z czym mog by pokrywane w klasach
308
Delphi 2005
Zmienne klas
Zmienne i waciwoci rwnie mona deklarowa tak jak metody statyczne, w wyniku czego pojawiaj si one tylko raz w ramach klasy, ale nie staj si czci kadego obiektu tworzonego na podstawie klasy. W przypadku zmiennych zadeklarowanie
takiego rodzaju zmiennej wymaga tylko dodania przed jej deklaracj sw kluczowych
class var.
Jako przykad przedstawi tutaj klas Color (jej deklaracj przedstawiam na listingu
3.29), wzorowan na klasie Color pochodzcej ze rodowiska .NET, w ktrej udostpniane s przygotowane wczeniej klasy kolorw zdefiniowane jako zmienne klasy.
Listing 3.29. Deklaracja klasy udostpniajcej statyczne zmienne obiektw kolorw
type
Color = class
class var White: Color;
class var Red: Color;
...
// Zastosowanie zmiennych klas:
Color.Red; // To wyraenie odczytuje zmienn Red tej klasy
309
Odpowied na to pytanie znale mona w konstruktorach klas, ktre rwnie s nowoci w stosunku do Delphi 7. Konstruktory klas to metody klas, deklarowane za
pomoc sw kluczowych class constructor, a w rodowisku CLR wywoywane s
automatycznie jeszcze przed pierwszym wykorzystaniem danej klasy. W takim konstruktorze mona na przykad zainicjowa wartoci wszystkich obiektw opisujcych
kolory, tak jak na listingu 3.30.
Listing 3.30. Statyczny konstruktor klasy
type
Color = class
class var White: Color;
class var Red: Color;
...
class constructor Create;
...
class constructor Color.Create;
begin
White := Color.Create;
Red := Color.Create;
...
Waciwoci klas
W podobny sposb mona deklarowa te waciwoci, tworzc w wyniku waciwoci
statyczne lub waciwoci klas. W tym celu waciwoci musz by zapisane w czci
deklaracji klasy rozpoczynajcej si od sw kluczowych class var. Tych sw uylimy ju wczeniej do deklarowania zmiennych statycznych, i cho na listingu 3.30
zastosowane byy one przy kadej deklaracji zmiennej, to w rzeczywistoci wystarczyoby tylko raz zapisa je przed pierwsz zmienn. W przykadowym kodzie z listingu 3.31 deklarowane s dwie zmienne i dwie waciwoci tylko do odczytu, wszystkie
bdce statycznymi elementami klasy.
Listing 3.31. Deklarowanie statycznych zmiennych i waciwoci
type
Color = class
class function GetWhite: Color; static;
class var
FRed: Color;
FWhite: Color;
property Red read FRed;
// Dostp poprzez zmienn statyczn
property White read GetWhite; // Dostp poprzez metod statyczn
...
public // Koniec czci "class var" w deklaracji klasy
...
end;
// Koniec deklaracji klasy
310
Delphi 2005
3.2.6. Dziedziczenie
Jedn z najbardziej podstawowych cech programowania obiektowego jest dziedziczenie,
w ktrym klasa bazowa przekazuje wszystkie swoje elementy do klasy wywiedzionej.
Klasa wywiedziona staje si dziki temu rozszerzeniem klasy bazowej. W rodowisku
.NET i w jzyku Object Pascal kada klasa moe mie tylko jedn klas bazow.
Obiekty klasy wywiedzionej zachowuj si pocztkowo dokadnie tak samo jak obiekty
klasy bazowej, ale dziki nowym deklaracjom metod i pokrywaniu implementacji istniejcych metod mona dodawa do wywiedzionej klasy nowe funkcje lub zmienia
w niej dziaanie funkcji odziedziczonych, przez co zmienia si te zachowanie obiektw
tej klasy.
W jzyku Object Pascal i w rodowisku .NET mechanizm dziedziczenia zaszyty jest
tak gboko, e nie da si w nich przygotowa nowej klasy bez dziedziczenia, nawet
jeeli w definicji nowej klasy nie podamy klasy bazowej. Taka klasa automatycznie
zostanie wywiedziona z klasy System.Object, ktra to w jzyku Object Pascal stosowana jest po nazw aliasu TObject.
Przykadem stosowania dziedziczenia jest kada klasa formularza przygotowywana
w projektancie formularzy:
type
TWinForm = class(System.Windows.Forms.Form)
311
{
{
{
{
{ bd niezgodnoci typw }
Klasy zamknite
Delphi dla .NET obsuguje te koncepcj klas zamknitych funkcjonujc w rodowisku .NET i w zwizku z tym wprowadzone zostao specjalne sowo kluczowe sealed.
Klasa zadeklarowana z tym sowem kluczowym nie moe sta si ju klas bazow
312
Delphi 2005
dla innej klasy. Przykadem takiej zamknitej klasy moe by klasa Thread bdca
czci biblioteki klas rodowiska .NET. W jzyku Object Pascal deklaracja tej klasy
wyglda mogaby tak jak na listingu 3.33.
Listing 3.33. Hipotetyczna deklaracja klasy Thread w jzyku Object Pascal
type
Thread = class sealed
procedure Start;
...
end;
MyThread = class(Thread)
rozbudowywana"
end;
Deklarowanie klasy jako zamknitej przydaje si wtedy, gdy dana klasa wewntrz
pewnej biblioteki uznawana jest za niezmienn, co oznacza, e nie powinno si zmienia
jej zachowania poprzez pokrywanie metod w klasach potomnych. Oczywicie podobny
efekt uzyska mona, nie deklarujc w klasie metod wirtualnych. Deklarowanie klasy
jako zamknitej daje jednak uytkownikowi dodatkow informacj. Oznacza to, e rozbudowywanie tej klasy w mechanizmie dziedziczenia nie ma ju sensu, wobec czego
naley w tym zakresie korzysta z innych metod, na przykad z agregacji, takiej jak
pokazana na listingu 3.34.
Listing 3.34. Rozbudowywanie klasy Therad poprzez agregacj
type
MyThread = class
// Klasa MyThread jest rozszerzeniem klasy Thread; przechowuje ona
obiekt typu Thread
T: Thread;
// i ewentualnie przekazuje wywoania rnych metod do przechowywanego
obiektu
procedure Start; // wywouje metod T.Start
end;
313
W takich sytuacjach skorzysta naley z uprzedzajcych deklaracji klas. Jeeli przykadowo chcielibymy poinformowa kompilator o istnieniu identyfikatora TDocument,
to wystarczy uy nastpujcej deklaracji:
type
TDocument = class;
Waciwa deklaracja klasy moe zosta zapisana w dalszej czci pliku rdowego.
Dziki takim deklaracjom wzajemne korzystanie z siebie dwch klas moe wyglda
tak jak na listingu 3.35.
Listing 3.35. Deklaracje dwch klas korzystajcych z siebie nawzajem
type
TDocument = class; { Klasa dokumentu }
TItem = class
{ Klasa jednego z elementw dokumentu }
{ Element musi wiedzie, w jakim dokumencie si znajduje }
ParentDocument: TDocument;
end;
TDocument = class;
{ Dokument musi zna przynajmniej swj pierwszy element: }
FirstItem: TItem;
end;
314
Delphi 2005
Wywoywanie konstruktorw
Zadaniem konstruktorw jest nie tylko inicjowanie obiektu, ale i samo jego utworzenie
(czyli zarezerwowanie dla niego pamici). Na przykad, chcc utworzy obiekt typu
DynamicForm nie mona zastosowa wywoania przedstawionego na listingu 3.37.
Listing 3.37. Niewaciwa prba utworzenia nowego obiektu
var
DynamicForm: Form;
begin
DynamicForm.Create; { obiekt nie zostanie utworzony! }
315
Przedstawiona w powyszym listingu instrukcja wywouje konstruktor Create w poczeniu z obiektem DynamicForm, ktry jeszcze nie istnieje. Chcc utworzy obiekt,
musimy przypisa do zmiennej obiektu (tutaj DynamicForm) cakiem nowy obiekt. Nowy
obiekt tworzony jest przez wywoania konstruktora na rzecz klasy, a nie na rzecz
zmiennej obiektu. Wywoanie metody Form.Create zarezerwuje pami na nowy obiekt
dynamiczny, zainicjuje go i zwrci wskazanie na niego, dziki czemu bdzie mona
je przypisa do zmiennej obiektu:
DynamicForm := Form.Create;
Jeeli konstruktor klasy bazowej pobiera jakie parametry, to dobrym rozwizaniem jest
przygotowanie w nowej klasie deklaracji konstruktora rwnie pobierajcego te same parametry. Dziki temu mona przekaza te parametry do konstruktora odziedziczonego.
316
Delphi 2005
rodowisko CLR rozpoznaje teraz zakoczenie metody i dziki temu wie, e po sowie kluczowym end wszystkie zmienne lokalne przestan istnie. W rodowisku CLR
z kadym utworzonym obiektem powizany jest licznik okrelajcy liczb istniejcych jeszcze wskaza na obiekt. W przedstawionym wyej kodzie licznik ten inicjowany jest wartoci 1, zaraz po zapisywaniu wskazania na ten obiekt do zmiennej F.
W momencie, gdy wykonywanie metody dojdzie do sowa kluczowego end, zmienna
ta przestanie istnie, a w zwizku z tym rodowisko CLR zmniejsza warto licznika
obiektu na zero. Zerowa warto licznika oznacza, e obiekt jest oznaczony do automatycznego usunicia z pamici.
Moemy te zaoy, e w trakcie dziaania procedury zawarto zmiennej F przekazywana jest te do innego obiektu, tak jak to pokazano na listingu 3.41.
317
Zamy, e metoda ZapiszFormularz zachowuje przekazany jej w parametrze formularz (na przykad wewntrz pewnej kolekcji), w wyniku czego CLR automatycznie
powiksza warto licznika obiektu tego formularza do wartoci 2. Jeeli teraz zmienna F
przestanie istnie, to warto licznika spadnie znowu do jedynki, ale tym razem nie
osigajc zera, przez co obiekt formularza nie zostanie oznaczony do usunicia z pamici.
W przypadku zwalniania obiektu (a mwic dokadniej, zmniejszania jego licznika
wskaza), ktry przechowuje w sobie inne obiekty, procedura zwalniania pamici jest
bardzo podobna do tej stosowanej przy zakoczeniu dziaania metody lokalnie rezerwujcej pami dla obiektw. Oznacza to, e w przypadku zwalniania formularza tak
jak w powyszym przykadzie, razem z formularzem usuwane s z pamici wszystkie
kontrolki tego formularza (oczywicie pod warunkiem, e nie s one wskazywane z innych
miejsc w kodzie programu).
rodowisko CLR ma pen kontrol nad wszystkimi wskazaniami wewntrz kodu zarzdzanego, dlatego mona z czystym sumieniem zakada, e zawsze bdzie wiedziao,
ktre obiekty mog by ju bezpiecznie usunite z pamici, a ktre nie2.
Jeeli jednak w gr wchodzi te kod nieobsugiwany, ktry otrzymuje wskazanie na obiekt zarzdzany,
to nie mona zagwarantowa prawidowego dziaania mechanizmu oczyszczania pamici.
318
Delphi 2005
w jzyku C#
odpowiada
wariant
... wariant
destruktora w Delphi rczny w Delphi
Wywoywanie
automatyczne
Finalize
Destruktor
Finalize
Finalize
Wywoywanie
rczne
Wzorzec
Wzorzec
IDispose
Free; Dispose;
Destruktor
Wzorzec IDispose
IDispose
Destruktory w Delphi
Tworzc samodzielnie destruktor, naley deklaracj metod rozpocz od sowa kluczowego destructor. W Delphi dla .NET wszystkie tworzone destruktory musz nazywa si Destroy, nie mog przyjmowa adnych parametrw, a w deklaracji klasy
musz by oznaczane dyrektyw override. W kadym wypadku, ostatni instrukcj
w kodzie destruktora powinno by wywoanie destruktora klasy bazowej, tak jak na
listingu 3.42.
319
Firma Borland zezwolia na stosowanie tej skadni w Delphi dla .NET, poniewa istniejcy ju kod przygotowany w Delphi bardzo czsto wykorzystuje takie wanie destruktory. Kompilator Delphi automatycznie przekada ten wzorzec destruktorw na
wzorzec IDisposable stosowany w rodowisku .NET:
W skompilowanym kompilacie co prawda pojawia si metoda Destroy,
Wykorzystanie tak przygotowanych obiektw wyglda powinno tak jak na listingu 3.43.
Listing 3.43. Sposb wykorzystania obiektu wyposaonego w destruktor
var
Obiekt : TGraphicElement;
begin
Obiekt := TGraphicElement.Create; // Konstruowanie obiektu
... uywanie obiektu ...
Obiekt.Free; // Zwalnianie zasobw obiektu
// Zwolnienie pamici realizowane przez mechanizm oczyszczania pamici
// nastpuje pniej automatycznie, ale moe by te wymuszone rcznie:
// GC.Collect;
// GC.WaitForPendingFinalizers;
320
Delphi 2005
321
Zastosowanie tej klasy musi w takim razie wyglda tak jak na listingu 3.45.
Listing 3.45. Zastosowanie klasy TestClass
var
obiekt: TestClass;
begin
obiekt := TestClass.Create;
... Uytkowanie klasy ...
// Zwolnienie zasobw klasy (bez zwalniania pamici)
obiekt.Dispose; // Rczne zwolnienie
Zwolnienie pamici zajmowanej przez obiekt nastpi automatycznie w czasie nieokrelonym po wywoaniu metody Dispose, kiedy mechanizm oczyszczania pamici znajdzie czas na wykonanie tej operacji albo zostanie ona rcznie wymuszona wywoaniem
metody GC.Collect. Najwaniejsze jest tutaj to, e dziki rcznemu wywoaniu metody
Dispose nie ma ju potrzeby wykonywania automatycznej finalizacji obiektu (dziki
wywoaniu metody SuppressFinalize z wntrza metody TestClass.Dispose), co oszczdza
mechanizmowi oczyszczania pamici czci prac zwizanych z zarzdzaniem obiektem.
Przedstawiony wyej wycinek kodu, poczony z inn klas bazow realizujc t
sam koncepcj za pomoc stosowanych w Delphi destruktorw, mona znale
na pycie CD doczonej do ksiki, w projekcie GCDisposeVariants.
Jeeli w klasie przygotowujemy destruktora i jednoczenie pozwalamy kompilatorowi
automatycznie wygenerowa interfejs IDispose, to tracimy moliwo rcznego wywoania metody Dispose tak jak pokazano na powyszym listingu. Konieczne jest
tutaj wykonanie dodatkowej konwersji typw: (obiekt as IDisposable).Dispose.
(Maa uwaga teoretyczna: Jeeli przygotowywa bdziemy destruktor zgodny ze
star tradycj Delphi, to najprawdopodobniej bdziemy wywoywa go zgodnie z t
tradycj, czyli za pomoc metody Free).
322
Delphi 2005
Kiedy kod formularza jest aktywny ju w czasie projektowania? Tylko wtedy, gdy inny formularz
zostaje z niego wywiedziony, co mielimy okazj obserwowa w punkcie 2.1.4.
323
uywany (czyli jego licznik wskaza ma warto zero), to pocztkowo dopisywany jest
do listy obiektw do finalizacji. W systemie od czasu do czasu wykonywane jest oczyszczanie pamici i wtedy dla kadego obiektu z tej listy wywoywana jest metoda Finalize,
a obiekty przekazywane s do listy obiektw przeznaczonych do zwolnienia. Wszystkie
obiekty usuwane s z pamici dopiero po przejrzeniu przez mechanizm oczyszczania
pamici tej drugiej listy.
Do tego wszystkiego dochodzi jeszcze taki problem, e mechanizm oczyszczania pamici
nie moe zwolni pamici tych obiektw, ktre wskazywane s przez obiekt wykonujcy metod Finalize, poniewa przez cay czas wykonywania tej metoda moe si
ona odwoywa do tych obiektw.
W metodzie finalizujcej jeden z obiektw mona odwoywa si te do innych
obiektw wykorzystywanych przez ten obiekt, poniewa ich zwolnienie nastpi
najwczeniej po zakoczeniu metody Finalize danego obiektu. Trzeba si jednak
liczy z tym, e dla tych istniejcych jeszcze obiektw wywoana moga by ju metoda Finalize, przez co nie bdziemy mieli penej moliwoci korzystania ze wszystkich funkcji tych obiektw. Kolejno wywoywania metod Finalize w grupie obiektw
nie zostaa nigdzie zdefiniowana i moe zmienia si przy poszczeglnych uruchomieniach programu, a take w zalenoci od wersji stosowanego rodowiska
CLR. Oznacza to, e w czasie tworzenia metod Finalize nie moemy zakada
konkretnej kolejnoci wywoa tych metod.
Po takim oznaczeniu obiektu moemy w kadej chwili sprawdzi, czy do zmiennej nadal
przypisany jest jaki obiekt, wywoujc w tym celu funkcj Assigned. Funkcja ta zwraca
warto False, jeeli podana zmienna przechowuje warto nil. Dziki temu mona
unikn pomykowego wywoywania metod zwolnionego ju obiektu:
324
Delphi 2005
if Assigned(DynamicObject)
then DynamicObject.ZrobCos;
Jeeli w metodzie Create w czasie dziaania metody OpenFile1 (albo wczeniej) wystpi jakikolwiek wyjtek, to w czasie dziaania destruktora zmienne File1 i File2 nie
bd zainicjowane. Wynika z tego, e kod finalizacji tego obiektu bdzie bezpieczny
tylko wtedy, gdy bdzie wyglda tak jak na listingu 3.49.
Listing 3.49. Prawidowa posta kodu destruktora obiektu DemoObject
if Assigned(File2) then
File2.Close;
if Assigned(File1) then
File1.Close;
325
Przykad motywacyjny
Standardowo wszystkie metody s niewirtualne, a metodami wirtualnymi staj si dopiero po zastosowaniu wobec nich dyrektywy virtual lub override. W poniszym
przykadzie chc odpowiedzie na rodzce si tu pytanie: do czego w ogle potrzebne
s metody wirtualne? Zamy, e w programie definiujemy kilka klas, ktre wszystkie
maj w sobie metod Pracuj, ale w kadej klasie metoda ta wykonywa bdzie cakowicie inne operacje. Przykadowy kod takich klas przedstawiam na listingu 3.50.
Listing 3.50. Przykadowa deklaracja klas zawierajcych metod o takiej samej nazwie
type
KlasaAbstrakcyjna = class
// Konstruktor Create dziedziczony jest z klasy System.Object
procedure Pracuj;
end;
Klasa1 = class (KlasaAbstrakcyjna) procedure Pracuj; end;
Klasa2 = class (KlasaAbstrakcyjna) procedure Pracuj; end;
...
Klasa10 = class (KlasaAbstrakcyjna) procedure Pracuj; end;
326
Delphi 2005
Metody wirtualne
Przedstawione w powyszym przykadzie metody niewirtualne niszcz cay sens stosowania dziedziczenia klas, ktry to mechanizm ma przede wszystkim umoliwia
zmian w klasach wywiedzionych zachowania metod zdefiniowanych w klasach bazowych. Rozwizaniem tego problemu jest zrezygnowanie z trwaego zapisywania w kodzie
programu metody KlasaAbstrakcyjna.Pracuj (takie rozwizanie nazywa si wczesnym
dowizaniem), na rzecz okrelania wywoywanej implementacji metody dopiero w czasie
pracy programu (pne dowizanie).
Dokadnie tak zachowuj si metody wirtualne. W kodzie przedstawionego wyej
przykadu przeczenie stosowanej metody dowizywania z wczesnej na pn wymaga tylko dopisania za nagwkiem metody Pracuj w klasie bazowej sowa kluczowego virtual, a za nagwkami tej samej metody we wszystkich klasach wywiedzionych
sowa kluczowego override, tak jak to pokazano na listingu 3.52.
Listing 3.52. Poprawiona deklaracja klas wczajca metody wirtualne
type
KlasaAbstrakcyjna = class
{ Konstruktor Create dziedziczony jest z klasy System.Object }
procedure Pracuj; virtual;
end;
Klasa1 = class (KlasaAbstrakcyjna)
procedure Pracuj; override;
end;
...
... pozostay kod - jak wyej ...
...
ObiektRoboczy.Pracuj; { zawsze wywoywana jest waciwa metoda }
327
Override
Proces ponownego definiowania odziedziczonej metody wirtualnej nazywany jest pokrywaniem tej metody. W tym celu niezbdne jest zastosowanie w definicji metody
sowa kluczowego override. Jeeli dyrektywa ta zostaaby opuszczona, to kompilator
utworzy samodzieln metod, ktra nie ma nic wsplnego z metod odziedziczon
z poprzedniego przykadu (w ktrym zmienna ObiektRoboczy zadeklarowana jest z typem
klasy abstrakcyjnej) i w zwizku z tym nie zostanie wywoana. Z punktu widzenia
nowej klasy nowa metoda o takiej samej nazwie, niedeklarowana ze sowem kluczowym
override zasania cakowicie metod odziedziczon.
Wskazwka dla uytkownikw jzyka Borland Pascal
Trzeba tu te zaznaczy, e w jzyku Object Pascal nie istniaa jeszcze dyrektywa override,
a metody wirtualne w klasach wywiedzionych zawsze pokrywane byy metodami o takich samych nazwach. Z tego powodu Delphi standardowo generuje ostrzeenie, jeeli odziedziczona
metoda w klasie definiowana jest bez dyrektywy override, czyli jest zasaniana.
Reintroduce
Oprcz tego w Delphi dostpna jest jeszcze dyrektywa reintroduce, umoliwiajca
zasonicie odziedziczonej metody podobnie do sytuacji opisanej przed chwil, ale
blokujca wypisywanie ostrzeenia przez kompilator. Wystarczy tylko za deklaracj
metody w klasie umieci sowo kluczowe reintroduce.
Wynika z tego, e jeeli w klasie wywiedzionej definiujemy te metod, ktrej nazwa
jest zgodna z nazw metody odziedziczonej to koniecznie musimy w jej deklaracji zastosowa jedno ze sw kluczowych override lub reintroduce, dziki czemu kompilator nie bdzie generowa komunikatw ostrzee, a i dla czowieka atwiejsze bdzie
czytanie kodu takiej klasy. Trzeba jednak pamita, e dyrektyw override stosowa
mona tylko wtedy, gdy lista parametrw i typ zwracanej wartoci nowej metody s
zgodne z tymi samymi danymi o metodzie odziedziczonej. Ograniczenie to nie dotyczy
dyrektywy reintroduce.
328
Delphi 2005
Polimorfizm
Koncepcja programowania realizowana za pomoc dziedziczenia i pnych dowiza
nazywana jest polimorfizmem. Obiektem polimorficznym nazywana jest zmienna obiektowa, dla ktrej kompilator nie jest w stanie okreli klasy obiektu, z jakim bdzie ona
zwizana w czasie dziaania programu. Co wicej, w czasie dziaania programu klasa
tej zmiennej moe si wielokrotnie zmienia, poniewa do jednej zmiennej przypisane
mog by obiekty wielu rnych klas.
Obiekt dostpny poprzez zmienn polimorficzn w kodzie programu opisany jest pewn
konkretn klas, ale w czasie dziaania moe przyjmowa rne formy. Obiekty polimorficzne spotyka si w programach w wielu rnych miejscach, na przykad:
W metodach, ktre w parametrach przyjmowa mog dowolne obiekty, jako typ
tych parametrw podaj klas Object. Taki typ maj na przykad parametry
sender przekazywane do metod obsugujcych zdarzenia formularza, a take
329
Klasy abstrakcyjne
W wielkich hierarchiach dziedziczenia, takich jak biblioteka FCL lub VCL.NET, bardzo
czsto zdarza si, e klasy wykorzystywane s tylko do tego, eby zdefiniowa czci
wsplne pewnych innych klas. W klasach tych moe si zdarzy, e co prawda deklarowane s metody wirtualne, ktre pokrywane s we wszystkich klasach wywiedzionych, ale wewntrz klasy bazowej w ogle nie s implementowane.
Deklarowanie takiej metody, ktra stanowi tylko rezerwacj miejsca dla metod w klasach
wywiedzionych, wymaga zastosowania w deklaracji sowa kluczowego abstract,
ktre okrela tak metod jako abstrakcyjn. Dyrektywa abstract musi znajdowa si
zaraz za dyrektyw virtual, poniewa metoda abstrakcyjna, ktra nie jest jednoczenie metod wirtualn, nie ma racji bytu. W przykadowej klasie KlasaAbstrakcyjna
metoda Pracuj mogaby by w takim razie zadeklarowana tak:
procedure Pracuj; virtual; abstract;
Taka deklaracja ma takie zalety, e metody tej nie trzeba definiowa w czci implementacji moduu, a jej przypadkowe wywoania s automatycznie blokowane, poniewa kompilator nie pozwala na tworzenie obiektu na podstawie klasy z metodami
abstrakcyjnymi. Kada prba utworzenia takiego obiektu spowoduje wywietlenie
bdu ju w czasie kompilacji programu.
W bibliotece VCL.NET znale mona kilka przykadw klas abstrakcyjnych: TStream,
TStrings i TPersistent. W rodowisku .NET klasy abstrakcyjne zdarzaj si duo rzadziej, poniewa abstrakcyjne elementy klas najczciej definiowane s jako interfejsy.
Dan klas mona te jawnie zadeklarowa jako abstrakcyjn:
type
Class1 = class abstract
end;
330
Delphi 2005
Operator is
Oba operatory is i as przeznaczone s do wykonywania bardzo eleganckiej konwersji
typw i sprawdzania zwizkw dziedziczenia czcych klasy. Po prawej stronie operatora zawsze znajduje si zmienna obiektowa (lub wyraenie, ktrego wynik jest
wskazaniem na obiekt) albo referencja klasy, natomiast po lewej stronie operatora moe
znajdowa si wycznie referencja klasy. Operator is sprawdza, czy klasa prawego
operandu jest jednym z potomkw klasy podanej w lewym operandzie. W punkcie
2.4.1 znale mona przykadowy kod (jego cz podaj na listingu 3.55), w ktrym,
w zalenoci od klasy aktualnie wybranego wpisu kontrolki ListBox, wywietlane jest
okno dialogowe przeznaczone do modyfikowania danych danej klasy wpisw.
Listing 3.55. Sprawdzanie klasy podanej zmiennej obiektowej
if ListBox1.SelectedItem is DesktopChangeEvent then
// Wywoanie okna dialogowego dla obiektw DesktopChangeEvent
end else // w przeciwnym wypadku prawdziwe jest: ListBox1.SelectedItem is AlarmEvent
// Wywoanie okna dialogowego dla obiektw AlarmEvent
Konwersja typw
Po sprawdzeniu za pomoc operatora is, czy podana zmienna wskazuje na obiekt
pewnej konkretnej klasy, chcielibymy skorzysta z pewnych specjalnych elementw
tej klasy. W przypomnianym wyej przykadzie chcielimy z kontrolki ListBox odczyta warto zmiennej SelectedItem i przekaza j do okna dialogowego, poniewa
to wanie w tej zmiennej zapisane s wszystkie dane, ktre chcemy edytowa w oknie
dialogowym. Ze wzgldu na uprzednie sprawdzenie typu obiektu przeprowadzone
operatorem is wiemy, e zmienna SelectedItem ma nie tylko zadeklarowany typ
System.Object, ale rwnie specjalny typ DesktopChangeEvent.
Chcc uzyska dostp do tych specjalnych elementw na przykad zmiennej ImageFileName moemy skorzysta z dwch wariantw skadniowych konwersji typw,
przedstawionych na listingu 3.56.
Listing 3.56. Dwa warianty zapisu konwersji typw
DesktopChangeEvent(ListBox1.SelectedItem).ImageFileName := NowaNazwa;
// lub:
(ListBox1.SelectedItem as DesktopChangeEvent).ImageFileName := NowaNazwa;
W rodowisku Win32 te dwa warianty zachowuj si nieco odmiennie: Przedstawione wyej operacje kontrolne wykonywane s tylko w wariancie z operatorem as.
Oznacza to, e pierwszy wariant jest nieco wydajniejszy pod warunkiem, e ju
wczeniej sami skontrolowalimy typ obiektu i mamy cakowit pewno, e bdzie
on zgodny z typem konwersji. Jeeli jednak bdziemy prbowa uzyska dostp do
elementw obiektu nieobsugiwanych przez jego typ i w zwizku z tym skonwertowa
go na typ niezgodny, to wywoany zostanie odpowiedni wyjtek.
331
Oba warianty powoduj dokadnie takie samo dziaanie na platformie .NET: Przed
konwersj sprawdzane jest, czy podany obiekt jest zgodny z podanym typem (jest to
odpowiednik jawnego zastosowania operatora is). Jeeli tak jest, to umoliwiany jest
dostp do wybranego elementu obiektu, a w przeciwnym wypadku wywoywany jest
wyjtek.
Alternatyw w stosunku do metody GetType jest wykorzystanie wbudowanej w kompilator funkcji typeof. Jej zalet w stosunku do metody GetType jest to, e nie potrzebuje ona obiektu, eby odczyta metadane jego typu, ale wystarczy poda jej w parametrze sam typ, tak jak na listingu 3.58.
Listing 3.58. Wykorzystanie funkcji typeof wbudowanej w kompilator
// TypeData: System.Type;
TypeData := typeof(sender); // Odpowiada podanemu wyej wywoaniu funkcji GetType
TypeData := typeof(Integer); // Zwraca metadane typu Integer
332
Delphi 2005
Referencje klas
Przedstawiona wyej klasa System.Type dostpna jest wycznie w rodowisku .NET,
ale samo Delphi dla wszystkich platform udostpnia te tak zwane referencje klas,
ktre pozwalaj w sposb przenony uzyskiwa najwaniejsze informacje o danym typie. Modu System przechowuje deklaracj typu opisujcego najbardziej podstawow
referencj klasy:
type
TClass = class of TObject;
Nie naley miesza identyfikatora TClass ze zwykymi klasami, ktre rwnie deklarowane s z wykorzystaniem sowa kluczowego class, ale bez sowa of. Typ referencji klasy nazywany jest te metaklas, poniewa referencje klas przechowuj tylko informacje o klasach, takie jak ich nazwa i dane klasy bazowej. Jeeli typ referencji
klas zadeklarowany zostanie zapisem of TMojaKlasa, to w ten sposb zawany jest
zakres klas, ktrych dotyczy mog takie referencje, do klasy TMojaKlasa i klas z niej
wywiedzionych. Wynika z tego, e typ TClass jest najbardziej oglnym typem referencji klas, poniewa umoliwia on odczytywanie informacji o dowolnych klasach.
Referencj klasy otrzyma mona przypisujc do zmiennej typu referencji nazw interesujcej nas klasy lub wywoujc metod ClassType dowolnego obiektu. Tak otrzyman referencj klasy mona zapisa do zmiennej o typie referencji klasy, tak jak to pokazano na listingu 3.59.
Listing 3.59. Sposb uzyskiwania referencji klasy
var
ClassRef: TClass;
begin
ClassRef := Button; // Moliwo 1.: "Przypisanie" nazwy klasy
ClassRef := Button1.ClassType; // Moliwo 2.: Wywoanie metody danego obiektu
Do rozbudowywania takich obcych obiektw firma Borland stosuje specjaln technik tak zwanych
klas pomocniczych (ang. Class helpers), ktrych nie mona myli z mechanizmem dziedziczenia.
S one krtko opisywane w systemie aktywnej pomocy Delphi, ale dla normalnego tworzenia aplikacji
w Delphi nie maj praktycznie adnego znaczenia.
333
Funkcja
Typ
zwracany
Wynik
class function
ClassName
String
class function
ClassNamels(Str)
Boolean
class function
ClassParent
TClass
function
ClassType
TClass
Klasa obiektu
class function
ClassInfo
Type
Class function
InheritsForm(AClass)
Boolean
Ten drugi wariant tak naprawd nie powinien by dopuszczalny, jako e wywoywana
jest w nim metoda statyczna ClassName na rzecz konkretnego obiektu. To rozwizanie
dziaa, poniewa w czasie pracy programu sprawdza on, jakiej klasy jest zmienna
Button1, i wywouje metod ClassName na rzecz tej wanie klasy, a nie na rzecz obiektu.
W rzeczywistoci drugie wywoanie metody ClassName wyglda nastpujco:
Name := Button1.ClassType.ClassName;
334
Delphi 2005
Jak wida, moemy co prawda utworzy obiekt odpowiedni dla wyboru dokonanego
przez uytkownika, ale zdecydowanie niezadowalajcy jest brak moliwoci zapisania
w zmiennej wybranej klasy. Po utworzeniu zmiennej ObiektRoboczy, uzyskujemy dostp do wszystkich wirtualnych funkcji tego obiektu, tak jak tego chcielimy. Jeeli
jednak w innym miejscu w programie konieczne byoby utworzenie drugiego obiektu
o tym samym typie (uzalenionym od wyboru uytkownika), to ponownie zostaniemy
zmuszeni do wpisywania dugiej instrukcji case (albo przygotowania specjalnej procedury pozwalajcej wielokrotnie wykorzysta raz wpisan instrukcj case).
Problem ten mona rozwiza o wiele bardziej elegancko, wykorzystujc przy tym
referencje klas, tak jak na listingu 3.62.
Listing 3.62. Przechowywanie referencji klasy w zmiennej
type
ReferencjaKlasyAbstrakcyjnej = class of KlasaAbstrakcyjna;
var
WybranaKlasa: ReferencjaKlasyAbstrakcyjnej;
Objekt: KlasaAbstrakcyjna;
begin
Objekt := WybranaKlasa.Create;
Dziki przedstawionej wyej instrukcji uzyskujemy dokadnie ten sam efekt, jaki tworzony by przez dugi blok instrukcji case z poprzedniego listingu. Oczywicie zmienna
referencji klasy musi ju wczeniej otrzyma odpowiedni warto. Jeeli warto ta
uzaleniona byaby od wyboru dokonanego przez uytkownika, to nadal bdziemy
musieli skorzysta z odpowiednio zmodyfikowanej instrukcji case, na przykad takiej
jak na listingu 3.63.
Listing 3.63. Instrukcja case przygotowujca referencj klasy
case WyborUzytkownika of
Button1: WybranaKlasa := Klasa1;
Button2: WybranaKlasa := Klasa2;
... itd.
Jest to jednak niezaprzeczalna zaleta, e instrukcja case musi tylko raz obsuy wybr dokonany przez uytkownika i zapisa go jako referencj klasy. Referencj klasy
mona sobie te wyobrazi jako zmienn podobn do zmiennej obiektowej, ktra zamiast obiektu przechowuje klas.
Konstruktory wirtualne
Przedstawiony wyej kod niesie ze sob pewne zagroenie, znajdujce si w wierszu:
Objekt := WybranaKlasa.Create;
Wywoanie to zawsze przygotuje obiekt waciwej klasy, ale za kadym razem wywoywany bdzie tylko konstruktor klasy KlasaAbstrakcyjna, ktry dziedziczony jest
przez wszystkie klasy wywiedzione. Jeeli klasy te musz wykonywa w swoich konstruktorach inne wane operacje inicjalizacji obiektu, to w powyszym wierszu kodu
335
336
Delphi 2005
Powysza deklaracja opisuje interfejs, za pomoc ktrego do obiektu kontenera moemy dodawa elementy opisywane cigiem znakw (AddElement), a take usuwa je
z tego kontenera podajc nazw elementu (DeleteElement). Pozostae dwie metody
pozwalaj na odczytanie liczby elementw zapisanych w kontenerze, jak rwnie na
uzyskanie nazwy pierwszego elementu. Sposb definiowania pierwszego elementu,
a take czci skadowych poszczeglnych elementw kontenera, nie jest okrelany
w samym interfejsie, ale musi by zdefiniowany w obiekcie kontenera. Przedstawiony
interfejs nie mwi te nic o samym obiekcie kontenera, poza tym, e przechowuje on
jakie nieokrelone elementy.
Przykad interfejsu przedstawiony na listingu 3.64 znale mona te na pycie CD
doczonej do ksiki, w projekcie InterfaceDemo. Projekt ten jest aplikacj
VCL.NET i ma demonstrowa wycznie zasad funkcjonowania interfejsw, jak
naley ich uywa i je implementowa. Z ca pewnoci nie jest to jednak przykad dobrego interfejsu. W rodowisku .NET bardzo szeroko stosowany jest wzorowy interfejs IList, ktry rwnie umoliwia dodawanie i usuwanie elementw ze
zbioru i w zwizku z tym w przykadowym programie mgby by wykorzystany
w miejscu samodzielnie definiowanego interfejsu IContainer. Interfejs IList jest
jednak o wiele bardziej rozbudowany, wobec czego w przykadowym programie atwiejsze byo zastosowanie znacznie mniejszego interfejsu IContainer.
337
Rysunek 3.11. Okno programu stosujcego dwa cakowicie rne obiekty-kontenery obsugujce
dokadnie ten sam interfejs (przykadowy program InterfaceDemo)
Zmienne interfejsw
Programowanie z wykorzystaniem interfejsw polega na zadeklarowaniu zmiennej
o typie interfejsu i wykorzystywaniu jej tak jak zwyczajnego obiektu. W przykadowym
formularzu znajdziemy zmienn CurrentContainer wskazujc na kontener wybrany
przez uytkownika za pomoc przecznikw (jej deklaracj przedstawiam na listingu
3.65). O tym, jak inicjowane s poszczeglne kontenery i jak przypisywane s one do
zmiennej CurrentContainer, bdziemy mwi nieco pniej.
Listing 3.65. Deklaracja zmiennej interfejsu kontenera
var
TForm1 = class ...
CurrentContainer: IContainer;
Nacinicie przyciskw Dodaj, Usu i Odczytaj pierwszy element spowoduje w programie wywoanie odpowiednich metod interfejsu IContainer, ktre przedstawiam na
listingu 3.66.
Listing 3.66. Metody interfejsu IContainer wywoywane w przykadowym programie
procedure TForm1.AddButtonClick(Sender: TObject);
begin
CurrentContainer.AddElement(ElementName.Text);
end;
procedure TForm1.DeleteButtonClick(Sender: TObject);
begin
CurrentContainer.DeleteElement(ElementName.Text);
end;
338
Delphi 2005
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('Pierwszy element to: '+
CurrentContainer.GetFirstElementName);
end;
W czasie kompilacji programu nie jest jeszcze okrelone, ktry rodzaj kontenera bdzie
zapisany w zmiennej CurrentContainer. Wynika z tego, e zmienna ta jest obiektem
polimorficznym (ang. Polymorphes object), ktry znamy ju z opisywanego wczeniej
podobnego traktowania klas. Jeeli nie wykorzystywalibymy interfejsw, to zmienna
CurrentContainer mogaby by te zadeklarowana tak:
var
CurrentContainer : TContainer
Wielk przewag interfejsw nad klas TContainer jest w fakt, e interfejs moe by
implementowany w cakowicie dowolnej klasie. Jeeli chcielibymy uzyska konkretn implementacj abstrakcyjnej klasy TContainer, to musielibymy przygotowa
now klas wywiedzion z klasy TContainer. Nowa klasa mogaby mie tylko jedn
klas bazow, dlatego klasa ta nie mogaby by ju wywodzona z adnej innej klasy.
Stosujc interfejs IContainer nie nakadamy na swoje poczynania takiego ograniczenia,
wobec czego w przykadowym programie interfejs ten implementowany jest przez
klas formularza.
Dziedziczenie interfejsw
W przypadku interfejsw obowizuj zasady dziedziczenia podobne do tych, ktre
znamy ju z dziedziczenia klas. Oznacza to, e stosujc zapis z listingu 3.68 moemy
zdefiniowa nowy interfejs, ktry bdzie mia wszystkie metody interfejsu IContainer
i dokada do nich wasn metod Mix.
Listing 3.68. Przykad dziedziczenia interfejsw
type
IMixableContainer = interface(IContainer)
procedure Mix;
end;
339
340
Delphi 2005
function GetElementCount: integer;
function GetFirstElementName: string;
end;
implementation
procedure TListContainer.DeleteElement(const ElementName: string);
begin
if IndexOf(ElementName)>-1 then
Delete(IndexOf(ElementName));
end;
function TListContainer.GetElementCount: integer;
begin
Result := Count;
end;
function TListContainer.GetFirstElementName: string;
begin
if Count>0 then Result:=Strings[0]
else Result := '(Lista jest pusta)';
if Result='' then Result := '(Pierwszy element nie ma nazwy.)';
end;
Klasa TListContainer dziedziczy pen funkcjonalno listy z klasy TStringList pochodzcej z biblioteki VCL.NET, i na przykad udostpnia wykorzystywane na listingu
metody Append, Delete, IndexOf, Count i Strings. Jak wida, klasa TListContainer waciwie przenosi dziaanie funkcji klasy TStringList do metod wymienianych w interfejsie IContainer.
Takie jawne powizanie metody interfejsu z metod implementacji moliwe jest rwnie
wtedy, gdy tworzymy wasn metod implementacji i nadajemy jest nazw zupenie
inn od nazwy metody interfejsu. W takiej sytuacji trzeba tylko pamita o uzupenieniu
nazwy metody interfejsu o odpowiedni klauzul zmiany nazwy.
341
implementacji metod interfejsw. Normalnie lista wyboru wywietlana wewntrz deklaracji klasy zawiera tylko metody odziedziczone z klasy bazowej, ktre mona pokry
w klasie wywiedzionej. Jeeli jednak w klasie tej brakuje jeszcze metod wymaganych
przez implementowany interfejs, to Delphi wywietla je na samym pocztku wywietlanej listy, a dodatkowo wyrnia kolorem czerwonym.
Oczywicie mona te wywoa funkcj uzupeniania klasy (naciskajc kombinacj
klawiszy Shift+Ctrl+C), eby w ten sposb przygotowa szkielety implementacji wszystkich metod wymienionych w deklaracji tej klasy.
342
Delphi 2005
Obiekt implementacji formularza jest automatycznie tworzony w kodzie przygotowanym przez Delphi, pamitamy wszak, e jest to formularz aplikacji.
Od tego momentu droga od obiektu do zmiennej interfejsu jest ju bardzo prosta
wystarczy do tej zmiennej przypisa istniejcy obiekt, tak jak na listingu 3.73.
Listing 3.73. Sposoby wizania zmiennej interfejsu z obiektem implementujcym interfejs
var
ListContainer: IContainer;
begin
ListContainer := ListContainerObject;
(* Mona te osign to samo nie wykorzystujc poredniczcej zmiennej ListContainerObject:
ListContainer := TListContainer.Create; *)
343
Ten nowy interfejs implementowany jest w obu przedstawianych do tej pory klasach.
Na listingu 3.76 przedstawiam skrt tej implementacji.
Listing 3.76. Implementacja drugiego interfejsu w programie przykadowym
// TForm1 = class(TForm, IContainer, IClassDescription)
// TListContainer = class(TStringList, IItemContainer, IClassDescription)
function TListContainer.GetClassDescription: string;
begin
Result:='Lista cigw znakw';
end;
function TForm1.GetClassDescription: string;
begin
Result := 'TForm1';
end;
Chcc uywa nowego interfejsu, nie musimy ju tworzy adnych nowych zmiennych typu IClassDescription, bo w zupenoci wystarczy nam istniejca ju zmienna
CurrentContainer, cho jest ona typu IContainer, ktry nie ma adnych zwizkw
z interfejsem IClassDescription.
Cay czas pracujemy tutaj na interfejsach, w zwizku z czym za pomoc operatora as
moemy zmieni typ zmiennej CurrentContainer na IClassDescription i wtedy wywoa na jej rzecz metod GetClassDescription. W przykadowym programie, po klikniciu na przeczniku aktualizowana jest zawarto kontrolki typu Label wypisujcej
tekst, jakim przedstawia si aktualnie wybrana implementacja kontenera. Odpowiedni
kod przedstawiam na listingu 3.77.
344
Delphi 2005
Listing 3.77. Wywoanie metody pochodzcej z drugiego interfejsu implementowanego w obu obiektach
procedure TForm1.ContainerTypClick(Sender: TObject);
begin
...
Label2.Caption := (CurrentContainer as IClassDescription).GetClassDescription;
end;
345
346
Delphi 2005
Przykad
Liczby cakowite
9876543210
Liczby szesnastkowe
$F0D9
Liczby
zmiennoprzecinkowe
Znaki
Cigi znakw
'XYZ'
Zbiory
[biSystemMenu, biMinimize,
biMaximize]
W przypadkach szczeglnych, w ktrych konieczne jest zastosowanie znaku ograniczajcego wewntrz cigu znakw albo jako pojedynczego znaku, wystarczy zapisa dwa
takie znaki jeden za drugim. Jeeli chcielibymy zapisa apostrof jako pojedynczy znak,
to naleaoby zastosowa zapis '''', natomiast cudzysw wewntrz cigu znakw
powinien wyglda tak: '"'.
Sowa kluczowe jzyka Object Pascal s sowami zarezerwowanymi, ktrych nie mona
stosowa w programach jako identyfikatory. Oto lista sw kluczowych jzyka:
and
array
as
asm
begin
case
class
const
constructor
destructor
dispinterface
div
do
downto
else
end
except
exports
file
finalization
finally
for
function
goto
if
implementation in
inherited
initialization inline
interface
is
label
library
mod
nil
not
object
of
or
out
packed
procedure
program
property
raise
record
repeat
resourcestring sealed
set
shl
shr
static
string
then
threadvar
to
try
type
unit
unsafe
until
uses
var
while
with
xor
Czci jzyka Object Pascal s rwnie sowa zapisane w nastpnej licie. Nazywane
s one dyrektywami standardowymi, a od sw kluczowych rni si tym, e nie s
zarezerwowane wycznie dla kompilatora. Dyrektywy te mog by uywane te jako
identyfikatory w tekcie programu, pod warunkiem jednak, e nie bd tworzy adnych dwuznacznoci w kodzie. Takie identyfikatory nie mog znale si tam, gdzie
najczciej uywane s dyrektywy standardowe, czyli na kocach nagwkw funkcji
i procedur. Z drugiej strony, dyrektywy standardowe nie mog by zapisywane w ciele
adnej metody. Sowa private, protected, public i published zarezerwowane zostay
w deklaracjach klas, dziki czemu unika si nieporozumie w tym zakresie.
347
absolute
abstract
assembler
automated
cdecl
contains
default
deprecated
dispid
dynamic
export
external
forward
implements
index
library
local
message
name
nodefault
overload
override
package
pascal
platform
private
protected
public
published
read
readonly
register
reintroduce
requires
resident
safecall
stdcall
stored
varargs
virtual
write
writeonly
Niektre z tych dyrektyw, takie jak package i requires, maj jakiekolwiek znaczenie
wycznie w tekcie rdowym pakietw, dlatego w edytorze Delphi, w tekcie zwykego
moduu, w ogle nie s wyrniane. Pozostae wyrniane w edytorze sowa stosowane w jzyku Object Pascal, niebdce sowami kluczowymi, to sowa on oraz near
i far, ktre dzisiaj nie maj ju adnego praktycznego znaczenia, ale s obsugiwane
w jzyku w ramach zgodnoci z poprzednimi wersjami jzyka.
Podobnie jak w przypadku identyfikatorw, jzyk Object Pascal nie rozrnia wielkich
i maych liter rwnie w sowach kluczowych i dyrektywach standardowych.
Operatory i znaki interpunkcyjne opisywane s w miar potrzeb w dalszej czci rozdziau.
Wczanie zasobw
Oprcz przedstawionych wczeniej przecznikw dostpne s te inne specjalne instrukcje, spord ktrych najczciej wykorzystywana jest instrukcja $R, zawsze generowana razem z kodem przygotowywanym przez Delphi. W kadym nowo utworzonym pliku projektu znale mona taki wiersz:
{$R 'WinForm.TWinForm.resources' 'WinForm.resx'}
Zapis ten powoduje, e automatycznie generowany plik zasobw WinForm.resx kompilowany jest do pliku WinForm.TWinForm.resources i wczany do aktualnego kompilatu.
348
Delphi 2005
Kompilacja warunkowa
Kompilacja warunkowa umoliwia wygenerowanie kilku wersji danego programu na
podstawie jednego pliku z kodem rdowym. Takie zabiegi opacaj si wtedy, gdy
rnice pomidzy tymi wersjami ograniczaj si do zaledwie kilku wierszy. Na przykad,
na podstawie aplikacji VCL.NET mona przygotowywa wersj dziaajc w rodowisku
.NET, wersj dla systemw Windows, a nawet wersj dla Linuksa. Innym przykadem
mog by dodawane do programu dodatkowe instrukcje wspomagajce wyszukiwanie
bdw, ktre powinny by usunite z ostatecznej wersji programu (takie dodatkowe
instrukcje przedstawiam na listingu 3.78.
Listing 3.78. Instrukcje kompilacji warunkowej
{$ifdef WarunekKompilatora}
... Kod programu ...
{$else}
... Alternatywny kod programu ...
{$endif}
Zdefiniowane symbole
Delphi 1
Delphi 2
Delphi 7
Kylix
Delphi 8
VER160, CLR
VER170, CLR
349
W kocu, Delphi udostpnia nam w poczeniu z dyrektyw $if jeszcze dwie specjalne
funkcje: Declared(MojSymbol), pozwalajc skontrolowa, czy zadeklarowany zosta
symbol jzyka Object Pascal, oraz Defined(SymbolKompilatora), ktra sprawdza, czy
zdefiniowany zosta podany symbol kompilatora. Ta druga funkcja jest praktycznie
rwnoznaczna z dyrektyw $ifdef.
Takimi dyrektywami oznacza mona rwnie dowolne obiekty programu, stae i funkcje.
Przykad takiego oznaczenia staej przedstawiam na listingu 3.79.
Listing 3.79. Oznaczenie staej jako zalenej od platformy
const
// Przykad pochodzi z pliku SysUtils.pas: Znacznik ReadOnly
// jest atrybutem obecnym wycznie w systemach plikw stosowanych
// w systemach Windows (w Linuksie dostp do plikw regulowany jest
// za pomoc uprawnie Uytkownika, Grupy i Wszystkich).
faReadOnly = $00000001 platform;
350
Delphi 2005
Oczywicie, podobnie mona oznacza te cae rekordy lub klasy, a nawet cae moduy
(odpowiednie deklaracje przedstawiam na listingu 3.80).
Listing 3.80. Oznaczenie klasy i moduu jako zalenych od platformy
unit ZbiorFunkcjiDlaWindows platform;
type
// Przykad z pliku SysUtils.pas: Klasa jzykw obsugiwanych
// przez system dostpna jest wycznie w systemie Windows
TLanguages = class
...
end platform;
Dziaanie tych dyrektyw polega na tym, e kompilator generuje ostrzeenia, gdy nasz program prbuje wykorzysta obiekt lub inny element oznaczony tak dyrektyw. Sposb
wypisywania tych ostrzee moe by sterowany za pomoc dyrektyw kompilatora $Hints
ON/OFF i $Warnings (wicej na ten temat znale mona w pomocy Delphi).
Bloki deklaracji
Dziki strukturze programu w jzyku Object Pascal, bdcej poczeniem poszczeglnych sekcji struktury, znacznie atwiejsze jest odrnianie w programie zmiennych, typw i staych ni na przykad w jzykach C++ i Java. Kady z tych trzech
rodzajw identyfikatorw musi by deklarowany w sekcji kodu rdowego opatrzonej specjalnym podpisem. Sekcje te mog pojawia si w pliku wielokrotnie i w dowolnej kolejnoci, przy czym zawsze musz znajdowa si poza tymi czciami pliku,
ktre zawieraj w sobie kod programu (czyli poza wszystkimi blokami begin end).
Na przykad zmienne zapisywane s w sekcjach oznaczonych sowem var, tak jak
na listingu 3.81.
Listing 3.81. Deklarowanie zmiennych
var
PewnaLiczba: Integer;
Przycisk1, Przycisk2: Char;
begin
...
351
Deklaracja zmiennej przypisuje jednemu lub kilku identyfikatorom zmiennych rozdzielanych przecinkami typ znajdujcy si za dwukropkiem. Zastosowane w powyszym
listingu typy integer i char s ju zdefiniowane w Delphi.
Deklaracje typw
W kadym pliku formularza znajdziemy zapisan deklaracj typu formularza, czyli
klasy formularza, ktra jest wyjtkowo obszernym przykadem deklaracji typu. Najprostszy sposb zadeklarowania wasnego typu polega na przygotowaniu zastpczego
identyfikatora dla jednego ze standardowo zdefiniowanych typw, tak jak na listingu 3.82.
Listing 3.82. Deklarowanie typu jako innej nazwy typu standardowego
TIndeksTabeli = Integer; { deklaruje nowy typ o nazwie TIndeksTabeli }
TIndeksStron = Integer;
TIndeksKsiazki = Byte;
Takie samodzielnie zdefiniowane typy stosowa mona dokadnie tak samo jak typy
standardowe:
var
NumerStrony : TIndeksStron;
Rnice w zapisach stosowanych w bloku deklaracji typw, w stosunku do bloku deklaracji zmiennych, to gwnie sowo type rozpoczynajce ten blok, moliwo tworzenia tylko jednego identyfikatora w ramach jednej deklaracji i stosowanie w miejscu dwukropka znaku rwnoci. Dziki stosowaniu przedstawionych wyej deklaracji
typw mamy pniej moliwo atwego przestawienia typu TIndeksKsiazki z typu
standardowego Byte na Word. W ten sposb uzyskamy wikszy zakres wartoci dla indeksw ksiek, podczas gdy pozostae zmienne stosujce typ Byte pozostan niezmienione. Jest to rozwizanie pozwalajce na zaoszczdzenie pracy zwizanej z wyszukiwaniem i zastpowaniem odpowiednich zapisw w kodzie programu.
352
Delphi 2005
W jzyku Object Pascal istniej dwa rne rodzaje staych. W rzeczywistych staych
danemu identyfikatorowi przypisywana jest warto staa, ktrej kompilator pniej
uywa wszdzie tam, gdzie w kodzie programu zastosowany zostanie ten identyfikator.
W tym procesie deklaracja identyfikatora staej jest cakowicie usuwana przez kompilator Delphi, w zwizku z czym w powstajcym kompilacie rodowiska .NET takiego identyfikatora ju nie znajdziemy. Zasada tych podmian zobrazowana zostaa
na listingu 3.83.
Listing 3.83. Zastosowanie identyfikatorw staych w programie
const
SzerokoscStandardowa = 200;
begin
Width := SzerokoscStandardowa; // zapis traktowany jest jak "Width := 200;"
Zmienne zainicjowane
W Delphi inicjowanie zmiennych ju w momencie ich deklaracji dozwolone jest tylko
w przypadku zmiennych globalnych. Wartoci inicjujce przypisywane s takim zmiennym
ju w momencie uruchomienia programu, zastpujc standardowe wartoci zerowe.
Skadnia takich deklaracji jest identyczna ze skadni stosowan przy deklarowaniu
staych z przypisanymi typami, a jedyna rnica polega na tym, e umieszczane s
one w sekcji var:
var
SampleRate: Word = 44100;
353
W tym przykadzie procedura AnyProcedure zlicza swoje wywoania w prywatnej zmiennej CallCounter, ktra przy uruchomieniu inicjowana jest zerem.
Podobnie jak wszystkie inne zmienne globalne, Delphi musi jako przedstawi tak zmienn w rodowisku .NET CLR. Przegldajc zawarto programu za pomoc
narzdzia Reflection i rozwijajc wzy podrzdne wza Unit, znajdziemy w nim
nazw @2$AnyProcedure$CallCounter.
Jeeli bdziemy zagnieda funkcje, to kada funkcja zagniedona bdzie miaa dostp
do wszystkich wczeniej zadeklarowanych zmiennych lokalnych funkcji obejmujcej.
354
Delphi 2005
Identyfikatory deklarowane wewntrz deklaracji klasy dostpne s we wszystkich metodach tej klasy, a dodatkowo widoczne s te w klasach wywiedzionych, cho w tym zakresie trzeba jeszcze uwzgldnia atrybuty widocznoci (omawiane byy w punkcie 3.2.2).
Identyfikatory zadeklarowane poza wymienionymi przed chwil obszarami nazywane
s identyfikatorami globalnymi, ktre widoczne s w ramach caego moduu, rozpoczynajc od wiersza, w ktrym zostay zadeklarowane.
Zakrywanie
Identyfikatory mog zosta czasowo zakryte przez identyfikatory zadeklarowane w zagniedonych obszarach widocznoci. W przykadzie przedstawionym na listingu 3.86
deklarowana jest zmienna globalna o nazwie Start, a zaraz obok niej deklarowana jest
zmienna lokalna o tej samej nazwie, zakrywajca zmienn globaln. Mimo zakrycia
zmiennej globalnej, nadal moemy uzyska do niej dostp bezporednio podajc zakres jej widocznoci. Zakres widocznoci zmiennej globalnej ma zawsze nazw moduu,
w ktrym zostaa ona zadeklarowana.
Listing 3.86. Zakrywanie zmiennej globalnej przez zmienn lokaln
unit Unit1;
interface
var
Start: Integer;
implementation
procedure TForm1.FormCreate(Sender: TObject);
var
Start: Integer;
begin
Unit1.Start := 0; // zapisuje warto do zmiennej globalnej
Start := 0;
// zapisuje warto do zmiennej lokalnej
...
355
3.5.6. Atrybuty
Atrybuty s najbardziej podstawow nowoci wprowadzon do Delphi dla .NET. Mona
je umieszcza przed kad deklaracj identyfikatora, dodajc w ten sposb uzupeniajce
informacje do deklarowanego identyfikatora, ktre co prawda nie graj adnej roli dla
kompilatora, ale znaczenia nabieraj w czasie dziaania programu. Kady atrybut staje
si zatem w czasie dziaania programu obiektem. Na przykad deklaracja przedstawiona na listingu 3.87 czy obiekt klasy CategoryAttribute z waciwoci NoSelection.
Listing 3.87. Atrybuty umieszczane przed deklaracjami
[Category(ColorPalCategory)]
property NoSelection: boolean read FNoSelection write FNoSelection;
356
Delphi 2005
W punkcie 6.6.3 wykorzystywanych bdzie jeszcze wiele innych atrybutw stosowanych w czasie projektowania.
W punkcie 3.1.3 wymienianych jest kilka atrybutw dotyczcych samych kompilatw.
W tym miejscu konieczne jest zastosowanie skadni rozszerzonej, poniewa atrybuty
te nie dotycz zapisanego za nimi elementu, ale wpywaj na cay tworzony aktualnie
kompilat i zapisywane s do jego manifestu:
[assembly: AssemblyTitle('Tytu mojego kompilatu')]
[assembly: AssemblyVersion('1.0.0.0')]
3.6. Typy
Podstawowe typy jzyka Object Pascal mniej wicej odpowiadaj rnym typom wartoci waciwoci wywietlanych w inspektorze obiektw, a dodatkowo umoliwiaj
tworzenie kolejnych wariacji typw danych.
Zakres wartoci
Wielko
klasa .NET
ShortInt
-128 127
1 bajt
SByte
Byte
0 255
1 bajt
Byte
SmallInt
-32768 32767
2 bajty
Int16
Word
0 65535
2 bajty
UInt16
LongWord
0 42949672950
4 bajty
UInt32
LongInt
-2147483648 2147483647
4 bajty
Int32
8 bajtw
Int64
Int64
-2
63
63
2 -1
Typy oglne:
Integer
-2147483648 2147483647
4 bajty
Int32
Cardinal
0 42949672950
4 bajty
UInt32
357
Typy Cardinal i Integer nie maj ustalonej z gry wielkoci, ale dopasowuj si do
aktualnej architektury procesora (na przykad w Delphi 2 w momencie przejcia z 16bitowej na 32-bitow architektur systemw Windows wielko tych dwch typw podwoia si z dwch do czterech bajtw) i dlatego nazywane s one typami oglnymi (ang.
Generic types), w przeciwiestwie do typw fundamentalnych (ang. Fundamental types),
ktre na pewno nie bd si zmienia w rnych architekturach procesorw. Najprawdopodobniej w przyszych architekturach 64-bitowych oglne typy liczb cakowitych
nie bd ju rozbudowywane, dlatego kady, kto chce korzysta z 64-bitowych liczb,
powinien ju teraz korzysta bezporednio z typu Int64.
Kolejny przykad wskazuje na to, e stosujc typ Int64 trzeba zawsze pamita o tym,
e standardowy typ liczby cakowitej w Delphi ma tylko szeroko 32-bitw. Ponisza
instrukcja w programie konsolowym spowoduje bd przepenienia:
Writeln('Wynik testu Int64: ' + Int64(1000000000 * 1000000000).ToString);
Problem polega na tym, e Delphi kad z podanych jawnie liczb interpretuje jako liczb
32-bitow, poniewa liczby te mona przedstawi w takiej postaci. W zwizku z tym
kompilator przygotuje na wynik mnoenia kolejn zmienn 32-bitow, chocia tym
przypadku potrzebna byaby akurat liczba 64-bitowa. Problem ten rozwiza mona
przeksztacajc obie liczby w wartoci typu Int64 jeszcze przed wykonaniem mnoenia:
Writeln(Int64(Int64(1000000000) * Int64(1000000000)).ToString);
Typ Currency
Typ Currency (waluta) moe przechowywa wartoci do 922 337 203 685 477,5807
i podobnie w zakresie liczb ujemnych, wobec czego nadaje si do wykonywania najdziwniejszych oblicze finansowych. Mimo e pozwala on zapisywa wartoci po
przecinku dziesitnym, to jednak nie jest to typ zmiennoprzecinkowy, poniewa moe
obsuy najwyej cztery pozycje po przecinku.
Jeeli zapomnimy na chwil o przecinku dziesitnym, to zauwaymy, e typ Currency
jest wartoci 64-bitow, ktra po prostu umoliwia zapisanie wartoci do dziesiciotysicznej czci jednostki monetarnej. Obliczenia, w ktrych stosowane s wycznie
wartoci typu Currency, mog by wykonywane w procesorze tak jak zwyczajne operacje na liczbach cakowitych (cho na procesorach 32-bitowych bd one nieco wolniejsze ni naturalne dla nich operacje 32-bitowe).
358
Delphi 2005
Typ Currency moe by wykorzystywany w dokadnie taki sam sposb jak wszystkie
pozostae typy, co oznacza, e mona na nich wykonywa te same operacje, a w razie
potrzeby zamienia go w wartoci zmiennoprzecinkowe (co moe spowodowa
pewn utrat dokadnoci). Oczywicie mona posugiwa si tym typem tak, jakby by
on typem wartoci zmiennoprzecinkowych i w podobny sposb czy z typami liczb cakowitych (zmieniajc liczb cakowit w liczb zmiennoprzecinkow albo typ Currency
przeksztacajc w liczb cakowit wywoaniami funkcji Trunc lub Round).
Wskazwka do rodowiska .NET
Typ Currency wystpuje wycznie w Delphi i implementowany jest w systemowym module
Delphi w specjalnej klasie. Typ ten nie jest identyczny z klas Decimal pochodzc ze rodowiska .NET, ktra rwnie stosowana jest w matematycznych operacjach finansowych, ale
zajmuje w pamici 96, bitw przez co udostpnia miejsce dla jeszcze dziewiciu dodatkowych cyfr znaczcych. Zakres liczb obsugiwanych przez typ Currency w rodowisku .NET dokadnie odwzorowuje typ System.Data.SqlTypes.SQLMoney.
Zakres
Single
1,5*10
-45
-324
Double
5*10
3,4*10
1,7*10
-4932
38
308
4932
Extended
3,4*10
Real
1,1*10
Dokadno
Wielko
Klasa .NET
7.8. pozycja
4 bajty
Single
15.16. pozycja
8 bajtw
Double
19.20. pozycja
10 bajtw
Double
359
Typy wyliczeniowe
W jzyku Pascal funkcjonuj dwa typy proste, ktre nie s definiowane adnym sowem
kluczowym, ale za pomoc specjalnie przygotowanej definicji: typy zbiorw i typy
wyliczeniowe.
Typy wyliczeniowe stosowane s w wielu waciwociach standardowych komponentw biblioteki VCL.NET. Na przykad waciwo WindowState klasy TForm zostaa
zdefiniowana z wykorzystaniem nastpujcego typu wyliczeniowego:
type
TWindowState: (wsNormal, wsMinimized, wsMaximized);
Jak wida, typ wyliczeniowy skada si z listy nazwanych stanw, ktre mona przypisywa zmiennej danego typu. Kompilator wewntrznie obsuguje wszystkie te wartoci w postaci staych, ktrym wartoci przypisywane s automatycznie. W powyszym
przykadzie staa wsNormal otrzyma warto 0, a staa wsMaximized warto 2.
Najczciej w typach wyliczeniowych w bibliotece VCL pierwsze znaki nazw poszczeglnych staych s skrtami od nazwy typu, na przykad skrt ws powsta z nazwy
waciwoci WindowState. W ten sposb unika si konfliktw z innymi typami wyliczeniowymi, w ktrych rwnie mgby wystpi stan o nazwie Normal.
W przeciwiestwie do praktyk stosowanych w jzykach C# i Java, w jzyku Object Pascal nie trzeba podawa nazwy typu wyliczeniowego przed wpisywan nazw stanu. Rnice pomidzy praktykami stosowanymi w tych jzykach przedstawiam na listingu 3.89.
Listing 3.89. Rnice w stosowaniu typw wyliczeniowych w rnych jzykach
var
ws: TWindowState;
begin
ws := wsNormal;
ws := TWindowState.wsNormal;
360
Delphi 2005
Mimo tego wszystkiego nie moemy jednak zapomina o tym, e zarwno typ Borland.
Vcl.Forms.TWindowState, jak i typ System.Windows.Forms.FormWindowState na poziomie rodowiska CLR s rwnoprawnymi typami wyliczeniowymi wywodzcymi si
z bazowej klasy Enum.
Rwnomierne rozoenie tworzonych automatycznie przecznikw, takie jak w powyszym przykadzie, uzyska mona stosujc komponent GroupBox (opisywany
bdzie w punkcie 6.7.1). Komponent ten moe te samodzielnie przygotowywa
przeczniki dla wartoci typu wyliczeniowego, jeeli taki typ zostanie przypisany
do waciwoci EnumType komponentu.
361
Za pomoc typw wyliczeniowych mona te przeprowadza obliczenia albo przeglda wszystkie wartoci wyliczenia w ptli, wystarczy tylko wykorzysta porzdkow
natur tych typw (o typach porzdkowych mwi bd na stronie 362).
Kompilator bdzie kontrolowa, eby zmiennym o tym typie przypisywane byy wartoci wycznie ze zdefiniowanego w nim zakresu. Jeeli zmiennej przypisywany bdzie
wynik wyraenia, ktry okrelany jest dopiero w czasie dziaania programu, to kompilator nie bdzie mg przewidywa, czy wynik tego wyraenia bdzie zawiera si
w zakresie typu. W zwizku z tym moemy skorzysta z opcji kompilatora $R+, wczajcej sprawdzanie zakresw, przez co zakresy sprawdzane bd take w czasie dziaania
programu, a w przypadku ich przekroczenia wywoywany bdzie wyjtek ERangeError.
Typy zakresw czciowych s specjaln funkcj dostpn wycznie w Delphi,
dlatego rodowisko CLR nie moe wykrywa ewentualnych przekrocze dopuszczalnego zakresu.
Typy logiczne
Typ logiczny (boolean) mona traktowa jak predefiniowany typ wyliczeniowy zawierajcy tylko dwie wartoci. S to wartoci prawdy (True) i faszu (False), ktre
powstaj w czasie okrelania wartoci wyrae logicznych, takich jak (x < MaxX) and
(y < MaxY).
362
Delphi 2005
Typy porzdkowe
Wszystkie typy proste z wyjtkiem typw zmiennoprzecinkowych i typu wielkich
liczb cakowitych Int64 okrelane s jeszcze jedn nazw: typy porzdkowe (ang.
Ordinal types). Ich najwaniejsz cech wspln jest to, e mog by bardzo atwo
przeksztacane w liczby cakowite, o ile ju nimi nie s. Tak dodatkow warto liczbow zmiennej porzdkowej uzyska mona poprzez wywoanie funkcji ord. Za poszczeglnymi typami porzdkowymi ukrywaj si nastpujce liczby:
liczba porzdkowa znaku z zestawu znakw ANSI lub zestawu znakw Unicode
(dla typu WideChar),
w przypadku zmiennych logicznych warto True reprezentuje 1, a warto
False 0,
stae danego typu wyliczeniowego standardowo numerowane s od zera w gr.
363
Konwersja wartoci
Doskonaym przykadem konwersji wartoci jest prba przypisania zawartoci wikszej zmiennej do mniejszej zmiennej. Na przykad wiedzc, e w jednej zmiennej typu
Word z ca pewnoci nie znajd si wartoci wiksze ni 1000, moemy podzieli jej
warto przez 10 i spokojnie dokona konwersji na typ Byte. Docelowy typ konwersji
stosuje si w takiej sytuacji podobnie jak funkcj, a konwertowan warto podaje si
w nawiasach, tak jak na listingu 3.95.
Listing 3.95. Konwersja typw wartoci
ZmiennaTypuByte := Byte(ZmiennaTypuWord div 10);
Znak := AnsiChar(KodZnaku);
// Zamiana liczby w znak
Falsz := Boolean(0);
// Zamiana liczby w warto logiczn
364
Delphi 2005
Priorytety operatorw
W zalenoci od swojego priorytetu, operatory podzielone zostay na cztery poziomy
priorytetw (priorytety te podaj w tabeli 3.7). Jeeli w wyraeniu stosowanych jest
kilka operatorw o jednakowym priorytecie, to zapisane w nich operacje wykonywane
s w kolejnoci od lewej do prawej. Jeeli chcielibymy, aby obliczenia wykonywane
byy w innej kolejnoci ni ta standardowa, to naley skorzysta z odpowiednio umiejscowionych nawiasw.
Tabela 3.7. Priorytety operatorw arytmetycznych
Priorytet
Operator
Opis
()
@, not
operatory jednoargumentowe
operatory mnoce
+, -, or, xor
operatory sumujce
operatory porwnujce
Operatory bitowe
Do czenia ze sob liczb za pomoc operacji wykonywanych na poziomie pojedynczych bitw wykorzystywane s operatory, ktrych nazwa okrela ju rodzaj wykonywanej operacji. Wszystkie rodzaje operatorw bitowych podaj w tabeli 3.8.
365
Poczenie
Przykad
not
bitowa negacja
and
or
xor
alternatywa wykluczajca
shl
shr
Operatory logiczne
Operatory not, and, or i xor mona te wykorzysta do czenia ze sob wartoci logicznych i w tym zastosowaniu oznaczaj operacje negacji, iloczynu i sumy logicznej,
a operator xor oznacza mniej wicej operacj jeden z dwch. W wyniku tych operacji na podstawie dwch wartoci logicznych tworzona jest trzecia warto logiczna.
Na przykad wyraenie:
Button1.Checked and Button2.Checked
Jeeli prawdziwy jest pierwszy operand operacji or, tak jak w pierwszym przykadzie,
to z gry wiadomo, e cae wyraenie bdzie miao warto prawdy, nawet jeeli drugi
operand bdzie faszywy. W takim razie, jeeli w drugiej czci wyraenia wykonywane maj by czasochonne, ale w takiej sytuacji niepotrzebne ju obliczenia, to dobrym rozwizaniem jest zaniechanie wykonywania tych oblicze i zwrcenie wycznie wartoci True z pierwszej czci wyraenia, przez co wykonane zostan instrukcje
po sowie kluczowym then.
W drugim przykadzie pene wyliczanie wartoci wyraenia mogoby mie te katastrofalne skutki. Jeeli uytkownik w ostatniej chwili rozmyliby si i chcia zablokowa
formatowanie nonika, w wyniku czego funkcja PozwolenieUzytkownika zwrciaby
366
Delphi 2005
warto False, ale program mimo to wykonaby ostatni funkcj tego wyraenia (FormatujNosnik), to mimo protestw uytkownika nonik i tak zostaby sformatowany.
Metoda wyliczania wyrae stosowana przez kompilator uzaleniona jest od ustawie
opcji Complete boolean eval dostpnej w oknie dialogowym opcji kompilatora lub
ustawienia dyrektywy kompilatora $B. Domylnie kompilator stara si zakoczy wyliczanie wyraenia tak szybko, jak tylko jest to moliwe, przez co oba podane wyej
przykady okazuj si bardzo sensowne.
Czasami przydaje si te wczenie penego wyliczania wartoci wyraenia. Podany
niej przykad wyraenia kompilowany jest z zaoeniem penego wyliczania wartoci,
co wynika z zastosowania opcji kompilatora $B+:
{$B+}
CalyTestOK := CDROM_Dziala and TwardyDyskOK;
W podanym przykadzie testowane maj by napdy CD-ROM oraz twarde dyski zainstalowane w systemie. Jeeli w czasie testowania napdu CD-ROM i jego sterownika
wykryta zostanie jaka nieprawidowo, to co prawda oglny wynik testw nie moe ju
ulec zmianie, ale uytkownik mimo to otrzyma jeszcze informacje o prawidowym (lub
nie) dziaaniu dyskw twardych (nastpi to w efekcie wykonania funkcji TwardyDyskOK).
Operatory porwnania
Operatory porwnania rwnie zwracaj w wyniku wartoci logiczne. Trzeba jednak
pamita o tym, e maj one niszy priorytet ni przedstawione wyej operatory logiczne, w zwizku z czym w przykadzie przedstawionym na listingu 3.96 konieczne
jest zastosowanie nawiasw, w przeciwnym razie bowiem kompilator poczyby najpierw wartoci zmiennych MaxX i Y operacj sumy logicznej (or), a potem zgosi bd
wynikajcy z nieprawidowego zastosowania operatora wikszoci (>).
Listing 3.96. Operatory porwnania maj mniejszy priorytet od operatorw operacji logicznych
if (X > MaxX) or (Y > MaxY) then
raise EIndexOutOfRange.Create('X lub Y'+
' poza dopuszczalnym zakresem wartoci');
367
3.6.3. Tablice
Tablice s indeksowanym uszeregowaniem w pamici wartoci tego samego typu.
W jzyku Object Pascal dostpne s te indeksowane waciwoci (mwiem o nich
w punkcie 3.2.4), ktre dla uytkownika wygldaj dokadnie tak samo jak tablice.
Dostp do poszczeglnych elementw tablic i indeksowanych waciwoci realizowany
jest w taki sam sposb: za nazw tablicy lub waciwoci podawany jest indeks elementu
zamknity w nawiasach kwadratowych. Indeksy nie musz by wcznie liczbami; tablice
mog by indeksowane dowolnymi typami porzdkowymi, a w przypadku waciwoci
dopuszcza si indeksowanie dowolnym typem.
W przykadzie przedstawionym na listingu 3.98 definiowane s trzy tablice, wrd ktrych
pierwsza wykorzystuje chyba najczciej stosowane indeksy typu integer, elementy
drugiej indeksowane s typem wyliczeniowym, a w trzeciej uyty zosta typ zakresu
czciowego.
Listing 3.98. Do indeksowania tablic mona stosowa rne typy porzdkowe
type
// dwa samodzielnie zdefiniowane typy porzdkowe
SkladowaKoloru = (czerwony, zielony, niebieski);
IndeksyTabeli = 1..100;
var
WartosciPomiarow: array[0..1000] of Double;
Kolor: array[SkladowaKoloru] of Integer;
ArkuszKalkulacyjny: array[IndeksyTabeli, IndeksyTabeli] of Integer;
368
Delphi 2005
Jak wida, pierwszemu elementowi tablicy mona nada indeks 0, 1 lub dowolny inny
z dopuszczalnego zakresu wartoci indeksw. Na listingu 3.99 przedstawiam zatem
sposoby odwoywania si do jednego z elementw trzech tablic zdefiniowanych na listingu 3.98.
Listing 3.99. Sposoby uzyskiwania dostpu do elementw rnie indeksowanych tablic
WartosciPomiarow[0] := WartoscPoczatkowa;
SkladowaZielona := Kolor[zielony];
ArkuszKalkulacyjny[5, 6] := '4*b';
Tablice dynamiczne
Jzyk Object Pascal oferuje szczegln obsug tablic dynamicznych, czyli tablic, dla
ktrych pami rezerwowana jest dopiero w czasie dziaania programu. Tablica dynamiczna deklarowana jest tak:
var
intArray1: array of Integer;
Podobnie jak w przypadku dynamicznych obiektw, ktre odkadane s na stert, musimy si specjalnie upewni, e na tablic przygotowana zostanie wystarczajca ilo
pamici. Odpowiedni procedur przedstawiam na listingu 3.100.
Listing 3.100. Na tablic dynamiczn trzeba jawnie zarezerwowa pami
begin
intArray1[1] := 100; // << Bd, pami nie jest jeszcze zarezerwowana
SetLength(intArray1, 100);
intArray1[100] := 100; // << Bd, dostpne s indeksy od 0 do 99
intArray1[0] := 100; // Warto zapisywana jest do pierwszego elementu
Najciekawsze w tym wszystkim jest to, e funkcj SetLength mona wywoywa wielokrotnie i w ten sposb dostosowywa wielko tablicy do aktualnych potrzeb. W czasie
takich zmian wielkoci tablicy jej zawarto moe by uszkodzona tylko w przypadku
zmniejszania jej rozmiaru.
Szczegln nowoci wprowadzon w rodowisku .NET w zwizku z dynamicznymi
tablicami jest fakt, e teraz ju funkcje samego systemu operacyjnego (w tym
miejscu mam na myli bibliotek FCL) zwracaj atwe w obsudze tablice dynamiczne, a nie wiele powizanych ze sob wskanikw na rne obszary pamici,
ktre zwracane s przez funkcje Windows API. Na przykad w punkcie 1.5.4 mwilimy o funkcji Process.GetProcessesByName zwracajcej warto typu array of
Process.
Klasa Array
rodowisko CLR traktuje kad tablic jak obiekt, podobnie jak i wszystkie inne typy
proste. W rodowisku .NET klas bazow dla wszystkich tablic jest klasa System.Array,
jednak zawarto takiej tablicy nie jest dostpna poprzez indeksy zapisane w nawiasach
369
prostoktnych, a wycznie poprzez wywoywanie metody GetValue i SetValue. Pozostae metody klasy Array pozwalaj na odczytanie indeksw granicznych, przeszukiwanie i sortowanie tablicy, kopiowanie i usuwanie wartoci, a take odwracanie kolejnoci elementw (Reverse). Dokadne dane na temat tych metod uzyska mona
w dokumentacji Delphi.
Dynamiczn tablic jzyka Object Pascal mona te przedstawia jako obiekt klasy
System.Array. W przykadowym kodzie z listingu 3.101 przygotowywany jest specjalny,
abstrakcyjny widok AbstractArray opisujcy istniejc tablic dynamiczn ProcessArray.
Listing 3.101. Reprezentowanie tablicy dynamicznej jako obiektu klasy System.Array
var
ProcessArray: array of Process;
AbstractArray: System.Array;
begin
ProcessArray := Process.GetProcessesByName('Idle');
AbstractArray := ProcessArray;
W klasie System.Array implementowane s interfejsy IList, ICollection i Ienumerable. W zwizku z tym wszystkie tablice dynamiczne tworzone w Delphi mona
obsugiwa tak samo jak kolekcje przedstawiane w punkcie 2.2.5.
Tablice wielowymiarowe
Wszystkie przedstawione do tej pory zasady mona atwo przenie na tablice wielowymiarowe. Musimy tylko wiedzie, jak deklarowane s takie tablice:
var
IntMatrix: array of array of Int64;
Kady ze stu elementw takiej tablicy mona traktowa tak samo jak zwyczajn
zmienn typu array of Int64. Oznacza to, e kady element takiej dynamicznej tablicy moe by osobno inicjowany poprzez przypisanie lub wywoanie procedury
SetLength. Jak wiemy, tablice dynamiczne mog mie rne wielkoci, wobec czego
kademu elementowi takiej tablicy moemy przypisa tablic o innej wielkoci, przez
co tablica IntMatrix nie byaby prostoktna, ale na przykad trjktna lub cakowicie
nieregularna.
370
Delphi 2005
Stae tablicowe
Tablice mog by te deklarowane jako stae, ktre przy okazji mona inicjowa
wartociami podawanymi bezporednio. W deklaracjach takich tablic naley wykorzystywa nastpujc skadni:
ArrayConst: array[0..10] of Byte = (0, 0, 0, 100, 1, 100, 0, 0, 0, 1, 0);
371
pozycj wyszukanego cigu znakw lub warto zera, jeeli podcig nie
zosta znaleziony. Podobn metod jest metoda System.IndexOf, ktra
pozwala dodatkowo na nieco bardziej elastyczne dziaanie, poniewa
umoliwia okrelenie pozycji startowej, od ktrej rozpocz ma si szukanie.
Alternatywnym rozwizaniem jest te metoda String.LastIndexOf, ktra
przeszukuje podany cig znakw od tyu. Obie metody IndexOf
i LastIndexOf w przeciwiestwie do funkcji Pos w przypadku
nieznalezienia szukanego podcigu zwracaj warto -1, a nie 0.
Delete(s, index, count) Usuwa count znakw z podanego cigu znakw s,
rozpoczynajc od pozycji index. W przeciwiestwie do metody String.Remove,
funkcja Delete nie zwraca odpowiednio zmodyfikowanej kopii cigu znakw,
372
Delphi 2005
Insert(substr, s, index) Do cigu znakw s wstawia na pozycji index
dodatkowy cig znakw substr. Jej odpowiednikiem jest metoda String.Insert,
ktra jednak nie modyfikuje rdowego cigu znakw, ale zwraca cig wynikowy.
Z kolei ponisze procedury s cakowicie niezalene od stosowanej metody indeksowania:
Length(s) Zwraca dugo cigu znakw liczon w znakach (odpowiada
waciwoci String.Length).
IntToStr(int) i IntToHex(int) Obie funkcje zamieniaj podan w parametrze
Te znaki, ktre le poza aktualnym kocem cigu znakw, mog by zapisywane dopiero
po jawnym powikszeniu dugoci cigu znakw. Pocztkowo cig znakw ma dugo zerow, poniewa w Delphi inicjowany jest cigiem pustym. Jeeli teraz chcielibymy zapisa pity znak cigu, to moglibymy to wykona za pomoc kodu przedstawionego na listingu 3.104.
Listing 3.104. Zapisywanie pojedynczych znakw w cigu
var
s: String;
begin
// Cig znakw s ma dugo zerow
SetLenght(s, 5);
// Cig znakw s ma dugo piciu znakw
s[5] := NowyZnak;
373
Jeeli takiej zmiennej przypiszemy teraz cig znakw krtszy ni zadeklarowane 100
znakw, to pozostaa pami bdzie niezagospodarowana.
Ten rodzaj cigu znakw wykorzystywany jest dokadnie tak samo jak dugie cigi
znakw, a dodatkowo cigi znakw starego typu mona atwo zamienia na typ System.
String.
Cigi znakw o staej dugoci na poziomie CLR implementowane s w postaci typw wartoci ich zamiana na typ System.String do pewnego stopnia odpowiada
opisywanemu w punkcie 3.6.6 mechanizmowi Boxingu.
374
Delphi 2005
Funkcja formatujca dostpna w Delphi umieszczona zostaa w module SysUtils i nazywa si Format. W jej pierwszym parametrze podawa naley formatujcy cig znakw, na przykad taki:
Wynik := Format('Przebieg %3d Diagnoza: %s.',[Count, Diag]);
375
W rodowisku .NET Delphi implementuje rekordy w postaci klasy, ktra jest automatycznie wywodzona z klasy System.ValueType (na temat typu System.ValueType
mwi bd w punkcie 3.6.6) i pozwala te na definiowanie dla tej klasy nowych metod.
Oto podstawowe rnice tej klasy w stosunku do normalnych klas:
Rekordy nie musz by tworzone wywoaniem konstruktora (wszystkie zmienne
Stae rekordw
Jzyk Object Pascal dopuszcza te moliwo definiowania rekordw jako wartoci
staych, tak jak pokazano na listingu 3.106.
Listing 3.106. Definicja staej rekordu
const
ObjectConst: TPlik = (Nazwa: 'FileDump.exe'; Sciezka: 'd:\Programy\BinUtils');
376
Delphi 2005
Jak wida, nie trzeba inicjowa wszystkich elementw rekordu, ale elementy inicjowane musz by podawane we waciwej kolejnoci. Wane jest te to, e za zapisem
inicjalizacji ostatniego elementu rekordu nie moe znale si znak rednika.
Zbiory
Zajmiemy si teraz typem, ktry w bibliotece FCL stosowany jest nader czsto typem zbiorw. Oglnie, skadnia przedstawiona na listingu 3.107 suy do deklarowania
typu zbioru oraz okrelania wartoci, jakie w tym zbiorze maj si znajdowa.
Listing 3.107. Deklarowanie zbioru
var
CharSet: set of AnsiChar;
begin
CharSet := ['a'..'z'];
Typ bazowy zbioru (czyli typ podawany za sowem kluczowym of) musi by typem
porzdkowym, ktry dodatkowo nie moe przyjmowa wicej ni 256 rnych wartoci.
W zmiennej zbioru dla kadej wartoci rezerwowany jest jeden bit, ktry ma okrela,
czy dana warto jest obecna w zbiorze, czy te nie. Przedstawiony powyej kod oznacza,
e zdefiniowany w nim zbir znakw ma wielko 32 bajtw.
Typ zbioru jest oczywicie interesujcy tylko wtedy, gdy mona do niego szybko dodawa i usuwa elementy oraz moliwe jest atwe sprawdzanie, czy dany element jest
zawarty w zbiorze. Operacje dodawania elementw do zbioru i usuwania ich z niego
uatwiaj dwie funkcje standardowe przedstawione na listingu 3.108.
Listing 3.108. Standardowe funkcje obsugujce dodawanie i usuwanie elementw ze zbioru
{ Dopisywanie elementu do zbioru: }
Include(CharSet, 'd');
{ Usuwanie elementu ze zbioru: }
Exclude(CharSet, 'x');
Do obsugi zbiorw, obok operatora dodawania (+), mona wykorzystywa jeszcze inne
operatory, wypisane w tabeli 3.9.
377
Funkcja
Wynik
Przykad
czenie
Zbir
[1..3,5,9]+[4..6]=[1..6,9]
Rnica
Zbir
['c'..'h']-['a'..'z']=[]
Cz wsplna
Zbir
[1,3,5,6,]*[1,2,3]=[1,3]
Rwno
Boolean
([niebieski]=[niebieski])=True
<>
Nierwno
Boolean
<=
Podzbir
Boolean
>=
Nadzbir
Boolean
<
Rzeczywisty podzbir
Boolean
>
Rzeczywisty nadzbir
Boolean
in
Zawieranie elementu
Boolean
([5]<=[1,5,10]=True)
([czerwony]<[czerwony])=False
(zielony in [czerwony, zielony])=True
Typy referencji
Typy referencji (nazywa s te typami wskanikowymi) charakteryzuj si tym, e wewntrznie realizowane s one wycznie poprzez wskanik. Zmienna typu referencyjnego przechowuje wycznie wskazanie na obiekt, ktry zosta dynamicznie utworzony
na stercie. Dziki tej waciwoci tego rodzaju zmiennych, kilka zmiennych moe
wskazywa na ten sam obiekt. Jeeli obiekt ten ulegnie jakiejkolwiek zmianie, to zmiana
ta widoczna bdzie natychmiast we wszystkich tych zmiennych. Zamy, e zmienna
ListView1 zadeklarowana zostaa jako typ referencyjny wskazujcy na kontrolk ListView.
W takich warunkach wywoanie:
MojObiekt := MojFormularz.ListView1;
spowoduje zapisanie do zmiennej MojObiekt wycznie referencji zapisanej w zmiennej ListView1, ale w systemie bdzie nadal istnia tylko jeden egzemplarz kontrolki
ListView. (Oprcz tego powikszona zostanie jeszcze warto licznika referencji kontrolki stosowanego przez mechanizm automatycznego zwalniania pamici). Podobnie,
w czasie przekazywania typu referencyjnego w parametrze metody kopiowana jest
zawsze wycznie referencja, co odpowiada metodzie przekazywania parametrw
przez referencj. Jak wida, nie da si przekaza zawartoci typu referencyjnego
przez warto.
W rodowisku .NET wszystkie typy s typami referencji, oprcz tych typw, ktre
wywiedzione s z klasy System.ValueType.
W jzyku Object Pascal typami referencji s:
klasy zadeklarowane sowem kluczowym class,
tablice,
wskaniki metod.
378
Delphi 2005
Typy wartoci
Typy wartoci nie s przechowywane na stercie, ale na stosie. Klasa typu wartoci zawsze
musi by klas wywiedzion z klasy System.ValueType. W jzyku Object Pascal kryteria te speniaj nastpujce typy:
typy proste, takie jak integer, boolean lub double (typy te odpowiadaj typom
Kada zmienna typu wartoci ma przydzielon swoj wasn pami na stosie i wasn kopi typu wartoci (wyjtkiem s parametry metod, ktre zadeklarowane zostay
jako parametry referencyjne). Przypisujc jedn zmienn typu wartoci do drugiej,
zawsze tworzymy pen kopi danych zapisanych w tej zmiennej, na przykad:
Integer2 := Integer1;
Record2 := Record1;
Mechanizm Boxingu
W rodowisku CLR mechanizm Boxingu tworzy moliwo przeksztacenia typu wartoci
w typ referencji, poprzez przeksztacenie jej w klas TObject, tak jak na listingu 3.109.
Listing 3.109. Przykad wykorzystania mechanizmu Boxingu
var
o: System.Object;
begin
o := System.Object(Integer(4));
379
Zastosowany przy tym mechanizm jest tutaj cakowicie logiczny i wynika z innych regu
obowizujcych w jzyku programowania. Klasa Object nie zostaa wywiedziona z klasy
System.ValueType i w zwizku z tym nie jest typem wartoci. Klasa ta jest jednak klas
nadrzdn w stosunku do klasy System.ValueType, co oznacza, e typ ValueType moe
by przeksztacony w typ Object.
W czasie dziaania programu cao nie przedstawia si jednak a tak atwo, poniewa
wartoci typu ValueType s odkadane na stosie i w czasie ich przeksztacania do typu
referencyjnego konieczne jest rezerwowanie pamici na stercie, a zawarto stosu musi
zosta skopiowana do sterty. W podanym wyej przykadzie powstajcy tak obiekt
zapisywany jest w zmiennej o, i w zwizku z tym moe by ona dowolnie przekazywana dalej, a sam obiekt zostanie usunity z pamici dopiero po wyzerowaniu wartoci
licznika jego referencji.
3.7. Instrukcje
W jzyku Object Pascal rozrnia si instrukcje proste i instrukcje strukturalne. Do instrukcji prostych zalicza si operacje przypisania za pomoc operatora przypisania (:=)
oraz wywoania procedur, funkcji i metod. Wikszo instrukcji strukturalnych suy
do kontrolowania przebiegu programu; zalicza si do nich ptle, sprawdzenia warunkw i skoki. W jzyku Pascal funkcjonuje jeszcze instrukcja with, ktra przeznaczona
jest przede wszystkim do uatwiania tworzenia tekstu programu i nie ma nic wsplnego
z przepywem programu.
Ptla for
Najprostsz struktur sterujc jest ptla for, wielokrotnie wykonujca jedn lub kilka
instrukcji. Ptla ta wystpuje w dwch wariantach: w pierwszym z nich poda naley
przedzia wartoci, w jakim ptla ma si wykonywa, a liczba obiegw ptli znana
jest jeszcze przed wykonaniem pierwszego obiegu. Przykad takiej ptli podaj na listingu 3.110.
Listing 3.110. Przykadowa ptla for
x := a + b
for i := a to b do
PowtarzanaInstrukcja;
380
Delphi 2005
Ptla forin
Drugi wariant ptli for przygotowany zosta specjalnie dla Delphi dla .NET, ale firma
Borland wprowadzia go dopiero do Delphi 2005. Ptla ta suy do przegldania kolekcji rnych wartoci za pomoc jednej zmiennej. Jako takie kolekcje wykorzystywane mog by tablice, cigi znakw, zbiory oraz obiekty kolekcji. W przykadzie
przedstawionym na listingu 3.111 przegldana jest kolekcja cigw znakw zgromadzona we waciwoci TextBox1.Lines (tablica). W procesie tym wykorzystywana
jest zmienna s typu String, a cakowita dugo wszystkich cigw znakw jest sumowana i wypisywana w oknie programu.
Listing 3.111. Przykad zastosowania ptli forin
procedure TWinForm2.TextBox1_TextChanged(sender: System.Object; e:
System.EventArgs);
var
s: String;
OgolnaDlugosc: Integer;
begin
for s in TextBox1.Lines do
inc(OgolnaDlugosc, s.Lenght);
Text := OgolnaDlugosc.ToString;
end;
Bloki
Wszystkie struktury sterujce mog wykonywa nie tylko pojedyncze instrukcje, ale
rwnie cae bloki kodu. We wszystkich przypadkach z wyjtkiem ptli repeatuntil
konieczne jest zamykanie takiego bloku kodu pomidzy sowami kluczowymi begin
i end, co doskonale wida na przykadzie ptli for przedstawionej na listingu 3.112.
381
Warunki
Wszystkie pozostae instrukcje dotyczce kontroli przepywu programu operuj na
jawnie sprecyzowanych warunkach. W czasie oceny takiego warunku w jzyku Object Pascal zawsze powstaje warto True (jeeli warunek zosta speniony) lub False
(w przypadku niespenienia warunku). Taki wynik logiczny moe zosta zwrcony przez
funkcje lub operatory (operatory porwnujce i operatory logiczne). Wszystkie operatory dostpne w jzyku Pascal oraz ich priorytety omawiaem ju w punkcie 3.6.2.
Instrukcja if
Instrukcja if skada si z wyraenia, ktrego warto musi zosta sprawdzona i maksymalnie dwch alternatywnych instrukcji lub blokw instrukcji. Instrukcja ocenia
podany warunek i w przypadku, gdy zwrci on warto True, wykonuje pierwsz instrukcj lub blok instrukcji, a jeeli warunek zwrci warto False, instrukcja wykona
instrukcj lub blok instrukcji zapisany po sowie kluczowym else. Przykad takiej
postaci instrukcji if przedstawiam na listingu 3.113.
Listing 3.113. Przykadowa instrukcja if
if Wyrazenie
then jestPrawdziwe
else jestFaszywe;
Nie chc tu wprowadza wykadu na temat rnic instrukcji if w jzyku Pascal w stosunku do podobnych instrukcji w innych jzykach programowania (na przykad w porwnaniu do jzykw wielkiej rodziny C). Musz jednak zaznaczy, e w jzyku Pascal
instrukcja if traktowana jest w caoci jako jedna instrukcja, razem z jej czci else.
Pamitamy, e znak rednika nie moe by umieszczany wewntrz instrukcji, w zwizku
z czym nie mona te umieszcza rednika przed sowem kluczowym else, tak jak
maj to w zwyczaju jzyki C# i Java. Na tej samej zasadzie opiera si te rozpoznawanie zagniede instrukcji if, takie jak na listingu 3.114.
Listing 3.114. Zagniedone instrukcje if
if Alternatywa1 then
if Alternatywa2 then
Instrukcja1
else Instrukcja2;
W powyszym kodzie powstaje pytanie, do ktrej instrukcji if naley instrukcja Instrukcja2. W jzyku Pascal kompilator oczekuje, e po pierwszym sowie kluczowym then
zapisana bdzie pena instrukcja, a ta nie koczy si jeszcze wraz z kocem instrukcji
382
Delphi 2005
Instrukcja1, poniewa nie zosta za ni umieszczony znak rednika (jeeli rednik
znalazby si w tym miejscu, to spowodowaby bd, poniewa przerywaby instrukcj zewntrzn). Z tego wynika, e w jzyku Pascal instrukcja Instrukcja2 zaliczona
zostanie jako cz drugiej instrukcji if. Podany wyej przykad mona by uzupeni
o jeszcze jedno sowo kluczowe else nalece do pierwszej instrukcji if, ale wtedy
konieczne byoby usunicie rednika zamykajcego instrukcj Instrukcja2.
Te wszystkie wtpliwoci z ca pewnoci nie bd si pojawia, jeeli instrukcje
przypisane do rnych czci poszczeglnych instrukcji if zamyka bdziemy pomidzy sowami kluczowymi begin i end. Na listingu 3.115 pokazano, e takie rozwizanie
pozwoli te na zmian przypisania czci else do innej instrukcji if ni na listingu 3.114.
Listing 3.115. Sowa kluczowe begin i end pozwalaj precyzyjnie umieci instrukcje w programie
if Alternatywa1 then begin
if Alternatywa2 then
Instrukcja1;
end
else Instrukcja2;
Ptla repeat
Ptla repeatuntil tak dugo wykonuje instrukcje zapisane pomidzy tymi dwoma sowami kluczowym, a warunek zapisany za sowem kluczowym until bdzie prawdziwy. Na listingu 3.116 przedstawiam nieskoczon ptl, ktra sama w sobie nie
wykonuje adnych sensownych operacji.
Listing 3.116. Przykadowa ptla repeatuntil
repeat
if a > 5 then a := a div 2;
a := a + 1;
until a > 9;
Ptla while
Cech ptli repeatuntil jest to, e warunek jej wykonania sprawdzany jest dopiero
po wykonaniu zawartych w niej instrukcji. Fakt ten czyni t ptl przypadkiem specjalnym najczciej stosowanej w jzyku Pascal ptli while. Ta ostatnia ju na samym
pocztku sprawdza, czy zawarto ptli w ogle powinna by wykonana. Przykad
ptli while przedstawiam na listingu 3.117.
Listing 3.117. Przykadowa ptla while
{ Oprnianie stosu }
while not Stack.Empty do
Stack.Pop;
W tym miejscu obowizuj oczywicie reguy zapisu blokw instrukcji oraz zasady
tworzenia warunku umieszczanego za sowem kluczowym while.
383
Instrukcja case
Instrukcja case pozwala na uniknicie bardzo zoonych konstrukcji z instrukcjami
if, tworzc tabel moliwych wartoci zwracanych i powizanych z nimi funkcji. Przykad
zastosowania instrukcji case podaj na listingu 3.118.
Listing 3.118. Przykad instrukcji case
case RadioGroup.ItemIndex of
0: Instrukcja1;
1: begin
... // kilka instrukcji
end;
2, 3: Instrukcja2;
else ...
end;
Zmienna zapisywana za sowem kluczowym case musi by zmienn typu porzdkowego. Jak wida na przykadzie, kilka warunkw rozdzielanych w kodzie przecinkami
moe by obsugiwanych przez ten sam wycinek kodu. Wszystkie warunki niewymienione w kodzie instrukcji case mog by obsugiwane przez kod umieszczony za
sowem kluczowym else.
Jeeli jeden z warunkw zostanie odnaleziony na przygotowanej licie, to wykonywane
s instrukcje z bloku kodu znajdujcego si bezporednio za warunkiem. Wynika z tego,
e nie ma potrzeby stosowania znanej z jzyka C instrukcji break.
Skoki
Jzyk Object Pascal moe realizowa cztery rodzaje skokw:
Stosujc wywoanie Exit, ktre nie jest sowem kluczowym, powodujemy
pocztek ptli, tak jakby ju w tym miejscu zakoczy si jej poprzedni obieg.
Z kolei instrukcja break powoduje natychmiastowe wyjcie z ptli.
W kocu, nadal dostpne s instrukcje goto, ale s one ju tylko reliktem
384
Delphi 2005
Instrukcja with
Jedyn struktur sterujc jzyka Object Pascal, ktrej odpowiednika nie znajdziemy
w adnym z jzykw wywodzcych si z jzyka C, jest instrukcja with. W instrukcji
tej podawany jest zakres widocznoci, ktry w podanym bloku kodu ma by przez
kompilator uznawany za zakres domylny. Korzystajc z instrukcji with mona zamiast zapisu:
DefaultFontStruct.Name := 'Phantom';
DefaultFontStruct.Height := 20;
DefaultFontStruct.UseRaytracing := True;
Jak mona si domyla, instrukcja with pozwala przesoni pewne identyfikatory aktualnego zakresu widocznoci. Co wicej, za sowem kluczowym with podawa mona
kilka zakresw widocznoci rozdzielanych przecinkami, a nawet zagnieda bloki
tworzone tymi instrukcjami. Te dwie ostatnie moliwoci s jednak bardzo rzadko wykorzystywane praktycznie.
385
Wywoanie procedury wymaga zapisania jej nazwy i podania w nawiasach listy jej
parametrw, na przykad tak:
WyswietlWartosc(3.3);
Deklaracje uprzedzajce
Jeeli dwie funkcje z jednego moduu, niebdce metodami i niezadeklarowane w czci
interfejsu moduu, wykorzystuj si wzajemnie, to o jednej z nich trzeba uprzedzajco
poinformowa kompilator. Przykad takiej uprzedzajcej deklaracji funkcji podaj na
listingu 3.120.
Listing 3.120. Przykad uprzedzajcej deklaracji funkcji
procedue ProcBuzywaA; forward; { deklaracja uprzedzajca }
procedue ProcAuzywaB;
begin
ProcBuzywaA;
end;
procedue ProcBuzywaA;
{ definicja procedury z deklaracji uprzedzajcej }
begin
ProcAuzywaB;
...
386
Delphi 2005
Parametry wartoci
W jzyku Pascal, podobnie jak i w wikszoci innych jzykw programowania, istniej dwie moliwoci przekazywania parametrw. W aktualnych parametrach funkcji
znale mog si oryginalne zmienne przekazane bezporednio do funkcji albo tylko
ich kopie. W tym drugim przypadku wywoywana funkcja moe dowolnie zmienia
zawarto otrzymanego parametru, bez wpywu na warto przechowywan w funkcji
wywoujcej. Takie parametry nazywane s parametrami wartoci, a ich przykad podaj
na listingu 3.121.
Listing 3.121. Przykad parametrw wartoci przekazywanych do funkcji
procedure PaintCells(od, do: Integer);
begin
for od := od to do do begin
...
end;
end;
var
Start, Stop: Integer;
begin
Start := 0;
Stop := 10;
PaintCells(Start, Stop);
W powyszym przykadzie zawarto zmiennej Start nie zostanie zmieniona, poniewa funkcja PaintCells otrzyma tylko kopi tej wartoci zapisan w zmiennej od.
Zmienna ta utworzona jest na stosie i zostanie zniszczona, gdy tylko funkcja PaintCells zakoczy swoj prac.
Jeeli parametr wartoci jest typu referencyjnego, czyli na przykad jest obiektem,
to nie jest tworzona kopia tego obiektu, ale do funkcji przekazywana jest tylko
kopia wskazania na ten obiekt. Dziki temu wewntrz procedury mona przypisa
do parametru inny obiekt, nie wpywajc tym samym na obiekt otrzymany z procedury wywoujcej. Jeeli jednak zmienimy wartoci waciwoci obiektu otrzymanego
w parametrze, to zmiany te widoczne bd rwnie w procedurze wywoujcej.
Parametry referencyjne
Jeeli chcielibymy, eby w poprzednim przykadzie wywoywana procedura otrzymywaa faktyczn warto zmiennej Start, to musielibymy nastpujco zmieni deklaracj
procedury PaintCells:
procedure PaintCells(var od: Integer; do: Integer);
387
Jeeli w deklaracji funkcji CheckArray nie byoby sowa kluczowego var, to przy kadym jej wywoaniu w programie rezerwowanych byoby 400 bajtw w pamici stosu,
do ktrych kopiowana byaby zawarto tablicy. Dziki sowu kluczowemu var do
funkcji przekazywany jest tylko wskanik na tablic, ktry ma wielko czterech bajtw.
Niebezpieczestwo polega na tym, e w tym rozwizaniu procedura moe zmienia
wartoci zapisane w tablicy. Ta wada parametrw referencyjnych zostaa usunita dziki
wprowadzeniu do listy parametrw sowa kluczowego const.
Ostatni przykad zadziaa tylko dlatego, e typ TBigArray jest typem wartoci jzyka
Object Pascal, a nie dynamiczn tablic rodowiska CLR. Takie tablice dynamiczne s po prostu typami referencji, ktre zawsze przekazywane s przez referencj,
i w zwizku z tym nie powinny by deklarowane jako parametry ze sowem kluczowym var. Zadeklarowanie w taki sposb parametru typu referencyjnego ma tak
zalet, e wywoywana funkcja moe w otrzymanym parametrze zapisywa wartoci zwracane.
Parametry stae
Jzyk Object Pascal umoliwia te zadeklarowanie parametrw z wykorzystaniem
sowa kluczowego const. Kompilator nie pozwala wtedy na zmian tak zadeklarowanych parametrw wewntrz wywoywanej procedury. Jeeli w parametrze i tak przekazywana miaaby by tylko kopia wartoci, to takie ograniczenie nie miaoby sensu,
dlatego parametry zadeklarowane ze sowem kluczowym const traktowane s jak parametry referencyjne. Jest to bardzo efektywne rozwizanie w przypadku zmiennych
typw wartoci, ktre zajmuj wiele pamici, poniewa zamiast kopiowania caej zmiennej, do procedury przekazywany jest tylko wskanik.
Tablice otwarte
Ostatnim wariantem parametru przekazywanego do procedury s tablice otwarte (ang.
Open array), a w przypadku cigw znakw otwarte cigi znakw (ang. Open string).
Z tego rodzaju parametrw korzysta mona pod warunkiem, e nie zostaa wyczona
opcja kompilatora $P+ (Open parameters standardowo jest ona wczona). Parametry te nazywane s otwartymi, poniewa w parametrach formalnych (czyli w deklaracji) nie okrela si, ile elementw ma mie tablica albo z ilu znakw skada si ma
cig znakw. Oznacza to, e w (aktualnych) parametrach funkcji przekazywane mog
by tablice i cigi znakw o dowolnej wielkoci. W przypadku tablic otwartych wystarczy,
388
Delphi 2005
e podany zostanie typ elementw tablicy, a przy okazji mona te wybiera tryb przekazywania tablicy do funkcji: normalny, var lub const (odpowiednie deklaracje przykadowe przedstawiam na listingu 3.123).
Listing 3.123. Przykadowe deklaracje parametrw otwartych tablic
function SumujTablice_Kopia(a: array of Integer): Integer;
function SumujTablice_Bezposrednio(const a: array of Integer): Integer;
procedure SortujTablice(var a: array of Integer);
Funkcja SortujTablice pobiera w parametrze dowoln tablic liczb cakowitych, dlatego bez adnych kopotw mona przekazywa jej zmienn ParamArray. W wywoaniu drugiej funkcji nie bya stosowana zmienna tablicowa, ale konstrukcja opisywana
w nastpnym podpunkcie.
389
Pierwszy element tablicy zawsze ma indeks zerowy, dlatego oglna liczba elementw
zapisanych w tablicy wynosi EndIndex+1. Funkcje Low i High s w Delphi funkcjami
niezalenymi od platformy, ktre umoliwiaj okrelenie granicznych indeksw tablicy.
W rodowisku .NET dostpna jest niezalena od jzyka metoda System.Array.Length,
ktr mona te wywoywa dla kadej tablicy otwartej jzyka Object Pascal w celu
uzyskania informacji o oglnej dugoci tablicy (wczajc w ni element o indeksie
zerowym).
W deklaracji tej najwaniejsze jest to, e wszystkie parametry, ktre maj pewn warto
standardow, musz by podawane na kocu listy parametrw. Dziki temu kompilator
przy wywoywaniu metody moe okreli, ile parametrw ma otrzyma warto standardow, kierujc si wycznie liczb parametrw wpisanych w wywoaniu. Wynika
z tego, e przykadow funkcj z listingu 3.127 mona wywoywa na trzy sposoby:
390
Delphi 2005
Str := MakeString(Lista);
Str := MakeString(Lista, ',');
Str := MakeString(Lista, ',', 30);
//
//
//
//
Przecianie metod
Przecianie metod pozwala na stosowanie tej samej nazwy dla wielu rnych metod,
rnicych si od siebie pobieranymi parametrami. W czasie tworzenia takich metod
trzeba zawsze zwraca uwag na nastpujce rzeczy:
Kady wariant przecianej metody musi by opatrzony dyrektyw overload.
Wszystkie przeciane funkcje otrzymujce t sam nazw musz si
391
W zwizku z tym moemy kad z tych funkcji przekaza w parametrze do przedstawianej przed chwil procedury RysujKrzywa:
RysujKrzywa(Funkcja1);
RysujKrzywa(Funkcja2);
Obiektowym wariantem typw proceduralnych s typy metod. Jeeli w podanym wyej przykadzie zamiast zwyczajnej funkcji w parametrze ma by przekazywana metoda pewnego obiektu, to musi zosta przygotowana specjalna deklaracja typu:
type
TIntMethod = function(X: Integer): Integer of object;
392
Delphi 2005
3.9. Wyjtki
Wyjtki najczciej stosowane s jako mechanizm obsugi bdw, cho w rzeczywistoci mog one by stosowane do obsugiwania wszystkich rodzajw sytuacji wyjtkowych (angielskie sowo Exception oznacza wanie wyjtek). W momencie wystpienia wyjtku przerywany jest aktualny przepyw programu i poszukiwana jest
metoda, ktra mogaby przechwyci ten wyjtek. Moe to by te ta sama metoda,
w ktrej wyjtek wystpi. Jeeli jednak metoda ta nie przechwyci wyjtku, to jej wykonywanie jest przerywane, a kontrola przekazywana jest to metody wywoujcej. Jeeli
ona rwnie nie przechwyci wyjtku, to nastpuje przerwanie take jej dziaania. Dzieje
si tak do czasu, a znaleziona zostanie metoda obsugujca dany wyjtek, przy czym
kada przerywana metoda ma jeszcze szanse na kocow obsug wyjtku (blok finally),
w ktrej moe wykona wane prace czyszczce.
Jeeli adna z wywoujcych metod nie bdzie w stanie obsuy wyjtku, to jest on obsugiwany w rodowisku CLR, ktre wywietla specjalny komunikat i pozwala uytkownikowi wybra pomidzy zakoczeniem programu a uruchomieniem debugera w celu
sprawdzenia programu.
Wyjtki s w Delphi obsugiwane od pierwszej wersji pakietu, a dzisiaj, w rodowisku
.NET, stay si preferowan metod obsugi bdw w programie.
Tworzenie obiektu wyjtku przebiega dokadnie tak samo jak w przypadku kadego
innego obiektu, czyli poprzez wywoanie konstruktora Create odpowiedniej klasy. Do
rozpoczcia specjalnego postpowania zwizanego z obsug wyjtkw konieczne jest
zastosowanie sowa kluczowego raise. Znaczenie tego sowa (podnie) doskonale
opisuje to, co dzieje si w momencie wywoania wyjtku. Przerywane jest wykonywanie aktualnej metody, a wyjtek przenoszony jest na wyszy poziom stosu wywoa. Moliwe sposoby reakcji metody wywoujcej przedstawiam w punkcie 3.9.4.
393
394
Delphi 2005
395
Zasoby chronione
Z powodu tego, e zasoby zarezerwowane w bloku try z ca pewnoci zostan zwolnione w bloku finally, blok try bardzo czsto nazywany jest te blokiem chronionym.
Jeeli wewntrz tego bloku rezerwowane s jeszcze inne zasoby, ktre rwnie musz
by odpowiednio chronione, to konieczne jest zagniedanie struktury sterujcej, tak
jak pokazano na listingu 3.133.
Listing 3.133. Zagniedone bloki chronione
AllocateRes1 { Zasb pierwszy }
try { blok chroniony dla zasobu pierwszego }
... { Pozycja (*) }
AllocateRes2 { zasb drugi }
try { blok chroniony dla zasobu drugiego }
...
finally
FreeRes2; { Zwolnienie zasobu drugiego }
end;
finally
FreeRes1; { Zwolnienie zasobu pierwszego }
end;
Jeeli w podanym kodzie wyjtek wystpi w pierwszym, ale jeszcze poza drugim
blokiem chronionym (pozycja oznaczona znakiem gwiazdki (*)), to wykonany zostanie
wycznie blok finally zwizany z zewntrznym blokiem chronionym. Wewntrzny
blok finally nie musi by wykonywany, poniewa drugi zasb nie zosta jeszcze zarezerwowany. Blok finally wczany jest do przewidywanego przepywu programu
dopiero wtedy, gdy wykonana zostanie pierwsza instrukcja z powizanego z nim bloku
try, a wtedy wykonywany jest nieodwoalnie, nawet jeeli wyjdziemy z bloku try
wywoujc instrukcj exit.
396
Delphi 2005
Blok except (obsugujcy wyjtki) suy do przygotowania takiej obsugi bdu, eby
program mg poprawnie wznowi swoje dziaanie. Pocztek chronionego tak bloku
kodu rozpoczyna si od sowa kluczowego try, podobnie jak w blokach chronionych
sowem kluczowym finally.
W przykadzie podanym na listingu 3.134 informacja o wyjtku przekazywana jest bezporednio do uytkownika.
Listing 3.134. Przykadowa obsuga wyjtku w programie
try
stream := FileStream.Create(...);
except
on FileNotFoundException do
MessageBox.Show('Pliku nie znaleziono!');
end;
Obie klauzule ondo musz zosta zamienione miejscami, eby przedstawiony kod
mg funkcjonowa waciwie. Po takiej zamianie drugi warunek zapisany za sowem
kluczowym on obsugiwaby wycznie wyjtki klasy SystemException, ale nie obsugiwaby wyjtkw klasy FormatException.
Mona tu te wymieni inne podobiestwa bloku obsugi wyjtkw do instrukcji case:
wicej ni jedna instrukcja musi zosta zamknita wewntrz bloku beginend, po sowie
kluczowym on podawa mona wicej ni jedn klas wyjtku, a poza tym przewidziana zostaa ga else obsugujca niewymienione do tej pory klasy wyjtkw.
To ostatnie rozwizanie nie jest raczej zalecanie, poniewa w ten sposb obsugiwane
bd te te wyjtki, ktrych powodu wywoania nie bdziemy zna. W takiej sytuacji
nie bdzie moliwe wychwycenie takiego wyjtku w miejscu do tego przewidzianym.
397
Istnieje jeszcze jedna moliwo odebrania obiektu zdarzenia, ktr mona wykorzystywa w gazi else bloku except. Dostpna w Delphi funkcja ExceptObject zwraca
aktualny obiekt wyjtku w postaci referencji typu System.Object albo warto nil, jeeli
aktualnie nie ma adnego wyjtku.
Wznawianie wyjtku
W bloku obsugujcym wyjtek mona ponownie wykorzysta sowo kluczowe raise.
Jeeli zostanie ono wywoane bez adnych parametrw, to powoduje ponowienie tego
wyjtku, ktry jest aktualnie obsugiwany. Jeeli w naszej funkcji moemy tylko czciowo obsuy stan bdu, to dziki takiemu rozwizaniu mamy moliwo wykonania
odpowiednich prac w jednym bloku except i przekazania wyjtku do dalszej obrbki
w funkcji wywoujcej. Przykad takiej sytuacji przedstawiam na listingu 3.137.
Listing 3.137. Przekazanie czciowo obsuonego wyjtku do funkcji wywoujcej
try
...
except
on Exception do begin
{ Zwalniane s wszystkie zasoby, ktre nie bd potrzebne tylko w przypadku
wystpienia pewnego wyjtku, ale w przypadku bezbdnego wykonania
metody powinny pozosta zarezerwowane. }
...
raise; { Pozostaa cz obsugi bdu wykonywana jest w funkcji wywoujcej }
end;
end;
398
Delphi 2005
Jeeli w bloku except wywoana zostanie instrukcja raise tworzca nowy obiekt wyjtku,
to zastpi on obsugiwany do tej pory wyjtek, chyba e zostanie on obsuony w ramach
tego samego bloku except. Takie dziaanie jest specjalnym przypadkiem nazywanym
zagniedaniem wyjtkw.
Zagniedanie wyjtkw
W czasie wykonywania bloku except moe oczywicie zdarzy si sytuacja, w ktrej
wygenerowany zostanie kolejny wyjtek. Taki dodatkowy wyjtek moe by obsugiwany wewntrz kolejnej struktury tryexcept (lub finally), na przykad tak jak na
listingu 3.138.
Listing 3.138. Zagniedona obsuga wyjtkw
try
except
on Wyjatek1 do
try
except
on Wyjatek2 do
end;
end;
Jeeli w pierwszym bloku except nie zostanie obsuony wygenerowany w nim wyjtek, to przekazany zostanie on na poziom wyej, zastpujc tym samym aktualny
obiekt wyjtku.
Zakoczenie wyjtku
Wygenerowany w programie wyjtek uznawany jest za obsuony w momencie wywoania bloku obsugi wyjtku, w ktrym nie zosta on odnowiony. Blok obsugujcy
wyjtek moe przyjmowa trzy rne postaci:
Klauzuli on zgodnej z aktualn klas wyjtku, ktra koczy stan wyjtkowy
wyjtki.
Bloku except, ktry zamiast listy obsugiwanych wyjtkw ujtych w klauzulach
on moe zawiera tylko cig instrukcji. Tak zapisany blok obsuguje wszystkie
399
3.9.5. Asercja
Na zakoczenie zajmiemy si technik wyszukiwania bdw i diagnozowania problemw, w ktrej wykorzystywane s rwnie wyjtki, a konkretnie klasa wyjtkw
Borland.System.EAssertionFailed.
Korzystajc z funkcji debugera opisywanych w podrozdziale 1.7 moemy sprawdza
w czasie dziaania programu, czy wszystko przebiega zgodnie z oczekiwaniami, na
przykad to, czy ptla while prawidowo schodzi do wartoci zera. Mwic bardziej
oglnie moemy kontrolowa, czy speniane s pewne warunki dziaania programu.
Musz tu zaznaczy, e Delphi pozwala na wykorzystywanie specjalnych mechanizmw kontrolujcych takie warunki pracy programu:
Stosujc standardow instrukcj Assert mona sprawdza pewne warunki,
400
Delphi 2005
W podanym kodzie przykadowym chcemy si upewni, e ptla rzeczywicie wykonywana bdzie tak dugo, jak dugo zmienna i bdzie miaa warto mniejsz od 100.
Zmienna i przyjmuje jednak wycznie wartoci nieparzyste, przez co nigdy nie zostanie
speniony warunek kocowy i = 100, poniewa zmienna i osignwszy warto 101
nie speni warunku zakoczenia ptli. W tym miejscu wkroczy do dziaania instrukcja
Assert, wskazujc na przyczyn bdu.
Co prawda instrukcja Assert jzyka Object Pascal ma w porwnaniu do makrodefinicji
Assert z jzyka C++ t wad, e nie wywietla automatycznie w oknie komunikatu
penej treci warunku, ale za to pozwala unikn stosowania schematw kodu, takich
jak przedstawiony na listingu 3.140, ktry musiaby si wielokrotnie pojawia w kodzie
rdowym programu.
Listing 3.140. Mao praktyczna metoda wyczania sprawdzania warunkw dziaania programu
{$ifdef AsercjaAktywna}
if not (i < 100) then
// Wywietlenie komunikatu lub wywoanie wyjtku
{$endif}