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

NazwaOpis
Integer-2 147 483 648 — 2 147 483 647
Int64-263 — 263 - 1
SmallInt-32 768 — 32 767
ShortInt-128 — 127
Byte0 — 255
Word0 — 65 535
LongWord0 — 4 294 967 295
Charpojedynczy znak
BooleanTRUE lub FALSE
ShortString255 znaków
AnsiString231 znaków
String231 znaków
Extended3,6 × 10-4951 — 1,1 × 104932
Double5,0 × 10-324 — 1,7 × 10308
Single1,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>

9 komentarzy

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....