Programowanie w języku Delphi » Delphi 7. Kompendium programisty

Rozdział 2. Język Object Pascal

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.

Spis treści

     1 Plik źródłowy projektu
          1.1 Najprostszy program
     2 Podstawowa składnia
          2.1 Wielkość liter
          2.2 Pamiętaj o średniku!
          2.3 Bloki begin i end
          2.4 Dyrektywa program
     3 Komentarze
     4 Zmienne
          4.1 Deklaracja zmiennych
          4.2 Typy zmiennych
          4.3 Deklaracja kilku zmiennych
          4.4 Przydział danych
               4.4.1 Przydział dynamiczny
     5 Stałe
          5.1 Domyślne typy stałych
     6 Używanie stałych i zmiennych w programie
     7 Tablice danych
          7.1 Tablice jako stałe
          7.2 Tablice wielowymiarowe
          7.3 Tablice dynamiczne
               7.3.1 Polecenia Low i High
     8 Operatory
     9 Aplikacje konsolowe
     10 Instrukcje warunkowe
          10.1 Instrukcja if..then
               10.1.1 Pobieranie tekstu z konsoli
               10.1.2 Kilka instrukcji po słowie then
               10.1.3 Kilka warunków do spełnienia
          10.2 Instrukcja case..of
               10.2.1 Zakresy
               10.2.2 Brak możliwości korzystania z łańcuchów
               10.2.3 Kilka instrukcji
          10.3 Instrukcja else
               10.3.1 Kiedy stosować średnik, a kiedy nie ?
               10.3.2 Kilka instrukcji if i else
               10.3.3 Kilka instrukcji po słowie begin
               10.3.4 Instrukcja else w case..of
     11 Procedury i funkcje
          11.1 Procedury
     12 Funkcje
          12.1 Zmienne lokalne
          12.2 Parametry procedur i funkcji
               12.2.1 Kilka parametrów procedur lub funkcji
          12.3 Parametry domyślne
               12.3.1 Parametry tego samego typu a wartości domyślne
               12.3.2 Kolejność wartości domyślnych
          12.4 Przeciążanie funkcji i procedur
          12.5 Typy parametrów przekazywanych do procedur i funkcji
               12.5.1 Przekazywanie parametrów poprzez stałą
               12.5.2 Przekazywanie parametrów przez referencję
               12.5.3 Dyrektywa out
          12.6 Procedury zagnieżdżone
     13 Własne typy danych
          13.1 Tablice jako nowy typ
     14 Aliasy typów
     15 Rekordy
          15.1 Przekazywanie rekordów jako parametr procedury
          15.2 Deklaracja rekordu jako zmienna
          15.3 Instrukcja packed
     16 Instrukcja wiążąca with
     17 Moduły
          17.1 Tworzenie nowego modułu
          17.2 Budowa modułu
               17.2.1 Nazwa
               17.2.2 Sekcja Interface
               17.2.3 Sekcja Implementation
          17.3 Włączanie modułu
          17.4 Sekcja Initialization oraz Finalization
     18 Konwersja typów
     19 Rzutowanie
     20 Pętle
          20.1 Pętla for..do
               20.1.1 Odliczanie od góry do dołu
          20.2 Pętla while..do
          20.3 Pętla repeat..until
          20.4 Procedura Continue
          20.5 Procedura Break
     21 Zbiory
          21.1 Przypisywanie elementów zbioru
          21.2 Odczytywanie elementów ze zbioru
               21.2.1 Zaprzeczanie
               21.2.2 Przekazywanie zbioru jako parametru procedury
          21.3 Dodawanie i odejmowanie elementów zbioru
               21.3.1 Include i Exclude
     22 Wskaźniki
          22.1 Tworzenie wskaźnika
          22.2 Przydział danych do wskaźników
               22.2.1 Uzyskiwanie adresów zmiennej
          22.3 Do czego to służy?
               22.3.1 Tworzenie wskaźników na rekordy
          22.4 Przydział i zwalnianie pamięci
          22.5 Wartość pusta
     23 Pliki dołączane
     24 Etykiety
     25 Podsumowanie


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 (=).

program varApp;
 
