2
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, wskaźnik, 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
3
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 źró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
4
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
5
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.
Wskaźniki .............................................................................. 106
7.1.
Definicja zmiennej wskaźnikowej ................................... 106
7.2.
Procedury dynamicznego rezerwowania i zwalniania
pamięci ............................................................................. 107
7.3.
Wskaźniki i tablice ........................................................... 111
7.4.
Operacje arytmetyczne na wskaźnikach .......................... 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
6
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 źró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
7
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.
8
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 znaleźć 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
9
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
10
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.
11
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.
12
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-
13
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 źró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,
14
•
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 źró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:
15
•
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. Łatwość
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 źró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ę.
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).
Algorytm
Kod źródłowy
Program
Problem
16
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 źródłowy, zwany teŜ kodem źró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óźnym, 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 wyraźnego (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.
17
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 źró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 źródłowego jest program wyniko-
wy, który moŜe być zapisany na dysku komputera i później wielokrotnie uru-
chamiany za pośrednictwem systemu operacyjnego.
1.8.
Rola kompilatora w procesie powstawania programu
Kompilacja, w programowaniu, to przetłumaczenie wersji źródłowej pro-
gramu na język maszyny (procesora). Kompilacji wersji źródłowej programu
dokonuje kompilator danego języka programowania.
Kompilator jest programem komputerowym lub zestawem programów,
który tłumaczy kod źródłowy, napisany w języku programowania (w języku
ź
ró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 źró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).
Rys. 1.2. Droga od kodu źródłowego do wykonywalnego programu
Tekst źródłowy
programu
Reprezentacja
pośrednia
Program
wykonywalny
kompilacja
konsolidacja
Biblioteki stan-
dardowe i inne
18
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 źró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 źró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 źródłowym przechowuje
informacje o jego połoŜeniu, typie i zasięgu. Leksykalna analiza dzieli
tekst źró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óźniejszych 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-
19
kich zmiennych lokalnych przed ich uŜyciem). To sprawdzanie kończy
się odrzuceniem źródła programu lub wygenerowaniem ostrzeŜeń.
•
middle-end
−
na tym etapie są dokonywane optymalizacje niezaleŜne od
kodów: źró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 wyraźnie „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óźniają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.
20
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 wyraźnie 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 źró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óźniejszym 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 źró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
21
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 źró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 źró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 źró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. Łączenie skompilowanego ko-
du źró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.
22
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
0
2
=
+
+
c
x
b
x
a
naleŜy przewidzieć równieŜ okoliczność,
kiedy nie posiada ono rzeczywistych pierwiastków. Ponadto, dla
0
=
a
nie jest
to równanie kwadratowe, gdyŜ wtedy degeneruje się ono do równania liniowe-
23
go. Poza tym, dla
,
0
=
a
0
=
b
i
0
≠
c
równanie jest sprzeczne, natomiast gdy
,
0
=
a
0
=
b
i
0
=
c
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-
24
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
25
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ę znaleźć 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.
26
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.
27
Tabela 2.1. Symbole stosowane w schematach blokowych
Nazwa bloku
Symbol
Znaczenie
STRZAŁKA
Określa kierunek (drogę) przepływu danych
lub kolejność wykonywania działań.
START
Od tego bloku rozpoczyna się wykonywanie
algorytmu. Występuje tylko raz w schemacie.
Wychodzi z niego tylko jedna strzałka.
STOP
Na tym bloku kończy się wykonywanie algo-
rytmu. Wchodzi do niego co najmniej jedna
strzałka. Najczęściej występuje tylko raz,
ale dla podniesienia czytelności schematu,
moŜe być powtórzony.
BLOK WEJŚCIA
/ WYJŚCIA
W tym bloku umieszcza się operacje
wprowadzania (odczytu) danych
albo wyprowadzania (zapisu) wyników.
BLOK
PRZETWARZANIA
(WYKONAWCZY)
W tym bloku umieszcza się obliczenia lub
podstawienia. Blok moŜe zawierać grupę
operacji, w efekcie których zmienia się war-
tość, postać lub miejsce zapisu danych.
BLOK
DECYZYJNY
(WARUNKOWY,
KIERUJĄCY)
W tym bloku umieszcza się warunek logiczny
(prosty lub złoŜony), decydujący o dalszej
drodze postępowania. JeŜeli jest spełniony,
wtedy są wykonywane operacje na „TAK”, w
przeciwnym wypadku – na „NIE”. Do zapisu
warunku logicznego naleŜy uŜywać symboli:
=,
≠
, >,
≥
, <,
≤
,
∧
(i),
∨
(lub).
PROCES
UPRZEDNIO
ZDEFINIOWANY
W tym bloku wpisuje się nazwę procesu
(podprogramu), który chcemy wykonać, zde-
finiowanego poza bieŜącym programem.
PUNKT
KONCENTRACJI
Punkt koncentracji jest uŜywany dla podnie-
sienia czytelności schematu. Oznacza miej-
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.
NIE
TAK
28
Przykład 1. Wyznaczanie największej spośród trzech liczb:
.
,
,
c
b
a
Rys. 2.1. Schemat blokowy algorytmu wyznaczania największej spośród liczb: a, b, c
Przykład 2. Rozwiązywanie równania liniowego
0
=
+
b
x
a
.
Rys. 2.2. Schemat blokowy algorytmu rozwiązywania równania liniowego
STOP
NIE
TAK
NIE
TAK
wczytaj a, b
a=0
b=0
START
x
←
−
b / a
niesko
ń
czenie
wiele rozwi
ą
za
ń
równanie
sprzeczne
wypisz x
STOP
NIE
TAK
NIE
NIE
TAK
TAK
wczytaj a, b, c
a>b
b>c
a>c
wypisz a
wypisz c
wypisz b
wypisz c
START
29
Przykład 3. Wyszukiwanie największego elementu w tablicy n-elementowej
A
.
Rys. 2.3. Schemat blokowy algorytmu wyznaczania największego elementu tablicy
A
Przykład 4. Obliczanie silni liczby naturalnej n.
Rys. 2.4. Schemat blokowy algorytmu obliczania silni liczby naturalnej n
STOP
wczytaj n
START
silnia
←
1
i
←
1
NIE
TAK
i < n
wypisz silnia
i
←
i + 1
silnia
←
silnia*i
TAK
NIE
wczytaj n,
A[1], …, A[n]
START
i
←
2
max
←
A[1]
TAK
NIE
i > n
wypisz max
STOP
max
←
A[i]
i
←
i + 1
A[i] > max
30
Przykład 5. Wyznaczanie średniej arytmetycznej n wczytywanych liczb.
Rys. 2.5. Schemat blokowy algorytmu wyznaczania średniej arytmetycznej n liczb
Przykład 6. Obliczanie NWD liczb x i y metodą z odejmowaniem.
Rys. 2.6. Schemat blokowy algorytmu obliczania NWD metodą z odejmowaniem
n
←
n
−
m
m
←
m
−
n
START
n
←
x, m
←
y
STOP
NIE
TAK
NIE
TAK
wczytaj x, y
n > m
wypisz NWD(x,y)
n = m
NWD(x,y)
←
n
wczytaj n
START
s
←
0
k
←
n
NIE
k > 0
wypisz s
STOP
s
←
s / n
s
←
s + a
wczytaj a
k
←
k
−
1
TAK
31
Przykład 7. Obliczanie NWD liczb x i y metodą dzielenia z resztą.
Rys. 2.7. Schemat blokowy algorytmu obliczania NWD metodą dzielenia z resztą
Przykład 8. Szybkie (z minimalną liczbą mnoŜeń) obliczanie a
n
.
Rys. 2.8. Schemat blokowy algorytmu szybkiego obliczania a
n
wczytaj a, n
k
←
n
x
←
a
START
wynik
←
1
NIE
TAK
k
≠
0
wypisz wynik
STOP
TAK
NIE
k
←
k
−
1
wynik
←
wynik
∗
x
k
←
k / 2
x
←
x
∗
x
k mod 2
≠
0
NIE
TAK
wczytaj x, y
y
≠
0
x
←
y
r
←
x mod y
y
←
r
STOP
START
NWD(x,y)
←
x
wypisz NWD(x,y)
32
Przykład 9. Testowanie złoŜoności liczby naturalnej.
Rys. 2.9. Schemat blokowy prostego algorytmu sprawdzającego, czy liczba jest złoŜona
wczytaj n
START
r
←
n mod k
k
←
2
TAK
NIE
TAK
NIE
r = 0
n – liczba zło
Ŝ
ona
k
←
k + 1
STOP
n = k
n –
liczba pierwsza
STOP
33
Przykład 10. Rozwiązywanie równania kwadratowego
.
0
2
=
+
+
c
x
b
x
a
Rys. 2.10. Schemat blokowy algorytmu rozwiązywania równania kwadratowego
TAK
NIE
TAK
D
=
0
x1
←
−
b/2a
x1
←
(
−
b+
√D
)/2a
STOP
x2
←
(
−
b
−
√D
)/2a
wypisz x1, x2
D
←
b*b
−
4*a*c
NIE
START
NIE
TAK
wczytaj a, b, c
a
≠
0
Równanie
liniowe
bx+c = 0
D
≥
0
Brak pierwiastków
rzeczywistych
x2
←
x1
34
Przykład 11. PrzybliŜone obliczanie pierwiastka kwadratowego z liczby a
≥
0.
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
2
1
n
a
a
a
K
Zad. 2.4. obliczania najmniejszej wspólnej wielokrotności pary liczb m i n we-
dług zaleŜności:
).
,
(
NWD
/
)
(
)
,
(
NWW
n
m
n
m
n
m
⋅
=
Zad. 2.5. skracania ułamka zwykłego.
TAK
wypisz x
STOP
NIE
x
←
a/4
x1
←
x
x
←
0.5(x1+a/x1)
NIE
TAK
wczytaj a
a
≥
0
eps
←
1e-9
a jest ujemne
START
|x-x1| < eps
35
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.
Zad. 2.10. obliczania pierwiastka n-tego stopnia z liczby a, tzn.
n
a
x
=
metodą
Newtona:
1
1
)
1
(
−
+
−
+
=
n
i
n
i
i
x
n
x
n
a
x
, gdzie
i
x
−
kolejne przybliŜenie
n
a
,
a
x
=
0
, z dokładnością eps taką, Ŝe
eps
x
x
i
i
<
−
+
1
.
Zad. 2.11. obliczania
∑
=
+
+
−
≈
N
n
n
n
x
n
x
0
1
2
)!
1
2
(
)
1
(
sin
oraz
∑
=
−
≈
N
n
n
n
x
n
x
0
2
)!
2
(
)
1
(
cos
.
Argument x jest dowolną liczbą rzeczywistą, N
−
liczbą naturalną.
Zad. 2.12. obliczania
!
...
!
2
!
1
1
2
n
x
x
x
e
n
x
+
+
+
≈
dla podanego n i x.
Zad. 2.13. obliczania metodą Hornera wartości wielomianu
.
)
(
0
1
1
1
a
x
a
x
a
x
a
x
w
n
n
n
n
n
+
+
+
+
=
−
−
K
Na przykład dla
:
4
=
n
))).
(
(
(
4
3
2
1
0
0
1
2
2
3
3
4
4
xa
a
x
a
x
a
x
a
a
x
a
x
a
x
a
x
a
+
+
+
+
=
+
+
+
+
Dane wejściowe to: n,
n
a
a
a
,
,
,
1
0
K
oraz konkretne
.
0
x
x
=
Zad. 2.14. obliczania wartości pochodnej wielomianu
)
(
x
w
n
w punkcie
.
0
x
x
=
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.
36
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 wyraźnie 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ądź 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 ..
37
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ą
38
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
10
cecha
.
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.
39
3.5.
Zasady deklarowania stałych i zmiennych
Stałe, zwane teŜ literałami, mają za zadanie zwiększenie czytelności kodu
ź
ró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.
40
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.
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 źró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, wskaźnikowego, proceduralnego albo identyfikator typu standardo-
wego lub zdefiniowanego wcześniej przez uŜytkownika.
Przykłady definicji typów:
identyfikator
delta
123
(byte)
zmienna
typ
warto
ść
41
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, wskaźnikowe, 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ł-
42
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
Shortint
P
1
−
128
127
Smallint
P
2
−
32 768
32 767
Longint
P
4
−
2 147 483 648
2 147 483 647
Byte
P
1
0
255
Word
P
2
0
65 535
DWord,
LongWord
P
4
0
4 294 967 295
QWord
P
8
0
18 446 744 073 709 551 615
Int64
P
8
−
9 223 372 036 854 775 808
9 223 372 036 854 775 807
Integer
O
4
−
2 147 483 648
2 147 483 647
Cardinal
O
4
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
AnsiChar
P
1
znaki uporządkowane zgodnie z tabelą roz-
szerzonego zestawu znaków ASCII (0
÷
255)
256
WideChar
P
2
znaki uporządkowane zgodnie z tabelą zna-
ków standardu UNICODE (0
÷
65535)
65536
Char
O
1
znaki uporządkowane zgodnie z tabelą roz-
szerzonego zestawu znaków ASCII (0
÷
255)
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
43
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)
Boolean
P
1
False (0)
True (1)
ByteBool
O
1
False (0)
True (od 1 do 255)
WordBool
O
2
False (0)
True (od 1 do 65535)
LongBool
O
4
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
≤
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;
44
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
Typ
B
Najmniejsza
wartość
dodatnia
Największa
wartość
dodatnia
Liczba
cyfr zna-
czących
mantysy
Real
O
4
albo
8
zaleŜy
od
platformy
zaleŜy
od
platformy
zaleŜy
od
platformy
Single
P
4
2
-149
≈
1.40E
−
45
3.4E+38
7 albo 8
Double
P
8
2
-1074
≈
4.94E
−
324
1.7E+308
15 albo 16
Extended
P
10
2
-16445
≈
3.65E
−
4951
1.1E+4932
19 albo 20
wartość najmniejsza
wartość największa
Comp
P
8
−
2
63
=
−
9223372036854775808
2
63
−
1 =
9223372036854775807
19 albo 20
Currency
P
8
−
922337203685477.5808
922337203685477.5807
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;
45
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 wskaźni-
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
]
2
≤
N
≤
255
Zawsze oznacza łańcuch krótki.
string
zaleŜy od
{
$H}
Typ
string oznacza typ ShortString w stanie {$H
−
}.
Typ
string oznacza typ AnsiString w stanie {$H+}.
ShortString
255
Zawsze oznacza łańcuch krótki.
AnsiString
dowolna
Długość ograniczona tylko rozmiarem pamięci. Pamięć
przydzielana dynamicznie na stercie. Przechowuje znaki
typu
Char. Łańcuch ten jest zakończony znakiem pu-
stym (#0), którego jednak nie zalicza się do łańcucha.
WideString
dowolna
Ma te sami właściwości, co typ
AnsiString,
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;
46
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);
47
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)
≡
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,
48
•
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.
49
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 wskaźnikowego.
W wyniku konwersji między dwoma niezgodnymi typami porządkowymi albo
dwoma niezgodnymi typami wskaźnikowymi 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.
50
Tabela 3.6. Operatory arytmetyczne
Operator
Nazwa operacji
Typ argumentów
Typ wyniku
+
J
identyczność
całkowity
całkowity
rzeczywisty
Extended
−−−−
J
zmiana znaku
całkowity
całkowity
rzeczywisty
Extended
+
D
dodawanie
całkowity
całkowity
rzeczywisty
rzeczywisty
−
D
odejmowanie
całkowity
całkowity
rzeczywisty
rzeczywisty
*
D
mnoŜenie
całkowity
całkowity
rzeczywisty
rzeczywisty
/
D
dzielenie
całkowity
Extended
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
≥
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.
51
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
2
b
, np. 7
shl 5
→
7
2
5
= 224.
Operację
shr moŜna stosować do szybkiego dzielenia całkowitego przez
potęgę liczby 2, mianowicie: a
shr b
→
a div 2
b
, np. 37
shr 3
→
37 div 2
3
= 4.
Tabela 3.7. Operatory logiczne
Operator
Nazwa operacji
Typ argumentów
Typ wyniku
not
J
negacja
całkowity
całkowity
logiczny
Boolean
and
D
koniunkcja
(logiczne i)
całkowity
całkowity
logiczny
Boolean
or
D
alternatywa
(logiczne lub)
całkowity
całkowity
logiczny
Boolean
xor
D
róŜnica symetryczna,
alternatywa wyklu-
czająca (e
xclusive
or), (logiczne albo)
całkowity
całkowity
logiczny
Boolean
shl
D
przesunięcie bitowe
w lewo (
shift left)
całkowity
całkowity
shr
D
przesunięcie bitowe
w prawo (
shift right)
całkowity
całkowity
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
52
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
//
←
ź
le
(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
≤
),
>= (większy lub równy, w matematyce znany jako
≥
),
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.
53
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
+
suma zbiorów
(A
∪
B)
c jest elementem zbioru A + B,
jeŜeli jest elementem zbioru A
lub jest elementem zbioru B
−
róŜnica zbiorów
( A \ B )
c jest elementem zbioru A
−
B,
jeŜeli jest elementem zbioru A
i nie jest elementem zbioru B
*
iloczyn zbiorów
( A
∩
B )
c jest elementem zbioru A * B,
jeŜeli jest elementem zbioru A
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-
54
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.
55
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;
56
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.
57
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'}
58
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 źró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
59
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.
a)
b)
Rys. 4.1. Graficzna postać instrukcji warunkowej:
a) w wersji pełnej, b) w wersji skróconej
NIE
TAK
warunek
instrukcja
NIE
TAK
warunek
instrukcja_1
instrukcja_2
60
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;
61
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
62
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.
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;
opcja_1 : ci
ą
g instrukcji_1
case
wyra
Ŝ
enie
of
ci
ą
g instrukcji
end
else
opcja_2 : ci
ą
g instrukcji_2
opcja_N : ci
ą
g instrukcji_N
63
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,
64
•
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,
65
•
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óźniej przyjął on
wartość
False, w przeciwnym razie pętla nigdy się nie przerwie (chyba,
Ŝ
e zastosujemy procedurę
Break).
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;
NIE
TAK
warunek
instrukcja
66
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.
Rys. 4.4. Schemat blokowy algorytmu wyznaczania NWD metodą kolejnych dzieleń
NIE
TAK
m mod k
≠
0
lub
n mod k
≠
0
k
←
k
−
1
STOP
START
wczytaj m, n
k
←
m
wypisz k
67
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.
Rys. 4.5. Schemat blokowy instrukcji iteracyjnej repeat
−
until
TAK
NIE
warunek
instrukcja
68
Przykład 6. Program oblicza n
k
.
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');
69
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
−−−−
to
−−−−
do
Pętla
repeat
−−−−
until
Pętla
while
−−−−
do
program p16;
const n=7;
var s,licznik:longint;
begin
s := 1;
for licznik:=2 to n do
s:=s*licznik;
write(' silnia= ',
s);
end.
program p17;
const n=7;
var s, licznik: Longint;
begin
s := 1;
licznik:=1;
repeat
s := s*licznik;
licznik
:= licznik+1;
until licznik>n;
write(' silnia= ',
s);
end.
program p18;
const n=7;
var s,licznik:longint;
begin
s:=1;
licznik:=2;
while licznik<=n do
begin
s := s*licznik;
licznik := licznik+1;
end;
write(' silnia= ',
s);
end.
70
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
−−−−
to
−−−−
do,
•
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
−−−−
downto
−−−−
do,
•
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;
71
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;
const n = 20;
var licznik : Byte;
BEGIN
//s:=1;
licznik := 0;
while licznik < n do
begin
licznik := licznik+1;
if licznik mod 3 = 0
then continue;
writeln(licznik);
end;
readln;
END.
program progr21;
const n = 20;
var licznik : Byte;
BEGIN
//s:=1;
licznik := 1;
while licznik < n do
begin
licznik := licznik+1;
if licznik mod 5 = 0
then break;
writeln(licznik);
end;
readln;
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;
72
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
73
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);
74
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 (
a
x
=
), z zadaną dokładnością eps, iteracyjną metodą Newtona
−
=
+
i
i
i
x
x
a
x
2
1
1
. Wartość
a , czyli
1
+
i
x
, ma dokładność eps, jeŜeli
eps
x
a
i
<
−
2
.
i
x oznacza kolejne przybliŜenie a .
Zad. 4.6. Napisać program obliczający sumę szeregu
∑
=
+
+
=
100
1
2
1
i
i
x
i
s
(x dowolne).
Zad. 4.7. Napisać program obliczania wyraŜenia danego wzorem
L
L
7
5
5
3
3
1
6
6
4
4
2
2
2
π
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
⋅
=
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 źródłowym
albo tablica jest wczytywana z klawiatury.
75
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.
76
Parametry formalne pojawiają się w nagłówku deklaracji funkcji bądź
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.
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.
nagłówek
procedury
begin
ci
ą
g instrukcji
end
cz
ęść
deklaracyjna
blok
procedury
77
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
78
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ść.
79
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;
procedure www(k, m : Integer);
begin // przekazywanie przez wartości
writeln('k=', k);
k := k+m;
writeln('k=', k);
end;
procedure zzz(var k, m : Integer);
begin // przekazywanie przez zmienne
writeln('k=', k);
k := k+m;
writeln('k=', k);
end;
var // c.d.
a, b : Integer;
BEGIN
a := 1;
b := a+1;
www(a, b+1);
writeln('a=', a);
www(a, b+1);
writeln('a=', a);
zzz(a, b);
writeln('a=', a);
zzz(a, b);
writeln('a=', a);
readln;
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;
80
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óźniej po to, aby w podprogramach
zdefiniowanych wcześniej moŜna było uŜyć wywołania tego podprogramu.
81
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.
82
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-
nagłówek
funkcji
begin
ci
ą
g instrukcji
end
cz
ęść
deklaracyjna
blok
funkcji
Result
←
wyra
Ŝ
enie
83
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 źró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;
84
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.
85
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 2
÷
10.
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;
86
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 źró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
w treści funkcji B.
Definicja funkcji B w czę-
ś
ci deklaracyjnej funkcji A
i wywołanie funkcji B w
treści funkcji A.
Wywołanie procedury A
w procedurze B.
function A:pewien_typ;
begin
...
end;
function B;
var x : pewien_typ;
begin
...
x :=
A;
...
end;
function A;
function B:pewien_typ;
begin
...
end;
var x : pewien_typ;
begin
...
x :=
B;
...
end;
procedure A;
begin
...
end;
procedure B;
begin
...
A;
end;
87
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 źró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.
Z
mienne 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,
88
•
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;
89
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;
90
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;
91
BEGIN
writeln('Podaj n=');
readln(n);
writeln(' n! = ', Rsilnia(n));
readln;
END.
Przykład 3. Rekurencyjne obliczanie kolejnych liczb ciągu Fibonacciego.
program FibRekur; // F
0
=0, F
1
=1, F
n
= F
n-1
+ F
n-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;
92
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-
93
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;
var i : byte;
procedure zewnetrzna;
procedure wewnetrzna;
begin
writeln('To napisała procedura
wewn
ę
trzna');
exit;
writeln('a tego nigdy nie zo-
baczymy');
end;
begin
writeln('To napisała procedura ze-
wn
ę
trzna');
wewnetrzna;
//tu wyw. procedur
ę
wewnetrzn
ą
writeln('ten napis si
ę
pojawi,
kiedy wewn
ę
trzna przeka
Ŝ
e sterowa-
nie do zewn
ę
trznej');
exit;
writeln('A ten napis ju
Ŝ
si
ę
nie
pojawi');
program Pr_sleep_halt;
Uses SysUtils;
//W tym module jest procedura sleep
var
s : string;
I : byte;
BEGIN
s := 'Dziala opoznienie- (sleep)';
for i := 1 to length(s) do
begin
write(s[i]);
sleep(400);
end;
// readln;
writeln ('a teraz zadziała proce-
dura Halt, za 5 sekund');
sleep(5000);
Halt;
Writeln('Tego napisu nie zobaczy-
my')
END.
94
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ądź 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.
95
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';
96
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.
97
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 e
x
według sumy:
!
...
!
2
!
1
1
e
2
n
x
x
x
n
x
+
+
+
+
≈
, x-dowolne.
Zad. 5.4. Napisać funkcję obliczania przybliŜonej wartości cos(x) według sumy:
)!
2
(
)
1
...(
!
6
!
4
!
2
1
cos
2
6
4
2
n
x
x
x
x
x
n
n
−
+
−
+
−
=
, x-dowolne.
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
k
k
n
n
n
k
n
⋅
⋅
⋅
+
−
−
=
K
K
2
1
)
1
(
)
1
(
. PoniewaŜ
k
n
zawsze przyjmuje warto-
ś
ci całkowite, więc takie skrócenie jest moŜliwe. Na przykład, dla
12
=
n
i
5
=
k
, po skróceniu ułamka, otrzymamy iloczyn:
12
11
3
2
⋅
⋅
⋅
.
Uwaga: zadanie ma wiele poprawnych rozwiązań.
98
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, wskaźnik 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, wskaźnikowym 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;
99
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’);
100
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.
101
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.
102
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;
103
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);
104
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ć
105
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 źró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.
106
7.
Wskaźniki
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 wskaźnikowego, w której pamiętany jest adres w pa-
mięci, gdzie ta zmienna dynamiczna się znajduje.
7.1.
Definicja zmiennej wskaźnikowej
Definicja zmiennej wskaźnikowej (w skrócie
−
wskaźnika) określa, jak in-
terpretować zawartość pamięci pod adresem pokazywanym przez wskaźnik.
Na przykład definicja wsk : ^
Byte; oznacza, Ŝe obszar pamięci, pokazywany
przez wskaźnik wsk, zawiera liczbę typu
Byte.
Przykłady deklaracji wskaźników:
var
wsk_int : ^
Integer; {wskaźnik na zmienną typu Integer}
wsk_char : ^
Char; {wskaźnik na zmienną typu Char}
wsk_real : ^
Single; {wskaźnik na zmienną typu Single}
type
tab =
array [1..10] of Integer; {definicja typu tablicowego}
wsk = ^ tab; {definicja typu będącego wskaźnikiem 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 wskaźników
do liczb całkowitych typu
Integer}
var p : Pointer; {wskaźnik 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 wskaźnikowego i na odwrót.
Uwagi dotyczące wskaźników:
•
deklaracja zmiennej wskaźnikowej nie jest jednoznaczna z utworzeniem
zmiennej wskazywanej,
107
•
kaŜda zmienna wskaźnikowa 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 wskaźnik moŜe odwoływać się do róŜnych miejsc w pamięci,
ale nie równocześnie,
•
wskaźnik słuŜący do pokazywania na obiekty jednego typu nie moŜe
(zazwyczaj) pokazywać na obiekty innego typu,
•
za pomocą wskaźnika uzyskujemy dostęp do pewnego obszaru pamięci
i moŜemy modyfikować jego zawartość,
•
wskaźniki mogą być elementami rekordów,
•
dozwolone jest uŜywanie wskaźników do rekordów.
Zmiennej wskaźnikowej 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 wskaźnik,
•
przypisanie wartości innej zainicjalizowanej zmiennej wskaźnikowej.
7.2.
Procedury dynamicznego rezerwowania i zwalniania pamięci
Wskaźnik 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 wskaźnika 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ą wskaźnika,
•
obiekty tworzone w ten sposób są dynamiczne i nie są wstępnie inicjali-
zowane zerami.
Standardowa stała
nil oznacza, Ŝe zmienna wskaźnikowa 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
108
Rys. 7.1. Wskaźnik z adresem pustym (nil)
Przypisanie wskaźnikowi wartości
nil jest wykorzystywane do zaznaczenia,
Ŝ
e wskaźnik nie pokazuje na nic konkretnego. Bez takiego przypisania wskaźnik
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 wskaźnika w typu ^typ1, który wskazuje na zmien-
ną typu typ1.
•
nadanie wartości tego wskaźnika 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 wskaźnik w, konkret-
nej wartości jakiegoś wyraŜenia.
var w : ^typ1;
New(w);
w^ := wyraŜenie;
Rys. 7.2. Wskaźnik 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;
109
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 wskaźnik 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. Wskaźnik 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 wskaźniki na ten sam łańcuch znakowy.
program wsk2;
type TDane = string[60];
var w1, w2 : ^TDane;
BEGIN
New(w1);
w1^ := 'Cos tam';
110
w2 := w1;
w2^ := 'Cos innego tam jest';
writeln(w1^);
writeln(w2^);
dispose(w2);
readln;
END.
Przykład 3.
UŜycie zmiennych wskaźnikowych 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 wskaźnikowi 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.
111
przydzielenie pamięci:
zwolnienie pamięci:
New (p);
Dispose (p);
GetMem (p, liczba_bajtów);
FreeMem (p, liczba_bajtów);
7.3.
Wskaźniki i tablice
Wskaźniki są przydatne do pracy z tablicami. Zapis w := @tab[3]; powo-
duje, Ŝe wskaźnik 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 wskaźnikó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 wska
ź
nikó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. Wskaźnik do tablicy statycznej.
program wsk5;
const MaxN = 5;
type Tab = array [1..MaxN] of Byte;
wskTab = ^Tab;
var i : Byte;
B : Tab; //tablica statyczna
112
w : wskTab;
BEGIN //
↓
wypełnienie tablicy statycznej liczbami
for i := 1 to MaxN do B[i]:=i;
w := @B; // ustawienie wska
ź
nika na tablic
ę
writeln(w^[1]); // writeln(B[1]);
inc(w); // inkrementacja adresu
writeln(w^[2]); // poza adres całej tablicy
w := @B[3]; // wska
ź
nik pokazuje na trzeci element
writeln(w^[1]);
readln;
END.
Przykład 3. Wskaźniki 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.
113
7.4.
Operacje arytmetyczne na wskaźnikach
PoniewaŜ wskaźniki przechowują adresy, które są liczbami, to dopuszczal-
ne są niektóre operacje arytmetyczne na wskaźnikach:
•
dodawanie do
−
i
odejmowanie od wskaźników liczb naturalnych, za
pomocą procedur inkrementacji
Inc i dekrementacji Dec, powoduje
przesuwanie wskaźników w kierunku wyŜszych lub niŜszych adresów.
PoniewaŜ wskaźnik pokazuje na obiekt pewnego typu i znany
jest rozmiar tego typu, więc wiadomo o ile bajtów trzeba prze-
sunąć wskaźnik.
Operacje zwiększania i zmniejszania wskaźników mają istotne
zastosowanie w stosunku do elementów tablic. JeŜeli np.
zmienna wskaźnikowa 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 wskaźnikowej 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 wskaźnika 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 wskaźników pokazujących na tę samą tablicę
−
w
wyniku dostajemy liczbę elementów tablicy dzielących elementy poka-
zywane przez oba wskaźniki. Liczba ta moŜe być ujemna lub dodatnia.
•
porównywanie wskaźników
−
wskaźniki moŜna ze sobą porównywać
za pomocą operatorów porównania: =,
<>, <, >, <=, >=,
równość wskaźników oznacza, Ŝe pokazują one na ten sam obiekt,
wskaźnik, 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 wskaźnikowymi, to porównanie adresów
przechowywanych w tych wskaźnikach przeprowadza się tak: w < x, natomiast
porównanie zawartości komórek pamięci odbywa się tak: w^ < x^.
114
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
wskaźnikowego, wykorzystywanego jako łącznik z następnym elementem listy.
type
wsk = ^ skladnik;
skladnik =
record
dane : typ;
next : wsk
end;
115
Dostęp do listy odbywa się poprzez wskaźnik, który zawiera adres pierw-
szego elementu na liście. Wskaźnik ten nazywany jest początkiem bądź 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.
116
•
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; // wska
ź
nik na nast
ę
pny elem.
end;
TLista = PElem; // wska
ź
nik 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;
117
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),
118
•
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; {wska
ź
nik 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;
119
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}
120
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);
121
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 wskaźnikó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ę wskaźnikó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;
122
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 wskaźnikiem 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 wskaźnikach. 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.
123
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-
124
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 źró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ą odpowiedź 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.
125
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;
126
// 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;
127
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
128
Result := d;
end;
var Pro1 : TProstokat;
BEGIN // PROGRAM GŁÓ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}
129
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');
130
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
131
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;
132
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;
133
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 wska
ź
nikiem 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.
134
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-
135
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;
type TRodzic = class
procedure napisz;
procedure wykonaj;
end;
procedure TRodzic.napisz;
begin
writeln('To napisał >> Rodzic');
end;
procedure TRodzic.wykonaj;
begin
napisz;
end;
type TPotomek = class(TRodzic)
procedure napisz;
end;
procedure TPotomek.napisz;
begin
writeln('to napisał > Potomek' );
end;
var potomek :TPotomek;
BEGIN
Potomek:= TPotomek.Create;
Potomek.wykonaj;
Readln;
Potomek.Free;
END.
program dziedzicz_polimorf;
type TRodzic = class
procedure napisz; virtual;
procedure wykonaj;
end;
procedure TRodzic.napisz;
begin
writeln('To napisał >> Rodzic');
end;
procedure TRodzic.wykonaj;
begin
napisz;
end;
type TPotomek = class(TRodzic)
procedure napisz; override;
end;
procedure TPotomek.napisz;
begin
writeln('to napisał > Potomek' );
end;
var potomek :TPotomek;
BEGIN
Potomek:= TPotomek.Create;
Potomek.wykonaj;
Readln;
Potomek.Free;
END.
136
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.
137
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;
138
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;
139
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;
140
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.
141
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óźniej 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).
142
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
143
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
144
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ę źró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.
145
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 źró-
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 źródeł
Edytor źródeł umoŜliwia podgląd oraz edycję kodu źró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 źró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.
146
•
Plik modułu z rozszerzeniem .PAS
−
plik tekstowy zawierający kod
ź
ró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 źró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 źró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.
147
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
148
•
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 źró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
149
Caption
Napis na komponencie (tekst)
Height
Wysokość komponentu
Width
Szerokość komponentu
Color
Kolor tła obiektu
Cursor
Kursor wyświetlany po umieszczeniu wskaźnika myszy nad
obiektem
Enabled
Określa, czy obiekt jest aktywny czy teŜ nie
Font
Czcionka uŜywana przez komponent
Hint
Wskazówka (etykietka podpowiedzi), pokazywana po umiesz-
czeniu kursora nad obiektem
Name
Nazwa komponentu
Visible
Właściwość określająca, czy komponent ma być widoczny pod-
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
OnDragDrop
Zdarzenie generowane jest w momencie, gdy w komponencie na-
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
150
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);
151
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 }
152
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.
153
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
154
//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.).
155
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);
156
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.
157
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
158
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
159
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
160
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.
161
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.
162
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.
163
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;
164
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;
165
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;
166
//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ą.
167
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+}
168
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);
169
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
170
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).
171
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;
172
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;
173
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.
174
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);
175
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
176
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:
177
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);
178
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]);
179
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.
180
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.).
181
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