Rozdział 2. Język Object Pascal
Adam Boduch
Rozdział ten stanowi wstęp do programowania w Delphi. Poznanie zasad działania języka Object Pascal jest niezbędnym warunkiem kontynuowania nauki środowiska Delphi. Wszystkie informacje postaram się przekazać dokładnie, przedstawiając je krok po kroku. Zacznę oczywiście od spraw podstawowych.
1 Plik źródłowy projektu
1.1 Najprostszy program
2 Podstawowa składnia
2.2 Wielkość liter
2.3 Pamiętaj o średniku!
2.4 Bloki begin i end
2.5 Dyrektywa Program
3 Komentarze
4 Zmienne
4.6 Deklaracja zmiennych
4.7 Typy zmiennych
4.8 Deklaracja kilku zmiennych
4.9 Przydział danych
W tym rozdziale:
- poznasz podstawową składnię języka Object Pascal;
- nauczysz się pisać programy konsolowe;
- poznasz takie niezbędne pojęcia, jak pętle, instrukcje warunkowe czy zmienne.
Plik źródłowy projektu
Z menu File wybierz polecenie New/Application, co spowoduje utworzenie nowego projektu — powinieneś pamiętać to z poprzedniego rozdziału.
W rozdziale tym porzucimy na chwilę projektowanie obiektowe (wizualne), nie będzie tu więc przykładów wykorzystania komponentów — tym zajmiemy się w rozdziale trzecim.
Z menu File wybierz polecenie Close — zostaniesz zapytany, czy nie chcesz zapisać zmian w pliku Unit1.pas. Kliknij przycisk No
— nie chcemy zapisywać pliku formularza. W tym momencie Edytor kodu powinien zostać zamknięty. Z menu Project wybierz View Source. Polecenie to spowoduje wyświetlenie w Edytorze kodu zawartości pliku głównego — *.dpr
.
Zapisz projekt pod nazwą dprFile. Zauważ, że tym razem nie zostałeś zapytany o nazwę formularza, gdyż wcześniej zamknęliśmy go.
Zawartość pliku DPR przedstawiona jest w listingu 2.1.
Listing 2.1. Zawartość pliku DPR
program dprFile;
uses
Forms;
{$R *.res}
begin
Application.Initialize;
Application.Run;
end.
Kod przedstawiony powyżej jest generowany automatycznie przez Delphi. Nie przejmuj się nim na razie — nie wszystko będzie nam potrzebne.
Najprostszy program
Rozłożymy zawartość pliku głównego na części, dzięki czemu będziesz miał możliwość dowiedzenia się, jakie funkcje pełnione są przez konkretne elementy kodu źródłowego.
Kod źródłowy najprostszego do napisania programu przedstawiony jest poniżej:
end.
To nie żart! Najprostszy program składa się właśnie z instrukcji end, z kropką na końcu. Możesz to sprawdzić — naciśnij klawisz F9, uruchamiając w ten sposób program. Oczywiście żadne polecenia oprócz end nie są wpisane, dlatego program zaraz po uruchomieniu zostanie zamknięty.
Podstawowa składnia
Kod źródłowy musi składać się z określonych poleceń, zakończonych określonymi znakami. Nie można pozostawić w kodzie żadnego bałaganu — nawet pominięcie jednego znaku czy zwykła literówka mogą spowodować niemożliwość uruchomienia programu. Na szczęście Delphi dysponuje na tyle dobrym kompilatorem, że miejsce błędu zostanie wskazane, a problem opisany. Przykładowo brak słowa kluczowego end przy próbie kompilacji spowoduje wskazanie błędu: [Error] dprMin.dpr(3): Declaration expected but end of file found.
Zapamiętaj pierwszą zasadę: program musi być zawsze zakończony poleceniem end.
(kropka na końcu!).
Wielkość liter
W języku Object Pascal — w odróżnieniu od C++ czy Javy — wielkość liter nie jest istotna. Dla kompilatora nie jest istotne, czy nazwa funkcji będzie pisana w taki czy inny sposób. Na przykład polecenie ShowMessage będzie znaczyło to samo, co showmessage. Można je także zapisywać w inny sposób:
showMessage showMEssaGe itd....
Nie jest zalecane pisanie kodu z wykorzystaniem jedynie małych liter - np. showmessage
. Wielu początkujących programistów, zafascynowanych nauką Delphi, nie pamięta o sposobie pisania kodu — projektanci ci nie stosują wcięć w kodzie, a wszystkie polecenia piszą małymi literami. Uwierz mi, że właśnie po sposobie pisania kodu można rozpoznać początkującego programistę — ci bardziej zaawansowani przyjęli takie zasady tworzenia kodu, aby był on bardziej przejrzysty. Z moją propozycją pisania kodu możesz zapoznać się w dodatku A.
Pamiętaj o średniku!
Zapamiętaj, że każda instrukcja w Delphi musi być zakończona znakiem średnika (;). Jest to informacja dla kompilatora, że w tym miejscu kończy się jedna instrukcja. Znak średnika jako symbol zakończenia instrukcji obowiązuje w większości języków programowania (Java, C/C++, Delphi, PHP).
Oczywiście istnieją pewne wyjątki od tej reguły. Na samym początku tego rozdziału zaprezentowałem Ci najprostszy program, który zakończony był znakiem kropki, a nie średnika!
Bloki begin i end
Właściwy kod programu zawsze wpisywany jest pomiędzy instrukcje Begin i End.
Słowo end
może oznaczać zarówno zakończenie jakiegoś bloku instrukcji, jak i zakończenie programu. W pierwszym przypadku na końcu tego słowa stawiamy znak średnika, a w drugim przypadku — znak kropki.
Program podczas uruchamiania zawsze „czyta” instrukcje rozpoczynające się od słowa begin — jest to jakby początek programu i właściwych poleceń, które mają być wykonane po starcie aplikacji.
Pamiętaj, aby ilość instrukcji begin była równa ilości instrukcji end — w przeciwnym razie kompilator wyświetli błąd:
[Error] dprMin.dpr(9): 'END' expected but end of file found.
Taki kod jest jak najbardziej prawidłowy:
begin
begin
begin
end;
end;
end.
Natomiast brak jednego ze słów end spowoduje wyżej wspomniany błąd. Jeżeli natomiast zabraknie jednego ze słów begin, Delphi wyświetli błąd: [Error] dprMin.dpr(10): '.' expected but ';' found.
Dyrektywa Program
Typowy program powinien składać się z głównej dyrektywy program oraz słów Begin i End. Co prawda najprostszy program to jedynie słowo end
, ale w prawidłowo zaprojektowanej aplikacji powinien znajdować się także nagłówek program, identyfikujący jej nazwę.
Po utworzeniu projektu dyrektywa program zawiera jego nazwę. Przykładowo jeżeli plik główny projektu nosi nazwę project.dpr, to pierwszy wiersz owego pliku wygląda tak:
program project;
Dyrektywa powinna być oczywiście zakończona znakiem średnika.
Komentarze
Komentarze są chyba najprostszym elementem każdego języka programowania. Jest to blok tekstu, który nie jest interpretowany przez kompilator. W komentarzach możesz zawrzeć swoje przemyślenia oraz uwagi dotyczące kodu źródłowego.
program project;
begin
{ to jest komentarz }
end.
Typowy komentarz Delphi zawarty jest pomiędzy znakami {}. W edytorze kodu komentarz jest wyróżniony kursywą i kolorem ciemnoniebieskim.
Istnieje kilka typów komentarzy — np. komentarz jednowierszowy:
// to jest komentarz jednowierszowy
Wiele osób ten rodzaj komentarzy nazywa komentarzami w stylu C, gdyż został on zapożyczony z języka C. Jak sama nazwa wskazuje, komentarzem jest tylko jeden wiersz kodu źródłowego:
program project;
begin
// tu jest komentarz
a tu już nie ma komentarza
end.
Drugi wiersz w bloku begin nie jest komentarzem — podczas kompilacji Delphi wskaże błąd.
Istnieje jeszcze jeden typ komentarzy — najrzadziej używany przez programistów:
program project;
begin
(*
komentowany kod
*)
end.
Zaletą tego typu komentarza jest to, że może on w sobie zawierać również znaki {.
program project;
begin
(*
komentarz...
{ jakiś tekst }
*)
end.
Jak widzisz, taki sposób pozwala na umieszczanie komentarzy w komentarzu, ale zapewne rzadko będziesz z niego korzystał.
Zmienne
Czym byłby program, który nie mógłby zapisywać danych do pamięci komputera — Praktycznie każdy program podczas działania korzysta z pamięci, aby przechować różne dane, potrzebne do dalszego jego działania.
Zmienne to obszar w pamięci komputera, który służy do przechowywania danych tymczasowych (obecnych w pamięci do czasu wyłączenia programu), mających postać liczb, tekstu itp.
Deklaracja zmiennych
Przed przydzieleniem danych do pamięci zmienną należy zadeklarować w kodzie programu. Deklaracja zmiennej powinna być umieszczona przed blokiem begin
. Przykładowa deklaracja może wyglądać tak:
program varApp;
var
Zmienna : String;
begin
end.
W razie potrzeby zadeklarowania zmiennej konieczne jest zastosowanie słowa kluczowego Var (skrót od słowa variable — zmienna). Stanowi to informację dla kompilatora, że po tym słowie kluczowym zostanie umieszczona deklaracja zmiennych.
Zmienna zawsze musi mieć nazwę! Dzięki tej nazwie możemy łatwo odwołać się do poszczególnych danych zapisanych w pamięci. Pierwszym członem deklaracji zmiennej musi być unikalna nazwa (nie mogą istnieć dwie takie same zmienne w programie). Po znaku dwukropka należy wpisać typ zmiennej (o typach zmiennych powiem później).
Z punktu widzenia kompilatora nie ma znaczenia, w jaki sposób zapiszesz (zadeklarujesz) zmienną — może więc odbyć się to tak:
var
zmienna:string;
lub tak:
var zmienna: string;
Dla zachowania przejrzystości kodu zalecane jest jednak stosowanie deklaracji w formie przedstawionej w pierwszym przykładzie.
Typy zmiennych
Typy zmiennych określają rodzaj danych, który będzie zapisywany w pamięci. W poprzednim podpunkcie podczas deklarowania zmiennej skorzystałem z typu String. Ten typ danych służy do przechowywania tekstu i tylko tekstu! Tak więc chcąc w pamięci komputera umieścić np. liczbę, należy skorzystać z innego typu zmiennej.
Typy zmiennych przedstawiłem w tabeli 2.1. Oczywiście tych typów jest więcej, ale nie musisz znać ich wszystkich — wystarczą te podstawowe.
Tabela 2.1. Typy zmiennych w Object Pascalu
Nazwa | Opis |
---|---|
Integer | -2 147 483 648 — 2 147 483 647 |
Int64 | -263 — 263 - 1 |
SmallInt | -32 768 — 32 767 |
ShortInt | -128 — 127 |
Byte | 0 — 255 |
Word | 0 — 65 535 |
LongWord | 0 — 4 294 967 295 |
Char | pojedynczy znak |
Boolean | TRUE lub FALSE |
ShortString | 255 znaków |
AnsiString | 231 znaków |
String | 231 znaków |
Extended | 3,6 × 10-4951 — 1,1 × 104932 |
Double | 5,0 × 10-324 — 1,7 × 10308 |
Single | 1,5 × 10-45 — 3,4 × 1038 |
Currency | -922 337 203 685 477,5808 — 922 337 203 685 477,5807 |
Niektóre z tych typów służą do przechowywania tekstu, inne do przechowywania liczb. Różni je „pojemność”. Przykładowo chcąc zapisać w pamięci jakąś dużą liczbę, nie skorzystasz z typu Byte
, ponieważ do tego typu mogą być przypisywane jedynie wartości z zakresu od 0 do 255. Możesz za to skorzystać z typu Int64
.
Oprócz, jak to wcześniej nazwałem, „pojemności” powyższe typy danych różni także ilość zajmowanej pamięci operacyjnej. Przykładowo typ Byte
„zżera” jedynie 1 bajt pamięci, a typ Int64
— 8 bajtów. Możesz pomyśleć, że to nieduża różnica, ale jeśli zmiennych 8 —bajtowych jest kilkadziesiąt (kilkaset?)— Jest to zwykłe marnowanie pamięci!
Podczas czytania tej książki oraz podczas przeglądania różnych kodów źródłowych możesz zauważyć, że dla typów liczbowych programiści często stosują zmienną Integer. Jest to jakby uniwersalny typ zmiennej liczbowej, gdyż jej zakres jest w miarę duży, a nie wykorzystuje ona aż tak dużo pamięci.
Deklaracja kilku zmiennych
Po wpisaniu słowa kluczowego var
możesz zadeklarować tyle zmiennych, ile będzie Ci potrzebne — nie musisz za każdym razem używać dyrektywy var
.
program varApp;
var
Zmienna1 : String;
Zmienna2 : String;
Zmienna3 : String;
begin
end.
W powyższym przypadku zadeklarowałeś trzy zmienne typu String
. Od tej pory dla kompilatora słowa Zmienna1, Zmienna2, Zmienna3 nie są już konstrukcjami nieznanymi — wiadome będzie, że w tym przypadku chodzi o zmienne.
Podczas deklaracji kilku zmiennych tego samego typu można wpisać wszystkie zmienne razem, oddzielając ich nazwy przecinkami:
program varApp;
var
Zmienna1, Zmienna2, Zmienna3 : String;
begin
end.
Z punktu widzenia kompilatora w tym wypadku również następuje deklaracja trzech zmiennych typu String. Chcąc jeszcze zadeklarować zmienne innego typu, należy to zrobić w ten sposób:
program varApp;
var
Zmienna1, Zmienna2, Zmienna3 : String;
Liczba1, Liczba2 : Integer;
begin
end.
Przydział danych
Przydzielenie danych dla zmiennej musi odbyć się w bloku Begin. Istnieje jednak możliwość przydzielenia danych w trakcie pisania programu.
Przydział statyczny
W celu określenia wartości dla konkretnej zmiennej należy to zrobić podczas jej deklarowania, używając w tym celu znaku równości (`=`).
```delphi
program varApp;
var
Zmienna1 : String = 'Oto zmienna nr 1';
begin
end.
```
Taki kod spowoduje, że na samym starcie programu zmienna <var>Zmienna1 </var>będzie miała wartość `Oto zmienna nr 1`.
<dfn>
Każdy tekst zadeklarowany w ramach zmiennej musi być ujęty w znaki apostrofów.</dfn>
Podczas pisania programu nie możesz przydzielić wartości kilku zmiennym naraz:
```delphi
program varApp;
var
Zmienna1, Zmienna2 : String = 'Oto zmienna nr 1';
begin
end.
```
Próba uruchomienia takiego programu spowoduje wyświetlenie błędu: <i>[Error] varApp.dpr(4): Cannot initialize multiple variables</i>.
```delphi
program varApp;
var
Zmienna1 : String = 'Oto zmienna nr 1';
Zmienna2 : String = 'Oto zmienna nr 2';
begin
end.
```
Natomiast kod przedstawiony powyżej będzie już całkiem prawidłowy.
<dfn>Przydział wartości dla zmiennej podczas pisania kodu często nazywany jest przydziałem domyślnym.
Jeżeli spróbujesz uruchomić program, a kompilator znajdzie zmienną, której nie przypisałeś żadnej wartości, zostanie wyświetlone ostrzeżenie: [Hint] varApp.dpr(4): Variable 'Zmienna1' is declared but never used in 'varApp'.</dfn>
### Przydział dynamiczny
Możliwa jest także zmiana zawartości danej zmiennej podczas pracy programu. Jest to czynność stosunkowo prosta — polega ona na zastosowaniu znaku :=, tzw. operatora przydziału. Oto przykład:
```delphi
program varApp;
var
Zmienna1 : String;
Zmienna2 : String;
begin
Zmienna1 := 'Oto jest zmienna nr 1';
Zmienna2 := 'Oto jest zmienna nr 2';
end.
```
Oczywiście nic się nie stanie jeżeli ponownie zmienisz wartość już raz deklarowanej zmiennej.
Stałe
===
Podobnie jak zmienne, stałe również służą do przechowywania jakichś danych podczas działania aplikacji. Jest jednak pomiędzy nimi jedna różnica — stałe, jak sama nazwa wskazuje, nie mogą podlegać modyfikacji podczas działania programu. Czyli wartość stałych jest określana już podczas pisania programu:
```delphi
program varConst;
const
Stala1 = 'Oto jest stała...';
begin
end.
```
Stałe, w odróżnieniu od zmiennych, deklarujemy z użyciem słowa kluczowego [[Delphi/const]]. Jak widzisz, nie deklarujemy także typu zmiennej — typ jest określany automatycznie na podstawie wartości.
Domyślne typy stałych
--------------------------
Jeżeli przykładowo przypisujesz stałej jakąś liczbę:
```delphi
const
Stala2 = 12;
```
Delphi uzna, że stała jest stałą typu [[Delphi/Integer]] (jest to domyślny typ stałych). Programista może to w dość prosty sposób zmienić:
```delphi
program varConst;
const
Stala1 = 'Oto jest stała...';
Stala2 : Byte = 12;
begin
end.
```
A zatem w tym przypadku `Stala2 `będzie stałą typu [[Delphi/Byte]] o wartości 12.
Jeżeli spróbujesz przypisać jakąś wartość stałej — przykładowo:
```delphi
begin
Stala1 := 'Inna wartość';
end.
```
Delphi uzna to za błąd i wyświetli podpowiedź dla Ciebie: <i>[Error] varConst.dpr(8): Left side cannot be assigned to.</i>
Używanie stałych i zmiennych w programie
===========================
Jeżeli potrafisz już deklarować stałe i zmienne, należy z nich wreszcie skorzystać. Przy tej okazji poznasz pierwsze polecenie — [[Delphi/ShowMessage]]. Jego użycie spowoduje wyświetlenie okienka informacyjnego. Z polecenia tego korzysta się w następujący sposób:
```delphi
program varConst;
uses Dialogs; // włączanie modułu do programu — tym zajmiemy się później
begin
ShowMessage('To jest tekst w okienku!');
end.
```
Program rozpoczynamy pewnym słowem kluczowym — [[Delphi/uses]]. Nie zawracaj sobie jednak tym głowy — zajmiemy się tą kwestią w dalszej części rozdziału. Najważniejsze jest polecenie `ShowMessage`. W nawiasie oraz w apostrofach wpisujemy tekst, który ma być wyświetlony w oknie. Uruchom teraz program, naciskając klawisz F9 — rezultat działania przedstawiony jest na rysunku 2.1.
![2.1.jpg](//static.4programmers.net/uploads/attachment/3383107724cfe51d46aa9b.jpg)
Rysunek 2.1. Rezultat działania programu
Zamiast tekstu w apostrofach możesz wpisać nazwę zmiennej — tym sposobem program podczas działania podstawi na to miejsce zawartość zmiennej. Tak samo ma się sprawa ze stałymi — oto przykład:
```delphi
program varConst;
uses Dialogs; // włączanie modułu do programu — tym zajmiemy się później
const
Stala1 = 'Oto jest stała...';
var
Zmienna1 : String;
begin
Zmienna1 := 'Tekst umieszczony w okienku!';
ShowMessage(Zmienna1);
ShowMessage(Stala1);
end.
```
Na samym początku w bloku [[Delphi/begin]] następuje przypisanie danych zmiennej, a kolejnym krokiem jest wyświetlenie jej zawartości; następne okienko wyświetli natomiast zawartość stałej.
[[Delphi/Tablice|Tablice danych]]
=========
Wyobraź sobie przypadek, gdy w programie musisz użyć wielu, naprawdę wielu zmiennych. Czy wygodne jest w takim przypadku deklarowanie tylu zmiennych, z inną nazwą dla każdej— Do tego właśnie służą tablice. Tablice deklarowane są za pomocą słowa kluczowego array.
```delphi
program arrayApp;
uses
Dialogs;
var
Tablica : array[0..1] of String;
begin
end.
```
Konstrukcja tablic jest dość specyficzna. Po słowie kluczowym [[Delphi/array]] w nawiasach kwadratowych należy wpisać ilość elementów, z których składać się będzie tablica.
Nazwa_Tablicy : array[Najmniejszy_Indeks..Największy_Indeks] of Typ_danych;
W powyższym przypadku najmniejszym indeksem jest 0, a największym — 1. Z tego powodu tablica składać się będzie z dwóch elementów (zero jest także liczone jako jeden element).
Przydział danych odbywa się także z zastosowaniem nawiasów kwadratowych:
```delphi
program arrayApp;
var
Tablica : array[0..1] of String;
begin
Tablica[0] := 'Pierwszy element tablicy';
Tablica[1] := 'Drugi element tablicy';
end.
```
Podsumujmy: z tablic korzysta się tak samo jak ze zmiennych. Jedyną różnicą jest to, że należy zawsze podawać numer indeksu, do którego chce się zapisać lub odczytać dane.
Tablice jako stałe
-------------------
Możliwe jest deklarowanie tablic jako stałych. Tak, jak w przypadku „zwykłych” stałych, dane także należy przypisać tablicy podczas projektowania aplikacji:
```delphi
program arrayConst;
const
Tablica : array[0..1] of String = (
('Pierwszy element'), ('Drugi element')
);
begin
end.
```
Także w tym przykładzie tablica składa się z dwóch elementów. Dodatkowe nawiasy zostały wprowadzone jedynie po to, aby zwiększyć czytelność — równie dobrze można by zapisać program w ten sposób:
```delphi
program arrayConst;
const
Tablica : array[0..1] of String = (
'Pierwszy element', 'Drugi element');
begin
end.
```
Obowiązkowy jest jedynie jeden nawias, w którym wypisujemy elementy tablicy, oddzielając je przecinkami.
Należy uważać na przydział danych — zgodnie z ilością elementów, jakie zostały zadeklarowane w kodzie. Przykładowy kod:
```delphi
program arrayConst;
const
Tablica : array[0..2] of String = (
'Pierwszy element', 'Drugi element');
begin
end.
```
nie będzie mógł zostać skompilowany — zadeklarowano trzy elementy, a dane przydzielono jedynie do dwóch. Delphi wyświetli błąd: <i>[Error] arrayConst.dpr(5): Number of elements differs from declaration.</i>
Tablice wielowymiarowe
---------------------------
Object Pascal umożliwia także deklarowanie tablic tzw. wielowymiarowych. Po zadeklarowaniu takich tablic do konkretnego elementu możemy odwołać się w następujący sposób:
```delphi
Tablica[0][0] := 'Przypisanie danych';
```
W powyższym przypadku skorzystałem jedynie z tablic dwuwymiarowych, których deklaracja wygląda tak:
```delphi
var
Tablica : array[0..1, 0..1] of String;
```
Deklaracja jest także specyficzna — polega bowiem na wypisywaniu ilości elementów w nawiasie kwadratowym, przy czym poszczególne elementy są oddzielone przecinkami. W przedstawionej wyżej deklaracji mamy aż 4 elementy! Przydział danych odbywa się w następujący sposób:
```delphi
program arrayarray;
var
Tablica : array[0..1, 0..1] of String;
begin
Tablica[0][0] := 'Element 1';
Tablica[0][1] := 'Element 2';
Tablica[1][0] := 'Element 3';
Tablica[1][1] := 'Element 4';
end.
```
Istotę działania tablic dwuwymiarowych lepiej zrozumiesz, przeglądając listing 2.2.
Listing 2.2. Program deklarujący dwuwymiarowe tablice
```delphi
program arrayarray;
var
Tablica : array[0..1, 0..2] of String;
begin
Tablica[0][0] := 'Fiat';
{ marka samochodu }
Tablica[0][1] := 'Uno';
Tablica[0][2] := 'Punto';
{ modele samochodu }
Tablica[1][0] := 'Audi';
Tablica[1][1] := 'A4';
Tablica[1][2] := 'A8';
end.
```
W tym przypadku nastąpiła deklaracja tablicy 2x3. Dwa główne elementy to element `Fiat `oraz element `Audi`. Kolejne dwa „podpola” określają modele samochodów.
Przedstawiając tablice wielowymiarowe, mówiłem tylko o dwóch wymiarach. Istnieje jednak możliwość zadeklarowania tablic, które będą miały wiele wymiarów.
```delphi
program arrayx3;
var
Tablica : array[0..1, 0..1, 0..1] of String;
begin
Tablica[0][0][0] := 'Wartość';
{ itd. }
end.
```
W tym przykładzie nasza tablica to tablica 3x2 typu [[Delphi/String]]. W jaki sposób dane są przydzielane do tej tablicy — Przykład znajduje się w powyższym kodzie źródłowym.
Tablice dynamiczne
-------------------------
Nieraz podczas pracy z Delphi w programie wymagane będzie zadeklarowania tablicy o niewiadomej liczbie elementów. Znaczy to, że programista w trakcie pisania programu nie jest w stanie określić, ile elementów tablicy będzie mu potrzebne. W tym celu w Delphi 4 zaimplementowano możliwość tworzenia tablic dynamicznych. Tablice dynamiczne deklaruje się bez podania ilości elementów:
```delphi
program dynArray;
var
Tablica : array of String;
begin
end.
```
Przy tej okazji poznasz nowe polecenie — [[Delphi/SetLength]]. Służy ono do określenia ilości elementów tablicy podczas działania programu. Pierwszym parametrem tego polecenia jest nazwa tablicy dynamicznej — drugi parametr to ilość elementów, z których tablica ma się składać. Parametry przekazywane do polecenia muszą być oddzielane przecinkami:
```delphi
program dynArray;
var
Tablica : array of String;
begin
SetLength(Tablica, 2);
end.
```
Od tej pory po uruchomieniu programu tablica składać się będzie z dwóch elementów. Wypełnianie elementów danymi odbywa się tak samo jak w przypadku zwykłych tablic:
```delphi
program dynArray;
var
Tablica : array of String;
begin
SetLength(Tablica, 2);
Tablica[0] := 'Wartość 1';
Tablica[1] := 'Wartość 2';
end.
```
Na poziomie tworzenia programu nie jest możliwe określenie przez kompilator, z ilu elementów ostatecznie będzie składać się tablica. Stąd próba odczytu elementu spoza tablicy może skończyć się komunikatem o błędzie!
### Polecenia Low i High
Oba polecenia — [[Delphi/Low]] i [[Delphi/High]] — mogą być użyte jedynie w połączeniu z tablicami. Warto je znać, gdyż czasem mogą się przydać. Zwracają one liczbę równą najmniejszemu (<i>Low</i>) oraz największemu (<i>High</i>) indeksowi tablicy.
Deklaracja tablicy może na przykład wyglądać w ten sposób:
```delphi
Tablica : array[10..100] of Integer;
```
Wywołanie polecenia `Low(Tablica)` spowoduje, że funkcja zwróci wartość 10. Natomiast funkcja `High` zwróci wartość 100.
Operatory
=======
W języku Object Pascal istnieje wiele operatorów. Dwa z nich zastosowałeś już wcześniej. Są to operatory przypisania (:=) i porównania (=). Operator porównania, jak zapewne zauważyłeś, jest także używany do przydzielania zmiennym i stałym domyślnych wartości.
Najprościej mówiąc, operatory to elementy (znaki) języka służące do manipulowania danymi. Istnieją operatory porównania, arytmetyczne i przypisania. Większość z nich zaprezentowałem w tabeli 2.2.
<div class="table">
<table>
<tbody>
<tr><th class="header">Operator</th><th class="header">Znaczenie</th></tr>
<tr><td class="row">=</td><td class="row">Porównanie — sprawdza, czy obie wartości są sobie równe</td></tr>
<tr><td class="row">:=</td><td class="row">Przypisanie danych — jeden z najważniejszych operatorów</td></tr>
<tr><td class="row"><></td><td class="row">Różne od — sprawdza, czy obie wartości są od siebie różne</td></tr>
<tr><td class="row">></td><td class="row">Większe od — sprawdza, czy jedna wartość z podanych zmiennych jest większa od drugiej</td></tr>
<tr><td class="row">></td><td class="row">Mniejsze od — sprawdza, czy jedna wartość jest mniejsza od drugiej</td></tr>
<tr><td class="row">>=</td><td class="row">Większe lub równe</td></tr>
<tr><td class="row"><=</td><td class="row">Mniejsze lub równe</td></tr>
<tr><td class="row">+</td><td class="row">Dodawanie</td></tr>
<tr><td class="row">-</td><td class="row">Odejmowanie</td></tr>
<tr><td class="row">*</td><td class="row">Mnożenie</td></tr>
<tr><td class="row">/</td><td class="row">Dzielenie</td></tr>
<tr><td class="row">div</td><td class="row">Dzielenie z obcięciem reszty</td></tr>
<tr><td class="row">mod</td><td class="row">Dzielenie z zachowaniem reszty z dzielenia</td></tr>
<tr><td class="row">and</td><td class="row">Porównywanie typów — logiczne i</td></tr>
<tr><td class="row">or</td><td class="row">Porównywanie typów — logiczne lub</td></tr>
<tr><td class="row">not</td><td class="row">Zaprzeczenie (stosowane podczas porównywania typów)</td></tr>
<tr><td class="row">xor</td><td class="row">Operator bitowy — dysjunkcja</td></tr>
<tr><td class="row">shl</td><td class="row">Operator bitowy — przesunięcie w lewo</td></tr>
<tr><td class="row">shr</td><td class="row">Operator bitowy — przesunięcie w prawo</td></tr>
</tbody>
</table>
</div>
Zastosowanie większości z tych operatorów poznasz w kolejnym podpunkcie — „Instrukcje warunkowe”. Oprócz wyżej podanych operatorów istnieją także funkcje [[Delphi/Inc]] oraz [[Delphi/Dec]]. Funkcje te służą odpowiednio do zwiększania i zmniejszania wartości parametru. Są one równoważne następującym instrukcjom:
```delphi
X := X + 1; // to samo co Inc(x)
Y := Y — 1; // to samo co Dec(Y)
```
Ponadto funkcje Inc i Dec posiadają opcjonalny parametr dodatkowy. Domyślnie bowiem wartość podanej zmiennej jest zwiększana lub zmniejszana o jeden. Drugi parametr decyduje o tym, czy ma to być wartość większa niż jeden — np.
```delphi
var
I : Integer;
begin
I := 1; // przydzielenie wartości początkowej
Inc(I, 2); // w tym momencie zmienna I posiada wartość 3
end.
```
Aplikacje konsolowe
=============
W dalszej części tego rozdziału korzystać będziemy z tzw. aplikacji konsolowych. Oczywiście będziemy się tym zajmować tylko w tym rozdziale — podczas czytania kolejnych korzystać już będziesz z głównych funkcji, jakie oferuje Delphi, czyli komponentów i formularzy. Na razie jednak, aby wszystko lepiej zrozumieć, proponuję Ci wykorzystanie w przykładach aplikacji konsolowych. Inaczej mówiąc, aplikacje konsolowe to programy, których rezultat wyświetlany jest w oknie trybu MS-DOS.
Aby program mógł działać w trybie konsolowym, nie trzeba wykonywać żadnych skomplikowanych czynności — wystarczy dodać do projektu taką oto dyrektywę:
`{$APPTYPE CONSOLE}`
Dyrektywa ta zawarta jest jednak w nawiasie klamrowym, czyli teoretycznie powinna być traktowana przez kompilator jako komentarz. Tak jednak nie jest, a to za sprawą znaku $ na samym początku. Przykładowy program może zatem wyglądać następująco:
```delphi
program appconsole;
{$APPTYPE CONSOLE}
begin
Readln;
end.
```
Instrukcja [[Delphi/Readln]] w bloku programu nakazuje czekanie na naciśnięcie przez użytkownika klawisza Enter. Dopiero po wykonaniu tej czynności program zostanie zamknięty.
Wpisywanie tekstu do okna konsoli odbywa się poprzez zastosowanie polecenia Writeln lub Write. W tym pierwszym na końcu dodawany zostaje znak nowej linii, natomiast w drugim — nie.
```delphi
program appconsole;
{$APPTYPE CONSOLE}
begin
Write('Linia, która nie będzie zakończona znakiem nowej linii');
Writeln('To linia, która będzie zakończona znakiem nowej linii');
Write('A ta znowu nie będzie...');
Readln;
end.
```
Uruchom program przedstawiony w powyższym listingu, aby sprawdzić rezultat działania.
Instrukcje warunkowe
==============
Instrukcje warunkowe są elementem programowania obecnym w każdym języku. Są bardzo często wykorzystywane, więc niezbędne jest poznanie ich i zrozumienie zasad ich działania. Instrukcje te służą bowiem do porównywania danych i wykonywania na nich różnych operacji. Wykorzystując instrukcje warunkowe, jesteś w stanie odpowiednio zareagować na daną sytuację.
Instrukcja if..then
--------------------
Podstawową instrukcją warunkową jest instrukcja if..then. Jej budowa jest następująca:
```delphi
if { tu następuje sprawdzanie warunku } then { wykonaj pewną operację }
```
Po słowie if musi się znaleźć pewien warunek do sprawdzenia — przykładowo
```delphi
if 4 > 3 then
```
Taki warunek zostanie zawsze spełniony, gdyż wiadomo, że cyfra 4 jest większa od 3. Tak więc za każdym razem zostanie wykonany kod umieszczony po słowie then.
```delphi
program if_then;
{$APPTYPE CONSOLE}
begin
if 4 > 3 then
Writeln('Tak... warunek został spełniony!');
Readln;
end.
```
Po uruchomieniu powyższego programu za każdym razem wyświetlony zostanie tekst Tak... Warunek został spełniony!.
### Pobieranie tekstu z konsoli
Przeprowadźmy małe ćwiczenie. Otóż po uruchomieniu programu zostaniemy poproszeni o wpisanie pewnego tekstu — imienia. Jeśli wpisane zostanie imię `Adam`, zostaną wykonane pewne czynności; w przeciwnym wypadku zostanie wyświetlony tekst powitalny. Kod programu realizującego to zadanie ma następującą postać:
```delphi
program if_then;
{$APPTYPE CONSOLE}
var
Imie : String;
begin
Writeln('Wpisz swoje imię...');
Readln(Imie); // pobranie wpisanej wartości
if Imie = 'Adam' then
Writeln('Super! Ja też mam na imię Adam!');
if Imie <> 'Adam' then
Writeln('Cześć ' + Imie);
Readln;
end.
```
Na samym początku programu pobierane są dane wpisane przez użytkownika programu. Realizuje to procedura Readln. Za jej pomocą dane wpisane przez użytkownika „wędrują” do zmiennej <var>Imie</var>. Cel naszego ćwiczenia to sprawdzenie, czy wpisany tekst to `Adam`.
Zwróć także uwagę na ten wiersz kodu:
```delphi
Writeln('Cześć ' + Imie);
```
Do wyświetlanego napisu Cześć dołączana jest także zawartość zmiennej <var>Imie</var>. Jak widzisz, operator (+) ma także takie zadanie — łączenie dwóch tekstów.
### Kilka instrukcji po słowie then
Istnieje pewna zasada, którą należy zapamiętać i która będzie wykorzystywana w dalszej części tego rozdziału. Otóż wiele instrukcji po słowie then musi być umieszczone dodatkowo w bloku tekstu między begin a end (przykład: listing 2.3.)
Listing 2.3. Pobieranie danych z konsoli i porównywanie ich za pomocą operatora if
```delphi
program if_then;
{$APPTYPE CONSOLE}
var
Imie : String;
begin
Writeln('Wpisz swoje imię...');
Readln(Imie); // pobranie wpisanej wartości
if Imie = 'Adam' then
begin // dodatkowa instrukcja begin
Writeln('Super! Ja też mam na imię Adam!');
Writeln('Hahhahaa...');
end;
Readln;
end.
```
W całym powyższym listingu interesuje nas jedynie ten fragment kodu:
```delphi
if Imie = 'Adam' then
begin // dodatkowa instrukcja begin
Writeln('Super! Ja też mam na imię Adam!');
Writeln('Hahhahaa....');
end;
```
Oznacza on, że gdy wartość zmiennej jest równa `Adam`, wykonane będą w wyniku tego dwie instrukcje [[Delphi/Writeln]]. Taki wypadek zmusza nas do umieszczenia dodatkowo słów kluczowych [[Delphi/begin]] oraz [[Delphi/end]]. Jeżeli blok begin..end nie znalazłby się w kodzie, to druga instrukcja `Writeln `wykonana zostałaby niezależnie od tego, czy zmienna <var>Imie </var>posiadałaby wartość `Adam`, czy też nie.
### Kilka warunków do spełnienia
To, czy dany kod zostanie wykonany, może zależeć od wielu czynników. Niekoniecznie musi być to jeden warunek do spełnienia — może być ich wiele. Jednak konieczne jest umieszczenie wszystkich tych warunków w nawiasie (listing 2.4.).
Listing 2.4. Kilka warunków w instrukcji if
```delphi
program if_then;
{$APPTYPE CONSOLE}
var
Imie : String;
begin
Writeln('Wpisz swoje imię...');
Readln(Imie); // pobranie wpisanej wartości
Randomize; // procedura losująca
if (Imie = 'Adam') and (Random(10) = 5) then
Writeln('Super! Ja też mam na imię Adam!');
Readln;
end.
```
Kod ten można przetłumaczyć następująco: — jeżeli zmienna Imie zawiera wartość <var>Adam </var>i rezultat losowania spośród 10 liczb wynosi 5, wyświetl tekst na konsoli—. Jak widzisz, aby warunek został spełniony pomyślnie, muszą zostać wykonane dwie czynności.
Wielu początkujących programistów często orientuje się, że zapomnieli zastosować nawiasów podczas sprawdzania kilku warunków. Spowoduje to w tym wypadku wyświetlenie błędu: <i>[Error] if..then.dpr(14): Incompatible types: 'String' and 'Integer'</i>.
<dfn>Poznałeś przy tej okazji znaczenie kolejnej procedury, a mianowicie Random. Polecenie to realizuje proces losowania spośród liczb, z których najwyższa podana jest w nawiasie. A zatem wywołanie procedury w ten sposób — Random(100) — spowoduje wylosowanie liczby z zakresu od 0 do 99. Tak! Nie pomyliłem się! Możliwe jest, że wyniku losowania zwrócona zostanie wartość 0.</dfn>
Pamiętaj, aby zawsze przed wywołaniem procedury [[Delphi/Random]] umieszczać w kodzie instrukcję [[Randomize]]. Powoduje ona zainicjowanie procesu losowania.
Instrukcja case..of
---------------------
Drugą instrukcją warunkową jest `case..of`. Instrukcja ta również realizuje proces porównywania danych. Często stosowana jest w przypadku, gdy mamy do sprawdzenia wiele warunków — instrukcja `if..then` nie zdaje wówczas egzaminu, gdyż należałoby porównać wiele wartości ze sobą. Idealnie za to nadaje się do tego instrukcja case..of; jej składnia jest następująca:
```delphi
case Nazwa_Zmiennej of
0: { instrukcje wykonywane w przypadku, gdy zmienna ma wartość 0 }
1: { instrukcje wykonywane w przypadku, gdy zmienna ma wartość 1 }
end;
```
Jak widać, schemat instrukcji jest raczej prosty. Nie zapomnij o słowie [[Delphi/end]];, kończącym instrukcję warunkową. Przykładowy program zaprezentowałem w listingu 2.5.
Listing 2.5. Program wykorzystujący instrukcję case..of
```delphi
program case_of;
{$APPTYPE CONSOLE}
var
Mandat : Integer;
begin
Mandat := 50;
case Mandat of
10: Writeln('E, 10 zł. Może jeszcze zapłacę?');
20: Writeln('20 zł — nie będę miał na chleb!');
50: Writeln('50 zł — Panie władzo... może dogadamy się w inny sposób?');
end;
Readln;
end.
```
Po uruchomieniu programu wykonany zostanie warunek ostatni, gdyż zmiennej <var>Mandat </var>nadałem „na sztywno” wartość 50; chciałem jednak tylko zaprezentować sposób użycia instrukcji `case..of`.
### Zakresy
Dużą zaletą instrukcji case..of jest możliwość sprawdzania wartości „od-do”. Przykładowo jeżeli wartość zmiennej Mandat wynosi między 10 a 15, wykonany zostanie konkretny warunek. Zmodyfikujemy poprzedni program do takiej postaci, aby wartość zmiennej była losowana:
```delphi
program case_of;
{$APPTYPE CONSOLE}
var
Mandat : Integer;
begin
Randomize;
Mandat := Random(50)+1; // dodajemy 1, aby nie zostało wylosowane 0
case Mandat of
1..10: Writeln('E, 10 zł. Może jeszcze zapłacę?');
11..20: Writeln('20 zł ? nie będę miał na chleb!');
21..50: Writeln('50 zł ? Panie władzo... może dogadamy się w inny sposób?');
end;
Readln;
end.
```
Dzięki zastosowaniu zakresów pierwsza instrukcja zostanie zrealizowana w przypadku, gdy zmienna <var>Mandat </var>będzie miała wartość od 1 do 10.
### Brak możliwości korzystania z łańcuchów
Wadą instrukcji `case..of` jest brak możliwości porównywania danych tekstowych. Najlepiej sprawdzić to na przykładzie — spójrz na poniższy kod:
```delphi
program case_strings;
{$APPTYPE CONSOLE}
var
S : String;
begin
S := 'Hę';
case S of
'Hę': Writeln('???');
end;
Readln;
end.
```
Próbujemy tu sprawdzić, czy zmienna <var>S</var> ma wartość `Hę`. Niestety próba kompilacji takiego programu zakończy się błędem: <i>[Error] case_strings.dpr(11): Ordinal type required</i>.
### Kilka instrukcji
Tak samo, jak w przypadku instrukcji warunkowej `if..then`, instrukcja `case..of `wymaga umieszczenia kodu w bloku `begin..end` w przypadku, gdy kod ten zawiera więcej niż jedną instrukcję.
```delphi
program case_of;
{$APPTYPE CONSOLE}
var
Mandat : Integer;
begin
Randomize;
Mandat := Random(50)+1; // dodajemy 1, aby nie zostało wylosowane 0
case Mandat of
1..10: Writeln('E, 10 zł. może jeszcze zapłacę');
11..20: Writeln('20 zł. — nie będę miał na chleb');
21..50:
begin
Writeln('50 zł. — Panie władzo... może dogadamy się w inny sposób?');
Writeln('Nieeeee...');
end;
end;
Readln;
end.
```
Sam widzisz — jeżeli wylosowana zostanie wartość pomiędzy 21 a 50, wykonane zostaną dwie instrukcje. W takim przypadku należy umieścić kod w dodatkowym bloku `begin..end`.
Instrukcja else
-----------------
Angielskie słowo [[Delphi/else]] można w tym kontekście przetłumaczyć jako w przeciwnym wypadku. Konstrukcja [[else]] jest zawsze stosowana w połączeniu z instrukcją i`f..then` oraz `case..of`. Za jej pomocą można wykonać takie operacje, które nie mieszczą się w ramach wcześniej wykorzystywanych instrukcji. Oczywiście wszystko najlepiej zrozumieć na przykładach — napisz zatem przykładowy program. Pamiętasz pierwszy program, jaki zaprezentowałem podczas omawiania instrukcji if — Zmodyfikuj go do takiej postaci:
```delphi
program if_then_else;
{$APPTYPE CONSOLE}
var
Imie : String;
begin
Writeln('Wpisz swoje imię...');
Readln(Imie); // pobranie wpisanej wartości
if Imie = 'Adam' then
Writeln('Super! Ja też mam na imię Adam!') // <—— uwaga! Brak średnika!
else Writeln('Cześć ' + Imie);
Readln;
end.
```
W poprzedniej wersji tego programu to, czy zmienna Imie nie zawiera wartości `Adam`, sprawdzane było w kolejnym warunku if. Teraz w przypadku, gdy zmienna nie będzie zawierać tej wartości, wyświetlony zostanie tekst powitalny. Wszystko możliwe jest za sprawą instrukcji else. Mam nadzieję, że ten przykład pomógł Ci zrozumieć, jak działa instrukcja [[Delphi/else]].
<dfn>Zauważ, że w wierszu nad instrukcją else nie ma średnika na końcu! To jest właśnie wyjątkowa sytuacja, kiedy nie stawiamy średnika!</dfn>
### Kiedy stosować średnik, a kiedy nie ?
Poprzedni przykład z użyciem instrukcji [[Delphi/if]] i [[else]] nie zawierał średnika. Kiedy stosować ten średnik, a kiedy nie — Spójrz — taki fragment kodu już wymaga postawienie średnika przed [[else]]:
```delphi
if Imie = 'Adam' then
begin
{ jakaś inna instrukcja }
Writeln('Super! Ja też mam na imię Adam!');
end else Writeln('Cześć ' + Imie);
```
Także w przypadku, gdy po słowie then stosujemy blok `begin..end`, średnik musi się w kodzie znaleźć! Ale zasada pozostaje taka sama — nie stosujemy go przed else!
```delphi
end else Writeln('Cześć ' + Imie);
```
Sprawa początkowo może wydać się nieco skomplikowana, ale po dokładniejszym przejrzeniu kodu można rozróżnić, kiedy należy stosować średnik na końcu, a kiedy nie.
### Kilka instrukcji if i else
Możliwe jest połączenie kilku instrukcji [[Delphi/if]] oraz [[else]]. Taka konstrukcja ma postać `else if { warunek }`
Jeżeli jeden warunek nie zostanie spełniony, nastąpi analiza drugiego. Jeżeli także drugi nie zostanie spełniony — analizowane będą kolejne warunki. Spójrz na poniższy kod:
```delphi
program if_else;
{$APPTYPE CONSOLE}
var
I : Integer;
begin
Randomize;
I := Random(50);
if i = 10 then Writeln('I = 10')
else if i = 20 then Writeln('I = 20')
else if i = 30 then Writeln('I = 30')
{ kod, w razie, gdy żaden warunek nie zostanie spełniony }
else Writeln('Żadna wartość nie jest odpowiednia!');
Readln;
end.
```
Na samym początku następuje sprawdzenie, czy wylosowana liczba to 10; w innym wypadku następuje sprawdzenie kolejno liczb 20 i 30, co tworzy następne warunki [[Delphi/if]]. Jeżeli żaden z poprzednich warunków nie zostanie zrealizowany, wykonany zostanie kod umieszczony po słowie else.
### Kilka instrukcji po słowie begin
Jak mówiłem wcześniej, często będzie sprawdzać się zasada mówiąca o tym, że gdy po jednym słowie kluczowym (np. [[Delphi/else]] lub [[then]]) wystąpi więcej niż jedna instrukcja, cały fragment kodu należy umieścić w dodatkowym bloku begin..end.
```delphi
if i = 10 then Writeln('I = 10')
else if i = 20 then Writeln('I = 20')
else if i = 30 then Writeln('I = 30')
{ kod wykonywany w razie, gdy żaden warunek nie zostanie spełniony }
else
begin
Writeln('Żadna wartość nie jest odpowiednia!');
Writeln('Spróbuj jeszcze raz');
end;
```
Jak widać w powyższym przykładzie, średnik nie został wstawiony ani po słowie [[Delphi/else]], ani przed nim.
### Instrukcja else w case..of
Możliwe jest także zastosowanie słowa kluczowego [[Delphi/else]] w instrukcji `case..of`. Jeżeli żaden ze sprawdzanych warunków case nie zostanie spełniony, można odpowiednio na to zareagować. Posłużę się jednym z poprzednich przykładów (listing 2.6).
Listing 2.6. Instrukcja else zagnieżdżona w case..of
```delphi
program case_of_else;
{$APPTYPE CONSOLE}
var
Mandat : Integer;
begin
Randomize;
Mandat := Random(100)+1; // dodajemy 1, aby nie zostało wylosowane 0
case Mandat of
1..10: Writeln('E, 10 zł. Może jeszcze zapłacę?');
11..20: Writeln('20 zł — nie będę miał na chleb!');
21..50:
begin
Writeln('50 zł — Panie władzo... może dogadamy się w inny sposób?');
Writeln('Nieeeee...');
end;
{ dodajemy else }
else Writeln('Jakiś inny mandacik?');
end;
Readln;
end.
```
Tym razem losowana jest liczba z zakresu od 1 do 100. Dzięki temu instrukcja [[Delphi/case]] może nie uwzględniać tej liczby — wykonany zostanie blok kodu umieszczony po słowie kluczowym else.
Procedury i funkcje
============
Podczas czytania tego rozdziału mogłeś zauważyć, że nieraz posługiwałem się słowem procedura lub funkcja w odniesieniu do poleceń języka Object Pascal. Mogłeś odnieść wrażenie, że wszystkie te słowa są synonimami — jednak sprawa wygląda nieco inaczej.
Procedury
---------------
<dfn>Procedura to wydzielony blok kodu, realizujący określone zadanie. </dfn>
Procedurą, z której korzystałeś już na samym początku tego rozdziału, jest [[Delphi/ShowMessage]]. Jak pamiętasz, procedura ta realizowała wyświetlanie okienka informacyjnego. Wyobraź sobie, że w danym programie musisz często wykonywać ten sam blok kodu — np. wyświetlanie kilku tekstów w konsoli. W takim przypadku za każdym razem należałoby wpisywać po kolei instrukcje [[Writeln]]. Dzięki zastosowaniu procedur możesz wpisać te instrukcje tylko raz — później pozostaje już tylko umieszczenie w odpowiednim miejscu nazwy procedury, aby wykonać kod w niej zawarty. Przykładowa deklaracja procedury wygląda tak:
```delphi
procedure Nazwa_Procedury;
begin
{ kod procedury }
end;
```
Jak widzisz, procedurę deklaruje się z użyciem słowa kluczowego [[Delphi/procedure]], po którym następuje nazwa. Nazwa procedury musi być unikalna dla każdego programu — nie może się powtarzać.
Od teraz za każdym razem, gdy gdzieś w kodzie wpiszesz słowo `Nazwa_Procedury`, wykonany zostanie kod umieszczony w jej wnętrzu. Przykładowy program z wykorzystaniem procedur przedstawiam poniżej.
```delphi
program ProcApp;
{$APPTYPE CONSOLE}
procedure Quit;
begin
Writeln('Wyjście z programu! Naciśnij Enter, aby zakończyć!');
Readln;
end;
var
Key : Char;
begin
Writeln('Naciśnij literę Q, aby zakończyć!');
Readln(Key);
if Key = 'Q' then Quit else Writeln('Fajnie, że jesteś z nami');
end.
```
Pamiętaj o tym, że procedury deklaruje się poza blokiem `begin..end`! W programie tym zadeklarowałem procedurę `Quit`. Od tej pory za każdym razem, gdy wpiszesz w programie słowo `Quit`, program wyświetli informację i — po naciśnięciu klawisza Enter — zakończy swe działanie.
W programie tym zadeklarowałem nowy typ zmiennej, jakiego do tej pory nie używałem. Typ [[Delphi/Char]], bo to o nim mowa, umożliwia zapisywanie w pamięci danych o postaci jedynie jednego znaku.
Funkcje
=====
Funkcje są w swoim działaniu bardzo podobne do procedur. Właściwie w innych językach programowania, jak C/C++ czy Java, procedury w ogóle nie istnieją — dostępne są jedynie funkcje. Podstawowa różnica pomiędzy procedurami a funkcjami polega na tym, że te drugie zwracają jakąś wartość.
Deklaracja funkcji jest bardzo specyficzna, następuje bowiem poprzez użycie słowa kluczowego [[Delphi/function]]. Oprócz tego funkcja, jak już mówiłem, musi zwracać jakąś wartość — po znaku dwukropka należy wpisać typ zwracanej wartości:
```delphi
function GetName : String;
begin
end;
```
Być może w dalszym ciągu nie rozumiesz, w czym funkcje różnią się od procedur — najłatwiej napisać przykładowy program — spójrz na listing 2.7
Listing 2.7. Przykładowy program wykorzystujący funkcję
```delphi
program FuncApp;
{$APPTYPE CONSOLE}
function GetName : String;
begin
Result := 'Adam Boduch';
end;
begin
Writeln('Nazywam się ' + GetName);
Readln;
end.
```
Po uruchomieniu takiego programu na konsoli wyświetlony zostanie napis `Nazywam się Adam Boduch`. To wszystko stanie się za sprawą wiersza:
```delphi
Result := 'Adam Boduch';
```
Słowo [[Delphi/Result]] jest jakby ukrytą zmienną — po przypisaniu jej wartości zostanie ona zwrócona przez funkcję.
Spróbuj to samo zrobić z procedurami — nie uda Ci się to, ponieważ procedura nie może zwrócić wartości; program nie zostanie zatem uruchomiony.
Zmienne lokalne
-------------------
Tak, jak w kodzie programu można zadeklarować zmienne i stałe, tak można je również zadeklarować w „ciele” procedury lub funkcji. Takie zmienne nazywa się zmiennymi lokalnymi o określonym czasie „życia”.
```delphi
function GetValue : String;
var
S : String;
begin
S := 'Khem...';
Result := S;
end;
```
Zasada deklarowania zmiennych jest taka sama — deklaracja umieszczana jest przed blokiem [[Delphi/begin]]. Zmienne lub stałe deklarowane w „ciele” procedury nie są widoczne poza ową procedurą. A zatem w przypadku, gdy spróbujesz odwołać się do zmiennej umieszczonej w procedurze, Delphi wyświetli komunikat o błędzie.
Dlaczego takie zmienne nazywane są zmiennymi o określonym czasie „życia”— Przyczyną tego jest fakt, że pamięć dla nich alokowana jest w momencie wywołania procedury, a zwalniania w momencie zakończenia działania.
Jeżeli mamy program, który korzysta z wielu procedur, zalecane jest używanie — o ile to możliwe — zmiennych lokalnych. Dzięki temu oszczędzamy pamięć.
W dalszej części tej książki możesz spotkać się z określeniem zmienne globalne. Takie zmienne to po prostu zwykłe zmienne, „widoczne” dla całego programu.
To, że zmienne zawarte w „ciele” procedury nie są widoczne na zewnątrz, nie oznacza, że procedura nie może używać zmiennych globalnych. Ta zasada działa tylko w jedną stronę!
Parametry procedur i funkcji
---------------------------------
Możliwe jest, wraz z wywołaniem danej procedury lub funkcji, przekazanie do niej danych — tzw. parametrów. Objaśnię to na przykładzie wspomnianej wcześniej procedury [[Delphi/ShowMessage]]. Podczas jej wywoływania w nawiasie należało wpisać wartości, a konkretnie tekst. Ten tekst mógł być przez procedurę wykorzystany do wyświetlania okna informacyjnego.
Deklaracja procedury lub funkcji zawierającej parametry wygląda następująco:
```delphi
procedure SetName(Name : String);
begin
{ kod procedury }
end;
```
Od tej pory chcąc wywołać procedurę SetName, należy podać wartość parametru; parametr musi być typu [[Delphi/String]].
Przykładowy program wyświetli na konsoli za pomocą procedury `SetName` tekst z parametru `Name`:
```delphi
program ProcParam;
{$APPTYPE CONSOLE}
procedure SetName(Name : String);
begin
Writeln('Nazywam się ' + Name);
end;
begin
SetName('Adam Boduch');
Readln;
end.
```
Procedura `SetName` odczytuje wartość przekazanego do niej parametru i wyświetla go wraz z innym tekstem na konsoli.
### Kilka parametrów procedur lub funkcji
Możliwe jest przekazanie do procedury lub funkcji kilku parametrów tego samego i innego typu. W takim przypadku wszystkie parametry należy oddzielić znakiem średnika:
```delphi
function SetName(FName : String; SName : String; Age : Byte);
```
Aby program został prawidłowo skompilowany, należy przekazać funkcji aż trzy parametry. Jeżeli jednak występuje kilka parametrów tego samego typu, można oddzielić je przecinkami:
```delphi
function SetName(FName, SName : String; Age : Byte);
```
Dzięki temu zapis funkcji jest nieco krótszy. Naturalnie z punktu widzenia kompilatora nie ma znaczenia, jak zapisana jest deklaracja — możliwe jest więc zapisanie tego w taki, czytelny dla nas sposób:
```delphi
function SetName(FName,
SName : String;
Age : Byte
) : String;
begin
end;
```
Parametry domyślne
-------------------------
Pamiętasz, jak wspominałem o funkcjach [[Delphi/Inc]] oraz [[Dec]]— Pierwszy ich parametr musiał być nazwą zmiennej, a drugi był opcjonalny. Delphi oferuje przydatną możliwość deklarowania parametrów domyślnych. Oznacza to, ze podczas wywoływania procedury lub funkcji parametr może, ale nie musi zostać wpisany — w takim wypadku Delphi zastosuje domyślną wartość, ustaloną przez programistę. Oto przykład deklaracji takiej procedury:
```delphi
procedure SetName(FName : String;
SName : String = 'Nieznany';
Age : Byte = 0
);
begin
end;
```
Parametry domyślne wpisujemy po znaku równości. W przypadku powyższej procedury możliwe jest albo wywołanie jej w ten sposób:
```delphi
SetName('Moje imię');
```
albo wpisanie wszystkich parametrów:
```delphi
SetName('Moje imię', 'Moje nazwisko', 100);
```
Oba sposoby są prawidłowe — jeżeli nie wpiszesz dwóch ostatnich parametrów, Delphi za domyślne wartości uzna te, które zostały wpisane w deklaracji procedury; a zatem dla parametru <var>SName</var> będzie to wartość `Nieznany`, a dla <var>Age</var> — 0.
### Parametry tego samego typu a wartości domyślne
Niestety nie jest możliwe deklarowanie kilku parametrów tego samego typu i jednoczesne przypisanie wartości jednemu z nich:
```delphi
procedure SetName(FName,
SName = 'Nieznany' : String;
Age : Byte = 0
);
```
Powyższa instrukcja spowoduje błąd — zawsze podczas określenia wartości domyślnych należy wpisać typ parametru.
### Kolejność wartości domyślnych
Należy jeszcze poczynić jedno istotne zastrzeżenie dotyczące deklaracji wartości domyślnych. Otóż błędna jest deklaracja wyglądająca w ten sposób:
```delphi
function Funkcja(X : Integer = 0; Y : Integer) : Integer;
begin
end;
```
Próbujemy w ten sposób przypisać wartość domyślną pierwszemu parametrowi, a drugiemu nie. Delphi zaprotestuje i wyświetli błąd: <i>[Error] OverProc.dpr(18): Default value required for 'Y'</i>. Jedynym wyjściem jest umieszczenie domyślnych parametrów na samym końcu lub deklaracja wartości domyślnych dla każdego parametru. Taki kod będzie już jak najbardziej prawidłowy:
```delphi
function Funkcja(Y : Integer; X : Integer = 0) : Integer;
begin
end;
```
Przeciążanie funkcji i procedur
-----------------------------------
Stosunkowo nową technologią w Delphi jest możliwość przeciążania procedur i funkcji. Przeciążanie polega na opatrzeniu funkcji i procedury specjalną klauzulą. Dzięki temu kompilator nie będzie protestował, gdy zadeklarujemy kilka funkcji lub procedur o tej samej nazwie! Warunkiem jest jednak to, że parametry muszą być różne, ale nazwa może pozostać ta sama.
Napiszmy przykładowy program, który będzie wykorzystywał funkcje mnożenia — przykładowa taka funkcja może wyglądać tak:
```delphi
function Mnozenie(X, Y : Integer) : Integer;
begin
Result := X * Y;
end;
```
Zasada działania jest prosta. Funkcja mnoży dwa parametry — X i Y, a następnie zwraca rezultat tego działania. Jednak podane w funkcji parametry mogą być tylko typu [[Delphi/Integer]], czyli mogą być wyłącznie liczbami całkowitymi. W przypadku, gdy chcemy do parametrów przekazać wartości zmiennoprzecinkowe, kompilator zasygnalizuje błąd. Można oczywiście zamiast typu Integer zastosować chociażby typ [[Currency]]. Można także wykorzystać dwie funkcje Mnozenie o różnych parametrach:
```delphi
function Mnozenie(X, Y : Integer) : Integer; overload;
begin
Result := X * Y;
end;
function Mnozenie(X, Y : Currency) : Currency; overload;
begin
Result := X * Y;
end;
```
Żeby wszystko zostało skompilowane dobrze, obie funkcje należy oznaczyć klauzulą [[Delphi/overload]]. Od tej chwili podczas wywoływania funkcji `Mnozenie `kompilator sam — na podstawie parametrów — ustali, jaką funkcję chcemy wywołać:
```delphi
Mnozenie(2, 2);
Mnozenie(2.5, 2.5);
```
Typy parametrów przekazywanych do procedur i funkcji
-----------------------------------------------------------------
To, co powiedziałem wcześniej na temat prostego przekazywania parametrów do funkcji i procedur, to nie wszystko. Istnieje możliwość przekazywania parametrów przez referencję lub przez stałą.
### Przekazywanie parametrów poprzez stałą
Umieszczenie w deklaracji parametrów słowa kluczowego [[Delphi/const]] spowoduje przekazywanie parametrów jako stałych.
```delphi
procedure Show(const Message : String);
begin
Writeln(Message);
end;
```
Co to oznacza — Procedura nie może w żaden sposób wpływać na zawartość parametru. Próba nadania przez procedurę jakiejś wartości spowoduje błąd: <i>[Error] ConstParam.dpr(7): Left side cannot be assigned to</i>.
Przekazując parametry przez stałą pozwalasz kompilatorowi na maksymalną optymalizację kodu.
### Przekazywanie parametrów przez referencję
Przekazywanie parametrów przez referencję polega na umieszczeniu przed parametrami słowa kluczowego [[Delphi/var]]. Dzięki temu kod znajdujący się wewnątrz procedury może zmienić wartość parametru. Aby lepiej to zrozumieć, przeprowadźmy małe ćwiczenie. Spójrz na poniższy listing 2.8.
Listing 2.8. Wartości przekazywane przez referencję
```delphi
program VarParam;
{$APPTYPE CONSOLE}
procedure SetValue(Message : String);
begin
{ próba nadania nowej wartości dla parametru }
Message := 'Hello there!';
end;
var
S : String;
begin
S := 'Hello World';
SetValue(S); // przekazanie zmiennej
Writeln(S); // odczyt wartości zmiennej
Readln;
end.
```
Teraz zagadka: jaką wartość będzie miała zmienna <var>S</var> — Uruchom program i sprawdź! Na ekranie konsoli zostanie wyświetlony napis `Hello World`, co oznacza, że procedurze nie udało się zmienić wartości parametru. Przedefiniuj teraz nieco deklarację procedury `SetValue`:
```delphi
procedure SetValue(var Message : String);
begin
{ próba nadania nowej wartości dla parametru }
Message := 'Hello there!';
end;
```
Dzięki dodaniu słowa [[Delphi/var]] i ponownemu uruchomieniu programu możesz przekonać się, że zmienna <var>S</var> będzie miała wartość Hello there. Oznacza to, że naszej procedurze udało się zmienić wartość tego parametru.
### Dyrektywa out
Zamiast słowa kluczowego var w deklaracji funkcji lub procedury możesz umieścić także słowo [[Delphi/out]]. Zasada działania dyrektywy [[out]] jest bardzo podobna do zasady działania dyrektywy [[var]]. Różnica polega na tym, że do parametru zadeklarowanego jako out nie można przekazać żadnej wartości. Zrozumiesz to na przykładzie poprzedniego programu — zmodyfikuj go do takiej postaci:
```delphi
program OutParam;
{$APPTYPE CONSOLE}
procedure SetValue(out Message : String);
begin
{ Message nie zawiera wartości! }
Message := 'Hello there!';
end;
var
S : String;
begin
S := 'Hello World';
SetValue(S); // przekazanie zmiennej
Writeln(S); // odczyt wartości zmiennej
Readln;
end.
```
W kodzie programu zapisana jest próba przekazania wartości do procedury `SetValue`. Jednak za sprawą dyrektywy [[Delphi/out]] wartość nie dociera do procedury. Inaczej mówiąc, parametr Message jest pusty. Sprawdź działanie programu i porównaj, jak program zachowuje się w przypadku, gdy w kodzie zamiast słowa out znajduje się instrukcja <var>var</var>.
Procedury zagnieżdżone
--------------------------
Nic nie stoi na przeszkodzie, aby daną procedurę lub funkcję umieścić w innej procedurze lub funkcji. Wygląda to mniej więcej tak:
```delphi
procedure A;
procedure B;
begin
end;
begin
end;
```
Z powyższego zapisu wynika, że procedura lub funkcja zagnieżdżona (w tym wypadku procedura B) musi zostać umieszczona przed blokiem [[Delphi/begin]].
Własne typy danych
=============
Object Pascal umożliwia deklarowanie własnych typów danych, które następnie można wykorzystać w programie. Własny typ danych można zadeklarować za pośrednictwem słowa kluczowego type. Przykładowa deklaracja własnego typu mogłaby wyglądać w ten sposób:
```delphi
type
TMediumValue = 0..20;
```
W światku programistów Delphi przyjęło się już, że każdy nowy typ danych poprzedzony jest dużą literą T — ja nie zamierzam od tej reguły odstępować.
Od tej pory możemy w swoim programie używać własnego typu danych — `TMediumValue`, który to typ może przybrać wartości liczbowe od 0 do 20.
Wykorzystanie takiego typu danych (stworzenie zmiennej) jest już proste:
```delphi
var
MediumValue : TMediumValue;
```
Można już korzystać z naszej zmiennej, tak jak z każdej innej:
```delphi
begin
MediumValue := 10;
end.
```
Możliwe jest także zadeklarowanie swojego własnego typu wyglądającego tak:
```delphi
TSamochody = (tsFiat, tsMercedes, tsOpel);
```
Po utworzeniu zmiennej wskazującej na ten typ będzie ona mogła zawierać jedną z podanych w nawiasie wartości.
Tablice jako nowy typ
-------------------------
Możliwe jest przekazywanie całych tablic jako parametrów do funkcji lub procedury. Jednak nie da się tego uczynić bezpośrednio — należy w tym celu utworzyć nowy typ danych, np. taki:
```delphi
type
TMyArray = array[0..20] of String;
```
Następnie można dopiero przekazać go jako parametr, tak jak to przedstawiono w poniższym przykładzie:
```delphi
program TypeArray;
{$APPTYPE CONSOLE}
type
TMyArray = array[0..20] of String;
procedure GetArray(MyArray : TMyArray);
begin
{ odebranie tablicy }
end;
var
MyArray : TMyArray;
begin
MyArray[0] := 'Element 1';
{ ... }
end.
```
Taki program powinien zostać skompilowany i zadziałać bez żadnych problemów.
Aliasy typów
========
Aliasy służą do tworzenia nowego typu, który w rzeczywistości wskazuje na inny i jest mu równoważny:
```delphi
Type
TMojTyp = Integer;
```
Od tego momentu `TMojTyp` będzie równoważny typowi [[Delphi/Integer]]. Z aliasów możesz korzystać, jeśłi chcesz zwiększyć czytelność kodu swojego programu. Ogólnie rzecz biorąc, nie jest to często używana funkcja.
Rekordy
=====
Rekordy to zorganizowana struktura danych, połączona w jedną całość. Jest to jakby „paczuszka” zawierająca określone elementy. Rekordy również możesz przekazywać jako „paczuszkę” do funkcji czy procedur w formie parametru.
Nowe rekordy deklarować można jako nowy typ danych lub jako zmienną, z użyciem słowa kluczowego [[Delphi/record]].
```delphi
type
TMyRecord = record
X : Integer;
Y : Integer;
end;
```
Budowa, jak widzisz, jest specyficzna — najpierw należy wpisać nazwę rekordu, a dalej po znaku równości słowo kluczowe [[Delphi/record]] (uwaga, brak średnika na końcu!). Następnie wypisujemy elementy, z których nasz rekord ma się składać.
Jako że zadeklarowałem rekord jako nowy typ danych, należy utworzyć dodatkowo zmienną wskazującą na ten typ. Przy tej okazji poznasz nowy operator Object Pascala — kropkę (.). Do poszczególnych pól rekordu odwołujemy się w ten sposób:
`MyRecord.X := 1;`
Po uprzednim utworzeniu zmiennej wskazującej na rekord.
Przekazywanie rekordów jako parametr procedury
----------------------------------------------------------
Napiszmy prosty program, którzy pobierze dwie liczby wpisane przez użytkownika i przekaże do procedury cały rekord.
Samo przekazanie rekordu do funkcji przebiega w sposób dość prosty:
```delphi
type
TMyRecord = record
X : Integer;
Y : Integer;
end;
function Dzielenie(MyRecord : TMyRecord) : Integer;
begin
Result := MyRecord.X div MyRecord.Y;
end;
```
Delphi wymaga, aby do funkcji Dzielenie przekazany został rekord `TMyRecord`. Kolejno następuje podzielenie elementu X przez element Y rekordu i zwrócenie wartości dzielenia. Cały program wygląda tak, jak na listingu 2.9.
Listing 2.9. Rekord przekazany jako parametr procedury
```delphi
program Recordapp;
{$APPTYPE CONSOLE}
uses SysUtils;
type
TMyRecord = record
X : Integer;
Y : Integer;
end;
function Dzielenie(MyRecord : TMyRecord) : Integer;
begin
Result := MyRecord.X div MyRecord.Y;
end;
var
MyRecord : TMyRecord;
Result : Integer;
begin
Writeln('Podaj pierwszą liczbę');
Readln(MyRecord.X);
Writeln('Podaje drugą liczbę');
Readln(MyRecord.Y);
Result := Dzielenie(MyRecord);
Writeln('Rezultat dzielenia ' + IntToStr(Result));
Readln;
end.
```
Pobranie danych realizuje polecenie [[Delphi/Readln]], ale o tym mówiliśmy już na początku tego rozdziału.
W powyższym listingu skorzystałem z funkcji konwersji — [[Delphi/IntToStr]]. O konwersji będzie mowa w podpunkcie „Konwersja typów”.
Deklaracja rekordu jako zmienna
---------------------------------------
Nie jest konieczne tworzenie nowego typu dla rekordu. Oznacza to, że zamiast deklarować kolejny rekord jako nowy typ (type), można zadeklarować go jako zmienną:
```delphi
var
Rec : record
X, Y : Integer;
end;
```
Wówczas nie jest konieczne tworzenie nowej zmiennej — od razu można zabrać się za przypisywanie danych do elementów rekordu.
Instrukcja packed
---------------------
W celu zapewnienia szybszego działania rozmiary rekordów są zaokrąglane do wartości 8 bajtów. Oznacza to, że po zsumowaniu wszystkich elemenów rekordu i określeniu, ile miejsca zajmie on w pamięci — całość zaokrąglana jest dodatkowo do 8 bajtów.
Umieszczenie instrukcji packed podczas deklaracji rekordu powoduje, że zostanie on „skompresowany”. Minusem takiej kompresji jest wolniejsze działanie.
```delphi
type
TMyRec = packed record
X, Y : Integer;
end;
```
Instrukcja wiążąca with
===============
Instrukcja with jest przeważnie używana wraz z rekordami lub obiektami (o obiektach będzie mowa w następnym rozdziale). Nie pełni ona żadnej znaczącej roli — uwalnia za to programistę od pisania zbędnego kodu. Załóżmy, że program zawiera następujący rekord:
```delphi
var
Rec : packed record
X, Y : Integer;
Imie : String[20];
Nazwisko : String[20];
Wiek : Byte;
end;
```
Prawidłowe wypełnienie rekordu przedstawione jest poniżej:
```delphi
Rec.X := 12;
Rec.Y := 24;
Rec.Imie := 'Jan';
Rec.Nazwisko := 'Kowalski';
Rec.Wiek := 20;
```
Dzięki zastosowaniu instrukcji wiążącej with kod ten można skrócić do takiej postaci:
```delphi
with Rec do
begin
X := 12;
Y := 24;
Imie := 'Jan';
Nazwisko := 'Kowalski';
Wiek := 20;
end;
```
Możliwe jest określenie długości zmiennej String, co przedstawiłem w powyższym przykładzie. Wystarczy w deklaracji wpisać w nawiasach kwadratowych maksymalną długość, jaką może posiadać zmienna.
Moduły
=====
Moduł (ang. unit) jest plikiem tekstowym, zawierającym polecenia interpretowane przez kompilator.
Podział programu na moduły pojawił się po raz pierwszy z jednej z wcześniejszych wersji Turbo Pascala. Zastosowanie modułów pozwala na podział kodu na osobne pliki. Przypomnij sobie przez chwilę poprzedni rozdział. Podczas tworzenia pierwszego programu i zapisywania pierwszego projektu na dysku został zapisany plik *.pas. Jest to właśnie plik modułu. Każdemu formularzowi odpowiada jeden moduł, ale z kolei moduł nie musi być formularzem.
Wyobraź sobie więc, że w swoim programie masz kilka formularzy i — w związku z tym — wiele plików; ponadto często korzystasz z jednej procedury. W takim wypadku w każdym z tych formularzy musiałbyś umieszczać ową procedurę. Zastosowanie w programie modułów zmienia tę sytuację, o czym za chwilę się przekonasz.
Tworzenie nowego modułu
------------------------------
1. Utwórz nowy projekt.
2. Zamknij Projektanta formularzy oraz Edytor kodu i doprowadź główny plik DPR do takiej postaci:
```delphi
program UnitApp;
begin
end.
```
3. Z menu File wybierz polecenie New/Unit. Spowoduje to stworzenie w Edytorze kodu nowej zakładki (nowego modułu).
4. Z menu File wybierz polecenie Save As — plik zapisz pod nazwą MainUnit.
Budowa modułu
------------------
Zaraz po stworzeniu nowego modułu Delphi generuje potrzebne instrukcje, które są niezbędnym elementem tego modułu.
```delphi
unit MainUnit;
interface
implementation
end.
```
### Nazwa
Pierwszy wiersz zawiera instrukcję unit, która określa nazwę modułu. Nie należy jej zmieniać — Delphi generuje tę nazwę automatycznie, w momencie zapisywania pliku na dysku. Podczas próby zmiany nazwy modułu wystąpi błąd: <i>[Error] MainUnit.pas(1): Unit identifier 'MainUnitx' does not match file name</i>.
### Sekcja Interface
Jest to tzw. część publiczna modułu. Tutaj należy umieszczać deklaracje procedur i funkcji, które mają być widoczne „na zewnątrz”, dla innych modułów. W tym momencie należy rozróżnić pewne pojęcia, takie jak deklaracja oraz definicja.
Deklaracja jest udostępnieniem jedynie „nagłówka” funkcji lub procedury. Dzięki temu procedury i funkcje są „widoczne” dla innych modułów. Np. deklaracja procedury ProcMain może wyglądać tak:
```delphi
procedure ProcMain(Param1, Param2 : Integer);
```
Definicja to cały kod procedury lub funkcji, zawarty w sekcji [[Delphi/Implementation]].
### Sekcja Implementation
Sekcja Implementation stanowi część prywatną modułu. Kod w niej zawarty w (procedury, funkcje, zmienne, tablice czy stałe) nie jest widoczny dla innych modułów — z punktu widzenia kompilatora dane zawarte w tej sekcji nie istnieją.
W sekcji Implementation należy także umieszczać definicję procedur i funkcji. Przykład prawidłowego modułu (deklaracji i definicji):
```delphi
unit MainUnit;
interface
procedure ProcMain(Param1, Param2 : Integer);
implementation
procedure ProcMain(Param1, Param2 : Integer);
begin
end;
end.
```
Pamiętaj o tym, że również moduł musi być zakończony instrukcją end. (kropka na końcu!).
Włączanie modułu
---------------------
Podczas poprzedniego ćwiczenia, którego celem było utworzenie nowego modułu, Delphi zwolnił nas z obowiązku włączenia tego modułu do głównego pliku *.dpr.
Włączenie modułu do programu (lub do innych modułów) realizowane jest za pomocą dyrektywy [[Delphi/uses]].
```delphi
uses
MainUnit in 'MainUnit.pas';
```
Od tej pory wszystkie deklaracje z modułu `MainUnit` będą widoczne także w obrębie programu głównego. Taka, wygenerowana przez Delphi, konstrukcja nie jest jedyną poprawną konstrukcją. Wystarczy, jeśli napiszesz tylko:
```delphi
uses
MainUnit;
```
Jeżeli będziesz chciał włączyć do programu kilka modułów, wypisz je jeden po drugim, oddzielając ich nazwy przecinkami.
Sekcja Initialization oraz Finalization
------------------------------------------
Dwie wspomniane sekcje — [[Delphi/Initialization]] oraz [[Delphi/Finalization]] — są opcjonalnymi sekcjami modułu.
Umieszczenie kodu bezpośrednio za sekcją [[Delphi/Initialization]] powoduje wykonanie go zaraz po uruchomieniu programu. Natomiast kod znajdujący się za sekcją Finalization wykonany zostanie po zakończeniu pracy z modułem. Przykład przedstawiono w listingu 2.10.
Listing 2.10. Wykorzystanie sekcji Initialization oraz Finalization
```delphi
unit MainUnit;
interface
uses Dialogs; // włączamy moduł Dialogs
procedure ProcMain(Param1, Param2 : Integer);
implementation
procedure ProcMain(Param1, Param2 : Integer);
begin
end;
initialization
ShowMessage('Rozpoczynamy pracę z modułem...');
finalization
ShowMessage('Kończymy pracę z modułem...');
end.
```
Po uruchomieniu programu zostanie wyświetlone okienko dialogowe. Tak samo stanie się po zamknięciu naszej aplikacji.
Najczęściej sekcje [[Delphi/Initialization]] oraz [[Delphi/Finalization]] używane są do przydzielania lub zwalniania danych (pamięci), przydzielanych w ramach konkretnego modułu.
Jeżeli w programie korzystasz z sekcji [[Delphi/Finalization]], to koniecznym staje się umieszczenie także sekcji Initialization. W przeciwnym wypadku próba uruchomienia programu zakończy się błędem: <i>[Error] MainUnit.pas(17): Declaration expected but 'FINALIZATION' found</i>. Sekcja Initialization może nie zawierać żadnego kodu — istotna jest tylko jej obecność w kodzie.
Konwersja typów
===========
Środowisko Delphi zostało tak zaprojektowane, aby nie dopuszczać do sytuacji, w której zmienna typu [[Delphi/Integer]] jest przekazywana np. do procedury [[ShowMessage]], która jako parametru wymaga danych typu [[String]]. Jednak tu z pomocą przychodzą nam funkcje konwersji, które pozwalają przekonwertować dane na inny typ. W rezultacie aby przekazać do procedury ShowMessage zmienną typu Integer, wystarczy umieścić w kodzie taki zapis:
```delphi
ShowMessage(IntToStr(Zmienna_Integer));
```
Polecenie IntToStr powoduje konwersję danych w postaci [[Delphi/Integer]] na format [[String]].
Funkcje konwersji, przedstawione w tabeli 2.3, zadeklarowane są w module SysUtils — koniecznym staje się więc włączenie nazwy tego modułu do listy [[Delphi/uses]].
<div class="table">
<table>
<tbody>
<tr><th class="header">Nazwa</th><th class="header">Opis</th></tr>
<tr><td class="row">IntToStr</td><td class="row">Konwertuje typ Integer na String</td></tr>
<tr><td class="row">StrToInt</td><td class="row">Konwertuje typ String na Integer</td></tr>
<tr><td class="row">CurrToStr</td><td class="row">Konwertuje typ Currency na String</td></tr>
<tr><td class="row">StrToCurr</td><td class="row">Konwertuje typ String na Currency</td></tr>
<tr><td class="row">DateTimeToStr</td><td class="row">Konwertuje typ TDateTime na String</td></tr>
<tr><td class="row">StrToDateTime</td><td class="row">Konwertuje typ String na TDateTime</td></tr>
<tr><td class="row">DateToStr</td><td class="row">Konwertuje typ TDate na String</td></tr>
<tr><td class="row">StrToDate</td><td class="row">Konwertuje typ String na TDate</td></tr>
<tr><td class="row">TimeToStr</td><td class="row">Konwertuje typ TTime na String</td></tr>
<tr><td class="row">TimeStrToTimeToStr</td><td class="row">Konwertuje typ String na TTime</td></tr>
<tr><td class="row">FloatToStr</td><td class="row">Konwertuje typ Extended na String</td></tr>
<tr><td class="row">StroToFloat</td><td class="row">Konwertuje typ String na Extended.</td></tr>
<tr><td class="row">IntToHex</td><td class="row">Konwertuje typ Integer do postaci heksydemalnej.</td></tr>
<tr><td class="row">StrPas</td><td class="row">Konwertuje typ String na PChar.</td></tr>
<tr><td class="row">String</td><td class="row">Konwertuje typ PChar na String.</td></tr>
<tr><td class="row">PChar</td><td class="row">Konwertuje typ String na PChar.</td></tr>
<tr><td class="row">StrToBool</td><td class="row">Konwertuje typ String na Boolean.</td></tr>
<tr><td class="row">StrToInt64</td><td class="row">Konwertuje typ String na Int64.</td></tr>
</tbody>
</table>
</div>
Przyznasz, że tych funkcji jest sporo. Nie musisz ich wszystkich pamiętać — zawsze możesz sięgnąć do tej książki. Jednak większość z tych nazw jest intuicyjna i stanowi tylko skrót do konwertowanych typów.
Rzutowanie
========
Przy rzutowaniu należy zachować szczególną ostrożność. Jest to bowiem sposób na „oszukanie” kompilatora. Jeżeli nie jesteś pewien, co robisz, możesz w konsekwencji doprowadzić do wystąpienia poważnych błędów podczas działania programu.
Najlepiej omówić to na przykładzie. Oto prosty kod źródłowy, który na pewno nie zostanie prawidłowo skompilowany:
```delphi
var
VarC : Char;
VarB : Byte;
begin
VarC := 'A';
VarB := VarC;
end.
```
Dane w postaci [[Delphi/Char]] (pojedynczy znak) próbujemy tu przypisać do zmiennej VarB, która jest zmienną typu [[Byte]]. Oczywiście kompilator wskaże błąd: <i>[Error] typcast.dpr(12): Incompatible types: 'Byte' and 'Char'</i>. Po drobnej modyfikacji cały program zostanie prawidłowo skompilowany i zadziała bez problemu:
```delphi
var
VarC : Char;
VarB : Byte;
begin
VarC := 'A';
VarB := Byte(VarC); // <—— rzutowanie
end.
```
Rzutowaniem jest właśnie przypisanie danych w ten sposób: Byte(VarC). W takim wypadku rzutujemy typ [[Delphi/Char]] na Byte, w wyniku czego zmienna VarB będzie posiadać wartość 65 (kod ASCII litery A).
Kolejny przykład — tym razem nieco praktyczniejszy, z którego pewnie nieraz skorzystasz. W module Windows zadeklarowana jest bardzo przydatna funkcja [[Delphi/MessageBox]]. Podobnie jak polecenie [[ShowMessage]], wyświetla ona okienka informacyjne. Różnica polega na tym, że w przypadku tej funkcji mamy większą kontrolę nad wyglądem okienka. Nie to jest jednak najważniejsze. Jako parametry tej funkcji należy podać dwie zmienne typu [[PChar]]. Typ [[PChar]] jest również typem zmiennych, który umożliwia przechowywanie tekstu, lecz z punktu widzenia kompilatora są to osobne rodzaje danych. Jednak w tym wypadku rzutowanie typów nie przyniesie żadnych niechcianych efektów:
```delphi
program PCharToStr;
uses Windows;
var
lpMessage, lpCaption : String;
begin
lpMessage := 'Okienko informacyjne';
lpCaption := 'Zamknięcie programu';
MessageBox(0, PChar(lpMessage), PChar(lpCaption), MB_OK);
end
```
.
Pierwszy parametr funkcji [[Delphi/MessageBox]] to tzw. uchwyt okna macierzystego. Na razie się nim nie przejmuj — możesz w tym miejscu wpisać cyfrę 0. Kolejny parametr to tekst znajdujący się w okienku informacyjnym. Jak widzisz, konieczne jest rzutowanie na typ [[PChar]]. Trzecim parametrem jest tytuł okienka (napis na pasku tytułowym), a ostatni parametr określa przyciski znajdujące się w oknie.
Więcej na temat funkcji [[Delphi/MessageBox]] możesz dowiedzieć się z pliku pomocy Delphi. Ja chciałem wspomnieć tylko o tym, że ostatnie parametry mogą być ze sobą „łączone” za pomocą operatora +. Na przykład takie wywołanie:
```delphi
MessageBox(0, PChar(lpMessage), PChar(lpCaption), MB_YESNOCANCEL + MB_ICONWARNING );
```
spowoduje pojawienie się przycisków Yes, No, Cancel oraz ikonki ostrzeżenia (ang. warning).
Pętle
====
W światku programistów pod słowem pętla kryje się pojęcie oznaczające ciągłe wykonywanie tych samych czynności. Jest to bardzo ważny element każdego języka programowania, dlatego konieczne jest zrozumienie istoty działania tego elementu.
Wyobraź sobie sytuację, w której musisz kilka razy wykonać tę samą czynność, na przykład wyświetlenie kilku linii tekstu. Zamiast kilkakrotnie pisać instrukcję Writeln, można skorzystać z pętli.
Pętla for..do
---------------
Jest to chyba najprostsza z możliwych pętli. Używaj jej zawsze wtedy, gdy wiesz dokładnie, ile wykonań (iteracji) danej czynności chcesz zastosować. Podczas korzystania z pętli for musisz zadeklarować zmienną, która za każdym wykonaniem danej czynności będzie przybierać wartość aktualnej iteracji. Żeby to lepiej zrozumieć, spójrz na przykładową budowę pętli:
```delphi
for Zmienna := 1 to 10 do { instrukcje }
```
Pierwszą instrukcją musi być słowo [[Delphi/for]]. Po nim następuje nazwa zmiennej, która musi przybrać wartość początkową. Następnie kolejne słowo kluczowe — to, po którym następuje wartość końcowa pętli. Powyższa konstrukcja spowoduje 10-krotne wykonanie poleceń umieszczonych po słowie do. Podsumowując, budowa pętli przedstawia się następująco:
```delphi
for Zmienna := {Wartość początkowa} to {Wartość końcowa} do {instrukcje }
```
Przykładowy program wyświetla na ekranie konsoli następujący tekst:
<tt>Odliczanie 1
Odliczanie 2
...</tt>
W celu zrealizowania tego zadania bez korzystania z pętli należałoby 10 razy przepisać instrukcję [[Delphi/Writeln]], co jest po prostu stratą czasu.
```delphi
program ForLoop;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
I : Integer; // deklaracja zmiennej
begin
for I := 1 to 10 do
Writeln('Odlicznie... ' + IntToStr(i));
Readln;
end.
```
Uruchom program i sprawdź jego działanie. Pozmieniaj wartość początkową i końcową, aby sprawdzić, jakie będzie zachowanie programu.
### Odliczanie od góry do dołu
Pętla, jaką przedstawiłem wyżej, realizuje odliczanie od dołu (wartości mniejszej) do góry (wartość wyższa). Istnieje możliwość odliczania odwrotnego, czyli od wartości wyższej do mniejszej. W tym celu słowo kluczowe to należy zastąpić słowem downto. Wystarczy zatem drobna zmiana pętli:
```delphi
for I := 10 downto 1 do
Writeln('Odlicznie... ' + IntToStr(i));
```
Teraz program wykona pętlę z wartością początkową równą 10.
Z pętlą [[Delphi/for]] wiąże się jedno ostrzeżenie kompilatora. Otóż w przypadku, gdy pętla [[for]] znajduje się wewnątrz procedury lub funkcji, zmienna pomocnicza musi być zmienną lokalną. Przykładowo próba skompilowania takiego kodu:
```delphi
var
I: Integer; // deklaracja zmiennej
procedure Loop;
begin
for I := 0 to 100 do { instrukcje }
end;
```
wiąże się z wystąpieniem ostrzeżenia: <i>[Warning] LoopBreak.dpr(10): For loop control variable must be simple local variable</i>. Kompilator próbuje Cię ostrzec, iż zmienna (w tym przypadku <var>I</var>) może być modyfikowana przez inne procedury, a to nie jest zalecane. Umieszczenie deklaracji zmiennej wewnątrz procedury `Loop` pozwoli na prawidłową kompilację programu.
Pętla while..do
-----------------
Niekiedy nie jesteś w stanie określić, ile iteracji będzie wymagała pętla; może nie będzie wymagana żadna iteracja, a może potrzebne będą ich setki — W takim przypadku należy skorzystać z pętli [[Delphi/while]]. Budowa takiej pętli jest następująca:
```delphi
while {Warunek do spełnienia} do
{ instrukcje }
```
Pętla będzie wykonywana, dopóki warunek zapisany pomiędzy słowami kluczowymi `while` i do nie zostanie spełniony.
Napiszmy prosty program, polegający na pobieraniu hasła dostępu. Jeżeli hasło będzie błędne, pętla zostanie wykonana po raz drugi; jeżeli hasło będzie poprawne — nastąpi zakończenie programu.
```delphi
program WhileLoop;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
Password : String; // deklaracja zmiennej
begin
while Password <> 'delphi7' do
begin
Writeln('Podaj hasło...');
Readln(Password);
end;
Writeln('Hasło poprawne!');
Readln;
end.
```
Omówię pokrótce program. Na samym początku umieszczamy warunek:
```delphi
while Password <> 'delphi7' do
```
Za jego pomocą sprawdzamy, czy zmienna <var>Password</var> jest różna od `delphi7` — jeżeli tak jest, następuje wykonanie instrukcji pętli znajdującej się w bloku begin..end. Jeżeli wpisane hasło jest niepoprawne, wykonywana zostaje kolejna iteracja; w przeciwnym wypadku pętla kończy swoje działanie.
Pętla repeat..until
-----------------------
Efekt zastosowania pętli repeat jest bardzo podobny do działania pętli [[Delphi/while]] — pętla ta także może być wykorzystywana w „nieskończoność”. Jedyna różnica polega na tym, że w pętli repeat warunek zakończenia sprawdzany jest dopiero na końcu wykonania. Oznacza, to, że pętla repeat zawsze będzie wykonana co najmniej raz. Dopiero po tej iteracji program sprawdzi, czy można „wyjść” z pętli. W przypadku pętli [[while]] warunek sprawdzany jest bezpośrednio przed jej wykonaniem, co w rezultacie może spowodować, że taka pętla nigdy nie zostanie wykonana.
Budowa pętli [[Delphi/repeat]] jest następująca:
```delphi
repeat
{ instrukcje do wykonania }
until { warunek zakończenia }
```
Pętla repeat, użyta w poprzednim przykładzie, wyglądałaby następująco:
```delphi
repeat
Writeln('Podaj hasło...');
Readln(Password);
until Password = 'delphi7';
```
Rezultat działania byłby identyczny.
Z pętlami wiąże się niebezpieczeństwo „zapętlenia”. Należy uważać, aby program nie wykonywał stale tych samych czynności — może do tego dojść, jeżeli warunek zakończenia nigdy nie zostanie spełniony.
Procedura Continue
----------------------
Polecenie [[Delphi/Continue]] może być używane tylko wraz z pętlami. Powoduje ono przejście do następnego wywołania pętli bez wykonywania dalszych instrukcji.
Oczywiście najlepiej istotę działania procedury [[Delphi/Continue]] poznasz na przykładzie. Załóżmy, że mamy pętlę [[Delphi/for]], która zostanie wykonana 10 razy. Za każdym razem program losuje jakąś liczbę z przedziału od 1 do 3 i na podstawie tej wylosowanej liczby wyświetla jakiś tekst. Dzięki poleceniu [[Delphi/Continue]] możemy sprawić, aby w przypadku, gdy wylosowaną liczbą będzie 1, ominąć wyświetlenie tekstu i przejść do następnej iteracji (listing 2.11.).
Listing 2.11. Zastosowanie instrukcji Continue w pętli for
```delphi
program LoopContinue;
{$APPTYPE CONSOLE}
var
I, Number : Integer; // deklaracja zmiennej
begin
Randomize;
for I := 1 to 10 do
begin
Number := Random(3)+1;
if Number = 1 then Continue;
case Number of
1: Writeln('Uuu, wylosowałeś 1');
2: Writeln('No, dwa... jeszcze może być');
3: Writeln('Dobrze');
end;
end;
Readln;
end.
```
Interesujący nas warunek znajduje się w tym miejscu: `if Number = 1 then Continue`;. Kompilator odczytuje to tak: jeżeli zmienna Number zawiera wartość 1, pomiń wykonywanie dalszych instrukcji i przejdź od razu do kolejnej iteracji.
Procedura Break
--------------------
Polecenie [[Delphi/Break]] również może być wykonane tylko w połączeniu z pętlami. W odróżnieniu od procedury [[Continue]] umożliwia ono wyjście z pętli (opuszczenie jej). Po napotkaniu instrukcji Break dalsze wykonywanie pętli zostaje wstrzymane, a program wykonuje polecenia umieszczone po pętli.
Zmodyfikujmy ostatni przykład, zastępując procedurę Continue poleceniem Break:
```delphi
program LoopBreak;
{$APPTYPE CONSOLE}
var
I, Number : Integer; // deklaracja zmiennej
begin
Randomize;
for I := 1 to 10 do
begin
Number := Random(3)+1;
if Number = 1 then
begin
Writeln('Wylosowano 1 — opuszczamy pętle...');
Break;
end;
case Number of
1: Writeln('Uuu, wylosowałeś 1');
2: Writeln('No, dwa... jeszcze może być');
3: Writeln('Dobrze');
end;
end;
Readln;
end.
```
Jeżeli program wylosuje cyfrę 1, program wyświetli stosowną informację i zakończy działanie pętli. Żaden kod umieszczony poniżej instrukcji [[Delphi/Break]] nie zostanie wykonany.
Zbiory
====
Zbiory są kolekcją danych tego samego typu. To zdanie zapewne niezbyt wyjaśnia funkcję zbiorów, spójrz więc na ten kod:
```delphi
type
TCar = (tcFiat, tcSkoda, tcOpel, tcFerrari, tcPorshe, tcPeugeot);
TCarSet = set of TCar;
```
W drugim wierszu znajduje się deklaracja nowego typu danych — `TCar`. Zmienna korzystająca z tego typu może zawierać jedną z wartości podanych w nawiasie. Natomiast typ `TCarSet` jest zbiorem danych `TCar`. Nowy zbiór deklaruje się za pomocą konstrukcji `set of`. Jak już mówiłem, zbiory są konstrukcją mogącą zawierać elementy określonych danych. Znaczy to, że zmienna korzystająca z typu `TCarSet` może zawierać np. wszystkie elementy lub tylko kilka spośród nich. Przy wykorzystaniu zwykłych zmiennych nie jest to możliwe, gdyż do takiej zmiennej można przypisać tylko jeden element `TCar`.
Możliwa jest także deklaracja bezpośrednia, czyli deklaracja bez tworzenia dodatkowego typu TCar:
```delphi
type
TCarSet = set of (tcFiat, tcSkoda, tcOpel, tcFerrari, tcPorshe, tcPeugeot);
```
Zbiory mogą być również zbiorami liczbowymi lub zawierającymi pojedyncze znaki:
```delphi
program Sets;
{$APPTYPE CONSOLE}
type
TCarSet = set of (tcFiat, tcSkoda, tcOpel, tcFerrari, tcPorshe, tcPeugeot);
TNumberSet = set of 0..9;
TCharSet = set of 'A'..'Z';
```
Przypisywanie elementów zbioru
---------------------------------------
Chcąc przypisać elementy zbioru do danej zmiennej, trzeba skorzystać z nawiasów kwadratowych.
```delphi
var
CarSet : TCarSet;
begin
CarSet := [tcSkoda, tcOpel];
```
Powyższy przykład powoduje przypisanie do zbioru dwóch elementów — `tcSkoda` i tcOpel. Możliwe jest oczywiście stworzenie kilku zmiennych korzystających z danego zbioru:
```delphi
var
Tanie,
Srednie,
Drogie : TCarSet;
begin
Tanie := [];
Srednie := [tcFiat, tcSkoda, tcOpel, tcPeugeot];
Drogie := [tcPorshe, tcFerrari];
end.
```
Do zmiennej <var>Tanie</var> przypisujemy zbiór pusty — po prostu nie zawiera on elementów, symbolizują go więc jedynie dwa nawiasy.
Odczytywanie elementów ze zbioru
------------------------------------------
Wraz ze zbiorami często używany jest operator [[Delphi/in]]. Służy on do sprawdzania, czy dany element należy do określonego zbioru. Przykładowo:
```delphi
if (tcFiat in Cars) then { czynności }
```
Zwróć uwagę na konstrukcję. Na początku należy wpisać nazwę elementu, a dopiero później zmienną wskazującą na zbiór. Jeżeli dany element należy do zbioru, wykonany zostanie kod znajdujący się po słowie [[Delphi/then]].
### Zaprzeczanie
Możesz zapytać: —co stanie się, gdy chcemy sprawdzić, czy dany element nie należy do zbioru ??. W takim wypadku zamiast nie możemy operatora in wpisać [[Delphi/out]], ale możliwe jest zastosowanie operatora [[not]], który jest zaprzeczeniem (o operatorach pisałem nieco wyżej).
```delphi
if not (tcFiat in Cars) then { czynności }
```
Jeżeli element `tcFiat `nie należy do zbioru `Cars`, warunek zostanie spełniony.
### Przekazywanie zbioru jako parametru procedury
Często podczas programowania w Object Pascalu natkniesz się na konstrukcję, która wymaga przekazania zbioru jako parametru procedury. Jeżeli więc podczas kompilacji wyświetlony zostanie błąd: <i>[Error] Sets.dpr(20): Incompatible types: 'TCarSet' and 'Enumeration'</i>, będzie to oznaczało, że podany parametr musi być zbiorem, czyli musi być przekazany w nawiasach kwadratowych. Oto przykład takiego programu:
```delphi
program Sets;
{$APPTYPE CONSOLE}
type
TCarSet = set of (tcFiat, tcSkoda, tcOpel, tcFerrari, tcPorsche, tcPeugeot);
procedure CoKupujemy(Cars : TCarSet);
begin
if (tcFiat in Cars) then Writeln('Kupujemy Fiata!');
if (tcSkoda in Cars) then Writeln('Kupujemy Skode!');
if (tcOpel in Cars) then Writeln('Kupujemy Opla!');
if (tcFerrari in Cars) then Writeln('Kupujemy Ferrari!');
if (tcPorsche in Cars) then Writeln('Kupujemy Porsche!');
if (tcPeugeot in Cars) then Writeln('Kupujemy Peugeota!');
end;
begin
CoKupujemy([tcPorsche, tcFerrari]);
Readln;
end.
```
Dodawanie i odejmowanie elementów zbioru
----------------------------------------------------
W celu dodania do zbioru lub odjęcia z niego jakiegoś elementu można skorzystać z operatorów + i —. Trzeba to jednak zapisać w specyficzny sposób:
```delphi
CarSet := CarSet + [tcFiat];
```
Za pomocą tego polecenia dodajemy do zbioru element tcFiat. Taka konstrukcja jest wymagana, gdyż gdybyśmy napisali tak:
```delphi
CarSet := [tcFiat];
```
spowodowałoby to „wymazanie” elementów poprzednio znajdujących się w zbiorze i dodanie jedynie `tcFiat`.
### Include i Exclude
Zalecaną metodą dodawania oraz odejmowania elementów zbioru są funkcje [[Delphi/Include]] oraz [[Exclude]]. Pierwsza z nich włącza element do zbioru, a druga odejmuje. Ich użycie jest zalecane ze względu na to, że działają o wiele szybciej niż operacje z zastosowaniem znaków + i — . Przykład użycia:
```delphi
Include(CarSet, tcFiat); // dodawanie
Exclude(CarSet, tcPorshe); // odejmowanie
```
Wskaźniki
=======
Wskaźniki to najtrudniejsza do opanowania czynność programistyczna — zarówno w Object Pascalu, jak i w innych językach programowania.
Zazwyczaj podczas przypisywania danych do zmiennej Delphi rezerwuje obszar w komórkach pamięci i tam umieszcza dane zmiennej. Gdy chcemy w programie odwołać się do zmiennej, Delphi — na podstawie jej nazwy — odszukuje komórkę pamięci, w której umieszczone są dane.
<dfn>Wskaźniki to zmienne, które wskazują na inną zmienną.</dfn>
Zapewne powyższa wskazówka niewiele Ci wyjaśnia. Wskaźniki to specjalny typ danych — w pamięci nie są przechowywane dane jako takich, lecz jedynie odpowiadające im adresy komórki pamięci.
Tworzenie wskaźnika
---------------------------
Zadeklarowania wskaźnika dokonuje się za pomocą operatora (^).
```delphi
var
P : ^String;
```
Od tego momentu w programie możemy korzystać ze wskaźnika <var>P</var>, wskazującego typ [[Delphi/String]]. We wszelkich operacjach dokonywanych na wskaźnikach muszą być wykorzystywane dwa operatory specyficzne jedynie dla typów wskaźnikowych — są to operatory ^ oraz @. Ich znaczenie poznasz w dalszej części tego rozdziału.
Przydział danych do wskaźników
--------------------------------------
Na samym początku przeprowadźmy pewien test. Spróbuj uruchomić taki program:
```delphi
program Pointers;
var
P : ^String;
begin
P^ := 'Delphi 7';
end.
```
Program próbuje przypisać określone dane do wskaźnika w postaci łańcucha tekstowego; musi to się odbyć z wykorzystaniem operatora ^, w przeciwnym wypadku Delphi zasygnalizuje błąd: [<i>Error] Pointers.dpr(12): Incompatible types: 'String' and 'Pointer'</i>.
Próba uruchomienia takiego programu zakończy się jednak błędem typu Runtime (patrz rysunek 2.2).
![2.2.jpg](//static.4programmers.net/uploads/attachment/4ccd36d6a9e33.jpg)
Rysunek 2.2. Komunikat o błędzie wyświetlony po uruchomieniu programu
Przyczyną błędu jest fakt, że do typu wskaźnikowego nie można przypisać w normalny sposób wartości. Wskaźniki muszą uprzednio wskazywać na inną, zwykłą zmienną.
<dfn>Przydzielaniem danych bezpośrednio do wskaźnika zajmiemy się w dalszej części tego rozdziału.</dfn>
Poniższy program zostanie skompilowany i, co najważniejsze, zadziała bez problemu:
```delphi
program Pointers;
var
S : String;
P : ^String;
begin
S := 'Delphi'; // przypisanie danych do zwykłej zmiennej
P := @S; // uzyskanie adresu zmiennej
P^ := 'Borland Delphi 7 Studio'; // modyfikacja danych
end.
```
### Uzyskiwanie adresów zmiennej
Jak widać w powyższym przykładzie, po zadeklarowaniu zmiennej S (typu String) wskaźnik musi uzyskać jej adres. Realizuje to operator @. Od tego momentu wskaźnik P wskazuje na zmienną S. Poniższa instrukcja:
```delphi
P^ := 'Borland Delphi 7 Studio';
```
w rzeczywistości spowoduje zmianę wartości zmiennej <var>S</var>! Dzieje się tak dlatego, że wskaźnik P wskazuje na zmienną S. A zatem zmieniając wartość wskaźnika, w rzeczywistości zmieniamy wartość zmiennej! Możesz to sprawdzić, dodając na końcu programu jeszcze jedną instrukcję:
```delphi
MessageBox(0, PChar(S), '', 0);
```
W okienku informacyjnym będzie widniał napis `Borland Delphi 7 Studio`.
Do czego to służy?
--------------------
To jest dobre pytanie! Można się zastanowić, do czego służą wskaźniki? . Założenie jest takie, że podczas tworzenia jakichś struktur — zarówno tablic, jak i rekordów — nie jest konieczne manipulowanie wielkimi blokami pamięci. Wystarczy tylko stworzyć wskaźnik tego rekordu i ewentualnie modyfikować w ten sposób dane, zamiast tworzyć kolejną instancję (kopię) rekordu.
### Tworzenie wskaźników na rekordy
Właśnie teraz przedstawię Ci przykład tego, o czym mówiłem wcześniej. Podczas tworzenia jakiegoś rekordu wskazane jest utworzenie nowego typu wskaźnikowego, wskazującego na ten rekord. Po wypełnieniu danych do procedury jest przekazywany jedynie wskaźnik tego rekordu:
```delphi
program PRecApp;
uses
Dialogs;
type
TInfoRec = packed record
FName : String[30];
SName : String[30];
Age : Byte;
Pesel : Int64;
Nip : String[60]
end;
PInfoRec = ^TInfoRec; // utworzenie wskaźnika
procedure SomeProc(InfoRec : PInfoRec);
begin
ShowMessage('Dotychczasowa wartość InfoRec.FName to ' + InfoRec.FName + '. Zmieniam na Adam');
InfoRec.FName := 'Adam'; // zmiana danych
end;
var
InfoRec: TInfoRec;
begin
InfoRec.FName := 'Jan';
InfoRec.SName := 'Kowalski';
InfoRec.Age := 41;
InfoRec.Pesel := 55012010013;
InfoRec.Nip := '34234—23432—23423';
SomeProc(@InfoRec);
ShowMessage(InfoRec.FName); // wyświetlenie zmienionej wartości
end.
```
Wskaźnik odczytuje dane rekordu InfoRec z pamięci — możliwa jest także zamiana tych danych, co zaprezentowałem w powyższym listingu.
Przydział i zwalnianie pamięci
-----------------------------------
Na samym początku omawiania wskaźników zaprezentowałem przykład, w którym próbowaliśmy przydzielić dane do wskaźnika. Uruchomienie tamtego programu skończyło się błędem, dlatego że nie zaalokowaliśmy pamięci dla tych wskaźników. Pamięć można zaalokować (przydzielić) za pomocą funkcji [[Delphi/New]].
<dfn>Stos to cały obszar pamięci rezerwowany dla aplikacji w trakcie jej uruchamiania.</dfn>
<dfn>
Sterta to cała dostępna pamięć komputera oraz ilość wolnego miejsca na dysku komputera.</dfn>
Po wywołaniu funkcji [[Delphi/New]] program automatycznie alokuje w sposób dynamiczny pamięć dla rekordu. Po skończeniu pracy z rekordem pamięć należy zwolnić za pomocą polecenia [[Dispose]] (listing 2.12.)
Listing 2.12. Przydział pamięci dla rekordu
```delphi
program NewPointer;
uses
Dialogs;
type
TInfoRec = packed record
FName : String[30];
SName : String[30];
Age : Byte;
Pesel : Int64;
Nip : String[60]
end;
PInfoRec = ^TInfoRec; // utworzenie wskaźnika
var
InfoRec: PInfoRec;
begin
New(InfoRec);
InfoRec^.FName := 'Jan';
InfoRec^.SName := 'Kowalski';
InfoRec^.Age := 41;
InfoRec^.Pesel := 55012010013;
InfoRec^.Nip := '34234—23432—23423';
ShowMessage(InfoRec^.FName); // wyświetlenie zmienionej wartości
Dispose(InfoRec);
end.
```
W celu zaalokowania pamięci można posłużyć się także procedurami [[Delphi/GetMem]] i [[FreeMem]]. Funkcja GetMem wymaga wpisania dodatkowego parametru, jakim jest ilość bajtów przeznaczonych do alokacji. Dane te uzyskujemy, wywołując funkcję [[SizeOf]] — np.:
```delphi
GetMem(InfoRec, SizeOf(InfoRec));
```
Zalecane jest jednak użycie funkcji New i Dispose.
Wartość pusta
-----------------
Nieraz podczas programowania spotkasz się z instrukcją [[Delphi/nil]]. Instrukcja ta używana jest wraz ze wskaźnikami i oznacza wartość pustą.
```delphi
Wskaznik := nil;
```
Taki zapis spowoduje, że do wskaźnika nie będą aktualnie przypisane żadne wartości.
Pliki dołączane
=========
Idea plików dołączanych jest bardzo prosta — polega na włączeniu w odpowiednim miejscu modułu pliku tekstowego, który jest traktowany jak integralna jego część.
Plik dołączany to nic innego, jak zwykły plik tekstowy. Z menu <i>File</i> wybierz pozycje New/Other. W oknie dialogowym kliknij ikonę Text. W Edytorze kodu pojawi się nowa zakładka — zapisz ten plik pod nazwą SHOW.INC, ale uprzednio wpisz w Edytorze kodu prostą instrukcję:
```delphi
ShowMessage('Hello World!');
```
Plik główny DPR powinien wyglądać tak:
```delphi
program IncludeInc;
uses Dialogs;
begin
{$I SHOW.INC}
end.
```
Dzięki dyektywie `{$I}` można włączyć plik do programu; będzie to równoważne wstawieniu w tym miejscu zawartości owego pliku SHOW.INC.
Etykiety
=====
Chociaż wielu programistów sprzeciwia się używaniu etykiet, ja omówię tutaj tę technologię, gdyż nieraz możesz być zmuszony do skorzystania z niej.
Etykiety, mówiąc prosto, to miejsce w kodzie (zakładka), do którego można zawsze przejść (przeskoczyć). Najpierw jednak taką etykietę należy zadeklarować — np. tak, jak zmienne, tyle że za pomocą słowa kluczowego label.
```delphi
label
Moja_Etykieta;
```
Dzięki takiej deklaracji kompilator „wie”, że ma do czynienia z etykietą. Przejście do takiej etykiety ogranicza się do wywołania słowa kluczowego `goto Nazwa_Etykiety`;
```delphi
program Labels;
{$APPTYPE CONSOLE}
uses SysUtils;
label
Moja_Etykieta;
var
I : Integer;
begin
Randomize;
Moja_Etykieta: I := Random(10);
Writeln('Wylosowałem ' + IntToStr(I));
if I = 5 then goto Moja_Etykieta; // jeźeli komputer wylosuje 5 - ponów losowanie
Readln;
end.
```
Samą „zakładkę” ustawia się, wpisując jej nazwę, a po dwukropku dalszą część kodu. Powyższy program realizuje losowanie liczby; jeżeli wylosowana zostanie liczba 5, program przechodzi do ustawionej wcześniej etykiety.
Podsumowanie
==========
Przyznam szczerze, że dla Ciebie mógł to być trudny, a zarazem niezbyt ciekawy rozdział tej książki. Cóż, przed przystąpieniem do tworzenia poważniejszych aplikacji w Delphi należało poznać podstawową składnię. Teraz masz już solidne podstawy do dalszej nauki. Nie martw się, jeżeli nie zrozumiałeś wszystkiego; jest to naturalne. Wiadomo, że nie jesteś w stanie w tak krótkim czasie zapamiętać wszystkiego naraz. Powracaj do tego rozdziału w razie, gdy czegoś zapomnisz.
<b>Załączniki:</b>
* [Listingi_2.zip](//4programmers.net/Download/1937/154)
<table style="width: 80%; margin: 0 auto 0 auto; border: none; background: none;">
<tr>
<td style="width: 40%; text-align: right; background: none;"><a href="http://helion.pl/view/260w/de25kp"><img src="http://helion.pl/okladki/120x156/de25kp.jpg" /></a></td>
<td style="width: 60%; padding: 5px; background: none;">
<b style="font-size: 14px">Więcej informacji</b><br /><br />
<span style="font-size: 14px; font-weight: bold"><a href="http://helion.pl/view/260w/de25kp">Delphi 2005. Kompendium programisty</a></span><br />
<a href="http://adam.boduch.net">Adam Boduch</a><br />
Format: <b>B5</b>, stron: <b>1048</b><br />
oprawa twarda<br />
Zawiera CD-ROM <br />
</td>
</tr>
</table>
<div style="width: 30%; padding: 5px; text-align: left; float: left">
<a href="/Delphi/Kompendium/Rozdział_1">« Rozdział 1. Podstawy Delphi.</a>
</div>
<div style="width: 30%; padding: 5px; text-align: center; float: left">
<b>[[Delphi/Kompendium|Spis treści]]</b>
</div>
<div style="width: 30%; padding: 5px; text-align: right; float: right">
<a href="/Delphi/Kompendium/Rozdział_3">Rozdział 3. Programowanie obiektowe »</a>
</div>
<br style="clear: both" />
<div style="border-top: 1px solid #ccc; font-size: 10px;"><i>[[Delphi/Kompendium/Prawa autorskie|©]] Helion 2003. Autor: <a href="http://adam.boduch.net">Adam Boduch</a>. Zabrania się rozpowszechniania tego tekstu bez zgody autora.</i></div>
Polecam każdemu początkującemu. Świetnie wytłumaczone i (co dla mnie ważne) każda rzecz umieszczona jest w idealnym miejscu. Nie ma nawału informacji na jeden temat tylko wszystko stopniowo, dokładnie poznajemy w odpowiednim czasie. :)
Pkt 2.3 Bloki begin i end
„ ...
Pamiętaj, aby ilość instrukcji begin była równa ilości instrukcji end — w przeciwnym razie kompilator
wyświetli błąd: [Error] dprMin.dpr(9): 'END' expected but end of file found.”
Nie jest to prawdą, bo instrukcję „case” również kończy „end”.
Ja bym napisał tak: należy dbać, aby instrukcje, które „tworzą pary”, takie jak begin - end,
zawsze miały „swoje odpowiedniki”.
Świetna książka, jak narazie jedynie pierwszy rozdział, ale chyba jak na początkującego lepiej trafić nie mogłem. Pozdrawiam serdecznie!
procedure TForm1.Button1Click(Sender: TObject);
label
etykieta;
var
i:integer;
begin
etykieta:
beep;
goto etykieta;
end;
nie wiem dlaczego ale coś nie moge poradzic sobie z Label / etykiety , caly czas jakis blad. nie moge skompilowac.
mam taki kod procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
label
etykieta;
goto etykieta;
end;
nie dziala
Witam!
W tabelce z operatorami jest błąd... O ile się nie mylę to w tej linijce:
"> Większe od — sprawdza, czy jedna wartość z podanych zmiennych jest większa od drugiej"
zamiast ">" powinno być "<" albo w linijce niżej jest błąd... :)
Pozdrawiam... I3L4D3... ;]
cytat:
"Pętla będzie wykonywana, dopóki warunek zapisany pomiędzy słowami kluczowymi while i do nie zostanie spełniony."
Zdeka odwrotnie!
Writeln();i readln();nie działa oddoje I/O Erro 105
"Byte „zżera” jedynie 2 bajty" ; oczywiscie 1 bajt a nie dwa :) chyba ze o czyms nie wiem....