e Wstep do programowania DS

background image

background image

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


background image

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

background image

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

background image

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

background image

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

background image

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.

background image

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

background image

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

background image

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.

background image

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

background image

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-

background image

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

background image

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:

background image

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

background image

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.

background image

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

background image

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-

background image

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.

background image

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

background image

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.

background image

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-

background image

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-

background image

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

background image

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.

background image

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.

background image

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

background image

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

background image

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

background image

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

background image

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)

background image

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

background image

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

background image

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

background image

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.

background image

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

background image

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ą

background image

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.

background image

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.

background image

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

ść

background image

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

background image

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

background image

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;

background image

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;

background image

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;

background image

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);

background image

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,

background image

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.

background image

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.

background image

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.

background image

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

background image

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.

background image

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-

background image

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.

background image

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;

background image

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.

background image

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

background image

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

background image

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

background image

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;

background image

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.

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

background image

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

background image

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,

background image

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ą

beginend,

instrukcja jest wykonywana tak długo, jak długo wartość warunku jest
równa

True,

background image

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

background image

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

background image

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

background image

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');

background image

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.


background image

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;

background image

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;

background image

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

background image

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ą

beginend;

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);

background image

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.

background image

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.

background image

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

background image

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

background image

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

background image

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;

background image

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.

background image

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.

background image

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

background image

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-

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;

background image

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.

background image

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;

background image

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;

background image

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,

background image

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;


background image

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;

background image

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;

background image

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;

background image

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-

background image

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.

background image

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.

background image

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

background image

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.

background image

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

background image

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;

background image

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’);

background image

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.

background image

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.

background image

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;

background image

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);

background image

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ć

background image

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.

background image

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,

background image

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

background image

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;

background image

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; { ąd zakresu zgłaszany przez kompilator }

wpom^ [25] :=

7; {ą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';

background image

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.

background image

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

background image

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.

background image

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

background image

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;

background image

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.

background image

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;

background image

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),

background image

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;


background image

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}

background image

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);

background image

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;

background image

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.


background image

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-

background image

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.

background image

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;

background image

126

// Pro2 := Tprostokat.Create;
// pro2.a := 10; pro2.b := 20; //

ą

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;

background image

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

background image

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}

background image

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');

background image

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

background image

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;

background image

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;

background image

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.

background image

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-

background image

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.

background image

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.

background image

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;

background image

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;


background image

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;



background image

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.

background image

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

background image

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

background image

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

background image

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.

background image

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.

background image

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.

background image

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

background image

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

background image

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


background image

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);

background image

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 }

background image

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.

background image

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; //

ą

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

background image

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

background image

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);

background image

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.

background image

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

background image

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

background image

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

background image

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.

background image

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.

background image

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.

background image

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;

background image

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;

background image

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;

background image

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

background image

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+}

background image

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);

background image

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

background image

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

GraphOpen – 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).

background image

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;

background image

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;

background image

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.

background image

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);

background image

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

background image

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:

background image

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);

background image

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

background image

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.

background image

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

background image

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


Wyszukiwarka

Podobne podstrony:
Projektowanie oprogramowania Wstep do programowania i techniki komputerowej
2011-2012 wstęp do P program, wstęp do psychologii k
zarz procesami planowanie, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Gorazd T Kurs C Wstęp do Programowania
PHP Praktyczne wprowadzenie R 4 Wstęp do programowania Proste skrypty PHP
klas sys komp, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Wstęp do programu z poprawką, bierzmowanie
entropia kodowanie, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
All, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
def informatyka, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
pradygmaty prog, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Wilkosz, Wstęp do programowania, kolokwia KD1-09 10l
Wilkosz, Wstęp do programowania, kolokwia K2-08 09l
srod programowania translatory, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
jezyk bnf ebnf, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Wilkosz, Wstęp do programowania, kolokwia K2-10 11l
System operacyjny, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI

więcej podobnych podstron