var
  Zmienna1 : String = 'Oto zmienna nr 1';
 
begin
 
end.


Taki kod spowoduje, że na samym starcie programu zmienna Zmienna1 będzie miała wartość Oto zmienna nr 1.

Każdy tekst zadeklarowany w ramach zmiennej musi być ujęty w znaki apostrofów.


Podczas pisania programu nie możesz przydzielić wartości kilku zmiennym naraz:

program varApp;
 
var
  Zmienna1, Zmienna2 : String = 'Oto zmienna nr 1';
 
begin
 
end.


Próba uruchomienia takiego programu spowoduje wyświetlenie błędu: [Error] varApp.dpr(4): Cannot initialize multiple variables.

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.

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


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:

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:

program varConst;
 
const
  Stala1 = 'Oto jest stała...';
 
begin
 
end.


Stałe, w odróżnieniu od zmiennych, deklarujemy z użyciem słowa kluczowego 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ę:

const
  Stala2 = 12;


Delphi uzna, że stała jest stałą typu Integer (jest to domyślny typ stałych). Programista może to w dość prosty sposób zmienić:

program varConst;
 
const
  Stala1 = 'Oto jest stała...';
  Stala2 : Byte = 12;
 
begin
 
end.


A zatem w tym przypadku Stala2 będzie stałą typu Byte o wartości 12.

Jeżeli spróbujesz przypisać jakąś wartość stałej — przykładowo:

begin
  Stala1 := 'Inna wartość';
end.


Delphi uzna to za błąd i wyświetli podpowiedź dla Ciebie: [Error] varConst.dpr(8): Left side cannot be assigned to.

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 — ShowMessage. Jego użycie spowoduje wyświetlenie okienka informacyjnego. Z polecenia tego korzysta się w następujący sposób:

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


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:

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 begin następuje przypisanie danych zmiennej, a kolejnym krokiem jest wyświetlenie jej zawartości; następne okienko wyświetli natomiast zawartość stałej.

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.

program arrayApp;
 
uses
  Dialogs;
 
var
  Tablica : array[0..1] of String;
 
begin
 
end.


Konstrukcja tablic jest dość specyficzna. Po słowie kluczowym 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:

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:

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:

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:

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: [Error] arrayConst.dpr(5): Number of elements differs from declaration.

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:

Tablica[0][0] := 'Przypisanie danych';


W powyższym przypadku skorzystałem jedynie z tablic dwuwymiarowych, których deklaracja wygląda tak:

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:

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

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

program dynArray;
 
var
  Tablica : array of String;
 
begin
 
end.


Przy tej okazji poznasz nowe polecenie — 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:

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:

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 — Low i 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 (Low) oraz największemu (High) indeksowi tablicy.

Deklaracja tablicy może na przykład wyglądać w ten sposób:

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.

OperatorZnaczenie
=Porównanie — sprawdza, czy obie wartości są sobie równe
:=Przypisanie danych — jeden z najważniejszych operatorów
Różne od — sprawdza, czy obie wartości są od siebie różne
>Większe od — sprawdza, czy jedna wartość z podanych zmiennych jest większa od drugiej
>Mniejsze od — sprawdza, czy jedna wartość jest mniejsza od drugiej
>=Większe lub równe
<=Mniejsze lub równe
+Dodawanie
-Odejmowanie
*Mnożenie
/Dzielenie
divDzielenie z obcięciem reszty
modDzielenie z zachowaniem reszty z dzielenia
andPorównywanie typów — logiczne i
orPorównywanie typów — logiczne lub
notZaprzeczenie (stosowane podczas porównywania typów)
xorOperator bitowy — dysjunkcja
shlOperator bitowy — przesunięcie w lewo
shrOperator bitowy — przesunięcie w prawo


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 Inc oraz Dec. Funkcje te służą odpowiednio do zwiększania i zmniejszania wartości parametru. Są one równoważne następującym instrukcjom:

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

program appconsole;
 
{$APPTYPE CONSOLE}
 
begin
  Readln;
end.


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

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:

if { tu następuje sprawdzanie warunku } then { wykonaj pewną operację }


