Wydano za zgodą Rektora
Materiały pomocnicze do zajęć z przedmiotu Wstęp do programowania
dla studentów kierunku informatyka.
W procesie wydawniczym pominięto
etap opracowania językowego.
Wersja elektroniczna materiałów
została przygotowana przez Autorów.
algorytm, program, instrukcja,
iteracja, zmienna plikowa, tablica, funkcja,
procedura, rekord, wskaznik, stos, moduł, obiekt,
klasa, konstruktor, metoda, aplikacja
ISBN 978-83-7199-862-7
Oficyna Wydawnicza Politechniki Rzeszowskiej
al. Powstańców Warszawy 12, 35-959 Rzeszów
e-mail: oficyna1@prz.rzeszow.pl
2
SPIS TREŚCI
1. Wprowadzenie do programowania......................................... 7
1.1. Kryteria wyboru języka programowania ............................. 7
1.2. Efektywność języków niskiego, średniego i wysokiego
poziomu................................................................................ 8
1.3. Język Pascal w ujęciu historycznym.................................... 9
1.4. Środowiska programistyczne u\ywające języka Pascal .... 10
1.5. Etapy tworzenia programu komputerowego ...................... 14
1.6. Cechy dobrego programu komputerowego........................ 14
1.7. Kod zródłowy programu w kontekście języka
programowania .................................................................. 15
1.8. Rola kompilatora w procesie powstawania programu ....... 17
1.9. Architektura kompilatora ................................................... 18
1.10. Podział kompilatorów ze względu na platformę................ 19
1.11. Podział kompilatorów ze względu na liczbę przejść ......... 20
2. Algorytmy i schematy blokowe ............................................. 22
2.1. Podstawowe cechy algorytmów......................................... 22
2.2. Klasyfikacja algorytmów ze względu na sposób
implementacji..................................................................... 23
2.3. Klasyfikacja algorytmów ze względu na sposób
zaprojektowania ................................................................. 24
2.4. Właściwości schematów blokowych ................................. 26
2.5. Schematy blokowe wybranych algorytmów ...................... 27
2.6. Zadania ............................................................................... 34
3. Podstawowe elementy języka Pascal..................................... 36
3.1. Zalety języka Pascal........................................................... 36
3.2. Standardowe symbole i słowa kluczowe ........................... 36
3
3.3. Sposoby zapisu liczb .......................................................... 37
3.4. Sposoby notowania komentarzy ........................................ 38
3.5. Zasady deklarowania stałych i zmiennych ........................ 39
3.6. Definicja typu i rodzaje typów danych .............................. 40
3.7. Rodzaje operatorów ........................................................... 49
3.8. Priorytet operatorów .......................................................... 54
3.9. Struktura programu ............................................................ 54
4. Instrukcje ................................................................................ 57
4.1. Instrukcje proste ................................................................. 57
4.2. Instrukcja zło\ona .............................................................. 58
4.3. Instrukcja warunkowa ........................................................ 59
4.4. Instrukcja wyboru .............................................................. 61
4.5. Instrukcje iteracyjne ........................................................... 63
4.6. Procedury Break i Continue ............................................... 70
4.7. Pętle i tablice ...................................................................... 71
4.8. Zadania ............................................................................... 74
5. Procedury i funkcje ................................................................ 75
5.1. Składnia procedury ............................................................ 75
5.2. Przekazywanie parametrów do procedur i funkcji ............ 77
5.3. Procedury i dyrektywa zapowiadająca Forward ............... 80
5.4. Składnia funkcji ................................................................. 81
5.5. Przecią\anie funkcji ........................................................... 85
5.6. Wywołanie funkcji przez inną funkcję .............................. 86
5.7. Zmienne globalne i lokalne................................................ 87
5.8. Funkcje rekurencyjne ......................................................... 90
5.9. Typ funkcyjny .................................................................... 91
5.10. Procedury kierujące działaniem programu ........................ 92
5.11. Kryteria stosowania procedur i funkcji.............................. 94
4
5.12. Moduły ............................................................................... 95
5.13. Zadania ............................................................................... 97
6. Operacje na plikach ............................................................... 98
6.1. Rodzaje plików .................................................................. 98
6.2. Etapy przetwarzania pliku.................................................. 99
6.3. Przykładowe programy działające na plikach ................. 102
6.4. Zadania ............................................................................. 104
7. Wskazniki .............................................................................. 106
7.1. Definicja zmiennej wskaznikowej ................................... 106
7.2. Procedury dynamicznego rezerwowania i zwalniania
pamięci ............................................................................. 107
7.3. Wskazniki i tablice........................................................... 111
7.4. Operacje arytmetyczne na wskaznikach .......................... 113
8. Struktury dynamiczne.......................................................... 114
8.1. Kolejka ............................................................................. 114
8.2. Lista jednokierunkowa ..................................................... 114
8.3. Stos jako dynamiczna struktura danych........................... 117
8.4. Zadania ............................................................................. 121
9. Klasy ...................................................................................... 123
9.1. Zasady deklarowania klasy .............................................. 123
9.2. Istota programowania obiektowego ................................. 124
9.3. Konstruktor i destruktor ................................................... 128
9.4. Typy konstruktorów ......................................................... 130
9.5. Dziedziczenie ................................................................... 133
9.6. Polimorfizm ..................................................................... 134
9.7. Metody wirtualne ............................................................. 136
9.8. Metody dynamiczne ......................................................... 138
5
10. Programowanie obiektowe - aplikacje ............................... 141
10.1. Komponenty LCL ............................................................ 141
10.2. Inspektor obiektów........................................................... 144
10.3. Okno komunikatów i okno Form ..................................... 145
10.4. Edytor zródeł.................................................................... 145
10.5. Obiekt Button1 klasy TButton ......................................... 147
10.6. Aplikacja Stoper............................................................... 150
10.7. Aplikacja Kalkulator........................................................ 154
10.8. Aplikacja Równanie kwadratowe .................................... 156
10.9. Rysowanie figur geometrycznych.................................... 161
10.10. Tablice i komponent TListBox .................................... 166
10.11. Okna dialogowe ........................................................... 170
10.12. Tablice jednowymiarowe i komponent TStringGrid ... 173
10.13. Tablice dwuwymiarowe i komponent TStringGrid ..... 176
10.14. Zadania ......................................................................... 180
LITERATURA ........................................................................... 181
6
1. Wprowadzenie do programowania
Programowanie komputerów (pisanie programów lub skryptów, kodowa-
nie) jest formą działalności twórczej człowieka i polega na pisaniu u\ytecznych,
mo\liwych do przechowywania i rozszerzania, zestawów instrukcji, które mogą
być interpretowane (wykonywane) przez system komputerowy (komputer).
Instrukcje, reprezentujące m.in. operacje arytmetyczne i logiczne, czynią pracę
umysłową u\ytkownika komputera prostszą i bardziej efektywną.
Programowanie komputerów to czynność polegająca na zapisaniu rozwią-
zania konkretnego problemu zazwyczaj w postaci tekstowej, w wybranym języ-
ku programowania, z zachowaniem syntaktyki (składni) oraz semantyki (znacze-
nia poszczególnych symboli oraz ich funkcji w programie) tego języka.
Komputery mo\na programować w jednym z wielu języków programowa-
nia. Ze względu na poziom abstrakcji, dzieli się je na:
" języki wysokiego poziomu, zbli\one do języka naturalnego,
" języki niskiego poziomu, stosowane do pisania programów bezpośred-
nio w kodzie maszynowym.
Program, przechowywany w postaci instrukcji binarnych, mo\e być wyko-
nywany przez komputer. Mówi się, \e jest on w postaci kodu maszynowego.
Początkowo programy były pisane wyłącznie w kodzie maszynowym. Obecnie
programy pisze się w językach wysokiego poziomu: C, C++, Java, Perl, Pascal.
Program napisany w języku wysokiego poziomu jest tłumaczony na kod maszy-
nowy za pomocą specjalnego programu.
1.1. Kryteria wyboru języka programowania
Najwa\niejszym czynnikiem, przy wyborze pierwszego języka do nauki
programowania, jest to, na ile u\yteczny będzie ten język w praktyce, na przy-
kład w przemyśle IT albo w otwartym projekcie. Język C++ lub Java będzie do-
brym wyborem, jednak oprócz u\yteczności i popularności są wa\ne równie\
inne czynniki, na które trzeba zwrócić uwagę.
Wiele języków programowania jest projektowanych specjalnie po to, by by-
ły łatwe do nauki, szczególnie dla osób zaczynających swoją przygodę z pro-
gramowaniem komputerów. Języki Basic oraz Pascal nale\ą do tej grupy języ-
ków. Są one jednak krytykowane za obni\oną wydajność, spowodowaną ograni-
czoną liczbą udogodnień. Nale\y pamiętać, \e podstawową funkcją tych języ-
ków jest nauka programowania strukturalnego. Dla zaawansowanych aplikacji
mogą być potrzebne bardziej wyspecjalizowane i kompleksowe języki.
7
Niektóre języki programowania są odpowiednie dla początkujących, po-
niewa\ mają prostą składnię (np. Python), natomiast dydaktycznie mogą spraw-
dzać się języki inne (np. Ada oraz Ruby).
Informacje o aktualnej popularności języków programowania (ranking ję-
zyków) mo\na znalezć na stronie http://www.tiobe.com/index.php/content/pa-
perinfo/tpci/index.html. W maju 2013 r. najbardziej popularne były języki:
C (18,729 %), Java (16,914 %), Objective-C (10,428 %), C++ (9,198 %), C#
(6,119 %), PHP (5,784 %), (Visual) Basic (4,656 %), Python (4,322 %), Perl
(2,276 %), Ruby (1,670 %), JavaScript (1,536 %), Visual Basic .NET (1,131 %),
Lisp (0,894 %), Transact-SQL (0,819 %), Pascal (0,805 %), Bash (0,792 %),
Delphi/Object Pascal (0,731 %), PL/SQL (0,708 %), Assembly (0,638 %), Lua
(0,632 %). W nawiasach podano w procentach liczbę programistów u\ywają-
cych danego języka.
Nauka akademickich języków, takich jak Prolog czy Smalltalk, mo\e wy-
dawać się nieprzydatna w przyszłej pracy zawodowej. Prostota tych języków
zmusza jednak do zmiany sposobu myślenia o programowaniu i uczy pewnych
umiejętności, które mo\na potem przenieść na inne języki. Ponadto, znajomość
wielu języków programowania rozwija elastyczność w myśleniu i sprzyja roz-
wojowi intelektualnemu programisty.
1.2. Efektywność języków niskiego, średniego i wysokiego poziomu
Jedne języki programowania oferują du\ą szybkość i sprawność działania
programu, natomiast inne są łatwiejsze składniowo. Pisanie w kodzie maszyno-
wym, najczęściej w asemblerze, mo\e dać w efekcie najszybszy i najbardziej
spójny program, jak to tylko mo\liwe. Jest to jednak zajęcie \mudne i czaso-
chłonne, gdy\ asemblery mają bardzo precyzyjną składnię. Pojedyncza instruk-
cja asemblera jest tłumaczona zazwyczaj na jedną lub dwie instrukcje procesora.
Pojedynczej instrukcji języka wysokiego poziomu odpowiada kilka, kilka-
naście, a czasem nawet kilkadziesiąt instrukcji asemblera, co znacznie przyspie-
sza pisanie programu. Programista musi w tej sytuacji polegać na kompilatorze
i ufać, \e przekształci on tę instrukcję w najefektywniejszą sekwencję elemen-
tarnych instrukcji procesora. Eksperymentując z opcjami kompilatora, mo\na
uzyskać kod maszynowy szybszy, ale o większym rozmiarze albo wolniejszy,
ale o mniejszym rozmiarze - w zale\ności od potrzeby.
Z reguły, choć nie zawsze, im łatwiej zaprogramować jakieś zadanie, tym
wolniej będzie wykonywał się program realizujący to zadanie. Z tego wynika, \e
najefektywniejsze pod względem jakości programu i czasu poświęconego na
programowanie są języki średniego poziomu. Na przykład, programy napisane
8
w asemblerze mogą działać szybciej ni\ programy napisane z wykorzystaniem
języków średniego poziomu. Z kolei nowocześnie zoptymalizowane kompilato-
ry dają często lepszy rezultat ni\ ten, jaki byłby w stanie osiągnąć przeciętny
programista.
W wielu przypadkach pisze się programy w językach ni\szego poziomu,
szczególnie te, które są składnikami systemów operacyjnych. Na przykład Li-
nuks i oprogramowanie GNOME zostały napisane w C, poniewa\ w obu przy-
padkach niezwykle istotna była szybkość działania oprogramowania. Jądro sys-
temu Linuks nie mo\e działać zbyt wolno, gdy\ musi pracować równocześnie
z wieloma programami, nieraz bardzo wymagającymi, zaś oprogramowanie baz
danych przetwarza ogromne ilości danych, i powolne jego działanie mogłoby
spowodować niedro\ność systemu. Oprócz tego, programy napisane w językach
ni\szego poziomu mają, z reguły, mniejszy rozmiar i zajmują mniej pamięci
operacyjnej, co w wypadku jądra systemu operacyjnego jest o tyle istotne,
\e pozostaje więcej zasobów dla innych programów, które dzięki temu działają
efektywniej.
1.3. Język Pascal w ujęciu historycznym
Jeszcze kilkanaście lat temu język Pascal był jednym z najpopularniejszych
języków programowania. Jest to język uniwersalny, wysokiego poziomu, ogól-
nego zastosowania, oparty na języku Algol (ang. Algorithmic Language). Pascal
został opracowany przez Niklausa Wirtha w 1970 roku. Pierwotnie słu\ył celom
edukacyjnym - głównie do nauki programowania strukturalnego.
Popularność Pascala w Polsce była większa ni\ w innych krajach ze
względu na:
" dostępność kompilatorów w pirackich wersjach (zanim pojawiło się
prawo ochrony własności intelektualnej),
" prostotę języka,
" popularyzację przez wy\sze uczelnie.
Szczyt popularności tego języka przypadł na lata 80. i początek 90. XX
wieku. Wraz ze zniesieniem ograniczeń narzuconych przez COCOM (Komitet
Koordynacyjny Wielostronnej Kontroli Eksportu), upowszechnieniem się sieci
Internet oraz systemów Uniks i Linuks - język Pascal został stopniowo wyparty
przez C i C++. Jedną z popularniejszych implementacji kompilatorów tego języ-
ka był produkt firmy Borland International o nazwie Turbo Pascal. W chwili
obecnej dość mocno rozpowszechnionym obiektowym dialektem języka Pascal
jest Object Pascal, który stanowi podstawę dla języków: Delphi, Delphi.NET
9
i Oxygene. Poza tym, istnieją wolne kompilatory Pascala (np. Free Pascal) oraz
wolne zintegrowane środowiska programistyczne (np. Lazarus).
Język Pascal bardzo rygorystycznie podchodzi do kontroli typów, tzn.
sprawdza, czy do zmiennej typu A nie próbuje się przypisać wartości typu B.
Jest on zatem językiem silnie typowanym.
Popularność Pascala wzrosła z pojawieniem się środowiska programistycz-
nego Delphi, opartego na obiektowym Pascalu, pozwalającego na błyskawiczne
tworzenie atrakcyjnych wizualnie aplikacji dla systemu operacyjnego Windows.
Wraz z pojawieniem się biblioteki Windows dla C++ oraz narzędzi do automa-
tycznego tworzenia graficznego interfejsu u\ytkownika (ang. Graphical User In-
terface = GUI), Pascal znów stracił na znaczeniu.
Dla pewnej grupy programistów niektóre cechy Pascala wykluczają jego
zastosowanie w powa\nych projektach i są powodem krytyki tego języka. We-
dług nich Pascal powinien zostać jedynie narzędziem do nauki programowania.
W sprzeczności z tymi zarzutami stoi fakt, \e w latach 80. i 90. XX wieku w ję-
zyku tym powstało tysiące aplikacji (równie\ komercyjnych).
Zalety Pascala (czytelność kodu oraz rygorystyczna kontrola typów da-
nych) wraz z pojawieniem się języka C, stały się dla programistów wadami.
Promowanym przez twórców C standardem stała się natomiast zwięzłość kodu.
1.4. Środowiska programistyczne u\ywające języka Pascal
Object Pascal jest to obiektowy język programowania, stanowiący obiek-
towe rozszerzenie strukturalnego Pascala. Object Pascal jest u\ywany jako ję-
zyk programowania w środowiskach programistycznych Borland Delphi i Kylix.
Object Pascal jest kontynuacją języka Turbo Pascal. Obecnie język ten właści-
wie jest nazywany Delphi Pascal. Charakteryzuje się głównie prostą składnią
i bardzo wysoką efektywnością tworzenia oprogramowania. Wpływ na to ma,
przede wszystkim, bardzo szybki kompilator (kilkakrotnie szybszy, w porówna-
niu do innych języków).
Free Pascal (FPK Pascal albo FPC) jest 32- oraz 64-bitowym kompilato-
rem języka Pascal, dostępnym na wiele ró\nych platform sprzętowych i syste-
mów operacyjnych.
Kompilator Free Pascal jest rozpowszechniany zgodnie z licencją GPL.
Biblioteki wykonawcze oraz dodatkowe pakiety, rozpowszechniane razem z
kompilatorem, objęte są jednak zmodyfikowaną licencją LGPL.
10
Wizualną częścią bibliotek dla FPC, zgodną z VCL (znanym z Delphi),
a tak\e stworzeniem narzędzia typu RAD (ang. Rapid Application Development)
- szybkiego tworzenia aplikacji, zajmuje się osobny projekt - Lazarus.
RAD jest to idea i technologia polegająca na udostępnieniu programiście
du\ych mo\liwości prototypowania oraz obszernego zestawu gotowych kompo-
nentów (np. zapewniających dostęp do bazy danych). Takie podejście umo\liwia
uzyskanie szybkiego efektu ju\ w pierwszych krokach programistycznych, jed-
nocześnie stanowi powa\ne zagro\enie dla projektów o większych rozmiarach,
ze względu na łatwość nieprzemyślanego modyfikowania. Narzędzia RAD są
rozwinięciem pomysłu IDE i doskonale nadają się do tworzenia prototypów.
Wygląd aplikacji projektuje się rozmieszczając kontrolki w obszarze okna pro-
jektowanego programu (np. metodą przeciągnij i upuść ).
Przykłady narzędzi typu RAD:
" Microsoft Visual Studio dla Microsoft Windows,
" Delphi firmy Borland oraz Embarcadero,
" Lazarus i Kylix dla GNU/Linuksa,
" Eclipse i NetBeans stworzone dla Javy i posiadające mo\liwość rozsze-
rzenia, w celu obsługi innych języków,
" Zend Studio dedykowane dla języka PHP.
FPC posiada własne IDE, stworzone w trybie tekstowym, jednak nie jest
ju\ ono wspierane. FPC rozpoznaje i kompiluje kod Pascala zapisany w poni\-
szych dialektach tego języka:
" FPC domyślny tryb pracy kompilatora, charakteryzujący się mo\liwo-
ścią przecią\ania funkcji i operatorów oraz zagnie\d\ania komentarzy,
" obiektowy FPC dopuszcza u\ycie rozszerzeń Object Pascala,
" Delphi zgodność z Delphi w wersji 4,
" GPC zgodność z kompilatorem GPC z pakietu GCC,
" TP zgodność z dialektem u\ytym w kompilatorze Turbo Pascal 7,
" GCC (ang. GNU Compiler Collection) to zestaw kompilatorów tworzo-
ny w ramach projektu GNU. Początkowo skrótowiec GCC oznaczał
GNU C Compiler, poniewa\ był to kompilator tylko języka C. Dostępne
są kompilatory dla języków: C, C++, Objective-C, Fortran, Java (GCJ),
a tak\e - eksperymentalnie - Ada, Pascal i Go. GCC działa na wielu ar-
chitekturach i systemach operacyjnych, a do operacji na plikach obiek-
towych u\ywa pakietu binutils.
11
Delphi (Borland Delphi) to zintegrowane środowisko programistyczne typu
RAD, przeznaczone do pracy pod kontrolą Microsoft Windows, działające zgod-
nie z zasadą dwustronnej edycji. Językiem programowania (obiektowym), osa-
dzonym w Delphi, jest Object Pascal.
Programy tworzone w Delphi muszą zostać skompilowane do postaci kodu
binarnego przed pierwszym wykonaniem. Mimo to, niektóre komponenty dzia-
łają ju\ w trakcie tworzenia projektu, umo\liwiając na bie\ąco śledzenie postę-
pów w pracy nad projektem.
Delphi zapamiętuje informacje o właściwościach obiektów i udostępnia je
programiście. Informacje te umo\liwiają programiście zmianę ich wartości bez
potrzeby pisania kodu programu oraz są u\ywane w trakcie działania programu.
Technika ta nosi nazwę RTTI (ang. Run Time Type Information).
Programy tworzone w Delphi pracują na zasadzie obsługi zdarzeń. Ka\de
polecenie (np. kliknięcie myszką) generuje zdarzenie, które, poprzez wewnętrz-
ne mechanizmy programu, jest przesyłane do odpowiedniego komponentu. Rola
programisty ogranicza się tylko do dołączenia odpowiedniego kodu, umo\liwia-
jącego obsługę tego zdarzenia.
Cechy i funkcjonalności Delphi:
" szerokie wspomaganie obsługi relacyjnych systemów bazodanowych
(biurkowych oraz SQL-owych),
" szeroki zestaw gotowych do u\ycia komponentów,
" dwustronna edycja - funkcja środowiska programistycznego odzwier-
ciedlająca w kodzie programu zmiany dokonywane na jego zasobach
(i czasem odwrotnie),
" mo\liwość budowania wizualnej części aplikacji za pomocą techniki
przeciągnij i upuść (ang. drag and drop),
" szybki i efektywny kompilator języka Object Pascal (do natywnego ko-
du maszynowego),
" rozszerzalne środowisko,
" dołączone ró\ne narzędzia, uzupełniające środowisko Delphi.
Delphi cieszy się w Polsce stosunkowo du\ą popularnością, w głównej mie-
rze ze względu na relatywną prostotę i powszechność ró\nego rodzaju poradni-
ków dla początkujących.
Embarcadero Delphi XE4 jest rozbudowanym rozwiązaniem programi-
stycznym do szybkiego tworzenia prawdziwie natywnych aplikacji dla kompute-
12
rów PC, tabletów oraz smartfonów. Natywne aplikacje dają większą kontrolę,
większe bezpieczeństwo oraz są lepiej dopasowane do oczekiwań u\ytkowni-
ków. Dzięki Delphi XE4 nie trzeba prowadzić jednocześnie wielu projektów,
aby tworzyć natywne aplikacje dla komputerów PC, tabletów i smartfonów, de-
dykowane ró\nym platformom (iOS, Microsoft Windows, Mac OS). W Delphi
XE4 mo\na zrobić to w ramach pojedynczego projektu. Tworzone aplikacje
w pełni wykorzystują mo\liwości i wydajność docelowych urządzeń, są wolne
od języków skryptowych i dodatkowych maszyn wirtualnych.
Kylix to zintegrowane środowisko dla programistów, pracujące pod Linuk-
sem, wyprodukowane przez firmę Borland. Pozwalało na pisanie aplikacji w ję-
zyku Delphi (Object Pascal) i korzystanie z komponentów CLX. Od wersji 3.
(2003 r.) Kylix umo\liwiało tak\e pisanie aplikacji w C++. Aplikacje CLX są
kompatybilne, na poziomie zródeł, ze środowiskiem Delphi, dzięki czemu zosta-
ło ułatwione przenoszenie tych aplikacji do systemu Windows. Mo\liwe jest tak-
\e przenoszenie aplikacji napisanych w Delphi lub w C++ z Windows do Linuk-
sa, ale nale\y w tym celu skorzystać z komponentów zawartych w międzyplat-
formowej bibliotece programistycznej CLX. Niestety, projekt Kylix został zanie-
chany po wydaniu trzeciej wersji.
Lazarus to zintegrowane środowisko programistyczne, oparte na kompila-
torze Free Pascal. Jest to, wzorowane na Delphi, wizualne środowisko progra-
mistyczne oraz biblioteka LCL (ang. Lazarus Component Library), która jest
odpowiednikiem VCL (ang. Visual Component Library).
Program napisany w środowisku Lazarus mo\na bez \adnych zmian skom-
pilować dla dowolnego obsługiwanego procesora, systemu operacyjnego i inter-
fejsu okienek. Lazarus jest zgodny z Delphi. Jest brakującą częścią układanki,
która pozwala na rozwijanie programów, podobnie jak w Delphi, na wszystkich
platformach obsługiwanych przez FPC.
W odró\nieniu od Javy, która stara się, aby raz napisana aplikacja działała
wszędzie, Lazarus i Free Pascal starają się, aby raz napisana aplikacja kompi-
lowała się wszędzie. Poniewa\ dostępny jest dokładnie taki sam kompilator, nie
trzeba wprowadzać zmian, aby otrzymać taki sam produkt dla ró\nych platform.
Główne cechy Lazarusa:
" program jest udostępniany na licencji GNU GPL, natomiast biblioteki -
na zmodyfikowanej licencji LGPL (co oznacza mo\liwość wykorzysta-
nia Lazarusa w projektach o zamkniętym kodzie),
" szybkie przejścia pomiędzy ró\nymi interfejsami i systemami zapewnia-
ją biblioteki The Interface - Widget,
13
" Lazarus jest jednym z nielicznych narzędzi, umo\liwiającym tworzenie
aplikacji dla urządzeń Pocket PC z Windows CE albo QT Extended.
Lazarus posiada:
" kompilator języka Pascal (Free Pascal),
" edytor kodu zródłowego,
" metodologię RAD,
" technikę wizualnego tworzenia okien programu (tzw. form),
" bibliotekę RTL (ang. Run-Time Library), stanowiącą zestaw gotowych
komponentów,
" mo\liwość generowania kodu dla wielu:
systemów operacyjnych (Win32, Win64, Linuks, Mac),
platform sprzętowych (Pentium, PowerPC, Mac).
1.5. Etapy tworzenia programu komputerowego
Aby rozwiązać problem, za pomocą programu komputerowego, nale\y:
" określić zadania, które program ma realizować, w celu rozwiązania po-
stawionego problemu, z wyró\nieniem informacji wejściowych i wyj-
ściowych - zdefiniować problem,
" opracować kroki postępowania prowadzące do otrzymania informacji
wyjściowej na podstawie informacji wejściowej, czyli wymyślić sposób
realizacji tych zadań - zaprojektować algorytm,
" zapisać algorytm w wybranym języku programowania, według zasad
i symboliki narzuconej przez konkretny język programowania zako-
dować program,
" skompilować i uruchomić program - zbudować program,
" sprawdzić, czy program działa poprawnie - przetestować program.
1.6. Cechy dobrego programu komputerowego
In\ynieria oprogramowania koncentruje się na praktycznej stronie wytwa-
rzania oprogramowania i obejmuje ró\ne aspekty jego produkcji, mianowicie:
analizę i określenie wymagań (specyfikację), projektowanie, wdro\enie (imple-
mentację), integrację poszczególnych składowych w jedną całość, modyfikację
i w końcu dostosowywanie gotowego programu do nowych celów (ewolucję).
Bez względu na sposób wytwarzania, końcowy program powinien posiadać
pewne fundamentalne właściwości:
14
" niezawodność - dobrze zaimplementowane algorytmy, prawidłowe za-
rządzanie zasobami, np. przepełnieniem bufora, oraz brak błędów lo-
gicznych typu: dzielenie przez zero, zły warunek krańcowy pętli itp.
" solidność - umiejętność radzenia sobie programu z nieprawidłowymi
lub uszkodzonymi danymi, a tak\e z niedostępnością zasobów takich
jak: pamięć, usługi systemu operacyjnego, usługi sieciowe itp.
" u\yteczność (ergonomia) - łatwość, z jaką u\ytkownik mo\e wykorzy-
stać program, równie\ do nietypowych zastosowań. U\yteczność zale\y
od tekstowych i graficznych elementów, które poprawiają przejrzystość,
intuicyjność, spójność i kompletność interfejsu u\ytkownika.
" przenośność - ró\norodność sprzętu komputerowego (platform) i sys-
temów operacyjnych, na których program mo\e być uruchamiany.
" łatwość konserwacji - łatwość, z jaką program mo\e być modyfikowa-
ny, przez aktualnych lub przyszłych deweloperów, w celu: zrobienia
ulepszeń lub dostosowania go do własnych potrzeb, adaptacji do nowe-
go środowiska albo usunięcia błędów i luk bezpieczeństwa. Aatwość
konserwacji nie musi być bezpośrednio widoczna dla u\ytkownika, ale
mo\e znacznie wpłynąć na losy programu w dłu\szym okresie.
" efektywność/wydajność - ilość zasobów komputera u\ywanych przez
program (im mniej, tym lepiej): czas procesora, wielkość pamięci, czę-
stość odczytu dysku twardego, pasmo danych karty sieciowej, anga\o-
wanie uwagi u\ytkownika, pozostawianie plików tymczasowych itp.
1.7. Kod zródłowy programu w kontekście języka programowania
Opracowanie algorytmu jeszcze nie rozwiązuje problemu. Dopiero zako-
dowanie go w postaci programu, w wybranym języku, przybli\a nas do celu
(rys. 1.1). Poniewa\ algorytm wymyśla człowiek, a wykonuje komputer, to za-
chodzi konieczność przetłumaczenia kolejnych kroków algorytmu na postać od-
powiednią (zrozumiałą) do automatycznego wykonywania przez maszynę.
Problem Algorytm Kod zródłowy Program
Rys. 1.1. Droga od problemu do programu go rozwiązującego
Język programowania jest to sztuczny język, zaprojektowany do wydawa-
nia rozkazów maszynie, głównie komputerowi. Jak ju\ wspomniano w akapicie
drugim na str. 7, opis konkretnego języka programowania jest zwykle dzielony
na dwa składniki: syntaktykę (formę) oraz semantykę (znaczenie).
15
Większość języków programowania jest czysto tekstowa. Tekst programu
zawiera: wyrazy, liczby i znaki przestankowe. Istnieją te\ języki programo-
wania, które są w swej naturze bardziej graficzne, a do zdefiniowania programu
stosuje się w nich relacje między symbolami graficznymi.
Tworzenia programów, za pomocą języków programowania, przypomina
układanie prostych zdań w języku naturalnym, zawierających wyra\enia arytme-
tyczne znane w matematyce. Programy pisze się w specjalnym edytorze tekstu.
W ten sposób powstaje tekst zródłowy, zwany te\ kodem zródłowym programu.
Syntaktyka języka opisuje dopuszczalne kombinacje symboli, które tworzą
składniowo poprawny program. Znaczenie konkretnej kombinacji symboli roz-
poznawane jest przez semantykę. Nie wszystkie programy poprawne syntak-
tycznie są poprawne semantycznie. Z semantyką związany jest system typów,
który definiuje, jak język programowania klasyfikuje wartości i wyra\enia, jak
mo\e nimi manipulować i w jaki sposób mogą one wchodzić w interakcje.
W statycznym typowaniu wszystkie wyra\enia mają przypisane typy na
etapie kompilacji, zanim program zostanie wykonany. Statycznie typowane ję-
zyki mogą być typowane jawnie albo wnioskująco. W pierwszym przypadku
programista musi zapisać typy w sposób jawny, w określonych miejscach w tek-
ście programu (np. podczas deklaracji zmiennych). W drugim przypadku kompi-
lator wnioskuje typy wyra\eń i deklaracji w oparciu o kontekst, w którym wy-
stępują. Popularnymi językami, w których manifestuje się typy są: C++, C#, Ja-
va. Pełne wnioskowanie o typach występuje w mniej znanych językach progra-
mowania: Haskell i ML.
Dynamiczne typowanie, zwane te\ póznym, określa bezpieczeństwo ope-
racji w trakcie jej wykonywania, tzn. typy są przypisywane wartościom otrzy-
mywanym w trakcie działania programu, a nie tekstowym wyra\eniom. Dyna-
micznie typowane języki nie wymagają od programisty wyraznego (precyzyjne-
go) podania typu wyra\enia. Dzięki temu pojedyncza zmienna mo\e odnosić się
do wartości o ró\nych typach w ró\nych punktach wykonywania programu.
Z tego powodu błędy związane z typami nie mogą być wykrywane automatycz-
nie, a\ do momentu, gdy odpowiedni fragment programu jest rzeczywiście wy-
konywany, potencjalnie czyniąc debugowanie bardziej zło\onym. Dynamicznie
typowanymi językami są: Lisp, Perl, Python, JavaScript i Ruby.
Słabe typowanie dopuszcza traktowanie wartości jednego typu jako warto-
ści innego typu. To mo\e być okazjonalnie u\yteczne, ale mo\e te\ powodować
pewnego rodzaju błędy programu, niewykrywalne na etapie kompilacji, a nawet
podczas uruchamiania.
16
Silne typowanie nie ma wad typowania słabego. Próba wykonania operacji
na wartości o niewłaściwym typie generuje błąd ju\ na etapie kompilacji. Silnie
typowane języki są często określane mianem bezpiecznych (ang. type-safe).
Większość języków programowania posiada powiązaną bibliotekę standar-
dową, która jest udostępniana wszystkim implementacjom tego języka. Biblio-
teka standardowa, zwana te\ rdzeniową, zawiera definicje powszechnie u\y-
wanych algorytmów, struktur danych oraz mechanizmów wejścia-wyjścia. Bi-
blioteka rdzeniowa jest, przez jej u\ytkowników, często traktowana jako część
języka. Projektanci tej biblioteki mogą jednak traktować ją jako oddzielny byt.
Po napisaniu programu, tekst zródłowy poddawany jest kompilacji przez
program zwany kompilatorem. Następnie, w fazie łączenia, dokonywane jest po-
łączenie procedur bibliotecznych z tekstem programu. Zadanie to realizuje linker
lub konsolidator.
Efektem końcowym przetworzenia kodu zródłowego jest program wyniko-
wy, który mo\e być zapisany na dysku komputera i pózniej wielokrotnie uru-
chamiany za pośrednictwem systemu operacyjnego.
1.8. Rola kompilatora w procesie powstawania programu
Kompilacja, w programowaniu, to przetłumaczenie wersji zródłowej pro-
gramu na język maszyny (procesora). Kompilacji wersji zródłowej programu
dokonuje kompilator danego języka programowania.
Kompilator jest programem komputerowym lub zestawem programów,
który tłumaczy kod zródłowy, napisany w języku programowania (w języku
zródłowym), na inny język komputerowy - język docelowy, posiadający zwykle
formę binarną, zwaną kodem obiektowym. Powodem, dla którego tłumaczy się
kod zródłowy jest utworzenie wykonywalnego programu (rys. 1.2). Kod obiek-
towy, zwany te\ modułowym, jest sekwencję wyra\eń lub instrukcji w języku
kodu maszyny (w postaci zer i jedynek).
kompilacja konsolidacja
Tekst zródłowy Reprezentacja Program
programu pośrednia wykonywalny
Biblioteki stan-
dardowe i inne
Rys. 1.2. Droga od kodu zródłowego do wykonywalnego programu
17
Kompilator wykonuje większość lub wszystkie następujące czynności:
analizę leksykalną, preprocessing, parsowanie, analizę semantyczną, generowa-
nie kodu, optymalizację kodu.
Nazwa kompilator, przede wszystkim, jest u\ywana w odniesieniu do pro-
gramów, które tłumaczą kod zródłowy z języka programowania wysokiego po-
ziomu do języka niskiego poziomu (kodu asemblera lub kodu maszynowego).
Kompilatory umo\liwiają tworzenie programów niezale\nych od maszyny.
Błędne działanie programu, spowodowane nieprawidłową pracą kompilato-
ra, jest bardzo trudne do wykrycia, dlatego twórcy kompilatorów dokładają
wszelkich starań, aby zapewnić wysoką jakość tego oprogramowania.
1.9. Architektura kompilatora
Ka\dy kompilator składa się z trzech głównych części:
" front-end - sprawdza, czy program u\ytkownika jest napisany popraw-
nie pod względem składniowym i znaczeniowym. Na tym etapie rapor-
towane są błędy, je\eli istnieją i wykonywana jest kontrola typów.
Front-end generuje pośrednią reprezentację (ang. Intermediate Repre-
sentation IR) kodu zródłowego, która jest przetwarzana w następnym
etapie. Front-end zarządza tablicą symboli, tzn. strukturą danych, która
dla ka\dego symbolu występującego w kodzie zródłowym przechowuje
informacje o jego poło\eniu, typie i zasięgu. Leksykalna analiza dzieli
tekst zródłowy na małe kawałki (tokeny). Ka\dy token jest pojedynczym
elementem języka programowania, takim jak: słowo kluczowe, identyfi-
kator, nazwa symbolu itp. Składnia tokena jest zwykle językiem regu-
larnym, więc do rozpoznania tokena mo\na wykorzystać automat skoń-
czony, skonstruowany z wyra\eń regularnych. Niektóre języki (np. C)
wymagają fazy preprocessingu, obsługującej podstawienia makr oraz
kompilację warunkową. Faza ta występuje przed analizą syntaktyczną i
semantyczną. Analiza syntaktyczna obejmuje parsowanie sekwencji to-
kenów w celu wyznaczenia syntaktycznej struktury programu. Ta faza
buduje drzewo parsowania, która zastępuje liniową sekwencję tokenów
strukturą drzewiastą, zgodnie z zasadami gramatyki formalnej, definiu-
jącej składnię języka programowania. Drzewo parsowania jest analizo-
wane i transformowane w pózniejszych fazach kompilacji. W czasie
analizy semantycznej kompilator dodaje informację znaczeniową do
drzewa parsowania i buduje tablicę symboli. Równie\ wtedy odbywa się
semantyczne sprawdzanie takich rzeczy jak: typy (na obecność błędów),
wiązanie obiektów (kojarzenie zmiennych i referencji do funkcji z ich
definicjami), definitywne przypisanie (wymaganie inicjalizacji wszyst-
18
kich zmiennych lokalnych przed ich u\yciem). To sprawdzanie kończy
się odrzuceniem zródła programu lub wygenerowaniem ostrze\eń.
" middle-end - na tym etapie są dokonywane optymalizacje niezale\ne od
kodów: zródłowego i maszynowego. Kod pośredni jest transformowany
do równowa\nej funkcjonalnie, ale szybszej lub mniejszej formy. Ty-
powe transformacje, którym poddawany jest kod IR, w celu optymaliza-
cji, to: usunięcie nieu\ywanego lub nieosiągalnego kodu, wykrycie i
propagacja wartości stałych, relokacja obliczeń do rzadziej wykonywa-
nych miejsc, specjalizacja obliczeń opartych na kontekście, wstawienie
kodu typu inline. Tak zoptymalizowany kod mo\e być dzielony między
ró\nymi wersjami kompilatora, obsługującymi ró\ne języki i procesory
docelowe. Z powodu dodatkowego czasu i dodatkowej przestrzeni, po-
trzebnych na analizę i optymalizację, niektóre kompilatory domyślnie
opuszczają ją. U\ytkownik musi wtedy sam wyraznie powiedzieć
kompilatorowi, które optymalizacje chce włączyć.
" back-end - jest odpowiedzialny za tłumaczenie kodu pośredniego, uzy-
skanego w fazie middle-end, na kod asemblera. Na tym etapie odbywają
się analizy, transformacje i optymalizacje przeznaczone dla określonego
typu komputera - generowany jest kod dla konkretnego procesora i sys-
temu operacyjnego. Instrukcje docelowe asemblera są dobierane dla
ka\dej instrukcji kodu pośredniego. Niektóre zmienne programu są alo-
kowane w rejestrach procesora. Generowane są tak\e dane ułatwiające
debugowanie. Back-end wykorzystuje architekturę procesora w celu
rozdziału pracy między jednostki wykonawcze procesora, wypełnia slo-
ty opózniające itp.
Przedstawiony podział kompilatora na 3 części pozwala łączyć front-end-y
ró\nych języków z back-end-ami ró\nych CPU. Praktycznym przykładem takie-
go podejścia są: GNU Compiler Collection, LLVM oraz Amsterdam Compiler
Kit, które mają wiele front-end-ów, wspólną analizę oraz wiele back-end-ów.
1.10. Podział kompilatorów ze względu na platformę
Jedna z klasyfikacji kompilatorów dzieli je ze względu na platformę,
na której będzie wykonywany kod przez dany kompilator wygenerowany. Tę
platformę nazywa się docelową. Natywny lub hostowany kompilator to taki,
którego wynik działania przeznaczony jest do uruchamiania na takim samym ty-
pie komputera i systemu operacyjnego, na jakim uruchamiano kompilator.
19
Je\eli skompilowany program daje się uruchomić na komputerze z CPU
i z systemem operacyjnym innymi od tych, na których kompilator był urucha-
miany, to do kompilacji został u\yty kompilator skrośny (ang. cross-compiler).
Efekt działania kompilatora skrośnego jest przeznaczony do uruchamiania
na innej platformie. Kompilatory skrośne są często stosowane do wytwarzania
oprogramowania dla systemów wbudowanych (ang. embedded systems), które
w zamierzeniu nie będą obsługiwać środowisk wytwarzania oprogramowania.
Efekt działania kompilatorów, które produkują kod dla wirtualnej maszyny
(ang. Virtual Machine = VM), mo\e być lub nie być uruchamiany na tej samej
platformie, na której kompilator wyprodukował kod.
Języki wy\szego poziomu zwykle pojawiają się - w zało\eniu - z pewnym
rodzajem translacji. Są one projektowane albo jako języki kompilowane, albo
jako interpretowane. Interpretacja niezupełnie zastępuje kompilację. Interpreta-
cja jedynie ukrywa kompilację przed u\ytkownikiem i czyni ją bardziej stop-
niowaną. Specyfikacje niektórych języków wyraznie mówią, \e ich implementa-
cje muszą posiadać mo\liwość kompilacji. Tak jest np. z Common Lisp, jednak
nie ma niczego szczególnego, tkwiącego w definicji Common Lisp, co by blo-
kowało go przed byciem interpretowanym. Nowoczesne trendy w kierunku
kompilacji typu just-in-time oraz interpretacji bajtkodu rozmywają tradycyjny
podział kompilatorów i interpretatorów na kategorie.
Kompilator, dla względnie prostego języka, napisany przez jedną osobę,
mo\e mieć postać pojedynczego, monolitycznego kawałka oprogramowania.
Gdy język zródłowy jest obszerny i skomplikowany, i wymagany jest wynik o
wysokiej jakości, wówczas projekt kompilatora mo\e być podzielony na kilka
niezale\nych faz, tzn. na małe fragmenty, które mogą być przekazane do opra-
cowania ró\nym osobom. Dzięki temu łatwo zastąpić jakąś fazę jej ulepszoną
wersją lub wprowadzić zupełnie nową fazę na pózniejszym etapie. Wszystkie,
nawet najmniejsze kompilatory, posiadają więcej ni\ dwie fazy. Czasem trudno
wskazać miejsce, w którym fazy front-end i back-end łączą się ze sobą.
1.11. Podział kompilatorów ze względu na liczbę przejść
Podział kompilatorów, ze względu na liczbę przejść (ang. pass), ma swoje
podło\e w ograniczeniach sprzętu komputerowego. Kompilacja wymaga prze-
prowadzenia mnóstwa działań, a wczesne komputery nie miały wystarczająco
pamięci, aby pomieścić jeden program, który zrobiłby to wszystko. Z tego po-
wodu kompilatory były dzielone na mniejsze programy, które robiły przejścia na
kodzie zródłowym lub na jego reprezentacji, wykonując jedną analizę lub trans-
lację. Umiejętność kompilacji w jednym przejściu (ang. single pass) uwa\ana
20
jest za zaletę, gdy\ upraszcza pisanie kompilatora, a poza tym kompilatory tego
typu (ang. one-pass) wykonują kompilację szybciej ni\ kompilatory multi-pass.
Tak więc, z powodu ograniczonych zasobów pierwszych systemów komputero-
wych, wiele wczesnych języków programowania było projektowanych tak, aby
mogły być kompilowane w jednym przejściu (np. Pascal).
W pewnych przypadkach zaprojektowana cecha języka programowania
mo\e wymagać więcej ni\ jednego przejścia nad kodem zródłowym. Wadą
kompilacji w jednym przejściu jest to, \e uniemo\liwia ona przeprowadzenie
wielu wyrafinowanych optymalizacji, potrzebnych do wygenerowania kodu o
wysokiej jakości. Czasami trudno dokładnie obliczyć, ile przejść wykonuje
kompilator optymalizujący. Na przykład, ró\ne fazy optymalizacji mogą anali-
zować jedno wyra\enie wiele razy, a inne - tylko jeden raz. Typowy kompilator
o wielu przejściach zwraca ostatecznie kod maszynowy. Są jednak inne rodzaje
kompilatora multi-pass, mianowicie:
" source-to-source - kompiluje język wysokiego poziomu do tego samego
lub innego języka wysokiego poziomu. Na przykład, kompilator do au-
tomatycznego zrównoleglania kodu mo\e do kodu wysokiego poziomu
dopisywać wstawki w tym samym języku wysokiego poziomu, określa-
jące jak prowadzić obliczenia równoległe.
" stage compiler - kompiluje kod zródłowy do kodu asemblera teoretycz-
nej maszyn (np. maszyny języka Prolog - Warren Abstract Machine).
Kompilatory bajtkodu dla języków Java i Pyton są odmianami stage
kompilatora.
" just-in-time compiler - w przypadku tego kompilatora, aplikacje są do-
starczane w postaci bajtkodu, który następnie jest kompilowany do kodu
natywnej maszyny, tu\ przed wykonaniem. Ten typ kompilatora u\ywa-
ny jest m.in. przez systemy: Smalltalk, Java oraz Microsoft .NET Com-
mon Intermediate Language (CIL).
Skompilowany kod zródłowy, zazwyczaj zapisywany w pliku na dysku ja-
ko tzw. plik obiektowy - object file, jest ju\ w języku maszynowym, ale musi
jeszcze zostać połączony z bibliotekami. Biblioteki zawierają dodatkowe in-
strukcje programu, napisane wcześniej przez tego samego programistę albo
przez kogoś innego, i oddzielnie skompilowane. Aączenie skompilowanego ko-
du zródłowego z bibliotekami statycznymi jest wykonywane przez program
zwany linkerem (ang. link - łączyć), na etapie linkowania, określanego te\ mia-
nem konsolidacji. W ten sposób otrzymujemy program gotowy do uruchomie-
nia (plik wykonywalny). Dodatkowo, podczas konsolidacji, do pliku wynikowe-
go mogą być dołączone odpowiednie nagłówki i informacje charakterystyczne
dla konkretnego formatu pliku wykonywalnego.
21
2. Algorytmy i schematy blokowe
Napisanie programu musi być poprzedzone opracowaniem odpowiedniego
algorytmu, tzn. przepisu na rozwiązanie konkretnego problemu.
Algorytmy mo\na zapisywać na ró\ne sposoby:
" w języku naturalnym,
" w pseudokodzie (w języku zbli\onym do naturalnego),
" w postaci schematu blokowego,
" w postaci prezentacji multimedialnej,
" w postaci instrukcji programu.
2.1. Podstawowe cechy algorytmów
Rozwa\my pewien zestaw danych, np. {a1, a2, a3} i zdefiniujmy na nim
pewną operację (np. "), która wygeneruje wyniki {x, y, z}. Przez algorytm bę-
dziemy w tym przypadku rozumieć zestaw czynności (obliczeń), które nale\y
wykonać, aby otrzymać dane wyjściowe z danych wejściowych.
Ogólnie, algorytm to zbiór reguł postępowania, mający na celu prze-
tworzenie informacji wejściowych w informacje wyjściowe. Informacje wej-
ściowe zazwyczaj są nazywane danymi, a informacje wyjściowe - wynikami.
Algorytm musi posiadać skończoną liczbę reguł postępowania i mo\e
zawierać tylko pewien skończony zbiór czynności (instrukcji).
Algorytm opracowuje się dla rozwiązywania problemów o powtarzalności
metod wnioskowania i dla ró\nych wejść. Oznacza to, \e algorytm słu\y do
rozwiązywania problemów tej samej klasy. Wykorzystywane dane powinny być
sparametryzowane, tzn. nie powinno się u\ywać wielkości stałych, ale pewnych
symboli, reprezentujących te dane, np.: a = 1, b = 2, x = a.
Algorytm projektuje się dla zadań, dla których istnieje rozwiązanie.
W przypadku gdy trudno jest udowodnić istnienie rozwiązania, nale\y określić
moment przerwania wykonywania zbioru reguł.
Algorytm powinien uwzględniać wszystkie mo\liwe sytuacje, jakie mo-
gą wystąpić podczas rozwiązywania zadania. Przykładowo, podczas rozwią-
zywania równania a x2 + b x + c = 0 nale\y przewidzieć równie\ okoliczność,
kiedy nie posiada ono rzeczywistych pierwiastków. Ponadto, dla a = 0 nie jest
to równanie kwadratowe, gdy\ wtedy degeneruje się ono do równania liniowe-
22
go. Poza tym, dla a = 0, b = 0 i c `" 0 równanie jest sprzeczne, natomiast gdy
a = 0, b = 0 i c = 0 równanie posiada nieskończenie wiele rozwiązań.
Algorytm mo\e być realizowany przez człowieka albo przez maszynę.
W przypadku komputera to procesor wykonuje operacje arytmetyczne, czyli:
dodawanie, odejmowanie, mno\enie i dzielenie. Budowa procesora determinuje,
w pewnym zakresie, zbiór operacji, które mo\na zastosować podczas rozwiązy-
wania danego zadania. Przykładami algorytmów są:
" przepis na przygotowanie jakiegoś dania,
" instrukcja składania komputera lub samochodu,
" sposób rozwiązywania równania kwadratowego,
" metoda obliczania wyznacznika macierzy itp.
2.2. Klasyfikacja algorytmów
ze względu na sposób implementacji
Istnieją ró\ne kryteria podziału algorytmów na klasy. Ze względu na sposób
implementacji, algorytmy dzieli się na:
" rekurencyjne (rekursywne) lub iteracyjne,
" szeregowe, równoległe lub rozproszone,
" deterministyczne lub niedeterministyczne,
" dokładne lub przybli\one.
Algorytm rekurencyjny to taki, który wywołuje siebie (odnosi się do sie-
bie) do momentu, a\ pewien warunek zostanie spełniony. Algorytm iteracyjny,
do rozwiązania problemu, u\ywa powtarzalnych konstrukcji, takich jak pętle.
Algorytm ten kończy działanie, gdy pętla wykona się określoną liczbę razy albo
gdy spełniony zostanie warunek przerwania tej pętli. Ka\dy algorytm rekuren-
cyjny posiada, mniej lub bardziej skomplikowaną, równowa\ną wersję ite-
racyjną, i odwrotnie. Przekształcenie algorytmu rekurencyjnego, w odpowiada-
jący mu funkcjonalnie algorytm iteracyjny, nazywane jest derekursywacją. Cho-
cia\ algorytmy rekurencyjne bywają prostsze do zrozumienia, to są obcią\one
du\ą zło\onością obliczeniową i pamięciową (kosztowna obsługa stosu wywo-
łań). Algorytmy iteracyjne są bli\sze architekturze procesorów i dlatego wyko-
nują się szybciej oraz zu\ywają mniej pamięci operacyjnej.
Algorytmy są zazwyczaj dyskutowane przy zało\eniu, \e komputer mo\e
wykonać jedną instrukcję w danym momencie. Algorytm projektowany do dzia-
łania w taki sposób nosi nazwę algorytmu szeregowego. Algorytmy równole-
głe wykorzystują natomiast zaletę architektury niektórych komputerów, polega-
23
jącą na mo\liwości pracowania kilku procesorów równocześnie nad rozwiąza-
niem danego problemu. Algorytmy rozproszone wykorzystują wiele kompute-
rów połączonych w sieć. Algorytmy równoległe i rozproszone dzielą problem na
mniej lub bardziej symetryczne podproblemy, by następnie zebrać te częściowe
wyniki z powrotem w całość. Algorytmy te zu\ywają czas nie tylko na oblicze-
nia, ale równie\ na komunikację między procesorami. Na przykład, algorytmy
sortowania mo\na efektywnie zamieniać na równoległe, ale proces komunikacji
jest zbyt czasochłonny. Iteracyjne algorytmy, w większości przypadków, mo\na
konwertować na równoległe. Niektóre algorytmy nie dają się zrównoleglić.
Algorytm deterministyczny rozwiązuje problem podając dokładnie okre-
ślony rezultat na ka\dym etapie. Działanie tego algorytmu jest całkowicie zde-
terminowane przez warunki początkowe. Kilkukrotne uruchomienie algorytmu
deterministycznego doprowadzi za ka\dym razem do takiego samego wyniku.
Algorytm niedeterministyczny rozwiązuje zadanie poprzez zgadywanie,
chocia\ tę metodę prób i błędów mo\na uczynić bardziej skuteczną, stosując od-
powiednie heurystyki, czyli oparte na doświadczeniu i intuicji techniki zgady-
wania, przewidywania itp. Podczas gdy wiele algorytmów zwraca dokładne
rozwiązanie, algorytmy niedeterministyczne szukają aproksymowanego rozwią-
zania, które jest bliskie dokładnemu. Proces aproksymacji mo\e stosować strate-
gię deterministyczną albo losową. Algorytmy przybli\one mają praktyczne
znacznie podczas rozwiązywania wyjątkowo trudnych problemów.
2.3. Klasyfikacja algorytmów
ze względu na sposób zaprojektowania
Ze względu na sposób zaprojektowania lub paradygmat mo\na wyró\nić
następujące typy algorytmów:
" brutalnej siły (ang. brute-force) lub wyczerpującego (gruntownego)
przeszukiwania (ang. exhaustive search),
" dziel i zwycię\aj (ang. divide and conquer),
" dynamicznego programowania (ang. dynamic programming),
" zachłanny (ang. greedy method),
" programowania liniowego (ang. linear programming),
" przeszukiwania i przeliczania (ang. search and enumeration).
Algorytm typu brutalnej siły przegląda (próbuje) wszystkie mo\liwe
rozwiązania w celu znalezienia tego najlepszego.
Algorytm typu dziel i zwycię\aj , w sposób powtarzalny, redukuje dany
problem (zazwyczaj poprzez zastosowanie rekurencji) do jednej lub więcej, ale
24
mniejszych instancji tego samego problemu do momentu, a\ te instancje są na
tyle małe, \e mo\na je łatwo rozwiązać.
Je\eli problem zawiera optymalne substruktury oraz nakładające się pod-
problemy, wówczas warto zastosować podejście zwane dynamicznym progra-
mowaniem. Pozwala ono uniknąć ponownego obliczania rozwiązań częścio-
wych, wyznaczonych dotychczas. Problem zawiera optymalne substruktury wte-
dy, gdy optymalne rozwiązanie całego problemu mo\e być skonstruowane
z optymalnych rozwiązań podproblemów. Problem wykazuje posiadanie nakła-
dających się podproblemów wtedy, gdy te same podproblemy są u\ywane do
rozwiązania wielu ró\nych instancji danego problemu.
Algorytm zachłanny jest podobny do programowania liniowego, z tym
wyjątkiem, \e rozwiązania podproblemów nie muszą być znane na ka\dym eta-
pie. Zamiast tego algorytm zachłanny wybiera takie rozwiązanie, które jest op-
tymalne na danym etapie. Algorytm zachłanny rozszerza rozwiązanie za pomocą
najlepszej przewidywanej decyzji na bie\ącym etapie i najlepszej decyzji podję-
tej na poprzednim etapie. Takie postępowanie nie jest wyczerpujące (nie prze-
szukuje wszystkich sytuacji) i w wielu przypadkach nie daje prawidłowych (do-
kładnych) rozwiązań problemów. Algorytmami tego typu są np. algorytmy wy-
znaczania minimalnego drzewa rozpinającego: Prima, Kruskala oraz Sollina.
Kiedy do rozwiązania problemu stosuje się programowanie liniowe, wtedy
określa się pewne nierówności na danych wejściowych, a następnie próbuje się
wyznaczyć maksimum lub minimum pewnej funkcji liniowej, zdefiniowanej dla
danych wejściowych. Rodzajowym algorytmem programowania liniowego jest
metoda simpleks. Bardziej skomplikowaną odmianą programowania liniowego
jest programowanie całkowitoliczbowe, z którym mamy do czynienia wówczas,
gdy przestrzeń dopuszczalnych rozwiązań jest ograniczona do liczb całkowitych.
Algorytm przeszukujący stara się znalezć element o określonych właści-
wościach w zbiorze innych elementów. Do wirtualnych przestrzeni wyszukiwa-
nia stosuje się metody: przeszukiwania lokalnego, drzewa przeszukiwań oraz
drzewa gry. Metaheurystyczne metody przeszukiwania lokalnego to: symulowa-
ne wy\arzanie, przeszukiwanie tabu oraz programowanie genetyczne. Do metod
drzewa przeszukiwań zalicza się algorytmy: przeszukiwania wgłąb (ang. depth-
first search = DFS), przeszukiwania wszerz (ang. breadth-first search = BFS),
ró\ne techniki przycinania drzewa, przeszukiwania z powrotami (ang. back-
tracking), a tak\e technikę podziału i ograniczeń BB (ang. branch and bound).
Do przeszukiwania drzew gier, takich jak szachy czy warcaby, stosuje się algo-
rytmy minimax albo alpha-beta, w ró\nych odmianach.
25
Ka\da dziedzina nauki ma swoje problemy do rozwiązania i wymaga efek-
tywnych algorytmów. Podobne zagadnienia z jednej dziedziny są często dysku-
towane wspólnie. Algorytmy z ró\nych dziedzin niejednokrotnie przenikają się
i są ze sobą powiązane. Ze względu na obszar zastosowań, algorytmy dzieli się
m.in. na: algorytmy przeszukujące, sortujące, numeryczne, grafowe, kombinato-
ryczne, działające na łańcuchach, kryptograficzne, związane z kompresją da-
nych, sztuczną inteligencją i uczeniem maszynowym.
Algorytmy są tak\e klasyfikowane ze względu na czas potrzebny do ich za-
kończenia w funkcji rozmiaru danych wejściowych. Wyró\niamy tutaj algoryt-
my o zło\oności liniowej, kwadratowej, logarytmicznej, a nawet wykładniczej.
Zło\oność czasowa algorytmu jest miarą ilości czasu potrzebnego do wy-
konania algorytmu w funkcji rozmiaru danych wejściowych (w funkcji długości
ciągu danych wejściowych). Zło\oność czasowa jest zwykle określana poprzez
obliczenie liczby elementarnych operacji wykonywanych przez algorytm. Po-
niewa\ potrzeba stałego czasu na wykonanie ka\dej elementarnej operacji, to
czas wykonania algorytmu i liczba elementarnych operacji są do siebie wprost
proporcjonalne, tzn. ró\nią się o co najwy\ej stały współczynnik. Zło\oność
czasową algorytmów wyra\a się za pomocą notacji du\e O , która opuszcza
składniki ni\szego rzędu. Mówi się, \e tak zdefiniowana zło\oność czasowa jest
opisana asymptotycznie, tzn. gdy rozmiar danych wejściowych rośnie do nie-
skończoności.
2.4. Właściwości schematów blokowych
Algorytm jest często przedstawiany za pomocą graficznej reprezentacji
z wykorzystaniem symboli graficznych. Ta reprezentacja nosi nazwę schematu
blokowego lub sieci działań.
Schemat blokowy jest poglądową formą graficznego przedstawienia algo-
rytmu. Tworzy się go korzystając ze ściśle określonego zbioru figur geome-
trycznych oraz stosując ustalone reguły ich łączenia. We wnętrzu bloków,
w umowny sposób, zapisuje się występujące w algorytmie operacje arytmetycz-
ne, logiczne, operacje wejścia i wyjścia oraz warunki, od których zale\ą decyzje,
co do kolejności wykonywania obliczeń.
Zaletą schematów blokowych jest to, \e graficznie reprezentują algorytm
zarówno ze względu na typy występujących w nim działań, jak i na ich kolej-
ność. Ka\dy schemat blokowy musi być spójny, tzn. od bloku start do bloku
stop musi prowadzić przynajmniej jedna droga.
26
Tabela 2.1. Symbole stosowane w schematach blokowych
Symbol
Nazwa bloku Znaczenie
Określa kierunek (drogę) przepływu danych
STRZAAKA
lub kolejność wykonywania działań.
Od tego bloku rozpoczyna się wykonywanie
START algorytmu. Występuje tylko raz w schemacie.
Wychodzi z niego tylko jedna strzałka.
Na tym bloku kończy się wykonywanie algo-
rytmu. Wchodzi do niego co najmniej jedna
STOP strzałka. Najczęściej występuje tylko raz,
ale dla podniesienia czytelności schematu,
mo\e być powtórzony.
W tym bloku umieszcza się operacje
BLOK WEJŚCIA
wprowadzania (odczytu) danych
/ WYJŚCIA
albo wyprowadzania (zapisu) wyników.
W tym bloku umieszcza się obliczenia lub
BLOK
podstawienia. Blok mo\e zawierać grupę
PRZETWARZANIA
operacji, w efekcie których zmienia się war-
(WYKONAWCZY)
tość, postać lub miejsce zapisu danych.
W tym bloku umieszcza się warunek logiczny
(prosty lub zło\ony), decydujący o dalszej
BLOK
drodze postępowania. Je\eli jest spełniony,
DECYZYJNY
TAK NIE
wtedy są wykonywane operacje na TAK , w
(WARUNKOWY,
przeciwnym wypadku na NIE . Do zapisu
KIERUJCY)
warunku logicznego nale\y u\ywać symboli:
=, `", >, e", <, d", '" (i), (" (lub).
W tym bloku wpisuje się nazwę procesu
PROCES
(podprogramu), który chcemy wykonać, zde-
UPRZEDNIO
finiowanego poza bie\ącym programem.
ZDEFINIOWANY
Punkt koncentracji jest u\ywany dla podnie-
PUNKT sienia czytelności schematu. Oznacza miej-
KONCENTRACJI sce, do którego wchodzi kilka strzałek,
i z którego wychodzi tylko jedna strzałka.
2.5. Schematy blokowe wybranych algorytmów
W rozdziale tym przedstawiono schematy blokowe prostych algorytmów.
Szczegółową ich analizę pozostawiamy Czytelnikowi.
27
Przykład 1. Wyznaczanie największej spośród trzech liczb: a, b, c.
START
wczytaj a, b, c
TAK NIE
a>b
TAK NIE NIE
TAK
a>c b>c
wypisz a
wypisz c wypisz b wypisz c
STOP
Rys. 2.1. Schemat blokowy algorytmu wyznaczania największej spośród liczb: a, b, c
Przykład 2. Rozwiązywanie równania liniowego a x + b = 0 .
START
wczytaj a, b
TAK NIE
a=0
x ! -b / a
TAK NIE
b=0
nieskończenie równanie
wypisz x
wiele rozwiązań sprzeczne
STOP
Rys. 2.2. Schemat blokowy algorytmu rozwiązywania równania liniowego
28
Przykład 3. Wyszukiwanie największego elementu w tablicy n-elementowej A.
START
wczytaj n,
A[1], & , A[n]
max ! A[1]
i ! 2
NIE TAK
i > n
NIE TAK wypisz max
A[i] > max
STOP
max ! A[i]
i ! i + 1
Rys. 2.3. Schemat blokowy algorytmu wyznaczania największego elementu tablicy A
Przykład 4. Obliczanie silni liczby naturalnej n.
START
wczytaj n
i ! 1
silnia ! 1
TAK NIE
i < n
i ! i + 1
wypisz silnia
silnia ! silnia*i
STOP
Rys. 2.4. Schemat blokowy algorytmu obliczania silni liczby naturalnej n
29
Przykład 5. Wyznaczanie średniej arytmetycznej n wczytywanych liczb.
START
wczytaj n
k ! n
s ! 0
TAK NIE
k > 0
s ! s / n
wczytaj a
s ! s + a
wypisz s
k ! k - 1
STOP
Rys. 2.5. Schemat blokowy algorytmu wyznaczania średniej arytmetycznej n liczb
Przykład 6. Obliczanie NWD liczb x i y metodą z odejmowaniem.
START
wczytaj x, y
n ! x, m ! y
NIE TAK
n = m
NWD(x,y) ! n
TAK NIE
n > m
wypisz NWD(x,y)
n ! n-m
m ! m-n
STOP
Rys. 2.6. Schemat blokowy algorytmu obliczania NWD metodą z odejmowaniem
30
Przykład 7. Obliczanie NWD liczb x i y metodą dzielenia z resztą.
START
wczytaj x, y
TAK NIE
y `" 0
r ! x mod y NWD(x,y) ! x
wypisz NWD(x,y)
x ! y
y ! r
STOP
Rys. 2.7. Schemat blokowy algorytmu obliczania NWD metodą dzielenia z resztą
Przykład 8. Szybkie (z minimalną liczbą mno\eń) obliczanie an.
START
wczytaj a, n
x ! a
k ! n
wynik ! 1
TAK NIE
k `" 0
TAK NIE
wypisz wynik
k mod 2 `" 0
wynik!wynik"x
x ! x"x STOP
k ! k-1
k ! k / 2
Rys. 2.8. Schemat blokowy algorytmu szybkiego obliczania an
31
Przykład 9. Testowanie zło\oności liczby naturalnej.
START
wczytaj n
k ! 2
r ! n mod k
NIE TAK
r = 0
k ! k + 1 n liczba zło\ona
NIE TAK
n = k
STOP
n liczba pierwsza
STOP
Rys. 2.9. Schemat blokowy prostego algorytmu sprawdzającego, czy liczba jest zło\ona
32
Przykład 10. Rozwiązywanie równania kwadratowego a x2 + b x + c = 0.
START
wczytaj a, b, c
TAK
NIE
a `" 0
Równanie
D ! b*b - 4*a*c
liniowe
bx+c = 0
TAK NIE
D e" 0
Brak pierwiastków
TAK NIE
rzeczywistych
D = 0
x1 ! -b/2a
x1!(-b+ D)/2a
"
D)/2a
x2 ! x1 x2!(-b-"
wypisz x1, x2
STOP
Rys. 2.10. Schemat blokowy algorytmu rozwiązywania równania kwadratowego
33
Przykład 11. Przybli\one obliczanie pierwiastka kwadratowego z liczby a e" 0.
START
eps ! 1e-9
wczytaj a a jest ujemne
TAK NIE
a e" 0
x ! a/4
x1 ! x
x ! 0.5(x1+a/x1)
NIE TAK
|x-x1| < eps
wypisz x
STOP
Rys. 2.11. Schemat blokowy algorytmu
przybli\onego obliczania pierwiastka kwadratowego z nieujemnej liczby a
2.6. Zadania
Narysować schematy blokowe algorytmów rozwiązujących problemy:
Zad. 2.1. zamiany liczby naturalnej dziesiętnej na liczbę szesnastkową.
Zad. 2.2. zamiany liczby szesnastkowej na liczbę w systemie dziesiętnym.
Zad. 2.3. obliczania NWD tablicy liczb, tzn. NWD(a1, a2,K, an).
Zad. 2.4. obliczania najmniejszej wspólnej wielokrotności pary liczb m i n we-
dług zale\ności: NWW(m, n) = (m " n) / NWD(m, n).
Zad. 2.5. skracania ułamka zwykłego.
34
Zad. 2.6. dodawania dwóch ułamków zwykłych poprzez sprowadzenie ich do
wspólnego mianownika, a następnie skrócenie powstałego ułamka.
Zad. 2.7. zamiany ułamka niewłaściwego na część całkowitą i ułamek właściwy.
Zad. 2.8. zamiany ułamka dziesiętnego na ułamek w systemie binarnym.
Zad. 2.9. zamiany ułamka okresowego na ułamek zwykły.
n
Zad. 2.10. obliczania pierwiastka n-tego stopnia z liczby a, tzn. x = a metodą
a + (n -1)xin
n
Newtona: xi+1 = , gdzie xi - kolejne przybli\enie a ,
n xin-1
x0 = a , z dokładnością eps taką, \e xi+1 - xi < eps .
N N
(-1)n (-1)n
Zad. 2.11. obliczania sin x H" x2n+1 oraz cos x H" x2n .
" "
(2n +1)! (2n)!
n=0 n=0
Argument x jest dowolną liczbą rzeczywistą, N - liczbą naturalną.
x x2 xn
Zad. 2.12. obliczania ex H"1+ + + ... dla podanego n i x.
1! 2! n!
Zad. 2.13. obliczania metodą Hornera wartości wielomianu
wn (x) = anxn + an-1xn-1 + K + a1x + a0. Na przykład dla n = 4 :
a4x4 + a3x3 + a2x2 + a1x + a0 = a0 + x(a1 + x(a2 + x(a3 + xa4))).
Dane wejściowe to: n, a0, a1, K, an oraz konkretne x = x0.
Zad. 2.14. obliczania wartości pochodnej wielomianu wn (x) w punkcie x = x0.
Zad. 2.15. transponowania macierzy kwadratowej.
Zad. 2.16. zamiany parami elementów macierzy kwadratowej, poło\onych sy-
metrycznie względem drugiej (nie głównej) przekątnej.
Zad. 2.17. obracania macierzy kwadratowej o 90, 180 i 270 stopni.
Zad. 2.18. wyznaczania w tablicy liczby najbli\szej zeru, ale ró\nej od zera.
Zad. 2.19. sortowania w miejscu jednowymiarowej tablicy liczb.
Zad. 2.20. sprawdzania, czy dwa wyrazy o tej samej długości są anagramami.
Zad. 2.21. odwracania w miejscu łańcucha znakowego o długości n. Czy trze-
ba osobno rozwa\ać n parzyste i n nieparzyste?
Zad. 2.22. dokładnego dodawania i odejmowania wielocyfrowych (do stu cyfr)
liczb naturalnych. Uwaga: liczby mogą posiadać ró\ną ilość cyfr.
35
3. Podstawowe elementy języka Pascal
3.1. Zalety języka Pascal
Pascal jest językiem wysokiego poziomu. Oznacza to, \e u\ytkownik piszą-
cy program w języku Pascal nie musi znać szczegółów wewnętrznej budowy
komputera. Pascal ma wbudowane precyzyjne mechanizmy kontroli struktur i
reguł gramatycznych, dzięki temu łatwa jest identyfikacja i poprawianie błędów.
Oprócz tego Pascal jest językiem:
" algorytmicznym - pomyślanym tak, aby łatwo mo\na w nim zapisywać
algorytmy opracowane przez u\ytkownika,
" strukturalnym - poszczególne fragmenty algorytmu mo\na zapisać w
postaci wyraznie wyodrębnionych struktur językowych,
" modularnym - program mo\na budować z oddzielnych 'cegiełek' (mo-
dułów), wymienialnych bez naruszenia pozostałej części programu,
" publikacyjnym - istnieje mo\liwość opublikowania oryginalnego, inte-
resującego algorytmu w notacji języka Pascal.
3.2. Standardowe symbole i słowa kluczowe
Języki programowania słu\ą do przedstawienia algorytmów w postaci, któ-
ra mo\e być wykonana przez komputer. Jedną z wygodnych form komunikacji
człowieka z maszyną jest zapis tekstowy, dlatego program jest tekstem. Ze
względu na ograniczenia praktyczne, tekst ten jest ciągiem znaków na jednym
poziomie, tzn. nie ma indeksowania stosowanego np. w matematyce czy fizyce.
Program pascalowy buduje się z elementarnych jednostek tekstowych języ-
ka nazywanych symbolami. Symbol mo\e być pojedynczym znakiem bądz cią-
giem znaków alfabetu języka.
W skład alfabetu języka Pascal wchodzą następujące symbole:
" 26 małych i 26 du\ych liter alfabetu łacińskiego oraz znak _,
" cyfry: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
" znak odstępu (spacja),
" jednoznakowe symbole specjalne: + - * / = ^ < > ( ) [ ] { }. , : ; # $ @,
" dwuznakowe symbole specjalne:
operator przypisania :=
operatory relacji: <> <= >=
oznaczenie zakresu ..
36
Słowem kluczowym nazywamy ka\dy z wyrazów języka angielskiego:
and, array, as, asm, begin, case, class, const, constructor, destructor,
div, do, downto, else, end, except, exports, file, finalization, finally, for,
function, goto, if, implementation, in, inherited, initialization, inline,
interface, is, label, library, mod, nil, not, object, of, or, packed,
procedure, program, property, raise, record, repeat, set, shl, shr, string,
then, threadvar, to, try, type, unit, until, uses, var, while, with, xor.
Słowa kluczowe są zastrze\one, tzn. programista nie mo\e przedefiniować
ich znaczenia (u\ywając np. słowa kluczowego jako nazwy zmiennej w swoim
programie). Słowa kluczowe mają specjalne znaczenie - są nazwami operato-
rów, elementami instrukcji itp.
Identyfikatorem (nazwą) jest dowolnie długi ciąg liter, cyfr i znaków pod-
kreślenia, zaczynający się od litery lub znaku podkreślenia. Znaczące są jednak
tylko 63 pierwsze znaki. Identyfikatory słu\ą do oznaczania programów, stałych,
typów, zmiennych, etykiet, pól w rekordach, funkcji, procedur, parametrów for-
malnych oraz pól i metod w obiektach. W identyfikatorach nie wolno u\ywać
polskich liter z ogonkami. Oprócz tego: litery a i A są nierozró\nialne, litery b
i B są nierozró\nialne itd.
Przykłady poprawnych nazw: liczba_1, stala3, m, B_205, XXL.
Przykłady niepoprawnych nazw: 2tablica, ście\ka, Suma-1, #5.
Pewne identyfikatory mają nazwy ustalone przez twórców Pascala. U\yt-
kownik mo\e przedefiniować pierwotne znaczenie tych identyfikatorów w sekcji
deklaracji programu i u\ywać ich w zdefiniowanym przez siebie znaczeniu.
Oznacza to utratę wbudowanych w języku ułatwień. Predefiniowane identyfika-
tory słu\ą do oznaczania stałych, zmiennych, typów, funkcji i procedur. Zostały
one zgrupowane w modułach standardowych System, SysUtils i Math. Aby
u\ywać tych modułów, nale\y je zadeklarować na początku programu, poprze-
dzając ich nazwy słowem kluczowym uses. Nazwy z modułu System są dostęp-
ne bez potrzeby deklaracji tego modułu.
3.3. Sposoby zapisu liczb
Liczby w języku Pascal zapisuje się w sposób podobny do notacji matema-
tycznej. W przypadku liczb rzeczywistych, zamiast przecinka oddzielającego
część całkowitą od części ułamkowej, stosuje się kropkę.
Liczby całkowite w systemie dziesiętnym zapisuje się u\ywając cyfr ze
zbioru {0, 1, & , 9} poprzedzonych znakiem + lub -. Znak + mo\na pominąć.
Liczba mo\e zaczynać się od jednej lub więcej cyfr 0. Między pierwszą cyfrą
37
liczby, a znakiem + lub - mo\e być odstęp. Przykłady poprawnych liczb w sys-
temie dziesiętnym: 2013, +02013, + 92, - 0008885.
Liczby całkowite w systemie szesnastkowym tworzy się z cyfr nale\ących
do zbioru {0, 1, 2, & , 9, A, B, C, D, E, F}, poprzedzonych symbolem $ oraz
ewentualnym znakiem + lub -. Symbol $ musi występować bezpośrednio przed
pierwszą cyfrą. Przykłady poprawnych liczb, zapisanych w systemie szesnast-
kowym: $9802, +$8FA3C, -$3FFFFFF, -$0007FFFFFF.
Liczba rzeczywista zbudowana jest z mantysy, litery E (lub e) oraz cechy.
Mantysa jest liczbą dziesiętną, składającą się z części całkowitej, kropki i części
ułamkowej, i mo\e być poprzedzona znakiem + lub -. Cecha jest liczbą całko-
witą poprzedzoną ewentualnie znakiem + lub -. Mantysa i cecha mo\e zawierać
wyłącznie cyfry dziesiętne. Dozwolone jest opuszczenie części ułamkowej wraz
z poprzedzającą ją kropką albo litery E i cechy. Symbolicznie liczbę rzeczywistą
mo\na zapisać tak: mantysaEcecha, czemu odpowiada wartość mantysa 10cecha.
Przykłady: -56.980, -4.6592e-103, 0.314E+1, 2013E+00, +89.9377, 686.2e23.
3.4. Sposoby notowania komentarzy
Komentarzem nazywamy dowolny ciąg znaków, ograniczony z lewej stro-
ny nawiasem klamrowym otwierającym {, a z prawej strony nawiasem klamro-
wym zamykającym }. Taki komentarz mo\e rozciągać się na wiele linii tekstu
programu. Nawias { nie musi być pierwszym znakiem w danej linii. Znakami
ograniczającymi komentarz mogą być tak\e symbole podwójne (* i *). Je\eli
bezpośrednio po znaku { lub (* występuje symbol $, to taki komentarz jest trak-
towany jak dyrektywa kompilatora. Komentarze mo\na zagnie\d\ać. Tekst roz-
poczynający się od znaków // jest traktowany jak komentarz, ale tylko do końca
bie\ącej linii. Umieszczanie komentarzy w programie nale\y do dobrej praktyki
programistycznej. Przykłady komentarzy:
{ Obliczanie miejsc zerowych funkcji kwadratowej }
(* Zapisanie wyników do pliku *)
// Sprawdzenie, czy delta jest większa od zera.
{ Komentarz (* zagnie\d\ony *) } albo { Komentarz { zagnie\d\ony } }
{$mode objfpc} ! dyrektywa kompilatora.
Separatorami są w programie pascalowym: średnik, przecinek, komentarz,
odstęp (spacja), przejście do następnego wiersza (enter) oraz wszystkie znaki
sterujące ASCII, tzn. znaki o kodach od 0 do 31. Separatory słu\ą do rozdziela-
nia poszczególnych elementów programu. Dowolne dwa słowa kluczowe, iden-
tyfikatory oraz liczby muszą być rozdzielone co najmniej jednym separatorem.
38
3.5. Zasady deklarowania stałych i zmiennych
Stałe, zwane te\ literałami, mają za zadanie zwiększenie czytelności kodu
zródłowego. Ka\de wystąpienie identyfikatora stałej jest równowa\ne wystąpie-
niu przypisanego mu wyra\enia stałego. Wyra\enie stałe to takie, które mo\e
być obliczone przez kompilator bez wykonywania programu. Stałe nie muszą
być liczbami. Wartości stałych nie zmieniają się przez cały czas działania pro-
gramu. Predefiniowane w module System stałe to: False, True, MaxInt,
MaxLongint. Stałe definiujemy po słowie const.
Przykłady definicji stałych:
const kierunek = 'informatyka';
liczba_pi = 3.1415926;
c = 299792458;
falsz = false;
sygnal = #$7; // znak o kodzie ASCII równym $7
Wyra\enie stałe jest to wyra\enie, które mo\e być obliczone przez kompi-
lator. W wyra\eniach stałych dopuszczalne jest stosowanie następujących funk-
cji standardowych: Abs, Addr, Chr, Hi, High, Length, Lo, Low, Odd, Ord,
Pred, Round, SizeOf, Succ, Swap, Trunc. Wyra\enia liczbowe stałe są typu
Longint albo Extended, w zale\ności od kontekstu. Mo\na te\ tworzyć wyra\e-
nia stałe typu łańcuchowego lub zbiorowego. Za pomocą słowa kluczowego
const wyra\eniu stałemu mo\na nadać nazwę i stosować tę nazwę w programie,
zamiast wyra\enia.
Przykłady wyra\eń stałych:
const cena = '100' + Chr(36);
rozmiar = (1 shl 20);
Zmienną nazywamy daną, która mo\e przyjmować ró\ne wartości w ra-
mach typu przypisanego do tej zmiennej. Zmienna jest wielkością zmieniającą
swoją wartość w trakcie wykonywania programu. Zmienna powinna być zade-
klarowana przed pierwszym u\yciem. Je\eli zmienna ma w programie zasięg
globalny, to w momencie deklaracji jest jej przypisywana wartość zerowa. De-
klaracje zmiennych sygnalizują kompilatorowi konieczność zarezerwowania dla
nich odpowiedniej ilości pamięci. Deklaracja zmiennej ma postać:
var nazwa_zmiennej : typ_zmiennej;
gdzie:
" var - słowo kluczowe oznaczające deklarację zmiennej,
" nazwa_zmiennej - identyfikator zmiennej,
" typ_zmiennej - zbiór wartości, jaki mo\e przyjmować ta zmienna.
39
Zmienna przechowuje pewną informację o stanie maszyny cyfrowej w jed-
nej lub więcej komórkach pamięci operacyjnej (rys. 3.1). Nazwa zmiennej po-
winna sugerować rolę tej zmiennej w programie.
delta
identyfikator
zmienna (byte)
123
wartość typ
Rys. 3.1. Graficzna reprezentacja zmiennej
Przykłady deklaracji zmiennych:
var miesiac : Byte;
pole, obwod : Double;
znak : Char;
tytul, autor : string;
Podczas deklaracji, zmiennym mo\na nadać wartości początkowe:
var miesiac : Byte = 5;
pole : Double = 100.0;
znak : Char = 'Z';
tytul : string = 'Pan Tadeusz';
Uwaga: deklaracja pole, obwod : Double = 100.0; jest niepoprawna,
poniewa\ niewiadomo, której zmiennej nale\y przypisać wartość 100.0.
3.6. Definicja typu i rodzaje typów danych
Typem nazywamy zbiór wartości zmiennej. Typy dzieli się na standardowe
(wbudowane) i niestandardowe. Typy standardowe są predefiniowane przez
twórców języka programowania, natomiast wszystkie typy wprowadzone przez
programistę muszą zostać przez niego opisane. Stosowanie w programie identy-
fikatorów typów zwiększa czytelność kodu zródłowego. Ka\da stała, zmienna,
wyra\enie lub wartość zwracana przez funkcję jest pewnego typu. Typ wyra\e-
nia określa tak\e zestaw dopuszczalnych operacji, które mo\na na nim przepro-
wadzić. Definicja typu jest następująca: type nazwa_typu = opis_typu;
" type - słowo kluczowe oznaczające definicję typu,
" nazwa_typu - identyfikator typu,
" opis_typu - opis jednego z typów: wyliczeniowego, okrojonego, łańcu-
chowego, tablicowego, rekordowego, zbiorowego, plikowego, klasowe-
go, wskaznikowego, proceduralnego albo identyfikator typu standardo-
wego lub zdefiniowanego wcześniej przez u\ytkownika.
Przykłady definicji typów:
40
type pora_roku = (Wiosna, Lato, Jesien, Zima);
liczba_rzymska = string;
temperatura = Real;
macierz = array [1..5, 1..5] of Integer;
Typy predefiniowane dzieli się, ze względu na postacie i zakresy, na:
" podstawowe - takie same bez względu na implementację języka,
" ogólne - zale\ne od procesora i systemu operacyjnego.
Do typów podstawowych zalicza się typy: proste, łańcuchowe, struktural-
ne, typy opisujące obiekty, wskaznikowe, proceduralne i wariantowe.
Typy proste dzieli się na:
" porządkowe: wyliczeniowe, całkowite, znakowe, logiczne, okrojone,
" rzeczywiste: Real, Single, Double, Extended, Comp, Currency.
Wszystkie typy proste składają się ze skończonego i uporządkowanego
zbioru wartości. Z ka\dą wartością typu porządkowego jest związana liczba po-
rządkowa (liczba całkowita) jednoznacznie określająca umiejscowienie tej war-
tości wśród wszystkich wartości danego typu.
Na podstawie typów prostych mo\na tworzyć inne, bardziej rozbudowane
struktury danych, tzw. typy strukturalne. Wprowadzono je w związku z dą\e-
niem do mo\liwie szerokiego zakresu zastosowań języka Pascal. Typami struk-
turalnymi są typy: tablicowe, rekordowe, zbiorowe i plikowe.
Typ wyliczeniowy stosuje się do zbiorów o niewielkiej liczbie elementów,
na których nie wykonuje się operacji arytmetycznych.
type identyfikator_typu = (lista_identyfikatorów);
W nawiasach ( ) podaje się listę ró\nych nazw, które tworzą zbiór wartości
tego typu, przy czym pierwszej nazwie odpowiada wartość 0, drugi - 1, trzeciej
- 2 itd. Przykłady definicji typu wyliczeniowego:
type pory_dnia = (rano, poludnie, wieczor, noc);
moje_grupy = (L5, L7, L12, L13, L16, L18);
kolory = (czerwony, zielony, niebieski);
greckie = (alfa, beta, gamma, delta);
Wszystkie typy całkowite są predefiniowane i są podzbiorami zbioru liczb
całkowitych (który w matematyce jest nieskończenie liczny). Wartość porząd-
kowa elementu dowolnego typu całkowitego jest równa wartości tego elementu.
Typami porządkowymi nie są jednak typy całkowite QWord i Int64. Typy cał-
41
kowite języka Free Pascal zebrano w tabeli 3.1. Dla typów Integer i Cardinal
podano liczbę bajtów dla 32 bitowej implementacji języka.
Tabela 3.1. Typy całkowite i ich zakresy
Typ B wartość minimalna wartość maksymalna
1
Shortint P -128 127
2
Smallint P -32 768 32 767
4
Longint P -2 147 483 648 2 147 483 647
1
Byte P 0 255
2
Word P 0 65 535
DWord,
4
P 0 4 294 967 295
LongWord
8
QWord P 0 18 446 744 073 709 551 615
8 -9 223 372 036 854 775 808
Int64 P 9 223 372 036 854 775 807
4
Integer O -2 147 483 648 2 147 483 647
4
Cardinal O 0 4 294 967 295
P typ podstawowy, O typ ogólny (zale\ny od rodzaju procesora i sys. oper.),
B liczba bajtów pamięci potrzebna do przechowywania zmiennej tego typu
Przykłady deklaracji zmiennych typu całkowitego:
var a, b, c : Shortint;
silnia : QWord;
licznik : Cardinal;
W programach zaleca się stosowanie ogólnych typów całkowitych, które
lepiej wykorzystują rodzaj procesora i zainstalowany system operacyjny.
W języku Free Pascal zdefiniowano dwa podstawowe typy znakowe i je-
den ogólny typ znakowy (tab. 3.2).
Tabela 3.2. Typy znakowe
Typ B Zbiór wartości Liczba znaków
znaki uporządkowane zgodnie z tabelą roz-
1
AnsiChar P 256
szerzonego zestawu znaków ASCII (0255)
znaki uporządkowane zgodnie z tabelą zna-
2
WideChar P 65536
ków standardu UNICODE (065535)
znaki uporządkowane zgodnie z tabelą roz-
Char O 1 szerzonego zestawu znaków ASCII (0255) 256
P typ podstawowy, O typ ogólny (zale\ny od rodzaju procesora i sys. oper.),
B liczba bajtów pamięci potrzebna do przechowywania zmiennej tego typu
42
Przykłady deklaracji zmiennych typu znakowego:
var znak : Char;
litera : AnsiChar;
calka : WideChar;
Podstawowym typem logicznym jest typ Boolean. Pozostałe typy logiczne,
wymienione w tabeli 3.3, stosuje się dla zapewnienia zgodności programów pas-
calowych z programami napisanymi w innych językach. Wartości typów logicz-
nych są oznaczane za pomocą dwóch predefiniowanych stałych False i True.
Tabela 3.3. Typy logiczne
Typ B zbiór wartości (wartość porządkowa)
1
Boolean P False (0) True (1)
1
ByteBool O False (0) True (od 1 do 255)
2
WordBool O False (0) True (od 1 do 65535)
4
LongBool O False (0) True (od 1 do 4 294 967 295)
P typ podstawowy, O typ ogólny (zale\ny od rodzaju procesora i sys. oper.),
B liczba bajtów pamięci potrzebna do przechowywania zmiennej tego typu
Przykłady deklaracji zmiennych typu logicznego:
var decyzja : Boolean;
ok : Boolean;
czy_trojkat : Boolean;
Typy okrojone słu\ą do ograniczenia zakresów wartości dowolnego z do-
tychczas opisanych typów porządkowych. Definicja typu okrojonego ma postać:
type identyfikator_typu = stala1 .. stala2;
Obie stałe muszą być tego samego typu porządkowego i stala1 d" stala2.
Przykłady definicji typów okrojonych:
type litera = 'a' .. 'z';
type cyfra_dz = 0 .. 9;
type cyfra_zn = '0' .. '9';
type miesiac = 1 .. 12;
type klasy = (Ia, Ib, IIa, IIb, IIc, IId, IIIa, IIIb);
klasy_drugie = IIa .. IId;
43
Do typów prostych nale\ą równie\ standardowe typy rzeczywiste (tab.
3.4), które nie są porządkowymi. Ka\dy z dostępnych typów rzeczywistych jest
dyskretnym i skończonym podzbiorem zbioru liczb rzeczywistych. Z tego po-
wodu nie wszystkie liczby rzeczywiste (z osi liczbowej) posiadają dokładną re-
prezentację w typach rzeczywistych. Maksymalna liczba cyfr znaczących man-
tysy wynosi 7, 15 albo 19 w sytuacji, gdy wyspecyfikowano kropkę dziesiętną.
Najmniejsza wartość liczb typu Real, Single, Double, Extended jest, w przybli-
\eniu, równa największej wartości dodatniej pomno\onej przez (-1). Typ Comp
stosuje się do zapamiętywania du\ych liczb całkowitych. Kompilator traktuje
dane tego typu jako wartości rzeczywiste bez części wykładniczej. Typ Curren-
cy słu\y do obliczeń pienię\nych, gdy\ liczby tego typu mają postać dokładną.
Tabela 3.4. Typy rzeczywiste
Liczba
Najmniejsza Największa
cyfr zna-
Typ B wartość wartość
czących
dodatnia dodatnia
mantysy
zale\y zale\y zale\y
4
albo
Real O od od od
8
platformy platformy platformy
4 3.4E+38
Single P 2-149H"1.40E-45 7 albo 8
8 1.7E+308
Double P 2-1074H"4.94E-324 15 albo 16
10 1.1E+4932
Extended P 2-16445H"3.65E-4951 19 albo 20
wartość najmniejsza wartość największa
-263 = 263 - 1 =
8
Comp P 19 albo 20
9223372036854775807
-9223372036854775808
8 922337203685477.5807
Currency P -922337203685477.5808 19
P typ podstawowy, O typ ogólny (zale\ny od rodzaju procesora i sys. oper.),
B liczba bajtów pamięci potrzebna do przechowywania zmiennej tego typu
Przykłady deklaracji zmiennych typu rzeczywistego:
var a, b, c : Single;
suma : Double;
moje_pi : Extended;
silnia : Comp;
kwota : Currency;
44
Zmienne typu łańcuchowego słu\ą do przechowywania ciągów znaków,
głównie typu Char. Free Pascal pozwala definiować łańcuchy krótkie (takie jak
w Turbo Pascalu) oraz długie (tab. 3.5). Pamięć, dla łańcuchów krótkich, jest
przydzielana statycznie. Mogą one jednak zmieniać swoją długość (w trakcie
wykonywania programu) od 0 do maksymalnej długości N, podanej w definicji
typu krótkiego lub do wartości domyślnej równej 255.
Ka\dy znak łańcucha krótkiego zajmuje 1 bajt pamięci. Pierwszy znak
znajduje się pod indeksem 1, drugi pod indeksem 2 itd. Po indeksem 0 jest
przechowywana informacja o aktualnej długości łańcucha.
Zmienna typu łańcucha długiego zajmuje w pamięci 4 bajty i jest wskazni-
kiem do miejsca w pamięci, w którym znajduje się łańcuch. Długość łańcucha
nie jest bezpośrednio pamiętana, tzn. jest wyznaczana na \ądanie u\ytkownika.
Tabela 3.5. Typy łańcuchowe
Typ Długość Uwagi
string[N] 2d"Nd"255 Zawsze oznacza łańcuch krótki.
zale\y od Typ string oznacza typ ShortString w stanie {$H-}.
string
{$H} Typ string oznacza typ AnsiString w stanie {$H+}.
ShortString 255 Zawsze oznacza łańcuch krótki.
Długość ograniczona tylko rozmiarem pamięci. Pamięć
przydzielana dynamicznie na stercie. Przechowuje znaki
AnsiString dowolna
typu Char. Aańcuch ten jest zakończony znakiem pu-
stym (#0), którego jednak nie zalicza się do łańcucha.
Ma te sami właściwości, co typ AnsiString,
WideString dowolna
z tym, \e przechowuje znaki typu WideChar.
Standardowo dyrektywa {$H} kompilatora jest włączona, tzn. ma stan {$H+}.
Elementami łańcucha są wartości typu Char albo WideChar. Do poszcze-
gólnych elementów łańcucha mo\na się odnosić tak, jak do elementów tablicy -
poprzez indeks, np. s[1], s[2], s[10]. Typ łańcuchowy jest jedynym typem tabli-
cowym, na którym dopuszczalne są operacje agregujące (np. '+') oraz wartości
tego typu mogą być parametrami procedur Write, Writeln, Read i Readln.
Przykłady deklaracji zmiennych typu łańcuchowego:
var imie : string[16];
s1, s2 : string;
linia : ShortString;
wydzial : AnsiString;
45
Typ tablicowy jest typem strukturalnym, czyli typem zło\onym. Zmienna
typu tablicowego przechowuje elementy tego samego typu (prostego, łańcucho-
wego lub strukturalnego). Ogólna definicja typu tablicowego ma postać:
type identyfikator_typu = array [typy_indeksowe] of typ_skladowy;
Typy indeksowe są opisami typu porządkowego z wyjątkiem Longint. Typ skła-
dowy oznacza dowolny typ. Przykłady definicji typów tablicowych:
{tablice 1-wymiarowe}
type IntList = array [1..100] of Integer;
type CharCode = array ['A'..'Z'] of Byte;
var A : IntList;
var C : CharCode;
&
A[10] := 14000; C['Z'] := 255; // przykłady u\ycia
{tablice 2-wymiarowe}
type Matrix = array [0..10, -5..5] of Real;
Macierz = array [0..10] of array [-5..5] of Real;
{tablica 2-wymiarowa jako tablica tablic 1-wymiarowych}
type Tab = array [1..100] of Single;
Wynik = array [1.. 50] of Tab;
var M : Matrix;
var W : Wynik; {tablica 2-wymiarowa}
{przykłady odwołań do elementów tablicy 2-wymiarowej}
M[1,0] := -1.043; // albo M[1][0] := -1.043;
W[2,35] := 21.25; // albo W[2][35] := 21.25;
Odwoływanie się do poszczególnych elementów tablicy wymaga wprowa-
dzenia pojęcia selektora. W typie tablicowym, podobnie jak w łańcuchowym, se-
lektorem są nawiasy kwadratowe, w których podajemy indeks(y) elementu(ów),
do którego(ych) odwołujemy się.
W Lazarus FPC jest dopuszczalna definicja zmiennych typu tablicowego
z wartościami początkowymi.
Przykłady takich definicji:
type Tabz = array [0..5] of Char;
var znaki : Tabz = ('a', 'z', 'A', 'X', '3', '#');
type Dane = array [1..4] of Smallint;
var liczby : Dane = (1, 2, 100, 5);
46
Dotychczas rozwa\ane tablice były statyczne, tzn. ich rozmiar był znany
kompilatorowi przed kompilacją. Mo\liwe jest tak\e definiowanie tablic o roz-
miarze określanym w trakcie wykonywania programu. Typ indeksowy takich ta-
blic jest domyślnie całkowity. Tablice dynamiczne, domyślnie, indeksuje się
od 0. Tablice dynamiczne definiuje się następująco:
type identyfikator_typu = array of typ_skladowy;
Przykład definicji tablicy dynamicznej:
type tablica = array of Integer;
var A : tablica;
BEGIN
SetLength(A, 10); { High(A) a" 9 }
A[0] := 15;
A[9] := -24;
END.
Rekord jest zło\oną strukturą danych, której składowe, zwane polami, mo-
gą być ró\nych typów. Pola rekordu równie\ mogą być typu strukturalnego. Typ
rekordowy definiuje się następująco:
type identyfikator_typu = record
pole1 : typ1;
pole2 : typ2;
&
poleN : typN;
end;
Przykład definicji typu rekordowego:
type TOsoba = record
imie : string[20];
plec : Char;
wiek : Byte;
adres : array [1..10] of string;
end;
Cechy typu rekordowego:
" rekordy mogą być zagnie\d\ane, tzn. pole rekordu mo\e być ponownie
typu rekordowego (struktura hierarchiczna),
" pola tego samego typu mogą wystąpić jako oddzielone przecinkiem,
47
" dostęp do poszczególnych pól zmiennej rekordowej odbywa się za po-
mocą kropki oraz nazwy pola (np. zespolona.re, zespolona.im),
" podobnie jak w przypadku tablic, zmienna rekordowa, jako całość, nie
mo\e być parametrem procedur: Write, Writeln, Read, Readln.
" zmienne rekordowe mogą wystąpić w instrukcji przypisania pod warun-
kiem, \e są tego samego typu,
" pole rekordu mo\e występować we wszystkich operacjach, jakie są do-
puszczalne dla typu, jakiego jest to pole; w szczególności pole rekordu
mo\e być parametrem procedury lub funkcji.
Typ zbiorowy (mnogościowy) jest zbiorem wszystkich podzbiorów danego
typu porządkowego. Typ zbiorowy definiuje się następująco:
type identyfikator_typu = set of typ_porzadkowy;
Liczba elementów typu porządkowego, zwanego te\ bazowym, występują-
cego w definicji typu zbiorowego, nie mo\e przekraczać 256. Ponadto liczby po-
rządkowe tych elementów muszą nale\eć do przedziału [0, 255] (dlatego nie-
prawidłowa jest definicja: set of [-5..5]). Wartości typu zbiorowego zapisuje się
przez podanie w nawiasach kwadratowych, w dowolnej kolejności, listy elemen-
tów danego zbioru. Zapis [ ] oznacza zbiór pusty.
Przykład 1. Definicja kilku typów zbiorowych.
type
CharSet = set of Char;
Digits = set of 0..9;
Day = (Sun, Mon, Tue, Wed, Thu, Fri, Sat);
Days = set of Day;
Przykład 2. Deklaracja i u\ycie zmiennych typu zbiorowego.
type znaki = set of Char;
var a, b, c : znaki;
z : Char;
BEGIN
a := ['0'..'9', 'a'..'z', 'A'..'Z'];
b := [ ]; // zbiór pusty
z := #27; // znak ASCII o kodzie równym 27
c := [z];
END.
48
Kompilator dopuszcza zmianę wartości wyra\enia jednego typu na wartość
innego typu. Operacja taka nazywa się konwersją typu. Zmiany typu dokonuje
się za pomocą konstrukcji: identyfikator_typu (wyra\enie). Identyfikator typu
i wyra\enie muszą być równocześnie typu porządkowego albo wskaznikowego.
W wyniku konwersji między dwoma niezgodnymi typami porządkowymi albo
dwoma niezgodnymi typami wskaznikowymi mo\e nastąpić obcięcie lub rozsze-
rzenie wartości przekształcanego wyra\enia.
Przykłady jawnych konwersji:
Integer('5') + Integer('6') 53 + 54 = 107
var b : Byte;
log : Boolean;
znak : Char;
c : Integer;
&
if not log and Boolean(b) then b := Byte('a');
znak := Char(65);
c := Byte('z') + 5;
Przykład niejawnej konwersji:
var c : Integer;
x, d : Double;
&
x := c + d;
W instrukcji x := c + d; zachodzi niejawna konwersja zmiennej c typu cał-
kowitego na zmienną typu rzeczywistego, poniewa\ d jest typu rzeczywistego.
3.7. Rodzaje operatorów
Aby zdefiniować w programie wyra\enie arytmetyczne, logiczne itp., obli-
czające pewną wartość, nale\y u\yć sensownej kombinacji operandów i operato-
rów. Operandami (argumentami) są zmienne, stałe lub inne wyra\enia. Zbiór
operatorów jest natomiast ściśle określony. Operatory dzieli się m. in. na: aryt-
metyczne, logiczne, relacyjne, teoriomnogościowe i konkatenacji.
Operatory arytmetyczne słu\ą do obliczania wartości wyra\eń liczbo-
wych. Typ zwracanego wyniku zale\y od typu argumentów (tab. 3.6). Nie istnie-
ją operatory potęgowania ani pierwiastkowania. Operacje te mo\na zrealizować
poprzez wielokrotne mno\enie albo za pomocą funkcji standardowych Exp i Ln.
49
Tabela 3.6. Operatory arytmetyczne
Operator Nazwa operacji Typ argumentów Typ wyniku
całkowity całkowity
+ J identyczność
rzeczywisty Extended
całkowity całkowity
-
-
- J zmiana znaku
-
rzeczywisty Extended
całkowity całkowity
+ D dodawanie
rzeczywisty rzeczywisty
całkowity całkowity
- D odejmowanie
rzeczywisty rzeczywisty
całkowity całkowity
* D mno\enie
rzeczywisty rzeczywisty
całkowity Extended
/ D dzielenie
rzeczywisty Extended
div D dzielenie całkowite całkowity całkowity
mod D reszta z dzielenia całkowity całkowity
J operator jednoargumentowy, D operator dwuargumentowy
Dzielenie całkowite argumentu a przez argument b, czyli a div b, zwraca:
" największą liczbę całkowitą mniejszą lub równą ilorazowi a/b, gdy a/b e" 0,
" najmniejszą liczbę całkowitą większą lub równą ilorazowi a/b, gdy a/b < 0,
" błąd, gdy b = 0.
15 div 6 2, -15 div 6 -2, 15 div -6 -2, -15 div -6 2.
Reszta z dzielenia argumentu a przez argument b, czyli a mod b, jest okre-
ślona następująco: a - (a div b)*b. Znak wyniku jest taki, jak znak argumentu a.
15 mod 6 3, -15 mod 6 -3, 15 mod -6 3, -15 mod -6 -3.
Je\eli oba argumenty operacji +, -, *, /, div, mod są ró\nych typów całko-
witych, to przed wykonaniem działania są one przekształcane to typu wspólne-
go, który jest standardowym typem całkowitym, mieszczącym w sobie wszyst-
kie mo\liwe wartości typów obu argumentów. Wynik te\ jest tego typu.
Operatory logiczne słu\ą do wykonywania operacji logicznych na warto-
ściach typów logicznych oraz na wartościach całkowitych (tab. 3.7). Je\eli oby-
dwa argumenty operatorów and, or lub xor są ró\nych typów całkowitych, to
typem wyniku jest typ wspólny obu argumentów.
50
Operacja a shl b (a shr b) przesuwa bity liczby a o b bitów w lewo
(w prawo). Typ wyniku jest taki sam, jak typ operandu a.
Podczas przesuwania bitów w lewo, z prawej strony liczby pojawiają się
dodatkowe bity równe 0. Bity stojące na najbardziej znaczących pozycjach są
tracone. Podobnie jest w przypadku przesuwania bitów w prawo. Nale\y pamię-
tać, \e podczas przesuwania bitów w prawo, bit znaku nie ulega powieleniu.
Operację shl mo\na stosować do szybkiego mno\enia liczby całkowitej
przez potęgę liczby 2, mianowicie: a shl b a 2b, np. 7 shl 5 7 25 = 224.
Operację shr mo\na stosować do szybkiego dzielenia całkowitego przez
potęgę liczby 2, mianowicie: a shr b a div 2b, np. 37 shr 3 37 div 23 = 4.
Tabela 3.7. Operatory logiczne
Operator Nazwa operacji Typ argumentów Typ wyniku
całkowity całkowity
not J negacja
logiczny Boolean
całkowity całkowity
koniunkcja
and D
(logiczne i) logiczny Boolean
alternatywa całkowity całkowity
or D
(logiczne lub)
logiczny Boolean
ró\nica symetryczna,
całkowity całkowity
alternatywa wyklu-
xor D
czająca (exclusive
logiczny Boolean
or), (logiczne albo)
przesunięcie bitowe
shl D całkowity całkowity
w lewo (shift left)
przesunięcie bitowe
shr D całkowity całkowity
w prawo (shift right)
J operator jednoargumentowy, D operator dwuargumentowy
Je\eli operandy operatora logicznego są typów logicznych, to wynik jest
określony zgodnie z tabelą 3.8.
Tabela 3.8. Wyniki operacji logicznych na argumentach typów logicznych
a b not a not b a and b a or b a xor b
False False True True False False False
False True True False False True True
True False False True False True True
True True False False True True False
51
Je\eli operandy operatora logicznego są typów całkowitych, to wynik jest
całkowity. Operacje logiczne wykonywane są na wszystkich parach bitów stoją-
cych na jednakowych pozycjach w obu operandach, zgodnie z tabelą 3.9.
Tabela 3.9. Wyniki operacji logicznych na parze bitów p i q liczb całkowitych
p q not p not q p and q p or q p xor q
0 0 1 1 0 0 0
0 1 1 0 0 1 1
1 0 0 1 0 1 1
1 1 0 0 1 1 0
Przykłady u\ycia operatorów logicznych:
(a > 2) and (a < 9) // !dobrze, a > 2 and a < 9 // !zle
(rok mod 4 = 0) and (rok mod 100 <> 0)
or (rok mod 400 = 0)
not a in [1..5]
Operatory relacyjne słu\ą do konstrukcji wyra\eń porównania. Są to na-
stępujące operatory dwuargumentowe: = (równy), <> (ró\ny, w matematyce
znany jako `"), < (mniejszy), > (większy), <= (mniejszy lub równy, w matematy-
ce znany jako d"), >= (większy lub równy, w matematyce znany jako e"), in (jest
elementem zbioru, w matematyce znany jako ").
Operatory <>, <=, >= nale\y pisać bez spacji między znakami!
Wynikiem wyra\enia porównania jest wartość True, gdy relacja jest praw-
dziwa oraz wartość False, gdy relacja jest fałszywa.
Za pomocą operatorów =, <>, <, >, <=, >= mo\na porównywać dwa argu-
menty o zgodnych typach prostych albo liczbę całkowitą z liczbą rzeczywistą
(np.: 5 = 5 True, -93.52 > 4 False, 0 >= 0.0 True, 7 <> 6.9 True).
Za pomocą operatorów =, <>, <, >, <=, >= mo\na porównywać dwa argu-
menty typu łańcuchowego (ShortString, string) lub znakowego (Char), przy
czym jeden argument mo\e być typu łańcuchowego, a drugi - znakowego. Znak
jest traktowany jak łańcuch o długości 1. Porównanie zachodzi pomiędzy zna-
kami znajdującymi się na tej samej pozycji w obu łańcuchach. Wartość znaku
jest równa jego wartości w rozszerzonej tabeli kodów ASCII. Wynik relacji
porównania dwóch łańcuchów jest równy wynikowi porównania pierwszej
(licząc od początku łańcucha) pary ró\nych znaków, stojących na identycz-
nych pozycjach. Pusty znak jest mniejszy od ka\dego innego znaku.
52
Przykłady porównań łańcuchów:
'informatyk' < 'informatyka' True
'400' = '400.0' False
'b205' >= 'B206' True
Na operandach a i b typu zbiorowego mo\na wykonywać operacje: =, <>,
<=, >=, in. Wyra\enie
a = b ma wartość True, je\eli oba zbiory zawierają te same elementy,
a <> b ma wartość True, je\eli zbiory ró\nią się co najmniej jednym elementem,
a <= b ma wartość True, je\eli zbiór a zawiera się w zbiorze b,
a >= b ma wartość True, je\eli zbiór b zawiera się w zbiorze a,
c in a ma wartość True, je\eli element c jest elementem zbioru a.
Operatory teoriomnogościowe +, - i * (tab. 3.10) słu\ą do wykonywania
operacji na argumentach typu zbiorowego o zgodnych typach bazowych.
Tabela 3.10. Operatory teoriomnogościowe
Operator Nazwa operacji Definicja operacji
c jest elementem zbioru A + B,
suma zbiorów
+ je\eli jest elementem zbioru A
(A *" B)
lub jest elementem zbioru B
c jest elementem zbioru A - B,
ró\nica zbiorów
- je\eli jest elementem zbioru A
( A \ B )
i nie jest elementem zbioru B
c jest elementem zbioru A * B,
iloczyn zbiorów
* je\eli jest elementem zbioru A
( A )" B )
i jest elementem zbioru B
c - element typu porządkowego
Przykłady operacji na zbiorach:
[10, 12, 14, 8] + [9, 8, 10, 14] [8, 9, 10, 12, 14]
[10, 12, 14, 8] - [9, 8, 10, 14] [12]
[10, 12, 14, 8] * [9, 8, 10, 14] [8, 10, 14]
[20..99] - [10..40] [41..99]
['a', 'b'] + ['b', 'c', 'd'] ['a', 'b', 'c', 'd']
Operator konkatenacji + u\yty po ka\dym operandzie (z wyjątkiem ostat-
niego) słu\y do połączenia dwóch lub więcej łańcuchów w jeden dłu\szy łań-
cuch. Argumenty muszą być typu łańcuchowego lub znakowego. Je\eli wszyst-
53
kie operandy są łańcuchami krótkimi (ShortString), wówczas łańcuch wyniko-
wy równie\ jest łańcuchem krótkim. Je\eli łańcuch powstały z połączenia łańcu-
chów krótkich zawiera więcej ni\ 255 znaków, to zostanie obcięty po 255 znaku.
Je\eli co najmniej jeden łańcuch jest łańcuchem długim (string), to wynik te\
jest tego typu. Przykład: Lazarus + + FPC Lazarus FPC .
3.8. Priorytet operatorów
Je\eli w wyra\eniu występuje większa liczba operatorów i operandów,
to w celu określenia jednoznacznej kolejności wykonywania działań, twórcy ję-
zyka Pascal wprowadzili pewien priorytet operatorów, czyli hierarchię ich
wa\ności (tab. 3.11). Aby wymusić inną kolejność operacji, ni\ wynika ona z
priorytetu operatorów, stosuje się nawiasy okrągłe, podobnie jak w matematyce.
Tabela 3.11. Priorytet operatorów (od najwy\szego do najni\szego)
Operatory Kategoria operatorów
+, -, @, not jednoargumentowe
*, /, div, mod, and, shl, shr, as multiplikatywne
+, -, or, xor addytywne
=, <>, <, >, <=, >=, in, is relacyjne
Obowiązują następujące zasady wiązania (przez kompilator) operandów do
operatorów:
" argument występujący między dwoma operatorami o ró\nych prioryte-
tach jest wiązany z operatorem o wy\szym priorytecie,
" argument znajdujący się między dwoma operatorami o tym samym prio-
rytecie jest wiązany z operatorem stojącym z lewej strony.
Operacje z równymi priorytetami są wykonywane kolejno od lewej strony
do prawej, chocia\ kompilator mo\e czasami, bez ostrze\enia, przestawić argu-
menty w celu wygenerowania optymalnego kodu wynikowego.
3.9. Struktura programu
Program napisany w języku Free Pascal składa się z nagłówka, deklaracji
u\ywanych w programie modułów, bloku i znaku kropki:
" w nagłówku podaje się nazwę programu,
" następnie deklaruje się moduły standardowe i u\ytkownika,
" blok składa się z opisu danych i części wykonawczej,
" kropka kończy tekst programu.
54
program nazwa_programu;
część deklaracyjna
część opisowa
begin
część wykonywalna
end.
Ka\da instrukcja programu jest zakończona średnikiem. Zazwyczaj układ
tekstu w linii czy te\ rozło\enie go na kilka linii nie ma znaczenia.
Część opisowa zawiera deklaracje typów, stałych, zmiennych, definicje
i deklaracje procedur i funkcji. Część wykonywalna instrukcje do wykonania.
Przykład 1. Elementarny program wypisujący tekst na ekranie.
program wyklad02;
const max = 10;
var liczba : Byte;
BEGIN
writeln('Początek');
liczba := 5 + max;
writeln('liczba =', liczba);
writeln('Koniec');
readln;
END.
Przykład 2. Program pokazuje u\ycie typów prostych.
program TypyProstePorz;
var
ok : Boolean;
x : 1..4;
dzien : (pon, wt, sr);
BEGIN
writeln(SizeOf(int64));
writeln(low(int64));
dzien := wt;
writeln(ord(dzien));
inc(dzien);
writeln(ord(dzien));
writeln(dzien=sr);
x := 4;
55
writeln(x);
dec(x);
writeln(x);
dec(x);
writeln(x);
writeln(high(x));
writeln(low(x));
readln;
END.
Przykład 3. Program ilustruje sposób u\ycia zmiennych typu zbiorowego.
program Typ_zbiorowy;
type zbior = set of Byte;
var x, z : zbior;
y : set of 0..9;
i : Integer;
BEGIN
x := [1,9,100,10]; {utworzenie zbioru}
i := 9;
y := [1,2,3,9,i+2]; {liczba 11 zostanie zignorowana}
z := [2,4,6,8];
writeln(i,' '); // u\ycie typu zbiorowego
if not i in [1..5] then writeln ('tak')
else writeln ('nie');
i := i+1;
writeln(i); // i 10
if i in y then writeln ('tak') else writeln ('nie');
readln;
END.
56
4. Instrukcje
Operacje wykonywane na danych są w programie opisywane za pomocą
instrukcji. Formalnie instrukcje dzieli się na proste i strukturalne.
Do instrukcji prostych zalicza się:
" instrukcję przypisania,
" instrukcję skoku,
" instrukcję pustą,
" instrukcję wywołania procedury.
Do instrukcji strukturalnych nale\ą:
" instrukcja zło\ona,
" instrukcja warunkowa,
" instrukcja iteracyjna,
" instrukcja wyboru,
" instrukcja wią\ąca.
4.1. Instrukcje proste
Do przypisania zmiennej wartości innej zmiennej lub wyra\enia słu\y
instrukcja przypisania, wykorzystująca symbol := (dwukropek i znak równo-
ści). Wartość wyra\enia występującego po prawej stronie symbolu przypisania
musi być takiego samego typu, jak zmienna, do której następuje przypisanie.
Przykłady u\ycia instrukcji przypisania:
liczba := 10;
przyblizone_pi := 3.14;
s := 'Komentarz';
A[1,1] := 22;
liczba := liczba + 6; // liczba 16
znak := 'h';
wiersz[i] := 'H';
czy_koniec := True;
warunek := b*b-4*a*c > 0;
Wiek := 21;
P := 'Pierwsza';
s := ''; {łańcuch pusty, brak spacji między ''}
s := ' '; {jedna spacja między apostrofami}
s := 'Witaj'; {s[1] 'W', s[1] := 'w'; s 'witaj'}
57
Instrukcja skoku ma postać: goto etykieta; Zmienną etykieta nale\y zade-
klarować na początku programu jako: label etykieta; a następnie mo\na jej u\yć
poprzedzając wybraną instrukcję nazwą etykiety i znakiem dwukropka, miano-
wicie: etykieta: instrukcja; Instrukcja goto etykieta; spowoduje przekazanie ste-
rowania do przodu albo do tyłu w celu pominięcia lub powtórzenia określonego
zestawu instrukcji i kontynuowania wykonywania programu od miejsca ozna-
czonego napisem etykieta: instrukcja; U\ywanie instrukcji skoku nie jest zaleca-
ne, gdy\ zmniejsza przejrzystość kodu zródłowego programu oraz utrudnia op-
tymalizację kodu przez kompilator. Instrukcja skoku mo\e być łatwo zastąpiona
bardziej bezpieczną instrukcją iteracyjną.
Instrukcja pusta jest stosowana w sytuacji, gdy musimy u\yć jakiejś in-
strukcji, ale w rzeczywistości nie chcemy nic obliczać. W Pascalu symbol śred-
nika oznacza czasem instrukcję pustą - instrukcja ta nie ma swojego symbolu.
Do wywołania procedury słu\y instrukcja:
nazwa_procedury; albo nazwa_procedury(lista_parametrów_aktualnych);
Parametry aktualne, czyli argumenty procedury, oddziela się przecinkami.
Przykłady wykorzystania popularnych procedur standardowych:
Writeln; // wypisuje pusty wiersz (pustą linię)
Write; // wypisuje pusty znak
Write('a'); // wypisuje znak a
Write(a); // wypisuje wartość zmiennej a
Writeln(x,2,y); { wypisuje wartość zmiennej x,liczbę 2,
wartość zmiennej y i przenosi kursor do następnej linii }
Read; // czyta pusty znak z klawiatury
Readln; // czyta Enter - przechodzi do następnej linii
Read(a); // czyta z klawiatury wartość zmiennej a
Readln(a); { czyta z klawiatury wartość zmiennej a i prze-
chodzi do następnej linii }
4.2. Instrukcja zło\ona
Instrukcja zło\ona (blok instrukcji) jest ciągiem instrukcji poprzedzo-
nych słowem kluczowym begin i zakończonych słowem kluczowym end:
begin
instrukcja_1;
instrukcja_2;
&
instrukcja_n
end
58
Przykład instrukcji zło\onej:
begin
x := 3;
y := x+b;
z := x+y+z;
writeln(z);
end
4.3. Instrukcja warunkowa
Instrukcja warunkowa uzale\nia wykonanie pewnej instrukcji od spełnienia
podanego warunku. Występuje w dwóch wersjach: pełnej (tzw. if - then - else)
- -
- -
- -
oraz skróconej (tzw. if -
- then).
-
-
Wersja pełna instrukcji warunkowej ma postać:
if warunek then instrukcja_1 // tu nie daje się średnika
else instrukcja_2;
Oznacza to, \e je\eli spełniony jest warunek, wówczas zostanie wykonana
instrukcja_1, w przeciwnym wypadku zostanie wykonana instrukcja_2.
Wersja skrócona instrukcji warunkowej ma postać:
if warunek then instrukcja;
Oznacza to, \e je\eli spełniony jest warunek, wówczas zostanie wykonana
instrukcja, w przeciwnym wypadku instrukcja zostanie pominięta.
TAK TAK
NIE NIE
warunek warunek
instrukcja_1 instrukcja_2 instrukcja
a) b)
Rys. 4.1. Graficzna postać instrukcji warunkowej:
a) w wersji pełnej, b) w wersji skróconej
59
Wyra\enie warunek musi być typu logicznego (Boolean), tzn. musi przyj-
mować wartość True albo False.
Przykłady u\ycia instrukcji warunkowej:
if a > 5 then b := a-5;
if (a > 0) and (a <= 10) then x := 5 else x := a+5;
if (dzielnik <> 0) then wynik := dzielna/dzielnik
else writeln('Nie mo\na dzielić przez zero!');
if (dzielnik <> 0) then
begin
wynik := dzielna/dzielnik;
writeln('dzielenie wykonano');
end
else writeln('Nie mo\na dzielić przez zero!');
if zn in ['0'..'9'] then writeln('zn jest cyfrą');
if not x in ['0'..'9'] then write('x nie jest cyfrą');
if a = b then
begin
writeln('a i b są takie same');
obwod := 4*a;
end;
Przykład 1. Program sprawdza, czy liczba jest większa od 100.
program prog01;
var liczba : Integer;
BEGIN
write('Proszę podać liczbę całkowitą: ');
readln(liczba);
if liczba > 100 then
writeln('Liczba jest większa od 100.')
else
writeln('Liczba jest mniejsza lub równa 100.');
readln;
END.
Przykład 2. Program szuka maksimum wśród 3 liczb rzeczywistych.
program instr_if_1;
60
var a, b, c, max : Single;
BEGIN
write('Podaj a '); readln(a);
write('Podaj b '); readln(b);
write('Podaj c '); readln(c);
if a >= b then if a >= c then max := a
else max := c
else if b >= c then max := b
else max := c;
write('max(a,b,c) = ', max:4:1);
readln;
END.
Przykład 3. Program zamienia ocenę numeryczną na ocenę tekstową.
Program Oceny;
var ocena : Byte;
BEGIN
write('Podaj ocenę ');
readln(ocena);
if ocena=6 then writeln('celujący')
else if ocena=5 then writeln('bardzo dobry')
else if ocena=4 then writeln('dobry')
else if ocena=3 then writeln('dostateczny')
else if ocena=2 then writeln('mierny')
else if ocena=1 then writeln('niedostateczny')
else writeln('Nieprawidłowa ocena!');
readln;
END.
4.4. Instrukcja wyboru
W programowaniu często mamy do czynienia z sytuacją, gdy wykonanie
ró\nych operacji jest uzale\nione od wartości pewnej zmiennej. Stosuje się wte-
dy instrukcję wyboru (tzw. case of). Składnia instrukcji wyboru ma postać:
case wyra\enie of
sekwencja_instrukcji_wyboru
end
albo
case wyra\enie of
sekwencja_instrukcji_wyboru
else instrukcja
end
61
Występujące w tej instrukcji wyra\enie nazywane jest selektorem. Jego
wartość musi być typu porządkowego. Sekwencja instrukcji wyboru zbudowana
jest z ciągu instrukcji. Ka\da z nich poprzedzona jest jedną lub kilkoma stałymi
wyboru, oddzielonymi od siebie przecinkiem - po nich występuje dwukropek.
case wyra\enie of
opcja_1 : ciąg instrukcji_1
opcja_2 : ciąg instrukcji_2
opcja_N : ciąg instrukcji_N
else
ciąg instrukcji
end
Rys. 4.2. Graficzna postać instrukcji wyboru
Przykład 1. Program wczytuje znak i wypisuje jego rodzaj.
program instr_case1;
var znak : Char;
BEGIN
write('Proszę wpisać dowolny znak: ');
readln(znak);
case znak of
'0' .. '9' : writeln('Wpisano cyfrę!');
'A' .. 'Z' : writeln('Wpisano wielką literę!');
'a' .. 'z' : writeln('Wpisano małą literę!');
'=' : writeln('Wpisano znak równości!');
'+','-' : writeln('Wpisano plus lub minus!');
else
writeln('Wpisano inny znak!')
end;
readln;
END.
Przykład 2. Program zamienia ocenę numeryczną na ocenę tekstową.
program instr_case2;
62
var ocena : Byte;
BEGIN
write('Podaj ocenę');
readln(ocena);
case ocena of
6: writeln('celujący');
5: writeln('bardzo dobry');
4: writeln('dobry');
3: writeln('dostateczny');
2: writeln('mierny');
1: writeln('niedostateczny')
end;
readln;
END.
4.5. Instrukcje iteracyjne
Instrukcja iteracyjna (pętla) stosowana jest w celu wielokrotnego wyko-
nania jednej lub więcej instrukcji. Są dostępne dwa rodzaje pętli:
" pętle wykonujące się określoną liczbę razy,
" pętle wykonujące się do momentu spełnienia pewnego warunku.
Pętla for (instrukcja dla ) wykonuje się określoną liczbę razy i występu-
je w dwóch wersjach:
for to do (pętla licząca w górę, a\ do ) oraz
for downto do (pętla licząca w dół, a\ do ).
Pełna składnia obu typów pętli for ma postać:
for licznik_pętli := początek to koniec do zawartość_pętli;
for licznik_pętli := koniec downto początek do zawartość_pętli;
Najwa\niejszy cechy pętli for to:
" zmienna sterująca licznik_pętli musi być typu porządkowego,
" zmienną licznik_pętli nale\y wcześniej zadeklarować,
" wartości początek i koniec muszą być tego samego typu, co licznik_pętli,
" literały początek i koniec mogą być stałymi lub wyra\eniami,
" licznik_pętli zwiększa swą wartość o 1 po ka\dej iteracji pętli to,
" licznik_pętli zmniejsza swą wartość o 1 po ka\dej iteracji pętli downto,
" je\eli koniec = początek, to zawartość_pętli wykona się tylko jeden raz,
63
" je\eli koniec > początek, to zawartość_pętli wykona się
koniec - początek + 1 razy,
" je\eli koniec < początek, to zawartość_pętli nie wykona się ani razu,
" je\eli zawartość_pętli zbudowana jest z kilku instrukcji, wtedy nale\y
zgrupować je za pomocą begin ... end.
W Lazarus FPC, w instrukcji zawartość_pętli, nie mo\e wystąpić instrukcja
przypisania do zmiennej licznik_pętli. Na przykład nieprawidłowa jest pętla:
for m := 1 to 10 do m := m+2;
Przykład 1. Program oblicza silnię.
program prog6;
var liczba : Integer;
silnia, i : Longint;
BEGIN
write('Podaj liczbę naturalną:');
readln(liczba);
silnia := 1;
for i := 1 to liczba do silnia := silnia*i;
writeln('Wartość silni dla ',liczba:2,' wynosi',silnia);
readln;
END.
Pętle, których działanie zale\y od warunku to:
while - do ( dopóki - wykonuj ) oraz repeat - until ( powtarzaj, a\ do ).
Pętla while - do słu\y do opisu iteracji ze sprawdzeniem warunku na po-
czątku i ma postać:
while warunek do instrukcja;
Cechy pętli while - do:
" warunek jest najczęściej wyra\eniem porównania,
" warunek musi dawać wartość logiczną True lub False,
" je\eli przed rozpoczęciem wykonywania pętli warunek ma wartość Fal-
se, wtedy instrukcja nie wykona się ani razu,
" instrukcja mo\e być dowolną instrukcją prostą lub strukturalną,
" je\eli pętla ma wykonywać kilka instrukcji, to nale\y je zgrupować za
pomocą begin & end,
" instrukcja jest wykonywana tak długo, jak długo wartość warunku jest
równa True,
64
" przy korzystaniu z tej pętli nale\y zwracać uwagę na to, aby miała pra-
widłowo określony warunek, tzn. \eby prędzej czy pózniej przyjął on
wartość False, w przeciwnym razie pętla nigdy się nie przerwie (chyba,
\e zastosujemy procedurę Break).
TAK
NIE
warunek
instrukcja
Rys. 4.3. Graficzna postać instrukcji iteracyjnej while - do
Przykład 2. Program wypisuje liczby: 0, 1, 2, & , 9.
program prog7;
var x : Integer;
BEGIN
x := 0;
while x < 10 do
begin
writeln(x);
x := x + 1;
end;
readln
END.
Przykłady 3. Program oblicza, ile co najmniej trzeba dodać kolejnych liczb na-
turalnych, aby ich suma była większa od podanej liczby.
program prog8;
var n, licznik, suma : Smallint;
BEGIN
write('Podaj liczbę całkowitą ');
readln(n);
licznik := 0;
suma := 0;
while suma <= n do
begin
licznik := licznik + 1;
65
suma := suma + licznik;
end;
writeln('Minimalna ilość liczb to ', licznik);
readln;
END.
Przykład 4. Program oblicza NWD dwóch liczb metodą z odejmowaniem.
program prog1_NWD;
var n, m : Smallint;
BEGIN
write('Podaj liczbę całkowitą m: ');
readln(m);
write('Podaj liczbę całkowitą n: ');
readln(n);
while not (m = n) do
if m > n then m := m-n
else n := n-m;
writeln(' NWD wynosi: ', n);
readln;
END.
START
wczytaj m, n
k ! m
k ! k - 1
m mod k `" 0
NIE
TAK
lub
n mod k `" 0
wypisz k
STOP
Rys. 4.4. Schemat blokowy algorytmu wyznaczania NWD metodą kolejnych dzieleń
66
Przykład 5. Program oblicza największy wspólny dzielnik według rys. 4.4.
program prog92_NWD;
var m, n, k : Integer;
BEGIN
writeln('Podaj m i n: ');
readln(m, n);
k := m;
while (n mod k <> 0) or (m mod k <> 0) do k := k-1;
writeln(k);
readln;
END.
Pętla repeat - until słu\y do opisu iteracji ze sprawdzeniem warunku na
końcu i ma postać:
repeat
instrukcja_1;
instrukcja_2;
&
instrukcja_n
until warunek;
Cechy pętli repeat - until:
" instrukcje wewnątrz pętli są wykonywane co najmniej 1 raz,
" po ka\dym wykonaniu instrukcji_n następuje sprawdzenie warunku,
" instrukcje wewnątrz pętli wykonywane są tak długo, jak długo warunek
ma wartość False,
" zakończenie pętli następuje wtedy, gdy warunek ma wartość True,
" dla zapewnienia wyjścia z pętli trzeba zadbać o to, aby warunek chocia\
raz osiągnął wartość True,
" wcześniejsze zakończenie pętli jest mo\liwe przy u\yciu Break.
instrukcja
NIE
TAK
warunek
Rys. 4.5. Schemat blokowy instrukcji iteracyjnej repeat - until
67
k
Przykład 6. Program oblicza n .
program prog10;
var
i, k : Integer;
n, x : Double;
BEGIN
write('Podaj podstawę n: ');
readln(n);
write('Podaj wykładnik k: ');
readln(k);
x := 1;
i := 1;
repeat
x := x*n;
i := i+1;
until i > k;
writeln('k-ta potęga liczby n wynosi: ', x);
readln;
END.
Przykład 7. Program czyta znaki z klawiatury i podaje ich kod ASCII.
Program prog12a;
var zn : Char;
BEGIN
repeat
readln(zn);
writeln(zn, ' ', byte(zn));
until zn = #13;
writeln('Był Enter!');
readln;
END.
Przykład 8. Program czyta znaki z klawiatury i przerywa działanie, je\eli wczy-
ta liczbę.
Program prog12b;
var zn : Char;
BEGIN
repeat
readln(zn);
writeln(zn, ' ', byte(zn));
until (zn >= '0') and (zn <= '9');
68
writeln('Była liczba!');
readln;
END.
Przykład 9. Ilustracja nieprawidłowego u\ycia pętli.
Program prog13;
var zn : Char;
k, x : Byte;
BEGIN
k := 5;
repeat
writeln(k);
k := k-1;
until k > 5; //warunek nigdy nie zostanie spełniony
k := 5;
x := 0;
repeat
writeln(k);
x := x+k;
until k < 0; //warunek nigdy nie zostanie spełniony
readln;
END.
Tabela 4.1. Zastosowanie trzech rodzajów pętli do tego samego zadania
Pętla for- -do Pętla repeat- -do
-to- -until Pętla while-
- - - -
- - - -
program p16; program p17; program p18;
const n=7; const n=7; const n=7;
var s,licznik:longint; var s, licznik: Longint; var s,licznik:longint;
begin begin begin
s := 1; s := 1; s:=1;
for licznik:=2 to n do licznik:=1; licznik:=2;
s:=s*licznik; repeat while licznik<=n do
write(' silnia= ', s); s := s*licznik; begin
end. licznik := licznik+1; s := s*licznik;
until licznik>n; licznik := licznik+1;
write(' silnia= ', s); end;
end. write(' silnia= ', s);
end.
69
Przy wyborze pętli warto kierować się poni\szymi wskazówkami:
" je\eli instrukcja ma być wykonana określoną liczbę razy i zmienna ste-
rująca pętli ma się zwiększać, nale\y wybrać pętlę for - - do,
- to -
- -
- -
" je\eli instrukcja ma być wykonana określoną liczbę razy i zmienna ste-
rująca pętli ma się zmniejszać, nale\y wybrać pętlę for - - do,
- downto -
- -
- -
" je\eli warunkiem przerwania pętli jest wyra\enie logiczne i instrukcja
ma być wykonana przynajmniej raz, nale\y wybrać repeat -
- until.
-
-
" je\eli warunkiem przerwania pętli jest wyra\enie logiczne i nic nie wia-
domo o pierwszym wykonaniu instrukcji, nale\y wybrać while -
- do.
-
-
" je\eli nie wiadomo, którą pętlę wybrać, wtedy skuteczna będzie while.
4.6. Procedury Break i Continue
Czasem mo\e zajść potrzeba natychmiastowego przerwania bie\ącej iteracji
pętli bez wykonywania instrukcji, które pozostały do końca tej iteracji i zakoń-
czenia wykonywania pętli w ogóle. W inny przypadku mo\e zajść potrzeba na-
tychmiastowego przerwania bie\ącej iteracji i rozpoczęcia następnej. Do tych
obu celów słu\ą - odpowiednio - procedury standardowe Break i Continue.
Są one zdefiniowane w module System.
Przykładowe u\ycie procedury Continue:
while warunek_1 do
begin
Instrukcja_1;
if warunek_2 then Continue;
Instrukcja_2;
Instrukcja_3;
end;
Je\eli warunek_2 będzie mieć wartość logiczną True, wtedy wszystkie in-
strukcje w danej iteracji, występujące po wywołaniu procedury Continue, zosta-
ną pominięte i rozpocznie się kolejna iteracja pętli.
Przykładowe u\ycie procedury Break:
while warunek_1 do
begin
Instrukcja_1;
if warunek_2 then Break;
Instrukcja_2;
70
Instrukcja_3;
end;
Instrukcja_4;
Je\eli warunek_2 będzie mieć wartość logiczną True, wtedy nastąpi na-
tychmiastowe przerwanie procesu iteracji. Następną wykonaną będzie pierwsza
instrukcja poza pętlą, czyli Instrukcja_4.
Procedury Break i Continue mogą być wywoływane tylko wewnątrz pętli.
Mogą być u\yte wewnątrz ka\dej instrukcji zło\onej występującej w pętlach for,
repeat oraz while. U\ycie tych procedur poza pętlą spowoduje błąd kompilacji.
Tabela 4.2. Przykłady u\ycia procedur Continue oraz Break
U\ycie procedury Continue U\ycie procedury Break
program progr20; program progr21;
const n = 20; const n = 20;
var licznik : Byte; var licznik : Byte;
BEGIN BEGIN
//s:=1; //s:=1;
licznik := 0; licznik := 1;
while licznik < n do while licznik < n do
begin begin
licznik := licznik+1; licznik := licznik+1;
if licznik mod 3 = 0 if licznik mod 5 = 0
then continue; then break;
writeln(licznik); writeln(licznik);
end; end;
readln; readln;
END. END.
4.7. Pętle i tablice
Pętle bardzo często stosuje się do danych typu tablicowego, dlatego poka-
\emy kilka przykładowych programów, w których są u\yte pętle na tablicach.
Przykład 1. Program wpisuje do tablicy znaki o kodach ASCII.
Program tablice_znakow;
const max = 58; // 1-wymiarowa tablica znaków
type Tznak = array [1..max] of Char;
var znak : Tznak;
i : Byte;
71
BEGIN
for i:=1 to max do znak[i] := char(i+byte('A')-1);
for i:=1 to max do write(znak[i]);
readln;
END.
Przykład 2. Program, na ró\ne sposoby, wpisuje dane liczbowe do tablic i wy-
pisuje dane z tablic na ekranie.
Program tablice_liczbowe;
const N = 5; {tablica 1-wymiarowa}
type TRtab = array [0..N] of Single;
TCtab = array [0..N] of Smallint; // zmień typ
var C : TCtab;
R : TRtab;
Cp : TCtab = (0,-1,4,20,-5,6);
i : Byte;
BEGIN
for i:=0 to N do C[i]:=i;//wypełnianie automatyczne
for i:=0 to N do
begin
write('R[',i,']=');
readln(R[i]); //czytanie z klawiatury
end;
for i:=0 to N do writeln(C[i]);//wypisanie w kolumnie
for i:=0 to N do write(Cp[i]:4);//wypisanie w wierszu
for i:=0 to N do writeln(R[i]:5:3);
readln; // ę! wypisanie+formatowanie
END.
Przykład 3. Program wpisuje dane liczbowe do tablicy dwuwymiarowej (macie-
rzy) i wypisuje dane z tablicy na ekranie.
Program macierze;
uses SysUtils;
const N = 4; {tablica 2-wymiarowa}
type TRMatrix = array [1..N, 1..N ] of Byte;
var M : TRMatrix;
i, j : Byte;
BEGIN
for i:=1 to N do
for j:=1 to N do M[i,j] := i+j-1;
for i:=1 to N do
72
begin
for j:=1 to N do write(M[i,j]:4);
writeln;
end;
readln;
END.
Do odwoływania się do poszczególnych pól rekordu lub pól i metod obiek-
tu słu\ą desygnatory pól i metod, składające się z identyfikatora odpowiedniego
pola lub metody i nazwy zmiennej rekordowej lub obiektowej. Odwołanie takie
zwykle wydłu\a tekst programu. Zastosowanie instrukcji wią\ącej pozwala na
wygodniejsze odwoływanie się do tych pól i poprawia przejrzystość programu.
Postać składni instrukcji wią\ącej jest następująca:
with lista_zmiennych do instrukcja;
lista_zmiennych zawiera oddzielone przecinkami identyfikatory zmiennych re-
kordowych lub klasowych, a instrukcja mo\e być dowolną instrukcją prostą lub
strukturalną. Je\eli po słowie kluczowym do występuje kilka instrukcji, to nale-
\y je zgrupować za pomocą begin & end;
Instrukcja wią\ąca wią\e pola typu rekordowego ze zmienną, do której na-
le\ą. Instrukcja wią\ąca mo\e być zagnie\d\ana.
Przykład 4. U\ycie instrukcji wią\ącej do zmiennych typu rekordowego.
program Dane_rekordy;
type Tdane = record
Imie : string[20];
Wiek : Byte;
Praca : array [1..10] of string;
end;
var prac1, prac2 : TDane;
BEGIN
prac1.imie := 'Leon';
prac1.wiek := 33;
prac1.praca[1] := 'Zaklad energetyczny';
prac1.praca[2] := 'Firma A';
prac2.imie := 'Daria';
prac2.wiek := 27;
prac2.praca[1] := 'Sąd rejonowy';
// wypisz dane o pracownikach
Write(prac1.imie, prac1.wiek);
73
writeln(prac1.praca[1], prac1.praca[2]);
// wypisz dane u\ywając instrukcji with
with prac1, prac2 do
Writeln(imie, wiek, praca[1], praca[2]);
readln;
END.
4.8. Zadania
Zad. 4.1. Napisać program porządkowania trzech liczb.
Zad. 4.2. Napisać program zamiany liczby dziesiętnej na szesnastkową.
Zad. 4.3. Napisać program zamiany liczby ósemkowej na dziesiętną.
Zad. 4.4. Napisać program wyznaczający najmniejszą wspólną wielokrotność
(NWW) dwóch liczb naturalnych, zgodnie ze wzorem (m n)/NWD(m,n).
Zad. 4.5. Napisać program obliczania pierwiastka kwadratowego z liczby nieu-
jemnej a ( x = a ), z zadaną dokładnością eps, iteracyjną metodą Newtona
ł ł
1 a
ł ł
xi+1 = - xi ł . Wartość a , czyli xi+1 , ma dokładność eps, je\eli
ł
2 xi łł
ł
a - xi2 < eps . xi oznacza kolejne przybli\enie a .
100
i + x
Zad. 4.6. Napisać program obliczający sumę szeregu s = (x dowolne).
"
i2 +1
i=1
Zad. 4.7. Napisać program obliczania wyra\enia danego wzorem
Ą 2 " 2 " 4 " 4 " 6 " 6L
=
2 1"3"3" 5"5" 7L
Zad. 4.8. Napisać program, który wyznaczy wartość maksymalną i wartość mak-
symalną modułu elementów tablicy A. Tablica ma N elementów typu rze-
czywistego tj. A[i] " R dla i=1, 2, & , N.
Zad. 4.9. Dla danej tablicy dwuwymiarowej A[i, j], i, j = 1..N wyznaczyć tabli-
cę transponowaną (kolumny zamienione na wiersze). Rozwa\yć dwa przy-
padki: tablica inicjalizowana jest podczas deklaracji w kodzie zródłowym
albo tablica jest wczytywana z klawiatury.
74
5. Procedury i funkcje
Podprogram to wyodrębniona część programu, stanowiąca całość, posia-
dająca nazwę i ustalony sposób wymiany informacji z pozostałymi częściami
programu. Ze względu na sposób wymiany informacji (lub jego brak) z pozosta-
łą częścią programu, podprogramy dzielimy na procedury i funkcje.
Funkcje i procedury:
" umieszcza się w części deklaracyjnej programu lub w odrębnej jednost-
ce kompilacyjnej,
" funkcja zazwyczaj operuje na pewnych argumentach (ale nie musi)
i zwraca pewną, obliczoną wartość (lub zestaw wartości),
" funkcja mo\e modyfikować argumenty, chocia\ zazwyczaj tylko wyko-
rzystuje do obliczeń przekazane jej wartości,
" procedura jest podobna do funkcji, z tym, \e nie mo\e zwracać wartości
w sposób zarezerwowany dla funkcji,
" procedura mo\e posiadać argumenty lub nie, mo\e zwracać wartości
do świata zewnętrznego , ale tylko w określony sposób.
5.1. Składnia procedury
Procedura to podprogram wykonujący jedną lub więcej czynności i zwra-
cający od jednego do kilku wyników lub niezwracający \adnego wyniku. Nazwa
procedury jest u\ywana w charakterze instrukcji w programie.
Składnia definicji procedury jest następująca:
procedure nazwa_procedury (lista_parametrów_formalnych);
część_deklaracyjna
begin
ciąg_instrukcji
end;
Część deklaracyjna oraz lista parametrów formalnych są opcjonalne, tzn.
mo\na ich nie definiować, je\eli nie są potrzebne. Część deklaracyjna procedury
mo\e zawierać te same elementy, co część opisowa programu tzn.: definicje ty-
pów, stałych, zmiennych, ale nie klas. Parametry formalne oddziela się średni-
kami. Lista parametrów formalnych określa równie\, w jaki sposób parametry
formalne zostaną zastąpione parametrami aktualnymi w trakcie wywołania pro-
cedury. Parametry aktualne w wywołaniu procedury oddziela się przecinkami.
75
Parametry formalne pojawiają się w nagłówku deklaracji funkcji bądz
procedury i są u\ywane w treści funkcji lub procedury jak zwykłe zmienne.
Liczba parametrów formalnych nie jest ograniczona i ich kolejność występowa-
nia jest dowolna. Parametr formalny jest obowiązkowo zmienną.
Parametry aktualne są parametrami występującymi w bloku, z którego
wywoływana jest funkcja lub procedura i u\ywane są podczas wywołania pro-
cedury lub funkcji. Liczba parametrów aktualnych musi być taka sama, jak licz-
ba parametrów formalnych w nagłówku i musi zachodzić zgodność typów.
Parametrem aktualnym mo\e być wyra\enie, zmienna albo stała.
nagłówek
procedury
część
deklaracyjna
begin
blok
ciąg instrukcji
procedury
end
Rys. 5.1. Schemat blokowy składni procedury
Wa\ne uwagi dotyczące wywoływania (u\ywania) procedur w programie:
" wywołanie procedury polega na u\yciu w programie jej nazwy z listą
parametrów aktualnych (je\eli istnieje) w nawiasach okrągłych:
nazwa_procedury (lista_parametrów_aktualnych);
przykład: wypisz (a, b, c);
" parametry aktualne oddziela się przecinkami,
" typy parametrów aktualnych muszą być zgodne z typami odpowiadają-
cych im parametrów formalnych,
" lista parametrów formalnych pozwala w sposób jawny przekazywać da-
ne do procedury lub funkcji.
76
5.2. Przekazywanie parametrów do procedur i funkcji
Zastępowanie parametrów formalnych aktualnymi jest nazywane przekazy-
waniem parametrów do procedury lub funkcji. Wyró\nia się trzy główne sposo-
by przekazywania parametrów: przez wartości, przez zmienne i przez stałe.
Parametry przekazywane przez wartości deklaruje się w nagłówku pro-
cedury (lub funkcji) następująco: procedure nazwa_procedury
(Identyfikator_1 : typ_1; Identyfikator_2 : typ_2; ...; Identyfikator_n : typ_n);
Przekazywanie przez wartość to przekazywanie w jedną stronę, tzn. praca
na kopii parametru. Parametry formalne przekazywane przez wartości (Identyfi-
kator_1, Identyfikator_2 itd.) są, w obrębie procedury (lub funkcji), traktowane
jak zmienne lokalne, którym nadaje się wartości początkowe równe wyliczo-
nym, w chwili wywołania, wartościom wyra\eń stanowiących parametry aktual-
ne.
Operacje wykonywane w treści funkcji lub procedury są wykonywane
zatem na kopii umieszczonej na stosie i w związku z tym nie powodują
zmian wartości odpowiadających im parametrów aktualnych.
Przykłady deklaracji procedur z parametrami przekazywanymi przez wartości:
procedure dodaj (a, b : Integer; c : Single);
procedure generator (x : Real);
procedure Drukuj(linia1, linia2 : string); // ShortString jest niedozwolony
procedure WypiszDate; // procedura bez parametrów
type Matrix = array [1..5, 1..4] of Double;
procedure Zapisz(M : Matrix);
Parametry przekazywane przez zmienne deklaruje się w nagłówku
procedury (lub funkcji) następująco: procedure nazwa_procedury (var Identy-
fikator_1 : typ_1; var Identyfikator_2 : typ_2; ...; var Identyfikator_n : typ_n);
Oprócz standardowych typów mo\na u\yć tak\e słów kluczowych string
i file.
Argumenty aktualne, odpowiadające parametrom formalnym przeka-
zywanym przez zmienne, muszą być zmiennymi.
U\ycie parametru przekazywanego przez zmienną oznacza pracę na
oryginale parametru aktualnego. Operacje wykonywane w treści funkcji lub
procedury na parametrach przekazywanych przez zmienne są wykonywane
(w momencie wywołania procedury lub funkcji) bezpośrednio na przekazywa-
nych do procedury lub funkcji parametrach aktualnych. Je\eli w treści proce-
dury lub funkcji zostaną u\yte instrukcje zmieniające wartość parametru
77
formalnego, to identyczne zmiany dokonają się równie\ w parametrze ak-
tualnym po wywołaniu procedury lub funkcji.
Przykłady deklaracji procedur z parametrami przekazywanymi przez zmienną:
procedure zapisz(var x : Byte);
procedure dodaj(var a : Integer; var b : Single);
procedure dodaj(a, b : Integer; var c : Single); // tylko c przez zmienną
type Matrix = array [1..5, 1..5] of Byte;
procedure dodajM(A, B : Matrix; var M : Matrix); // tylko M -
Dzięki parametrom przekazywanym przez zmienne procedury mogą,
w pewnym sensie, zwracać wartości.
Parametry typu plikowego muszą być przekazywane przez zmienną.
Dopuszczalne jest, aby część parametrów była przekazywana przez war-
tość, a reszta przez zmienne.
Parametry przekazywane przez stałe deklaruje się w nagłówku procedu-
ry (lub funkcji) następująco: procedure nazwa_procedury (const Identyfika-
tor_1 : typ_1; const Identyfikator_2 : typ_2; ...; const Identyfikator_n : typ_n);
Parametry przekazywane przez stałe są w treści procedury lub funkcji
traktowane jako zmienne lokalne przeznaczone tylko do odczytu. W treści
procedury lub funkcji nie jest dozwolone przypisywanie im jakichkolwiek war-
tości. Zastosowanie instrukcji przypisania do parametru przekazywanego przez
stałą wygeneruje błąd na etapie kompilacji.
Dla parametrów typu łańcuchowego i strukturalnego zaleca się stosować
przekazywanie przez stałe, gdy\ kompilator generuje dla nich bardziej efektyw-
ny kod.
Parametry otwarte umo\liwiają przekazywanie do tej samej procedury lub
funkcji tablic o ró\nych rozmiarach. Deklaracja tablicowego parametru otwarte-
go ma postać:
var nazwa_zmiennej : array of nazwa_typu lub
const nazwa_zmiennej : array of nazwa_typu lub
nazwa_zmiennej : array of nazwa_typu
w zale\ności od tego, czy deklarowany parametr jest przekazywany przez
zmienną, stałą czy wartość.
78
W treści procedury lub funkcji tablicowy parametr otwarty jest traktowany
jak array [0..n-1] of nazwa_typu, gdzie n oznacza liczbę elementów argumentu.
W treści procedury lub funkcji, do określenia liczby elementów tablicy
otwartej, stosuje się funkcję standardową High. Do tej tablicy mo\na te\ stoso-
wać funkcje SizeOf i Low.
Tabela 5.1. Program z definicjami procedur, demonstrujący ich działanie
program Proc1; var // c.d.
a, b : Integer;
procedure www(k, m : Integer); BEGIN
begin // przekazywanie przez wartości a := 1;
writeln('k=', k); b := a+1;
k := k+m; www(a, b+1);
writeln('k=', k); writeln('a=', a);
end; www(a, b+1);
writeln('a=', a);
procedure zzz(var k, m : Integer); zzz(a, b);
begin // przekazywanie przez zmienne writeln('a=', a);
writeln('k=', k); zzz(a, b);
k := k+m; writeln('a=', a);
writeln('k=', k); readln;
end; END.
Przykład 1. Generowanie i wypisywanie zawartości macierzy.
program Proc3;
uses SysUtils;
const RM = 5;
type matrix = array [1..RM, 1..RM] of Byte;
procedure gener(var M : matrix);
var i, k : Byte; //zmienne lokalne
begin
Randomize;
for i:=1 to RM do
for k:=1 to RM do
M[i,k] := random(10);
end;
procedure wypisz(M : matrix);
var i, k : Byte;
79
begin
for i:=1 to RM do
begin
for k:=1 to RM do write(M[i,k]:4);
writeln;
end;
writeln;
end;
var A, M : Matrix;
BEGIN
gener(M);
wypisz(M);
gener(A);
wypisz(A);
readln;
END.
W Lazarus FPC istnieje mo\liwość podania domyślnej wartości parame-
tru wywołania procedury lub funkcji przez u\ycie słowa kluczowego const
w nagłówku przed nazwą parametru oraz podanie jego wartości po znaku =.
Przykład 3. Procedura z domyślną wartością parametru wywołania.
program Proc2;
Procedure demo(const napis : string = 'Domyslny');
begin
writeln(napis);
end;
var s : string;
BEGIN
s := 'procedura demo';
demo(s);
demo;
demo('dowolny napis');
readln;
END.
5.3. Procedury i dyrektywa zapowiadająca Forward
Dyrektywy zapowiadającej Forward u\ywa się w celu zadeklarowania
podprogramu, który zostanie zdefiniowany pózniej po to, aby w podprogramach
zdefiniowanych wcześniej mo\na było u\yć wywołania tego podprogramu.
80
Przykład u\ycia dyrektywy zapowiadającej Forward:
Program proc5;
var a : Byte;
procedure druga (var y : Byte); forward;
function pierwsza (var x : Byte) : Byte;
begin
inc(x);
if x < 10 then druga(x); // Result := x;
end;
procedure druga (var y : Byte); // forward;
begin
pierwsza(y);
end;
BEGIN
a := 4; // wartość początkowa
pierwsza(a);
writeln(a); // wartość końcowa
readln;
END.
5.4. Składnia funkcji
Funkcja to podprogram wykonujący jedną lub więcej czynności i zwraca-
jący jeden typ wartości, ale niekoniecznie tylko jedną wartość. Mo\e to być np.
tablica wartości. Nazwa funkcji u\ywana jest w charakterze wyra\enia lub in-
strukcji.
81
nagłówek
funkcji
część
deklaracyjna
begin
ciąg instrukcji
blok
funkcji
Result ! wyra\enie
end
Rys. 5.2. Schemat blokowy składni funkcji
Podstawowa składnia definicji funkcji jest następująca:
function nazwa_funkcji (lista_parametrów_formalnych) : typ_wyniku;
// deklaracja lokalnych typów (type), stałych (const) i zmiennych (var)
Begin
Ciąg_instrukcji;
Result := wyra\enie; // albo nazwa_funkcji := wyra\enie;
End;
Lista parametrów formalnych mo\e być pusta. Wówczas nawiasy okrągłe
mo\na opuścić albo pozostawić.
Poniewa\ funkcja zwraca pewną wartość, to w treści funkcji musi wystą-
pić co najmniej jedna instrukcja przypisania postaci:
nazwa_funkcji := wyra\enie; albo Result := wyra\enie;
która powoduje przypisanie wartości wyra\enie nazwie funkcji. Identyfikator
Result jest domyślną zmienną lokalną w funkcji i nie ma potrzeby jej deklaro-
wać. Typ zmiennej Result jest taki sam, jak typ_wyniku!
Identyfikator Result mo\e wystąpić wiele razy w treści funkcji, ale jako
wartość zwracana przez funkcję będzie ta, która jako ostatnia została przypisana
do zmiennej Result. Nale\y tak u\ywać instrukcji warunkowych i pętli w treści
funkcji, aby w ka\dej sytuacji, tzn. bez względu na wartości logiczne warunków
występujących w treści funkcji, zawsze była przypisana pewna wartość do
zmiennej Result. Tak jak ka\da zmienna lokalna, równie\ zmienna Result po-
82
winna być zainicjalizowana przed pierwszym u\yciem, w przeciwnym razie
wartość tej zmiennej jest nieokreślona (losowa).
Wykonanie instrukcji Result := wyra\enie; nie kończy działanie funkcji,
lecz tylko ustala wartość zwracaną przez funkcję. Wszystkie instrukcje, które
znajdują się dalej w treści funkcji są wykonywane, a\ do słowa kluczowego
end; Wystąpienie instrukcji Result := wyra\enie_2; powoduje zmianę wartości
zwracanej przez funkcję.
Wywołanie funkcji polega na wpisaniu w kodzie zródłowym programu na-
zwy funkcji z odpowiednią listą parametrów aktualnych:
nazwa_funkcji (lista_parametrów_aktualnych);
Parametry aktualne oddziela się przecinkami.
Je\eli typ_wyniku nie zgadza się z typem wartości podstawianej pod zmien-
ną Result, wówczas wartość podstawiana jest odpowiednio konwertowana, jeśli
jest to mo\liwe.
Uwagi odnośnie parametrów formalnych i aktualnych oraz na temat sposo-
bów przekazywania parametrów do funkcji są takie same jak dla procedur.
Przykład funkcji z parametrami a i b, przekazywanymi przez wartość:
function dodaj (a : Byte; b : Single) : Single;
begin
Result := a+b; // albo dodaj := a+b;
end;
Przykład wywołania funkcji dodaj:
var a, x : Byte;
b, y, wynik : Single;
...
wynik := dodaj(x, y);
wynik := dodaj(x, dodaj(x, y));
Przykład ilustrujący przekazywanie tablic do funkcji:
type Wektor = array [1..10] of Smallint;
function Iloczyn_skalarny (A, B : Wektor) : Smallint;
83
Przykład wywołania funkcji Iloczyn_skalarny:
var A, B : Tablica;
wynik : Smallint;
...
wynik := Iloczyn_skalarny(A, B); writeln( wynik= , wynik);
// albo
writeln( wynik= , Iloczyn_skalarny(A, B));
Przykład 1. Wywołanie funkcji z parametrami przekazywanymi przez wartość.
program Podpr2;
function suma2(k, m : Integer) : Integer;
begin
Result := k+m;
Result := 2*Result;
//suma2 := 2*(k+m); // niepolecane
end;
var a, b, x : Integer;
BEGIN
x := suma2(1,4); //wywołanie funkcji suma2
writeln('suma=', x);
a := 2; b := 3;
writeln(suma2(2,3)); //wywołanie funkcji suma2
writeln(suma2(a,b)); //wywołanie funkcji suma2
readln;
END.
Przykład 2. Funkcja wypelniacz wypełnia tablicę A podaną liczbą x.
program Podpr5;
type TArr = array [1..10] of Integer;
function wypelniacz(x : Integer) : TArr;
var i : Integer;
begin
for i := 1 to 10 do Result[i] := x;
end;
var A : TArr;
i : Byte;
BEGIN
A := wypelniacz(2);
for i := 1 to 10 do write(A[i], ' ');
readln;
END.
84
5.5. Przecią\anie funkcji
Przecią\anie (przeładowywanie) funkcji polega na definiowaniu kilku
funkcji lub procedur o tych samych nazwach ró\niących się zestawem parame-
trów formalnych wywołania. W takim przypadku trzeba u\yć dyrektywy over-
load na końcu nagłówka ka\dej przecią\onej funkcji lub procedury.
O tym, którą przecią\oną funkcję wywołać, decyduje kompilator, ba-
dając typy parametrów aktualnych u\ytych w konkretnym wywołaniu.
Funkcji i procedur o tych samych nazwach i o jednakowych listach parame-
trów formalnych, ale ró\nych typach wartości zwracanej nie wolno przecią\ać.
Przykład 1. Przecią\anie funkcji dodaj.
program Podpr4;
function dodaj(a,b : Byte) : Byte; Overload;
begin
Result := a+b;
end;
function dodaj(a,b : string) : string; Overload;
begin
Result := a+b;
end;
BEGIN
writeln('dodawanie liczb =', dodaj( 1, 2 ));
writeln('dodawanie łancuchow =', dodaj('1', '2'));
readln;
END.
Przykład 2. Funkcja zamiany liczby naturalnej na kod o podstawie 210.
Program fun_4;
const MaxPodst = 10;
type TPodst = 2..MaxPodst;
function Card2Str(x : Cardinal; p : TPodst) : string;
type TCyfra = 0..MaxPodst-1;
var cyfra : TCyfra;
begin
Result := '';
repeat
cyfra := x mod p;
x := x div p;
Result := char(cyfra + byte('0')) + Result;
until x = 0;
end;
85
var a : Cardinal;
b : TPodst;
BEGIN
write('a=');
readln(a);
write('podstawa systemu liczenia:');
readln(b);
writeln(Card2Str(a, b));
readln;
END.
5.6. Wywołanie funkcji przez inną funkcję
W kodzie zródłowym funkcji mo\e wystąpić wywołanie innej funkcji. Wy-
konywanie bie\ącej funkcji jest wówczas chwilowo przerywane, a rozpoczyna
się wykonywanie drugiej. Po zakończeniu wykonania funkcji drugiej sterowanie
wraca do pierwszej funkcji, do miejsca gdzie została przerwana. Tak więc funk-
cje i procedury mo\na zagnie\d\ać.
W treści funkcji lub procedury mo\na umieścić wywołanie innej funkcji lub
procedury, która została zdefiniowana wcześniej.
W części deklaracyjnej funkcji (procedury) tzw. zewnętrznej mo\na zdefi-
niować inną funkcję (procedurę) tzw. wewnętrzną, która będzie lokalna, tzn. wi-
doczna tylko w treści procedury zewnętrznej.
Wywołanie funkcji A Definicja funkcji B w czę- Wywołanie procedury A
w treści funkcji B. ści deklaracyjnej funkcji A w procedurze B.
i wywołanie funkcji B w
treści funkcji A.
function A:pewien_typ; function A; procedure A;
begin begin
... function B:pewien_typ; ...
end; begin end;
...
function B; end; procedure B;
var x : pewien_typ; begin
begin var x : pewien_typ; ...
... begin A;
x := A; ... end;
... x := B;
end; ...
end;
86
5.7. Zmienne globalne i lokalne
Zmienne globalne to zmienne zdefiniowane na zewnątrz procedur, funkcji
oraz metod. Zmiennych globalnych mo\na u\ywać we wszystkich funkcjach
i procedurach pliku zródłowego, w którym zostały zadeklarowane. Wszystkie
zmienne globalne są umieszczane w tzw. segmencie danych, dla którego pamięć
jest przydzielana tylko raz, w chwili rozpoczęcia wykonywania programu.
Pamięć ta jest zerowana na starcie programu, tzn. zmiennym globalnym są
przypisywane wartości zerowe odpowiedniego typu. Tak więc, je\eli progra-
mista zapomni zainicjalizować zmienną globalną, to istnieje du\a szansa, \e
program będzie działał prawidłowo, gdy\ bardzo często pierwszą przypisywaną
(celowo) wartością do zmiennej globalnej jest właśnie wartość zerowa.
Powinno się unikać u\ywania zmiennych globalnych w treści procedur
i funkcji, gdy\ utrudnia to śledzenie poszczególnych fragmentów kodu i popra-
wianie błędów (debugowanie). Zmienną globalną powinno przekazywać się do
procedur lub funkcji:
" przez wartość, je\eli chcemy działać na kopii zmiennej globalnej,
" przez zmienną, je\eli chcemy zmienić wartość tej zmiennej globalnej,
" przez stałą, je\eli chcemy tylko odczytać wartość zmiennej globalnej.
Zmienne lokalne to wszystkie pozostałe zmienne w programie, tzn. te,
które nie są globalne. Właściwości zmiennych lokalnych są następujące:
" zmienne lokalne są umieszczane w tzw. segmencie stosowym, czyli w
obszarze pamięci podręcznej (w innym obszarze ni\ zmienne globalne),
" przy ka\dorazowym wywołaniu funkcji zmiennym lokalnym przydzie-
lana jest na stosie pamięć, a po zakończeniu wykonywania funkcji pa-
mięć ta jest zwalniana,
" wewnątrz funkcji mo\na definiować tylko zmienne lokalne,
" zmienna lokalna danej funkcji jest widoczna tylko w tej funkcji, w któ-
rej została zadeklarowana,
" nie mo\na w jednej funkcji odwołać się do zmiennej lokalnej zadekla-
rowanej w innej funkcji,
" wiele funkcji mo\e mieć zmienną lokalną o takiej samej nazwie,
" nazwy zmiennych lokalnych i globalnych mogą być jednakowe (wtedy
jednak zmienne lokalne przysłaniają zmienne globalne),
" zmienne lokalne nie są zerowane przy powoływaniu do \ycia , tzn.
mają wartość losową, która przy ka\dym kolejnym uruchomieniu pro-
gramu mo\e być inna ni\ poprzednio,
87
" po zakończeniu wykonywania bloku instrukcji, dla którego została po-
wołana do \ycia zmienna lokalna, przestaje ona istnieć (jest usuwana ze
stosu),
" przy ponownym wejściu do danego bloku (np. przy ponownym wywo-
łaniu tej samej funkcji) zmienna lokalna jest powoływana do \ycia po
raz kolejny,
" przerwanie wykonywania jednej funkcji przez wywołanie innej, a nawet
tej samej, nie powoduje zlikwidowania zmiennych lokalnych pierwszej
funkcji znajdujących się ju\ na stosie. Oznacza to, \e mogą istnieć na
stosie dwie zmienne lokalne o jednakowej nazwie nale\ące do tej samej
funkcji, ale posiadające ró\ne wartości. Zadaniem kompilatora jest pra-
widłowe rozró\nienie tych zmiennych.
" zmienne lokalne nie mogą mieć nazw takich, jak nazwy parametrów
formalnych funkcji.
Takie same uwagi odnoszą się do zmiennych lokalnych w procedurach.
Przykład 1. Program z jedną zmienną lokalną i jedną zmienną globalną.
program proc4;
var zm_globalna : Integer;
Procedure proc_glob;
var zm_lok : Integer;
begin
zm_lok := 1;
zm_globalna := zm_globalna+1;
writeln('zm_lok=', zm_lok);
writeln('zm_glob=', zm_globalna);
end;
BEGIN
zm_globalna := 5;
writeln( 'zm_glob=', zm_globalna );
proc_glob;
writeln( 'zm_g=', zm_globalna ); // 6
zm_globalna := 9;
writeln( zm_globalna ); // 9
proc_glob; // 10
writeln( zm_globalna ); // 10
readln;
END;
88
Przykład 2. Przysłanianie zamiennych globalnych przez zmienne lokalne.
program fun_zmienne;
var x, y : Byte; // zmienne globalne
Function zewn(a : Byte) : Byte;
var x : Byte; // zmienna lokalna
function wewn(a : Byte) : Byte;
var x : Byte; // zmienna lokalna
begin
x := 1;
x := x+1;
Result := x;
end;
Begin // funkcja zewn
x := 10;
x := x + wewn(a);
Result := x;
End;
BEGIN
x := 100;
y := x + zewn(x);
{Jaka będzie wartość y? Za pomocą klawisza F7 i okna
Czujki nale\y prześledzić wartości zmiennych y i x.}
writeln('Jaki wynik? ..', y);
readln;
END.
Przykład 3. Przekazywanie parametrów przez zmienną.
program z1fun;
var x, y : Integer;
Function zad1(a : Integer; var b : Integer) : Integer;
function wew1(c, d : Integer) : Integer;
begin
d := c+d;
wew1 := d+x; // albo Result := d+x;
end;
Begin
a := a+b;
b := a+b;
Result := wew1(a,b); // albo zad1 := wew1(a,b);
End;
89
BEGIN
x := 2;
y := 1;
writeln(zad1(x, y)); // ?
writeln(zad1(y, x)); // ?
writeln(x, y); // x=?, y=?
readln;
END.
5.8. Funkcje rekurencyjne
Rekurencja polega na tym, \e funkcja mo\e wywoływać samą siebie. Mo\e
to robić ka\da funkcja, ale jeśli nie ma w niej warunku zatrzymującego rekuren-
cję, to będzie się wywoływać w nieskończoność, tzn. a\ do wyczerpania pamięci
na stosie. Gdy funkcja rekurencyjna wywołuje samą siebie, na stosie są ju\
zmienne lokalne będące własnością pierwszego wywołania tej funkcji. Kolejne
jej wywołanie umieszcza na stosie swoje własne zmienne lokalne.
Przykład 1. Procedura wywołująca siebie rekurencyjnie.
Program procedura_rek;
var k : Integer;
procedure inkrementacja(x : Integer);
begin
x := x+1;
if x < 4 then inkrementacja(x) else
writeln('Koniec wywoływania rekursyjnego dla x =',x);
writeln('Jestem ostatnim poleceniem na poziomie ',x);
end;
BEGIN
k := 0;
inkrementacja(k);
readln;
END.
Przykład 2. Rekurencyjne obliczanie silni.
program silnia_rekurencyjna;
function Rsilnia(liczba : Integer) : Cardinal;
begin
if liczba=0 then Rsilnia := 1
else Rsilnia := liczba*Rsilnia(liczba-1);
end;
var n : Byte;
90
BEGIN
writeln('Podaj n=');
readln(n);
writeln(' n! = ', Rsilnia(n));
readln;
END.
Przykład 3. Rekurencyjne obliczanie kolejnych liczb ciągu Fibonacciego.
program FibRekur; // F0=0, F1=1, Fn = Fn-1 + Fn-2
function Fib(n : Cardinal) : Cardinal;
begin
if n <= 1 then Result := n
else Result := Fib(n-1) + Fib(n-2);
end;
var i : Integer;
BEGIN
for i := 0 to 40 do writeln(i:4, ' : ', Fib(i));
readln;
END.
5.9. Typ funkcyjny
Typ funkcyjny (proceduralny) deklarowany słowami kluczowymi type oraz
procedure lub function stworzony został po to, aby traktować procedury lub
funkcje jako wartości pewnych zmiennych.
type MojaFunkcja = function (a, b : byte) : real;
W przedstawionym przykładzie wartość zmiennej typu proceduralnego
MojaFunkcja staje się funkcją realizującą zadania wybrane przez u\ytkownika.
Typy i zmienne proceduralne mo\na równie\ definiować w sekcji deklara-
cji zmiennych, np.: var MojaFunkcja : function (a, b : byte) : real;
Przykład:
program typ_procedur2;
type TFunkcja = function(a,b : Integer) : Real;
function dodaj(skladnik1, skladnik2 : Integer) : Real;
begin
Result := skladnik1 + skladnik2;
end;
function odejmij(sklad1, sklad2 : Integer) : Real;
begin
Result := sklad1 - sklad2;
end;
91
function pomnoz(czynnik1, czynnik2 : Integer) : Real;
begin
Result := czynnik1 * czynnik2;
end;
function podziel(dzielna, dzielnik : Integer) : Real;
begin
if dzielnik<>0 then Result := dzielna / dzielnik;
end;
var
mFunkcja : TFunkcja;
x1, x2 : Integer;
zn : Char;
BEGIN
writeln('Podaj dwie liczby');
readln(x1,x2);
writeln('wybierz dzialanie: +dodawanie, -odejmownie,
* mnozenie,/ dzielenie');
repeat
readln(zn);
until zn in ['+', '-', '*', '/'];
case zn of
'+' : mFunkcja := @dodaj;
'-' : mFunkcja := @odejmij;
'*' : mFunkcja := @pomnoz;
'/' : mFunkcja := @podziel;
end;
writeln('wynik wynosi= ', mfunkcja(x1, x2));
readln;
END.
5.10. Procedury kierujące działaniem programu
Wśród procedur standardowych modułu System oraz SysUtils znajdują się
procedury kierujące działaniem programu, mianowicie:
" Exit procedura ta słu\y do przerwania bie\ącego podprogramu (pro-
cedury lub funkcji) i powrotu do programu głównego. Jeśli jest wywoła-
na z programu głównego, to kończy jego działanie.
" Halt (kod_wyjścia) procedura ta powoduje natychmiastowe zakończe-
nie działania programu i przekazuje sterowanie do systemu operacyjne-
go. Procedura Halt mo\e być wywołana bez parametru lub z jednym pa-
rametrem typu Integer, którego wartość jest zwracana systemowi opera-
92
cyjnemu jako kod wyjścia z programu. U\ycie wywołania procedury
Halt w postaci Halt; albo Halt (0); oznacza normalne (bez błędu) za-
kończenie programu. Procedura Halt znajduje zastosowanie przy wywo-
łaniach danego programu tekstowego z innego programu.
" RunError procedura ta powoduje przerwanie wykonywania programu
i wygenerowanie błędu jego wykonywania o podanym w nawiasie ko-
dzie (argument typu Byte). Wywołanie RunError; oznacza, \e kod błę-
du wynosi 0, natomiast RunError (kod_błędu); zwraca kod_błędu.
" Sleep - procedura ta jest wywoływana z jednym parametrem typu Car-
dinal. Powoduje ona zatrzymanie programu na liczbę milisekund równą
wartości podanego parametru.
" Abort - ta bezparametrowa procedura generuje tzw. cichy błąd, który
nie jest wyświetlany w postaci komunikatu. Procedura Abort kończy
działanie programu bez informacji o błędzie.
Przykłady:
program Pr_exit; program Pr_sleep_halt;
var i : byte; Uses SysUtils;
procedure zewnetrzna; //W tym module jest procedura sleep
procedure wewnetrzna; var
begin s : string;
writeln('To napisała procedura I : byte;
wewnętrzna');
exit; BEGIN
writeln('a tego nigdy nie zo- s := 'Dziala opoznienie- (sleep)';
baczymy'); for i := 1 to length(s) do
end; begin
write(s[i]);
begin sleep(400);
writeln('To napisała procedura ze- end;
wnętrzna'); // readln;
wewnetrzna;
//tu wyw. procedurę wewnetrzną writeln ('a teraz zadziała proce-
writeln('ten napis się pojawi, dura Halt, za 5 sekund');
kiedy wewnętrzna przeka\e sterowa- sleep(5000);
nie do zewnętrznej'); Halt;
exit; Writeln('Tego napisu nie zobaczy-
writeln('A ten napis ju\ się nie my')
pojawi'); END.
93
end;
BEGIN // program główny
writeln('wywołujemy procedurę ze-
wnętrzną');
zewnetrzna;
readln;
exit;
writeln('ten napis te\ się nie po-
jawi');
readln;
END.
5.11. Kryteria stosowania procedur i funkcji
Podczas podejmowania decyzji o wyborze procedury czy funkcji do zapro-
gramowania podprogramu, warto kierować się następującymi wskazówkami:
" jeśli podprogram ma zwracać więcej ni\ jedną wartość bądz modyfiko-
wać wartości parametrów aktualnych, nale\y u\yć procedury,
" jeśli podprogram ma zwracać dokładnie jedną wartość, najlepiej u\yć
funkcji,
" bardzo często procedurę mo\na zastąpić równowa\ną funkcją i odwrot-
nie, wtedy nale\y wybrać ten sposób, który mamy lepiej opanowany,
" zmienne mające w podprogramie charakter roboczy nale\y deklarować
jako zmienne lokalne,
" bardziej bezpieczne i zalecane jest przekazywanie parametrów aktual-
nych do podprogramu przez wartość, gdy\ są one w ten sposób chronio-
ne przed niepotrzebnymi zmianami (wszelkie zmiany, je\eli będą w
podprogramie, zostaną dokonane na kopiach parametrów aktualnych),
" jeśli parametr słu\y do komunikowania otoczeniu efektów wykonania
podprogramu, to musi być przekazywany przez zmienną,
" jeśli zachodzi konieczność przekazania du\ej struktury danych do pod-
programu, to nale\y unikać przekazywania jej przez wartość. Pozwoli to
na lepsze gospodarowanie pamięcią, poniewa\ dla parametrów przeka-
zywanych przez wartość jest tworzona ich kopia na stosie.
94
5.12. Moduły
Funkcje i procedury, napisane przez u\ytkownika, mogą być połączone
w jeden moduł, z którego mo\na korzystać w wielu aplikacjach.
Reguły tworzenia modułów są następujące:
" nazwa pliku, w którym znajduje się moduł, powinna być taka sama, jak
nazwa modułu,
" moduł składa się z części opisowej, implementacyjnej i inicjalizującej;
część inicjalizująca musi kończyć się słowem kluczowym end i kropką,
" stosowanie modułów powoduje, \e właściwe programy są znacznie
krótsze, bardziej przejrzyste i czytelne,
" dzięki temu, \e moduły przechowywane są w postaci skompilowanej,
w trakcie kompilacji programu definicje zawarte w modułach są do pro-
gramu wynikowego dołączane, a zatem kompilacja przebiega szybciej.
Fragment modułu, umieszczony pomiędzy słowami interface i implemen-
tation, jest tzw. częścią publiczną, a pomiędzy słowami implementation oraz
end jest tzw. częścią prywatną modułu.
W programach, w których zadeklarowano chęć korzystania z modułu, są
dostępne tylko definicje umieszczone w części publicznej, tzn.:
" \aden typ, zmienna lub stała umieszczona w części prywatnej nie
jest dostępna w programie,
" definicje funkcji i procedur, niezale\nie od tego czy mają być dostępne
w programie czy nie, muszą być umieszczone w części prywatnej,
" procedura lub funkcja staje się publiczna, gdy jej nagłówek zostanie
umieszczony w części publicznej modułu.
Liczba modułów, z których mo\e składać się program, jest praktycznie nie-
ograniczona. Jeśli jeden moduł korzysta z innego, to istotna jest kolejność nazw
na liście deklarowanych modułów. Moduł wykorzystywany powinien znajdować
się przed modułem wykorzystującym go.
Nazwy występujące w programie są nadrzędne w stosunku do tych wystę-
pujących w modułach. Przesłonięte identyfikatory z modułu są dostępne w pro-
gramie, jeśli są poprzedzone nazwą modułu i kropką.
Dołączanie modułu w programie odbywa się za pomocą słowa kluczowego
uses według szablonu:
program z_wlasnym_modulem;
uses
SysUtils, System, Unit1 in 'Unit1.pas';
95
Unit Unit1
Interface
// część publiczna
Implementation
// część prywatna
End.
Przykład. Definiowanie własnego modułu.
Program przyklad_modulu;
uses SysUtils, modul1;
var a : Tab;
BEGIN
gener(A);
wypisz(A);
readln;
END.
Unit modul1;
Interface
const n = 2;
type Tab = array [1..N,1..N] of Integer;
procedure gener(var T : Tab);
procedure wypisz(T : Tab);
Implementation
procedure gener(var T : Tab);
var i, j : Byte;
Begin
for i:=1 to N do
for j:=1 to N do T[i,j] := random(10);
End;
procedure wypisz(T : Tab);
var i, j : Byte;
Begin
for i := 1 to N do
for j := 1 to N do
if j < N then write(T[i,j]:4,' ')
else writeln(T[i,j]:4)
End;
End. // Unit & End.
96
5.13. Zadania
Zad. 5.1. Napisać funkcję zamiany liczby dziesiętnej na szesnastkową.
Zad. 5.2. Napisać funkcję zamiany liczby szesnastkowej na dziesiętną.
Zad. 5.3. Napisać funkcję obliczania przybli\onej wartości ex według sumy:
x x2 xn
ex H" 1+ + + ...+ , x-dowolne.
1! 2! n!
Zad. 5.4. Napisać funkcję obliczania przybli\onej wartości cos(x) według sumy:
x2 x4 x6 x2n
cos x = 1- + - + ...(-1)n , x-dowolne.
2! 4! 6! (2n)!
Zad. 5.5. Napisać program i rekurencyjną funkcję odwracania ciągu liter. Wśród
parametrów formalnych funkcji powinny wystąpić: napis typu string
oraz indeks typu Byte.
Zad. 5.6. Napisać program i rekurencyjną funkcję obliczania największego
wspólnego dzielnika NWD dwóch liczb.
Zad. 5.7. Napisać program obliczania największego wspólnego dzielnika n liczb.
Zad. 5.8. Napisać program i funkcję, która zwróci tablicę n-elementową typu
Integer (generowanie danych losowych). Argumentem wejściowym
jest rozmiar tablicy, czyli n.
Zad. 5.9. Napisać funkcję, która zwróci element o maksymalnej wartości bez-
względnej dla tablicy n-elementowej.
Zad. 5.10. Napisać program i funkcję, która wyznaczy element najbardziej zbli-
\ony do średniej arytmetycznej elementów tablicy n-elementowej typu
Double.
Zad. 5.11. Napisać funkcję Dwumian(n, k : Cardinal) : wektor, gdzie type wek-
tor = array of Cardinal, która zwróci wektor czynników powstałych
po pełnym skróceniu ułamka dla współczynnika dwumianowego
n n
ł ł -1)K(n - k +1)
ł ł
n (n
ł ł = . Poniewa\ zawsze przyjmuje warto-
ł ł
łk ł łk ł
1" 2"K" k
ł łł ł łł
ści całkowite, więc takie skrócenie jest mo\liwe. Na przykład, dla
n =12 i k = 5, po skróceniu ułamka, otrzymamy iloczyn: 2"3"11"12 .
Uwaga: zadanie ma wiele poprawnych rozwiązań.
97
6. Operacje na plikach
Plik jest logicznym modelem fizycznego zbioru danych. Fizyczne zbiory
danych mogą być wprowadzane i wyprowadzane przez urządzenia zewnętrzne
komputera takie jak: klawiatura, ekran, drukarka, dyski stałe, optyczne itp. Do-
stęp do poszczególnych elementów pliku odbywa się sekwencyjnie, tzn. w da-
nym momencie dostępny jest co najwy\ej jeden element pliku. Następny ele-
ment mo\e być dostępny dopiero po wykonaniu jakiejś operacji na poprzednim
elemencie. Kolejność elementów w pliku zale\y od kolejności, w jakiej zostały
do niego zapisane. Liczba elementów pliku jest ograniczona pojemnością dysku.
Plik jest ciągiem elementów tego samego typu. Liczba elementów pliku
zale\y od programu go zapisującego. Od tablicy plik ró\ni się metodą dostępu
do poszczególnych elementów oraz tym, \e mo\e istnieć w pamięci dyskowej
komputera po zakończeniu działania programu (i po wyłączeniu zasilania).
Aby w programie było mo\liwe odczytywanie danych z pliku lub zapisy-
wanie danych do pliku, konieczne jest zdefiniowanie zmiennej plikowej odpo-
wiedniego typu i skojarzenie jej (przywiązanie) do konkretnego pliku na dysku.
Najlepiej najpierw zdefiniować typ plikowy, a potem zmienną plikową.
Zmienna plikowa (zmienna wskazująca, wskaznik poło\enia) zawsze pokazuje
na jakieś miejsce w pliku. Zmienna plikowa zezwala na dostęp do ka\dego ele-
mentu pliku i pośredniczy we wszystkich operacjach na nim wykonywanych.
6.1. Rodzaje plików
Mo\liwe są trzy sposoby definicji typu plikowego:
1) type nazwa_typu_plikowego = file of typ_elementów_w_pliku;
Ten typ plikowy oznacza plik zdefiniowany, poniewa\ w sposób jawny podany
jest typ elementów w pliku. Nazwa typ_elementów_w_pliku mo\e być dowol-
nym typem prostym, porządkowym, strukturalnym, wskaznikowym lub zdefi-
niowanym przez u\ytkownika (np. rekordem). Plik zdefiniowany jest plikiem
binarnym, tzn. niezrozumiałym (nieczytelnym) po otwarciu go w edytorze tek-
stu. Przykłady deklaracji zmiennych plikowych typu zdefiniowanego:
type dane = file of Double;
var moj_plik : dane;
type MaleLiczby = file of Smallint;
var plik_temp : MaleLiczby;
98
2) type nazwa_typu_plikowego = file;
Powy\sza definicja oznacza, \e typ elementów w pliku jest niezdefiniowany.
Pliki niezdefiniowane stosuje się w celu dostępu do fizycznych zbiorów dys-
kowych zgodnie z ich wewnętrznym formatem. Przykład: type plik = file;
3) type nazwa_typu_plikowego = TextFile; (albo Text)
W Lazarus FPC istnieje predefiniowany typ określający plik tekstowy. Elemen-
tami pliku tekstowego są wiersze składające się ze znaków. Znaki te mogą two-
rzyć liczby, wyrazy, tabele danych itp. Ka\dy wiersz zakończony jest parą zna-
ków CR i LF (ang. Carriage Return i Line Feed), których kody ASCII wynoszą
13 i 10 w systemie dziesiętnym oraz D i A w systemie szesnastkowym.
Przykład deklaracji pliku tekstowego: var plik_1 : TextFile;
6.2. Etapy przetwarzania pliku
Na przetwarzanie pliku dowolnego rodzaju składają się następujące czynności:
1. Deklaracja zmiennej plikowej.
2. Skojarzenie zmiennej plikowej z fizycznym zbiorem danych.
3. Otwarcie pliku.
4. Wykonanie operacji na pliku (odczyt danych lub zapis).
5. Zamknięcie pliku.
1. Przykłady deklaracji zmiennych plikowych:
var plik1 : file of Integer; // plik zdefiniowany
plik2 : file of Byte; // plik zdefiniowany
plik3 : TextFile; // plik tekstowy
plik4 : file; // plik niezdefiniowany
type TZesp = record
re, im : Extended;
end;
var plik5 : file of TZesp; // plik zdefiniowany
2. Do skojarzenia zmiennej plikowej z fizycznym zbiorem danych słu\y proce-
dura standardowa AssignFile (zmienna_plikowa, łańcuch_znakowy);
Przykłady kojarzenia zmiennej plikowej z plikiem na dysku:
AssignFile (plik1, wyniki.bin );
AssignFile (plik2, c:\Dane\dane ); // plik bez rozszerzenia
AssignFile (plik3, osoby.txt );
99
3. W zale\ności od kierunku przesyłania danych, stosowane są trzy sposoby
otwierania lub tworzenia pliku:
a) otwarcie istniejącego pliku do odczytu - zmienna plikowa pokazuje na pierw-
szy element w pliku. Przykłady: Reset (plik1); Reset (plik2);
b) otwarcie istniejącego pliku tekstowego w celu dopisywania tekstu na końcu
pliku. Zmienna plikowa pokazuje na miejsce za ostatnim znakiem w pliku tek-
stowym. Przykłady: Append (plik1); Append (plik2); Procedurę Append mo\na
stosować tylko do plików tekstowych.
c) utworzenie nowego pliku, w którym będą zapisywane dane. Je\eli istniał plik
o nazwie takiej samej, jak ta, którą chcemy u\yć, to zostanie zniszczony. Przy-
kłady: Rewrite (plik1), Rewrite (plik4, 10); Drugie wywołanie jest stosowane
tylko do plików niezdefiniowanych, pierwsze - do wszystkich. Liczba 10 ozna-
cza, \e ka\dy zapis do pliku niezdefiniowanego będzie składał się z 10 bajtów.
Zapis Rewrite (plik4) oznacza, \e domyślny rozmiar zapisu wynosi 128 bajtów.
4. Przetwarzanie pliku oznacza odczytywanie danych znajdujących się w pliku
albo zapisywanie danych do niego.
Zapisywanie danych do plików tekstowych odbywa się za pomocą procedur
Write i Writeln. Przykłady:
Write (plik3, zmienna); Write (plik3, zmienna1, zmienna2);
Writeln (plik3, a, + , b, = , a+b);
Zapisywanie danych do plików zdefiniowanych odbywa się za pomocą pro-
cedury Write. Przykłady: Write (plik1, liczba); Write (plik2, a, b, c);
W tym przypadku zmienna liczba musi być typu Integer; natomiast zmienne a, b
i c muszą być typu Byte.
Zapisywanie danych do plików niezdefiniowanych odbywa się za pomocą
procedury BlockWrite. Przykłady:
BlockWrite (plik4, bufor, licznik); Zmienna bufor jest zmienną dowolnego typu.
Zmienna licznik jest typu Integer i określa liczbę zapisów do pliku niezdefinio-
wanego, które chcemy wykonać. Bajty do zapisu są pobierane po kolei z bufora.
Liczbę bajtów dla jednego zapisu określa się wcześniej w procedurze Rewrite.
Je\eli rzeczywista liczba wykonanych zapisów na dysk jest ró\na od wartości
licznika, to wystąpi błąd. Jeśli w wywołaniu funkcji BlockWrite u\yjemy po-
mocniczej zmiennej faktyczny_licznik w postaci: BlockWrite (plik4, bufor, licz-
nik, faktyczny_licznik); to błąd nie wystąpi, a liczbę udanych zapisów będzie
mo\na odczytać w zmiennej faktyczny_licznik.
100
Odczyt danych z plików zdefiniowanych odbywa się za pomocą procedury
Read. Przykłady: Read (plik1, liczba); Read (plik2, a, b, c);
W tym przypadku zmienna liczba musi być typu Integer; natomiast zmienne a, b
i c muszą być typu Byte.
Odczyt danych z plików niezdefiniowanych odbywa się za pomocą proce-
dury BlockRead. Przykłady:
BlockRead (plik4, bufor, licznik); albo
BlockRead (plik4, bufor, licznik, faktyczny_licznik);
Znaczenie zmiennych bufor, licznik i faktyczny_licznik jest takie samo, jak dla
procedury BlockWrite.
Odczyt danych z plików tekstowych odbywa się za pomocą procedur Read
i Readln. Przykłady: Read (plik3, znak); zmienna znak mo\e być typu np. Char.
Readln (plik3, a, b); zmienne a i b mogą być typu np. Integer i Extended.
5. Po zakończeniu przetwarzania pliku nale\y go zamknąć procedurą CloseFile.
Przykłady: CloseFile (plik1); CloseFile (plik2);
W przetwarzaniu plików tekstowych wykorzystywane są następujące funk-
cje standardowe, zwracające True lub False:
Eof (zmienna_plikowa) - funkcja ta zwraca wartość True, je\eli zmienna_
plikowa pokazuje na miejsce za ostatnim znakiem w pliku lub gdy plik nie za-
wiera \adnych znaków.
EoLn (zmienna_plikowa) - funkcja ta zwraca wartość True, je\eli zmien-
na_plikowa pokazuje na znak końca wiersza (CR) lub gdy Eof (zmien-
na_plikowa) = True.
SeekEof (zmienna_plikowa) - funkcja ta zwraca wartość True, je\eli
zmienna_ plikowa pokazuje na miejsce za ostatnim znakiem w pliku lub gdy plik
nie zawiera \adnych znaków, lub gdy jedynymi znakami do końca pliku są znaki
odstępu (spacje), tabulacji lub CR i LF.
SeekEoLn (zmienna_plikowa) - funkcja ta zwraca wartość True, je\eli
zmienna_plikowa pokazuje na znak końca wiersza (CR) lub gdy Eof (zmien-
na_plikowa) = True, lub gdy jedynymi znakami do końca wiersza są znaki od-
stępu (spacje), tabulacji lub CR i LF.
101
6.3. Przykładowe programy działające na plikach
Przykład 1. Zapis liczb rzeczywistych do pliku zdefiniowanego.
program Pliki1;
var
f : file of Real;
x : Real;
BEGIN
AssignFile(f, 'plik.bin');
Rewrite(f);
x := 3.1415926;
write(f,x);
x := -1.8;
write(f,x);
write(f,x,x); // writeln(f,x,x); <-- niedozwolone
CloseFile(f);
readln;
END.
Przykład 2. Odczyt liczb rzeczywistych z pliku zdefiniowanego z przykładu 1.
program Pliki2;
var
f : file of Real;
x : Real;
BEGIN
AssignFile(f, 'plik.bin');
Reset(f);
while not EoF(f) do
begin
read(f, x);
writeln(x);
end;
CloseFile(f);
readln;
END.
Przykład 3. Zapis dwóch liczb rzeczywistych i całkowitej do pliku tekstowego.
program Pliki3;
var
f : TextFile;
x : Real;
102
BEGIN
AssignFile(f, 'plik.txt');
Rewrite(f);
x := 3.14;
writeln(f, x, ' ', -x, ' ', 2+2);
writeln(f, x, ' ', -x:8:3, ' ', 5);
CloseFile(f);
readln;
END.
Przykład 4. Odczyt dwóch liczb rzeczywistych i całkowitej z pliku tekstowego.
program Pliki4; //odczyt danych z pliku z przykładu 3.
var
f : TextFile;
x, y : Real;
i : Integer;
BEGIN
AssignFile(f, 'plik.txt');
Reset(f);
while not EoF(f) do
begin
readln(f,x,y,i);
writeln('x=', x, ' y=', y, ' i=', i);
end;
CloseFile(f);
readln;
END.
Przykład 5. Zapis liczb całkowitych z tablicy do pliku tekstowego i ich odczyt.
program wewy1;
const N = 5;
var
we, wy : TextFile;
zn : Char;
A, B : array [1..N] of Byte;
i : Byte;
BEGIN
for i := 1 to N do A[i] := 10*i;
AssignFile(we, 'Tplik.txt');
Rewrite(we);
for i := 1 to N do Writeln(we, A[i]);
CloseFile(we);
103
AssignFile(wy, 'Tplik.txt');
Reset(wy);
i := 1;
while (not EoF(wy)) and (i<=N) do
begin
readln(wy, B[i]);
write(B[i]);
i := i+1;
end;
CloseFile(wy);
readln;
END.
Przykład 6. Odczyt łańcuchów znakowych z pliku tekstowego.
program wewy2;
var
we1 : TextFile;
napis : string;
BEGIN
AssignFile(we1, 'wewy2.lpr');
Reset(we1);
while not EoF(we1) do
begin
readln(we1, napis);
if length(napis)<>0 then
if napis[1] in ['A'..'Z'] then writeln(napis)
// wypisuje na ekranie tylko te wiersze,
// które zaczynają się od wielkiej litery
end;
CloseFile(we1);
readln;
END.
6.4. Zadania
Zad. 6.1. W pliku tekstowym liczby.txt są zapisane liczby rzeczywiste po dwie
w ka\dym wierszu, oddzielone białymi znakami. Program ma czytać te pa-
ry liczb. Jeśli ich iloczyn będzie większy od zera, to program ma zapisać go
do pliku tekstowego dod.txt. Jeśli ich iloczyn będzie mniejszy od zera, to
program ma go zapisać do pliku ujem.txt.
Zad. 6.2. W pliku tekstowym we1.txt znajduje się kilka wierszy tekstu. Napisać
program zawierający funkcję Parzystosc_E(s : string), która sprawdzi, czy
w danym wierszu jest parzysta liczba znaków e i E . Funkcja ma zwracać
104
wartość True, je\eli liczba znaków e i E jest parzysta (zero te\ jest
parzyste) oraz ma zwracać wartość False w przeciwnym wypadku.
Argumentem wejściowym funkcji jest wiersz pliku. Wynik działania
funkcji przedstawić na ekranie.
Zad. 6.3. Program czyta wiersze pliku tekstowego we6.txt. Za ka\dym razem, po
napotkaniu wiersza, którego pierwszym znakiem jest 'A'..'Z', rozpoczyna
łączenie (sklejanie) kolejnych wierszy, rozdzielając je spacją. Sklejony
wiersz jest zapisywany do pliku wy6.txt, przy czym na początku, w nawia-
sach, jest wpisywana liczba jego znaków.
Zad. 6.4. Program ma czytać plik tekstowy zad4.lpr - wiersz po wierszu - i jeśli
w wierszu znajdzie ciąg dwóch ukośników '//', to zapisze dalszy ciąg tego
wiersza do pliku tekstowego komentarze.txt oraz wypisze na ekranie.
Zad. 6.5. Napisać program Dekomentator, który wejściowy plik tekstowy,
zawierający kod zródłowy programu w Lazarus FPC, przetworzy tak, \e
usunie z niego wszystkie mo\liwe komentarze (jedno i wielo-liniowe),
w tym tak\e zagnie\d\one. Uwaga na dyrektywy kompilatora!
Zad. 6.6. W kolejnych wierszach pliku tekstowego we5.txt znajduje się albo
liczba naturalna, albo jakiś napis. Wiersz z liczbą naturalną tym ró\ni się od
napisu, \e występują w nim wyłącznie cyfry '0'..'9' oraz znaki + , - .
Napisać program, który odczyta kolejne wiersze wspomnianego pliku
i zapisze do pliku tekstowego wy8.txt wyłącznie wiersze zawierające
napisy. Trzeba zaprogramować funkcję JestLiczba zwracającą dla danego
wiersza wartość True, jeśli zawiera on liczbę i False, jeśli zawiera on napis.
Zad. 6.7. Napisać program, który wczyta plik tekstowy we7.txt i sprawdzi, ile
zdań jest w tym pliku. Zdanie zaczyna się wielką literą i kończy się kropką,
pytajnikiem lub wykrzyknikiem.
105
7. Wskazniki
Zmienne typów prostych i strukturalnych istnieją przez cały czas wykony-
wania tej części programu, w której zostały zadeklarowane. Są to tzw. zmienne
statyczne.
Zmienne dynamiczne natomiast reprezentują obiekty, dla których pamięć
jest przydzielana i zwalniana na \ądanie, w trakcie pracy programu. Zmienna
dynamiczna nie posiada własnego identyfikatora, a odwołanie do niej następuje
za pomocą zmiennej typu wskaznikowego, w której pamiętany jest adres w pa-
mięci, gdzie ta zmienna dynamiczna się znajduje.
7.1. Definicja zmiennej wskaznikowej
Definicja zmiennej wskaznikowej (w skrócie - wskaznika) określa, jak in-
terpretować zawartość pamięci pod adresem pokazywanym przez wskaznik.
Na przykład definicja wsk : ^ Byte; oznacza, \e obszar pamięci, pokazywany
przez wskaznik wsk, zawiera liczbę typu Byte.
Przykłady deklaracji wskazników:
var
wsk_int : ^ Integer; {wskaznik na zmienną typu Integer}
wsk_char : ^ Char; {wskaznik na zmienną typu Char}
wsk_real : ^ Single; {wskaznik na zmienną typu Single}
type
tab = array [1..10] of Integer; {definicja typu tablicowego}
wsk = ^ tab; {definicja typu będącego wskaznikiem do tablicy}
var
wsk_tab : ^ tab; {definicja zmiennej wskazującej na tablicę}
// wsk_tab : wsk; ! to samo, co wsk_tab : ^ tab;
tab_wsk : array [1..10] of ^ Integer; {tablica zawierająca 10 wskazników
do liczb całkowitych typu Integer}
var p : Pointer; {wskaznik na zmienną dowolnego typu}
Zmienne typu Pointer nie pokazują na dane \adnego konkretnego typu.
Zawartość zmiennej typu Pointer mo\na podstawić do zmiennej dowolnego
typu wskaznikowego i na odwrót.
Uwagi dotyczące wskazników:
" deklaracja zmiennej wskaznikowej nie jest jednoznaczna z utworzeniem
zmiennej wskazywanej,
106
" ka\da zmienna wskaznikowa musi być przed u\yciem zainicjalizowana,
" nazwa zmiennej odwołuje się do obszaru pamięci o określonym adresie,
" adres pamięci, pod którym znajduje się zmienna, mo\na uzyskać za po-
mocą operatora @ , np. @ zmienna.
" ten sam wskaznik mo\e odwoływać się do ró\nych miejsc w pamięci,
ale nie równocześnie,
" wskaznik słu\ący do pokazywania na obiekty jednego typu nie mo\e
(zazwyczaj) pokazywać na obiekty innego typu,
" za pomocą wskaznika uzyskujemy dostęp do pewnego obszaru pamięci
i mo\emy modyfikować jego zawartość,
" wskazniki mogą być elementami rekordów,
" dozwolone jest u\ywanie wskazników do rekordów.
Zmiennej wskaznikowej mo\na nadać wartość początkową na kilka sposo-
bów, mianowicie poprzez:
" przypisanie standardowej wartości stałej nil,
" przypisanie adresu zmiennej dynamicznej utworzonej za pomocą proce-
dury New lub GetMem,
" przypisanie adresu istniejącej zmiennej, której typ jest taki, na jaki po-
kazuje wskaznik,
" przypisanie wartości innej zainicjalizowanej zmiennej wskaznikowej.
7.2. Procedury dynamicznego rezerwowania i zwalniania pamięci
Wskaznik pokazuje na pewien obszar w pamięci i w związku z tym:
" mo\e to być obszar przechowujący zmienną, a więc ju\ zarezerwowany,
" mo\na zarezerwować (zaalokować) nowy obszar dla wskaznika za po-
mocą procedury New,
" jeśli obszar pamięci jest ju\ niepotrzebny, to nale\y go zwolnić za po-
mocą procedury Dispose,
" obiekty utworzone za pomocą operatora New istnieją dopóty, dopóki nie
zostaną zlikwidowane procedurą Dispose,
" do obiektów utworzonych za pomocą procedury New mo\na odwoływać
się tylko za pomocą wskaznika,
" obiekty tworzone w ten sposób są dynamiczne i nie są wstępnie inicjali-
zowane zerami.
Standardowa stała nil oznacza, \e zmienna wskaznikowa na nic nie pokazu-
je; nil jest to tzw. adres zero , czyli \aden konkretny adres. Przykład:
var wsk : ^ Integer; . . . wsk := nil; // wsk pokazuje na adres zerowy
107
Rys. 7.1. Wskaznik z adresem pustym (nil)
Przypisanie wskaznikowi wartości nil jest wykorzystywane do zaznaczenia,
\e wskaznik nie pokazuje na nic konkretnego. Bez takiego przypisania wskaznik
pokazuje na jakiś losowy adres w pamięci.
Powstanie zmiennej dynamicznej w : ^typ1, za pomocą procedury New, od-
bywa się w kilku etapach:
" utworzenie nowej zmiennej dynamicznej typu typ1, która w tym mo-
mencie jest nieokreślona - nadawana jest jej automatycznie nazwa w^.
" utworzenie nowego wskaznika w typu ^typ1, który wskazuje na zmien-
ną typu typ1.
" nadanie wartości tego wskaznika zmiennej, dla której procedura New
została wywołana (czyli nadanie zmiennej dynamicznej w^ adresu pa-
mięci przeznaczonego dla zmiennej typu typ1).
" zapisanie do obszaru pamięci, na który wskazuje wskaznik w, konkret-
nej wartości jakiegoś wyra\enia.
var w : ^typ1;
New(w);
w^ := wyra\enie;
Rys. 7.2. Wskaznik W pokazujący obszar pamięci W^
Procedura GetMem ró\ni się od procedury New tym, \e wielkość zarezer-
wowanego miejsca w pamięci określana jest dopiero w czasie działania progra-
mu i sami mo\emy zdecydować o rozmiarze potrzebnej pamięci, podając drugi
parametr tej procedury, tzn. liczbę bajtów:
wsk : ^typ;
GetMem (wsk, liczba_bajtów);
Przykład u\ycia procedury GetMem:
type tab = array [1..20] of Integer;
wsk_tab = ^tab;
108
var
wpom : wsk_tab;
&
GetMem (wpom, 20*SizeOf (Integer)); {utworzenie zmiennej dynamicznej
o 20 elementach typu Integer}
wpom^ [ 1] := 5; { poprawne odwołanie do pierwszego elementu }
wpom^ [-2] := 10; { błąd zakresu zgłaszany przez kompilator }
wpom^ [25] := -7; { błąd zakresu niezgłaszany przez kompilator (25>20) }
Funkcja standardowa Addr (x) zwraca wskaznik typu Pointer, zawierający
adres miejsca, gdzie zapamiętane jest x, np. w := Addr (x);
Operator adresu @ słu\y do odczytania adresu zmiennej, na przykład:
var x : typ;
w : ^typ;
&
x := 12;
w := @ x; // wtedy w^ = x = 12
Przykład 1. Wskaznik na łańcuch znakowy.
program wsk1;
type TDane = string[60];
var
w1 : ^TDane;
BEGIN
New(w1);
w1^ := 'Cos tam';
Writeln(w1^);
w1^[2] := 'U'; //podmiana litery 'o' na 'U'
Writeln(w1^);
Dispose(w1);
readln;
END.
Przykład 2. Dwa wskazniki na ten sam łańcuch znakowy.
program wsk2;
type TDane = string[60];
var w1, w2 : ^TDane;
BEGIN
New(w1);
w1^ := 'Cos tam';
109
w2 := w1;
w2^ := 'Cos innego tam jest';
writeln(w1^);
writeln(w2^);
dispose(w2);
readln;
END.
Przykład 3. U\ycie zmiennych wskaznikowych i dynamicznych.
program wsk3;
type
wsk_int = ^Integer;
typDanych = record
imie, nazwisko : string;
end;
wsk_rek = ^typDanych;
var
x1, x2 : wsk_int;
r1 : wsk_rek;
BEGIN
New(r1); // zmienna dynamiczna typu rekordowego
writeln('Podaj imię i nazwisko: ');
readln(r1^.imie, r1^.nazwisko);
with r1^ do writeln(imie, nazwisko);
dispose(r1);
New(x1); // zmienna dynamiczna typu integer
New(x2);
x1^ := 5;
x2^ := 10;
writeln('x1^=', x1^);
x2^ := x1^; // przepisanie zawartości
x2 := x1; // przepisanie adresu
writeln('x1^=', x1^);
writeln('x2^=', x2^);
dispose(x1);
// dispose(x2); // nie mo\na zwolnić dwa razy
readln;
END.
Zwalnianie pamięci przydzielonej wskaznikowi w sposób dynamiczny od-
bywa się za pomocą procedur Dispose i FreeMem. Procedury te umo\liwiają
powtórne wykorzystanie obszarów pamięci oddawanych przez niepotrzebne ju\
zmienne dynamiczne.
110
przydzielenie pamięci: zwolnienie pamięci:
New (p); Dispose (p);
GetMem (p, liczba_bajtów); FreeMem (p, liczba_bajtów);
7.3. Wskazniki i tablice
Wskazniki są przydatne do pracy z tablicami. Zapis w := @tab[3]; powo-
duje, \e wskaznik w ustawia się na elemencie o indeksie 3 tablicy tab.
Nazwa tablicy jest równocześnie adresem jej pierwszego elementu, zatem
przypisanie w := @tab; jest równowa\ne przypisaniu w = @tab[pocz], jeśli
pocz jest pierwszym elementem tablicy tab.
Przykład 1. Tablica wskazników do łańcuchów znakowych.
program wsk4;
type TDane = string[30];
const MaxN = 5;
var A : array [0..MaxN-1] of ^TDane; // tablica wsk.
i : 0..MaxN;
BEGIN // tablica pustych wskazników
for i := 0 to MaxN-1 do A[i] := nil;
New(A[2]);
A[2]^ := 'NapisNr1'; //dodawanie danych do tablicy
New(A[4]);
A[4]^ := 'NapisNr2';
for i := 0 to MaxN-1 do
if A[i] <> nil then
begin
writeln('nr: ', i, ' ', A[i]^);
dispose(A[i]); //usuwanie danych z tablicy
// A[i] := nil;
end;
readln;
END.
Przyklad 2. Wskaznik do tablicy statycznej.
program wsk5;
const MaxN = 5;
type Tab = array [1..MaxN] of Byte;
wskTab = ^Tab;
var i : Byte;
B : Tab; //tablica statyczna
111
w : wskTab;
BEGIN // wypełnienie tablicy statycznej liczbami
for i := 1 to MaxN do B[i]:=i;
w := @B; // ustawienie wskaznika na tablicę
writeln(w^[1]); // writeln(B[1]);
inc(w); // inkrementacja adresu
writeln(w^[2]); // poza adres całej tablicy
w := @B[3]; // wskaznik pokazuje na trzeci element
writeln(w^[1]);
readln;
END.
Przykład 3. Wskazniki do tablicy dynamicznej.
program wsk6;
const N = 5;
type Tab = array [1..N] of Byte;
wskTab = ^Tab;
var i : Byte;
k : ^Byte;
w, x : wskTab;
BEGIN
New(w); // utworzenie dynamicznej tablicy
for i := 1 to N do w^[i] := i; // wpisanie danych
writeln('Pierwszy =', w^[1]);
writeln('Drugi =', w^[2]);
writeln('N-ty =', w^[N]);
x := w;
x^[N] := 115;
for i := 1 to N do write(x^[i]:5);
writeln;
k := @w;
writeln(k^);
k := @w[1];// pierwszy element tablicy
writeln(k^);
Dispose(w);//zwolnienie pamięci przydzielonej tablicy
readln;
END.
112
7.4. Operacje arytmetyczne na wskaznikach
Poniewa\ wskazniki przechowują adresy, które są liczbami, to dopuszczal-
ne są niektóre operacje arytmetyczne na wskaznikach:
" dodawanie do - i odejmowanie od wskazników liczb naturalnych, za
pomocą procedur inkrementacji Inc i dekrementacji Dec, powoduje
przesuwanie wskazników w kierunku wy\szych lub ni\szych adresów.
Poniewa\ wskaznik pokazuje na obiekt pewnego typu i znany
jest rozmiar tego typu, więc wiadomo o ile bajtów trzeba prze-
sunąć wskaznik.
Operacje zwiększania i zmniejszania wskazników mają istotne
zastosowanie w stosunku do elementów tablic. Je\eli np.
zmienna wskaznikowa zw wskazuje na piąty element tablicy, to
po Dec(zw) będzie wskazywać na czwarty element, niezale\nie
od typu elementów tablicy.
U\ywając procedur Inc i Dec trzeba uwa\ać, \eby nie wyjść ze
wskazaniem poza zakres tablicy.
Zwiększenie lub zmniejszenie zmiennej wskaznikowej o okre-
ślonym typie (ale nie Pointer) te\ odbywa się za pomocą proce-
dur Inc i Dec. Na przykład: x : ^ Double; Inc (x, 2) spowoduje
zwiększenie wskaznika x o 2*8 = 16 bajtów, natomiast k : ^ In-
teger; Inc (k, 3) spowoduje zwiększenie k o 3*4 = 12 bajtów.
" odejmowanie dwóch wskazników pokazujących na tę samą tablicę - w
wyniku dostajemy liczbę elementów tablicy dzielących elementy poka-
zywane przez oba wskazniki. Liczba ta mo\e być ujemna lub dodatnia.
" porównywanie wskazników - wskazniki mo\na ze sobą porównywać
za pomocą operatorów porównania: =, <>, <, >, <=, >=,
równość wskazników oznacza, \e pokazują one na ten sam obiekt,
wskaznik, który jest mniejszy, pokazuje na element o ni\szym ad-
resie (w tablicy na element o ni\szym indeksie).
Je\eli w oraz x są zmiennymi wskaznikowymi, to porównanie adresów
przechowywanych w tych wskaznikach przeprowadza się tak: w < x, natomiast
porównanie zawartości komórek pamięci odbywa się tak: w^ < x^.
113
8. Struktury dynamiczne
W wielu algorytmach pojawia się potrzeba wykorzystania struktur danych,
umo\liwiających w sposób dynamiczny wstawianie i pobieranie danych. Naj-
prostszymi taki strukturami są kolejka, lista i stos.
8.1. Kolejka
Kolejka to rodzaj listy jednokierunkowej, do której mo\na dopisać element
tylko na końcu, a usunąć tylko element znajdujący się na początku.
Kolejka realizuje strategię wstawiania/pobierania, opartą na zasadzie pierw-
szy wszedł, pierwszy wyjdzie. Kolejka zachowuje kolejność wstawianych ele-
mentów.
Operacje, które wykonuje się na kolejce to: wstawianie elementu, pobranie
elementu, usunięcie elementu, usunięcie całej kolejki, wyświetlenie zawartości
kolejki oraz sprawdzenie, czy kolejka jest pusta.
Rys. 8.1. Schemat blokowy kolejki
8.2. Lista jednokierunkowa
Lista jednokierunkowa jest zbiorem elementów zwanych węzłami, z któ-
rych ka\dy jest zwykle rekordem i składa się z dwóch części: pola danych i pola
wskaznikowego, wykorzystywanego jako łącznik z następnym elementem listy.
type
wsk = ^ skladnik;
skladnik = record
dane : typ;
next : wsk
end;
114
Dostęp do listy odbywa się poprzez wskaznik, który zawiera adres pierw-
szego elementu na liście. Wskaznik ten nazywany jest początkiem bądz korze-
niem listy. Ka\dy następny składnik listy jest dostępny poprzez składową zawie-
rającą adres w składniku poprzednim.
Rys. 8.2. Schemat blokowy listy
Przykład tworzenia nowej listy:
type
wsk = ^ skladnik;
skladnik = record
dane : typ;
next : wsk
end;
procedure tworz_liste;
var zn : Char;
begin
pocz := nil;
repeat
New(wsk);
write('Podaj kolejny
element listy')
readln(wsk^.dane);
wsk^.next := pocz;
pocz := wsk;
write('Czy kontynuować?');
readln(zn)
until zn <> 't';
end;
Najwa\niejsze operacje dotyczące wszystkich typów list to:
" dopisywanie nowego elementu:
kiedy lista jest pusta,
na jej początku,
na jej końcu,
w dowolnym miejscu wewnątrz listy.
115
" usuwanie elementu:
znajdującego się na początku,
znajdującego się na końcu,
w dowolnym miejscu wewnątrz listy.
" wyświetlanie listy.
Aby mo\liwe było wykonanie operacji wstawiania oraz usuwania nowych
elementów, lista musi być posortowana według określonego klucza. Kluczem do
sortowania mo\e być np. pole nazwisko, jeśli mamy do czynienia z listą osób.
Nazwiska typu łańcuchowego mo\emy łatwo porównywać alfabetycznie.
Przykład. Program tworzy listę dynamiczną, ale bez sortowania. Polem danych
jest łańcuch 60-znakowy. Program zawiera procedury: dopisywania elementu na
początku listy, wypisywania całej listy i usuwania listy z pamięci.
program Lista1;
type
TDane = string[60]; // łańcuch 60-znakowy
PElem = ^TElem;
TElem = record
Dane : TDane; // pole danych
Nast : PElem; // wskaznik na następny elem.
end;
TLista = PElem; // wskaznik na początek listy
procedure Inicjalizuj(var Lista : TLista);
begin
Lista := nil;
end;
procedure Dopisz(var Lista : TLista; D : TDane);
var w : PElem;
begin
w := Lista;
New(Lista);
Lista^.Dane := D;
Lista^.Nast := w;
end;
procedure WypiszWszystko(Lista : TLista);
var w : PElem;
116
begin
w := Lista;
while w <> nil do
begin
writeln(w^.Dane);
w := w^.Nast;
end;
end;
procedure UsunWszystko(var Lista : TLista);
var w : PElem;
begin
while Lista <> nil do
begin
w := Lista;
Lista := Lista^.Nast;
Dispose(w);
end;
end;
var Lst : TLista;
BEGIN
Inicjalizuj(Lst);
Dopisz(Lst, 'pierwszy');
Dopisz(Lst, 'drugi');
Dopisz(Lst, 'trzeci');
WypiszWszystko(Lst);
UsunWszystko(Lst);
readln;
END.
8.3. Stos jako dynamiczna struktura danych
Stos jest dynamiczną strukturą danych, która umo\liwia wstawianie ele-
mentów i ich pobieranie tylko z początku, zwanego wierzchołkiem stosu. Stos
realizuje strategię wstawiania/pobierania LIFO (Last In-First Out), czyli ostatni
wszedł, pierwszy wyjdzie. Stos odwraca kolejność wstawianych elementów.
Operacje wykonywane na stosie to:
" wstawienie elementu na stos (ang. push),
" zdjęcie elementu ze stosu (ang. pop),
" sprawdzenie, czy stos jest pusty (ang. empty),
" odczytanie elementu na szczycie (ang. top),
" inicjalizacja stosu (ang. init),
117
" usunięcie całego stosu (ang. clear),
" wyświetlenie zawartości stosu (ang. display).
Przykład. Program wykonuje operacje arytmetyczne na elementach stosu. Ele-
mentami stosu są tablice kwadratowe. Zaprogramowane operacje to: wkładanie
tablicy na stos, zdejmowania tablicy ze stosu oraz dodawanie, odejmowanie
i mno\enie dwóch tablic znajdujących się na szczycie stosu. W programie głów-
nym zakodowano operacje: C := A+B, C := A-B, C := A*(B+C).
Program Stos;
const NMax = 2;
type float = Double;
TArr = array [0..NMax-1, 0..NMax-1] of float;
PElement = ^TElement;
TElement = record
poprz : PElement;
X : TArr;
end;
TStos = PElement; {wskaznik na wierzchołek stosu}
procedure NaStos(var St : TStos; _X : TArr);
{_X - dane nowego elementu stosu}
var w : PElement;
begin
New(w);
w^.X := _X;
w^.poprz := St;
St := w;
end;
procedure ZeStosu(var St : TStos; var _X : TArr);
{X - zawartość likwidowanego elementu}
var w : PElement;
begin
if (St=nil) then EXIT; {błąd!}
w := St;
_X := w^.X;
St := w^.poprz;
Dispose(w);
end;
118
procedure Dodaj(var St : TStos);
{Dodaje dwa szczytowe elementy stosu - zdejmuje pierw-
szy, a wynik umieszcza w wierzchołku stosu.}
var i, j : 0..NMax-1;
T : TArr;
begin
ZeStosu(St, T);
for i := 0 to NMax-1 do
for j := 0 to NMax-1 do
St^.X[i,j] := St^.X[i,j] + T[i,j];
end;
procedure Odejmij(var St : TStos);
{Odejmuje najwy\szy od ni\szego elementu stosu - zdej-
muje 1 element, a wynik umieszcza w wierzchołku stosu}
var i, j : 0..NMax-1;
T : TArr;
begin
ZeStosu(St, T);
for i := 0 to NMax-1 do
for j := 0 to NMax-1 do
St^.X[i,j] := St^.X[i,j] - T[i,j];
end;
procedure Mnoz(var St : TStos);
{Mno\y elementy stosu i wynik umieszcza
w wierzchołku stosu}
var i, j, k : 0..NMax-1;
A {czynnik}, C {wynik} : TArr;
begin
ZeStosu(St, A); {drugi czynnik}
for i := 0 to NMax-1 do
for j := 0 to NMax-1 do
begin
C[i,j] := 0;
for k := 0 to NMax-1 do
C[i,j] := C[i,j] + St^.X[i,k]*A[k,j];
end;
ZeStosu(St, A); {zdejmuje ze stosu drugi czynnik}
NaStos(St, C); {wkłada iloczyn na stos}
end;
procedure Wyswietl(Nagl : string; A : TArr);
{wyświetla element wskazywany przez W,
na początku wypisuje nagłówek w postaci łańcucha}
119
var i, j : 0..NMax-1;
begin
writeln(Nagl);
for i := 0 to NMax-1 do
begin
for j := 0 to NMax-1 do
write(A[i,j]:6:1, ' ');
writeln;
end;
writeln;
end;
procedure InicjalizujDowolnie(var A : TArr);
var i, j : 0..NMax-1;
begin
for i := 0 to NMax-1 do
for j := 0 to NMax-1 do
A[i,j] := random(10)-5.0;
end;
procedure Pause;
begin
write('Naciśnij ENTER...':60);
readln;
end;
procedure TestStNil;
begin
if St<>nil then writeln('Program zawiera błędy!');
end;
var St : TStos;
A, B, C : TArr;
BEGIN
writeln(#13#10,'Stos i odwrotna notacja polska':60);
St := nil;
Randomize;
InicjalizujDowolnie(A);
InicjalizujDowolnie(B);
Wyswietl('A:', A);
Wyswietl('B:', B);
NaStos(St, A);
NaStos(St, B);
Dodaj(St);
ZeStosu(St, C);
120
Wyswietl('C=A+B', C);
TestStNil; Pause;
NaStos(St, A);
NaStos(St, B);
Odejmij(St);
ZeStosu(St, C);
Wyswietl('C=A-B', C);
TestStNil; Pause;
NaStos(St, A);
NaStos(St, B);
Mnoz(St);
ZeStosu(St, C);
Wyswietl('C=A*B', C);
TestStNil; Pause;
NaStos(St, A);
NaStos(St, B);
NaStos(St, C);
Dodaj(St);
Mnoz(St);
ZeStosu(St,C);
Wyswietl('C=A*(B+C)', C);
TestStNil; Pause;
END.
8.4. Zadania
Zad. 8.1. Zadeklarować tablicę 100 wskazników do rekordów. W rekordzie
umieścić imię i nazwisko typu string. Program ma czytać z klawiatury in-
deks komórki do wypełnienia. Dane wprowadzamy dopóty, dopóki poda-
ny indeks nie przekroczy zakresu indeksów tablicy. Ten sam indeks mo\-
na podawać wielokrotnie. Na koniec program wypisuje wszystkie dane
oraz niszczy zmienne dynamiczne.
Zad. 8.2. Zadeklarować tablicę wskazników do obiektów. Dane: tablica 100 ko-
mórek typu class, w klasie: imię i nazwisko typu string[40] oraz kon-
struktor. Program ma czytać indeks komórki do wypełnienia. Dane poda-
jemy dopóty, dopóki indeks nie przekroczy zakresu indeksów tablicy.
Na koniec program wypisuje wszystkie dane i niszczy obiekty.
Zad. 8.3. Zaprogramować stos liczb zespolonych. W tym celu zdefiniować
wierzchołek St typu PStos oraz PStos = ^TStos; TZesp = record re, im :
Real; end; W programie uwzględnić następujące operacje:
a) wkładanie na stos - procedure push(var St : PStos; D : TZesp);
b) zdejmowanie ze stosu - function pop(var St : PStos) : TZesp;
121
c) dodawanie, odejmowanie i mno\enie pary liczb z wierzchołka stosu -
function dod(a,b : TZesp): TZesp; function odej(a, b : TZesp): TZesp;
W programie głównym zaprogramować obliczanie wyra\enia c := a + (b
* c) w odwrotnej notacji polskiej (ONP).
Zad. 8.4. Zaprogramować stos tak, jak w zad. 8.3, ale u\yć typu class zamiast
record. Zdefiniować klasy: TZesp, TElemSt i TStos. W klasie TZesp,
oprócz konstruktora Create, ustawiającego pola re, im, zdefiniować
dodatkowy constructor dod(a, b : TZesp); i podobnie - odejm i mnoz.
W tych konstruktorach nale\y tak\e wywołać inherited Create; oraz
(na końcu) a.Free; Uwaga: zmienna var c : TZesp; jest wskaznikiem do
obiektu, więc nie trzeba dla niej wywoływać konstruktora, poniewa\ w in-
strukcji c := St.pop; przeka\emy do c adres istniejącego obiektu (stworzo-
nego konstruktorem dod).
Zad. 8.5. Zaprogramować listę jednokierunkową na wskaznikach. Dane: imię,
nazwisko i rok urodzenia. W programie uwzględnić operacje:
a) dodawania osoby, tak \e nazwiska są uło\one rosnąco alfabetycznie,
b) usuwania osoby o określonym nazwisku,
c) wyświetlania całej listy,
d) usuwania całej listy.
Zad. 8.6. Zaprogramować listę jednokierunkową na klasach. W klasie zadekla-
rować: imię, nazwisko i wiek. Uwzględnić operacje:
a) dodawania osoby, tak \e nazwiska są uło\one rosnąco alfabetycznie,
b) usuwania osoby o określonym nazwisku,
c) wyświetlania całej listy,
d) usuwania całej listy.
Zad. 8.7. Zaprogramować kolejkę na klasach. W klasie zadeklarować: imię, na-
zwisko oraz rok studiów. W programie uwzględnić operacje:
a) dodawania elementu na końcu kolejki,
b) usuwania pierwszego elementu kolejki,
c) wyświetlania całej kolejki,
d) usuwanie całej kolejki.
122
9. Klasy
Klasa jest zło\oną strukturą danych, rozbudowaną o funkcje składowe.
Klasa jest wzorcem, tzn. typem dla swoich zmiennych, czyli obiektów. Obiek-
tem nazywamy konkretny egzemplarz danej klasy. Klasę tworzą: dane skła-
dowe (zmienne) oraz funkcje składowe (metody), w tym specjalne metody -
konstruktory i destruktory.
9.1. Zasady deklarowania klasy
Klasę deklaruje się przy u\yciu słowa kluczowego class, mianowicie:
type TNazwaKlasy = class & end;
W dobrym stylu jest nadawanie klasie nazwy z du\ej litery T. Elementy
klasy są podzielone na sekcje: publiczną, prywatną i chronioną. Elementy te
mogą być deklarowane w dowolnej liczbie występujących po sobie sekcji wy-
dzielonych przez etykiety public lub private.
Elementy klasy są domyślnie prywatne. Metod i zmiennych publicz-
nych klasy mo\na u\ywać wszędzie w programie, natomiast do metod i
zmiennych prywatnych dostęp mają tylko inne metody klasy. Ponadto:
" klasa powinna być abstrakcyjnym typem danych, czyli takim, który raz
zaprojektowany mo\e być u\ywany przez innych programistów bez ko-
nieczności wgłębiania się w jego mechanizm,
" sekcja publiczna deklaracji klasy nazywana jest interfejsem klasy. Za-
warte w niej metody, wraz z opisami ich parametrów, działanie i format
wyniku powinny dawać programiście minimum informacji o klasie,
" według zasady abstrakcyjności danych, wszystkie dane składowe klasy
powinny znajdować się w sekcji prywatnej,
" w sekcji publicznej nale\y umieszczać metody operujące na danych
prywatnych,
" jeśli metoda publiczna wykonuje wiele obliczeń lub pewne z nich są
wykonywane przez wiele metod publicznych, to mo\na je wyodrębnić w
postaci osobnej metody prywatnej. Są one wtedy wyłącznie na u\ytek
implementacji klasy.
" implementacja klasy powinna być zaprojektowana przez programistę w
sposób, który daje mu mo\liwość rozbudowania i udoskonalania działa-
123
nia funkcji jej składowych, bez wprowadzania jakichkolwiek zmian w
interfejsie,
" dostęp do danych i funkcji składowych obiektu uzyskuje się przez u\y-
cie operatora dostępu . (kropki).
Najwa\niejsze sekcje w obrębie klasy to:
" public definiuje pola i metody publiczne, które są dostępne z dowol-
nego miejsca programu oraz z modułu, w którym zostały zdefiniowane,
" private definiuje pola i metody prywatne, które są niedostępne spoza
modułu (pliku kodu zródłowego), w którym zdefiniowano klasę,
" protected definiuje pola i metody chronione, które są widoczne tylko
w danej klasie i w klasach pochodnych (tych, które z niej dziedziczą).
9.2. Istota programowania obiektowego
Programowanie obiektowe to programowanie, w którym klasy stanowią
najwa\niejszą część w konstrukcji programu. Podstawowe cechy programowa-
nia obiektowego to:
" dziedziczenie,
" hermetyzacja,
" polimorfizm,
" rozszerzalność.
Dziedziczenie to budowa jednej klasy na bazie drugiej, przez dodawa-
nie/przesłanianie składowych klasy bazowej. Dziedziczenie umo\liwia klasie
pochodnej uzyskanie dostępu do pól i metod swoich klas rodzicielskich. Klasy
potomne dziedziczą charakterystyki i działania klasy rodzic. Klasy pochodne
mogą zmieniać operacje odziedziczone i mogą definiować nowe.
Hermetyzacja (inkapsulacja) to cecha, która pozwala na łączenie danych z
metodami w obrębie obiektu.
Polimorfizm to mo\liwość dzielenia pojedynczego działania i nazwy dzia-
łania poprzez hierarchię obiektową w sposób właściwy dla ka\dego obiektu w
tej hierarchii. Polimorfizm pozwala ka\dej klasie na posiadanie własnych po-
trzeb. Metody te zapewniają jednolitą odpowiedz na komunikaty dochodzące do
\ądanych klas w hierarchii. Polimorfizm umo\liwia obiektom generowanie wła-
snych odpowiedzi na podobne komunikaty.
Rozszerzalność pozwala rozszerzać i uaktualniać skompilowane moduły.
124
Przykład 1. Deklaracja klasy z ró\nymi sekcjami.
Program figura_10;
type
TFigura = class
private
bok : Byte;
podstawa : 1..100;
procedure wypisz;
protected
kat : Integer;
ile_pr : 0..2;
function pole(a,b : Byte) : Integer;
public
nazwa : string;
procedure init;
procedure free;
end;
Przykład 2. Ilustracja definicji metod klasy.
program figura_02;
type
TProstokat = class
a, b : Integer; // wymiary prostokąta
function obwod : Integer;
function pole : Integer;
end;
function TProstokat.obwod : Integer;
begin
Result := (a+b)*2;
end;
function TProstokat.pole : Integer;
begin
Result := a*b;
end;
var Pro1, Pro2 : TProstokat;
BEGIN
Pro1 := Tprostokat.Create;
Pro1.a := 4;
Pro1.b := 2;
125
// Pro2 := Tprostokat.Create;
// pro2.a := 10; pro2.b := 20; // błąd! brak Create!
writeln('obwod= ', Pro1.obwod);
writeln('Pole = ', Pro1.pole);
//writeln('Pole2= ', Pro2.pole);
Pro1.Free;
readln;
END.
Przykład 3. U\ycie dwóch klas w jednym programie.
program figury_03;
type
TProstokat = class
a, b : Integer; // wymiary prostokąta
function obwod : Integer;
function pole : Integer;
end;
function TProstokat.obwod : Integer;
begin
Result := (a+b)*2;
end;
function TProstokat.pole : Integer;
begin
Result := a*b;
end;
type
Trojkat = class
a, b, c : Integer; // wymiary trójkąta
function obwod : Integer;
function pole : Integer;
end;
function Trojkat.obwod : Integer;
begin
Result := a+b+c;
end;
function Trojkat.pole : Integer;
begin
Result := a*(b+c) div 2;
end;
126
var prost1 : TProstokat; // zmienne obiektowe
trojk1 : Trojkat;
BEGIN
prost1 := TProstokat.Create;
// obowiązkowe wywołanie konstruktora
prost1.a := 5;
prost1.b := 7;
writeln('obwod pr= ', prost1.obwod);
writeln('Pole pr = ', prost1.pole);
readln;
trojk1 := Trojkat.Create;
trojk1.a := 2;
trojk1.b := 3;
trojk1.c := 4;
writeln('obwod tr= ', trojk1.obwod);
writeln('Pole tr= ', trojk1.pole);
prost1.Free; // obowiązkowe
trojk1.Free; // obowiązkowe
readln;
END.
Przykład 4. Ilustracja ró\nych sposobów dostępu do pól klasy.
program PrKonstr4;
type
TProstokat = class
a : Integer;
procedure ustaw(_d : Integer);
function wypisz : Integer;
private
d : Integer;
protected
b : Integer;
public
c : Integer;
end;
procedure TProstokat.ustaw(_d : Integer);
begin
d := _d;
end;
function TProstokat.wypisz : Integer;
begin
127
Result := d;
end;
var Pro1 : TProstokat;
BEGIN // PROGRAM GAÓWNY
Pro1 := Tprostokat.Create;
Pro1.a := 4;
Pro1.b := 2;// w module jest dostęp do pola protected
Pro1.c := 50;
// Pro1.d := 1; // brak dostępu do pola prywatnego
writeln('a = ', Pro1.a);
writeln('b = ', Pro1.b);
writeln('c = ', Pro1.c);
Pro1.ustaw(100);
writeln('d = ', Pro1.Wypisz);
readln;
Pro1.Free
END.
9.3. Konstruktor i destruktor
" konstruktor Create tworzy obiekt (przydziela wymaganą pamięć),
" destruktor Free zwalnia przydzieloną pamięć,
" klasa mo\e zawierać więcej ni\ jeden konstruktor, a powinna zawierać
jeden destruktor,
" konstruktor powinien być wywołany jako pierwsza metoda dla nowego
obiektu.
" wywołanie destruktora kończy pracę z obiektem; aby ponownie korzy-
stać z obiektu, nale\y ponownie wywołać konstruktor,
" u\ycie konstruktora Create jest obowiązkowe dla ka\dego egzemplarza
klasy (przydziela wymaganą pamięć), np.
TABC = class & end;
&
var ob1 : TABC;
ob1 := TABC.Create;
" u\ycie destruktor Free jest obowiązkowe dla ka\dego egzemplarza kla-
sy (zwalnia przydzieloną pamięć), np. ob1.Free;
Przykład 1. U\ycie konstruktora i destruktora.
program Pr_Konstructor1;
type TProst = class
x, y : Integer;
a, b : Integer; {boki}
128
constructor Create;
destructor Free;
function pole : Longint;
end;
constructor TProst.Create;
begin // Inherited Create;
a := 20; b := 4;
end;
destructor Tprost.Free;
begin // Inherited Free;{ koniec pracy z TKwadrat }
end;
function TProst.pole : Longint;
begin
Result := a*b;
end;
var Pkat : TProst;
BEGIN
Pkat := Tprost.Create;
writeln('Pole prostokata: ', Pkat.pole);
Pkat.a := 100;
writeln('Pole prostokata: ', Pkat.pole);
Pkat.Free;
readln;
END.
Przykład 2. U\ycie destruktora i konstruktora.
program pr_konstruktor2;
type TProst = class
a, b : Integer; {boki prostokąta}
constructor Create(_a,_b : Integer);
destructor Destroy;
function pole : Longint;
end;
constructor TProst.Create(_a, _b : Integer);
begin
Inherited Create; // nieobowiązkowe
a := _a;
b := _b;
writeln('Działa konstruktor TProst');
129
end;
destructor TProst.Destroy;
begin
writeln('Destruktor - Koniec pracy z TProst');
Inherited Destroy;
end;
function TProst.pole : Longint;
begin
pole := a*b;
end;
var p1, p2 : TProst;
BEGIN
p1 := Tprost.Create(20,30);
p2 := Tprost.Create(4,7);
writeln('Pole prostokata : ', P1.pole);
p1.Destroy; // Pkat.Free;
p2.Destroy;
readln;
END.
9.4. Typy konstruktorów
Je\eli nie zostanie zdefiniowany przez u\ytkownika w klasie \aden kon-
struktor, wówczas kompilator wykorzysta konstruktor klasy TObject, która jest
nadrzędna dla wszystkich klas.
Wyró\niamy trzy rodzaje konstruktorów:
" domyślny (bez parametrów),
" zwykły (z parametrami) - mo\e być wiele takich konstruktorów,
" kopiujący.
Jeśli zadeklarujemy jakikolwiek konstruktor pobierający parametry, wtedy
musimy tak\e zadeklarować konstruktor domyślny (bezparametrowy). Przy de-
finicji konstruktora domyślnego wskazane jest inicjalizowanie danych.
Przykład. 1. Program pokazuje u\ycie kilku konstruktorów w jednej klasie.
Program konstruktory;
type
TPot = class
private
130
x, y : Integer;
public
constructor Create; overload; //domyślny
constructor Create(dx, dy : Integer); overload;
//zwykły
constructor Create(const dxy : TPot); overload;
//kopiujący
end;
Przykład 2. U\ycie konstruktora zwykłego i domyślnego.
program Pr_Konstruktor3;
uses SysUtils;
type
TFig = class
bok : ^Byte; // konstruktor zwykły
constructor Create(x : Byte); overload;
constructor Create; overload;
destructor Free; // ę!konstruktor domyślny
procedure wypisz;
function pole : Integer;
end;
constructor TFig.Create;
begin
New(bok);
end;
constructor TFig.Create(x : Byte);
begin
Create;
bok^ := x;
end;
destructor TFig.Free;
begin
Dispose(bok);
end;
procedure TFig.wypisz;
begin
writeln('bok = ', bok^);
end;
function Tfig.pole : Integer;
131
begin
Result := bok^ * bok^;
end;
var kwadrat : TFig;
BEGIN
kwadrat := TFig.Create(5);
kwadrat.wypisz;
kriteln('Pole kwadratu=', kwadrat.pole)
kwadrat.Destroy;
END.
Przykład 3. U\ycie konstruktora domyślnego, zwykłego i kopiującego.
Program Pr_Konstruktor4;
type
TPot = class
private
x, y : Integer;
public
constructor Create; overload; //!domyślny // zwykły
constructor Create(dx, dy : Integer); overload;
constructor Create(const DK : TPot); overload;
end; //ę!kopiujący
constructor TPot.Create;
begin
inherited Create;
x := 1; y := 0;
end;
constructor TPot.Create(dx, dy : Integer);
begin
inherited Create;
x := dx;
y := dy;
end;
constructor TPot.Create(const DK : TPot);
begin
inherited Create;
x := DK.x;
y := DK.y;
end;
132
var a, b, c, d : TPot;
BEGIN
a := TPot.Create;
b := TPot.Create( 7,13 );
c := TPot.Create( b );
// tworzenie obiektu przez kopiowanie obiektu b
// d := TPot.Create;
d := b; // d jest tylko wskaznikiem na obiekt b
writeln(' a: ',a.x,' ',a.y);
writeln(' b: ',b.x,' ',b.y);
writeln(' c ',c.x,' ',c.y);
writeln(' d ',d.x,' ',d.y);
a.Free;
b.Free;
c.Free;
// d.Free; // d pokazuje na to samo, co b (d=b)
readln
END.
9.5. Dziedziczenie
W dziedziczeniu nowy typ klasy tworzony jest na bazie istniejącego typu.
Nowo zdefiniowany typ klasy dziedziczy wszystkie pola i metody bez koniecz-
ności ich definiowania. Typ, od którego dziedziczona jest struktura, nazywa się
typem rodzicem (ang. ancestor type) lub typem bazowym. Typ tworzony od ty-
pu bazowego to typ potomny (ang. descendant type) lub typ pochodny. Dziedzi-
czenie mo\e być pośrednie albo bezpośrednie.
Dziedziczenie metod mo\e być dwojakiego rodzaju:
" bez przesłaniania - deklaracje metod w typie przodka i potomka zawie-
rają inne identyfikatory,
" z przesłanianiem metod - deklaracja metody w typie potomnym ma taki
sam identyfikator, jak metoda w typie bazowym. Wówczas metoda w
typie potomnym przesłania (redefiniuje) metodę odziedziczoną od typu
przodka.
Pola klasy nie mogą być przesłaniane, tzn. \aden typ potomny nie mo\e
posiadać pól o identyfikatorach występujących w typie bazowym. Przesłonięcie
metody statycznej dopuszcza zmiany nagłówka w dowolny sposób. Nowa meto-
da mo\e mieć zarówno inne parametry jak i ró\ną treść.
Procedura nadająca wartości polom w typie pochodnym mo\e wykorzysty-
wać procedurę nadającą wartości polom typu bazowego i określać tylko dodat-
kowe pola typu pochodnego.
133
Przykład. Aby program działał prawidłowo, nale\y klasę potomka uzupełnić
o metodę wykonaj, o identycznej treści jak w klasie TRodzic.
Program dziedicz_1;
type TRodzic = class
procedure napisz;
end;
procedure TRodzic.napisz;
begin
writeln('to napisał Rodzic');
end;
type TPotomek = class(TRodzic)
procedure napisz;
end;
procedure TPotomek.napisz;
begin
Inherited napisz;
writeln('To dopisało \ycie (Potomek)');
//writeln('A to dopisało \ycie (Potomek)');
end;
var potomek : TPotomek;
BEGIN
Potomek := TPotomek.Create;
Potomek.napisz;
readln;
Potomek.Free;
END.
9.6. Polimorfizm
Polimorfizm (wielopostaciowość) jest mechanizmem, dzięki któremu ta
sama metoda (o tej samej nazwie (i parametrach)) mo\e mieć ró\ne skutki dzia-
łania (ró\ne znaczenie) w zale\ności od tego, jaki obiekt ją wywołuje.
Dziedziczenie homomorficzne zachodzi wtedy, gdy połączenia miedzy me-
todami obiektów są wykonywane w trakcie kompilacji programu.
Dziedziczenie polimorficzne polega na korzystaniu z tablic metod wirtual-
nych (VMT) dla ka\dej klasy, w której są adresy metod w danej klasie.
Wzajemne wywoływanie metod odbywa się za pośrednictwem adresów zawar-
134
tych w tablicach VMT, a te są ró\ne dla klasy przodka i potomka. Metoda w kla-
sie przodka prawidłowo rozpozna wywoływaną metodę równie\ w klasie po-
tomka.
W dziedziczeniu polimorficznym nale\y u\ywać metod wirtualnych,
tj. w nagłówku metody przodka trzeba wpisać dyrektywę virtual, np. procedu-
re metoda; virtual; ale w deklaracji nagłówka metody potomka nale\y u\yć dy-
rektywy override, np. procedure metoda; override;
Przykłady dziedziczenia:
Dziedziczenie homomorficzne Dziedziczenie polimorficzne
program dziedzicz_homomorf; program dziedzicz_polimorf;
type TRodzic = class type TRodzic = class
procedure napisz; procedure napisz; virtual;
procedure wykonaj; procedure wykonaj;
end; end;
procedure TRodzic.napisz; procedure TRodzic.napisz;
begin begin
writeln('To napisał >> Rodzic'); writeln('To napisał >> Rodzic');
end; end;
procedure TRodzic.wykonaj; procedure TRodzic.wykonaj;
begin begin
napisz; napisz;
end; end;
type TPotomek = class(TRodzic) type TPotomek = class(TRodzic)
procedure napisz; procedure napisz; override;
end; end;
procedure TPotomek.napisz; procedure TPotomek.napisz;
begin begin
writeln('to napisał > Potomek' ); writeln('to napisał > Potomek' );
end; end;
var potomek :TPotomek; var potomek :TPotomek;
BEGIN BEGIN
Potomek:= TPotomek.Create; Potomek:= TPotomek.Create;
Potomek.wykonaj; Potomek.wykonaj;
Readln; Readln;
Potomek.Free; Potomek.Free;
END. END.
135
9.7. Metody wirtualne
W Lazarus FPC metody wirtualne mogą być równie\ pokrywane metodami
statycznymi albo innymi metodami wirtualnymi. Z tego powodu, przy dziedzi-
czeniu polimorficznym, konieczne jest u\ywanie dyrektywy override w klasie
potomka. Je\eli metodę wirtualną zakrywamy metodą statyczną (brak override),
rezygnujemy z polimorfizmu i kompilator wyświetla ostrze\enie, dlatego dyrek-
tywę override nale\y zastąpić dyrektywą reintroduce.
W dziedziczeniu polimorficznym nale\y u\ywać metod wirtualnych.
W nagłówku metody przodka nale\y u\yć dyrektywy virtual, np.:
procedure metoda; virtual;
W deklaracji nagłówka metody potomka nale\y jednak u\yć override, np.:
procedure metoda; override;
Przykład 1. Ilustracja u\ycia dyrektywy reintroduce.
program dziedzicz_reintro;
type TRodzic = class
procedure dodaj(x : Byte); virtual;
end;
procedure TRodzic.dodaj(x : Byte);
begin
writeln(x+10);
end;
type TPotomek = class(TRodzic)
procedure dodaj(x : Byte); reintroduce;
end;
procedure TPotomek.dodaj(x : Byte);
begin
writeln(x, ' + dziesiec');
end;
var potomek : TPotomek;
BEGIN
Potomek := TPotomek.Create;
Potomek.dodaj(1);
readln;
Potomek.Free;
END.
136
Przykład 2. Dziedziczenie na zasadzie rodzic potomek wnuk.
program dziedziczenie_wnuk;
type TRodzic = class
procedure dodaj(x : Byte); virtual;
procedure wykonaj(b : Byte);
end;
procedure TRodzic.dodaj(x : Byte);
begin
writeln(x+10);
end;
procedure TRodzic.wykonaj(b : Byte);
begin
dodaj(b);
end;
type TPotomek = class(TRodzic)
procedure dodaj(x : Byte); override;
end;
procedure TPotomek.dodaj(x : Byte);
begin
writeln(x,' + sto');
end;
type TWnuk = class(TPotomek)
procedure dodaj(x : Byte); override;
end;
procedure TWnuk.dodaj(x : Byte);
begin
writeln(x,' plus 1000');
end;
var Pot : TRodzic;
BEGIN
Pot := TRodzic.Create;
Pot.wykonaj(1);
Pot.Free;
Pot := TPotomek.Create;
Pot.wykonaj(1);
Pot.Free;
137
Pot := TWnuk.Create;
Pot.wykonaj(1);
Pot.Free;
readln;
END.
9.8. Metody dynamiczne
Dziedziczenie polimorficzne zapewniają:
" metody wirtualne deklarowane słowem virtual,
" metody dynamiczne deklarowane słowem dynamic.
Metody te działają tak samo, ale ró\nią się sposobem optymalizacji. Meto-
da dynamiczna daje mniejszy rozmiar kodu wynikowego programu po kompila-
cji, natomiast metoda wirtualna powoduje, \e program po kompilacji działa
szybciej ni\ dla metody dynamicznej. Z polimorfizmem mamy do czynienia
równie\ w przypadku wywołania metod z parametrem aktualnym typu potom-
nego względem typu parametru formalnego.
Przykład. Dziedziczenie na zasadzie Fig(Figura) Pr(Prostokąt).
program dziedzicz_dyn;
type TFig = class
x, y : Integer;
nazwa: string[20];
constructor Create;
destructor Destroy;
procedure narysuj;
procedure zmaz;
end;
constructor TFig.Create;
begin
inherited Create;
x := 1; y := 2;
end;
destructor TFig.Destroy;
begin zmaz;
inherited Destroy;
end;
138
procedure TFig.narysuj;
begin
writeln('Narysowano figurę x=',x,' y=',y);
end;
procedure TFig.zmaz;
begin
writeln('Zmazano figure x=',x,' y=',y,' ',nazwa);
end;
// klasy dziedziczące z TFig
type TProst = class(TFig)
a, b : Integer;
constructor Create(a_, b_: Integer; name : string);
procedure narysuj;
procedure zmaz_np;
procedure przesun(x1,y1 : Integer);
end;
constructor TProst.Create(a_,b_:Integer; name :string);
begin
inherited Create;
a := a_; b := b_;
nazwa:=name;
end;
procedure TProst.narysuj;
begin
write('+ Rysowanie prostokąta x=',x,' y=',y);
writeln('+ o rozmiarach a=',a,' b=',b);
end;
procedure TProst.zmaz_np;
begin
writeln('Zmazano prostokąt x=',x, 'y=',y, ' ',nazwa);
end;
procedure TProst.przesun(x1,y1 : Integer);
begin
zmaz; // zmaz_np;
x := x1; y := y1;
narysuj;
end;
139
var Fig : TFig;
Pr1 : TProst;
BEGIN
Fig := TFig.Create;
Fig.narysuj;
Fig.Destroy; // Fig.Free;
Fig := TProst.Create(2,2,'Kwadrat');
Fig.narysuj; // Fig.przesun(10, 10);
Fig.Destroy;
Pr1 := Tprost.Create(10,15,'Prost 1');
Pr1.narysuj;
Pr1.przesun(100, 100);
Pr1.zmaz_np;
Pr1.zmaz;
Pr1.Free;
readln;
END.
140
10. Programowanie obiektowe - aplikacje
LCL jest biblioteką wizualnych komponentów, stworzoną w języku Object
Pascal, na potrzeby środowiska Lazarus. Biblioteka LCL nale\y do jednych z
bardziej przejrzystych i dobrze zaprojektowanych bibliotek wspomagających
programowanie w środowisku Lazarus, zwłaszcza tworzenie interfejsu u\yt-
kownika. Podstawą biblioteki LCL jest klasa bazowa TClass.
VCL jest biblioteką wizualnych komponentów, stworzoną w języku Object
Pascal przez firmę Borland, na potrzeby środowiska Delphi, a pózniej zaadap-
towaną równie\ do środowiska C++ Builder. Obecnie biblioteka VCL integruje
w sobie dodatkowo mo\liwość korzystania z technologii .NET firmy Microsoft.
Podstawą biblioteki VCL jest klasa bazowa TObject. Z niej dziedziczą wszystkie
pozostałe klasy biblioteki.
VCL to skrót od ang. Visual Component Library (Biblioteka Wizualnych
Komponentów). Jest jednym z najwa\niejszych elementów Delphi. To właśnie
niej zawdzięczamy łatwość i szybkość projektowania aplikacji. Biblioteka VCL
składa się z gotowych, napisanych wcześniej, komponentów, które podczas
kompilacji są dołączane do wykonywalnego pliku naszego programu. Po skom-
pilowaniu program nie wymaga \adnych dodatkowych plików. Mo\e być uru-
chamiany na dowolnym komputerze, bez względu na rodzaj zastosowanych
komponentów.
Standardowe komponenty VCL/LCL nadają się do większości zastosowań.
Gdybyśmy jednak potrzebowali jakichś specjalnych funkcji, mo\emy doinsta-
lować dodatkowe komponenty (w sieci jest ich bardzo wiele) lub napisać wła-
sny. Zbiór komponentów to zbiór elementów, z których poprzez odpowiednich
ich dobór, mo\na w krótkim czasie zbudować aplikację. Komponenty posiadają
określone metody, właściwości oraz zdarzenia. Te pojęcia określają sposób
komunikowania się komponentu z otoczeniem.
10.1. Komponenty LCL
Środowisko Lazarus zawiera zbiór podstawowych komponentów wykorzy-
stywanych do tworzenia aplikacji. Są to przyciski, okienka, napisy, suwaki, pola
wyboru, menu, tabelki itp. Komponenty zgrupowane są w kilku zakładkach.
Istnieje mo\liwość dodania nowych komponentów i stworzenia własnych. Kom-
ponenty LCL znajdują się na pasku, pod listwą Menu (rys. 10.1).
141
Rys. 10.1. Widok paska menu środowiska Lazarus
Grupa Standard zawiera najczęściej wykorzystywane komponenty. Słu\ą one
do projektowania wyglądu aplikacji. Oto jej składniki:
TFrames - Ramki (frames) mają podobne
właściwości jak formularze (forms), z tym
wyjątkiem, \e ramka mo\e być osadzona
wewnątrz formy. Wprowadzenie ramek
bardzo ułatwiło projektowanie wyglądu
niektórych aplikacji.
TMainMenu - główne menu danego for-
mularza.
TPopUpMenu - menu wyświetlane po
kliknięciu prawym przyciskiem myszki na
danym obiekcie.
TLabel (etykieta) pole słu\ące do wy-
świetlania tekstu.
TEdit - pole słu\ące do edycji jednego
wiersza tekstu.
TMemo - pole tekstowe z mo\liwością
edycji, odczytu i zapisu wyświetlanego
tekstu.
TButton przycisk.
TCheckBox - pole wyboru.
TRadioButton - pole wyboru jednej z kil-
ku opcji.
TListBox - wyświetla listę elementów.
TComboBox - wyświetla listę elementów
z mo\liwością wpisania tekstu.
TScrollBar - pasek przewijania.
Rys. 10.2. Widok okna komponentów
zakładka Standard
142
TGroupBox - grupuje kilka komponentów np. typu RadioButton lub CheckBox.
TRadioGroup - grupuje komponenty RadioButton.
TPanel - komponent ogólny w kształcie prostokąta, w którym mo\na umiesz-
czać inne komponenty.
ActionList - komponent pozwalający na dodawanie własnych procedur obsługi
do niektórych akcji wykonywanych przez u\ytkownika.
Grupa Additional zawiera uzupełniające kom-
ponenty, kształtujące wygląd naszej aplikacji
oraz ułatwiające komunikację z u\ytkownikiem.
TBitBtn przycisk, na którym umieszczony jest
rysunek (Ikona).
TSpeedButton - przycisk umieszczany w pasku
narzędzi.
TMaskEdit - pole edycji pozwalające na filtro-
wanie i formatowanie danych wprowadzanych
przez u\ytkownika.
TStringGrid arkusz, którego elementami są
łańcuchy znaków.
TDrawGrid - arkusz przeznaczony do wprowa-
dzania danych innych ni\ tekstowe.
TImage - komponent wyświetlający grafikę.
TShape - figura geometryczna.
TBevel - tworzy wypukłe lub wklęsłe linie, pro-
stokąty, lub ramki.
TScrollBox - przewijane okienko mogące za-
wierać inne komponenty.
TCheckListBox - przewijana lista z mo\liwością
zaznaczenia poszczególnych pozycji.
TSplitter - słu\y do przesuwania części okienka.
TStaticText tekst statyczny, komponent dzia-
łający podobnie jak Label.
TControlBar - pasek narzędzi z mo\liwością
przestawiania poszczególnych pozycji.
ApplicationEvents - niewizualny komponent
umo\liwiający obsługę globalnych zdarzeń
aplikacji.
TLabeledEdit - pole edycyjne z tekstem opisu.
TColorBox - okno kolorów systemowych.
Rys. 10.3. Widok okna komponentów
zakładka Additional
143
Grupa System
Na tej karcie znajdują się komponenty korzystające bezpośrednio z funkcji
systemowych, np. TTimer wywołuje zadaną procedurę w określonych odstępach
czasu.
Grupa Dialogs zawiera komponenty wywo-
łujące ró\nego typu okienka dialogowe:
TOpenDialog - okienko otwierania pliku.
TSaveDialog - okienko zapisywania pliku.
TOpenPictureDialog - okienko otwierania
pliku z podglądem graficznym.
TSavePictureDialog - okienko zapisywania
pliku z podglądem graficznym.
TFontDialog - okienko wyboru czcionki.
TColorDialog - okienko wyboru koloru.
TPrintDialog - okienko drukowania.
TPrinterSetupDialog - okienko ustawień dru-
karki.
TFindDialog - okienko obsługujące procedu-
ry przeszukiwania.
TReplaceDialog - okienko obsługujące pro-
cedury zamiany zadanej frazy.
TPageSetupDialog - okienko ustawień strony.
Rys. 10.4. Widok okna komponentów
zakładka Dialogs
10.2. Inspektor obiektów
Inspektor obiektów pozwala zobaczyć wszystkie komponenty u\yte w
programie. Pokazuje jednocześnie jak są one rozmieszczone na formie. Ułatwia
szybkie przełączanie się między nimi. Ka\dy obiekt widoczny w Inspektorze
obiektów ma wyświetloną nazwę i klasę zródłową. Znajdują się w nim równie\
cztery sekcje: Właściwości, Zdarzenia, Ulubione, Ograniczenia.
Sekcja Właściwości zawiera właściwości u\ywanych obiektów. Wiele war-
tości jest ustalanych domyślnie w trakcie inicjalizacji komponentu. Wartości
domyślne mo\na zmieniać w trakcie projektowania aplikacji lub w kodzie.
144
W sekcji Zdarzenia mo\na generować podprogramy, które reagują na okre-
ślone zdarzenia związane z danym komponentem. Zakładka ta składa się z
dwóch kolumn: lewa zawiera nazwy poszczególnych zdarzeń, a prawa - proce-
dury przypisane do nich. Na przykład, zdarzenie OnClick odpowiada sytuacji,
gdy dany komponent zostanie kliknięty myszką, a przypisany podprogram wy-
kona określoną operację.
10.3. Okno komunikatów i okno Form
Komunikaty to opisy, uwagi, ostrze\enia i błędy generowane przez kompi-
lator Lazarus. Pokazywane są w oknie. Komunikaty stanowią podstawowe zró-
dło informacji o problemach uniemo\liwiających uruchomienie programu.
Rys. 10.5. Widok okna komunikatów
Okno Form:
" okno graficzne, na którym rozmieszcza się komponenty aplikacji,
" obsługuje tryb WYSIWYG (ang. What You See Is What You Get),
" umo\liwia zmianę właściwości danego komponentu oraz wygenerowa-
nie kodu obsługi zdarzenia OnClick poprzez kliknięcie na nim.
10.4. Edytor zródeł
Edytor zródeł umo\liwia podgląd oraz edycję kodu zródłowego tworzone-
go programu. Wa\ne fragmenty kodu są wyró\nione kolorem lub tłustym dru-
kiem. Domyślnie są uwzględniane wcięcia w kodzie.
Pliki tworzone przez edytor zródeł to:
" Plik główny projektu z rozszerzeniem .LPR - plik tekstowy, który za-
wiera informacje o formularzach i modułach aplikacji. Znajduje się tam
równie\ kod, który inicjalizuje aplikację.
" Plik projektu z rozszerzeniem .LPI - plik tekstowy, który zawiera in-
formacje o konfiguracji środowiska Lazarus w formacie xml.
145
" Plik modułu z rozszerzeniem .PAS - plik tekstowy zawierający kod
zródłowy modułu w języku Object Pascal. Mo\e być stowarzyszony z
formularzem lub stanowić samodzielny składnik projektu.
" Plik formularza z rozszerzeniem .LFM - plik binarny zawierający defi-
nicję formularza. Ka\dy taki plik powiązany jest z modułem (plik
.PAS), zawierającym program zródłowy związany z obsługą formularza.
" Plik zasobów z rozszerzeniem .RES - binarny plik zasobów projektu.
Rys. 10.6. Widok okna Edytora zródeł
W efekcie kompilacji tworzony jest wykonywalny plik wynikowy (pro-
gram) z rozszerzeniem .EXE. Jest to plik wygenerowany przez projekt. Tworzo-
ne są tak\e pliki modułów z rozszerzeniem .PPU. Zawierają one skompilowane
wersje modułów zawartych w projekcie.
Ka\dy program składa się z wielu plików, które mają takie same nazwy, ale
ró\ne rozszerzenia. Wysoce zalecane jest przechowywanie (zapisywanie) pli-
ków ka\dego projektu w oddzielnym folderze.
146
10.5. Obiekt Button1 klasy TButton
Rys. 10.7. Widok komponentu Button1
Umieszczenie na formie komponentu Button z palety Standard powoduje
pojawienie się na formie prostokątnego przycisku z napisem Button1.
Zaznaczenie go pojedynczym kliknięciem pozwala w okienku Inspektora
obiektów zapoznać się z jego właściwościami oraz zdarzeniami.
Rys. 10.8. Widok okna Inspektora obiektów z komponentem Button1 klasy TButton
147
" W oknie Inspektora obiektów są widoczne właściwości komponentu
Button1. Zmiana właściwości powoduje m.in. zmianę jego poło\enia,
rozmiaru, koloru, nazwy i wielu innych parametrów.
" Właściwości przycisku mo\na zmieniać bezpośrednio z poziomu In-
spektora obiektów (w czasie projektowania aplikacji) albo programo-
wo, za pomocą odpowiednich instrukcji w kodzie zródłowym aplikacji.
Przykładowe polecenie zmieniające właściwość Caption komponentu
Button1: Button1.Caption := 'Nowy tekst nagłówka';
Zmiana dowolnej właściwości jest natychmiast aktualizowana. W pewnych
przypadkach niektóre właściwości są tylko do odczytu (ang. read only), znaczy
to \e nie mo\emy danej właściwości zmienić - mo\emy ją jednak odczytać.
Pełną listę właściwości znajdziemy w systemie pomocy. Po zaznaczeniu
komponentu i naciśnięciu klawisza F1 wyświetli się okienko z opisem dostęp-
nych metod, właściwości i zdarzeń.
W oknie Inspektora obiektów znajduje się sekcja Zdarzenia (ang.
Events). Ka\dy komponent posiada swój zbiór zdarzeń, które mo\emy oprogra-
mować. Nazwa ka\dego zdarzenia składa się z przedrostka On i tekstu informu-
jącego, czego dotyczy dane zdarzenie.
Zdarzenia mają za zadanie sprawdzać, czy nie wystąpiła określona czyn-
ność i w przypadku jej wystąpienia wywołać odpowiadającą jej procedurę. Kli-
kając dwukrotnie na pole obok nazwy wybranego zdarzenia przechodzimy do
edycji kodu procedury jego obsługi.
Najczęściej u\ywanym zdarzeniem jest zdarzenie OnClick reagujące na
kliknięcie myszką na danym komponencie.
Inny dostęp do zdarzenia uzyskuje się po dwukrotnym kliknięciu na kom-
ponent. Oprócz właściwości i zdarzeń, komponenty posiadają równie\ metody.
Są one widoczne w Inspektorze obiektów, w zakładce Właściwości. Metody są
to funkcje i procedury, które wykonuję na komponencie określone operacje.
Przykładowo, metodę powodującą \e przycisk staje się niewidoczny wywołuje-
my następującym poleceniem: Button1.Hide;
Tabela 10.1. Lista wybranych właściwości klasy TControl
widocznych w oknie Inspektora obiektów
Nazwa Opis
Align Określa domyślne poło\enie komponentu
AutoSize Automatyczne utrzymywanie pierwotnego rozmiaru komponentu
148
Caption Napis na komponencie (tekst)
Height Wysokość komponentu
Width Szerokość komponentu
Color Kolor tła obiektu
Kursor wyświetlany po umieszczeniu wskaznika myszy nad
Cursor
obiektem
Enabled Określa, czy obiekt jest aktywny czy te\ nie
Font Czcionka u\ywana przez komponent
Wskazówka (etykietka podpowiedzi), pokazywana po umiesz-
Hint
czeniu kursora nad obiektem
Name Nazwa komponentu
Właściwość określająca, czy komponent ma być widoczny pod-
Visible
czas działania programu
Tabela 10.2. Lista wybranych zdarzeń TControl
widoczna w oknie Inspektora obiektów
Nazwa Opis
OnClick Aplikacja podejmuje działanie po kliknięciu myszką na danym
komponencie
OnClose Zdarzenie związane z zamykaniem okna
Zdarzenie generowane jest w momencie, gdy w komponencie na-
OnDragDrop
stępuje upuszczenie danych przeciąganych metodą drag and
drop
OnKeyDown Akcja po naciśnięciu dowolnego klawisza
OnKeyPress Akcja po przytrzymaniu dowolnego klawisza
OnMouseDown Akcja po kliknięciu w obszarze komponentu
OnMouseMove Akcja po ruchu kursora myszki na komponentem
OnEnter Akcja na wejście do komponentu
OnExit Akcja po wyjściu z komponentu
149
10.6. Aplikacja Stoper
Zbudujemy stoper elektroniczny na bazie komponentu TTimer:
" Stoper1: w tej wersji wykorzystane zostaną komponenty TTimer oraz
TLabel. Stoper będzie zliczał sekundy i minuty od chwili 00:00.
" Stoper2: zmodyfikujemy Stoper1 tak, aby zliczanie sekund następowało
co 200ms, oraz zbudujemy funkcję wyświetlania czasu, która minuty i
sekundy zawsze wyświetli dwucyfrowo.
" Stoper3: wykorzystamy komponenty TTimer, TLabel oraz TButton
(x3); przycisk Start (Button1) uruchomi stoper, przycisk Stop (Button2)
zatrzyma stoper, przycisk Koniec (Button3) zamknie aplikację.
" Stoper4: wykorzystamy komponenty TPanel (x2), TTimer, TButton
(x4) i TLabel; przycisk Reset uruchomi stoper od nowa, przycisk Stop
zatrzyma stoper, Start ponowi zliczanie sekund, przycisk Koniec za-
mknie aplikację. Cyfry stopera umieścimy w TLabel, na Panel1 uło\y-
my wszystkie przyciski, na Panel2 umieścimy stoper.
Aplikacja Stoper1:
1. Otworzyć aplikację VCL.
2. Wstawić komponenty typu TLabel i TTimer.
3. W polu Caption formularza Form1 wpisać Stoper1 , zmienić nazwę kompo-
nentu Label1 na Label00_00 (zmiana w polu Name).
4. W polu Caption komponentu Label00_00 wpisać 00:00, w polu Font ustawić
rozmiar czcionki na 48, zmienić kolor (np. na niebieski).
5. Wygenerować procedurę Timer1Timer klikając dwukrotnie na ikonie
komponentu Timer1 (w Inspektorze obiektów, w zakładce Zdarzenia, zo-
staje włączona metoda i dołączona do kodu klasy Form1).
6. W procedurze Timer1Timer wpisać poni\szy kod zliczania sekund i minut.
// procedura Timer1Timer, która inkrementuje zmienną sec
// co 1000 ms, czyli co sekundę i zlicza te\ minuty,
// procedura Timer1Timer pochodzi od obiektu Timer1
procedure TForm1.Timer1Timer(Sender: TObject);
begin
inc(sek); //zliczamy co 1000ms
if sek = 60 then
begin
sek := 0;
inc(min);
150
end;
Label00_00.Caption := IntToStr(min)+':'+IntToStr(sek);
end;
Rys. 10.9. Widok środowiska Lazarus budowa aplikacji Stoper1
Komponenty graficzne opisane są w pliku tekstowym Unit1.pas.
Kod aplikacji Stoper1 (formularza Form1) znajduje się w module Unit1.pas:
unit Unit1;
{$mode objfpc}{$H+}
Interface
uses Classes, SysUtils, FileUtil, LResources, Forms,
Controls, Graphics, Dialogs, StdCtrls, ExtCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
Label_00: TLabel;
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
private { private declarations }
public { public declarations }
151
min, sek: Integer;
end;
var Form1: TForm1;
implementation
{ TForm1 }
procedure TForm1.Timer1Timer(Sender: TObject);
begin
inc(sek); // tu będziemy co 1000ms
if sek >= 60 then
begin
sek := 0;
inc(min);
end;
Label_00.Caption := IntToStr(min)+':'+IntToStr(sek);
end;
initialization
{$I unit1.lrs}
end.
Plik główny aplikacji, czyli stoper1.lpr ma postać:
program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms, Unit1, LResources
{$IFDEF WINDOWS}{$R project1.rc}{$ENDIF}
BEGIN
{$I stoper1.lrs}
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
END.
152
Aplikacja Stoper3:
Rys. 10.10. Widok formularza i aplikacji Stoper3
Zmienne sek i min definiujemy jako składniki klasy Form1.
TForm1 = class(TForm)
&
public { Public declarations }
min, sek : Integer;
Treść procedury dla przycisku Start:
procedure TForm1.Button1Click(Sender: TObject);
begin
sek:=0; min:=0; //zerowanie zmiennych
Label1.Caption := '00:00'; //wyzerowanie stopera
Timer1.Enabled := True; //włączenie stopera
end;
Treść procedury dla przycisku Stop:
procedure TForm1.Button2Click(Sender: TObject);
begin
Timer1.Enabled := False; // zatrzymanie stopera
end;
Treść procedury dla przycisku Koniec:
procedure TForm1.Button3Click(Sender: TObject);
begin
Close; // zamknięcie aplikacji
end;
Treść procedury dla komponentu Timer1: //zliczanie czasu stopera3
procedure TForm1.Timer1Timer(Sender: TObject);
begin
153
//tu będziemy co 1000ms
inc(sek); //zlicza sekundy
if sek >= 60 then
Begin
sek := 0;
inc(min); //zlicza minuty
end;
Label1.Caption := IntToStr(min)+ ':'+IntToStr(sek);
end;
10.7. Aplikacja Kalkulator
Zbudować czterodziałaniowy kalkulator arytmetyczny na liczbach całkowi-
tych (lub rzeczywistych).
Zało\enia:
" dane (argumenty) będą wpisywane w dwóch oknach tekstowych,
" wynik będzie umieszczany w (trzecim) oknie tekstowym,
" działania arytmetyczne + * / będą wywoływane przyciskami TButton,
" dodatkowe dwa przyciski: C wyczyści wynik; Zamknij (lub Koniec)
zamknie aplikację.
Rys. 10.11. Widok formularza i aplikacji kalkulatora 4-działaniowego
Wszystkim komponentom nale\y wpisać nazwy odpowiednie do ich funk-
cji. Trzeba tego dokonać w oknie Inspektora obiektów, w polu Name po klik-
nięciu wybranego obiektu. Przy okazji zmienić napisy na komponentach w po-
lach Caption oraz wielkość czcionki w polach Font (na 20 pkt.).
154
Tabela 10.3. Ustawienia komponentów w aplikacji Kalkulator
Name Caption Component
Label1_a a Label1
Label2_b b Label2
Label3_wyn Wynik Label3
B_Dodaj + Button1
B_Odejm Button2
-
B_Mnoz * Button3
B_Dziel / Button4
B_Czysc C Button5
B_Koniec Koniec Button6
Panel1 Kalkulator Panel1
Name Text Component
x1 znak pusty Edit1
x2 znak pusty Edit2
Wynik znak pusty Edit3
Poprzez podwójne kliknięcie lewym przyciskiem myszy wygenerować pro-
cedury dla ka\dego przycisku. Zmienne dla argumentów zdefiniować jako
zmienne globalne modułu lub pola w klasie Form1.
var a, b, c : Integer; // zmienne modułu unit1.pas
public
a, b, c : Integer; // pola klasy Form1.
procedure TForm1.B_DodajClick(Sender: TObject);
begin //zamiana tekstu w oknach edycji x1 i x2 na liczby
a:=strtoInt(x1.text);
b:=strtoInt(x2.text);
c:=a+b; //dodawanie liczb
wynik.text:=inttostr(c); //zamiana liczby c na tekst
Label3_wyn.caption:='suma';//zmienny komentarz wyniku
end;
procedure TForm1.B_OdejmClick(Sender: TObject);
begin
a:=strtoint(x1.text);
b:=strtoint(x2.text);
c:=a-b;
wynik.text:=inttostr(c);
Label3_wyn.caption:= 'ró\nica';
end;
procedure TForm1.B_MnozClick(Sender: TObject);
begin
a:=strtoint(x1.text);
155
b:=strtoint(x2.text);
c:=a*b;
wynik.text:=inttostr(c);
Label3_wynik.caption:='iloczyn';
end;
procedure TForm1.B_DzielClick(Sender: TObject);
begin
a:=strtoint(x1.text);
b:=strtoint(x2.text);
if b<>0 then c:=a div b else c:=0;
if b<>0 then wynik.text:=inttostr(c)
else begin wynik.Text:='Error!';
showmessage('Nie dziel przez zero !');
end;
//wyswietlenie komunikatu w dodatkowym oknie
Label3_wynik.caption:='iloraz';
end;
procedure TForm1.B_czyscClick(Sender: TObject);
begin
x1.text:=''; //czyszczenie okna edycji x1
x2.text:='';
wynik.text:=''; //czyszczenie okna edycji wyniku
label3_wynik.caption:='wynik';
end;
procedure TForm1.B_KoniecClick(Sender: TObject);
begin
Close; //zamknięcie aplikacji
end;
10.8. Aplikacja Równanie kwadratowe
Zadanie polega na obliczaniu i wyświetlaniu pierwiastków równania kwa-
dratowego oraz rysowaniu wykresu odpowiadającej mu paraboli. Zało\enia:
" współczynniki a, b i c równania będą podawane w oknach SpinEdit,
" wyniki (pierwiastki) będą wyświetlane w oknach edycji TEdit,
" komentarze pojawią się w etykietach TLabel,
" wykres zostanie sporządzony w komponencie TChart,
" zaprogramowana zostanie akcja na przycisk.
Rozwa\one zostaną cztery przypadki:
" dwa pierwiastki rzeczywiste ró\ne,
" jeden pierwiastek podwójny widoczna jedna wartość w jednym oknie,
" brak pierwiastków odpowiedni komunikat i brak okien,
" rozwiązanie równania liniowego, gdy a = 0.
156
Formularz aplikacji i rozmieszczenie komponentów pokazano na rys. 10.12.
Rys. 10.12. Widok formularza aplikacji do rozwiązywania równań kwadratowych
Wstawianie wykresu:
1. wstaw komponent TChart,
2. wykonaj szybkie podwójne kliknięcie lewym przyciskiem na wykresie.
Pojawi się okno wstawiania linii do wykresów (Edit series Chart1).
Wybierz Add (wstawianie komponentu ChartSeriesEdit), czyli dodaj
jedną linię na wykresie.
3. sformatuj linię wykresu, u\ywając właściwości SeriesColor, Title itp.
Rys. 10.13. Okno do wstawiania/usuwania linii z wykresu
157
Kod modułu unit1, czyli rozwiązanie zadania:
Unit unit1;
TForm1 = class(TForm)
Button1: TButton; //oblicz
Chart1: TChart; // wykres - siatka
wynikx1: TEdit; // okno wyniku pierwszego pierwiastka
wynikx2: TEdit; // okno wyniku drugiego pierwiastka
chart2LS: TLineSeries; //wykres linia funkcji kw.
funckja: TLabel; // opis
lx1: TLabel; // opis
lx2: TLabel;
lax2: TLabel;
lbx: TLabel;
lc: TLabel;
abox: TFloatSpinEdit; // współczynnik równania (a)
bbox: TFloatSpinEdit; // współczynnik równania (b)
cbox: TFloatSpinEdit; // współczynnik równania (c)
procedure Button1Click(Sender: TObject);
//procedura obliczania pierwiastków równania
private
{ private declarations }
public
{ public declarations }
end;
var Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
const
MARGIN = 10; //definiuje margines
var
a: Double; // parametr a
b: Double; // parametr b
c: Double; // parametr c
x1, x2: Double; // x1 i x2 pierwiastki równania kwadr.
delta: Double; // wyró\nik trójmianu kwadratowego
// zmienne potrzebne do pętli for i wykresu
x, y: Double;
i: Integer;
mlewy, mprawy: Longint; //marginesy
158
begin
a := abox.Value; //dane ze SpeedButtons
b := bbox.Value;
c := cbox.Value;
if a<>0 then //sprawdzamy, czy a jest ró\ne od zera
begin
// piszemy postać ogolną funkcji
funckja.caption := FloatToStr(a)+'x^2+'+FloatToStr(b)
+'x+'+FloatToStr(c);
//obliczamy deltę ze wzoru: b^2 - 4*a*c
delta := b*b-(4*a*c);
x1 := 0;
x2 := 0;
if delta>0 then //je\eli delta>0, to 2 pierwiastki
begin
x1 := (-b+sqrt(delta))/(2*a);//obliczenie pierwiastków
x2 := (-b-sqrt(delta))/(2*a);
wynikx1.Text := FloatToStr(x1);
//wyświetlenie wyników
wynikx2.Text := FloatToStr(x2);
if x2>x1 then //ustalenie marginesu wykresu
begin
mprawy := round(x1)+MARGIN;
mlewy := round(x2)-MARGIN;
end
else
begin
mlewy := round(x1)-MARGIN;
mprawy := round(x2)+MARGIN;
end;
end
else
if delta=0 then //je\eli delta=0, to 1 pierwiastek
begin
x1 := -b/(2*a); // wzor na pierwiastek
wynikx1.Text := FloatToStr(x1);//wyświetlenie wyn.
wynikx2.Text := FloatToStr(x1);
mlewy := round(x1-MARGIN);
mprawy := round(x1+MARGIN); //ustalenie marginesu
end
else
if delta<0 then // brak pierwiastków rzeczywistych
begin
159
wynikx1.Text := 'brak miejsc zerowych';
//wyswietlenie wyników
wynikx2.Text := '';
mlewy := -5; //ustalenie marginesu wykresu
mprawy := 5;
end;
end
else //funkcja liniowa
begin
x1 := 0; //zerowanie pierwiastków
x2 := 0;
funckja.caption := FloatToStr(b)+'x+'+FloatToStr(c);
//wyświetlenie funkcji liniowej
if c <> 0 then
// je\eli parametr c<>0, to:
begin
x1 := (-b)/c;
wynikx1.Text := FloatToStr(x1);
//wyswietlenie wyników
wynikx2.Text := '';
end;
else //jezeli c=0, to brak
begin
x1 := 0;
wynikx1.Text := FloatToStr(x1);
wynikx1.Text := ''; //wyświetlenie wyników
wynikx2.Text := '';
end;
mlewy := round(x1)-MARGIN; //ustalenie marginesu
mprawy := round(x1)+MARGIN;
end;
chart2LS.Clear; // czyszczenie wykresu
// rysowanie funkcji kwadratowej!
for i := mlewy to mprawy do
begin
x := i;
y := (a*x*x)+(b*x)+c;
chart2LS.AddXY(x, y); //rysowanie punktu na wykresie
end;
end;
end.
160
Rys. 10.14. Widok okna aplikacji rozwiązującej równanie kwadratowe
10.9. Rysowanie figur geometrycznych
Zbudujemy aplikację, która pozwoli rysować figury geometryczne. Figury
mo\na rysować w kilku komponentach, jednak najbardziej odpowiednim do te-
go celu jest komponent TShape.
Zało\enie: rysowanie figur geometrycznych na ró\ne sposoby.
Rozwiązanie:
a) rysowanie figur w komponencie TImage,
b) rysowanie prostych figur w komponencie TShape,
c) rysowanie zło\onych figur w komponencie TShape,
Potrzebne będą komponenty: TRadioGroup x2, TImage, TShape x3, TPanel.
161
Rys. 10.15. Widok formularza aplikacji do rysowania figur
Zło\one kształty (np. trójkąty o ró\nym poło\eniu) mo\na uzyskać poprzez
odpowiednie przesłanianie i poło\enie komponentów Shape2, Shape3 i Panel:
1) komponent Shape2 ustaw jako Deltoid, rozmiar (Height=100,
Width=200), kształt: Shape=stsquaredDiamond,
2) komponent Shape3 ustaw jako kwadrat: rozmiar (Height=100,
Width=100), kształt: Shape=setsquare,
3) komponenty Shape2 i Shape3 rozmieść tak, aby Shape3 zakrywało pra-
wą połowę komponentu Shape2,
4) komponent Panel1: rozmiar (Height=50, Width=200), ustaw tak, by za-
krywał dolną połowę komponentów Shape2 i Shape3.
162
Rys. 10.16. Widok aplikacji w trakcie wyświetlania figur
Kod modułu (programu):
TForm1 = class(TForm)
Image1: TImage;
Panel1: TPanel;
RadioGroup1: TRadioGroup;
RadioGroup2: TRadioGroup;
Shape1: TShape;
Shape2: TShape;
Shape3: TShape; // konstruktor
procedure FormCreate(Sender: TObject);
procedure RadioGroup1Click(Sender: TObject);
procedure RadioGroup2Click(Sender: TObject);
private
{ private declarations }
public // punkty do rysowania trójkąta
trojkat: array [1..3] of TPoint;
x: Integer;
end;
var Form1: TForm1;
szer, wys: Integer;
kolor: Tcolor;
163
implementation
{ TForm1 }
// rysowanie figur w komponentach Image1 i Shape1
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin //ustalenie wierzchołków trójkąta (dowolnie)
troj[1].X := 100; troj[1].Y := 100;
troj[2].X := 100; troj[2].Y := 0;
troj[3].X := 0; troj[3].Y := 100;
Image1.Canvas.Brush.Color := clWhite;
//Image1.Canvas.ClipRect;
Image1.Canvas.FillRect(Canvas.ClipRect); //czyszczenie
Shape1.Canvas.Brush.Color:=clWhite; // na biało
Shape1.Canvas.ClipRect;
Shape1.Canvas.FillRect(Canvas.ClipRect); //czyszczenie
//wybór figury do narysowania w Image1
//rysowanie koła
if Radiogroup1.ItemIndex=0 then
Image1.Canvas.Ellipse(100,100,0,0);
//rysowanie kwadratu
if Radiogroup1.ItemIndex=1 then
Image1.Canvas.Rectangle(100,100,0,0);
//rysowanie prostokąta
if Radiogroup1.ItemIndex=2 then
Image1.Canvas.Rectangle(100,150,0,0);
//rysowanie trójkąta
if Radiogroup1.ItemIndex=3 then
Image1.Canvas.Polygon(troj);
//wybór figury do narysowania w Shape1 poprz Canvas
//rysowanie koła
if Radiogroup1.ItemIndex=0 then
Shape1.Canvas.ellipse(100,100,0,0);
//rysowanie kwadratu
if Radiogroup1.ItemIndex=1 then
Shape1.Canvas.Rectangle(100,100,0,0);
//rysowanie prostokąta
if Radiogroup1.ItemIndex=2 then
Shape1.Canvas.RoundRect(100,150,0,0,15,15);
//rysowanie trójkąta
if Radiogroup1.ItemIndex=3 then
Shape1.Canvas.Polygon(troj);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin // shape2.Width:=100;
wys := shape2.Height;
164
szer := shape2.Width;
Shape3.Brush.color := clDefault; //kolor tla
kolor := Panel1.color;
// Shape3.Pen.color := clDefault;
// aby zaslaniajacy ksztalt nie byl widoczny
Panel1.visible := false;
end;
//rysowanie figur w komponentach Shape2 i Shape3
procedure TForm1.RadioGroup2Click(Sender: TObject);
begin
Shape2.Brush.Color := 123; //kolor brązowy
Shape3.visible := false;
Panel1.Visible := False;
//schowanie komponentu przyslaniającego Panel1
if Radiogroup2.ItemIndex=0 then
Shape2.shape := stCircle; //rysowanie koła
if Radiogroup2.ItemIndex=1 then
Shape2.shape := stSquare; //rysowanie kwadratu
if Radiogroup2.ItemIndex=2 then
Shape2.shape := stRoundSquare;
//rysowanie zaokrąglonego kwadratu
if Radiogroup2.ItemIndex=3 then
begin
Shape2.shape := stRectangle; //rysowanie prostokąta
end;
if Radiogroup2.ItemIndex=4 then
Shape2.shape := stRoundRect;
//rysowanie zaokrąglonego prostokąta
if Radiogroup2.ItemIndex=5 then
Shape2.shape := stEllipse; //rysowanie elipsy
if Radiogroup2.ItemIndex=6 then
Shape2.shape := stDiamond; //rysowanie deltoidu
if Radiogroup2.ItemIndex=7 then
begin
Shape2.shape := stSquaredDiamond;
//rysowanie trojkata przez zasloniecie
//polowy rombu przez kwadrat (Shape3)
Shape3.Pen.Style := psClear;//przezroczyste pioro
//Shape3.Pen.Color := kolor; ;
Shape3.shape := stSquare;
Shape3.visible := true;
end;
if Radiogroup2.ItemIndex=8 then
begin
Shape2.shape := stSquaredDiamond;
165
//rysowanie trojkata przez zasloniecie
//polowy rombu przez Panel1
Panel1.Visible := True;
end;
end;
end.
10.10. Tablice i komponent TListBox
Poka\emy jak stworzyć aplikację, której celem jest praca z tablicą jedno-
wymiarową o określonym rozmiarze. Zadania szczegółowe:
" generowanie danych losowych do tablicy (przycisk Losuj Button2),
" wyświetlenie elementów tablicy (komponent Listbox1),
" wybór typu elementów tablicy (byte, Integer, float) (komponent Radio-
Group1),
" zmiana zakresu losowanych danych (ComboBox1),
" czyszczenie okna z danymi czyli komponentu listbox1 (przycisk Wy-
czysc Button3),
" dodanie liczby z okna typu TEdit do okna typu TListBox (przycisk
Dodaj Button1),
" pomno\enie elementów tablicy przez liczbę i wyświetlenie ich w innym
oknie (komponent memo1, przycisk Mnoz Button4),
" obliczenie średniej z elementów tablicy (przycisk Srednia - Button5
-
-
-
+Label1),
" obliczenie sumy z elementów tablicy (przycisk Srednia - Button6
-
-
-
+Label1),
" obliczenie największego elementu tablicy (przycisk Max - Button7
-
-
-
+Label1).
Rozwiązanie:
Rozmiar tablicy ustalono na 20. Typ elementów to Float, który mo\e prze-
chować elementy typu Integer i Byte. Aktualny rozmiar tablicy N ustalono na 10
(czyli będzie wyświetlana tablica 10-elementowa). Tablicę zadeklarowano jako
zmienną globalną modułu (przed sekcją implementation). Zmienną x, do mno-
\enia tablicy, te\ zadeklarowano jako zmienną globalną.
166
Rys. 10.17. Widok formularza wyświetlania tablicy w komponencie ListBox
Rys. 10.18. Widok aplikacji - wyświetlanie tablicy i operacje na tablicy
Kod modułu:
unit Unit1;
{$mode objfpc}{$H+}
167
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics,
Dialogs, StdCtrls, ExtCtrls;
type { TForm1 }
TForm1 = class(TForm)
Button1: TButton; Button2: TButton;
Button3: TButton; Button4: TButton;
Button5: TButton; Button6: TButton;
Button7: TButton;
ComboBox1: TComboBox;
Edit1: TEdit;
Label1: TLabel;
ListBox1: TListBox;
Memo1: TMemo;
RadioGroup1: TRadioGroup;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
procedure Button7Click(Sender: TObject);
private
public
end;
var Form1: TForm1;
A : Array[0..20] of float;
N : Integer = 9;
zakres : Integer = 10;
x : Integer;
implementation
{$R *.lfm} { TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
ListBox1.Items.Add(Edit1.Text)
end;
procedure TForm1.Button2Click(Sender: TObject);
var i: Byte;
begin
zakres:=StrtoInt(ComboBox1.Text);
168
for i:=0 to N do
begin
if RadioGroup1.ItemIndex=0 then
ListBox1.Items.Add(InttoStr(random(zakres)));
if RadioGroup1.ItemIndex=1 then
ListBox1.Items.Add(InttoStr(2*random(zakres)-zakres));
if RadioGroup1.ItemIndex=2 then
ListBox1.Items.Add(FloattoStr(2*random-1));
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
Memo1.Text :=ListBox1.Items.GetText;
ListBox1.Items.Clear();
end;
procedure TForm1.Button4Click(Sender: TObject);
var i: Byte;
begin
x := 1;
x := StrtoInt(Edit1.Text);
Memo1.Text := '';
for i:=0 to N do
begin
ListBox1.ItemIndex:=i;
A[i] := StrtoFloat
(ListBox1.Items.Strings[ListBox1.ItemIndex]);
Memo1.Lines.Add(FloattoStr(A[i]*x));
end;
end;
procedure TForm1.Button5Click(Sender: TObject);
var i: Byte;
max: Double;
begin
max := A[0];
for i:=0 to N do
if A[i]> max then max := A[i];
Label1.Caption := FloatToStr(max);
end;
procedure TForm1.Button6Click(Sender: TObject);
var i: Byte;
s: Double;
begin
169
s := 0;
for i:=0 to N do s := s+A[i];
Label1.Caption := FloatToStr(s/(N+1));
end;
procedure TForm1.Button7Click(Sender: TObject);
var i: Byte;
s: Double;
begin
s := 0;
for i:=0 to N do
begin
// ListBox1.ItemIndex := i;
// A[i] := StrtoFloat
// ListBox1.Items.Strings[ListBox1.ItemIndex];
s := s+A[i];
end;
Label1.Caption := FloatToStr(s);
end;
end.
10.11. Okna dialogowe
Napiszemy aplikację, której celem jest praca z oknami dialogowymi.
Zadania szczegółowe:
" pisanie tekstu w oknie tekstowym (w komponencie Memo1),
" zapisanie tekstu znajdującego się w Memo1 do pliku tekstowego
(za pomocą przycisku Save Button3 oraz komponentu SaveDialog1),
" odczytanie tekstu z pliku i wpisanie go do okna Memo1 (za pomocą
przycisku Open Button1 oraz komponentu OpenPictureDialog1),
" odczytanie rysunku i pokazanie go w oknie graficznym (za pomocą
przycisku Graph Open Button2 oraz Image1 i OpenDialog1),
" zmiana koloru czcionki tekstu (za pomocą przycisku Color Button4
oraz komponentu ColorDialog1),
" zmiana formatu czcionki tekstu (za pomocą przycisku Font Button6
oraz komponentu FontDialog1).
170
Rys. 10.19. Widok formularza aplikacji do edytowania tekstu
i wykorzystania okien dialogowych
Kod modułu:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses Classes, SysUtils, FileUtil, Forms, Controls,
Graphics, Dialogs, StdCtrls, ExtCtrls, PopupNotifier,
ExtDlgs, SynEdit, PrintersDlgs;
type { TForm1 }
TForm1 = class(TForm)
Button1: TButton; Button2: TButton;
Button3: TButton; Button4: TButton;
Button5: TButton; Button6: TButton;
ColorDialog1: TColorDialog;
FontDialog1: TFontDialog;
Image1: TImage;
Memo1: TMemo; Memo2: TMemo;
OpenDialog1: TOpenDialog;
OpenPictureDialog1: TOpenPictureDialog;
PrintDialog1: TPrintDialog;
SaveDialog1: TSaveDialog;
171
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
private
public
{ public declarations }
end;
var Form1: TForm1;
implementation
{$R *.lfm} { TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin //uzycie OpenDialog
if OpenDialog1.Execute then
Memo1.Lines.LoadFromFile(OpenDialog1.FileName);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin //ustawienie filtru
OpenPictureDialog1.Filter:='*.bmp';
if OpenPictureDialog1.Execute then //załadowanie obrazu
Image1.Picture.LoadFromFile
(OpenPictureDialog1.FileName);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
if SaveDialog1.Execute then
Memo1.Lines.SaveToFile(SaveDialog1.FileName);
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
if colorDialog1.Execute then
Memo1.Color:=ColorDialog1.Color;
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
if PrintDialog1.Execute then Memo2.Text:='Drukuj';
// PrintDialog1.Print:=Memo1.Lines.ToString;
end;
172
procedure TForm1.Button6Click(Sender: TObject);
begin
if FontDialog1.Execute then
Memo1.Font:=FontDialog1.Font;
end;
end.
10.12. Tablice jednowymiarowe i komponent TStringGrid
Napiszemy aplikację, do pracy z tablicą jednowymiarową, przy u\yciu
komponentu TSringGrid.
Zadania szczegółowe:
" generowanie danych losowych do tablicy (przycisk Losowo-Button1);
" wyświetlenie elementów tablicy (komponent StringGrid1)
" obliczenie sumy z elementów tablicy (przycisk Suma Button2 + La-
bel1)
" obliczenie średniej arytmetycznej elementów tablicy (przycisk Srednia
Button3 + Label2)
" obliczenie największego elementu tablicy (komponent LabelEdit1)
" zmiana koloru siatki (komponent CheckBox1)
Rozwiązanie:
Rozmiar tablicy ustalono na 10 (N = 9, indeksowanie od 0). Typ elementów
to Integer. Tablicę zadeklarowano jako pole klasy. Tablicę jednowymiarową
mo\na wyświetlić w komponencie StringGrid (siatka) w poziomie (kolumnowo)
lub w pionie (wierszowo). W tej aplikacji tablica będzie wyświetlana w jednej
kolumnie. Jest to wynik ustawienia parametru RowCount=11 (10+1) - licznik
wierszy. Dodatkowa jedynka to liczba wierszy nagłówkowych (ustalonych pa-
rametrem FixedRows =1).
Drugi rozmiar siatki StringGrid1 to parametr ColCount=2 (1+1) - licznik
kolumn. Dodatkowa jedynka to liczba kolumn nagłówkowych (ustalonych pa-
rametrem FixedCols=1). W ustalonych kolumnach i wierszach mo\na wstawiać
np. podpisy kolumn/wierszy, numery kolumn/wierszy itp.
173
Rys. 10.20. a) Widok formularza do generowania i wyświetlania tablicy,
b) widok działającej aplikacji
Kod modułu:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses Classes, SysUtils, FileUtil, Forms, Controls,
Graphics, Dialogs, StdCtrls, Grids, Buttons, ExtCtrls;
const N=9;
type { TForm1 }
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
CheckBox1: TCheckBox;
Label1: TLabel;
Label2: TLabel;
LabeledEdit1: TLabeledEdit;
StringGrid1: TStringGrid;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure CheckBox1Change(Sender: TObject);
procedure FormCreate(Sender: TObject);
174
procedure LabeledEdit1Click(Sender: TObject);
public
wektor:array[0..N]of integer;
end;
var Form1: TForm1;
wektor1 : array [0..N] of Integer;
implementation
{$R *.lfm} { TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
var i : Byte;
begin
for i:=0 to N do //numerowanie wierszy
StringGrid1.Cells[0,i+1] := Inttostr(i+1);
// Cels[k,w] k=kolumny w=wiersze
end;
procedure TForm1.Button1Click(Sender: TObject);
var i : Integer;
begin
Randomize;
for i := 0 to N do
wektor[i] := random(21)-10; //wpis do wektora
//przepisanie danych tablicy do siatki
for i:=0 to N do
StringGrid1.Cells[1,i+1]:=Inttostr(wektor[i]);
end;
procedure TForm1.Button2Click(Sender: TObject);
var suma, i : Integer;
begin
suma:=0;
for i:=0 to N do
begin
wektor[i]:=StrToInt(StringGrid1.Cells[1,i+1]);
suma:=suma+wektor[i];
end;
Label1.Caption:='suma='+IntToStr(suma);
end;
procedure TForm1.Button3Click(Sender: TObject);
var suma, i : Integer;
begin
suma := 0;
for i := 0 to N do
175
begin
wektor[i] := StrToInt(StringGrid1.Cells[1,i+1]);
suma := suma+wektor[i];
end;
Label2.Caption:='srednia='+FloatToStr(suma/(N+1));
end;
procedure TForm1.LabeledEdit1Click(Sender: TObject);
var max, i : Integer;
begin
max := wektor[0];
for i := 0 to N do
begin
wektor[i] := StrToInt(StringGrid1.Cells[1,i+1]);
if wektor[i] > max then max := wektor[i];
end;
LabeledEdit1.text:=IntToStr(max);
end;
procedure TForm1.CheckBox1Change(Sender: TObject);
begin
if CheckBox1.Checked := True then
StringGrid1.Color := clYellow
else StringGrid1.Color := clDefault;
end;
end.
10.13. Tablice dwuwymiarowe i komponent TStringGrid
Napiszemy aplikację, do pracy z tablicą dwuwymiarową, przy u\yciu kom-
ponentu TSringGrid.
Zadania szczegółowe:
" generowanie danych losowych do tablicy (przycisk Losowo:Button1),
" wyświetlenie elementów tablicy (komponent StringGrid1),
" obliczenie sumy elementów tablicy (przycisk Suma:Button2 +Label1),
" obliczenie średniej arytmetycznej elementów tablicy (przycisk Srednia:
Button3 + Label2),
" obliczenie największego elementu tablicy (komponent LabelEdit1),
" zamykanie aplikacji (przycisk Koniec: Button4).
Rozwiązanie:
176
Rozmiar tablicy ustalono na 10x10 (rM=10). Typ elementów - na Integer.
Tablicę zadeklarowano jako pole klasy. Aktualny rozmiar tablicy N = 4 ustalono
w konstruktorze (FormCreate). Ustawiono parametr RowCount = 11 (10+1) -
licznik wierszy oraz parametr ColCount = 11 (10+1) - licznik kolumn. Kolumna
i wiersz nagłówkowy: parametr FixedCols = 1 oraz FixedRows = 1.
Rys. 10.21. Widok formularza aplikacji do generowania i wyświetlania macierzy
Kod modułu:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses Classes, SysUtils, FileUtil, Forms, Controls,
Graphics, Dialogs, StdCtrls,Grids, Buttons, ExtCtrls;
const rM = 10;
type { TForm1 }
TForm1 = class(TForm)
Button1: TButton; Button2: TButton;
Button3: TButton; Button4: TButton;
Label1: TLabel; Label2: TLabel;
LabeledEdit1: TLabeledEdit;
StringGrid1: TStringGrid;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure LabeledEdit1Change(Sender: TObject);
177
procedure LabeledEdit1Click(Sender: TObject);
private
{ private declarations }
public
macierz : array [1..rM,1..rM] of Integer;
N : Byte; //rozmiar siatki
end;
var Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender : TObject);
var i : Byte;
begin
N := 4;
for i := 1 to N do //numerowanie wierszy
StringGrid1.Cells[0,i] := IntToStr(i);
for i := 1 to N do //numerowanie kolumn
StringGrid1.Cells[i,0] := IntToStr(i);
// Cels[k,w] k=kolumny w=wiersze
end;
procedure TForm1.Button1Click(Sender : TObject);
var i, j : Integer;
begin
Randomize;
for i:=1 to N do
for j:=1 to N do
macierz[i,j] := random(21)-10; //wpis do wektora
// przepisanie danych z macierzy do siatki
for i:=1 to N do
for j:=1 to N do
StringGrid1.Cells[j,i] := IntToStr(macierz[i,j]);
end;
procedure TForm1.Button2Click(Sender : TObject);
var suma, i, j : Integer;
begin
suma := 0;
for i := 1 to N do
for j := 1 to N do
begin
macierz[i,j] := StrToInt(StringGrid1.Cells[j,i]);
178
suma := suma+macierz[i,j];
end;
Label1.Caption := 'suma=' + IntToStr(suma);
end;
procedure TForm1.Button3Click(Sender : TObject);
var suma, i, j : Integer;
begin
suma := 0;
for i := 1 to N do
for j := 1 to N do
begin
macierz[i,j] := StrToInt(StringGrid1.Cells[j,i]);
suma := suma+macierz[i,j];
end;
Label2.Caption := 'srednia='+FloatToStr(suma/(N*N));
end;
procedure TForm1.LabeledEdit1Click(Sender : TObject);
var max, i, j : Integer;
begin
max := macierz[1,1];
for i := 1 to N do
for j := 1 to N do
begin
macierz[i,j]:=StrToInt(StringGrid1.Cells[j,i]);
if macierz[i,j]> max then
begin
max := macierz[i,j];
StringGrid1.GridLineColor := clYellow;
end
else
StringGrid1.GridLineColor := clGray;
end;
LabeledEdit1.text := IntToStr(max);
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
Close;
end;
end.
179
10.14. Zadania
Zad. 10.1. Zbudować aplikację Silnia, która będzie obliczać wartość silni dla
podanej liczby naturalnej (u\yć komponentów Button, EditBox i Label).
Zad. 10.2. Zbudować aplikację Licznik, która będzie zliczać do przodu kliknięcia
na przycisku1 oraz zliczać do tyłu kliknięcia na przycisku2. W oknie
edycyjnym ma być mo\liwość ustalenia krotności zliczania (od 1 do 10).
Zad. 10.3. Zbudować aplikację Potęgowanie, która będzie obliczać wartość po-
tęgi danej liczby: a) wersja podstawowa, czyli tylko potęga całkowita
dodatnia; b) potęga całkowita ujemna; c) potęga rzeczywista.
Zad. 10.4. Zbudować aplikację Stoper,
a) która wyświetla ró\nicę czasu aktualnego i czasu startu aplikacji wyko-
rzystując funkcję Time lub Now. Program ma posiadać dwa przyciski:
START/STOP oraz RESET.
b) po 10 sekundach od startu stopera cyfry zmieniają stan co 200ms za-
miast co 1 sekundę, a nagłówek programu zmienia się na Stoper przy-
spieszył .
c) w oknie głównym pokazuje aktualny czas (wskazówka: przydatna bę-
dzie funkcja Now (lub funkcja Time) oraz TimeToStr).
d) po 10 sekundach od startu stopera na płótnie formy ma pojawiać się ko-
ło z czarnym brzegiem i wnętrzem migającym na czerwono-czarno, co
sekundę. U\yć właściwości Canvas, najlepiej dla komponentu Image,
ewentualnie dla głównej formy programu.
e) wielkość cyfr zmienia się stosownie do zmiennych rozmiarów okna
aplikacji. Wskazówki: aby okno mogło zmieniać rozmiary, trzeba wła-
ściwość BorderStyle ustawić na bsSizeable. U\yć w programie właści-
wości ClientHeight, ClientWidth.
Zad. 10.5. Zbudować kalkulator do obliczeń na liczbach rzeczywistych.
Zad. 10.6. Zbudować kalkulator do obliczeń na liczbach zespolonych.
Zad. 10.7. Zbudować kalkulator do obliczeń na macierzach kwadratowych.
Zad. 10.8. Zbudować aplikację dokładnego dodawania du\ych liczb naturalnych.
Zad. 10.9. Zbudować aplikację, która będzie prostym edytorem tekstu.
Zad. 10.10. Zbudować aplikację, która będzie rysować ró\ne figury: trójkąt,
kwadrat, prostokąt, trapez, romb, równoległobok, sześciokąt. Rozmiary
figur podawane mają być w oknach typu TextBox. Aplikacja ma liczyć
obwód i pole figury.
Zad. 10.11. Zbudować aplikację do konwersji liczb na ró\ne systemy liczbowe
(dziesiętny, binarny, szesnastkowy, ósemkowy itp.).
180
LITERATURA
[1] Cantu M.: Delphi 7. Praktyka programowania. Mikom, Warszawa, 2004.
[2] Gierliński M.: Pascal nawet Ty mo\esz programować. Wydawnictwo Edi-
tion 2000, Kraków, 1998.
[3] Kierzkowski A.: Turbo Pascal. Ćwiczenia praktyczne. Helion, Gliwice,
2000.
[4] Kott Ryszard K.: Programowanie w języku Pascal. WNT, Warszawa, 1988.
[5] Marciniak A.: Object Pascal język programowania w środowisku Borland
Delphi 2.0. Nakom, Poznań, 1997.
[6] Porębski W.: Pascal. Wprowadzenie do programowania. Komputerowa
Oficyna Wydawnicza HELP , Michałowice pod Warszawą, 1999.
[7] Stephens R.: Algorytmy i struktury danych z przykładami w Delphi. Helion,
Gliwice, 2000.
[8] Struzińska-Walczak A., Walczak K.: Nauka programowania dla & ju\ nie
całkiem początkujących. Wydawnictwo W&W, Warszawa, 2004.
[9] Struzińska-Walczak A., Walczak K.: Nauka programowania w systemie
Delphi. Wydawnictwo W&W, Warszawa, 2004.
[10] Strzałkowski K.: Podstawy Delphi. Wydawnictwo Stachurski, Kielce, 2000.
[11] http://wazniak.mimuw.edu.pl/index.php?title=Wst%C4%99p_do_progra
mowania
[12] http://wazniak.mimuw.edu.pl/index.php?title=Paradygmaty_progra
mowania
[13] http://wazniak.mimuw.edu.pl/index.php?title=Matematyka_dyskretna_1
[14] http://wiki.freepascal.org/Main_Page
[15] http://wiki.freepascal.org/Object_Pascal_Tutorial
[16] http://www.tutorialspoint.com/pascal/index.htm
[17] http://turbopascal.helion.pl
[18] http://www.pascal.vj.e.pl
[19] http://edu.i-lo.tarnow.pl/inf/utils/010_2010/index.php
[20] http://javablock.sourceforge.net/
[21] http://www.pm.waw.pl/~marwoj/javablock/javablock.pdf
[22] http://lobrzozow.internetdsl.pl/materialy/ti/Ti2.htm
181
Wyszukiwarka
Podobne podstrony:
Wstęp do programowania CProjektowanie oprogramowania Wstep do programowania i techniki komputerowejWstep do programowania w jezyku C wstpcpWstep do programowania w jezyku C wstpchWstep do programowania w jezyku C wstpchwstęp do programowaniaWstep do pracy z grafiką i programem PAINT paliwoda10 Wstep do prawoznawstwa01 Wprowadzenie do programowania w jezyku Cwięcej podobnych podstron