z przyk│adami w Delphi
WstΩp
Tytu│ tego artyku│u mo┐e siΩ wydawaµ co
najmniej dziwny. Dlatego ju┐ na wstΩpie spieszΩ z
wyja╢nieniem, o co chodzi. Podczas codziennej
praktyki programistycznej zawsze trzeba sobie jako╢
organizowaµ r≤┐ne struktury danych i jak
najoptymalniej zapisywaµ je w pamiΩci. Wiele
(nazwijmy to na razie tak og≤lnie) element≤w w
Windows, a w Delphi w szczeg≤lno╢ci, posiada
wbudowan▒ mo┐liwo╢µ przechowywania warto╢ci
32-bitowej. Tak▒ liczbΩ mo┐na te┐ sobie oczywi╢cie,
podobnie, jak ka┐d▒ inn▒ dan▒ samemu przechowywaµ
w zdefiniowanej przez siebie zmiennej.
Dlaczego 32 bity?
Dlaczego w│a╢ciwie za temat obra│em w│a╢nie
liczby 32-bitowe? Co w nich jest takiego szczeg≤lnego?
Po pierwsze: 32-bity to w│a╢nie tyle danych, na
ile zoptymalizowane s▒ dzisiejsze 32-bitowe
procesory (jak Pentium), oraz 32-bitowe systemy
operacyjne (jak Windows). Dlatego jest to
najbardziej optymalna ilo╢µ danych. Zar≤wno z
punktu widzenia oszczΩdno╢ci czasu procesora, jak
i pamiΩci. Je╢li deklarujesz zmienn▒ typu Byte
czy Word, one i tak bΩd▒ przechowywane ka┐da w 4
bajtach (32 bitach), z kt≤rych wykorzystane bΩdzie
tylko odpowiednio 1 i 2. Tak to robi procesor, tak
to robi kompilator. Jest to podyktowane wzglΩdami
optymalizacji – po prostu takie liczby
szybciej siΩ przetwarza, zapisuje i odczytuje.
Po drugie: m≤wi▒c liczba mam na my╢li jedynie
jej typ docelowy. Przecie┐ mo┐na w 32-bitach opisaµ
tak┐e dowolne inne dane, innego typu. Co zreszt▒ bΩdziemy
robili w dalszej czΩ╢ci artyku│u! Bardzo wiele r≤┐nego
typu danych, jakich u┐ywa siΩ w Windows i og≤lnie
w programowaniu zajmuje w│a╢nie 32 bity lub
najlepiej jest je w│a╢nie tak zapisywaµ. Oto wiΩc
widaµ jeszcze jeden pow≤d, dlaczego w│a╢nie 32
bity: poniewa┐ za pomoc▒ takiej w│a╢nie ilo╢ci
danych mo┐na zapisywaµ najwiΩcej r≤┐nych rodzaj≤w
danych przy jednoczesnej oszczΩdno╢ci pamiΩci
(nic siΩ nie marnuje) i optymalnie szybkim czasie
ich przetwarzania.
Typy 32-bitowe
W Delphi istnieje szereg typ≤w przechowuj▒cych
ca│kowite liczby 32-bitowe. Typy Integer (ze
znakiem) i Cardinal (bez znaku) maj▒ to do siebie,
┐e zajmuj▒ tyle bit≤w, ilu bitowy jest
kompilator. W Turbo Pascalu dla DOS by│o to 16 bit≤w.
W Delphi dla Windows jest ich 32. Inne typy, od
zawsze 32-bitowe to Longint (ze znakiem) i LongWord
(bez niego). Tak wiΩc w 32-bitowym Delphi (wersja 2
i nastΩpne) typy Integer i Longint s▒ identyczne.
Zmienne tego typu mog▒ przechowywaµ warto╢ci z
zakresu od –2`147`483`648 do +2`147`483`647.
Osobi╢cie lubiΩ m≤wiµ na co dzie±, ┐e jest to
zakres od oko│o minus 2 miliard≤w do oko│o plus 2
miliard≤w. Cardinal i LongWord za╢ od 0 do
+4`294`967`295.
Rzutowanie typ≤w
Je╢li jakie╢ miejsce przeznaczone jest na
przechowywanie warto╢ci 32-bitowej, mo┐emy
wykorzystaµ j▒ w dowolny spos≤b. Niekonieczne
zgodnie z typem, jaki zosta│ dla niej
zadeklarowany. Je╢li to Ty sam deklarujesz zmienn▒,
mo┐esz nadaµ jej taki typ, jakiego typu dane bΩdziesz
tam przechowywa│. Ale je╢li takie miejsce ju┐
istnieje, a jego typ jest niezgodny z typem danych,
jakie chcesz do niego zapisywaµ i z niego odczytywaµ?
Wtedy u┐yj rzutowania typ≤w. W Delphi wygl▒da ono
nastΩpuj▒co:
Zmienna_typu_1 := Typ_1(Zmienna_typu_2);
co oznacza, ┐e dane typu 2
„przerabiasz” na dane typu 1. Na czym
owo przerabianie polega? Je╢li obydwa typy s▒
takiego samego rozmiaru (np. 32 bity), komputer po
prostu przepisuje dane bit-po-bicie. Zupe│nie nie
patrzy, co to za typy i jak one siΩ maj▒ do
siebie. Nie pr≤buje przeprowadziµ ┐adnej
konwersji. I o to w│a╢nie nam chodzi. Przecie┐
miejsce ma tylko przechowywaµ Twoje dane. A ┐e
jest innego typu, ni┐ byµ powinno, to ju┐ nie
Twoja wina. Kiedy bΩdziesz chcia│ odczytaµ dane z
takiego miejsca, z powrotem przeprowadzisz
rzutowanie typ≤w w drug▒ stronΩ – na typ
docelowy.
S│≤w kilka o s│ownictwie
CiΩ┐ko jest pisaµ o programowaniu. Choµ mo┐na
z │atwo╢ci▒ wyra┐aµ siΩ precyzyjnie, kiedy
chce siΩ u┐yµ jakiego╢ s│owa og≤lnego pojawiaj▒
siΩ problemy. Wyrazy takie, jak sk│adnik, czyli
inaczej komponent, a tak┐e obiekt i wiele innych
maj▒ swoje precyzyjne znaczenia. Dlatego w artykule
tym na ujΩcie pewnych zagadnie± og≤lnych bΩdΩ u┐ywa│
s│≤w miejsce lub element.
Do czego mo┐e siΩ przydaµ liczba 32-bitowa
Inaczej m≤wi▒c: jakie dane i jakiego typu mo┐na
zapisaµ (czasem po koniecznym rzutowaniu typ≤w) w
32 bitach? Poni┐ej przedstawiam najczΩ╢ciej
spotykane w praktyce programistycznej zastosowania.
Indeks
32-bitow▒ liczbΩ ze znakiem typu Integer lub
Longint mo┐na u┐yµ jako indeksu, czyli do
oznaczania kolejnych numer≤w jaki╢ element≤w.
Zakres, jaki ten typ udostΩpnia jest wystarczaj▒co
du┐y, ┐eby ponumerowaµ dowoln▒ ilo╢µ znajduj▒cych
siΩ w pamiΩci RAM obiekt≤w. Rozpatrzmy przyk│ad:
Choµby ka┐dy numerowany element sk│ada│ siΩ
tylko ze swojego indeksu, bez ┐adnych dodatkowych
danych. Aby wyczerpa│a siΩ pula wszystkich
dodatnich indeks≤w, obiekty te musia│yby zaj▒µ w
sumie 4 * 2`147`483`647 = 8`589`934`592 B, czyli 8
GB. A kto ma tyle pamiΩci RAM? Dlatego zastosowanie
liczby 32-bitowej do przechowywania indeks≤w jaki╢
element≤w, czyli nadawania im kolejnych numer≤w
wydaje siΩ byµ rozwi▒zaniem wrΩcz idealnym. I to
nawet, je╢li „zmarnujΩ siΩ” ca│a
ujemna po│owa dostΩpnego zakresu warto╢ci.
Licznik
Innym zastosowaniem liczby 32-bitowej jest u┐ycie
jej w roli licznika. Dok│adnie chodzi o to, ┐eby
liczba taka by│a w ka┐dym cyklu jakiego╢
algorytmu zwiΩkszana (lub zmniejszana), zwykle o 1.
Jakie ma to zastosowanie? Ogromne! Liczniki s▒ wszΩdzie!
Oto przyk│ad: W ka┐dym cyklu zwiΩkszasz warto╢µ
licznika o 1. Je╢li jaka╢ czΩ╢µ kodu ma byµ
wykonywana co, powiedzmy, 7 cykl, napisz co╢
takiego:
if (Licznik mod 7) = 0 then ...
Oznacza to, ┐e je╢li reszta z dzielenia
licznika przez 7 bΩdzie wynosi│a 0 (innymi s│owy
licznik bΩdzie podzielny przez 7), dany kod ma
zostaµ wykonany.
Pozostaje jeszcze jedno zagadnienie. Czy
zastosowanie 32-bitowej liczby ze znakiem w roli
licznika nie natrafi na problem w postaci
wyczerpania siΩ zakresu? Owszem. Tutaj, w przeciwie±stwie
do indeksu, takie zagro┐enie istnieje i, w zale┐no╢ci
od konkretnego przypadku, mo┐e okazaµ siΩ ca│kiem
realne. Je╢li licznik ma liczyµ tylko do okre╢lonej
wielko╢ci, nie ma problemu. Wielko╢µ ta
najprawdopodobniej nie bΩdzie wiΩksza ni┐ 2 mld.
A co, je╢li licznik ten ma liczyµ w niesko±czono╢µ?
To zale┐y... Je╢li np. na on byµ zwiΩkszany co
sekundΩ, to ┐eby przekroczyµ zakres, bΩdzie na
to potrzebowa│ w przybli┐eniu 68 lat! Co innego,
je╢li licznik ten jest inkrementowany bardzo czΩsto,
wiele tysiΩcy razy na sekundΩ, w dodatku przez
stosunkowo d│ugi czas. A co bΩdzie, je╢li zakres
siΩ sko±czy? C≤┐... Licznik powinien siΩ
wyzerowaµ, ale niczego nie obiecujΩ. R≤┐nie to
mo┐e byµ. Radzi│bym jednak nie zostawiaµ tak
tego i co╢ z tym zrobiµ. Najprostszy ╢rodek
zaradczy to wykrywanie przekroczenia zakresu:
if (Licznik = MAX_INT) then Licznik := 0;
Jeszcze pro╢ciej sprawa wygl▒da, je╢li licznik
ma liczyµ tylko do ilu╢ tam, np. co╢ siΩ ma dziaµ
jedynie co 7 cykl. W takim wypadku zamiast
inkrementowaµ licznik w niesko±czono╢µ i ka┐dorazowo
obliczaµ resztΩ z dzielenia, mo┐na zrobiµ co╢
takiego:
if Licznik = 6 {7-1} then
begin
{ Co╢, co ma siΩ zrobiµ }
Licznik := 0;
end;
Liczba
Dotychczas nie wspomnia│em jeszcze o najbardziej
naturalnym, choµ wcale nie najprostszym
zastosowaniu liczby 32-bitowej: jako po prostu liczbΩ.
Liczby takie mog▒ siΩ przydaµ – i przydaj▒
siΩ czΩsto! Bardzo r≤┐ne jest ich zastosowanie.
Tak samo r≤┐ne s▒ niestety wymagania, jakie takim
liczbom siΩ stawia. W zale┐no╢ci od konkretnego
zastosowania taka liczba mo┐e spokojnie mie╢ciµ
siΩ w zakresie typu Integer, w innym przypadku za╢
przekraczaµ go o ca│e rzΩdy wielko╢ci. Je╢li
masz pewno╢µ, ┐e liczba nie przekroczy warto╢ci
MAX_INT w jedn▒ stronΩ, a –2`147`483`648 w
drug▒ lub je╢li j▒ kontrolujesz, wszystko jest
OK. Je╢li jednak zakres ten oka┐e siΩ
niewystarczaj▒cy, zmuszony bΩdziesz zastosowaµ
inne, „wysokobitowe” typy liczbowe: sta│oprzecinkowe
Cardinal, Int64 lub zmiennoprzecinkowe Single,
Double czy Comp. Ten ostatni nie przechowuje czΩ╢ci
u│amkowej, jest wiΩc w rzeczywisto╢ci typem ca│kowitym.
Zaliczany jest jednak ze wzglΩdu na FPU –
koprocesor arytmetyczny, do typ≤w
zmiennoprzecinkowych i jest z nimi kompatybilny
podczas konwersji.
Je╢li potrzebny jest Ci odrobinΩ wiΩkszy
zakres, ni┐ daje typ Integer (maksymalnie 2 razy),
nie masz natomiast zamiaru u┐ywaµ liczb ujemnych,
warto skorzystaµ z typ≤w Cardinal oraz Longword. S▒
to 32-bitowe typy ca│kowite (w│a╢ciwie
trafniejszym okre╢leniem by│oby tutaj
„naturalne”) bez znaku. Oznacza to, ┐e
ich zakres wynosi od 0 do 4`294`967`295. Liczba taka
jest r≤wnie┐ 32-bitowa. Co z tego wynika, nie
trzeba chyba nikomu m≤wiµ? Mo┐na j▒ przechowywaµ
w ka┐dym miejscu, gdzie do wykorzystania s▒ w│a╢nie
32 bity. Pojawia siΩ tu tylko jeden problem, je╢li
miejsce, w kt≤rym tak▒ liczbΩ bΩdziesz
przechowywaµ jest typu Integer. Chodzi o to, ┐e je╢li
napiszesz co╢ takiego:
{ ¼LE!!! }
Liczba_Integer := Moja_liczba_Cardinal;
Komputer bΩdzie stara│ siΩ przekonwertowaµ j▒.
Nie powinno byµ z tym problem≤w szczeg≤lnie, ┐e
oba typy maj▒ podobn▒ budowΩ i dzia│anie. R≤┐ni▒
siΩ „tylko” jednym bitem. I w│a╢nie
to jest ten bit sporny. Pomy╢l, co bΩdzie, je╢li
Moja_Liczba_Cardinal nale┐y do g≤rnego zakresu
powy┐ej MAX_INT, np. ma warto╢µ r≤wn▒ oko│o 4
mld? Komputer zaprotestuje – liczba
konwertowana poza zakresem. A rozwi▒zanie jest
takie:
Liczba_Integer := Integer(Moja_liczba_Cardinal);
Jak widaµ jest tu rzutowanie typ≤w. DziΩki
takiemu zabiegowi komputer nie bΩdzie siΩ
zastanawia│, ┐e ta liczba jest takiego typu, a ta
zmienna takiego i pr≤bowa│ czegokolwiek konwertowaµ.
I o to w│a╢nie chodzi. Je╢li liczbΩ typu
Cardinal o warto╢ci 4 mld przypiszemy zmiennej typu
Integer, jej warto╢µ bΩdzie wynosi│a jakie╢ -2
mld. Sk▒d to wynika? Ot≤┐ jeden z bit≤w jest w
typie Integer przeznaczony na przechowywanie znaku
(+/-), w typie Cardinal za╢, podobnie, jak
wszystkie pozosta│e, zawiera warto╢µ bΩd▒c▒ sk│adow▒
liczby. Ale nic to nie szkodzi. Je╢li teraz
odczytamy tak▒ liczbΩ typu Integer z powrotem jako
typu Cardinal, otrzymamy nasz orygina│ w
nienaruszonym stanie.
Moja_zmienna_Cardinal := Cardinal(Liczba_Integer);
Uchwyt
Byµ mo┐e nie ka┐da osoba programuj▒ca w
Delphi wie, co to uchwyt (ang. Handle). Ale ka┐dy
prawdziwy programista pisz▒cy w Windows wiedzieµ o
tym powinien. Szczeg≤│owe wyja╢nienie tego pojΩcia
nie le┐y niestety w zakresie tego artyku│u. Po
szczeg≤│y odsy│am do wszelkiego rodzaju
dokumentacji Win32API®. Og≤lnie rzecz bior▒c
uchwyt to liczba – indeks. Windows nadaje
takie indeksy unikatowe w danej chwili w skali ca│ego
systemu znajduj▒cym siΩ w pamiΩci obiektom takim,
jak okna, przyciski czy paski przewijania. Inne
uchwyty mog▒ wskazywaµ na obiekty GDI takie, jak
konteksty urz▒dzenia (DC – Device Context;
odpowiednik Canvasa z Delphi), pi≤ra, pΩdzle i
inne. Uchwytem pos│uguje siΩ program tak┐e
podczas operacji na plikach i kluczach Rejestru oraz
w wielu innych sytuacjach. Jak dzia│aj▒ uchwyty?
Tworz▒c jaki╢ element (jak okno czy przycisk) b▒d╝
otwieraj▒c istniej▒cy (np. plik) otrzymujesz jego
uchwyt. Od tej chwili podajesz go jako pierwszy
parametr funkcji, kt≤re wywo│ujesz chc▒c wykonaµ
jakie╢ operacje na tym elemencie. Potem musisz go
zamkn▒µ lub usun▒µ.
Do reprezentacji uchwyt≤w istnieje wiele typ≤w.
THANDLE, HWND, HINST to tylko niekt≤re z nich. Oto,
jak typy takie zdefiniowane s▒ w kodzie ╝r≤d│owym
modu│u Windows:
type
HICON = type LongWord;
I tak dla ka┐dego typu uchwytu. Czym jest typ
LongWord? Jak ju┐ pisa│em wy┐ej, to 32-bitowy (a
jak┐e!) typ ca│kowity bez znaku w 32-bitowym
kompilatorze Delphi r≤wny Cardinal. Tak wiΩc do
zapisania uchwytu idealnie nadaje siΩ 32-bitowa
liczba.
Wska╝nik
Wska╝nik, jaki jest, ka┐dy widzi. Liczba
oznaczaj▒ca numer kom≤rki w pamiΩci, gdzie co╢
jest zapisane. Dawno dawno temu, w epoce DOSa │upanego
nie mieli╢my jeszcze p│askiego 32(a ile┐by mog│o
byµ?) -bitowego modelu pamiΩci wirtualnej. PamiΩµ
RAM by│a wtedy podzielona na strony. Obowi▒zywa│y
2 rodzaje wska╝nik≤w: tzw. bliskie (ang. near lub
short) sk│adaj▒ce siΩ jedynie z numeru kom≤rki
pamiΩci (w odniesieniu do jakiej╢ tam bie┐▒cej
jej czΩ╢ci) 16-bitowe oraz tzw. dalekie (ang. far
lub long), kt≤re sk│ada│y siΩ z tzw. adresu i
offsetu, czyli odpowiednio numeru
„strony” pamiΩci i numeru kom≤rki na
tej stronie.
No, to tyle historii. Teraz nasta│y nam lepsze
czasy. Mamy jednolite wska╝niki. A zajmuj▒ one
(no, zgadnij?) – 32 bity! Tak jest! Dlatego w│a╢nie
wska╝niki nadaj▒ siΩ r≤wnie┐ do przechowywania
w miejscach przeznaczonych na liczby 32-bitowe.
Powiem wiΩcej: jest to jedno z najczΩstszych, o
ile nie najczΩstsze ich zastosowanie! I to do tego
stopnia, ┐e wiele takich miejsc nie jest domy╢lnie
typu Integer, ale w│a╢nie Pointer lub TObject.
Nie bez powodu tak d│ugo zwlekam z napisaniem,
jak nazywa siΩ typ wska╝nikowy. Ot≤┐ sprawa nie
jest taka prosta, jak w przypadku innych typ≤w.
Typem wska╝nikowym uniwersalnym jest typ Pointer.
Nie ma on zdefiniowanego typu danych, kt≤re znajduj▒
siΩ pod wskazywanym przez niego adresem. Mo┐na by
rzec, ┐e jest bezpostaciowy, amorficzny. Jego cech▒
jest kompatybilno╢µ ze wszystkimi innymi typami
wska╝nikowymi. Nie posiada on natomiast pewnej
funkcjonalno╢ci „zwyk│ych” wska╝nik≤w.
Istnieje wiele predefiniowanych typ≤w wska╝nikowych.
Ich nazwy zaczynaj▒ siΩ od litery P. Aby
zdefiniowaµ w│asny taki typ, nale┐y pos│u┐yµ
siΩ znakiem ^:
type
PInteger = ^Integer;
TMojRec = record
Pole1: TDateTime;
Pole2: Integer;
end;
PMojRec = ^TMojRec;
A oto, jak mo┐na przyk│adowo u┐ywaµ liczby
32-bitowej w roli wska╝nika:
var
Rec: PMojRec;
begin
New(Rec);
Liczba_Integer := Integer(Rec);
Wska╝nik do obiektu
Pisz▒c o wska╝nikach celowo zaprezentowa│em
wska╝niki do typ≤w prostych oraz do rekord≤w.
Pomin▒│em natomiast zagadnienie u┐ywania wska╝nik≤w
w programowaniu zorientowanym obiektowo (ang. OOP
– Object Oriented Programming). Tutaj nie
definiuje siΩ wska╝nik≤w jako takich. Delphi, a
precyzyjniej Object Pascal ma to do siebie, ┐e, w
przeciwie±stwie do C++, nie mo┐na w tym jΩzyku
tak po prostu zdefiniowaµ zmiennej typu obiektowego
(klasy). W C++, aby stworzyµ wska╝nik do instancji
(egzemplarza) obiektu danej klasy, nale┐y jawnie
zadeklarowaµ go jako wska╝nik. Inaczej jest w
Delphi – tu ka┐da zmienna, ka┐de pole typu
TObject lub jego potomka to automatycznie wska╝nik
– nie da siΩ tego obej╢µ. Tak wiΩc aby
stworzyµ faktyczny obiekt w pamiΩci nie wystarczy
zadeklarowaµ zmiennej odpowiedniego typu. Trzeba
tak┐e ten obiekt utworzyµ za pomoc▒ jednego z
jego konstruktor≤w. Konstruktor to tak naprawdΩ
nic innego, jak funkcja, kt≤ra zwraca wska╝nik do
nowo utworzonej instancji obiektu. A wiΩc:
var
Button: TButton;
begin
Button := TButton.Create(...);
Button to w rzeczywisto╢ci wska╝nik, zmienna
wska╝nikowa. Na pocz▒tku jej warto╢µ jest
nieustalona. Po instrukcji przypisania przechowuje
ona wska╝nik do instancji obiektu w pamiΩci. A jak
wska╝nik – to wiadomo co. 32 bity itd.! No wiΩc:
Liczba_Integer := Integer(Przycisk);
Dzia│a? Musi dzia│aµ! I tak oto mamy kolejne
(jedno z najwa┐niejszych) zastosowanie liczby
32-bitowej.
Kolor
Do przechowywania koloru s│u┐y typ TColor.
Zdefiniowany jest on w module Graphics w nastΩpuj▒cy
spos≤b:
type
TColor = -$7FFFFFFF-1..$7FFFFFFF;
Jest to wiΩc typ bΩd▒cy podzbiorem liczb typu
Integer. Aby zmie╢ciµ ca│y jego zakres, potrzebne
s▒ 32 bity! Nie s▒ co prawda wszystkie
wykorzystane, ale co z tego? Typ TColor u┐ywany
jest w wielu miejscach Delphi. A skoro zajmuje 32
bity, mamy nastΩpne zagadnienie, kt≤re kwalifikuje
siΩ do wykorzystania element≤w maj▒cych za cel
przechowywanie 32-bitowych liczb. Je╢li taka liczba
zdefiniowana jest jako typu Integer, nie potrzeba ┐adnego
rzutowania typ≤w ani konwersji. Mo┐na wszystko za│atwiµ
za pomoc▒ zwyk│ego przypisania.
Liczba_Integer := Button.Font.Color;
Bajty i bity
Dotychczas u┐ywali╢my liczby 32-bitowej traktuj▒c
j▒ jako ca│o╢µ. Wynika│o to z jednej z uwag
wspomnianych we wstΩpie – liczby 1 i
2-bajtowe i tak przechowywane s▒ w 4 bajtach. Ale
co, je╢li do dyspozycji mamy wiele liczb
32-bitowych? Powiedzmy: po jednej dla ka┐dego
elementu, z kt≤rym skojarzone dane musimy gdzie╢
przechowywaµ. Czy nie op│aca│oby siΩ wykorzystaµ
z tych 32 bit≤w osobno pojedynczych s│≤w (po 2
bajty), bajt≤w, a nawet bit≤w? Okazuje siΩ, ┐e
tak. Powiem wiΩcej: sam Windows API stosuje tak▒
technikΩ. Za przyk│ad niech pos│u┐y jedna z
najprostszych funkcji: MessageBox. Jej nag│≤wek,
cytowany za Win32 Programmer’s Reference brzmi
nastΩpuj▒co:
int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message boxUINT uType // style of message box
);
Ostatni parametr jest typu UINT, kt≤rego
odpowiednikiem w Delphi jest m.in. LongWord. Podaje
siΩ w nim sumΩ warto╢ci, za kt≤re u┐ywa siΩ
sta│ych rozpoczynaj▒cych siΩ od MB_. W
rzeczywisto╢ci oznaczaj▒ one poszczeg≤lne bity
32-bitowej liczby. Bo gdyby tw≤rcy Windows chcieli
ka┐d▒ z takich flag odbieraµ w osobnym parametrze
tej funkcji typu BOOL (Boolean), to popatrz, jak
wielkie by│oby to marnotrawstwo! Za ka┐dym
parametrem u┐ywane 32 bity, wykorzystywany 1! A
tak: mamy jedn▒ liczbΩ 32-bitow▒. W parametrze
sumujemy sobie sta│e. Funkcja sprawdza, kt≤re z
bit≤w tego parametru s▒ ustawione i na tej
podstawie pokazuje odpowiednio wygl▒daj▒ce i
zachowuj▒ce siΩ okienko z komunikatem. Tak wiΩc u┐ywanie
32-bitowych liczb w roli flag ma g│Ωboki sens.
Teraz zastan≤wmy siΩ, jak mo┐na to zrealizowaµ.
Co prawda operacje na bitach to obszerny temat (byµ
mo┐e po╢wiΩcΩ mu osobny artyku│), ale
podstawowe sprawy postaram siΩ poni┐ej przedstawiµ.
Niech wiΩc Flaga bΩdzie zmienn▒ typu Cardinal.
var
Flaga: Cardinal;
Zdefiniujemy sobie sta│e:
const
mf_Flag1 = 1;
mf_Flag2 = 2;
mf_Flag3 = 4;
mf_Flag4 = 8;
itd... Teraz przypisujemy zmiennej warto╢µ tak▒,
┐eby ustawione by│y bity 2 i 4.
Flaga := mf_Flag2 + mf_Flag4;
lub
Flaga := mf_Flag2 or mf_Flag4;
W pierwszym przypadku wykonujemy arytmetyczn▒
operacjΩ dodawania liczb. W drugim bitow▒ operacjΩ
OR. Efekt jest jednak ten sam. Zmienna zawiera teraz
liczbΩ 10. Ale to jest ma│o wa┐ne. Jej znaczenie
staje siΩ bardziej jasne, je┐eli jej warto╢µ
przedstawimy w systemie binarnym. BΩdzie to
00001010. Czyli widzimy, ┐e ustawione s▒ bity 2 i
4. I o to chodzi│o!
Teraz sprawd╝my, kt≤re bity s▒ ustawione. Jak
to siΩ robi? Trzeba sobie „przefiltrowaµ”
zmienn▒ z kt≤r▒╢ ze sta│ych operacj▒ AND.
Wtedy wynikiem jest albo warto╢µ tej sta│ej (bit
by│ ustawiony), albo 0 (nie by│).
if (Flaga and mf_Flag2) = mf_Flag2 then
{ Co siΩ ma staµ, je╢li bit jest ustawiony };
»eby rozk│adaµ sobie liczbΩ 32-bitow▒ na
poszczeg≤lne s│owa, a p≤╝niej bajty u┐yj
wbudowanych funkcji High i Low. Przydatne mog▒ siΩ
te┐ okazaµ operatory shr i shl. Pozosta│e rzeczy
uda Ci siΩ zrobiµ za pomoc▒ ca│kowitych operacji
arytmetycznych, jak + lub * oraz logicznych, jak AND,
OR czy XOR.
Gdzie s▒ liczby 32-bitowe?
Du┐o dotychczas m≤wili╢my o tym, co mo┐na
przechowywaµ w „miejscach” na dane
zajmuj▒ce 32 bity. Teraz zastan≤wmy siΩ, gdzie
takie miejsca mo┐na znale╝µ. Wbrew pozorom takie
miejsca s▒ i wcale nie trzeba ich deklarowaµ we w│asnym
zakresie. Delphi zawiera wiele mechanizm≤w celowo
przewidzianych dla przechowywania tego typu danych.
Poni┐ej omawiamy niekt≤re z nich.
W│asne zmienne
Najbardziej oczywistym miejscem na przechowywanie
danych 32-bitowych jest oczywi╢cie zmienna. Tak▒
zmienn▒ mo┐esz sobie sam zadeklarowaµ. Czy to bΩdzie
zmienna globalna w programie, w module, w sekcji
interface czy implementation, czy te┐ zmienna
lokalna procedury, funkcji lub metody. W zmiennej
takiej przechowuje siΩ zwykle jedn▒ konkretn▒
rzecz. Dlatego nie jest potrzebne zwykle rzutowanie
typ≤w. Po prostu deklarujesz zmienn▒ takiego typu,
jakiego typu dane bΩdziesz w niej przechowywa│. Mo┐e
to byµ np. typ Integer lub Longint, Cardinal lub
LongWord, Pointer czy dowolny inny wska╝nik,
TObject lub dowolna inne klasa, TColor albo
jakikolwiek inny typ, z kt≤rego wyra┐enie
SizeOf(Nazwa_typu) zwraca liczbΩ 4 (4 bajty = 32
bity).
W│a╢ciwo╢µ TComponent.Tag
Byµ mo┐e zauwa┐y│e╢, ┐e wszystkie
komponenty, jakie stawiasz na formatce posiadaj▒ w│a╢ciwo╢µ
Tag. Jest ona typu Longint, a wiΩc jest to
32-bitowa liczba ca│kowita za znakiem. Je╢li
interesowa│e╢ siΩ tym bli┐ej, wiesz mo┐e, ┐e
nie jest i nie bΩdzie ona u┐ywana przez Delphi.
Jest po prostu 32-bitowe pole do wykorzystania dla
programisty w dowolny spos≤b.
Do czego takie pole w komponencie mog│oby siΩ
przydaµ? Przyznam Ci siΩ szczerze, ┐e sam u┐y│em
go tylko 2 razy w ┐yciu. W obydwu przypadkach
chodzi│o o Timer. Po prostu ka┐dy cykl Timera mia│
inkrementowaµ pewien licznik. Licznik ten mog│em
oczywi╢cie zdefiniowaµ jako zmienn▒. Logiczne
wyda│o mi siΩ jednak u┐ycie w tej roli w│a╢ciwo╢ci
Tag Timera, jako, ┐e ten licznik niejako do niego
nale┐a│, by│ z nim w jaki╢ spos≤b zwi▒zany.
W│a╢ciwo╢µ Tag nie jest jednak tak bezu┐yteczna,
jak mog│oby siΩ wydawaµ. Wyobra╝ sobie: piszesz
program, w kt≤rym u┐ytkownik mo┐e dowolnie
konfigurowaµ, jakie przyciski maj▒ siΩ znale╝µ
na pasku narzΩdzi. Z tego wniosek – przyciski
te musz▒ byµ tworzone dynamicznie w czasie dzia│ania
programu. I tutaj pojawia siΩ kilka problem≤w.
Zdarzeniu OnClick ka┐dego z tych przycisk≤w bΩdzie
podczas jego tworzenia przypisywany adres procedury,
kt≤r▒ wcze╢niej zdefiniowa│e╢ w kodzie. Sk▒d
ona ma wiedzieµ, z kt≤rego przycisku pochodzi
zdarzenie? Rozwi▒zaniem jest tutaj w│a╢nie w│a╢ciwo╢µ
Tag. Dla przyk│adu: bΩdziesz przechowywa│ w jakie╢
tablicy czy innej strukturze danych informacje o ka┐dym
z tych przycisk≤w. Podczas dynamicznego ich
tworzenia w│a╢ciwo╢ci Tag ka┐dego z przycisk≤w
przypiszesz numer (indeks) rekordu, kt≤ry opisuje
ten przycisk b▒d╝ bezpo╢rednio wska╝nik do
takiego rekordu. Procedura obs│ugi zdarzenia
OnClick zawsze otrzymuje w parametrze Sender wska╝nik
do obiektu, na rzecz kt≤rego zosta│a wywo│ana.
Typ tego wska╝nika to TObject. Je┐eli jednak masz
pewno╢µ, ┐e procedura obs│uguje wy│▒cznie
zdarzenia przycisk≤w TToolButton, mo┐esz bez
sprawdzania, czy
(Sender is TToolButton)
za│o┐yµ, ┐e tak jest w i zawsze traktowaµ
Sender jako wska╝nik na instancjΩ obiektu klasy
TToolButton. Sprawdzasz wiΩc warto╢µ Tag takiego
przycisku i ju┐ wiesz, o kt≤ry przycisk chodzi, a
co za tym idzie wiesz, kt≤re polecenie trzeba wywo│aµ
w reakcji na jego klikniΩcie.
procedure Button1Click(Sender: TObject);
begin
MojaZmienna := (Sender as TToolButton).Tag;
if MojaZmienna = ... then ...
end;
W│a╢ciwo╢µ Data w TListItem i TTreeNode
Czym s▒ windowsowe kontrolki ListView i TreeView,
nie trudno siΩ dowiedzieµ. Wystarczy w│▒czyµ
Eksplorator Windows. Drzewo folder≤w po lewej
stronie to TreeView, czyli widok drzewa. Lista
folder≤w i plik≤w po prawej stronie to ListView,
czyli widok listy. Ten ostatni mo┐e byµ w jednym z
4 stan≤w: du┐e ikony, ma│e ikony, lista lub
szczeg≤│y. Element≤w ka┐dego z tych widok≤w nie
da siΩ przechowywaµ w zwyk│ych stringlistach, jak
to robi m.in. ListBox. Tutaj ka┐dy element
opisywany jest przez poka╝ny zesp≤│ cech, jakie
posiada. Dlatego stworzono specjalne klasy
reprezentuj▒ce te elementy. S▒ to odpowiednio
TlistItem dla elementu widoku listy i TtreeNode dla
elementu widoku drzewa. ú▒czy je jedna cecha:
obydwie posiadaj▒ w│a╢ciwo╢µ Data typu Pointer.
Jest to miejsce na 32-bitow▒ dan▒ do dowolnego
wykorzystania dla programisty. Jednym s│owem: je╢li
chcesz razem z ka┐dym elementem listy lub drzewa
przechowywaµ jak▒╢ dan▒, kt≤ra mie╢ci siΩ w
32 bitach, zapisz j▒, po wykonaniu koniecznego
rzutowania typ≤w, w Data. Je╢li dane te nie
mieszcz▒ siΩ w 32 bitach, stw≤rz w│asny typ
rekordowy oraz wska╝nikowy do niego lub w│asn▒
klasΩ. Potem bΩdziesz dla ka┐dego elementu
alokowa│ dynamicznie w pamiΩci nowy egzemplarz
tego typu, a w pole Data wpisywa│ wska╝nik do
niego.
W│a╢ciwo╢µ Objects w TStrings
KlasΩ TStrings i jej pochodn▒ –
TStringList zna chyba ka┐dy, choµ mo┐e nie ka┐dy
zdaje sobie z tego sprawΩ. To w│a╢nie w tej
klasie, przeznaczonej do przechowywania
wielolinijkowego tekstu, zapisywana jest zawarto╢µ
komponent≤w Memo, ListBox, CheckListBox i wielu
innych. Tak wiΩc Memo1.Lines, ListBox1.Items,
CheckListBox1.Items, ListView1.Items[x].SubStrings
to w│a╢nie pola typu TStrings. To z nich mo┐esz
odczytywaµ i do nich zapisywaµ ca│y tekst,
poszczeg≤lne linijki lub nawet pojedyncze znaki.
Klasa TStringList, bΩd▒ca potomkiem TStrings,
przeznaczona jest do wykorzystania przez programistΩ
we w│asnym zakresie – programista musi sam
zadeklarowaµ i utworzyµ obiekt tej klasy, a
informacje tekstowe w nim przechowywane nie s▒
nigdzie prezentowane w spos≤b automatyczny –
programista mo┐e je wykorzystaµ w dowolny spos≤b.
var
SL: TStringList;
begin
SL := TStringList.Create;
SL.Add('1 linijka');
Nie ka┐dy jednak wie, ┐e z ka┐d▒ z linijek
tekstu skojarzona jest... 32-bitowa liczba! Tak
jest! Przy ka┐dej linijce tekstu mo┐esz
przechowywaµ sobie dowoln▒ 32-bitow▒ informacjΩ
– np. o kolorze, jaki ma mieµ dana linijka
tekstu, jak▒╢ liczbΩ czy te┐ wska╝nik. Liczby
te s▒ typu TObject, ale mo┐na je wykorzystaµ w
dowolny spos≤b. Aby dodaµ now▒ linijkΩ od razu
przypisuj▒c skojarzon▒ z ni▒ liczbΩ, zamiast Add
u┐yj metody AddObject:
SL.AddObject('1 linijka', TObject(x));
DostΩp do tych liczb masz w obie strony (zapis /
odczyt) za pomoc▒ tablicowej w│a╢ciwo╢ci Objects.
Je╢li chcesz teraz zmieniµ warto╢µ liczby
skojarzonej z 1 linijk▒ tekstu, u┐yj instrukcji:
SL.Objects[0] := TObject(y);
Jakie jest praktyczne wykorzystanie klasy
TStringList? Okazuje siΩ, ┐e przeogromne! Je╢li
masz przechowywaµ listΩ rekord≤w, a ka┐dy z tych
rekord≤w zawiera jedno pole typu │a±cuchowego
– StringList jest jak znalaz│! Deklarujesz w│asny
rekord i wska╝nik do niego lub klasΩ, kt≤ra bΩdzie
przechowywa│a wszystkie pozosta│e pola jednego
rekordu. Wszystkie opr≤cz wspomnianego │a±cucha.
NastΩpnie dla ka┐dego nowo tworzonego rekordu:
tworzysz dynamicznie w pamiΩci nowy egzemplarz
danego typu rekordowego lub klasy, wype│niasz go
danymi, do StringListy dodajesz │a±cuch
skojarzonej z ni▒ liczbie przypisuj▒c jednocze╢nie
skojarzonej z ni▒ liczbie wska╝nik do nowo
zaalokowanej pamiΩci.
Wykorzystanie StringList≤w mo┐na te┐ stosowaµ
w bardziej zaawansowany spos≤b i w po│▒czeniu np.
z innymi stringlistami lub np. z typem TList. Przyk│ad:
masz stworzyµ strukturΩ danych, kt≤rej rekord sk│ada
siΩ z dw≤ch │a±cuch≤w. Nic prostszego!
Deklarujesz 2 StringListy. Dla ka┐dego tworzonego
rekordu dodajesz do ka┐dego StringLista odpowiedni
│a±cuch. Usuwaj▒c za╢ – usuwasz │a±cuch
o danej pozycji r≤wnocze╢nie z obu list. W ten oto
spos≤b obydwie listy przechowuj▒ zawsze tΩ sam▒
liczbΩ element≤w, a ich pozycjΩ o tych samych
indeksach przechowuj▒ po jednym polu z jednego
rekordu.
Klasa TList
Jest ma│o znana. Na czym polega jej dzia│anie?
Na pocz▒tek mo┐e napiszΩ, ┐e jest do
wykorzystania dla programisty – trzeba j▒
sobie zadeklarowaµ i stworzyµ we w│asnym zakresie
podobnie, jak TStringList.
var
L: TList;
begin
L := TList.Create;
TList jak sama nazwa wskazuje, to taki
TStringList bez string≤w. A je╢li z klasy
przeznaczonej do przechowywania listy │a±cuch≤w
odejmiemy │a±cuchy, to co nam zostanie? Nic? Nie!
PamiΩtasz? StringList potrafi przechowywaµ
skojarzon▒ z ka┐dym │a±cuchem liczbΩ 32-bitow▒.
Czyli? Klasa TList to klasa przeznaczona do
przechowywania listy liczb 32-bitowych!
Jak to wykorzystaµ? Na przyk│ad: je╢li masz
przechowywaµ listΩ jaki╢ liczb, indeks≤w, kolor≤w
itp. A w praktyce, wykorzystuj▒c te liczby w roli
wska╝nik≤w, mo┐esz pomagaj▒c sobie klas▒ TList
tworzyµ dowolne listowe struktury danych. Po
prostu: deklarujesz typ rekordowy lub klasΩ.
Tworzysz w pamiΩci egzemplarze tej klasy czy tego
rekordu, a na li╢cie przechowujesz wska╝niki do
niego. Prawda, ┐e przydatne? A jakie uniwersalne!
Tylko nie zapomnij przed usuniΩciem pozycji z listy
zwolniµ pamiΩµ zajmowan▒ przez strukturΩ, do kt≤rej
ta pozycja przechowuje wska╝nik!
Bezpiecze±stwo
Podczas przeprowadzania opisywanych wy┐ej
operacji mo┐e wyst▒piµ wiele b│Ωd≤w i
sytuacji, kt≤re z pewno╢ci▒ nie s▒ po┐▒dane
przez programistΩ. W rozdziale tym poruszΩ tak┐e
temat warto╢ci „pustych”, ich
znaczenie, realizacjΩ w praktyce oraz bezpieczne
wykorzystanie.
Warto╢ci „puste”
Przechowuj▒c w miejscu na 32-bitow▒ liczbΩ
dane jakiego╢ rodzaju czΩsto przychodzi ustaliµ
pewn▒ warto╢µ, kt≤r▒ bΩdzie oznacza│a warto╢µ
„pust▒”. W przypadku liczby mo┐e to byµ
np. 0. W przypadku przechowywania indeksu, gdzie
elementy liczone s▒ od 0, za warto╢µ tak▒
przyjmuje siΩ zwykle –1. Dla wska╝nik≤w
jest to nil.
Bezpiecze±stwo warto╢ci pustych
U┐ywaj▒c warto╢ci pustych (ich u┐ycie nie
zawsze jest konieczne) poci▒ga za sob▒ konieczno╢µ
„pilnowania” – czΩstego
sprawdzania przed u┐yciem, czy dana warto╢µ nie
jest pusta. Je╢li np. chcesz zaindeksowaµ
StringList indeksem odczytanym z takiego miejsca, a
zapisana tam warto╢µ jest „pusta”
– wynosi –1 – bΩdzie b│▒d
„List index out of bounds (-1)”. Dlatego
zawsze sprawdzaj przed u┐yciem, czy warto╢µ nie
jest pusta, je╢li nie masz co do tego pewno╢ci.
Podobnie ze wska╝nikami – zawsze
sprawdzaj, czy nie wynosz▒ one nil. W przeciwnym
przypadku bΩdzie „Access Violation”, a
tego typu b│Ωdy s▒ wyj▒tkowo wredne i trudne do
zlokalizowania. Dlatego trzeba, szczeg≤lnie przy
operacjach na wska╝nikach, zapobiegaµ im
najlepiej, jak tylko siΩ da.
Bezpiecze±stwo wska╝nik≤w
Jak ju┐ wy┐ej wspomnia│em, wska╝niki to
bardzo b│Ωdogenna dziedzina programowania. Dlatego
podczas operowania na nich trzeba wyj▒tkowo uwa┐aµ.
Oto kilka wskaz≤wek dotycz▒cych wska╝nik≤w:
- Zawsze przed odwo│aniem siΩ do miejsca w
pamiΩci, na kt≤re wska╝nik wskazuje
sprawdzaj, czy jego warto╢µ nie wynosi nil.
- Je╢li zwalniasz pamiΩµ, wszystkim wska╝nikom,
kt≤re na ten obszar wskazywa│y przypisz warto╢µ
nil. W przeciwnym wypadku bΩd▒ one skierowane
na przypadkowy blok pamiΩci.
- Usuwaj▒c element przechowuj▒cy wska╝nik do
jakiej╢ struktury w pamiΩci nie zapomnij
uprzednio zwolniµ tej pamiΩci.
Oczywi╢cie nie zawsze trzeba stosowaµ wszystkie
wymienione tutaj zalecenia. Zale┐y to od
konkretnego przypadku.
Og≤lnie nieprawid│owo╢ci w operacjach na wska╝nikach
mo┐na podzieliµ na dwie kategorie:
- Zwolnienie pamiΩci tam, gdzie nie trzeba i
wtedy, kiedy nie potrzeba. Potem program bΩdzie
siΩ pr≤bowa│ odwo│aµ do ju┐ nieistniej▒cej
struktury i powstanie b│▒d.
- Niezwolnienie pamiΩci tam, gdzie jest to
konieczne. PamiΩµ pozostanie zaalokowana, a
ty, usuwaj▒c wszystkie wska╝niki do niej,
stracisz mo┐liwo╢µ jakiegokolwiek z ni▒
kontaktu. PamiΩµ siΩ zmarnuje.
Bezpiecze±stwo list
Je╢li przechowujesz jakie╢ elementy na li╢cie
np. TStrings, TStringList czy TList, pamiΩtaj o
kilku rzeczach.
Zawsze uwzglΩdnij, co bΩdzie, je╢li lista jest
pusta tzn. zawiera 0 element≤w. Nie musisz tego
robiµ jedynie, je╢li stosujesz pΩtlΩ for:
for I := 0 to SL.Count-1 do ...
Je╢li lista jest pusta, SL.Count = 0. Z tego
wniosek, ┐e SL.Count-1 wynosi –1, co jest
mniejsze od warto╢ci pocz▒tkowej 0. W takim
przypadku pΩtla nie wykona siΩ ani raz.
Je╢li masz 2 lub wiΩcej list przechowuj▒cych
pola wsp≤lnej struktury danych, zawsze pamiΩtaj,
aby usuwaµ i dodawaµ elementy do wszystkich na
raz. DziΩki temu wszystkie bΩd▒ zawsze
przechowywa│y tak▒ sam▒ ilo╢µ element≤w, a
elementy o konkretnym indeksie ka┐dej z list bΩd▒
zawiera│y pola jednego rekordu.
Je╢li integrujesz po wszystkich elementach listy
usuwaj▒c przy okazji niekt≤re z nich, zawsze r≤b
to od ko±ca. Je╢li bowiem napiszesz tak:
{ ¼LE!!! }
for I := 0 to SL.Count-1 do
if SL[I] = x then SL.Delete(I);
Zastan≤w siΩ, co bΩdzie w nastΩpuj▒cej
sytuacji: Lista liczy sobie 3 elementy o indeksach,
wiadomo, 0, 1 i 2. PΩtla ma siΩ wiΩc wykonaµ w 3
krokach, w kt≤rych zmienna steruj▒ca I przybiera│a
bΩdzie kolejno takie w│a╢nie warto╢ci. Oto
dochodzi do pierwszego elementu – indeks 0.
Program zostawia go w spokoju. Idzie do drugiego.
Ten trzeba usun▒µ. Robi wiΩc to. Od tej chwili
lista liczy ju┐ tylko 2 elementy: pierwszy 0 i
drugi 1, przesuniΩty w d≤│ po usuniΩciu ╢rodkowego.
PΩtla natomiast bΩdzie chcia│a uzyskaµ dostΩp
do elementu 3 (indeks 2) i... „List index out
of bounds (2)”! Dlatego prawid│owo bΩdzie:
{ DOBRZE }
for I := SL.Count-1 downto 0 do
if SL[I] = x then SL.Delete(I);
Zako±czenie
ZdajΩ sobie sprawΩ, ┐e opisany tu przeze mnie
temat nie jest jaki╢ wyj▒tkowo trudny ani wa┐ny.
Ma jednak jedn▒ szczeg≤ln▒ cechΩ. Mianowicie
takiego zestawienia nie spotkasz w ┐adnej ksi▒┐ce
czy innej dokumentacji. Ja przynajmniej do tej pory
nie spotka│em. Zawarte tu informacje w ma│ej czΩ╢ci
opar│em na jaki╢ konkretnych ╝r≤d│ach, za to w
decyduj▒cej wiΩkszo╢ci na w│asnych do╢wiadczeniach
w 4-letniej praktyce programistycznej. Dlatego
prezentowany poni┐ej spis literatury i dokumentacji
nale┐y traktowaµ mniej jako dokumenty, z kt≤rych
korzysta│em podczas pisania tego artyku│u, a
bardziej jako dokumenty, kt≤rych przeczytanie lub z
kt≤rych korzystanie polecam Tobie, drogi
czytelniku. Mam nadziejΩ, ┐e zaprezentowane tu
przeze mnie informacje pomog▒ wielu „tw≤rcom
program≤w”, kt≤rzy potrafi▒ tylko postawiµ
komponenty na formatce, staµ siΩ prawdziwymi
programistami.
Literatura:
- MSDN Library – January 2000.
- Stephens Rod: Algorytmy i struktury danych z
przyk│adami w Delphi. Helion, 2000.
- Win32 Programmer's Reference.
- Wr≤blewski Piotr: Algorytmy, struktury danych
i techniki programowania. Helion, 1997.
Adam Sawicki
sawickiap@poczta.onet.pl
|