Po słowie if musi się znaleźć pewien warunek do sprawdzenia — przykładowo

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.

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ć:

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 Imie. Cel naszego ćwiczenia to sprawdzenie, czy wpisany tekst to Adam.
Zwróć także uwagę na ten wiersz kodu:

Writeln('Cześć ' + Imie);


Do wyświetlanego napisu Cześć dołączana jest także zawartość zmiennej Imie. 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
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:

  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 Writeln. Taki wypadek zmusza nas do umieszczenia dodatkowo słów kluczowych begin oraz 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 Imie 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
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ść Adam 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: [Error] if..then.dpr(14): Incompatible types: 'String' and 'Integer'.

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.

Pamiętaj, aby zawsze przed wywołaniem procedury 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:

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 end;, kończącym instrukcję warunkową. Przykładowy program zaprezentowałem w listingu 2.5.

Listing 2.5. Program wykorzystujący instrukcję case..of
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 Mandat 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:

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 Mandat 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:

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 S ma wartość . Niestety próba kompilacji takiego programu zakończy się błędem: [Error] case_strings.dpr(11): Ordinal type required.

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

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 else można w tym kontekście przetłumaczyć jako w przeciwnym wypadku. Konstrukcja else jest zawsze stosowana w połączeniu z instrukcją if..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:

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

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!


Kiedy stosować średnik, a kiedy nie ?


Poprzedni przykład z użyciem instrukcji if i else nie zawierał średnika. Kiedy stosować ten średnik, a kiedy nie —  Spójrz — taki fragment kodu już wymaga postawienie średnika przed else:

  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!

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

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 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. else lub then) wystąpi więcej niż jedna instrukcja, cały fragment kodu należy umieścić w dodatkowym bloku begin..end.

  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 else, ani przed nim.

Instrukcja else w case..of


Możliwe jest także zastosowanie słowa kluczowego 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
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 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


Procedura to wydzielony blok kodu, realizujący określone zadanie.

Procedurą, z której korzystałeś już na samym początku tego rozdziału, jest 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:

procedure Nazwa_Procedury;
begin
    { kod procedury }
end;


Jak widzisz, procedurę deklaruje się z użyciem słowa kluczowego 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.

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 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 function. Oprócz tego funkcja, jak już mówiłem, musi zwracać jakąś wartość — po znaku dwukropka należy wpisać typ zwracanej wartości:

  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ę
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:

Result := 'Adam Boduch';


Słowo 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”.

function GetValue : String;
var
  S : String;
begin
  S := 'Khem...';
  Result := S;
end;


Zasada deklarowania zmiennych jest taka sama — deklaracja umieszczana jest przed blokiem 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 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:

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

Przykładowy program wyświetli na konsoli za pomocą procedury SetName tekst z parametru Name:

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:

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:

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:

  function SetName(FName,
                   SName : String;
                   Age : Byte
                   ) : String;
  begin
 
  end;


Parametry domyślne


Pamiętasz, jak wspominałem o funkcjach 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:

  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:

SetName('Moje imię');


albo wpisanie wszystkich parametrów:

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 SName będzie to wartość Nieznany, a dla Age — 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:

  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:

  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: [Error] OverProc.dpr(18): Default value required for 'Y'. 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:

  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:

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

  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ą overload. Od tej chwili podczas wywoływania funkcji Mnozenie kompilator sam — na podstawie parametrów — ustali, jaką funkcję chcemy wywołać:

  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 const spowoduje przekazywanie parametrów jako stałych.

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: [Error] ConstParam.dpr(7): Left side cannot be assigned to.
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 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ę
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 S — 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:

procedure SetValue(var Message : String);
begin
{ próba nadania nowej wartości dla parametru }
  Message := 'Hello there!';
end;


Dzięki dodaniu słowa var i ponownemu uruchomieniu programu możesz przekonać się, że zmienna S 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 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:

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

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:

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

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:

var
  MediumValue : TMediumValue;


Można już korzystać z naszej zmiennej, tak jak z każdej innej:

begin
  MediumValue := 10;
end.


Możliwe jest także zadeklarowanie swojego własnego typu wyglądającego tak:

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:

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:

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:

Type
  TMojTyp = Integer;


Od tego momentu TMojTyp będzie równoważny typowi 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 record.

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

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
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 Readln, ale o tym mówiliśmy już na początku tego rozdziału.
W powyższym listingu skorzystałem z funkcji konwersji — 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ą:

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.

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:

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:

  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:

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

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: [Error] MainUnit.pas(1): Unit identifier 'MainUnitx' does not match file name.

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:

procedure ProcMain(Param1, Param2 : Integer);


Definicja to cały kod procedury lub funkcji, zawarty w sekcji 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):

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

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:

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  — Initialization oraz Finalization  — są opcjonalnymi sekcjami modułu.

Umieszczenie kodu bezpośrednio za sekcją 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
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 Initialization oraz Finalization używane są do przydzielania lub zwalniania danych (pamięci), przydzielanych w ramach konkretnego modułu.

Jeżeli w programie korzystasz z sekcji Finalization, to koniecznym staje się umieszczenie także sekcji Initialization. W przeciwnym wypadku próba uruchomienia programu zakończy się błędem: [Error] MainUnit.pas(17): Declaration expected but 'FINALIZATION' found. 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 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:
ShowMessage(IntToStr(Zmienna_Integer));


Polecenie IntToStr powoduje konwersję danych w postaci 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 uses.

NazwaOpis
IntToStrKonwertuje typ Integer na String
StrToIntKonwertuje typ String na Integer
CurrToStrKonwertuje typ Currency na String
StrToCurrKonwertuje typ String na Currency
DateTimeToStrKonwertuje typ TDateTime na String
StrToDateTimeKonwertuje typ String na TDateTime
DateToStrKonwertuje typ TDate na String
StrToDateKonwertuje typ String na TDate
TimeToStrKonwertuje typ TTime na String
TimeStrToTimeToStrKonwertuje typ String na TTime
FloatToStrKonwertuje typ Extended na String
StroToFloatKonwertuje typ String na Extended.
IntToHexKonwertuje typ Integer do postaci heksydemalnej.
StrPasKonwertuje typ String na PChar.
StringKonwertuje typ PChar na String.
PCharKonwertuje typ String na PChar.
StrToBoolKonwertuje typ String na Boolean.
StrToInt64Konwertuje typ String na Int64.


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:

var
  VarC : Char;
  VarB : Byte;
 
begin
  VarC := 'A';
  VarB := VarC;
end. 


Dane w postaci Char (pojedynczy znak) próbujemy tu przypisać do zmiennej VarB, która jest zmienną typu Byte. Oczywiście kompilator wskaże błąd: [Error] typcast.dpr(12): Incompatible types: 'Byte' and 'Char'. Po drobnej modyfikacji cały program zostanie prawidłowo skompilowany i zadziała bez problemu:

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

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

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:

for Zmienna := 1 to 10 do { instrukcje }


Pierwszą instrukcją musi być słowo 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:

for Zmienna := {Wartość początkowa} to {Wartość końcowa} do {instrukcje }


Przykładowy program wyświetla na ekranie konsoli następujący tekst:

Odliczanie 1
Odliczanie 2
...


W celu zrealizowania tego zadania bez korzystania z pętli należałoby 10 razy przepisać instrukcję Writeln, co jest po prostu stratą czasu.

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:

  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ą 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:

var
  I: Integer; // deklaracja zmiennej
 
  procedure Loop;
  begin
    for I := 0 to 100 do { instrukcje }
  end;


wiąże się z wystąpieniem ostrzeżenia: [Warning] LoopBreak.dpr(10): For loop control variable must be simple local variable. Kompilator próbuje Cię ostrzec, iż zmienna (w tym przypadku I) 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 while. Budowa takiej pętli jest następująca:

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

while Password <> 'delphi7' do


Za jego pomocą sprawdzamy, czy zmienna Password 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 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 repeat jest następująca:

repeat
  { instrukcje do wykonania }
until { warunek zakończenia }


Pętla repeat, użyta w poprzednim przykładzie, wyglądałaby następująco:

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 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 Continue poznasz na przykładzie. Załóżmy, że mamy pętlę 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 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
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 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:

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

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:

type
  TCarSet = set of (tcFiat, tcSkoda, tcOpel, tcFerrari, tcPorshe, tcPeugeot);


Zbiory mogą być również zbiorami liczbowymi lub zawierającymi pojedyncze znaki:
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.

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:

var
  Tanie,
  Srednie,
  Drogie : TCarSet;
 
begin
  Tanie := [];
  Srednie := [tcFiat, tcSkoda, tcOpel, tcPeugeot];
  Drogie := [tcPorshe, tcFerrari];
end.


Do zmiennej Tanie 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 in. Służy on do sprawdzania, czy dany element należy do określonego zbioru. Przykładowo:

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 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ć out, ale możliwe jest zastosowanie operatora not, który jest zaprzeczeniem (o operatorach pisałem nieco wyżej).

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: [Error] Sets.dpr(20): Incompatible types: 'TCarSet' and 'Enumeration', będzie to oznaczało, że podany parametr musi być zbiorem, czyli musi być przekazany w nawiasach kwadratowych. Oto przykład takiego programu:

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:

CarSet := CarSet + [tcFiat];


Za pomocą tego polecenia dodajemy do zbioru element tcFiat. Taka konstrukcja jest wymagana, gdyż gdybyśmy napisali tak:

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

Wskaźniki to zmienne, które wskazują na inną zmienną.

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 (^).

var
  P : ^String;


Od tego momentu w programie możemy korzystać ze wskaźnika P, wskazującego typ 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:

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: [Error] Pointers.dpr(12): Incompatible types: 'String' and 'Pointer'.

Próba uruchomienia takiego programu zakończy się jednak błędem typu Runtime (patrz rysunek 2.2).


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

Przydzielaniem danych bezpośrednio do wskaźnika zajmiemy się w dalszej części tego rozdziału.

Poniższy program zostanie skompilowany i, co najważniejsze, zadziała bez problemu:

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:

P^ := 'Borland Delphi 7 Studio';


w rzeczywistości spowoduje zmianę wartości zmiennej S! 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ę:
  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:

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

Stos to cały obszar pamięci rezerwowany dla aplikacji w trakcie jej uruchamiania.

Sterta to cała dostępna pamięć komputera oraz ilość wolnego miejsca na dysku komputera.


Po wywołaniu funkcji 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
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 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.:
GetMem(InfoRec, SizeOf(InfoRec));


Zalecane jest jednak użycie funkcji New i Dispose.

Wartość pusta


Nieraz podczas programowania spotkasz się z instrukcją nil. Instrukcja ta używana jest wraz ze wskaźnikami i oznacza wartość pustą.

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 File 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ę:

ShowMessage('Hello World!');


Plik główny DPR powinien wyglądać tak:

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.

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;

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.

Załączniki:

de25kp.jpg Więcej informacji

Delphi 2005. Kompendium programisty
Adam Boduch
Format: B5, stron: 1048
oprawa twarda
Zawiera CD-ROM





© Helion 2003. Autor: Adam Boduch. Zabrania się rozpowszechniania tego tekstu bez zgody autora.

9 komentarzy

mj28u 2014-06-26 20:29

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

SlotaWoj 2013-04-19 20:43

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

PeterZof 2012-06-05 22:38

Świetna książka, jak narazie jedynie pierwszy rozdział, ale chyba jak na początkującego lepiej trafić nie mogłem. Pozdrawiam serdecznie!

doktorex 2011-12-22 14:59

procedure TForm1.Button1Click(Sender: TObject);
label
      etykieta;
var
      i:integer;


begin
    etykieta:
        beep;
    goto etykieta;
end;

ajo000 2011-08-19 11:40

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

blade95 2009-04-05 22:40

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

JaskMar 2008-07-18 15:21

cytat:
"Pętla będzie wykonywana, dopóki warunek zapisany pomiędzy słowami kluczowymi while i do nie zostanie spełniony."

Zdeka odwrotnie!

kakazx 2008-03-02 16:38

Writeln();i readln();nie działa oddoje I/O Erro 105

hofas 2007-08-09 16:46

"Byte „zżera” jedynie 2 bajty" ; oczywiscie 1 bajt a nie dwa :) chyba ze o czyms nie wiem....