Post on 27-Feb-2019
WINCENTY PIRJANOWICZ
PROGRAMOWANIEKOMPUTERÓW
( W tym pliku znajdują się materiały ze skryptu W. Pirjanowicza dotyczące algorytmów, opisuje się język Pascal z dużą ilością przykładów oraz proponuje się 10 wariantów zadań do kolokwium lub egzaminu )
WYŻSZA SZKOŁA INFORMATYKI I EKONOMIITOWARZYSTWA WIEDZY POWSZECHNEJ W OLSZTYNIE
Olsztyn 2001
SPIS TREŚCI
PRZEDMOWA_____________________________________________________________________4
1. WSTĘP_________________________________________________________________________51.1. Kilka ważniejszych definicji____________________________________________________________6
1.2. Klasyfikacja narzędzi informatycznych___________________________________________________7
1.3. Klasyfikacja oprogramowania__________________________________________________________7
2. PROCESY ROZWIĄZYWANIA PROBLEMÓW STOSUNKOWO PROSTYCH_______________9
3. STRUKTURY DANYCH___________________________________________________________123.1. Klasyfikacja typów danych____________________________________________________________13
3.1.1. Proste typy danych........................................................................................................................................ 133.1.2. Strukturalne typy danych............................................................................................................................... 16
4. ALGORYTMY, METODY OPISYWANIA ALGORYTMÓW_____________________________214.1. Pseudokod__________________________________________________________________________22
4.2. Schematy blokowe___________________________________________________________________24
4.3. Schematy grafowe____________________________________________________________________26
4.4. Tablicy decyzyjne____________________________________________________________________33
4.5. Zadania do ćwiczeń__________________________________________________________________34
5. ZŁOŻONOŚĆ I EFEKTYWNOŚĆ OBLICZENIOWA ALGORYTMÓW____________________365.1. Klasy złożoności problemów kombinatorycznych_________________________________________36
5.2. Algorytmy rekurencyjne, iteracje_______________________________________________________38
5.3. Modularyzacja algorytmów____________________________________________________________41
5.4. Struktury blokowe (zasięg ważności deklaracji obiektów)__________________________________43
5.5. Zadania do ćwiczeń__________________________________________________________________46
6. WYSZUKIWANIE, SORTOWANIE DANYCH. GENEROWANIE PERMUTACJI LOSOWYCH_________________________________________________________________________________47
6.1. Wyszukiwanie danych________________________________________________________________47
6.2. Sortowanie danych___________________________________________________________________506.2.1. Sortowanie bąbelkowe................................................................................................................................... 516.2.2. Sortowanie przez selekcję.............................................................................................................................. 516.2.3. Sortowanie przez wstawianie......................................................................................................................... 536.2.4. Sortowanie szybkie (QuickSort).................................................................................................................... 54
6.3. Scalanie ciągów uporządkowanych_____________________________________________________55
6.4. Sortowanie zewnętrzne (plików)________________________________________________________57
6.5. Pojęcie o problemach i metodach szeregowania___________________________________________58
6.6. Generowanie permutacji losowych______________________________________________________58
6.7. Zadania do ćwiczeń__________________________________________________________________60
7. JĘZYK PROGRAMOWANIA PASCAL_______________________________________________617.1. Alfabet języka i struktura programu____________________________________________________62
2
7.2. Typy danych________________________________________________________________________66
7.3. Deklaracje_________________________________________________________________________697.3.1. Deklaracja etykiet.......................................................................................................................................... 697.3.2. Deklaracja stałych......................................................................................................................................... 707.3.3. Deklaracja typów........................................................................................................................................... 707.3.4. Deklaracja zmiennych................................................................................................................................... 71
7.4. Instrukcje__________________________________________________________________________717.4.1. Instrukcja przypisania.................................................................................................................................... 727.4.2. Instrukcja skoku............................................................................................................................................ 737.4.3. Instrukcje wywołania procedur, wprowadzania / wyprowadzania danych......................................................737.4.4. Instrukcja złożona......................................................................................................................................... 757.4.5. Instrukcje warunkowe IF i CASE................................................................................................................... 767.4.6. Instrukcje określania pętli FOR, WHILE, REPEAT.......................................................................................777.4.7. Zadania do ćwiczeń....................................................................................................................................... 80
7.5. Procedury i funkcje__________________________________________________________________817.5.1. Deklaracje oraz przykłady zastosowań procedur i funkcji..............................................................................817.5.3. Sposoby porzucenia iteracji pętli, pętli, procedury, programu........................................................................987.5.4. Zadania do ćwiczeń..................................................................................................................................... 100
7.6. Typy strukturalne__________________________________________________________________1017.6.1. Tablice........................................................................................................................................................ 1017.6.2. Rekordy....................................................................................................................................................... 1047.6.3. Zbiory......................................................................................................................................................... 107
7.7. Pliki______________________________________________________________________________1087.7.1. Zadania do ćwiczeń..................................................................................................................................... 112
7.8. Dynamiczne struktury danych________________________________________________________1127.8.1. Organizacja struktur danych w postaci stosów i kolejek...............................................................................1147.8.2. Zadania do ćwiczeń..................................................................................................................................... 118
7.9. Dodatkowe informacje związane z systemem Turbo Pascal________________________________1187.9.1. Biblioteki procedur i funkcji........................................................................................................................ 1187.9.2. Podstawowe procedury i funkcje modułu GRAPH.......................................................................................1227.9.3. Zlecenia edytora tekstu systemu Turbo Pascal.............................................................................................1347.9.4. Zadania do ćwiczeń..................................................................................................................................... 135
8. WPROWADZENIE DO PROGRAMOWANIA W SYSTEMIE DELPHI___________________1368.1. Historia rozwoju, podstawowe pojęcia języków programowania obiektowego_________________136
8.2. Podstawowe informacje o systemie programowania Delphi________________________________1408.2.1. Interfejs użytkownika w Delphi................................................................................................................... 1408.2.2. Wykonanie programów pascalowych w Delphi............................................................................................1438.2.3. Struktura modułu w języku Pascal i jej uzupełnienia w Delphi....................................................................144
10. DODATEK. PRZYKŁADOWE TEMATY ZADAŃ DO KOLOKWIUM I EGZAMINÓW_____145
LITERATURA____________________________________________________________________151
3
PRZEDMOWA
Celem skryptu jest wspomaganie studentom nauczania się zasadom rozwiązywania
złożonych zadań za pomocą komputera. Dotyczy to przede wszystkim takich etapów
jak: wybór lub opracowanie algorytmu, ocena jego efektywności obliczeniowej,
opracowanie struktury danych, stosowanie odpowiednich technik programowania do
tworzenia oraz testowania programów. Jako narzędzie do tworzenia własnych
programów opisuje się język Pascal łatwy do zrozumienia, wystarczający do
rozwiązywania wielu problemów praktycznych oraz wszystkich zadań
umieszczonych w tym skrypcie.
Do sprawdzenia uzyskanej wiedzy zaleca się wykonanie zadań przytoczonych na
końcu rozdziałów. Zadania bardziej trudne do rozwiązywania są zwykle na końcu
listy zadań. W dodatku skryptu skompletowano dziesięć wariantów zadań, które
mogą się znaleźć na kolokwiaсh lub egzaminach.
Uważa się, że czytelnik zapoznał się już z podstawowymi pojęciami informatyki
przynajmniej na poziomie przedmiotu „Wstęp do informatyki” i takie pojęcia jak
dane, język programowania, kompilator, program, ... są używane w skrypcie bez ich
definicji.
Skrypt opracowano dla studentów Wyższej Szkoły Informatyki i Ekonomii
Towarzystwa Wiedzy Powszechnej w Olsztynie. Treści rozdziałów dobierano tak, aby
spełnić wymagania programowe, dotyczące wykładów i ćwiczeń z przedmiotu
„Programowanie komputerów” na studiach licencyjnych na kierunku „Informatyka i
Ekonomia.”
Pragnę podziękować Szanownemu Recenzentowi tego skryptu prof. dr hab.
Michałowi Grabowskiemu za szereg cennych uwag i zastrzeżeń, które pozwoliły
skorygować błędy i nieścisłości popełnione przez autora.
W. Pirjanowicz
4
1. WSTĘP
Przez programowanie komputerów (w skrócie programowanie) będziemy
rozumieli umiejętność rozwiązywania problemów za pomocą komputera. Obecnie
programowanie stało się nauką, której opanowanie ma zasadnicze znaczenie dla
rozwiązywania wielu zadań praktycznych.
Zapoczątkowały rozwój programowania jako nauki pracy Dijkstry i Hoare’a. Hoare
C.A.R. w artykule „Axiomatic basis of computer programming (publikacja w Comm.
ACM, 1969, 12, No. 10, s. 576-583) udowadnia, że programy można analizować
stosując ścisłe rozumowania matematyczne, a przez trzy lata, Dijkstra E.W. w pracy
„Notes on structured programming” (publikacja w Structured programming, New York,
Academic Press 1972, s. 1-82) pokazuje programowanie jako przedmiot nauki i
współzawodnictwa intelektualnego. W obu pracach dyskutowano problemy
konstruowania algorytmów, aspekty budowy i analizy programów reprezentujących
algorytmy w komputerach. Ważną publikacją, która wprowadziła porządek do
różnorodnych terminologii i pojęć dotyczących sposobu przedstawiania (struktur)
danych, była praca Hoare’a „Notes on data strukturing” (Structured programming, New
York, Academic Press 1972, s. 83-174). Wynika z niej, że struktura i wybór
algorytmów rozwiązujących problem ściśle zależą od struktury danych dotyczących
tego problemu. Czyli, problemy tworzenia programów oraz sposoby przedstawiania
danych są ze sobą ściśle powiązane.
Systematyczne i naukowe podejście do konstrukcji programów proklamowane w
pracach Dijkstry i Hoare’a, ma szczególne znaczenie dzisiaj, kiedy co raz częściej
mamy za zadanie opracowanie dużych, złożonych programów, w których korzysta się
ze skomplikowanych zbiorów danych. Dla tego i my, w tym skrypcie, poświęcimy
część uwagi strukturom danych, algorytmom, a następnie temu, jak realizuje się
algorytmy w postaci programów komputerowych, wybierając w tym celu jako narzędzie
system Turbo Pascal. Będą również przedstawione zasady programowania w systemie
5
Delphi oraz zapoznamy się z podstawami inżynierii oprogramowania zasadami
tworzenia złożonych systemów oprogramowania.
W celu uporządkowania już znanych nam pojęć związanych z informatyką,
wprowadźmy kilka definicji, które będą używane w kolejnych rozdziałach oraz
przypomnijmy [33] klasyfikacje narzędzi informatycznych i oprogramowania
komputerów.
1.1. Kilka ważniejszych definicji
Za pomocą symbolu ":=" będziemy oznaczali operacje przypisania zmiennej
umieszczonej z lewej strony tego symbolu wartości wyrażenia znajdującego się po
stronie prawej. Na przykład: x := 25; y := 5 - sin(x); Średnik najczęściej będzie
oznaczał koniec wykonania operacji.
Pod pojęciem zmienna będziemy rozumieli nazwę własną, identyfikator miejsca
(komórki) w którym można przechowywać jakąś wartość. Przypisanie zmiennej pewnej
wartości oznacza umieszczenie w tym miejscu nowej informacji wymazującej
poprzednią, która w tym miejscu była.
Przez wyrażenie będziemy rozumieli napis określający sposób obliczania pewnej
wartości. Wyrażenie może się składać z jednego argumentu lub kilku argumentów
połączonych przez operatory z wykorzystaniem (w razie potrzeby) nawiasów okrągłych.
Terminem argument określamy stałą, zmienną lub wywołanie funkcji, wynikiem
wykonania której jest pewna wartość (liczbowa, tekstowa lub logiczna).
Pojęcie stała oznacza daną, która nie zmienia swej wartości podczas działania
programu, natomiast zmienna oznacza daną, której wartość może zostać zmieniona.
Stałe tekstowe określamy jako tekst ujęty w apostrofy.
Jeśli w wyrażeniu jako operatory będziemy wykorzystywać tylko operacje
arytmetyczne: +, -, *, /, to wyrażenie będziemy nazywać wyrażeniem arytmetycznym.
Wyrażenie logiczne (warunek) jest to wyrażenie, które może zawierać operacje
arytmetyczne, operatory porównania ("=" równy, "" nierówny, "<" mniejszy niż,
"" mniejszy lub równy, ">" większy, "" większy lub równy) i operacje logiczne:
6
"i" , "lub" , "nie". Te ostatnie operacje, używane w języku Pascal, mają odpowiednio
nazwy and, or, not. Na przykład wartością wyrażenia logicznego:
((5 * A + sin(X)) > B) lub (A < X) ,
w zależności od wartości danych A, B i X , może być prawda (ang. true) lub fałsz
(ang. false), czyli warunek określony przez to wyrażenie jest spełniony lub nie.
1.2. Klasyfikacja narzędzi informatycznych
Do narzędzi informatycznych zaliczamy wszystko to, co pozwala informatyce
funkcjonować i rozwijać się, a mianowicie:
1) komputery oraz urządzenia techniczne współpracujące z nimi,
2) oprogramowanie komputerów,
3) sposoby komunikowania się komputerów pomiędzy sobą,
4) metody (algorytmy) rozwiązywania poszczególnych zadań,
5) sposoby oraz narzędzia organizacji, przechowywania oraz przekazywania danych,
6) języki programowania.
Możemy teraz uściślić zdefiniowane wyżej pojęcie „programowanie komputerów”,
jako umiejętność stosowania narzędzi 4-6 dla tworzenia podstawowego narzędzia
informatyki oprogramowania komputerów w celu rozwiązywania za jego pomocą
poszczególnych zadań. W opanowaniu tej umiejętności kluczową rolę odgrywają
pojęcia związane z algorytmami i strukturami danych.
1.3. Klasyfikacja oprogramowania
W zależności od przeznaczenia, programy komputerowe (oprogramowanie) można
podzielić na dwie podstawowe części: oprogramowanie systemowe i oprogramowanie
użytkowe. Do oprogramowania systemowego odnosimy te programy (pakiety
programów) bez których komputer nie jest w stanie prawidłowo funkcjonować podczas
pracy indywidualnej lub w połączeniu z innymi komputerami.
7
Do oprogramowania użytkowego włączamy te programy lub pakiety programów,
które realizują konkretne zadania merytoryczne i są w stanie zaspokoić potrzeby
poszczególnych użytkowników komputera.
Podstawowe składniki warstw oprogramowania przedstawiono na rys. 1.1. Opis tych
składników można znaleźć w [33].
OPROGRAMOWANIE KOMPUTERÓW
OPROGRAMOWANIE SYSTEMOWE OPROGRAMOWANIE UŻYTKOWE
Systemy operacyjne Systemy programowania
Programy diagnostyki Pakiety programów narzędzio-sprzętu wych ogólnego przeznaczenia
Programy obsługi Standardowe programysieci komputerowych użytkowe
Programy usługowe Indywidualne programyużytkowe
Rys. 1.1. Podstawowe składniki warstw oprogramowania komputerowego
8
2. PROCESY ROZWIĄZYWANIA PROBLEMÓW STOSUNKOWO PROSTYCH
Istnieje bardzo dużo problemów, jak np. automatyczne sterowanie procesami
produkcyjnymi, prognozowanie pogody, sterowanie statkami bezzałogowymi itp., do
rozwiązania których muszą być opracowane złożone systemy oprogramowania.
Sposobami tworzenia takich systemów zajmuje się dziedzina informatyki nazywana
obecnie inżynierią oprogramowania [21,22,26,30,37]. Z podstawami tej nauki można
zapoznać się w rozdziale 8.
W tym rozdziale omówimy procesy komputerowego rozwiązywania problemów
spotykanych dzisiaj codziennie, tak zwanych problemów „średniej trudności”.
Rozwiązywanie takich problemów składa się z następujących podstawowych etapów:
problem projektowanie rozwiązanie problemu analiza poprawności i
efektywności rozwiązania. Współzależności między tymi etapami pokazane są na rys.
2.1.
Etap projektowania jest przeznaczony do określenia warunków, metody działania
oraz struktury programu w formie kompletnej specyfikacji. Przez specyfikację problemu
rozumiemy:
definicję warunków rozwiązywania problemu (typ komputera, zestaw jego
urządzeń, oprogramowanie, dopuszczalny czas trwania rozwiązywania itp.),
opis struktur danych wejściowych i wyjściowych, możliwe związki zachodzące
między nimi, sposób (forma) przedstawiania tych danych na nośnikach,
dobór (konstruowanie) systemu testów dla sprawdzania poprawności rozwiązania
problemu.
Dobór testów stanowiących kryterium oceny poprawności rozwiązania jest częścią
poprawnej specyfikacji problemu i często jest jej najtrudniejszym elementem. Podczas
rozwiązywania stosunkowo prostych zadań etap opracowania specyfikacji dość często
jest pomijany (nie udokumentowany, istniejący wyłącznie w głowie rozwiązującego
problem).
Problem9
Projektowanie
Algorytm
Język programowania Program źródłowy
Translacja Program maszynowy
Dane Wykonanie programuprzez komputer
WynikiOcena poprawnościrozwiązania
Rys. 2.1. Schemat rozwiązania problemu za pomocą komputera
Etap rozwiązywania problemu, w zależności od jego złożoności, potrzebuje mniej
lub więcej pracy. Do rozwiązywania problemu musi być wybrany istniejący algorytm
lub, jeśli takiego nie ma, opracowany nowy. Z kolei dalej musi być ułożony
odpowiedni program w wybranym języku programowania i na jego podstawie
otrzymany program do wykonania w języku maszynowym. Istnieje oczywiście wiele
zadań, do rozwiązywania których może być zastosowane gotowe oprogramowanie (w
takim przypadku etap kodowania i weryfikacji programu pomija się). Po uzyskaniu
programu maszynowego powierza się jego wykonanie komputerowi.
Ocena poprawności otrzymanego rozwiązania problemu jest sprawdzaniem
zgodności wyników rozwiązania ze specyfikacją. Analiza efektywności rozwiązania
potrzebuje wyjaśnienia wartości takich parametrów, jak dokładność obliczeń, czas
trwania obliczeń, potrzebna pamięć komputera dla przechowywania programów, danych
wejściowych i wyjściowych.
W zależności od złożoności problemu i metody jego rozwiązania oraz od wybranego
języka programowania każdy z przedstawionych na schemacie etapów potrzebuje mniej
lub więcej nakładów pieniężnych, czasowych, liczby zatrudnionych wykonawców. Tak
na przykład, ocena poprawności rozwiązania jest podstawową częścią etapu testowania
10
programu. Etap ten, służący do wyszukiwania możliwych błędów w programie, w
jawny sposób nie został przedstawiony na schemacie. Błędy spowodowane naruszaniem
składni języka programowania są dość łatwo wykrywane podczas translacji za pomocą
samego komputera. Natomiast błędy logiki algorytmu, nieodpowiednia definicja danych
lub organizacja pamięci dla ich rozmieszczenia potrzebują wiele wysiłków, by je
odnaleźć i usunąć z programu. W zależności od charakteru odnalezionego błędu jego
usunięcie może potrzebować powrotu do już wykonanych etapów (być może do
uściślenia treści problemu), dokonania niezbędnych zmian i ponownego wykonania
wszystkich kolejnych etapów.
11
3. STRUKTURY DANYCH
Od wyboru właściwej w danym momencie struktury danych zależy szybkość
działania programu komputerowego, łatwość jego modyfikacji, czytelność zapisu
algorytmów. Stąd temat, który zostanie poruszony w tym rozdziale ma szczególne
znaczenie.
Terminu struktury danych używa się w wielu różnych znaczeniach. Na przykład,
może to dotyczyć sposobu przedstawiania (przechowywania) danych w komputerze.
Może to również oznaczać opis, definicję abstrakcyjną pewnego zbioru informacji (z
podaniem ewentualnych relacji pomiędzy jej składowymi) bez odniesienia do nośników
pamięci, w których informacje te będą przechowywane. Definiując abstrakcyjną
strukturę danych (np. stosując pojęcia teorii grafów, sieci) bierze się pod uwagę
wyłącznie takie jej cechy, które nie zależą od ewentualnej realizacji komputerowej.
Prawdopodobnie wspólnym dla różnych interpretacji pojęcia struktury danych będzie
jego określenie jako zgrupowanie elementów wyposażonych w ustalone cechy
syntaktyczne i wykazujące ustalone własności wobec działania ustalonych operacji.
Wybór właściwej struktury danych zawsze powinien być podporządkowany
problemowi, który ma być rozwiązany. Po określeniu struktury danych następuje wybór
reprezentacji tych informacji na nośnikach pamięci komputerowej.
Wybór reprezentacji danych w komputerze zależy nie tylko od możliwości
technicznych komputera. Trzeba również brać pod uwagę operacje, które mają być
wykonywane na danych. Wiemy, że w różny sposób są reprezentowane w komórkach
komputera liczby całkowite i rzeczywiste, symbole tekstowe i graficzne. W związku z
tym rozróżniamy pewne typy danych i operacje, które mogą być wykonywane na
danych każdego z takich typów.
12
3.1. Klasyfikacja typów danych
Obecnie najczęściej używa się następującej klasyfikacji typów danych: proste,
strukturalne, wskaźnikowe. Podając opis każdej z tych grup danych, w nawiasach
będziemy określać oznaczenie odpowiednich typów w języku angielskim, tak, jak te
typy są definiowane w większości języków programowania wysokiego poziomu. Z
kolei dalej będziemy dość często używać tych angielskich notacji, aby Czytelnik mógł
szybciej przyzwyczaić się do definicji typów w językach programowania i łatwiej
zrozumiał rozdział 7 opisujący Pascal.
3.1.1. Proste typy danych
Do typu prostego standardowego należą: całkowity (integer), rzeczywisty (real),
logiczny (boolean), znakowy (char). W językach programowania typy
całkowity i rzeczywisty, oprócz wymienionych notacji standardowych, mogą mieć
dodatkowe notacje, określające kilka możliwych zakresów wartości danych
zdefiniowanych przez te typy. Dla Pascala znajdzie Czytelnik te inne notacje oraz im
odpowiadające zakresy danych w rozdz. 7.2.
Typ integer określa podzbiór wszystkich liczb całkowitych, którego zakres jest
związany z wybranym językiem programowania i może być uzależniony od
właściwości komputera na którym program wykonuje się. Na przykład w implementacji
Pascala dla komputerów zgodnych z IBM PC do typu integer należą liczby całkowite
z zakresu od -32768 do +32767.
Typ real stanowi pewien podzbiór liczb rzeczywistych. Zakłada się, że operacje
arytmetyczne wykonywane nad wartościami typu integer dają wyniki dokładne.
Natomiast wykonanie tych operacji nad wartościami typu real dopuszcza wyniki
niedokładne w ramach pewnych błędów zaokrąglenia spowodowanych wykonywaniem
obliczeń na skończonej liczbie cyfr. Właśnie to jest powodem rozróżniania typów
integer i real w większości języków programowania.
13
Typ boolean logiczny, definiuje tylko dwie wartości oznaczane identyfikatorami
true (prawda) i false (fałsz). Do operatorów logicznych (boolowskich) stosowanych do
wykonania operacji na wartościach tego typu zalicza się koniunkcję (, i, and),
alternatywę (, lub, or), negację (, nie, not). Zastosowanie operatora porównania daje
wynik typu boolean, w związku z tym wynik porównania może być przypisany
zmiennej tego typu lub może być użyty jako argument operatora boolowskiego w
wyrażeniu logicznym.
Typ char znakowy, zawiera 26 liter łacińskich, 10 cyfr arabskich i pewną liczbę
innych znaków graficznych, jak np. znaki przystankowe i inne, które w postaci
pojedynczych symboli mogą być wprowadzane z klawiatury lub drukowane na
drukarce.
Dla umożliwienia konwersji pomiędzy typami char i integer większość języków
programowania oferuje dwie funkcji ord(z) i chr(k). Zastosowanie funkcji ord(z) jako
wynik daje liczbę porządkową znaku z w zbiorze char. Zastosowanie funkcji chr(k)
jako wynik daje k - ty znak ze zbioru char. Jak widać, są to funkcje odwrotne, czyli
mają miejsce równości: ord(chr(k))=k oraz chr(ord(z))=z .
Do typów prostych zaliczają również typy wyliczeniowy i okrojony. Te typy zwykle
muszą być definiowane w programie, gdyż dotyczą szczegółów konkretnego zadania,
które musimy rozwiązać (w odróżnieniu od wyżej wymienionych typów
standardowych, deklaracja których jest znana kompilatorom języków programowania).
Typ wyliczeniowy definiuje się przez podanie w nawiasach wszystkich możliwych
wartości dla zmiennej, którą taki typ będzie określał.
Przykłady:
type obiekt=(etykieta, stała, typ, zmienna, funkcja, procedura);
type struktura=(łańcuch, tablica, rekord, zbiór, plik, lista);
type ranga=(szeregowiec, kapral, sierżant, porucznik, kapitan, major, pułkownik,
generał); 14
type waluta=(złoty, euro, dolar, marka, funt);
Definicja typu wyliczeniowego wprowadza nie tylko nowy identyfikator typu, lecz
jednocześnie zbiór identyfikatorów oznaczających poszczególne wartości
definiowanego typu. Po definicji typu, aby mieć możliwość wykonania operacji z
danymi związanymi z tym typem, musi być zadeklarowana jedna lub więcej (w
zależności od potrzeb) zmiennych, którym mogą być przypisywane poszczególne
wartości definiowanego typu. Na przykład:
var t: obiekt;
var x: struktura;
var y1, y2: ranga;
var z: waluta;
Możliwe są wtedy następujące instrukcje przypisania:
t:= stała; x:= rekord; y1:= kapitan; y2:= pułkownik; z:= złoty;
Zauważmy, że wytłuszczone w powyższych definicjach słowa type i var oznaczają
wykonanie czynności deklarowania, odpowiednio, typów danych oraz zmiennych,
którym przypisuje się wartości określonego typu.
Typ okrojony definiuje granicy przedziału, z którego zmienna pewnego typu
(standardowego lub wcześniej zadeklarowanego) może przyjmować swe wartości.
Przykłady:
type rok_urodzenia = 1930..2000;
type lit = ’A’ ..’z’;
type cyf = ’0’ ..’9’;
type oficer = porucznik .. generał;
15
Zadeklarujemy zmienne, którym mogą być przypisywane wartości typu okrojonego.
Na przykład:
var
ru : rok_urodzenia;
L1, L2 : lit;
C : cyf;
O : oficer;
Możliwe są wtedy następujące instrukcje przypisania:
ru:= 1944; L1:= ’k’; L2:= ’K’; c:= ’4’; O:= generał;
Natomiast niedozwolone są następujące:
ru:= 2001; L1:= ’6’; c:= ’X’; O:= sierżant;
Do typu prostego można odnieść także tak zwany typ łańcuchowy (string)
wprowadzony w systemie Turbo Pascal. Jest on bardzo przydatny do wykonania
operacji z ciągami znaków (tekstami). Ma on postać string[n], gdzie liczba n określa
ilość znaków, które mogą być wprowadzone do zmiennej łańcuchowej. Przyjmuje się,
że typ łańcuchowy string (bez parametru n) jest identyczny z typem string[255].
3.1.2. Strukturalne typy danych
Przez typ strukturalny określa się typ zawierający kilka składowych typu prostego lub
wcześniej zdefiniowanego typu strukturalnego. A więc typ strukturalny zwykle jest
obiektem złożonym. Do typów strukturalnych najczęściej zalicza się typy: tablicowy
(array), rekordowy ( record), zbiorowy ( set), plikowy ( file). Definicja i
użytkowanie tych typów w Pascalu jest przedstawiona w rozdz. 7.6, 7.7. W tym miejscu
określimy pojęcia ogólne dotyczące typów strukturalnych.
Tablice. Tablica jest najbardziej znaną strukturą danych. Jest ona strukturą
jednorodną składa się ze składowych tego samego typu zwanego typem podstawowym
(prostego lub wcześniej zdefiniowanego typu tablicowego). Tablica jest strukturą o
dostępie swobodnym wszystkie składowe mogą być wybrane w dowolnej kolejności i
są jednakowo dostępne. W celu wybrania pojedynczej składowej z tablicy, nazwę 16
zmiennej tablicowej uzupełnia się tzw. indeksami (jednym dla tablicy
jednowymiarowej, dwoma dla tablicy dwuwymiarowej,...) określającymi
jednoznacznie składową tablicy.
type wektor =array [0..10] of real; macierz1 =array [1..15,1..40] of integer; macierz2 =array [1..3] of wektor; wezly =array [1..30] of char; var w : wektor; a, b : macierz1; c : macierz2; wez : wezly;
Do poszczególnych składowych tablicy odwołujemy się za pomocą nazwy zmiennej
tablicowej i podanie w nawiasach kwadratowych wartości indeksów, odpowiadających
wybranej składowej:
w[4] wybór piątej składowej wektora w;
a[3,25] wybór 25 składowej 3-go wiersza macierzy a typu macierz1;
b[3,65] błąd, każdy wiersz macierzy b posiada tylko 40 składowych;
c[62,28] błąd, macierz c posiada tylko 3 wierszy i 10 kolumn.
Rekordy. Typ rekordowy jest ogólną metodą tworzenia typów złożonych poprzez
łączenie w typ złożony elementów o dowolnych, być może złożonych typach. Te
elementy rekordu, jako jego poszczególne składowe, nazywane są często polami
rekordu.
Przykład:
type przedm_naucz = record
kod_przedmiotu : string[6];nazwa_prz : string[20];godz_wykl : 1..60; godz_cwicz : 1..60;
end;
17
var P : przedm_naucz ; Spis : array [1..15] of przedm_naucz;
W tym przykładzie określiliśmy typ rekordowy przedm_naucz oraz zdefiniowaliśmy
dwie zmienne typu rekordowego P i Spis. Składowym zmiennej rekordowej P mogą być
przypisane cztery wartości dotyczące jednego wybranego przedmiotu nauczania (kod
przedmiotu, nazwa przedmiotu, liczba godzin wykładu, liczba godzin ćwiczeń).
Natomiast zmienna rekordowa Spis określa listę z 15 rekordów pozwalających opisać
15 przedmiotów.
Aby wyszczególnić pewne pole zmiennej rekordowej zapisuje się nazwę tej
zmiennej oraz oddzieloną od niej za pomocą kropki, nazwę pola zdefiniowaną w typie
rekordowym. Na przykład, przypisanie przedmiotowi liczby trzydziestu godzin
wykładowych dokonuje się następująco:
p.godz_wykl := 30;
Przypisanie trzeciemu na liście przedmiotowi nazwy Informatyka wygląda
następująco:
Spis[3]. nazwa_prz := ’Informatyka’;
Zbiory. Typ zbiorowy ( set) jest trzecią oprócz tablic i rekordów strukturą
danych. Definiuje się on za pomocą deklaracji o postaci:
type Z= set of T ;
gdzie Z jest nazwą typu zbiorowego, który jest deklarowany, T typ składowych
zbioru. Wartościami zmiennej x typu Z są zbiory elementów typu T.
Podczas rozwiązywania zadań praktycznych typ zbiorowy wykorzystuje się dość
rzadko. Osób zainteresowanych szczegółami stosowania tego typu w Pascalu odsyłamy
do rozdziału 7.6.3 lub do zapoznania się z książką Niklausa Wirtha [41].
18
Pliki. Omówione dotychczas struktury danych tablice, rekordy i zbiory
charakteryzuje wspólna cecha, jaką jest skończoność mocy każdej z tych struktur.
Reprezentacja takich danych w pamięci komputera nie sprawia dużych kłopotów.
Natomiast w praktyce używa się również tzw. wysoce zorganizowane struktury o
mocy nieskończonej ciągi, drzewa, grafy itd. Do reprezentacji w komputerze drzew i
grafów mogą być zastosowane tzw. funkcje rekurencyjne, dynamiczne struktury danych
o których mowa w kolejnych rozdziałach skryptu.
W tej chwili określimy pojęcie ciągu. Ciąg o typie podstawowym To jest to albo ciąg
pusty, albo konkatenacja ciągu (o typie podstawowym To) z wartością typu To . Z tej
definicji wynika, że każda konkretna wartość ciągu zawiera skończoną liczbę
składowych typu To. Natomiast dla każdego takiego ciągu o skończonej liczbie
składowych można skonstruować ciąg od niego dłuższy. Czyli, liczba składowych które
może zawierać ciąg jest nieograniczona.
Tego typu rozważania można zastosować do innych wysoce zorganizowanych
struktur (drzew, grafów). Najważniejszym wnioskiem wynikającym z tych rozważań
jest fakt, że wielkość pamięci potrzebnej do pomieszczenia wartości wysoce
zorganizowanego typu strukturalnego nie jest znana podczas translacji programu.
Powstaje więc problem zorganizowania pewnego schematu dynamicznego przydziału
pamięci, w którym pamięć jest pobierana wtedy, gdy odpowiednie wartości wzrastają i
zwalniana gdy te wartości maleją.
Ten schemat dynamicznego przydziału pamięci zwykle jest związany z każdym
konkretnym rozwiązywanym problemem i dla tego dowolne struktury danych tego typu
powinni być konstruowane przez programistę. Jest to oczywiście możliwe tylko wtedy,
gdy stosowany język programowania posiada możliwości dynamicznego przydzielania
pamięci elementom takich struktur, ich dynamicznego łączenia oraz odwoływania się do
tych elementów. Sposoby generowania takich struktur w środowisku Turbo Pascal są
omawiane w rozdz. 7.8.
19
Natomiast jednej struktury ciągu, wysoce zorganizowanego w tym sensie, że jego
moc jest nieskończona, używa się tak często i w tak dużej liczbie zastosowań, że zalicza
się go do zbioru struktur podstawowych.
Aby zaznaczyć, że ciąg wprowadzony jako podstawowa struktura danych pozwala na
stosowanie jedynie ograniczonego zbioru operatorów i ma ściśle sekwencyjny dostęp do
składowych, strukturę tą nazywają plikiem sekwencyjnym lub krótko plikiem.
Sposoby definicji plików oraz przykłady ich wykorzystania w języku Pascal są
przedstawione w rozdz. 7.7.
20
4. ALGORYTMY, METODY OPISYWANIA ALGORYTMÓW
Jednym z ważniejszych etapów rozwiązywania problemów za pomocą komputera jest
tworzenie metody (lub jej wyboru spośród istniejących), która może być zastosowana
do rozwiązania określonego zadania. W zależności od złożoności metody i sposobu jej
przedstawienia w postaci algorytmu nakłady pieniężne, czasowe i inne będą mniejsze
lub większe podczas wykonywania kolejnych etapów rozwiązywania problemu.
Przez algorytm określa się opis procesu przetwarzania informacji. Uściślając tą
definicją, możemy określić algorytm jako opis danych wejściowych, potrzebnych do
rozwiązania problemu, łącznie z jednoznacznie zrozumiałym opisem czynności
(deklaracji, instrukcji, zleceń) pozwalających na podstawie wartości danych
wejściowych otrzymać oczekiwane wyniki. Liczba operacji algorytmu (zleceń
wykonanych przez komputer) powinna być skończona, to znaczy powinna być taką, aby
komputer w dopuszczalnym terminie swojej pracy był w stanie wykonać te operacje.
Tylko w takim przypadku możemy mówić o wykonywalności algorytmu. Czyli, chodzi
tu o możliwość realną, a nie tylko teoretyczną otrzymania potrzebnych wyników przy
pomocy wybranego algorytmu.
Wszystkie algorytmy ze względu na swoją konstrukcję można podzielić na cztery
grupy: proste, rozgałęzione, cykliczne, mieszane.
Do algorytmów prostych odnosimy algorytmy, zawierające sekwencje
poszczególnych instrukcji, z których każda jest wykonywana tylko jeden raz.
Przez algorytmy rozgałęzione rozumiemy algorytmy dopuszczające alternatywność
rozwiązań danego problemu w zależności od spełnienia określonego warunku. W
zależności od tego, jaką wartość on przyjmuje (spełnia się warunek albo nie), następuje
wybór jednej z dwóch możliwych dróg realizacji algorytmu. Również często w tego
typu algorytmach stosuje się zapytania wielowariantowe (w językach programowania
odpowiednią takiemu zapytaniu instrukcją jest instrukcja case). W zależności od
uzyskanej odpowiedzi na takie zapytanie istnieje wiele alternatywnych dróg kontynuacji
rozwiązywania danego problemu.21
Algorytm nazywamy cyklicznym, jeśli określa on wielokrotne powtórzenie
wykonania tego samego typu komend. Spośród algorytmów cyklicznych rozróżniamy
algorytmy rekurencyjne i algorytmy iteracyjne. Bardziej szczegółowo każdy z tych
typów algorytmów omawia się w rozdziale 5.2.
Przez algorytmy mieszane rozumiemy algorytmy, w których poszukiwanie
rozwiązania problemu jest oparte na połączeniu jednego lub kilku z ww. typów
algorytmów.
Określony problem dość często może być rozwiązany na wiele sposobów, przez
różne algorytmy. Wybór najlepszego algorytmu spośród kilku istniejących jest jednym z
ważniejszych problemów przy wykorzystywaniu komputerów. Wyboru tego dokonuje
się według ustalonego kryterium, np. dokładności oczekiwanych wyników, liczby
elementarnych operacji, potrzebnych do rozwiązania problemu lub minimalizacji
potrzebnej pamięci komputera.
Najczęściej używanymi sposobami przedstawiania algorytmów są pseudokod i
schematy blokowe. Interesującą metodą jest przedstawianie algorytmów za pomocą
schematów grafowych. Rozwiązywanie pewnych problemów może być przedstawione
w postacie tzw. tablic decyzyjnych.
4.1. Pseudokod
Przez opis algorytmu za pomocą pseudokodu (pseudojęzyka programowania)
będziemy rozumieli określenie algorytmu z wykorzystaniem słów języka naturalnego z
konstrukcjami napisów zbliżonymi do składni odpowiednich instrukcji języków
programowania wysokiego poziomu (np. do składni instrukcji języka Pascal).
Wyjaśnimy tą metodę na przykładach, rozwiązując następujące zadania.
Zadanie 4.1. Obliczyć sumę Y = X[1] + X[2] +...+ X[N], w której liczba
całkowita N określa liczbę składników sumy, a zmienne rzeczywiste X[i], i = 1,
2, ..., N, są danymi wejściowymi.
22
W przedstawionym niżej algorytmie liczba powtórzeń pętli jest zdefiniowana na
podstawie znanej liczby składników sumy (przyjmuje się, że wartość zmiennej N przed
wykonaniem pętli sumowania będzie określona). Zlecenie "Pisz" wyprowadza wartości
listy obiektów podanej w nawiasach, to znaczy wyprowadza wartość stałej tekstowej
tak, jak ona jest zapisana w apostrofach oraz obliczoną wartość zmiennej Y.
Początek;Zmienne I, N liczby całkowite;
Y liczba rzeczywista;X[1..N] tablica liczb rzeczywistych;
Czytaj (N, X); I:=0; Y:=0; Wykonuj N iteracji następnej pętli
Początek_pętliI:=I+1; Y:=Y+X[I]
Koniec_pętli; Pisz ( 'Suma = ', Y)
Koniec.
Przykładem algorytmu z określeniem pętli przez warunek jest rozwiązanie
następującego zadania.
Zadanie 4.2. Znaleźć największy wspólny dzielnik dla dodatnich liczb całkowitych
X i Y, tj. NWD(X,Y), wykorzystując algorytm Euklidesa.
Algorytm rozwiązania tego zadania przedstawiony w postaci pseudokodu wygląda
następująco:
23
Początek;
X, Y, R1, R2 liczby całkowite;
Czytaj (X,Y); R1:=X; R2:=Y;
Dopóki R1 nie jest równe R2 wykonuj
(Jeśli R1 > R2 to R1 := R1-R2,
w przeciwnym przypadku R2 := R2-R1);
Pisz ('X = ', X, ' Y = ', Y, ' NWD(X,Y) = ', R1);
Koniec.
4.2. Schematy blokowe
Przy opisywaniu algorytmu w postaci schematu blokowego wykorzystuje się symbole
graficzne. Najczęściej używane z nich są przedstawione na rys. 4.1.
Początek, koniec lub przerwa algorytmu.
Wprowadzanie / wyprowadzanie danych.
Operacja (proces).
Proces alternatywny.
Proces uprzednio zdefiniowany (moduł).
Warunek (blok decyzyjny).
Operacja sumowania.
Operacja rozdzielenia.
Operacja sortowania.
Operacja wybierania.
Operacja łączenia.
Rys. 4.1 (początek). Symbole graficzne schematów blokowych.
24
Łącznik stronicowy.
Łącznik międzystronicowy.
Pamięć wewnętrzna.
Pamięć o dostępie sekwencyjnym.
Pamięć o dostępie bezpośrednim.
Dysk magnetyczny.
Monitor.
Dokument.
Dokument złożony.
Rys. 4.1 (zakończenie). Symbole graficzne schematów blokowych.
Schemat blokowy nazywamy strukturalnym, jeżeli jest on skonstruowany z
elementów przedstawionych na rys. 4.2. Na rysunku tym symbol "B" oznacza warunek,
a znaki "+ -" jego spełnienie lub niespełnienie. Przez "A1", "A2" określone są
odpowiednie operacje. Struktury oznaczają: a) operacja prosta, b) operacja
warunkowa, c), d) dwa warianty definicji pętli.
a) b) c) d)+ - -
B B A1+
- +A1 A1 A2 A1 B
Rys. 4.2. Konstrukcje strukturalne schematów blokowych
25
Sposoby łączenia symboli graficznych między sobą powinny być zrozumiałe po
obejrzeniu rys. 4.3, który przedstawia schemat blokowy algorytmu rozwiązania zadania
4.2.
Początek
Czytaj(X,Y)
R1:=X; R2:=Y;
< 0 R1-R2 > 0
R2:=R2-R1; = 0 R1:=R1-R2;
Pisz(X,Y,R1)
Koniec
Rys. 4.3. Schemat blokowy odnalezienia NWD (X,Y)
4.3. Schematy grafowe
Sposób opisywania algorytmów w postaci schematów grafowych w Polsce prawie nie
jest znany. Czytelnik, który nie jest (nie zamierza być) matematykiem lub
informatykiem może spokojnie opuścić ten rozdział. Zawarte w nim informacje nie są
konieczne do zrozumienia dalej omawianych zagadnień.
Umieszczenie w skrypcie sposobu opisywania algorytmów w postaci schematów
grafowych uważamy za stosowne z następujących powodów:
1) Jest to ciekawy, przynajmniej w sensie teoretycznym, sposób opisywania
algorytmów. W byłym Związku Radzieckim, a obecnie w Rosji, na Ukrainie i w
Białorusi nauczanie inżynierii oprogramowania (technologiom programowania)
bazuje się na tej metodzie przedstawiania algorytmów jako na w pełni
konkurencyjnej z innymi [R1-R4].
26
2) Metoda opisywania algorytmów za pomocą schematów grafowych jest wdrożona w
odpowiednie narzędzia CASE środki techniczne i programowe wspomagające pracę
nad złożonymi systemami oprogramowania. Opracowano te narzędzia w Kijowie, jak
dla dużych komputerów, tak i dla komputerów typu IBM PC [R1]. Translator,
wbudowany w narzędzia CASE, konwertuje schematy grafowe algorytmu
bezpośrednio w program maszynowy.
Struktury schematów grafowych. Podczas stosowania schematów grafowych używa
się dwóch struktur i dwóch operacji na nich. Pierwsza struktura nazywa się strukturą
bazową i w najprostszym przypadku ma postać następującą:
B o o
A
Można zatem powiedzieć, że jest to strzałka (łuk), łącząca dwa wierzchołki. Nad i
pod strzałką mogą występować dowolne napisy "B" i "A", o znaczeniu: "B" - warunek,
"A" - dowolna operacja, pod którą będziemy rozumieli ciąg operatorów nadawania
wartości zmiennym, wprowadzanie lub wyprowadzanie danych, operatory wywołania
funkcji lub procedur. Wykonanie operacji "A" oraz przejście od lewego wierzchołka do
prawego w tej strukturze możliwe jest tylko wtedy, gdy warunek "B" ponad strzałką
będzie spełniony. Na przykład, w następnej strukturze:
X < Y o o Y := Y-X
wykonanie operacji pod strzałką i przejście do prawego wierzchołka jest możliwe tylko
wtedy, gdy warunek nad strzałką jest spełniony.
Bardziej ogólne określenie struktury bazowej jest przedstawione na rys. 4.4.
B1 o o
A1
27
... ...Bn
An
Bn+1
An+1... ...
Bn+k
An+k
Rys. 4.4. Struktura bazowa
W strukturze tej przez Bi oznaczone są warunki, przez Ai operacje, i=1,2,...,n+k.
Umówmy się, że strzałki skierowane od lewej strony do prawej nazywać będziemy
prawymi, a strzałki w odwrotnym kierunku lewymi.
Reguły wykonania bazowej struktury są następujące:
1) Wybiera się pierwszą (od góry) z prawych strzałek ze spełnionym warunkiem,
wykonuje się odpowiednie operacje opisane pod tą strzałką i przechodzi się do
prawego wierzchołka.
2) Sprawdza się, czy są w tej strukturze lewe strzałki. Gdy takie strzałki są, to
wybiera się pierwsza z nich (od góry) ze spełnionym warunkiem nad strzałką,
następnie wykonuje się odpowiednie operacje określone pod tą strzałką oraz wraca
się do lewego wierzchołka i dalej proces zaczyna się od nowa, to znaczy od
punktu1). Jeśli lewych strzałek nie ma lub warunki nad każdą z nich nie są
spełnione, oznacza to sytuację wyjścia ze struktury bazowej. Gdy wśród strzałek,
prawych lub lewych, są strzałki z "pustym" warunkiem, to, niezależnie od tego, w
jakim miejscu struktury bazowej one występują, przejście po takiej strzałce z
wykonaniem operacji pod nią możliwe jest tylko wtedy, gdy nie ma analogicznych
(prawych lub lewych) strzałek ze spełnionym warunkiem.
28
Dla wyjścia ze struktury bazowej, podczas jej wykonania powinna powstać taka
sytuacja, że:
wśród prawych strzałek istnieje chociaż jedna ze spełnionym (lub pustym)
warunkiem nad nią;
wśród lewych strzałek (gdy są takie) nie powinno być żadnej ze spełnionym
warunkiem.
Zauważmy, że z definicji struktury bazowej wynika łatwość jej zastosowania jak dla
wykonywania operacji wielowarunkowych, tak i dla określania pętli algorytmów.
Inny, łatwiejszy sposób określania pętli jest możliwy poprzez zastosowanie drugiej
struktury, nazywanej strukturą specjalną, czyli pętlą. Jej prosty przypadek
zademonstrujemy na przykładzie przedstawionym na rys. 4.5.
o==============o
k < nk := k+1
Rys. 4.5. Przykład struktury specjalnej
Obydwa wierzchołki w tej strukturze, lewy i prawy, są traktowane jako jeden (dla
obydwóch przypisuje się ten sam numer w połączeniach struktur grafowych pomiędzy
sobą). Struktura (operacja pod strzałką) wykonuje się wielokrotnie, dopóki warunek nad
strzałką jest spełniony. Wyjście ze struktury (przejście do kolejnej struktury połączonej
z aktualną) dokonuje się przy niespełnieniu warunku nad strzałką.
Bardziej ogólne określenie struktury specjalnej przedstawione jest na rys. 4.6.
29
o==============o
B1 A1
... ...Bn An
Rys. 4.6. Postać ogólna struktury specjalnej (pętli)
Reguły wykonania tej struktury są następujące: wybiera się pierwszą (od góry) ze
strzałek, nad którą warunek jest spełniony, wykonuje się odpowiednią operację pod tą
strzałką i powraca się podwójną linią do lewego wierzchołka, po czym proces analizy
zaczyna się od nowa. Wyjście z pętli (przejście do kolejnej struktury) dokonuje się
wtedy, gdy żaden z warunków nad strzałkami nie jest spełniony.
Jak widać z opisu działania struktury specjalnej, dla uniknięcia „wiecznej” pętli
(bezustannego cyklu powtórzeń pętli), rezultat wykonania operacji zapisanych pod
strzałkami powinien być taki, aby w trakcie realizacji kolejnych iteracji powstała
sytuacja, w której żaden warunek z zapisanych ponad strzałkami nie mógłby być
spełniony.
Dwie możliwe operacje połączenia podstawowych struktur są następujące:
1) Połączenie sekwencyjne bazowych i specjalnych struktur w dowolnym porządku.
3) Rozerwanie dowolnej prawej strzałki na dwie nowe "prawe" strzałki i włączenie
pomiędzy nimi dowolnej (bazowej lub specjalnej) struktury. Operacja "włączenia"
jest też dopuszczalna w miejscu połączenia dowolnej prawej strzałki z jej lewym
lub prawym wierzchołkiem.
Ilustracją operacji połączeń są odpowiednio rys. 4.7 i rys. 4.8.
30
o o======o o o o===========o o
Rys. 4.7. Połączenie sekwencyjne Rys. 4.8. Włączenie struktur
W wyniku takiego połączenia struktur zawsze otrzymujemy strukturalne, grafowe
schematy z jednym wejściem (wierzchołek początkowy) i jednym wyjściem
(wierzchołek ostatni). Udowodniono [R1], że dowolny algorytm może być
przedstawiony za pomocą strukturalnego schematu grafowego.
Dla ilustracji połączeń struktur, na rys.4.9 przedstawiono schemat grafowy
algorytmu, analizującego dane wejściowe oraz rozwiązującego problem 4.2 –
odnalezienie NWD dwóch dodatnich liczb całkowitych X i Y.
(X>0) & (Y>0) o o o===========o o
Czytaj (X,Y); Pisz (‘NWD(X,Y)=’, R1);R1>R2
R1:=R1-R2;
R1<R2
R2:=R2-R1;
Pisz ( ‘Sprawdź dane wejściowe’ );
Rys. 4.9. Schemat grafowy odnalezienia NWD (X,Y)
W praktyce zdarzają się sytuacje, gdy trzeba przerwać działanie algorytmu i przejść
na początek lub koniec pewnej struktury. Dlatego każda struktura może być oznaczona
etykietą (identyfikatorem lub liczbą całkowitą bez znaku). Etykietę zapisuje się
bezpośrednio po prawej stronie początkowego wierzchołka struktury. Dla przejścia do
oznaczonej etykietą struktury, wystarczy wpisać na końcu strzałki dokonującej
potrzebny skok tą samą etykietę, poprzedzając ją symbolem '*' lub symbolem '#'. Jeśli
31
etykieta jest poprzedzana symbolem '*', to skok dokonuje się na początek oznaczonej
przez etykietę struktury, jeśli symbolem '#', to – na koniec oznaczonej struktury.
Jeśli na końcu strzałki wypisany jest sam symbol '*' (lub symbol '#'), bez etykiety,
oznacza to skok na początek (koniec) całego schematu grafowego. Na rys. 4.10 za
pomocą przerywanych linii pokazane są możliwe skoki, dokonywane przez oznaczone
strzałki.
o o Et1=========o o o
* #Et1
# *Et1
Rys. 4.10. Dokonywanie skoków w schematach grafowych
a) if 1 then b)
if 2 then + 1 -begin + -
if 3 then 5 2 6
end + -else 4 3 4
else 6 ; 57.
c) 1 2 3 7o o o o o
5 7
4
6
Rys. 4.11. Trzy sposoby opisywania tego samego algorytmu: a) – pseudokod, b) – schemat blokowy, c) – schemat grafowy
32
Aby ułatwić Czytelnikowi dokonanie samodzielnej oceny skuteczności opisu
algorytmów za pomocą schematów grafowych na rys. 4.11 przedstawiliśmy trzy
sposoby opisywania tego samego algorytmu. Przez 1, 2, 3 oznaczono sprawdzanie
warunków, przez 4, 5, 6, 7 – wykonanie odpowiednich operacji.
4.4. Tablicy decyzyjne
W trakcie rozwiązywania zadań ekonomicznych, do określenia algorytmu podjęcia
decyzji mogą być stosowane tak zwane tablicy decyzyjne. Zasady ich budowy i
wykorzystywania przedstawimy na następującym przykładzie.
W pewnej firmie pracownikom na koniec roku może przysługiwać awans i premia.
Przyznanie tych wyróżnień jest uzależnione od trzech warunków: stażu pracy,
wykonania planu, absencji w pracy.
Oznaczmy przez M, S, D pracujących w firmie, odpowiednio, mniej niż 3 lata, od 3
do10 lat, więcej niż 10 lat. Wykonanie planu przez pracownika oznaczmy przez T, nie
wykonanie przez N. Absencję w pracy do 10 dni w roku oznaczmy przez m, a
absencję przekraczającą 10 dni w roku przez d.
Przy tych założeniach możemy sformułować następującą tablicę decyzyjną 4.1.
Tabela 4.1. Przyznawanie awansu i premii pracownikomPole warunków Kombinacje wariantów decyzji
decyzji 1 2 3 4 5 6 7 8 9 10 11 121. Staż pracy M M M M S S S S D D D D2. Wykonanie planu T T N N T T N N T T N N3. Absencja w pracy m d m d m d m
.d m d m d
Warianty decyzji1. Awans i premia 100% *2. Premia 100% * * * * *3. Premia 50% * *4. Nie przyznać premii * * * *
33
W polu warunków zostały wypisany wszystkie możliwe ich kombinacje (3x2x2=12).
W polu działań oznaczono symbolem gwiazdki wariant decyzji (wniosku), który należy
podjąć dla danej kombinacji wartości poszczególnych warunków. Przedstawiony sposób
wnioskowania pokazuje możliwość podjęcia decyzji w oparciu o tablicę decyzyjną.
4.5. Zadania do ćwiczeń
Daj opis algorytmu rozwiązywania następujących zadań na pseudokodzie oraz (lub)
za pomocą schematu blokowego:
1) Oblicz wartość funkcji silni n! = 1 2 ... n dla dowolnej wartości argumentu n
nieujemnej, całkowitej.
2) Dla jakiej wartości n po raz pierwszy będzie spełniono n! > .
3) Dla jakiej wartości j po raz pierwszy będzie spełniono
.
4) Danymi wejściowymi są m , X0 . Obliczyć i wydrukować wartości
X i = X i-1 * X i-1 + sin(X i-1 ) + 1, dla i=1, 2,..., m, oraz sumę .
5) Ciąg liczb Fibonacciego określa wzór
fi+1 = fi + fi -1 , gdzie f0 = 0 , f1 = 1, i =1, 2, 3,....
Dla jakiej wartości i po raz pierwszy będzie spełniono .
6) Dane wejściowe zadania: n, S0 , S1 . Obliczyć wartości S2 , ..., Sn według wzoru:
, dla i =1, 2, ..., n-1.
Podsumować dodatnie wartości Si , i = 0, ..., n oraz podać ich ilość.
7) Dane wejściowe: m, X(0), X(1). Stosując wzór
X(i) = 0.5*X(i-1) + cos(X(i-1)+X(i-2))
34
znaleźć wartości X(2), ..., X(m). Poza tym, dla i = 0, ..., m obliczyć
oraz sumę
35
5. ZŁOŻONOŚĆ I EFEKTYWNOŚĆ OBLICZENIOWA ALGORYTMÓW
Złożoność i efektywność obliczeniowa algorytmu jest ściśle związana ze złożonością
problemu, który się rozwiązuje za pomocą tego algorytmu. Złożoność problemu,
wymieniana często jako złożoność czasowa, określa się niezbędną liczbą operacji
elementarnych, które muszą być wykonane do znalezienia rozwiązania problemu. Przez
operacje elementarne rozumiemy operacje arytmetyczne „+”, „-”, „”, „/” oraz operacje
logiczne (operacje porównywania oraz operatory logiczne „i”, „lub”, „negacja”).
Złożoność i efektywność obliczeniowa algorytmu zależy istotnie od sposobu
opisywania oraz realizacji komputerowej algorytmu. Chodzi tu o dość częste potrzeby
wyboru: czy dać opis algorytmu w postaci procedury rekurencyjnej, czy też zastosować
pewne rozwiązania iteracyjne.
Ważnym jest także dokonanie, w razie potrzeby, modularyzacji algorytmu
wyodrębnieniu w nim możliwie niezależnych części modułów, z których każdy
opisuje określone czynności wykonywane na pewnych obiektach, oraz ustaleniu tego,
jak te moduły powinno połączyć w spójną całość.
Omówieniu tych zagadnień poświęca się niniejszy rozdział.
5.1. Klasy złożoności problemów kombinatorycznych
W rozdziale tym określimy pewne klasy złożoności czasowej problemów
kombinatorycznych. Do kombinatoryki zalicza się zagadnienia, w których wykonuje się
operacje na zbiorach danych skończonych. Problemy kombinatoryczne dzielą na
decyzyjne i optymalizacyjne.
Problemy decyzyjne najczęściej bywają sformułowane w postaci pytań, na które
potrzebna jest odpowiedź „tak” lub „nie”. Natomiast problemy optymalizacyjne to
takie, w których należy znaleźć ekstremum (wartość minimalną lub maksymalną)
zdefiniowanej w tym problemie funkcji celu przy spełnieniu pewnych warunków
ograniczających.
36
Zwykle najbardziej zasadniczym jest pytanie, czy dla rozważanego problemu w ogóle
istnieje jakiś rozwiązujący go algorytm. Jeśli taki algorytm istnieje, to można
powiedzieć, że problem jest rozstrzygalny, jeśli nie problem jest nierozstrzygalny.
Mówimy tu na razie o rozstrzygalności teoretycznej, gdyż w wielu przypadkach
znaleziony algorytm, dostarczający rozwiązanie w teorii, po jego adaptacji
komputerowej nie jest w stanie tego realizować w rozsądnym czasie za pomocą
istniejących systemów komputerowych. Dla tego pojawia się naturalny postulat
rozróżniania algorytmów lepszych i gorszych z obliczeniowego punktu widzenia.
Bardziej ściśle sformułowanie tego postulatu dał J. Edmonds (w pracy „Paths, trees and
flowers”, Canadian Journal Of Mathematics, 1965, 17, s. 449-467). Określił on jako
efektywne (dobre) algorytmy takie, które dają rozwiązanie w czasie ograniczonym od
góry przez wartość wielomianu zależnego od rozmiaru problemu (np. od n liczby
zmiennych, wartości których musimy ustalić podczas rozwiązywania problemu).
Wszystkie inne algorytmy Edmonds odniósł do nieefektywnych (złych). Tak ustalone
pojęcie efektywności algorytmu zostaje aktualnym do dziś.
Dokonując klasyfikacji problemów kombinatorycznych, można powiedzieć, że
wszystkie problemy, dla rozwiązania których istnieje chociaż jeden efektywny
algorytm, odnoszą się do klasy problemów wielomianowych. Problemy, dla
rozwiązania których nie udaje się zbudować efektywnego algorytmu, odnoszą się do
klasy niewielomianowych.
Bardziej szczegółowe informacje, dotyczące klas złożoności problemów, analizy
algorytmów można znaleźć w [2, 6]. Przykłady problemów należących do klasy
problemów niewielomianowych (komiwojażera, plecaka, kolorowania, szeregowania i
innych) można znaleźć w [6, 36].
37
5.2. Algorytmy rekurencyjne, iteracje
Obiekt nazywają rekurencyjnym, jeśli jego definicja odwołuje się do niego samego.
Przykładem rekurencji w życiu codziennym może być wielokrotne, co raz mniejsze
odbicie w lustrze swojego wizerunku, jeśli mamy dwa lustra jedno przed sobą, a
drugie z tyłu (np. w przedziale pociągu).
W matematyce rekurencja stosuje się bardzo często do definicji nieskończonego
zbioru obiektów za pomocą skończonego wyrażenia. Na przykład, obliczenie wartości
funkcji silni S(n) = n! dla dowolnej wartości argumentu n nieujemnej, całkowitej
można określić następująco:
(5.1) S(n) = 1 jeśli n=0, w wypadku przeciwnym S(n) = n S(n-1).
W podobny sposób nieskończoną liczbę obliczeń można opisać za pomocą
skończonego programu rekurencyjnego bez określenia jawnych iteracji w takim
programie.
Drugim przykładem bardziej skomplikowanego schematu rekurencyjnego jest
obliczanie liczb Fibonacciego:
(5.2) F(n) = 0 jeśli n=0, w wypadku przeciwnym,
{ F(n) = 1 jeśli n=1, w wypadku przeciwnym F(n) = F(n-1) + F(n-2)
}.
Wielokrotnie wykonywane fragmenty programów, przeznaczone do obliczania
pewnych wartości, w językach programowania są deklarowane jako podprogramy.
Najczęściej wyróżniają dwa typy podprogramów: procedury i funkcje (w rozdz. 7.5
podaje się ich opis w Pascalu). Jeśli wzór rekurencyjny typu (5.2) przedstawiono w
postaci procedury lub funkcji, to obliczanie wartości F(n) dokonuje się za pomocą
uruchomienia procedury lub wywołania funkcji dla zadanej wartości parametru n. Takie
procedury lub funkcje, które w swej treści wywołują same siebie, noszą nazwę procedur
lub funkcji rekurencyjnych.
38
Z postaci (5.2) widać, że każde wywołanie funkcji dla wartości n > 1 powoduje dwa
dalsze wywołania, tj. całkowita liczba wywołań rośnie wykładniczo. Na przykład,
schemat obliczenia F(4) wygląda następująco:
4
3 2
2 1 1 0
1 0
Rys. 5.2.1. Dziewięć wywołań funkcji dla obliczenia F(4)
Łatwo sprawdzić, że dla F(5) będzie 15 wywołań itp. Czyli, sposób rekurencyjny
przedstawiania algorytmu, świetny z punktu widzenia matematyki, nie zawsze jest
dobrym do implementacji komputerowej.
O wiele lepiej obliczać liczby Fibonacciego posługując się schematem iteracyjnym,
określając identyczne obliczenia za pomocą pętli programu. Tak więc, zarezerwujmy
trzy zmienne x, y, z dla trzech kolejnych wartości liczb Fibonacciego. Wtedy algorytm
iteracyjny obliczenia wartości F(n) dla n > 0 będzie wyglądać następująco:
Początek
Określmy wartość n; ustalmy x := 0; y := 1; z := 1; i :=1;
dopóki i < n wykonuj
( z := x+ y; x := y; y := z; i := i+1 );
F(n) := z
Koniec algorytmu.
Proponuję Czytelnikowi sprawdzić, czy ten sam algorytm, zapisany niżej w inny
sposób, bez wprowadzania zmiennej pomocniczej z, działa prawidłowo:
39
Początek
Określmy wartość n; ustalmy x := 0; y := 1; i :=1;
dopóki i < n wykonuj
( y := y+ x; x := y - x; i := i+1 );
F(n) := y
Koniec algorytmu.
Z dwóch sposobów obliczania liczb Fibonacciego, analizując metody rozwiązywania
wielu innych problemów, wynika wniosek: unikajmy rekurencji, jeśli istnieje proste
rozwiązanie iteracyjne. Jednak nie musimy unikać rekurencji za wszelką cenę. Istnieje
mnóstwo jej zastosowań, za pomocą których uzyskujemy programy z prostą, dobrze
zrozumiałą logiką ich działania, z niewielką ilością kodu źródłowego, dostarczające
wyniki w czasie porównywalnym z czasem działania procedur iteracyjnych. Wiele
ciekawych algorytmów rekurencyjnych zapisanych w kodzie Pascala można znaleźć w
[41]. Interesujące tego typu algorytmy zapisane w jeżyku C++ przedstawiono w [42].
Jednak wybór algorytmu zależy nie tylko od tego, aby jak najszybciej, jak najprościej
rozwiązać problem. Niewłaściwy dobór algorytmu może spowodować takie sytuacje, w
których dla pewnych wartości danych wejściowych program może zacząć działać
nieprzewidzianie i spowodować sytuacje awaryjne. Aby to sprawdzić, rozważmy
następujące zadanie.
Zadanie 5.2.1. Obliczyć wiarygodność (prawdopodobieństwo) tego, że spośród k
losowo spotkanych osób nie ma żadnej pary urodzonych w tym samym dniu.
Wzór matematyczny obliczania wiarygodności tego zdarzenia wygląda
następująco:
(5.3) P(k)=
Do obliczania P(k) są dopuszczalne jeszcze dwa kolejne wzory matematyczne:
40
(5.4) P(k)=
(5.5) P(k)=
Czyli, mamy trzy różne algorytmy obliczania tej samej wartości. Najlepszą
komputerową realizacją jest oczywiście pierwszy algorytm nie powoduje sytuacji
awaryjnych (przepełnienia komórki), liczba operacji algorytmu jest funkcją liniową
od ilości zmiennych k. Dopuszczalnym też jest trzeci algorytm. Ale w żadnym razie
nie może być zastosowany w postaci programu sposób (5.4) nawet dla niewielkich
wartości k szybko powstaje sytuacja „przepełnienia” komórek komputera
zarezerwowanych dla obliczenia wartości licznika i mianownika.
5.3. Modularyzacja algorytmów
Zasada modularyzacji (wyodrębnienie w algorytmie postępowania możliwie
niezależnych części modułów) występuje w każdej dziedzinie działalności ludzkiej
cechującej się różnorodnością i współdziałaniem funkcji. W sferze produkcji wyroby
użytkowe składają się z odrębnych części (np. samochód składa się z podwozia,
nadwozia, silnika, kół itd.). Części te zwykle są wytwarzane niezależnie od siebie,
niekiedy również niezależnie kupuje się, a mimo to tworzą one określoną całość
funkcjonalną dzięki spełnieniu przez poszczególne części zespołu warunków
charakteryzujących ich przewidywane działanie w układzie całego wyrobu.
Rozważając o modularyzacji algorytmów, określimy najpierw trzy cechy
niezależność semantyczną, niezależność syntaktyczną, abstrakcję danych, jakie
powinien posiadać fragment programu, aby można było go nazwać modułem.
Niezależność semantyczna. Moduł algorytmu powinien określać szereg działań
wykonywanych na dobrze zdefiniowanych obiektach w celu uzyskania pewnego
wyniku. Deklaracje modułu, jego współdziałanie z otoczeniem muszą być takie, aby
zamiana, w razie potrzeby, danego modułu na inny, dostarczający identyczne wyniki na
podstawie tych samych wartości danych wejściowych, nie zmieniła znaczenia całości
41
działania algorytmu. Wywnioskowanie wyniku działania modułu powinno być
możliwym przez analizę tylko tego, aktualnego modułu, nie zwracając uwagi na
pozostałe części algorytmu. Tą ostatnią własność modułów algorytmów określają
mianem semantycznej niezależności modułów.
Niezależność syntaktyczna. Moduły algorytmu łączą się w całość (bezbłędnie
działający algorytm) na podstawie ustalonych reguł, określających współzależności i
współdziałanie poszczególnych modułów. Właśnie tą możliwość łączenia się modułów
w całość, niezależnie od ich wewnętrznej budowy, określa się mianem syntaktycznej
niezależności modułów. Ta cecha modułów jest bardzo przydatna wtedy, gdy oddzielne
moduły algorytmu są pisane przez różne osoby, które nie muszą przestrzegać
identyczności konwencji notacyjnych używanych do formułowania treści modułów.
Abstrakcja danych. Każdy z modułów jednego algorytmu odgrywa swoją konkretną
rolę podczas rozwiązywaniu jednego zadania. Stąd ustala się zasady współdziałania
poszczególnych modułów (kolejność wykonania), określa się jednoznaczny sposób
przekazywania informacji między nimi. Ze względu na zapotrzebowanie semantycznej i
syntaktycznej niezależności modułów interpretacja przekazywanych informacji
pomiędzy nimi nie może być zależna od interpretacji informacji ustalonych wewnątrz
modułów. Tą ostatnią własność nazywają zasadą abstrakcji danych. Nazwa „abstrakcja
danych” obejmuje wszelkie informacje wymieniane między modułami algorytmu, nie
tylko te, rozumiane bardziej wąsko, określane jako „dane”, niezbędne do obliczania
pewnych wartości.
Biorąc pod uwagę wymienione własności modułów, na każdy z nich możemy teras
spojrzeć z dwu odmiennych punktów widzenia: od wewnątrz i z zewnątrz. Patrząc na
moduł z wewnątrz, skupiamy uwagę na sposobach realizacji zadanych czynności i na
wszelkich szczegółach dotyczących tych realizacji. Natomiast, patrząc na moduł z
zewnątrz możemy traktować go jako spójną całość, jako opis czynności mających w
określonych warunkach określone skutki, przy czym nie interesują nas szczegóły
wykonania tych czynności. Faktycznie modularyzacja algorytmów pozwala na
ukrywanie zbędnych informacji: z wewnętrznego punktu widzenia zbędnymi są 42
informacje dotyczące wszystkich innych modułów, a z zewnętrznego punktu widzenia
są zbędne informacje dotyczące szczegółów realizacji zadanych czynności.
5.4. Struktury blokowe (zasięg ważności deklaracji obiektów)
Bardzo ważnym problemem przy modularnym projektowaniu algorytmów jest
sprawa pośrednictwa między modułami, a dokładniej sprawa komunikacji między
modułem a światem zewnętrznym. Komunikacja ta, jak również metody realizacji
zadanych czynności modułu są uzależnieni od sposobów deklaracji obiektów (struktur
danych), z którymi ma do czynienia algorytm i dany konkretny moduł. W obszernych
algorytmach obiektów tych, prostych i złożonych (strukturalnych), może być wiele.
Każdemu z nich odpowiadają miejsca w pamięci komputera. Natomiast, w tej
konkretnej chwili wykonywania programu, a dokładniej, w chwili wykonywania jego
pewnego modułu X (przecież rozważamy działanie algorytmu modularnego),
najczęściej nie wszystkie obiekty algorytmu są używane naraz. Więc, redukując liczbę
deklaracji obiektów do rzeczywiście niezbędnych, możemy oszczędzać pewną część
pamięci komputera. Ten aspekt sprawy często ma bardzo istotne znaczenie.
Pewne obiekty mają sens tylko w kontekście określonych operacji, które mogą nimi
manipulować. Z drugiej strony, niektóre operacje mają sens tylko w zastosowaniu do
obiektów określonego typu. Czyli, tworząc algorytmy (projektując oprogramowanie
komputera) ustala się powiązania ze sobą operacji i obiektów. Aby wyjaśnić, jak to się
robi, wprowadzimy dwie konwencje oraz pojęcie struktury blokowej.
Pierwszą konwencją jest ograniczenie zasięgu ważności deklaracji obiektów do
ściśle wytyczonego fragmentu tekstu algorytmu. Przyjmiemy, że granice takiego
fragmentu tekstu będą oznaczane parą nawiasów syntaktycznych Początek...Koniec. W
tych ustaleniach słowo „ważność” moglibyśmy zamienić na „widzialność” lub
„obowiązywanie”, a parę nawiasów początek...koniec na nawiasy begin...end lub na
parę nawiasów klamrowych {...}. W różnych książkach opisujących algorytmy używa
się też tych słów i oznaczeń.
43
Dla uproszczenia dalszych sformułowań oraz zrozumienia problemu, wprowadzimy
pojęcie struktury blokowej, w skrócie bloku, rozumiejąc przez to ograniczony
nawiasami Początek...Koniec fragment algorytmu zawierający lokalne deklaracje.
Jako przykład stosowania pierwszej konwencji, poniżej przedstawiamy algorytm
5.4.1 rozwiązujący następujące zadanie.
Zadanie 5.4.1. Obliczyć z dokładnością = 10 -8 wartość , gdzie A – dowolna
nieujemna liczba rzeczywista wprowadzana z klawiatury. Do obliczeń zastosować
następujący wzór rekurencyjny xi+1 = (xi + A / xi )/ 2 , który opisuje rząd wartości
zbliżających się ku wartości Y. Jako pierwszą wartość tego rządu, wybrać xo = A.
Przerwać liczenie gdy będzie spełniono | (xi+1 - xi ) / xi+1 | < . Przyjąć Y = xi+1 .
Algorytm 5.4.1.
PoczątekStała D=10 -8 ; Zmienne rzeczywiste A, X, t;Wprowadź A; X := A; {przyjmuje się jako początkowa przybliżona wartość pierwiastka}
Powtarzaj t := X; {poprzednia przybl.wartość}
X := (X + A /X)/2; {kolejna przybl. wartość}Aż do | (X - t ) / X | < D ;
Drukuj X;Koniec.
Schemat powiązania ze sobą operacji i obiektów (zasięg widzialności obiektów) w
algorytmie 5.4.1 przedstawiono na rys. 5.1.
Drugą konwencję notacyjną, związaną z ograniczeniem zasięgu deklaracji, określimy
w sposób następujący: fragmenty algorytmu zawierające własne deklaracji mogą
być pogrążone w innych fragmentach algorytmu, też zaopatrzonych w deklaracje.
Czyli, jeden blok może być pogrążony w innym bloku.
Początek44
Deklaracje obiektów D,A,X,t;
Instrukcje; Widać D, A, X, tKoniec.
Rys. 5.1. Powiązanie ze sobą operacji i obiektów w algorytmie 5.4.1
Przykładem stosowania drugiej konwencji jest algorytm 5.4.2, przedstawiający inną
interpretację algorytmu rozwiązującego zadanie 5.4.1.
Algorytm 5.4.2.
PoczątekZmienne rzeczywiste A, Wynik;Wprowadź A;Początek
Stała D=10 -8 ; Zmienne rzeczywiste X, t; X := A;
Powtarzaj t := X; {poprzednia przybl. wartość}
X := (X + A /X)/2; {kolejna przybl. wartość}Aż do | (X - t ) / X | < D ;
` Wynik:=X;Koniec;
Drukuj Wynik;Koniec.
Powiązanie ze sobą operacji i obiektów w tym algorytmie przedstawiono na rys. 5.2.
Początek
Deklaracje obiektów A, Wynik;
Instrukcje 1; Widać A, WynikPoczątek
Deklaracje obiektów D, X, t;
Instrukcje 2; Widać A,Wynik,D,X,tKoniec
45
Instrukcje 3; Widać A, WynikKoniec.
Rys. 5.2. Powiązanie ze sobą operacji i obiektów w algorytmie 5.4.2
5.5. Zadania do ćwiczeń
1) Oszacuj liczbę operacji niezbędnych do odnalezienia wartości n-ej liczby
Fibonacciego za pomocą rekurencyjnego algorytmu (5.2).
2) Ile operacji wykonuje się za pomocą iteracyjnego algorytmu dla odnalezienia n-ej
liczby Fibonacciego?
3) Następująca funkcja Ackermanna w sposób rekurencyjny pozwala obliczyć wartości
składowych macierzy A, wierszy której są ponumerowane liczbami 0,1,2,...,m, a
kolumny liczbami 0,1,2,...,n:
A(m, n) = n +1 jeśli m=0, w wypadku przeciwnym,
A(m, n) = A(m-1, 1) jeśli n=0, w wypadku przeciwnym
A(m, n) = A(m-1, A(m, n-1)).
Przedstaw algorytm iteracyjny obliczający wartości A(i, j), i=0,1,2,...,m,
j=0,1,2,...,n.
46
6. WYSZUKIWANIE, SORTOWANIE DANYCH. GENEROWANIE PERMUTACJI LOSOWYCH
W tym rozdziale przedstawimy opis kilku najczęściej używanych metod
wyszukiwania i sortowania danych oraz zapoznamy się z ciekawymi z punktu widzenia
informatyki modelami i metodami szeregowania danych.
Danymi, o których mowa w tym rozdziale, mogą być ciągi liczb lub listy rekordów
(wierszy pewnej tablicy). Kryterium wyszukiwania lub sortowania wówczas odnosi się
do pewnego wyróżnionego pola rekordu (jednej ze składowych wiersza), które
nazywają kluczem. W razie potrzeby zamiany miejscami tego pola z analogicznym
polem innego rekordu, dokonuje się zamiany nie wyodrębnionych pól, lecz
zawierających ich rekordów w całości. Dla uproszczenia wykładni będziemy
analizować metody sortowania rosnącego ciągu liczb. Dla sortowania malejącego
zmiana algorytmu będzie ewidentna.
Przedstawimy w tym rozdziale trzy metody sortowania: bąbelkowego, przez
wstawianie oraz sortowanie szybkie. Kilka innych metod można znaleźć w [41].
6.1. Wyszukiwanie danych
Problem wyszukiwania polega na ustaleniu odpowiedzi, czy pewien element b należy
do zbioru A czy też nie i, jeśli należy, to powinno być ustalone miejsce jego ulokowania
w A. W zadaniach informatycznych takim zbiorem A zwykle jest pewien plik danych, a
naszym zadaniem jest uzupełnić plik lub znaleźć w nim elementy posiadające pewne
cechy i z kolei dalej wykorzystywać te elementy w obliczeniach, modyfikować je lub
kasować. Operacje te (zwane operacjami edycji i modyfikacji danych) są
implementowane w postaci pewnych algorytmów we wszystkich programach
narzędziowych (np. w programach pakietu MS Office Word, Excel, Access). W takich
programach najczęściej wykonuje się operacje edycji i modyfikacji danych przez
stosowanie odpowiednich opcji dostępnych po "pstryknięciu" myszką na przyciski
menu plik i edycja widoczne po uruchomieniu programów (np. opcji Znajdź...,
Zamień..., Idź do...). 47
W rozdziale tym postaramy się wyjaśnić zasady wykonywania wyżej wymienionych
operacji w zbiorze uporządkowanym. Inne algorytmy, dla innych zbiorów danych
można znaleźć w [2].
Wyszukiwanie w zbiorze uporządkowanym. Jak już wspomniałem wyżej,
elementami zbioru danych mogą być liczby lub wiersze pewnej tablicy. Tą tablicą może
być macierz, wiersze której są zbudowane ze składowych tego samego typu lub
macierz, jako lista rekordów zawierających pola danych różnego typu.
Najbardziej efektywnym (szybkim) sposobem wyszukiwania jest wyszukiwanie w
zbiorze uporządkowanym (tak zwana metoda dzielenia na pół). Przyjmijmy, że zbór A
zawierający n elementów jest uporządkowany rosnąco według wartości , i=1, ..., n.
Jeśli zbiór jest ciągiem liczbowym, to w postaci występują jego składowe liczbowe.
W przypadku zbioru rekordów, jest wartością klucza rekordu (pola wyróżnianego w
wierszu tablicy). W każdym razie uważamy, że dla elementów A jest spełniony
warunek:
(6.1) .
Odpowiedź na pytanie, czy pewien element b należy do zbioru A (dokładniej - do
zbioru wartości ai , i=1, ..., n) czy też nie, i, jeśli należy, to gdzie jest jego miejsce w A ,
daje następująca procedura rekurencyjna:
48
Początek { algorytm wyszukiwania w zbiorze uporządkowanym }
1) Ustal wartość wskaźnika skuteczności poszukiwań = 1.
2) Jeśli = 0, to { b A, koniec algorytmu}, w wypadku przeciwnym znajdź w (6.1)
element środkowy a = ak , gdzie k = [ (1+n)/2 ] część całkowita wyniku
operacji, wykonanych w nawiasach kwadratowych.
3) Jeśli a = b, to { b A; b znajduje się na miejscu k w ciągu (6.1); koniec
algorytmu}.
4) Jeśli a < b, to {wybierz prawą połowę ciągu (6.1) i określ ją jako ciąg (6.1);
oblicz ilość elementów w nowym ciągu n = n - k; ustal = n; przejdź do
wykonania punktu 2}, w wypadku przeciwnym {wybierz lewą połowę ciągu (6.1),
określ ją jako ciąg (6.1); oblicz ilość elementów w nowym ciągu n = k - 1; ustal
= n; przejdź do wykonania punktu 2}.
Koniec algorytmu.
Schematycznie ta metoda dzielenia na pół przedstawia się w postaci drzewa
binarnych poszukiwań i właśnie pod taką nazwą najczęściej opisuje się w książkach.
Drzewo binarnych poszukiwań jest to drzewo binarne, w którego wierzchołkach
umieszczono elementy zbioru A w taki sposób, aby spełniony był następujący warunek:
jeśli w wierzchołku drzewa k umieszczony jest element x A, to dla każdego elementu
yA umieszczonego z lewej strony od k zachodzi y < x, natomiast dla każdego
elementu yA umieszczonego z prawej strony od k zachodzi y > x . Wierzchołek, który
nie ma kolejnych rozgałęzień nazywa się liściem.
Korzeń drzewa 52 (środek ciągu) o
28 72o o
16 35 60 81o o o o
12 20 75 87o o o o
Rys. 6.1. Przykład drzewa poszukiwań binarnych
49
6.2. Sortowanie danych
Sortowaniem będziemy nazywać proces ustawiania składowych n elementowego
zbioru X =(X1 , ..., Xn ) w określonym porządku. Różnego typu operacje, np. wyszukiwania
danych, przebiegają o wiele efektywniej (szybciej) na uporządkowanym zbiorze
danych, niż na nieuporządkowanym: w słownikach, w księgach telefonicznych, na
listach płac, itp. Sortowanie danych, rosnące lub malejące, jest jedną z najczęściej
wykonywanych operacji informatycznych. Opisując algorytmy sortowania przyjmiemy,
że zbiorem X jest ciąg liczb X1 , ..., Xn , podlegający sortowaniu rosnącemu.
Metody sortowania dzielimy na dwie grupy: sortowania wewnętrznego (zwanego
inaczej sortowaniem tablic) i sortowania zewnętrznego (sortowania plików).
Proces sortowania przebiega w pamięci operacyjnej (wewnętrznej) komputera.
Metody sortowania wewnętrznego zbioru X przewidują, że X jako całość mieści się w
pamięci wewnętrznej. Natomiast, jeżeli rozmiar tej pamięci jest niewystarczającym dla
przechowywania X, to znaczy, że X jest zapisany w postaci jednego lub kilku plików w
pamięci zewnętrznej (np. na jednym lub kilku dyskach twardych), do sortowania X
muszą być zastosowane metody sortowania zewnętrznego. Rozpoczniemy opis od
podstawowych metod sortowania wewnętrznego, a jako ostatni przedstawimy sposób
wykonania sortowania zewnętrznego.
50
6.2.1. Sortowanie bąbelkowe
Jest to metoda najprostsza w realizacji komputerowej i jedna z najgorszych z punktu
widzenia niezbędnej liczby operacji dla wykonania sortowania. Algorytm sortowania
bąbelkowego wygląda następująco.
Początek { algorytm sortowania bąbelkowego }
Dane wejściowe: n długość ciągu;
X=( X1 , X2 , ..., Xn ) wartości składowych ciągu.
Dopóki n > 1 wykonuj
( n:=n-1; dla każdego z j=1, 2, ..., n wykonuj
jeśli Xj > Xj+1 to (R:= Xj ; Xj := Xj+1 ; Xj+1 := R ) )
Koniec algorytmu.
Dla sortowania bąbelkowego łatwo oblicza się dokładną liczbę operacji, które
algorytm wykonuje. Wynosi ona
P(n) = (n-1)+(n-2)+(n-3)+...+1= n(n-1)/2
porównań oraz nie przekraczającą P(n) liczbę ewentualnych zamian miejscami par
liczb, czyli rzędu n2 operacji. To ostatnie w języku matematycznym zapisuje się przez
notację Q(n2).
6.2.2. Sortowanie przez selekcję
Sortowanie przez selekcję odbywa się w następujący sposób. Wyznacza się
największy element w n elementowym ciągu i zamienia się go miejscami z ostatnim
elementem tego ciągu. Oblicza się n := n – 1 i proces zaczyna się od poprzedniego
zdania, aż dopóki wartość n nie stanie się jedynką.
Sortowanie przez selekcję potrzebuje wykonania podobnej liczby operacji, jak i
sortowanie bąbelkowe. Liczba ta wynosi
P(n) = (n-1)+(n-2)+(n-3)+...+1= n(n-1)/2
51
porównań oraz nie przekraczającą P(n) liczbę ewentualnych zamian miejscami par
liczb, czyli P(n) = Q(n2).
. Początek {algorytm sortowania przez selekcję}
Dane wejściowe: n długość ciągu; X=( X1 , X2 , ..., Xn ) wartości składowych ciągu.
Dopóki n > 1 wykonuj
(max:=1;
dla każdego z j=2, 3, ..., n wykonuj
jeśli ( Xj > Xmax ) to
max := j;
jeśli (n max) to (R:= Xn ; Xn := Xmax ; Xmax := R );
n:=n-1; )
Koniec algorytmu.
52
6.2.3. Sortowanie przez wstawianie
Metody tej używa większość graczy w karty. Na przykład, do prawej ręki bierze się
wszystkie rozdane karty (ciąg nieuporządkowany). Z kolei dalej, przenosi się z prawej
ręki do lewej, jedną po drugiej wszystkie karty, wstawiając je w rosnący ciąg kart w
lewej ręce tak, aby wynikowy ciąg był posortowany. Schemat wykonania tych operacji
dla sześciu kart przedstawia rys. 6.2.
3 1 5 6 2 4
3 1 5 6 2 4
1 3 5 6 2 4
1 3 5 6 2 4
1 3 5 6 2 4
1 2 3 5 6 4
1 2 3 4 5 6
(ręka lewa karty posortowane) (ręka prawa karty do posortowania)
Rys. 6.2. Schemat sortowania przez wstawianie
W algorytmie wstawiania, opis którego przedstawiamy niżej, spodziewana liczba
operacji P(n) = Q(n2), czyli jest zbliżona do poprzednich metod.
Początek { algorytm sortowania przez wstawianie }
Dane wejściowe: n długość ciągu; X=( X1,X2,...,Xn ) wartości składowych ciągu.
Dla każdego z i=2, 3, ..., n wykonuj
(r:= X[i]; j:= i-1;
Dopóki (r < X[j] ) oraz ( j 1)
wykonuj ( X[j+1] := X[j]; j := j-1 );
X[j+1] := r );
Koniec algorytmu.
53
6.2.4. Sortowanie szybkie (QuickSort)
Sortowanie szybkie polega na wykonywaniu następującej procedury rekurencyjnej (w
sposób schematyczny przedstawionej na rys. 6.3):
Początek procedury {sortowanie QuickSort}
1) W ciągu do sortowania wybieramy pierwszy element, który będzie spełniał
rolę elementu osiowego ciągu.
2) Dzielimy ciąg do sortowania na dwa podzbiory lewy i prawy, w sposób
następujący: do lewego podzbioru wybieramy z ciągu do sortowania wartości
mniejsze od elementu osiowego, a do prawego wartości większe lub równe
od elementu osiowego. Każdy z podzbiorów może: a) zostać pustym, b)
zawierać tylko jeden element, który nazwiemy liściem, c) zawierać więcej niż
jeden element.
3) Jeśli lewy podzbiór zawiera więcej niż jeden element, to przyjmujemy za ciąg
do sortowania ten podzbiór i wykonujemy punkty 1, 2 algorytmu. W wypadku
przeciwnym wykonujemy kolejny punkt algorytmu.
4) Jeśli prawy podzbiór zawiera więcej niż jeden element, to przyjmujemy za
ciąg do sortowania ten podzbiór i wykonujemy punkty 1, 2, 3 algorytmu. W
wypadku przeciwnym wykonujemy kolejny punkt algorytmu.
5) Wypisujemy ciąg posortowany wybierając od lewej strony do prawej (po kolei
w każdym zbudowanym rozgałęzieniu drzewa sortującego) liście lewe,
element osiowy, liście prawe.
Koniec procedury
.
Analizując poniższy rys. 6.3 widać, że przechodząc od skrajnie lewej gałęzi drzewa
do skrajnie prawej i odwiedzając w pierwszej kolejności „lewe” jego odnogi,
przechadzamy się po posortowanej liście danych
54
8 3 4 5 15 8 1 52 14 5 3
8 3 4 5 1 15 8 52 14 53
3 15 1 4 5 8 14 52 53
4 8 52 5 14 53
1 3 4 5 8 8 14 15 52 53
Rys. 6.3. Schemat algorytmu sortowania QuickSort
W punkcie 1 procedury, jako element osiowy wybieraliśmy pierwszy element ciągu.
Z tymże powodzeniem, jako element osiowy, może być wybierany jego ostatni element
lub pewien inny. Powoduje to możliwość powstania wielu implementacji
programowych algorytmu QuickSort. Spodziewana liczba operacji w algorytmie
QuickSort jest rzędu , gdzie n liczba elementów w ciągu do sortowania.
6.3. Scalanie ciągów uporządkowanych
Problemem pokrewnym do sortowania jest zadanie scalania, którego definicja
jest następująca. Mamy dwa uporządkowane rosnąco ciągi X i Y długości,
odpowiednio, m i n. Czyli, dla składowych ciągów spełnia się X1 X2 ... Xm i Y1
Y2 ... Yn. Należy połączyć (scalić) te ciągi w jeden ciąg uporządkowany Z1 Z2
... Zm+n.
Schemat algorytmu scalania wygląda następująco. Rozpatrujemy elementy obu
ciągów X i Y w kolejności od najmniejszego do największego, porównując bieżące
elementy i przesyłając mniejszy z nich do ciągu wynikowego Z.
Przedstawiając niżej szczegółową procedurę scalania użyjemy dla odmiany notacji
angielskich (konstrukcji języka Pascal) opisujących poszczególne operacje algorytmu.
55
Początek { procedura scalania posortowanych ciągów X i Y }
Dane wejściowe: m, n długości ciągów X i Y; X, Y ciągi posortowanych wartości.
Z wynik scalania ciągów X i Y {ciąg długości m+n}.BEGIN
i:=1; j:=1; for k:=1 to m+n do Begin If (i<=m) and (j<=n) then
if X[i] < Y[j] then begin Z[k]:=X[i]; i:=i+1; end
else begin Z[k]:=Y[j]; j:=j+1; end
else if (i>m) and (j<=n) then
begin Z[k]:=Y[j]; j:=j+1; end
else begin Z[k]:=X[i]; i:=i+1; end;
End;END; {procedury Scalanie}
Koniec algorytmu.
Algorytm scalania potrzebuje wykonania Q(n + m) operacji – zawiera jedną pętlę dla
wykonania m+n operacji porównywania elementów X i Y oraz przekazywania
odpowiednich wartości do ciągu Z..
56
6.4. Sortowanie zewnętrzne (plików)
Obecnie stają się już typowymi rozmiary pamięci wewnętrznej w 128 Mb lub
więcej nawet dla komputerów personalnych. Dla tego mniej aktualną jest potrzeba
stosowania metod sortowania zewnętrznego, gdy n długość ciągu do sortowania
przekracza rozmiar pamięci wewnętrznej. Natomiast w praktyce problemy tego typu
nadal występują i informatyk powinien wiedzieć jak się je rozwiązuje.
Załóżmy, że n elementów listy X do sortowania znajdują się w pewnym pliku
pamięci zewnętrznej. Z tego pliku możemy sprowadzić do pamięci wewnętrznej tylko
jedną porcję (blok) danych, np. z liczbą składowych m < n.
Algorytm sortowania zewnętrznego składa się z dwóch głównych kroków.
Krok 1. Dokonuje się kolejnych wczytywań z listy X (z pamięci zewnętrznej) aż do
jej wyczerpania bloków danych, do pamięci wewnętrznej. Każdy wczytany blok danych
porządkuje się za pomocą dowolnej metody sortowania wewnętrznego i z kolei dalej
zapisuje się w postaci odrębnego pliku na dysk. Wynikiem wykonania Kroku 1 jest
rozbicie listy X na bloki posortowanych danych zapisanych w plikach B1 , ... , Bk, gdzie
k = [n / m] najmniejsza liczba całkowita nie mniejsza niż wartość n / m.
Krok 2. Rozważony uprzednio algorytm scalania daje się łatwo zmodyfikować do
scalania par bloków posortowanych danych, zapisanych w odrębnych plikach na
jednym lub kilku dyskach. Elementy scalanych bloków są dostępne porcjami
ściąganymi do pamięci wewnętrznej. Wyniki scalania wszystkich porcji jednej pary
bloków zapisuje się konsekwentnie do pliku wynikowego wyniku scalania aktualnej
pary bloków. Czyli, Krok 2 polega na wykonaniu scalania wielofazowego, tzn.
powtarzaniu scalania par bloków branych z różnych plików tak długo, aż uzyskamy
jeden blok wynikowy, który i będzie posortowaną listą X..
Jak widać z algorytmu, w metodzie sortowania zewnętrznego za główną, dominującą
operację przyjmuje się przekazywanie porcji danych między pamięcią wewnętrzną a
zewnętrzną.
57
6.5. Pojęcie o problemach i metodach szeregowania
Przez problem szeregowania określamy ustalenie optymalnej kolejności
wykonywanych zadań na zbiorze maszyn dowolnego typu przy ograniczeniach
nałożonych na te zadania (czynności, pracy, operacje), maszyny (typy, możliwości
zastosowań, wydajność) oraz ich wzajemne relacje. W szczególności, zadania mogą być
częściowo uporządkowane przez ograniczenia technologiczne związane z ich
wykonaniem.
Celem jest znalezienie takiego uszeregowania zadań, które optymalizuje wybraną
miarę efektywności działania systemu zadania-maszyny. Problemy szeregowania zadań
należą do klasy problemów optymalizacyjnych, w których zmienne, wszystkie lub ich
podzbiory, muszą przyjmować wartości całkowitoliczbowe. Stąd odnosi się problemy
szeregowania do klasy niewielomianowo trudnych zadań. Model matematyczny takiego
problemu najczęściej przedstawia się w postaci sieci czynności.
Do rozwiązywania problemów szeregowania z liczbą zmiennych
całkowitoliczbowych rzędu kilkuset, mogą być zastosowane metody dokładne (np.
algorytm Balasa). Dla rozwiązywania zadań praktycznych z dużą liczbą zmiennych
używa się najczęściej metod przybliżonych. Dodatkowe informacje dotyczące
problemów szeregowania oraz metod ich rozwiązywania można znaleźć w [36].
6.6. Generowanie permutacji losowych
Przez generowanie permutacji losowej rozumiemy ustawienie elementów dowolnego
zbioru (ciągu) w kolejności losowej J = (j1, ... , jn). W tym paragrafie
opisuje się algorytm (procedura G), budujący permutacje losowe J elementów ciągu Jo,
posiadający pewne ciekawe właściwości.
Procedura G była opracowana i opublikowana przez autora skryptu w roku 1977,
wykorzystywana do tworzenia przybliżonych algorytmów rozwiązywania zadań
optymalizacyjnych (programowania zero-jedynkowego, komiwojażera). Jej właściwość
(patrz uwagę 6.6.1) pozwoliła autorowi zbudować powyższe algorytmy z adaptacją do
danych wejściowych problemu, efektywnie rozwiązujące zadania z kilkustami 58
zmiennymi całkowitoliczbowymi. Procedura G może mieć wiele innych zastosowań,
na przykład, do rozwiązywania zadań szeregowania lub bardziej prostszych, takich jak
poniższe zadanie 6.6.1. Jest ona podstawowym elementem programów 7.5.7 i 7.5.8 w
rozdziale 7.5.
Procedura G, , {1,2,3}.
1) Określamy początkowy ciąg . Przyjmujemy k=n. Wybieramy, w
zależności od potrzeb (patrz uwagę 6.6.1), wartość = 1 = 2 = 3.
2) Obliczamy = 1- , gdzie (0,1) liczba losowa z rozkładem jednostajnym w
(0,1).
3) Ustalamy i=[k]+1 , gdzie symbol [a] oznacza część całkowitą liczby a.
4) Zamieniamy miejscami i -ty i k -ty elementy ciągu Jn-k, otrzymując w rezultacie
ciąg Jn-k+1.
5) Przyjmujemy k:=k-1 i, jeśli k>1, to powracamy do punktu 2, a jeśli k=1, to losowa
permutacja J=Jn-1 została otrzymana.
Uwaga 6.6.1. Wybierając parametr =1 procedura G, generuje losowe permutacje
jednakowo prawdopodobne. Natomiast, wybierając >1 otrzymujemy za pomocą G,
losowe permutacje w pewnym sensie zbliżone do początkowego ciągu Jo i ta tendencja
zbliżenia jest tym większa, im większa jest wartość .
Zadanie 6.6.1. Ze zbioru Q , zawierającego n różnych ponumerowanych elementów,
w sposób losowy równowiarygodny wybrać m różnych składowych, gdzie m n.
Algorytm rozwiązania.
1) Za pomocą procedury G1 generujemy permutację losową J = (j1, ... , jn) ciągu
liczb Jo = (1, 2, ..., n) numerów elementów zbioru Q.
2) Ze zbioru Q wybieramy składowe o numerach jk , k = 1,2,...,m, jk J.
59
6.7. Zadania do ćwiczeń
W pewnej firmie zbiór Q określa n akcjonariuszy firmy. Każdemu i -mu
akcjonariuszowi jest przypisany współczynnik wagowy wi , i=1,...,n, związany
ilościowo z posiadaną przez akcjonariusza liczbą akcji oraz odwzorowujący pewne
cechy jego osobowości, np. poziom i profil wykształcenia.
a) Daj opis algorytmu, który znajduje spośród wartości wi , i=1,...,n, wartość
minimalną, maksymalną, srednią arytmetyczną oraz wskazuje numery
pracowników firmy którym przypisano te wartości.
b) Przedstaw algorytm, który ze zbioru Q wybiera m osób do zarządu firmy w
sposób losowy, taki, aby wiarygodność być wybranym była tym większa, im
większą wartość współczynnika wagowego wi posiada i -ta osoba. Wskazówka do
rozwiązania: zastosuj procedurę G2 lub G3.
60
7. JĘZYK PROGRAMOWANIA PASCAL
Autorem języka programowania Pascal jest Niklaus Wirth. Pierwsza jego publikacja
z opisem języka ukazała się w 1971 roku. Prawie od razu Pascal stał się jednym z
najpopularniejszych języków programowania. Pojawiły się różne jego wersje. Celem
ich ujednolicenia w 1980 roku Międzynarodowa Organizacja Normalizacyjna ISO
przedstawiła propozycję standardu Pascala.
W rozdziale tym podamy wiadomości dotyczące opisu standardowego Pascala oraz
pewne jego rozszerzenia wbudowane w różne wersje systemu Turbo Pascal (w skrócie
TP). Turbo Pascal jest produktem znacznie rozszerzającym możliwości Pascala
standardowego. Pod nazwą TP kryje się interakcyjny zintegrowany system
programowania, który składa się z kompilatora języka TP, edytora tekstów programów,
bibliotek procedur i funkcji. System TP w wielu kierunkach znacznie rozszerza standard
języka Pascal.
Dla ułatwienia współpracy z systemem operacyjnym Windows opracowano wersję
systemu TP która nosi nazwę Borland Pascal (BP).
Popularność Pascala wynika z faktu, że jest on uniwersalnym językiem
programowania, nadającym się do rozwiązywania problemów z różnych dziedzin,
prostym, łatwym do zrozumienia przy rozwiązywaniu prostych problemów oraz ma
dużo zalet i szeroką gamę środków niezbędnych do pracy zawodowego programisty.
Wymienimy te podstawowe zalety i środki:
Programowanie w Pascalu zezwala na łatwe zapisywanie dużych, kompleksowych
programów z bloków (modułów), analogicznie do budowy domu z cegieł i innych
półfabrykatów.
Pascal ma możliwość definiowania własnych struktur (typów) danych, jeśli nie
wystarczają określone w tym języku standardowe typy danych.
Ważniejsze cechy oprogramowania niezawodność oraz czytelność tekstów
źródłowych w Pascalu są łatwiej osiągalne, niż w innych podobnych językach.
61
7.1. Alfabet języka i struktura programu
W języku Pascal można używać tylko tych symboli, które należą do alfabetu języka.
Alfabet Pascala składa się z wielkich i małych liter języka łacińskiego; cyfr
0,1,2,3,4,5,6,7,8,9; symboli specjalnych oraz słów kluczowych.
Symbole specjalne obejmują :
+ - * / = < > ( ) [ ] { } : . , ' ; oraz spację i strzałkę pionową, rolę której na
klawiaturze może spełniać symbol ^ .
Do słów kluczowych należą następujące nazwy: Program, Label, Const, Type,
Function, Procedure, Var, Begin, End oraz nazwy instrukcji i nazwy operatorów.
Kompilatory Pascala nie odróżniają małych liter od wielkich i tym samym pozwalają
wykorzystać małe i wielkie litery w dowolnej kolejności przy zapisie słów kluczowych
oraz różnych nazw, określanych przez programistę,.
W języku Pascal występują dwa typy liczb: całkowite i rzeczywiste. Wartości liczb
całkowitych należą przedziału (-32768; +32767).
Przykłady liczb całkowitych:
+378 -25 0 1991
Liczby rzeczywiste można zapisywać w dwóch postaciach. W pierwszej postaci
liczba dziesiętna zawiera kropkę dziesiętną i może zostać zapisana ze znakiem + lub - .
Przykłady liczb rzeczywistych:
-19.91 0.0 0.025 25.0
W drugiej postaci, znormalizowanej (z literą E), liczba rzeczywista składa się z
liczby całkowitej lub liczby rzeczywistej w pierwszej postaci, litery E oraz liczby
całkowitej ze znakiem lub bez znaku, która stanowi potęgę liczby 10. Oto poprzednie
przykłady liczb rzeczywistych w drugiej postaci:
-0.1991E+2 0E0 25E-3 25E0
Każdy program w Pascalu składa się z trzech części: a) nagłówka programu, b)
części deklaracyjnej, c) części operacyjnej (podstawowej części programu).
Nagłówek programu stanowi wiersz o następującym formacie:
62
PROGRAM nazwa ( );
W tym wierszu słowo „nazwa” jest nazwą własną (identyfikatorem) programu, a
wewnątrz nawiasów może być podana informacja o sposobie (urządzeniach)
wprowadzania lub wyprowadzania danych dotyczących programu, na przykład:
Program SYMULACJA(input, output);
Jeśli program dla wymiany danych wykorzystuje standardowe urządzenia (monitor,
drukarka), to w wielu implementacjach Pascala nawiasy, razem z informacją w nich
zawartą, mogą być opuszczone. Średnik kończący nagłówek jest zawsze niezbędny.
W części deklaracyjnej powinny być zdefiniowane nazwy i typy danych używanych
w programie oraz deklaracje procedur i funkcji.
Część operacyjna zawiera ciąg zleceń (instrukcji) przekazywanych komputerowi do
wykonania. Część ta ma następującą postać:
begin ciąg instrukcji
end. Każda definicja danych, każda instrukcja powinna zawierać umieszczony na końcu
średnik. Wyjątkiem mogą być tylko instrukcje przed słowem end, kiedy średnik na
końcu instrukcji może być opuszczony. Pomiędzy instrukcją a słowem end może być
wstawiony tylko komentarz, czyli dowolny napis, ujęty w nawiasy klamrowe lub
zawarty pomiędzy parą symboli (* i *). Żadne inne symbole nie są dopuszczalne.
Liczba spacji między poszczególnymi słowami programu nie ma znaczenia. Przez
„słowo” rozumiemy liczbę, nazwę, zlecenie, symbol specjalny lub słowo kluczowe. Na
końcu programu (tzn. jego ostatnim symbolem) powinna być kropka.
Wyjaśnimy teraz strukturę programu na przykładach dwóch wersjach programu do
wprowadzania i sumowania N wartości liczbowych. Pierwsza wersja (program 7.1.1)
wykonuje odczyt z klawiatury i sumowanie N liczb rzeczywistych, gdzie wartością N
może być dowolna liczba całkowita. W drugiej wersji (program 7.1.2) oprócz
omówionych operacji dodatkowo w pamięci wewnętrznej komputera określa się tablicę,
w której wprowadzone liczby są przechowywane.
63
W nawiasach klamrowych podane są komentarze. Komentarze mogą być włączone w
dowolnym miejscu programu i nie mają żadnego wpływu na jego działanie. Tekst
zawarty w apostrofach określa stałą tekstową, czyli po prostu tekst, który powinien być
wyświetlony na ekranie w niezmienionej postaci.
Niektóre kompilatory Pascala zezwalają na wykorzystanie w stałych tekstowych i
komentarzach liter języka rodzimego, nie należących do standardowego alfabetu
Pascala. Jeśli takiej wersji kompilatora nie posiadamy, to w programie możemy
wykorzystać wyłącznie symbole określone w podanym wyżej alfabecie języka.
W komentarzach do programów przedstawionych w tej książce nie będziemy używać
polskich liter w celu uniknięcia niepożądanych komplikacji podczas wykonania tych
programów na różnych komputerach.
Program 7.1.1. Wprowadzanie i sumowanie liczb
Program SUMA1; { Naglowek programu. }
Var {Deklaracja zmiennych: }
I, N : integer; { - calkowitych, }
X, S : real; { - rzeczywistych. }
BEGIN { Poczatek czesci operac.} Write('Podaj liczbe ', {Wskazowka na podanie }
'skladnikow sumy: N = '); { wartosci N }
Readln(N); { Wczytanie wartosci N }
S := 0; { Poczatkowo suma S=0 }
For I:=1 to N do {Okreslanie petli }
Begin { Poczatek petli}
Write('Podaj kolejna liczbe: '); Readln(X); { Wprowadzanie liczb }
S := S + X; {Dodawanie kolejn.liczb}
End; { Koniec petli}
Writeln('Suma liczb S = ',S); {Wyswietlanie wynikow}
Writeln('* Dziekuje, koniec pracy. *')END. { K o n i e c programu }
Przykład wykonania programu 7.1.1: Podaj liczbe skladnikow sumy: N = 3 Podaj kolejna liczbe: 35
64
Podaj kolejna liczbe: 3.6 Podaj kolejna liczbe: 4 Suma liczb S = 4.2600000000E+01 * Dziekuje, koniec pracy. * W programie tym tekst: 'Podaj kolejna liczbe: jest wyświetlany na ekranie N razy.
Dla dużej wartości N podobny tekst lepiej wyświetlić tylko jeden raz. Dlatego
konieczne jest anulowanie zlecenia wyświetlania tego tekstu w środku pętli i
ulokowanie odpowiedniej wskazówki użytkownikowi przed instrukcją pętli. Uwagi te są
uwzględnione w programie 7.1.2. Do przechowywania nie więcej niż 500
wprowadzanych liczb rzeczywistych została określona w nim tabela o nazwie X.
Program 7.1.2. Formowanie tabeli i sumowanie jej składowych.
Program SUMA2; { Naglowek programu }
Const Nmax=500; { Deklaracja stalej Nmax }
Var { Deklaracja zmiennych: }
I, N : integer; { - calkowitych, }
S : real; { -rzeczywistej }
X : array[1..Nmax] of real; { Deklaracja tabeli X }
BEGIN { Poczatek czesci operac. }
Write('Podaj liczbe skladnikow', { Wskazowka na podanie }
' sumy: N = '); { wartosci N }
Readln(N); { Wprowadzanie N }
S := 0.; { S - dla sumowania liczb }
Writeln('Podaj ',N, { Wskazowka na wprowa- }
'liczb rzeczywistych:'); { dzanie liczb. }
For I:=1 to N do { Okreslanie petli. }
Begin { Poczatek petli }
Readln(X[I]); { Wprowadzanie liczby }
S:=S+X[I]; {Dodawanie liczby do sumy}
End; { Koniec petli }
Writeln('Suma liczb S = ',S) { Wyswietlanie wynikow }
END. { K o n i e c programu. }
Przykład wykonania programu 7.1.2:
Podaj ilosc skladnikow sumy: N = 3 Podaj 3 liczby rzeczywiste : 2.6
65
3 16.25 Suma liczb S = 2.1850000000E+01
7.2. Typy danych
Instrukcje (operacje) w Pascalu mogą być wykonywane z danymi zdefiniowanymi
jako stała lub zmienna. W programowaniu przez stałą rozumiemy taką daną, wartość
której nie zmienia się w trakcie działania programu, a przez zmienną taką daną, której
mogą być przypisywane różne wartości. Deklarowanie zmiennych w programie polega
na określeniu ich nazw (identyfikatorów) oraz na podaniu ich typów, to znaczy
wyznaczeniu zakresu możliwych wartości. Wybierając identyfikatory danych, trzeba
pamiętać, że niektóre kompilatory Pascala rozróżniają tylko początkowe osiem znaków
identyfikatora. Przekroczenie tego może spowodować błędy.
Podstawowe typy danych, jakie mogą występować w programach przygotowanych w
języku Pascal (dokładniej w TP), przedstawia rys. 7.1.
Omówmy najważniejsze z tych typów. Do standardowych typów danych zaliczamy:
INTEGER, REAL, CHAR, BOOLEAN. INTEGER to typ całkowity, do którego
należą wszystkie liczby całkowite (dodatnie i ujemne). W TP wartości liczb typu
INTEGER są w przedziale od -32768 do +32767. Z typem całkowitym są związane
następujące operatory arytmetyczne: "+" dodawanie, "-" odejmowanie, "*"
mnożenie, " DIV " dzielenie, " MOD " reszta z dzielenia.
Przykłady wykonania ostatnich operatorów:
8 DIV 3 = 2; 10 MOD 3 = 1;
REAL typ rzeczywisty, określa zbiór liczb rzeczywistych. Z typem rzeczywistym są
związane następujące operatory arytmetyczne: "+" dodawanie, "-" odejmowanie, "*"
mnożenie, "/" dzielenie.
całkowity INTEGER, WORD, BYTE, SHORTINT, LONGINT rzeczywisty REAL, SINGLE, DOUBLE, EXTENDED,COMP znakowy CHAR
66
logiczny BOOLEAN prosty okrojony
wyliczeniowy
Typ łańcuchowy STRING
tablicowy ARRAY strukturalny rekordowy RECORD
zbiorowy SET plikowy FILE
wskaźnikowy
Rys. 7.1. Klasyfikacja typów danych w TP
CHAR typ znakowy, definiujący skończony zbiór wartości, którego elementami są
znaki dostępne w języku Pascal.
BOOLEAN typ logiczny, określający dwuelementowy zbiór wartości. Wartościom
tego typu odpowiadają standardowe nazwy: TRUE (prawda) i FALSE (fałsz).
Użycie operatorów logicznych:
AND ("i" operator koniunkcji),
OR ("lub" operator alternatywy),
NOT ("nie" operator logicznego zaprzeczenia)
umożliwia otrzymywanie wyników zgodnie z zasadami algebry Boole'a. Wartości
TRUE lub FALSE mogą przybierać również wyniki zastosowania operatorów
porównania: "=" równy, "<>" nierówny, "<" mniejszy, "<=" mniejszy lub równy,
">" większy, ">=" większy lub równy. Obydwa argumenty przy porównywaniu
dwóch wartości zazwyczaj muszą być tego samego typu.
Podczas obliczania wartości wyrażeń arytmetycznych i logicznych kolejność
wykonywania operacji, przy braku nawiasów, jest następująca:
1) NOT
2) * / DIV MOD AND
3) + - OR
67
4) <= = >= > < <>
Operacje z tego samego poziomu są wykonywane od lewej do prawej.
Przykłady deklaracji typów, w tym typów wyliczeniowego i okrojonego, są podane w
następnym rozdziale. W celu zdefiniowania bardziej złożonych typów danych, niż
przedstawione powyżej, użytkownik może określić (według ustalonych reguł) własne
typy danych.
W systemie Turbo Pascal (TP), zaczynając od jego wersji 6.0 wprowadzono 4 nowe
typy całkowite, określające odpowiednio liczby z następujących przedziałów (w
nawiasach okrągłych podano liczbę bajt pamięci zajmowanych przez jedną wartość):
BYTE < 0; 255 > (1 bajt),
SHORTINT < -128; +128 > (1 bajt),
WORD < 0; 65535 > (2 bajty),
LONGINT <-2147483648, +2147483647> (4 bajty).
Przypomnijmy, iż liczby typu INTEGER < -32768, 32767 > zajmują po 2 bajty
pamięci.
Do pracy z koprocesorem arytmetycznym 8087 (urządzeniem do rozszerzania
możliwości i przyśpieszania wykonania operacji arytmetycznych) w TP wprowadzono 4
dodatkowe typy rzeczywiste: SINGLE, DOUBLE, EXTENDED i COMP. Przez typ
COMP określa się wartości całkowite przechowywane w komórkach komputera według
reguł kodowania liczb rzeczywistych.
Szczegółowe dane związane z wartościami typu rzeczywistego przedstawiono w
następującej tabeli:
Typ Przedział dla wartości Liczba cyfr Liczba bajtówREAL 2.9E-39..1.7E38 11-12 6SINGLE 1.5E-45..3.4E38 7-8 4DOUBLE 5.0E-324..1.7E308 15-16 8EXTENDED 3.4E-4932..1.1E4932 19-20 10COMP -9.2E18..+9.2E18 19-20 8
68
Dla ułatwienia pracy z ciągami znaków (tekstami) w języku TP wprowadzono typ
łańcuchowy:
STRING[n].
Typ łańcuchowy określa zbiór składający się z n znaków rozszerzonego kodu
ASCII. Przyjmuje się, że typ łańcuchowy STRING jest identyczny z typem
STRING[255].
7.3. Deklaracje
7.3.1. Deklaracja etykiet
Dowolna instrukcja programu może być poprzedzona etykietą. Umożliwia to zmianę
sekwencyjnego wykonania ciągu instrukcji programu i przekazywanie sterowania
(dokonywania skoku) do instrukcji poprzedzonej etykietą za pomocą instrukcji skoku
GOTO . Etykieta składa się z identyfikatora lub ciągu do czterech cyfr. Bezpośrednio po
etykiecie występuje znak „:” (dwukropek), który oddziela daną etykietę od innej
etykiety lub instrukcji. Wszystkie używane w programie etykiety powinny być
zadeklarowane w części deklaracyjnej programu w sposób następujący:
LABEL lista_etykiet;
Poszczególne etykiety w liście etykiet oddziela się przecinkiem. Przykład:
LABEL 1, 2, 3, Andrzej, Pawel, Natalia;
7.3.2. Deklaracja stałych
Typy stałych są określane w części deklaracyjnej programu poprzez definicje ich
wartości. Lista określanych stałych powinna być poprzedzana słowem CONST. Na
przykład:
CONST Pi = 3.141593; { stała rzeczywista } UWM = 'Uniwersytet Warmińsko-Mazurski'; { stała znakowa (tekstowa) } KonSwiat = FALSE; { stała logiczna }
69
Tak zdefiniowane stałe występują w części operacyjnej programu jako nazwy. Jest
oczywiste, że oprócz stałych określonych w części deklaracyjnej, w instrukcjach
programu mogą również wystąpić stałe zapisane w postaci liczb całkowitych lub
rzeczywistych, łańcuchów symboli lub stałych logicznych TRUE lub FALSE.
7.3.3. Deklaracja typów
Dla standardowych typów danych ustalone są ich nazwy, dopuszczalne wartości i
operacje nad nimi. Standardowe typy nie potrzebują specjalnej deklaracji. Omówimy tu,
w jaki sposób można stosować pewne własne typy danych. Aby zdefiniować własny typ
danych należy określić typy jego składowych, a następnie przyporządkować nazwę
zbiorowi tych składowych. Własne typy definiujemy w części deklaracyjnej programu.
Na początku części deklaracyjnej programu, dotyczącej definiowania typów danych,
umieszcza się słowo TYPE, za którym każdy wprowadzany typ określa się w postaci
następującej:
Nazwa_typu = Typ;
Przedstawimy po dwa przykłady definiowania własnych typów danych
wyliczeniowego i okrojonego. TYPE POGODA = (slonce, chmury, deszcz, snieg);
DniTyg = (poniedz,wtor,sroda,czwart,piat,sob,niedz); DniMies = 1..31; DniRob =
poniedz..piat; 7.3.4. Deklaracja zmiennych
Wszystkie zmienne, które wystąpią w części operacyjnej programu muszą zostać
zadeklarowane, tzn. dla każdej z nich muszą być przyporządkowane nazwa oraz
określony typ. Deklaracja zmiennych zaczyna się słowem VAR, za którym podaje się
listę zmiennych, dwukropek i nazwy typu lub definicje typu. Oto przykład tej części
deklaracyjnej:
VAR M, N, I, J : Integer; R, S : Real; Dzen1, Dzen2 : DniTyg;
70
Data : DniMies; RokNar : 1910..2000; Litera : 'A'..'Z'; Koniec : Boolean;
7.4. Instrukcje
Instrukcje opisują ciąg operacji, które mają być wykonane na obiektach
zdefiniowanych w dziale deklaracji. Instrukcje języka Pascal dzielą się na proste, tj.
takie, które nie zawierają jako składowych innych instrukcji, oraz strukturalne,
zbudowane na podstawie pewnego schematu strukturalizacji kilku instrukcji.
Do instrukcji prostych zaliczamy:
instrukcję przypisania,
instrukcję skoku,
instrukcję wywołania procedury.
Jako instrukcje strukturalne określamy:
instrukcję złożoną,
instrukcję warunkową,
instrukcję określania pętli programu (iteracyjną).
Poszczególne instrukcje oddziela się średnikami. Każda instrukcja może być
poprzedzona jedną lub kilkoma etykietami.
7.4.1. Instrukcja przypisania
Instrukcja przypisania składa się z trzech części i ma następującą postać:
X := w;
gdzie: X nazwa zmiennej lub nazwa funkcji; symbole ":=" oznacza przypisanie
wartości; w wyrażenie arytmetyczne lub logiczne. Zazwyczaj typ zmiennej
umieszczony z lewej strony symbolu ":=" powinien być taki sam jak i typ wyrażenia z
jego prawej strony.
Przykłady:
VAR
71
Pi, d1, d2, d3 : real; L : boolean;UWM,Tek : string;B : array [1..20] of real; {wektor, 20 składowych rzeczywistych}A : array [1..10,1..20] of real; {tablica, 10x20 składowych rzeczywistych}
Begin Pi := 3.141593;
d1 := d2+d3-5.2; B[5] := (B[3] + d1) * Pi; A[3,4] := (A[1,2]*B[5]-d1) / 2.27; UWM := 'Uniwersytet Warmińsko-Mazurski'; Tek := UWM + ' w Olsztynie';
Oczywiście, wartość wyrażenia zapisana z prawej strony symbolu przypisania ":="
może być obliczona tylko wtedy, gdy komputer będzie znał wartości wszystkich
składowych tego wyrażenia.
72
7.4.2. Instrukcja skoku
Jest to instrukcja nie strukturalna i bardzo trudna w analizie poprawności. Nie zaleca
się stosować instrukcji skoku w programach. Wymieniamy tą instrukcję tylko dla tego,
że jest ona w opisie standardu Pascala. Opisujemy ją również ze względów
historycznych wcześniej, podczas pisania prostych programów, bez wymagań ich
strukturalności instrukcja ta była stosowana dość często.
Instrukcja skoku ma postać:
GOTO etykieta;
Powoduje ona przejście do instrukcji programu poprzedzonej odpowiednią etykietą.
Niedozwolony jest skok do podprogramu oraz do wnętrza instrukcji strukturalnej.
Przykład fragmentów programu z zastosowaniem instrukcji GOTO:
Program Rodzina; LABEL 1, Nat; ................ Begin ............ 1: y:=5*x+1; ............ GOTO 1; ......... GOTO Nat; ............ Nat: Natali := 'artysta'; ............ End.
7.4.3. Instrukcje wywołania procedur, wprowadzania / wyprowadzania danych
Do wprowadzania z klawiatury (ze standartowego pliku wejściowego INPUT)
wartości zmiennych wejściowych wykorzystuje się następującą procedurę języka
Pascal:
READ (lista zmiennych);
73
Zmienne listy zmiennych, których nazwy są rozdzielane przecinkiem, powinny
należeć do jednego ze standardowych typów danych. Po spotkaniu słowa READ
komputer przerywa wykonanie programu i oczekuje na wprowadzenie wartości
wszystkich zmiennych. Jeśli po podaniu kolejnej wartości zmiennej nie zostanie
naciśnięty klawisz ENTER, to następna wartość powinna być oddzielona od poprzedniej
przynajmniej jedną spacją. Po podaniu ostatniej wartości z listy zmiennych użycie
klawisza ENTER jest obowiązkowe.
W celu wyprowadzenia wartości pewnych obiektów na ekran monitora (standardowy
plik wyjściowy OUTPUT), wykorzystujemy następującą procedurę Pascala:
WRITE (lista obiektów);
Elementami listy obiektów mogą być nazwy danych (zmiennych lub stałych),
wyrażenia (arytmetyczne lub logiczne), napisy (teksty w apostrofach). Rozdzielane są
one między sobą przecinkiem. Po wypisaniu wartości obiektów z podanej listy, kursor
zostaje za ostatnim wyprowadzonym symbolem. Jeśli w procedurze wyprowadzania
danych zamiast słowa WRITE wykorzystamy WRITELN, to po wypisaniu wartości
obiektów z podanej listy kursor zostanie przemieszczony na początek nowego wiersza.
Przy wykorzystaniu kompilatora TURBO Pascal, dla wyprowadzania kopii
wyświetlanych wartości na drukarkę, jako pierwszy parametr listy obiektów podaje się
wskaźnik LST, a nazwa procedury tym razem ma postać następującą:
WRITE(LST, lista_obiektów);
Przy stosowaniu procedury WRITE lub WRITELN istnieje możliwość formatowania
wyprowadzanych danych. Tak, za każdą nazwą zmiennej lub stałej może być
umieszczony dwukropek i liczba całkowita, określająca ilość pozycji (na ekranie lub na
papierze) zarezerwowanych dla wyprowadzanej wartości. Dla liczb rzeczywistych
istnieje możliwość określenia ilości pozycji przeznaczonych dla części ułamkowej
poprzez dopisanie jeszcze jednego dwukropka i liczby całkowitej.
Na przykład dla wartości R = 25.376 instrukcja
Write('Promien = ',R:6:2);
spowoduje wyprowadzanie następujących wartości:74
Promien = 25.38
Przykłady wykorzystania procedur wyprowadzania danych podano w następującym
programie 7.4.1.
Program 7.4.1. Porównywanie liczb z wykorzystaniem zmiennej logicznej
Program Porown; Var M, N: integer; test: boolean;Begin Write('Podaj dwie liczby calkowite: '); Read(M,N); test := M < N; Writeln('To jest ',test,', ze ',M,' mniej od ',N); Writeln('(TRUE - prawda, FALSE – falsz)')End.
Oto przykład wykonania tego programu:
Podaj dwie liczby calkowite: 7 3To jest FALSE, ze 7 mniej od 3(TRUE - prawda, FALSE – falsz)
W opisie składni kolejnych instrukcji przez słowo „instrukcja”, „instrukcja-1” lub
„instrukcja-2” będziemy rozumieli albo pojedynczą instrukcję języka Pascal, albo
instrukcję złożoną.
7.4.4. Instrukcja złożona
Przez instrukcję złożoną będziemy rozumieli ciąg instrukcji ujęty w nawiasy BEGIN
(początek) i END (koniec). Po tej definicji wprowadzone w rozdz. 4.1 pojęcie "część
operacyjna programu" może być określone jako instrukcja złożona z kropką po słowie
END. Instrukcja złożona tworzy z ciągu instrukcji jedną i jest używana w przypadku,
gdy składnia języka wymaga użycia jednej instrukcji, a niezbędne jest wykonanie wielu.
7.4.5. Instrukcje warunkowe IF i CASE
75
Instrukcja warunkowa IF służy do podejmowania decyzji w Pascalu i ma następującą
postać:
IF warunek THEN instrukcja-1 ELSE instrukcja-2;
lub krótką formę tej instrukcji:
IF warunek THEN instrukcja;
Pojęcie warunku, czyli wyrażenia logicznego, określone jest w rozdz. 1.1. Jeśli
warunek przed THEN jest spełniony, następuje wykonanie instrukcji umieszczonej za
THEN , zaś w przypadku przeciwnym wykonywana jest instrukcja znajdująca się za
słowem ELSE. Po wykonaniu wybranej listy instrukcji program przechodzi do
wykonywania instrukcji znajdującej się bezpośrednio za instrukcją IF.
Przykłady:
IF a>b THEN Y:=a-b; IF (a<=0) OR (b<=0) THEN Begin WRITELN('Sprawdz dane'); EXIT End; IF a>b THEN Y:=a-b ELSE Y:=b-a; IF a>b THEN Y:=a-b ELSE IF a<b THEN Y:=b-a ELSE NWD:=Y;
Instrukcja IF służy do dokonywania wyboru między dwiema możliwościami. Można
ją oczywiście powtórzyć wiele razy, jeśli jest wiele możliwości wyboru. W tej ostatniej
sytuacji może być użyta inna instrukcja instrukcja wielowarunkowa CASE.
Sposób wykorzystania instrukcji CASE wyjaśnimy na przykładzie programu 7.4.2.
Program ten wczytuje liczbę z przedziału od 1 do 7 i przypisuje jej wartość zmiennej
Dzien. Następnie instrukcja CASE przekazuje wykonanie instrukcji z etykietą równą
wartości zmiennej Dzien, tzn wyświetla odpowiedni dzień tygodnia. Poza listą
76
instrukcji z etykietami zawsze powinno być zapisane słowo END (sygnał końca tej
listy).
Program 7.4.2. Wykorzystanie instrukcji CASE
Program DniTyg; VAR Dzien: INTEGER; Begin WRITE('Podaj numer dnia w tygodniu: '); READ(Dzien); CASE Dzien OF 1: WRITE(' Poniedzialek.'); 2: WRITE(' Wtorek.'); 3: WRITE(' Sroda.'); 4: WRITE(' Czwartek.'); 5: WRITE(' Piatek.'); 6: WRITE(' Sobota.'); 7: WRITE(' Niedziela.') END; WRITELN End.
Przykład wykonania programu 7.4.2:
Podaj numer dnia w tygodniu: 5 Piatek. 7.4.6. Instrukcje określania pętli FOR, WHILE, REPEAT
Przez pętlę (iteracje) rozumiemy wielokrotne powtarzanie pewnej instrukcji. Do
definiowania pętli w języku Pascal wykorzystuje się następujące instrukcje FOR,
WHILE, REPEAT.
Pierwszą instrukcją określania pętli jest:
FOR zmienna_całkowita := wartość_początkowa_zmiennej
TO wartość_końcowa_zmiennej DO instrukcja ;
77
Instrukcja będzie powtórzona tyle razy, ile wartości z krokiem jeden
przyporządkowano zmiennej całkowitej, sterowanej przez instrukcję FOR. Przykład
użycia instrukcji FOR mieliśmy wyżej w programach 7.1.1 i 7.1.2 dwóch sposobach
rozwiązywania problemu 4.1.
Jeśli w instrukcji FOR zamiast słowa TO użyjemy słowo DOWNTO to zmiana
wartości zmiennej sterowanej będzie się odbywała z krokiem minus jeden. Zaleca się
nie modyfikować w treści instrukcji FOR sterowanej przez nią zmiennej.
Drugą instrukcją określania pętli jest:
WHILE wyrażenie_logiczne DO instrukcja ;
Instrukcję wykonuje się dopóty, dopóki wyrażenie logiczne jest spełnione. Zwróćmy
uwagę na to, że komputer sprawdza wartość wyrażenia logicznego przed wykonaniem
instrukcji. Jeżeli wartością tego wyrażenia jest TRUE (prawda), tj. jeżeli warunek został
spełniony, zaczyna się wykonywanie listy instrukcji. Jeżeli wartością wyrażenia
logicznego jest FALSE (fałsz), to wykonana zostanie kolejna instrukcja znajdująca się
bezpośrednio po instrukcji WHILE.
Program 7.4.3 rozwiązuje wcześniej opisany problem 3.1.2 (znalezienie
największego wspólnego dzielnika dwóch dodatnich liczb całkowitych). Pętla w tym
programie określona jest za pomocą instrukcji WHILE.
Program 7.4.3. Znalezienie NWD dla dodatnich liczb całkowitych
Program DemWhile; Var X,Y,R1,R2 : integer;Begin Repeat {sprawdzanie prawidlowosci danych wejsciowych} Write('Podaj dwie dodatnie liczby calkowite: '); Read(X,Y); R1:=X; R2:=Y; Until (X > 0 ) and ( Y > 0 ); {wyjscie z petli, jesli obydwie wartosci dodatnie} While R1 <> R2 Do { dopoki R1 nie = R2 wykonuj }
78
If R1 > R2 Then R1:=R1-R2 Else R2:=R2-R1; Writeln('X = ',X,' Y = ',Y,' NOD(X,Y) = ',R1)End.
Przykłady wykonania programu 7.4.3:
Podaj dwie dodatnie liczby całkowite: 120 25 X = 120 Y = 25 NOD(X,Y) = 5
Trzecią instrukcją określania pętli jest:
REPEAT instrukcja UNTIL wyrażenie_logiczne ;
Instrukcję wykonuje się dopóty, dopóki wyrażenie logiczne nie zostanie spełnione,
czyli po wykonaniu instrukcji komputer sprawdza wartość wyrażenia logicznego. Jeżeli
wartością tego wyrażenia jest FALSE (fałsz), wykonanie instrukcji zaczyna się od
początku. Gdy zaś wartością tego wyrażenia logicznego jest TRUE (prawda), wykonuje
się instrukcję występującą bezpośrednio po instrukcji REPEAT.
Pętla obliczeń w programie 7.4.3, która jest określona za pomocą instrukcji WHILE,
może być zdefiniowana za pomocą instrukcji REPEAT następująco:
REPEAT IF R1 > R2 THEN R1:=R1-R2 ELSE R2:=R2-R1;UNTIL R1 = R2; Zauważmy, że w tym przypadku określenie pętli za pomocą REPEAT spowoduje
błąd obliczeń NWD(X,Y) dla jednakowych (większych od zera) wartości wejściowych
X i Y. Błąd będzie spowodowany tym, że w przeciwieństwie do instrukcji WHILE
warunek wykonania pętli zostanie sprawdzony po wykonaniu instrukcji, i w tym,
szczegółowym przypadku w ogóle nie będzie wyjścia z pętli (przed UNTIL wartość R2
staje się zerem).
79
7.4.7. Zadania do ćwiczeń
1) Czy są dopuszczalne w Pascalu następujące instrukcje:L1 := x < y;L2 := sin(a)+1 >= pi; y := a + x - L1;L3 := L2 or L1;L3 := ’TWP’=TWP; L3 := ’TWP’=TWP and L1; L3 := (’TWP’=TWP) or L2;
Odpowiedź: Instrukcje w lewej kolumnie są dopuszczalne pod warunkiem deklaracji zmiennych jak np.:
Var L1, L2, L3 : boolean; x, y, a : integer; TWP : string;
w instrukcjach prawej kolumny nie są zgodne typy zmiennych, odpowiednio, L1 i y , oraz L1
i TWP.
2) Podaj wyświetlane na ekranie wartości po wykonaniu fragmentu programu:
x:=10; y:=2; a:=8; b:=3;if a < b then
x:=x+5;y:=y+4;
write (x,’ ’,y); Odpowiedź : 10 6
3) Co się wyświetli na ekranie po wykonaniu:
x:=4.2;while x<12 do x:=x+0.5;write (x:4:1);
Odpowiedź: 12.24) Co się wyświetli na ekranie po wykonaniu:
Var k,x,y,z : real; begin x:= -3.14; y:= -x; k:=0;
repeatk := k+1; z := x + y;
writeln ( k:2:0, ’) ’, x:8:2,’ ’,z:8:2); x := x+3;
until x>y;writeln (x:8:2);
end.Odpowiedź: 1) -3.14 0
2) -0.14 33) 2.86 6 5.86
80
7.5. Procedury i funkcje
Podczas pisania dużego programu dzieli się go często na spójne tematycznie części
podprogramy. Taki styl programowania ma następujące zalety:
program staje się łatwiejszy do zrozumienia,
każdy podprogram może być projektowany i testowany oddzielnie,
podprogram może być wywołany wielokrotnie w trakcie działania programu,
sprawdzone podprogramy można wykorzystać w innych programach.
Podprogramy w Pascalu występują w postaci procedur lub funkcji. Do
podprogramów należą również moduły (pakiety programów). Te ostatnie omówimy w
rozdziale 7.9.
7.5.1. Deklaracje oraz przykłady zastosowań procedur i funkcji
Deklaracja procedur i funkcji dokonuje się w części deklaracyjnej programu, poza
deklaracją stałych, typów, zmiennych. W części operacyjnej programu mogą być
wywoływane tylko takie procedury i funkcje, które są zdefiniowane w dziale deklaracji.
Deklaracja procedur i funkcji zaczyna się nagłówkiem. Poza nagłówkiem definiują się
obiekty lokalne danej procedury lub funkcji (o ile jest taka potrzeba) i określa się
instrukcja złożona, dokonująca wymaganych obliczeń.
Nagłówek procedury zapisuje się w sposób następujący:
PROCEDURE nazwa_procedury (lista_parametrów_formalnych);
Nagłówek funkcji ma inną postać:
FUNCTION nazwa_funkcji(lista_parametrów_formalnych) : typ;
Przez parametry aktualne rozumiemy obiekty, których wartości są przypisywane
odpowiednim parametrom formalnym podczas wywoływania podprogramu. Ponieważ
parametry formalne i aktualne mają taką samą składnię i znaczenie dla funkcji i
procedur, będą one omawiane łącznie dla obu rodzajów podprogramów.
81
Dla każdej ze składowych listy parametrów formalnych powinien być określony jej
typ w sposób następujący:
parametr: typ_parametru;
Parametry mające ten sam typ mogą być zgrupowane, to znaczy przedstawione w
postaci ciągu parametrów rozdzielanych przecinkami. Jeżeli wartości pewnych
parametrów mają być zmienione za pomocą instrukcji procedury lub funkcji po to, aby
mogły być wykorzystane na zewnątrz procedury lub funkcji (już zmienione), to
deklaracje takich parametrów poprzedza się słowem VAR:
VAR parametr: typ_parametru;
Ten ostatni sposób przekazania parametru nazywany jest przekazywaniem parametru
przez zmienną. Faktycznie oznacza to, że nazwy parametrów formalnych,
poprzedzonych słowem VAR i nazwy odpowiadających im parametrów aktualnych
określają te same fizyczne komórki komputera.
Jeśli parametr formalny nie poprzedza się słowem VAR, jest to przekazywaniem
parametru przez wartość. Po prostu parametr formalny podczas wywoływania
podprogramu zastępuje się wartością parametru aktualnego.
Przykłady nagłówków procedury i funkcji:
PROCEDURE Proc_1 (A,B: String; C:Real; Var D,E,F:Real);FUNCTION FNK (G,H: Integer; Var S: String): Real; Jeśli lista parametrów formalnych jest pusta, to w deklaracjach takich procedur i
funkcji (oraz w instrukcjach wywołujących te podprogramy) nawiasy razem z listą
parametrów mogą być opuszczone.
Podczas wywoływania procedur lub funkcji, poza ich nazwami muszą być zadane (o
ile to jest potrzebne) wartości parametrów aktualnych.
Istota różnicy między procedurą a funkcją polega na tym, że procedura na podstawie
wartości parametrów wejściowych przedstawionych na liście parametrów, generuje
jedną lub więcej wartości wyjściowych, które mogą być przypisywane
odpowiednim parametrom z listy parametrów. Natomiast funkcja na podstawie
82
wartości parametrów wejściowych generuje tylko jedną wartość wyjściową, którą
przypisuje się nazwie funkcji.
Drugą różnicą między procedurą a funkcją jest to, że pierwszą wykonuje się za
pomocą specjalnej instrukcji procedury, natomiast drugą wywołuje się poprzez podanie
jej nazwy bezpośrednio w wyrażeniach obliczeniowych lub w instrukcjach
wyprowadzania danych.
Zadanie 7.5.1. Wyświetlić na ekranie pewien komunikat definiowany przez funkcję.
Rozwiązaniem tego zadania jest program 7.5.1, jako przykład deklaracji i wywołania
funkcji bez parametrów.
Program 7.5.1. Deklaracja i wywołanie funkcji bez parametrów
Program FunkcjaBezParam; FUNCTION Fun : string; begin Fun:='TWP w Olsztynie'; end; begin write(Fun); end.
W zależności od sposobu deklaracji procedury lub funkcji, opis parametrów
aktualnych może być pusty albo zawierać listę parametrów ujętą w nawiasy okrągłe.
Postać parametru aktualnego zależy od sposobu zdefiniowania odpowiadającego mu
parametru formalnego. Jeśli parametr formalny zdefiniowano w powiązaniu ze słowem
kluczowym VAR (przekazywanie parametru przez zmienną), to parametr aktualny
(argument wywołania) musi być zmienną tego samego typu, co parametr formalny.
Podczas wykonywania programu adresem takiego parametru formalnego staje się adres
zmiennej będącej parametrem aktualnym, tzn. wszelkie operacje dokonywane na
parametrze formalnym dotyczą bezpośrednio tej zmiennej.
83
Jeśli parametr formalny zdefiniowano bez słowa VAR, to parametr aktualny musi być
wyrażeniem o wartości zgodnej w sensie przypisania z typem parametru formalnego.
Wartość wyrażenia jest obliczana przed rozpoczęciem wykonywania instrukcji
podprogramu i następnie przypisywana lokalnej zmiennej roboczej w podprogramie. W
trakcie wykonywania podprogramu parametr formalny oznacza tę zmienną.
Jako przykład definicji i wywołania procedury przedstawiamy niżej program 7.5.2
rozwiązywania następującego zadania:
Zadanie 7.5.2. Uporządkować rosnąco i wyświetlić na ekranie N par liczb
rzeczywistych wprowadzanych z klawiatury.
W programie 7.5.2 parametry X i Y procedury PORZ określone są jako zmienne, tj.
poprzedzone słowem Var. Oznacza to, że wartości tych parametrów mogą być
zmienione za pomocą instrukcji procedury i dalej wykorzystane na zewnątrz procedury
(już zmienione).
84
Program 7.5.2. Przykład stosowania procedury dla porządkowania par liczb
Program DemProc; Var I,N : integer; L1,L2 : real; Procedure PORZ (Var X,Y : real); Var Z : real; begin Z:=Y; Y:=X; X:=Z end; Begin Write('Podaj N = ');
Readln(N);For I:=1 to N do Begin Write('Podaj ',I:3,' para liczb: '); Read(L1,L2); If L1 > L2 then PORZ(L1,L2); Writeln(L1:5:1,L2:5:1) End; Writeln('* Dziekuje, koniec pracy. *')END.
Przykład wykonania programu 7.5.2:
Podaj N = 2Podaj 1 para liczb: 2.5 1 1.0 2.5Podaj 2 para liczb: 25 36.5 25.0 36.5* Dziekuje, koniec pracy. *
Kolejnym przykładem definicji i wywołania funkcji bez parametrów będzie
rozwiązanie następującego zadania:
Zadanie 7.5.3. Zadeklarować funkcję dla generowania liczb losowych w przedziale (0,
1), wykorzystując w tym celu niżej określony algorytm Devisa. Stosując zadeklarowaną
funkcję, dla wartości danych wejściowych N, a, b obliczyć i wydrukować N liczb
losowych zawartych w przedziale (a, b).
Algorytm Devisa generowania liczb losowych w przedziale (0, 1) określa się
następująco. Dla dwóch zmiennych V1 i V2 przypisuje się wartości początkowe:
V1 := 3.1415926 i V2 := 0.5421018.
Każde kolejne wykonanie następujących pięciu operacji:
1) X := V1 + V2; 2) Jeśli X >= 4 to wykonać X:= X -4;
85
3) V1 := V2; 4) V2 := X; 5) X := X / 4;
generuje liczbę losową X w przedziale (0, 1).
Stosując wzór Y = (b - a) * X + a, gdzie X jest liczbą losową wybieraną z przedziału
(0, 1), uzyskujemy wartości losowe Y należące do przedziału (a, b).
Rozwiązaniem problemu 7.5.2 jest program 7.5.3, w którym algorytm Devisa
przedstawiono w postaci podprogramu-funkcji Devis.
Program 7.5.3. Definicja i stosowanie generatora liczb losowych
Program DemFunc; Var I, N : integer; A, B, V1, V2, X, Y : real; Function Devis : real; { Deklaracja funkcji } begin X:=V1+V2; If X >= 4. then X := X-4.; V1 := V2; V2 := X; Devis := X * 0.25 end; { Koniec deklaracji funkcji }Begin { Czesc operacyjna programu } V1:=3.1415926; V2:=0.5421018; Write('Podaj N = '); Readln(N); Write('Podaj wartosci A, B: '); Readln(A,B); For I:=1 to N do Begin Y := (B - A) * Devis + A; Write(Y:6:3,' ') End; WritelnEND.
Przykład wykonania programu 7.5.3:Podaj N = 6Podaj wartosci A, B: 0 2018.418 1.129 19.547 0.676 0.224 0.900
86
Zauważmy, że w Turbo Pascalu dla generowania wartości losowych może być
wykorzystana funkcja standardowa o nazwie Random (patrz tabl.7.5.1). Przed
pierwszym wywołaniem funkcji Random zaleca się uruchomienie procedury
Randomize przeznaczonej do zainicjowania generatora liczb losowych. Bez
uruchomienia tej procedury ponowne wykonania programu z użyciem tylko funkcji
Random będą dawały te same ciągi wartości losowych. Stosowanie wartości losowych
można zobaczyć w poniższych programach 7.5.7, 7.5.8, 7.5.9.
W tablicy 7.5.1 podajemy wykaz ważniejszych funkcji standardowych języka Pascal
stosowanych w obliczeniach matematycznych, określając dopuszczalne typy
argumentów i typy wartości tych funkcji.
Tablica 7.5.1. Wykaz ważniejszych funkcji standardowych
Funkcja Typ Typ wartości Wartość funkcji,argumentu funkcji uwagi
ABS(X) INTEGER, REAL INTEGER, REAL |X|SQR(X) INTEGER, REAL INTEGER, REAL X^2SQRT(X) INTEGER, REAL REAL PierwiastekSIN(X) INTEGER, REAL REAL Sin(X), X w radianachCOS(X) INTEGER, REAL REAL Cos(X), X w radianachEXP(X) INTEGER, REAL REAL Eksponenta XLN(X) INTEGER, REAL REAL Log. natur. X>0ARCTAN(X) INTEGER, REAL REAL Arctan(X), X w radianachRANDOM REAL Wartość losowa w (0, 1) RANDOM(X) WORD WORD Wartość losowa całkowita w [0, X)TRUNC(X) REAL INTEGER Obcięcie części ułamkowejROUND(X) REAL INTEGER Zaokrąglenie do wartości całejORD(X) porządkowy INTEGER Numer porządkowySUCC(X) porządkowy jak argum. NastępnikPRED(X) porządkowy jak argum. PoprzednikCHR(X) INTEGER CHAR Znak o danym kodzie
ODD(X) INTEGER nieparzyste BOOLEAN TRUE
ODD(X) INTEGER parzyste BOOLEAN FALSE
EOF(f) plikowy BOOLEAN Koniec plikuEOLN(f) text BOOLEAN Koniec wiersza
87
W praktyce ma wiele zastosowań standardowa procedura VAL, przeznaczona do
konwersji liczb z postaci łańcuchowej do ich prezentacji typu REAL. Aby wyjaśnić
działanie tej procedury rozwiążmy następujące zadanie.
Zadanie 7.5.4. Dla każdej z wartości liczbowych zmiennej x, zadeklarowanej jako
zmienna łańcuchowa, obliczyć i wydrukować wartości funkcji
Warunkiem zakończenia cyklu obliczeń jest przypisanie zmiennej x wartości symbolu
„* ”.
Rozwiązaniem tego zadania jest program 7.5.4, w którym szczegółowe komentarze
wyjaśniają zasady działania procedury Val .
Program 7.5.4. Demonstracja działania procedury Val
Program Demo_proc_Val;Uses crt; Var S : string; K : integer; L, funkcja : real; Function F(x:real): real; begin if x < 0. then F:=sqr (x) else F:=sqrt(x); end;BEGIN clrscr; writeln(' Dla danej wartosci X program zwraca'); writeln(' F(X) = X*X jesli X<0,'); writeln(' F(X) = pierwiastek z X, jesli X>=0'); writeln(' Po zakonczeniu wprowadzania wartosciej X wcisnij "*"'); writeln(' --------------------------------------------------'); writeln;
repeat repeat
88
write(' Wcisnij "*" lub podaj X = '); readln(S); if S='*' then HALT; {wyjscie z programu} VAL (S, L, K); {Konwersja wartosci liczbowej, wprowadzonej do S, zadeklarowanej jako zmienna lancuchowa, do wartosci L typu real.
Jesli po wykonaniu tej operacji wartosc K=0, to konwersje wykonanopoprawnie. Wartosc K<>0 wskazuje numer znaku we wprowadzonymlancuchu, ktory spowodowal blad konwersji}
IF K<>0 then writeln ('pozycja ',K,' jest zla'); until K=0; funkcja := F(l); writeln(' F(X) = ',funkcja:6:2); until false; {tak sie okresla "wieczna" petle z ktorej w tym programie wychodzimy za pomoca instrukcji HALT }END.
Następnym przykładem będzie program rozwiązujący zadanie 7.5.5 w postaci
procedury, która, dla spełnienia swoich zadań, zleca wykonanie pewnych czynności
innej procedurze.
Zadanie 7.5.5. Narysować romb z gwiazd zawierający z każdej strony swoich
przekątnych po n rzędów gwiazd. Na przykład, dla n=1 musi być następujący rysunek:
*****
Program 7.5.5. Rysowanie rombu z gwiazd
Program Romb_gwiazd; { Rysuje romb z gwiazd }Uses crt;Const g = '*';Var n,k: word;
Procedure LiniaGwiazd(k:word); Var l : word; begin write(g:(40-k)); FOR l:=1 TO k DO write(g,g); writeln; end; { koniec procedury LiniaGwiazd }Procedure Romb(n:word);
89
begin writeln; writeln(g:40); FOR k:=1 TO n-1 DO LiniaGwiazd(k); FOR k:=n-2 downTO 1 DO LiniaGwiazd(k); writeln(g:40); end; { koniec procedury Romb } BEGIN { czesc operacyjna }
clrscr;writeln(' Podaj liczbe N wieksza od jeden,',' a zobaczysz romb zbudowany');write (' z 2*N-1 wierszy z gwiazdek, N = ');readln(n);Romb(n);writeln (' Wcisnij Enter');readln;
END. { koniec czesci operacyjnej }
Struktury blokowe. W części deklaracyjnej dowolnej procedury lub funkcji może być
zadeklarowana (pogrążona w nią) inna procedura lub funkcja. Tworzy się w ten sposób
programy ze strukturą blokową. Jest to programowa implementacja pojęcia struktury
blokowej (bloku) wprowadzonego w rozdz. 5.4. Przykładem tego typu deklaracji może
być program 7.5.6.
Program 7.5.6. Organizacja struktur blokowych
Program struktury_blokowe; { przykład deklaracji i wykonania procedury
zawerajacej wewnetrzne deklaracji funkcji }Uses Crt;Var s1,s2:string;
Procedure P1(var t1,t2:string); Function F1:string;
begin writeln(' Zima '); F1:=' Wiosna ';end; {funkcji F1}
Function F2:string;begin writeln(' Lato ');
90
F2:=' Jesien';end; {funkcji F2}
begin { procedury P1 ) t1:=F1; t2:=F2; end; {procedury P1}
Begin {czesc operacyjna programu}clrscr;P1(s1,s2);Writeln(s1,s2);Writeln(' -------------- Wcisnij Enter');readln;
End. { programu }
Wynikiem wykonania programu 7.5.6 jest wyświetlanie na ekranie następujących
pięciu linijek:
ZimaLatoWiosna Jesien------------------ wcisnij Enter
Chyba nie powinno Czytelnikowi sprawić trudności zrozumienie tego, dla czego
właśnie taki jest wydruk?
Zadanie 7.5.7. Zadeklarować procedurę generowania permutacji losowych stosując
algorytm G opisany w rozdziale 6.6. Dać przykład wykonania procedury.
Program 7.5.7. Generowanie permutacji losowych
Program Permutacje_losowe; Const Nmax = 50; Type W = array[1..Nmax] of word;Var n, a, i : word; J : W; { J - dla permutacji}PROCEDURE Perm ( n, a : word; var J : W); { n - liczba skladowych ciagu wejsciowego J; obliczona permutacja J zastepuje J na wyjsciu;
91
a = 1- powoduje generowanie permutacji rownowiarygodnych, a = 2 lub a = 3 spowoduje generowanie permutacji w pewnym sensie zblizonych do ciagu wejsciowego.} Var x, y : real; i, k, s : word;begin For k:=n DownTo 2 Do begin x := Random;
y := 1-x; If a=2 Then y := 1-x*x; If a=3 Then y := 1-x*x*x; i := round (k*y)+1; if i>k then i := k; s := J[k]; J[k] := J[i]; J[i] := s; end;end; { Procedury G(a) } Begin { przykład generowania permutacji losowych } Write('Podaj a, n ='); Readln(a, n); Randomize; {zainicjowanie generatora liczb losowych} For i:=1 To n Do J[i] := i; Perm(n,a,J); For i:=1 To n Do Write(' ',J[i]:3); End. { koniec przykladu }
Innym przykładem zastosowania procedury G, jest rozwiązanie następującego
zadania.
Zadanie 7.5.8. Dla zadanej liczby n rozmiaru macierzy kwadratowej, wygenerować
losową macierz X = {xij}n*n w każdym wierszu i w każdej kolumnie której jest tylko
jedna jedynka, a reszta wartości- to zera.
W takiej macierzy suma składowych w dowolnym wierszu i dowolnej kolumnie
wynosi jeden. Szczegółowymi przypadkami takiej macierzy są zerowe macierze
kwadratowe na przekątnych których postawiono jedynki. Uzyskana macierz
„samotnych” jedynek może określać przydział n stanowisk pracy dla n pracowników
(xij=1, jeśli i -te stanowisko zajmuje j -ty pracownik oraz x ij=0, w wypadku
92
przeciwnym). Inną interpretacją takiej macierzy jest definicja trasy przejazdu
komiwojażera w problemie komiwojażera (xij=1, gdy przejazd od i -go do j -go miasta
jest zaplanowany oraz xij=0, w wypadku przeciwnym).
Takie zadanie rozwiązuje program 7.5.8. W nim została zastosowana procedura
Perm2, która jest uproszczoną wersją procedury Perm ze stałą wartością parametru
a=1.
Program 7.5.8. Budowa macierzy „samotnych” jedynek
Program Losowa_macierz; {„samotne” jedynki } Uses Crt; Const Nmax = 50; Type W = array [1..Nmax] of word; Var n, i, k, s : word;
J : W; X : array[1..Nmax] of W;
PROCEDURE Perm2 (n : word; var J : W); {Uproszczona wersja procedury Perm. Sluzy do generowania
permutacji losowych rownowiarygodnych } Var i,k,s : word; Begin
For k :=n DownTo 2 Do begin i := Round(k*Random); if i=0 then i:=1; s := J[k]; J[k] := J[i]; J[i] := s; end; End; { koniec procedury Perm2 }
BEGINClrScr;Write(' Podaj n = '); Readln(n);Randomize; {zainicjowanie generatora liczb losowych}For i := 1 To n Do
For k := 1 To n Do X[i,k] := 0;For i:=1 To n Do J[i] := i;Perm2(n, J);
For i:=1 To n Do begin
s := J[i]; X[i,s] := 1; end;
93
For i:=1 To n Do begin For k:=1 To n Do Write(' ',X[i,k]:3); writeln;
end; Writeln (' Wcisnij Enter ...'); readln;END.
Zadanie 7.5.9. W prostokącie o współrzędnych: (a, b) lewy górny róg, (c, d)
prawy dolny róg, losuje się N odcinków. Każdy odcinek określa się przez
wygenerowanie dwóch par wartości losowych (x1 , y1 ) i (x2 , y2 ) z rozkładem
jednostajnym, takich, że x1 , x2 (a, c) , y1 , y2 (d, b). Sprawdzić eksperymentalnie
hipotezę, że długość odcinka , jako wartość losowa
ulega rozkładu normalnemu.
Aby rozwiązać to zadanie, niezbędna jest pewna wiedza ze statystyki. Oto kilku
wyjaśnień wymienianych pojęć.
Przez rozkład jednostajny zmiennej losowej w pewnym przedziale występowania
jej wartości, należy rozumieć równomierne, o jednakowej gęstości, wypełnianie tego
przedziału wartościami .
Zmienna losowa ulega rozkładu normalnemu, jeśli przeważająca większość
wystąpień tej zmiennej jest ulokowana dookoła jej wartości średniej. Im większa jest
odległość od wartości średniej, tym mniej wiarygodnym jest pojawienie się wartości
takiej zmiennej.
W uporządkowanym ciągu z n wartości , wartość znajdująca się pośrodku tego
ciągu określana jest jako mediana. Dla liczby n parzystej, mediana oblicza się jako
połowa sumy dwóch środkowych wartości tego ciągu. Jeśli zmienna losowa ulega
rozkładu normalnemu, to wartość mediany będzie zbliżona do wartości średniej
wszystkich wystąpień .
94
Dla sprawdzania hipotezy określonej w zadaniu 7.5.9 proponuje się poniższy
program 7.5.9. Współrzędne prostokąta określono w nim przez stałe (a, b)=(0, 15) i (c,
d)=(20, 0). Sprawdzając działanie programu Czytelnik może przyjąć inne wartości lub
zmodyfikować program dla wprowadzania współrzędnych a, b, c, d z klawiatury.
Po uruchomieniu, program 7.5.9 potrzebuje wprowadzania liczby N ilości losowych
odcinków do wygenerowania, po za czym generuje te odcinki wewnątrz określonego
prostokąta, oblicza i zapisuje długości odcinków w tablicy o nazwie R. Z kolei dalej
oblicza się wartość średnia tych długości oraz ich medianę.
Przy określonych w programie współrzędnych a, b, c, d uruchomienie programu dla
N=100, 200, 500, 1000, 2000, 10000 daje za każdym razem inne wartości średniej i
mediany, ale wszystkie one są zgrupowane około liczby 9. Podobny ciekawy wynik
proponuje się czytelnikowi sprawdzić dla innych współrzędnych prostokąta.
Program 7.5.9. Generowanie odcinków losowych
Program Odcinki_losowe; { W prostokacie o wspolrzednych: (a,b) - lewy gorny rog, (c,d) - prawy dolny rog, generujemy N losowych odcinkow. Obliczamy ich odleglosci i zachowujemy je w tabeli R. Obliczamy S- srednia tych odleglosci, oraz me- mediane, czyli wartosc srednia w uporzadkowanym ciagu wartosci }
Uses Crt; Const Nmax=10000; a=0.; b=15.; c=20.; d=0.; Type W = array[1..Nmax] of real; Var
N, i : word; x1,x2,y1,y2,dxy, {dxy - dlugosc odcinka } s,me : real; {srednia, mediana } R : W; {odleglosci odcinkow losowych}
{-------------------------------------------------------------}Function XY( x1,y1, x2, y2 : real) : real; begin { odleglosc miedzy (x1,y1) i (x2,y2) } XY:=sqrt(sqr(x2-x1)+sqr(y2-y1))
95
end; { koniec obliczania odleglosci }{----------------------------------------------------} Procedure SORT (n:word; Var R : W); Var z:real; j:word; begin { sortowanie babelkowe rosnace } while n>1 do begin n:=n-1; for j:=1 to n do begin if R[j] > R[j+1] then begin z := R[j]; R[j] := R[j+1]; R[j+1]:=z; end; end; end; end; { Procedury SORT }{----------------------------------------------------}Begin {czesc operacyjna} Randomize; ClrScr; Write(' Podaj liczbe odcinkow N = '); Readln(n); s:=0.; For i:=1 to N do {zapamietujemy w R odleglosci N odcinkow} begin x1 := (c-a) * random + a; x2 := (c-a) * random + a; y1 := (d-b) * random + b; y2 := (d-b) * random + b; R[i] := XY( x1,y1, x2, y2); {liczenie kolejnej odleglosci} s := s + R[i]; {suma wszystkich odleglosci} end; writeln; s := s / N; {wartosc srednia dlugosci odcinkow} WRITEln(' Srednia odleglosc odcinkow losowych ',s:6:2); sort (n,r); { sortowanie odleglosci odcinkow } writeln;
i := n div 2; { obliczanie mediany me := ... } if ( n mod 2 ) = 0 then me := (r[i]+r[i+1]) / 2 else me := r[i+1]; WRITEln(' Mediana odleglosci odcinkow ',me:6:2);
96
End.
Ostatnim zadaniem w tym rozdziale, ciekawym z punku widzenia zastosowań, jest
zadanie 7.5.10.
Zadanie 7.5.10. Stworzyć procedurę-funkcję o nazwie Kwota, która na podstawie
wartości parametru X liczby rzeczywistej, określającej kwotę w złotych i groszach,
podaje jako wynik kwotę słownie, która jest przypisywana nazwie funkcji.
Poszczególne słowa kwoty odwzorować tylko przez trzy pierwsze litery. W postaci
separatora rozdzielającego poszczególne słowa należy użyć symbolu gwiazdki. Na
przykład: X=83.52 zapisuje się osi*trz*zl*pie*dwa*gr .
Program 7.5.10. Wydruk obliczonej kwoty słownie
Program Kwota_zlotych; Uses Crt; Var X: real; I : integer;Function Kwota (X:real):string; Var X_text :string[25];
Z :char;Kwa :string[60];
begin str(X:8:2,X_text); {konwersja liczby do tekstu} kwa:=''; { dla podsumowania symboli }
for i:=0 to length(X_text) do begin Z:=X_text[i]; case Z of '0' : kwa:=kwa+'zer*'; '1' : kwa:=kwa+'jed*'; '2' : kwa:=kwa+'dwa*'; '3' : kwa:=kwa+'trz*'; '4' : kwa:=kwa+'czt*'; '5' : kwa:=kwa+'pie*';
97
'6' : kwa:=kwa+'sze*'; '7' : kwa:=kwa+'sie*'; '8' : kwa:=kwa+'osi*'; '9' : kwa:=kwa+'dzi*'; '.' : kwa:=kwa+'zl*' end; {case } end; { for } kwa:=kwa+'gr'; Kwota := kwa; end; { funkcji Kwota }BEGIN {część operacyjna } clrscr; write(' Podaj liczbe '); readln(X); writeln(Kwota(X)); writeln; writeln(' Wcisnij Enter...'); readln;END.
7.5.3. Sposoby porzucenia iteracji pętli, pętli, procedury, programu
W rozdz. 7.9 są opisane podstawowe moduły systemu TURBO PASCAL zawierające
zbiory procedur i funkcji, które mogą być wykorzystywane przy tworzeniu programu.
Na przykład, moduł SYSTEM dołączany do programu automatycznie podczas jego
translacji, zawiera podstawowe procedury i funkcje do pracy z katalogami, plikami,
zmiennymi, do obliczeń matematycznych. Najważniejsze z tych procedur i funkcji
wymieniliśmy już wyżej w tabl. 7.5.1. Oprócz tego, moduł SYSTEM zawiera cztery
procedury bezparametrowe Continue, Break, Exit oraz Halt, pozwalające na
bezpośrednie porzucenie od momentu ich wykonania, odpowiednio: iteracji pętli, pętli,
procedury, programu.
Continue. Ta procedura przerywa działanie tylko jednego, bieżącego cyklu pętli. Na
przykład, wynikiem wykonania następującego programu 7.5.11 są wyprowadzone
liczby: 5 6 7 8 9 16 17 18. Warunkowe wykonanie Continue wyeliminowało
zakończenie iteracji pętli dla wartości zmiennej sterującej 9 < k <16.
Program 7.5.11. Demonstracja działania procedury Continue
98
Program Demo_Continue;Var k:word;
begin for k:=5 to 18 do begin
if (k>9) and (k<16) then Continue;Write(' ',k);
end; writeln;end.
Break. Wykonanie tej procedury wewnątrz pętli FOR, WHILE lub REPEAT polega
na przeskakiwaniu do wykonania pierwszej instrukcji za zakresem pętli. Jeżeli Break
występuje w pętli zagnieżdżonej, to spowodowane będzie wyskakiwanie tylko z
jednego poziomu pętli zakończona będzie najbardziej wewnętrzna pętla zawierająca
Break. Przykładem stosowania tej procedury jest program 7.5.12 z „wieczną” pętlą,
jedynym wyjściem z której jest wciśnięcie małej lub dużej litery Q. Spowoduje to
wykonanie procedury Break przekazującej sterowanie instrukcji Writeln położonej za
zakresem pętli.
Program 7.5.12. Demonstracja działania procedury Break
Program Demo_Break;Var z:char;
Begin repeat
Write('Podaj znak (wyjscie => Q) '); readln(z); if (z='q') or (z='Q') then Break;
until false; Writeln('Znak=',z);
End.
99
Exit. To zlecenie użyte wewnątrz procedury powoduje przeskakiwanie do ostatniego
słowa End w części operacyjnej procedury. Użycie Exit bezpośrednio w zakresie
programu głównego spowoduje skok do końca programu i jego zakończenie.
Halt. Ta procedura natychmiastowo przerywa wykonywany program i dokonuje
powrotu do systemu operacyjnego lub do innego procesu, który zainicjował przerwany
program.
Na przykład, użycie w programie 7.5.12 zamiast procedury Break dowolnego ze
zleceń: Exit lub Halt, daje ten sam efekt – wyjście z programu bez żadnego wydruku
(instrukcja Writeln pomija się).
7.5.4. Zadania do ćwiczeń
1) Przedstaw procedurę w języku Pascal sortowania bąbelkowego (patrz rozdz. 6).
2) Daj opis procedury Pascalowej sortowania przez wstawianie (patrz rozdz. 6).
3) Przedstaw opis procedury sortowania szybkiego (patrz rozdz. 6) w wersji
rekurencyjnej. Pomoc, w razie potrzeby, możesz znaleźć w [41].
4) Daj opis procedury sortowania szybkiego w wersji iteracyjnej. Pomoc w razie
potrzeby, uzyskasz w [41].
5) Oblicz czas działania procedur opisanych w 3) i 4), sortując za ich pomocą te same
ciągi z 1000, 5000, 10000 wartości losowych wygenerowanych z wykorzystaniem
procedury Random. Do liczenia czasu zastosować procedurę GetTime(godz, min,
sek, sek100), za pomocą której jej parametrom zmiennym całkowitym, jest
przypisywany aktualny czas (godzina, minuta, sekunda, setna część sekundy).
6) Stosując program 7.5.7 zbadaj eksperymentalnie właściwości generatora G
permutacji losowych dla = 1, 2, 3. Dla każdej z tych wartości wygeneruj po 10
permutacji losowych ciągu liczb 1, 2,..., 20. Przeanalizuj wyniki.
7) Oblicz czas generowania permutacji losowych dla 500, 1000, 2000, 5000 liczb
naturalnych za pomocą procedury Perm z programu 7.5.7.
8) Stwórz procedurę, która na podstawie wartości parametru X liczby rzeczywistej,
określającej kwotę w złotych i groszach, podaje jako wynik, kwotę słownie w 100
postaci zdania w języku polskim. Wynik ten powinien być przypisywany parametru
wyjściowemu procedury o nazwie Kwota.
7.6. Typy strukturalne
Dotychczas rozpatrywaliśmy zmienne, których typy należały do klasy typów
prostych. Teraz zajmiemy się zmiennymi typu strukturalnego. Każda z takich
zmiennych składa się z więcej niż jednej składowej. Każda składowa może być typu
prostego lub wcześniej zdefiniowanego typu strukturalnego. W Pascalu programista ma
do dyspozycji cztery metody definiowania obiektów złożonych, prowadzące do czterech
klas typów: tablicowych, rekordowych, zbiorowych i plikowych. Na początku zajmiemy
się typami tablicowym, rekordowym i zbiorowym. Struktury tych typów (w odróżnieniu
od typu plikowego) są reprezentowane w pamięci operacyjnej komputera.
7.6.1. Tablice
W języku Pascal można deklarować tablice (np. wektory, macierze), które zawierają
określoną liczbę składowych tego samego typu. Typ tablicowy określa się następująco:
Nazwa = ARRAY [lista_typów_indeksów] OF typ_składowych;
Typ każdego indeksu z listy typów indeksów powinien reprezentować skończony
ciąg wartości. W związku z tym może on być jednym z następujących typów:
wyliczeniowym, okrojonym, BOOLEAN lub CHAR. Typ składowych jest dowolny, to
znaczy, że może to być również typ tablicowy.
Przykłady deklaracji tablic i zmiennych tablicowych:
Const M=40; N=50; TYPE Wektor=ARRAY [0..30] OF REAL; Mac1 =ARRAY [1..M,1..N] of INTEGER; Mac2 =ARRAY [1..N] of WEKTOR;
101
Wezly =Array ['a'..'z'] of Integer; VAR Wekt : Wektor; A, B : Mac1; C : Mac2; Wezl : Wezly; Znak : ARRAY[0..45] of CHAR;
Do poszczególnej składowej tablicy można się odwołać przez nazwę zmiennej
tablicowej i podanie w nawiasach kwadratowych wartości indeksów, odpowiadających
wybranej składowej. Przyjmując powyższe określenie zmiennych tablicowych, podamy
następujące przykłady:
WEKT[4] wybór piątej składowej wektora WEKT;
A[3,25] wybór 25 składowej 3-go wiersza macierzy A;
B[3,65] błąd, trzeci wiersz macierzy B posiada tylko 50 składowych;
C[62,28] błąd, macierz C posiada 50 wierszy i 31 kolumn;
WEZL['R'] wartość (numer) węzła o nazwie 'R'.
Zasady określania i używania tablic wyjaśnimy, rozwiązując następujące zadanie.
Zadanie 7.6.1. Wprowadzić wartość N jako rozmiar macierzy kwadratowej.
Stosując wzór a[i,j] = i+j-1; i = 1, ..., N, j = 1, ..., N, otrzymać w pamięci
komputera wartości składowych macierzy A. Wyprowadzić macierz A po kolei wiersz
po wierszu na ekran monitora oraz na drukarkę, w zależności od życzenia użytkownika.
Takie zadanie rozwiązuje program 7.6.1. Istnieje tu możliwość wydruku wyników.
Wystarczy udzielić odpowiedzi na zadane przez program pytanie naciśnięciem klawisza
Y lub N (tak albo nie). Skierowanie wyników na drukarkę w systemie Turbo Pascal
określa się przez podanie parametru o nazwie LST, jako pierwszego, na liście
parametrów procedury WRITE.
102
Program 7.6.1. Praca z macierzą, możliwość wydruku wyników
PROGRAM Tablica_Drukuj; USES Printer; CONST Nmax = 50; { Maks. rozmiar macierzy } TYPE Macierz = ARRAY[1..Nmax,1..Nmax] OF INTEGER; VAR A : Macierz; Yes : CHAR; N,I,J : INTEGER; T : BOOLEAN;BEGIN Write ('Podaj rozmiar macierzy N = '); Readln(N); Repeat {oczekuje się wcisniecie Y, y, N lub n } WRITE('Wyprowadzic macierz na drukarke ? (Y/N) --> '); READLN(Yes); T := (Yes='Y') or (Yes='y'); UNTIL T or (Yes='N') or (Yes='n'); Writeln; For I:= 1 to N do { Definicja petli dla N wierszy } Begin WRITE('I = ',I,') '); IF T Then WRITE(lst,'I = ',I,') '); For J:= 1 to N do { Definicja petli dla N kolumn } begin A[i,j] := i+j-1; WRITE(A[i,j]:6); IF T Then WRITE(lst,A[i,j]:6) end; IF T Then WRITEln(lst); Writeln EndEND.
Przykład działania tego programu dla wartości N = 5.
a) Zawartość ekranu podczas wprowadzania danych:
Podaj rozmiar macierzy N = 5103
Wyprowadzic macierz na drukarke ? (Y/N) --> TWyprowadzic macierz na drukarke ? (Y/N) --> Y
b) Wyświetlone i wydrukowane wyniki:
I = 1) 1 2 3 4 5I = 2) 2 3 4 5 6I = 3) 3 4 5 6 7I = 4) 4 5 6 7 8I = 5) 5 6 7 8 9
7.6.2. Rekordy Rozważane powyżej zmienne typu tablicowego mają określoną liczbę składowych
tego samego typu. Natomiast w praktyce często mamy do czynienia ze strukturą
danych, której składowe są różnego typu, na przykład lista danych o studentach uczelni.
Każdy wiersz (rekord) tej listy może zawierać następujące składowe: nazwisko i imię,
wydział, numer indeksu, rok urodzenia, stan cywilny itp. Tego typu struktury danych w
Pascalu są zdefiniowane za pomocą typu rekordowego. Definicja tego typu zaczyna się
identyfikatorem RECORD i kończy się słowem END. Pomiędzy nimi podaje się listę
składowych, nazywanych polami rekordu. Dla każdej składowej określa się jej
identyfikator i typ.
Dostęp do zawartości poszczególnych składowych rekordu następuje poprzez
podanie nazwy zmiennej rekordowej, kropki i nazwy składowej (pola). Zasadę definicji
i używania zmiennych typu rekordowego wyjaśnimy, rozwiązując następujące zadanie.
Zadanie 7.6.2. Utworzyć listę danych o studentach, zawierającą: nazwisko i imię,
numer indeksu, rok urodzenia, stan cywilny. Wprowadzić odpowiednie dane o
studentach do listy. Wyprowadzić zawartość listy na monitor w postaci tabeli.
Rozwiązaniem tego zadania jest program 7.6.2. Wyjaśnimy niektóre szczegóły jego
konstrukcji i działania. Dane o każdym studencie początkowo są wprowadzane do
odpowiednich pól zmiennej rekordowej o nazwie St, po czym zawartość tej zmiennej
104
przesyłana jest do kolejnego wiersza listy studentów o nazwie L. Po wprowadzeniu
nazwiska dodajemy spacje, uzupełniające długość wprowadzonej wartości do długości
zarezerwowanego pola (tj. do 28 symboli). W przeciwnym wypadku nazwiska
zostałyby przesunięte do prawej krawędzi.
Program 7.6.2. Formowanie i wyprowadzanie listy studentów
PROGRAM DemoRekord; CONST Nmax = 30; { Maks. liczba studentow w grupie } S= ' | '; { Stala tekstowa dla wyprowadzania wynikow} T1= '| N | Nazwisko i Imie |'; T2='Num. Ind.|Rok Ur|St.Cyw'; Spac=' '; TYPE Student = RECORD { Okreslanie typu rekordowego} NP : 1..30; { Numer porzadkowy} NazwImie : String [28]; { Nazwisko i imie} NumInd:String[7]; { Numer indeksu } RokUr: 1940..2000; { Rok urodzenia } StCyw: CHAR; { Stan cyw.: 0-wolny, 1-nie } END; { Koniec deklaracji rekordu } VAR St : Student; { Definicja zmiennej rekordowej } L : ARRAY [1..Nmax] of Student; {L - lista studentow } N, I: integer;begin Write ('Podaj liczbe studentow w grupie N = '); Readln(N); For I:= 1 to N do {Petla wprowadzania danych do listy L} begin St.NP := I; {Okreslanie skladowej NP } Write ('Podaj ',I,' nazwisko: '); Readln(St.NazwImie); St.NazwImie:=St.NazwImie + Spac; Write ('Podaj numer indeksu: '); Readln(St.NumInd); Write ('Podaj Rok Urodzenia: '); Readln(St.RokUr); Write ('Podaj stan cywilny ( 0 - jest wolny, 1 - nie ): ');
105
Readln(St.StCyw); L[I]:=St; { (I-ty wiersz listy L) = zawartosci St } end; For I:=1 To 58 Do Write('-'); Writeln; Writeln(T1,T2); For I:=1 To 58 Do Write('-'); Writeln; For I:=1 to N do { Petla wyprowadzania danych } Writeln('| ', L[I].NP:2, S, L[I].NazwImie:28, S,L[I].NumInd:7, S, L[I].RokUr:4, S, L[I].StCyw, S); For I:=1 To 58 Do Write('-'); Writeln END.
Przykład działania programu 7.6.2:
Podaj liczbe studentow w grupie N = 2Podaj 1 nazwisko: Cybruk AdrianaPodaj numer indeksu: 3295Podaj Rok Urodzenia: 1983Podaj stan cywilny ( 0 - jest wolny, 1 - nie ): 0Podaj 2 nazwisko: Pirjanowicz NataliaPodaj numer indeksu: 3057Podaj Rok Urodzenia: 1984Podaj stan cywilny ( 0 - jest wolny, 1 - nie ): 0 ----------------------------------------------------------| N | Nazwisko i imie |Num. Ind.|Rok Ur|St.Cyw----------------------------------------------------------| 1 | Cybruk Adriana | 3295 | 1983 | 0 || 2 | Pirjanowicz Natalia | 3057 | 1984 | 0 |----------------------------------------------------------7.6.3. Zbiory
Definicja typu zbiorowego ma postać:
TYPE identyfikator_typu = SET OF typ_porządkowy;
Typ zbiorowy jest zbiorem wszystkich podzbiorów typu porządkowego (składowe
którego da się ponumerować), będącego tu typem bazowym. Liczba elementów typu
porządkowego występującego w powyższej definicji nie może przekraczać 256.
Oznacza to, że żaden ze standardowych typów porządkowych, określających większą
niż podana liczbę wartości, nie może być użyty do określenia typu zbiorowego.106
Wartości typu zbiorowego zapisuje się przez podanie w nawiasach kwadratowych
listy elementów danego zbioru. Poszczególne elementy tej listy oddziela się
przecinkami. Przykład deklaracji typów oraz zmiennych zbiorowych podano w
programie 7.6.3.
Program 7.6.3. Przykład określenia typów i zmiennych zbiorowych
Program Zbiory; TYPE
Dzien_tyg = (poniedzialek,wtorek,sroda,czwartek,piatek,sobota,niedziela);Dzien = SET OF Dzien_tyg;Symbol = SET OF Char;Duze_lit = SET OF 'A'..'Z';
VARdzien_szczeg : Dzien_tyg;zbior_dni : Dzien;s1, s2 : Symbol;dl : Duze_lit;
begin dzien_szczeg:=sobota; {bez apostrofow, tak jak w TYPE ...} zbior_dni:=[wtorek,sroda]; s1:= ['A','#']; { s1 jest podzbiorem typu znakowego } dl:= ['A','#']; {compilator nie widzi bledu wystepujacego w tym wierszu} s2:=s1+dl; { operacja dopuszczalna do wykonania } writeln(s1,s2,dzien_szczeg); {blad, zawartosci zmiennych typu zbiorowego
nie mozna wydrukowac}end.
Elementami typu Symbol są dowolne podzbiory zbioru znaków ASCII. Jako element
typu Durze_lit może występować dowolny podzbiór zbioru dużych liter.
W przypadku deklaracji zmiennych wartością zmiennej Zbior_dni może być
dowolny podzbiór nazw dni wymienionych w deklaracji typu Dzien_tyg. Natomiast
wartością zmiennej Dzien_szczeg może być nazwa dowolnego dnia
wyspecyfikowanego w definicji typu Dzien_tyg.
7.7. Pliki
107
Wszystkie wcześniej omówione typy danych odznaczają się tym, że wartości
określane za ich pomocą nie są zachowywane po zakończeniu działania programu.
Ponieważ często dane są potrzebne do dalszego przetwarzania, muszą one być
przechowywane w pamięci zewnętrznej (na dyskach twardych, dyskietkach itp.) w
jedynej możliwej formie w postaci pliku.
Język PASCAL kojarzy fizyczny zbiór na dysku z własną zmienną, która jest
określana jako zmienna plikowa. W opisie standardu języka Pascal i w niektórych jego
implementacjach nazwy wykorzystywanych w programie plików mogą być określone
jako parametry nagłówka programu, tj. wymienione w nawiasach po nazwie programu.
Reguły określania i wykorzystywania plików zależą od implementacji języka
PASCAL. W tym miejscu podamy przykłady wykorzystania plików dla języka TURBO
PASCAL.
W systemie TP definicja typu plikowego i zmiennej plikowej jest następująca:
TYPE nazwa_typu = FILE OF typ_składowych;VAR nazwa_zmiennej_plikowej : nazwa_typu;
Plik, którego składowe są typu CHAR, nazywa się plikiem tekstowym. Plik tekstowy
ma standardowy typ TEXT, który określany jest następująco:
TYPE TEXT = FILE OF CHAR;
W programie definicja typu pliku tekstowego może być opuszczona i określanie
odpowiedniej zmiennej plikowej, np. o nazwie TKT dokonuje się następująco:
VAR TKT: TEXT;
Aby podać przykład określenia typu i zmiennej plikowej, wróćmy do powyższego
programu 12 (tworzenie listy studentów). Mamy tam zdefiniowany typ rekordowy o
nazwie Student. Wykorzystując definicję tego typu określimy listę studentów w
108
pamięci zewnętrznej komputera. Deklaracje typu plikowego i zmiennej plikowej są
następujące:
TYPE Lista = FILE OF Student; { Okreslanie typu plikowego } VAR Str : Lista; { Okreslanie zmiennej plikowej } Nazwa zmiennej plikowej używanej w programie jest logicznym modelem
fizycznego zbioru danych o dostępie sekwencyjnym, tzn. w danym momencie może być
dostępny tylko jeden element. Dlatego, aby wszystkie kolejne operacje nad zmienną
plikową były równoznaczne operacjom na pliku (na fizycznym zbiorze danych), w TP
wykorzystuje się procedurę:
ASSIGN (Pl, Str); skojarzenie pliku o nazwie Pl,
określonego w programie jako zmienna plikowa, z nazwą pliku na dysku, przypisaną
zmiennej tekstowej Str.
Oprócz ASSIGN do pracy z plikami przeznaczone są następujące procedury:
REWRITE(Pl); tworzenie nowego pliku na dysku, o nazwie skojarzonej ze
zmienną plikową Pl. Jeśli plik o takiej nazwie istniał na dysku, to po wykonaniu
procedury REWRITE jego zawartość zostanie usunięta. Dlatego musimy pamiętać o
ostrożnym korzystaniu z tej procedury, aby niechcący nie zlikwidować potrzebnego
pliku. Do sprawdzania tego, czy plik jest pusty, czy nie wykorzystuje się funkcję
logiczną EOF(Pl). Funkcja ta przyjmuje wartość TRUE, jeśli plik Pl jest pusty (na
przykład po wykonaniu procedury REWRITE), lub wartość FALSE podczas wykonania
operacji nad składowymi istniejącego pliku.
RESET(Pl); otwarcie istniejącego pliku w celu przeczytania jego elementów lub
dopisania nowych składowych do jego zawartości. Wskaźnik pliku (określający
porządkowy numer aktualnie dostępnej składowej pliku) ustawia się na pierwszy
element pliku.
READ(Pl, L); czytanie z pliku Pl wartości zmiennych, których nazwy są
wymienione na liście L.109
WRITE(Pl,L); zapisywanie do pliku Pl zmiennych, których nazwy są wymienione
na liście L.
SEEK(Pl,n); ustawienie wskaźnika pliku Pl na jego składowej o numerze
porządkowym równym n. Zwróćmy uwagę, że numeracja składowych pliku zaczyna się
od zera. Na przykład, wykonanie procedury SEEK(Pl,5) oznacza ustawienie wskaźnika
pliku na jego szóstej składowej.
CLOSE(Pl); zamknięcie pliku Pl. Przed wyjściem z programu wszystkie otwarte
pliki powinny być zamknięte za pomocą tej procedury.
Sposób wykorzystania procedur związanych z plikami przedstawimy na przykładzie
rozwiązania następującego zadania.
Zadanie 7.7.1. Utworzyć na dysku plik tekstowy. Wprowadzić do pliku dowolny
tekst w postaci jednego wiersza do 255 symboli. Odczytać i wyświetlić wprowadzony
tekst na ekranie.
Rozwiązaniem zadania 7.7.1 jest program 7.7.1. W programie tym zmiennej Nazwa
program przypisuje nazwę dysku oraz nazwę tworzonego na nim pliku. Do zmiennej
Tek1 wprowadza się z klawiatury tekst, a następnie zawartość zmiennej Tek1 jest
zapisywana do tworzonego pliku. Po odczytaniu z pliku, tekst przypisuje się zmiennej
Tek2 i kolejno wyprowadza się na ekran.
Program 7.7.1. Przykład wykonania podstawowych operacji z plikiem tekstowym
PROGRAM DemoPlik; Var Tek1, Tek2, Nazwa : string; I : Integer; Plik : Text; { Okreslanie zmiennej plikowej }Begin WRITELN ('Podaj dysk (A: lub C:) i nazwe pliku tekstowego,'); WRITE ('ktory chcesz zalozyc => '); Readln(nazwa); { Wprowadzanie nazwy pliku } Assign(Plik,nazwa); { Skojarzenie zmiennej plikowej} { z podana nazwa pliku }
110
Rewrite(Plik); { Kreacja nowego pliku na dysku} WRITELN('Napisz tekst (max 255 znakow), nacisnij ENTER'); WRITELN; {Wyprowadzanie pustego wiersza} Readln(Tek1); {Wprowadzanie tekstu z klawiatury} WRITELN(Plik,Tek1); { Nacisniecie ENTER zapisuje} WRITELN; { tekst do pliku } Close(Plik); { Z a m k n i e c i e pliku } WRITELN('Odczyt tekstu z pliku ', nazwa); WRITELN; RESET (Plik); { Otwarcie pliku do czytania } ReadLN(Plik,Tek2); WRITELN(Tek2); Close(Plik) { Z a m k n i e c i e pliku }End.
7.7.1. Zadania do ćwiczeń
1) Uzupełnij program 7.5.8 tak, aby zbudowana „macierz samotnych jedynek”
została zapamiętana wiersz po wierszu w pliku o podanej z klawiatury nazwie. Adres
pliku (dysk, ścieżka) również wprowadza się z klawiatury po odpowiednim zapytaniu
programu.
2) Uzupełnij program 7.6.1 tak, aby zostały zapamiętany w pliku o podanej z
klawiatury nazwie: rozmiar obliczonej tablicy i z kolei dalej, wiersz po wierszu
składowe tablicy. Adres pliku: dysk, ścieżka również są wprowadzane z klawiatury.
3) Zachowaj na dysku listę studentów stworzoną w programie 7.6.2.
7.8. Dynamiczne struktury danych
Wszystkie omówione dotychczas typy, a także zmienne zdefiniowane za pomocą
typu, nazywane są często statycznymi. Zmienne statyczne są stosowane wtedy, gdy
pamięć wykorzystywana w programie może być określona w trakcie pisania programu.
W praktyce często określenie obszaru pamięci niezbędnej dla wykonania programu
może zostać dokonane dopiero w momencie jego uruchomienia. Przy pisaniu tego
rodzaju programów zwykle są używane tzw. zmienne dynamiczne. Są one tworzone w
danym bloku w trakcie jego wykonywania za pomocą niżej określonej procedury NEW. 111
Tego rodzaju zmienne nie są deklarowane w części deklaracyjnej programu i w związku
z tym nie można się na nie powoływać w części operacyjnej za pomocą nazwy. Do
zmiennej dynamicznej odwołujemy się, używając tzw. wskaźnika, określającego
miejsce w pamięci (adres), w którym znajduje się ta zmienna. Deklaracja wskaźnika,
np. o nazwie Wsk, wskazującego na zmienną dynamiczną typu Tzd, jest możliwa w
sposób następujący:
VAR Wsk: ^Tzd; Oczywiście typ Tzd musi być znany komputerowi może to być
typ standardowy lub typ zdefiniowany przez użytkownika. Dla otrzymania wartości
zmiennej dynamicznej typu Tzd wystarczy podać identyfikator Wsk^.
Jeśli na przykład mają być określone zmienne wskaźnikowe (wskaźniki) Z, Wsk1 i
Wsk2, wskazujące na zmienne dynamiczne typu CHAR, to ich definicję możemy podać
na dwa sposoby:
TYPE Typ_Wsk = ^CHAR;VAR Z, Wsk1, Wsk2: Typ_Wsk;
lub bardziej krótko:
VAR Z, Wsk1, Wsk2: ^CHAR;
W przypadku ostatnich deklaracji, w programie możemy mieć do czynienia ze
zmiennymi dynamicznymi Z^, Wsk1^ oraz Wsk2^, którym po ich utworzeniu mogą być
przypisane wartości stałych typu CHAR, np.:
Wsk1^ := 'R';
Wsk2^ := 'S';
Jedyną stałą wartością, która może być przypisana zmiennej wskaźnikowej jest NIL.
Przypisanie zmiennej wskaźnikowej wartości NIL oznacza, że zmienna ta nie wskazuje
na żadną zmienną dynamiczną. W ten sposób po wykonaniu instrukcji
Wsk1 := NIL;
zmienna wskaźnikowa Wsk1 nie wskazuje na żadną zmienną dynamiczną Wsk1^ .
112
Zadeklarowanie zmiennej wskaźnikowej stwarza jedynie możliwość utworzenia
związanej z nią zmiennej dynamicznej. Utworzenie zmiennej dynamicznej dokonuje się
poprzez wywołanie procedury
NEW(Z);
gdzie Z jest wskaźnikiem zmiennej dynamicznej tego typu, który należy utworzyć.
Wykonanie procedury NEW(Z) powoduje przydzielenie pamięci dla zmiennej
dynamicznej, a zmiennej wskaźnikowej przypisuje się kod adresu tej pamięci. Jeśli
zmienna wskaźnikowa zadeklarowana jest jak powyżej, to po wykonaniu procedury
NEW(Z) utworzonej zmiennej dynamicznej może być przypisana następująca wartość
Z^ := '*';
Gdyby wartość zmiennej wskaźnikowej Z była NIL, to wykonanie ostatniej instrukcji
byłoby niemożliwe. W praktyce wartość NIL zmiennej wskaźnikowej wykorzystuje się
jako warunek zakończenia wykonania operacji z ciągiem zmiennych dynamicznych.
Zmienna dynamiczna istnieje do chwili przerwania obliczeń lub do momentu jej
zlikwidowania za pomocą procedury:
DISPOSE(Z);
gdzie Z jest wskaźnikiem zmiennej dynamicznej.
7.8.1. Organizacja struktur danych w postaci stosów i kolejek
Wskaźniki są efektywnym środkiem umożliwiającym tworzenie w trakcie działania
programu tak zwanych list liniowych. Lista liniowa jest uporządkowaną strukturą, w
której każda składowa ma adres kolejnej składowej listy. W każdej składowej listy
mogą być wyróżnione dwie części: podstawowa, z przechowywanymi danymi, oraz
dodatkowa część (wskaźnik), wskazująca na kolejną składową listy liniowej. Z każdą
listą liniową jest zawsze związana zmienna wskaźnikowa, której wartością jest adres
pierwszego elementu listy liniowej. Jeśli lista nie zawiera składowych (jest pusta), to
wartością zmiennej wskaźnikowej jest NIL. Można wyróżnić dwa rodzaje list liniowych
stosy i kolejki.
113
Stos i kolejka składają się ze zmiennej liczby elementów. Do stosu można dołączyć
element, bądź też ze stosu można zdjąć element. Zdjąć i dołączyć można wyłącznie
ostatni element stosu, zwany jego wierzchołkiem. Jest to zasada rodzaju: "ostatni
przyszedł pierwszy wyszedł". Natomiast w kolejce działa zasada jak w kolejce, z którą
mamy do czynienia w życiu codziennym: "pierwszy przyszedł pierwszy wyszedł", tj.
dołączanie elementów do kolejki dokonuje się z jednej strony, a ich zdejmowanie z
drugiej.
Podstawowymi operacjami, jakie można wykonać na listach liniowych, są:
utworzenie listy, dodawanie nowych lub usuwanie zbędnych elementów listy, przegląd
zawartości elementów listy. Przykłady wykonania tych operacji na stosie są podane w
postaci odpowiednich procedur w programie 7.8.1. Część operacyjna programu daje
możliwość sprawdzić eksperymentalnie działanie tych procedur.
Program 7.8.1. Podstawowe operacje na stosie
PROGRAM OperStos; { Operacje na s t o s i e } USES CRT; TYPE Pt = ^Stos; { Okreslanie typu wskaznikowego } Stos = record Inf : integer; { Czesc podstawowa } Next: Pt { Wskaznik } end; VAR Top, NewElem : Pt; { Zmienne wskaznikowe } Value, N, i, Oper : Integer;{-----------------------------------------------------}PROCEDURE DodSt; { Dodawanie elementow do s t o s u. } { Przyjmujemy, ze typ elementow Stosa - INTEGER } { Sygnalem konca dodawania elementow do stosu } { jest wprowadzanie z klawiatury wartosci 999 }Begin WRITELN ('Wprowadzaj liczby calkowite (kolejne ', 'wartosci stosu)'); WRITELN ('Liczba 999 - sygnal konca wprowadzania'); WHILE True DO {Okreslanie petli wprowadzania liczb} Begin
114
READ(Value); { Wprowadzanie wartosci liczby } IF Value=999 THEN EXIT; { Wyjscie z petli } NEW(NewElem); {Utworzenie zmiennej dynamicznej} NewElem^.Inf:=Value; {Wartosc czesci podstawow.} NewElem^.Next:=Top; {Wartosc w s k a z n i k a} Top:=NewElem {Adres ostatnio okreslonej zmiennej} { dynamicznej - wierzcholka stosu. } EndEnd;{-----------------------------------------------------}PROCEDURE UtwSt; { Utworzenie s_t_o_s_u. } { Przyjmujemy, ze typ elementow Stosu - INTEGER } { Sygnalem konca wprowadzania elementow do stosu } { jest wprowadzanie z klawiatury wartosci 999 }Begin Top:= NIL; { Top = kolejne wartosci wskaznika } DodSt {tzn kolejny tekst jak w czesci operacyjnej procedury DodSt }End;{-----------------------------------------------------}PROCEDURE KasSt; { Usuwanie wierzcholka stosu }Begin; IF Top=NIL THEN Begin WRITELN('* Stos jest p u s t y *'); EXIT { Wyjscie z procedury } End; Top:=Top^.Next {Wskaznik = adres poprzedn.elem.stosu}End;{-----------------------------------------------------}PROCEDURE PrzegSt; { Przeglad wartosci elementow } {stosu od jego wiezcholka do konca }Begin WRITELN ('Przeglad wartosci elementow stosu ', 'od jego wierzcholka do konca:'); NewElem := Top; IF Top=NIL THEN WRITE('* Stos jest p u s t y *'); WHILE NewElem <> NIL DO {Okreslanie petli przegl.} Begin WRITE(' ',NewElem^.Inf); NewElem := NewElem^.Next EndEnd;
115
{*****************************************************}Begin; { Program wykonania operacji na s t o s i e }Clrscr; { Czyszczenie ekranu }Writeln;Writeln('Uruchomiles program wykonania podstawowych', ' operacji na s t o s i e.');WHILE True DO { Stale wyswietlanie menu } Begin Writeln; Writeln(' ----------------------------------------'); Writeln(' 1 - U t w o r z e n i e s t o s u.'); Writeln(' ( Uwaga ! Poprzednia zawartosc ', 's t o s u bedzie skasowana. )'); Writeln; Writeln(' 2 - Dodawanie elementow do s t o s u.'); Writeln; Writeln(' 3 - Usuwanie N elementow stosu ', '(wierzcholek i dalej po kolei).'); Writeln; Writeln(' 4 - Przeglad wartosci elementow stosu ', 'od jego wierzcholka do konca.'); Writeln; Writeln(' 5 - Wyjscie z programu.'); Writeln(' ----------------------------------------'); Writeln; Write('Podaj numer operacji => '); Readln(Oper); Writeln; CASE Oper OF { Uruchomienie odpowiedniej procedury: } 1: UtwSt; 2: DodSt; 3: begin Write('Podaj wartosc N = '); Read(N); For i:=1 to N do KasSt end; 4: PrzegSt; 5: If Oper >= 5 Then EXIT {Wyjscie z petli} END EndEnd. { Koniec programu 7.8.1 }
116
7.8.2. Zadania do ćwiczeń
1) Analogicznie do programu 7.8.1 opracuj program wykonujący podstawowe operacje
na liście liniowej typu kolejka.
2) Dla danych wejściowych n, a, b wygeneruj n wartości losowych całkowitych
zawartych w przedziale (a, b) oraz zachowaj te wartości w postaci stosu.
3) Uważając poprzednie zadanie za rozwiązane, znajdź sumę składowych stosu oraz
policz, ile składowych zawiera stos.
7.9. Dodatkowe informacje związane z systemem Turbo Pascal
7.9.1. Biblioteki procedur i funkcji
Ważnym elementem języka TURBO PASCAL są moduły. Moduły stanowią swego
rodzaju bibliotekę programów. Korzystanie z programów zawartych w modułach jest
możliwe bez potrzeby powtórnej kompilacji.
Struktura modułu podobna jest do struktury programu. Moduł, tak jak i program,
składa się z nagłówka, części deklaracyjnej i części wykonawczej (operacyjnej)
zakończonej kropką. W odróżnieniu od programu, część deklaracyjna modułu jest
rozbita na część publiczną i prywatną. Kolejność słów kluczowych w podanej niżej
strukturze modułu musi być zachowana:
UNIT nazwa_modułu ;
INTERFACE { rozpoczęcie części publicznej }
USES lista_używanych_(dołączanych)_Unit;
{ deklaracje części publicznej: stałe, typy, zmienne
oraz nagłówki (!) procedur i funkcji, które
będą używane na zewnątrz danego UNIT };
IMPLEMENTATION { rozpoczęcie części prywatnej };
{ deklaracje dotyczące części prywatnej: lokalne stałe,
typy, zmienne oraz procedury i funkcje, których nagłówki
117
są wymienione w części Interface lub które
są wewnętrzne dla danego UNIT };
Begin
{część wykonawcza}
End. { koniec modułu }
W wyniku kompilacji pliku Nazwa_pliku.PAS, zawierającego tekst zdefiniowanego
modułu, tworzy się na dysku plik Nazwa_pliku.TPU.
Dołączenie pliku, zawierającego skompilowany moduł, do właściwego programu
następuje poprzez deklarację:
USES nazwa_modułu; lub USES ($U nazwa_pliku) nazwa_modułu;
zależnie od tego, czy nazwy modułu i pliku zawierającego jego kod są takie same czy
też nie.
Podamy teraz krótki opis podstawowych modułów systemu TURBO PASCAL. Są to:
SYSTEM, CRT, DOS, GRAPH, OVERLAY, TURBO VISION, zawierających
procedury i funkcje wykorzystywane przy tworzeniu programu.
Moduł SYSTEM. Zawiera on podstawowe procedury i funkcje do pracy z
katalogami, plikami, zmiennymi, do obliczeń matematycznych. Moduł System jest
dołączany do programu automatycznie i nie jest wymagana deklaracja jego użycia w
części USES na początku programu.
Moduł CRT. W tym module zebrane są procedury i funkcje obsługi ekranu,
klawiatury oraz dźwięku. Umożliwiają one definiowanie w trybie tekstowym okien,
wybór koloru tekstu i jego tła w przypadku kolorowych kart graficznych, odczytywanie
znaków z klawiatury, generowanie dźwięku o dowolnej częstotliwości i długości.
Moduł DOS. Moduł ten zawiera procedury i funkcje pozwalające wykonać
większość zleceń systemu operacyjnego, związanych z operacjami na dyskach,
operacjami na funkcjach przerwań BIOS-u i DOS-u, ustawienia lub podania daty i czasu 118
z zegara systemowego itp. Żadna z tych procedur i funkcji nie występuje w
standardowym języku Pascal.
Moduł GRAPH. Jest to niezbędny moduł do obsługi grafiki komputerowej (pracy w
trybie graficznym). Zebrane w tym module procedury i funkcje umożliwiają tworzenie
obrazów graficznych na podstawie kart graficznych, stosowanych w komputerach typu
IBM PC. Przy zastosowaniu tych procedur i funkcji łatwe jest rysowanie na ekranie
krzywych różnych kształtów i kolorów, wypełnianie konturów określonym kolorem,
wyprowadzanie na ekran napisów (poziomo i pionowo) różnymi krojami pisma, z
możliwością ich zmniejszania lub powiększania. Opis podstawowych procedur i funkcji
modułu GRAPH jest podany w rozdz. 7.9.2.
Moduł OVERLAY. Moduł Overlay zawiera procedury i funkcje umożliwiające
tworzenie struktur nakładkowych.
Biblioteka TURBO VISION. Jest to element systemu TP, wprowadzony do jego
wersji 6.0. Biblioteka składa się z dziewięciu modułów (APP, DIALOGS, DRIVERS,
HISTLIST, MEMORY, MENUS, OBJECTS, TEXTVIEW, VIEWS), przeznaczonych
do konstruowania interakcyjnych systemów i programów użytkowych opartych na
okienkach. W modułach tej biblioteki są zdefiniowane obiekty: typy, stałe, zmienne,
funkcje i procedury, przeznaczone do konstrukcji okienkowych programów
użytkowych. Program użytkowy, skonstruowany z wykorzystaniem biblioteki Turbo
Vision, składa się z tzw. widoków, zdarzeń i obiektów niemych.
Widokiem nazywa się każdy obiekt (typ lub zmienna obiektowa), który powoduje
wyświetlenie jakiegoś obrazu na ekranie monitora. Obraz jest tu pojęciem dość
dowolnym i oznacza każdy element wyświetlany na ekranie (linie, pola, brzegi okienek,
poprzeczki, piktogramy itp.). Widoki połączone (powiązane) ze sobą nazywają się
grupami. Na grupie można operować w taki sam sposób jak na pojedynczym widoku.
Wykorzystanie biblioteki Turbo Vision umożliwia tworzenie tylko prostokątnych
widoków.
119
Zdarzenie jest zjawiskiem wymagającym reakcji użytkownika. Reakcję stanowi
naciśnięcie klawisza na klawiaturze, kliknięcie myszką lub "impuls" z innego obiektu
biblioteki Turbo Vision. Zdarzenia są grupowane w kolejki zdarzeń i przetwarzane w
pewnej kolejności poprzez specjalną procedurę ich obsługi.
Obiektem niemym nazywa się każdy obiekt programu, który nie jest widokiem.
Obiekty nieme mogą wykonywać obliczenia lub zapewniać komunikację z
urządzeniami zewnętrznymi, ale bezpośrednio nie mogą wyświetlać wyników na
ekranie. W przypadku konieczności wyświetlenia jakiegoś elementu na ekranie obiekt
niemy musi być w pewny sposób połączony z odpowiednim widokiem.
Przy współpracy z biblioteką Turbo Vision użytkownik znacznie skraca czas na:
Konstruowanie złożonych, nakładających się na siebie okienek z możliwością
ich skalowania.
Tworzenie wielopoziomowych (zstępujących) menu programu użytkownika.
Sterowanie programu użytkownika za pomocą myszki.
Możliwość tworzenia okienek dialogowych (analogicznych do okienek w
zintegrowanym systemie Turbo Pascal).
Określanie kolorów okienek i ich elementów na podstawie dostępnych palet
kolorów.
Konstruowanie okienek ze sterującymi poprzeczkami i piktogramami.
Standardowe interpretowanie naciśnięcia klawiszy klawiatury i myszki.
120
7.9.2. Podstawowe procedury i funkcje modułu GRAPH
W tym rozdziale są określone podstawowe procedury i funkcje modułu GRAPH
niezbędne do pracy w trybie graficznym w systemie TP wersji 5.0 lub wyższej.
Procedury inicjowania i zamykania trybu graficznego:
DetectGraph (var STER, TRYB: Integer);
sprawdzenie aktualnie zainstalowanego sterownika ekranu (STER numer
sterownika ekranu, TRYB numer trybu graficznego).
InitGraph (var STER, TRYB: Integer; ADRES: String);
inicjowanie modułu GRAPH (STER numer sterownika ekranu, TRYB numer trybu
graficznego, ADRES ścieżka dostępu do katalogu zawierającego sterowniki trybu
graficznego. Zwykle ADRES:='C:\TP\BGI'; dla Turbo Pascala lub ADRES:='C:\BP\
BGI'; dla Borland Pascala).
CloseGraph;
zakończenie pracy w trybie graficznym.
Procedury ustalania parametrów graficznych:
RestoreCRTMode;
przełączenie na tryb znakowy (powrót na tryb używany przed trybem graficznym).
SetGraphMode (var TRYB: Integer);
przełączenie na tryb graficzny (TRYB numer trybu graficznego).
SetViewPort (X1,Y1,X2,Y2: Integer; OBCIECIE: Boolean);
121
definiowanie okna na ekranie graficznym (X1,Y1 współrzędne lewego górnego
rogu okna, X2, Y2 prawego dolnego rogu okna; OBCIECIE = True, jeśli rysunki
mają być obcinane do rozmiarów okna, w przeciwnym przypadku OBCIECIE =False).
SetAllPalette (var PALETA: PaletteType);
ustalenie kolorów w palecie według tablicy, której pierwszym elementem jest liczba
kolorów w palecie, a następne elementy numery zdefiniowanych kolorów palety,
wybierane z następujących:
0 czarny, 8 ciemnoszary,
1 niebieski, 9 jasnoniebieski,
2 zielony, 10 jasnozielony,
3 turkusowy, 11 jasnoturkusowy,
4 czerwony, 12 jasnoczerwony,
5 karmazynowy, 13 jasnokarmazynowy,
6 brązowy, 14 żółty,
7 jasnoszary, 15 biały.
SetBkColor (KOL: Word);
ustalenie koloru tła na kolor o numerze KOL.
SetColor (KOL: Word);
ustalenie koloru kursora graficznego na kolor o numerze KOL.
SetPalette (KOLNUM: Word; KOL: ShortInt);
zmiana koloru na pozycji w palecie o numerze KOLNUM na kolor o numerze KOL.
SetLineStyle (RODZ, WZOR, GRUB: Word);
wybór linii do rysowania:
RODZ = 0 linia ciągła, 122
RODZ = 1 linia przerywana kropkowana,
RODZ = 2 linia przerywana (kreska kropka),
RODZ = 3 linia przerywana (kreski),
RODZ = 4 linia definiowana przez użytkownika;
WZOR wzór linii definiowanej przez użytkownika;
GRUB = 1 linia normalnej grubości (o jeden punkt),
GRUB = 3 linia pogrubiona (trzy punkty).
SetFillStyle (NUM, KOL: Word);
wybór wzoru NUM i koloru KOL do wypełniania konturów:
NUM = 0 kolor tła, wzór pusty,
NUM = 1 kolor kursora graficznego,
NUM = 2 kreski poziome,
NUM = 3 kreski prawo-skośne,
NUM = 4 kreski prawo-skośne pogrubione,
NUM = 5 kreski lewo-skośne,
NUM = 6 kreski lewo-skośne pogrubione,
NUM = 7 kratka prosta,
NUM = 8 kratka ukośna,
NUM = 9 punkty zagęszczone,
NUM = 10 kropki,
NUM = 11 gęste kropki,
NUM = 12 wzór definiowany przez użytkownika.
SetFillPattern (WZOR: FillPatternType; KOL: Word);
definiowanie wzoru wypełniania konturów przez użytkownika. Wzór definiuje się
zadaniem wartości zmiennej WZOR typu FillPatternType , będącej ośmioelementową
tablicą typu Byte. Każdy z elementów tablicy jest cyfrą dwójkową: wartość 1 oznacza
123
punkt świecący na ekranie, 0 punkt wygaszony. Kolor punktów świecących określa
się liczbą KOL.
SetTextStyle (KROJ, KIERUN, ROZM: Word);
zadanie parametrów wyprowadzania tekstu:
KROJ numer kroju pisma,
KROJ = 0 pismo standardowe (8x8 punktów),
KROJ = 1 antykwa,
KROJ = 2 pismo techniczne,
KROJ = 3 antykwa bezszeryfowa,
KROJ = 4 pismo gotyckie;
KIERUN kierunek wyprowadzania tekstu,
KIERUN = 0 od lewej strony do prawej,
KIERUN = 1 z dołu do góry (pionowe);
ROZM - rozmiar znaków,
ROZM = 0 - wielkość standardowa.
SetTextJustify (POZIOM, PION: Word);
ustalenie dosunięcia (wyrównywania) tekstu względem pozycji kursora graficznego:
POZIOM = 0 - wyrównanie od lewej strony,
POZIOM = 1 - wyśrodkowanie,
POZIOM = 2 - wyrównanie od prawej strony,
PION = 0 - wyrównanie od dołu,
PION = 1 - wycentrowanie,
PION = 2 - wyrównanie od góry.
SetAspectRatio (Var RX, RY: Word);
ustalenie rozdzielczości ekranu RX w kierunku poziomym
i RY w kierunku pionowym. 124
Procedury rysowania elementów graficznych (rodzaj i kolor rysowanych linii oraz
rodzaj i kolor znaków wypełniających muszą być wcześniej ustalone):
PutPixel (X, Y: Integer; KOL: Word);
wyświetlanie (rysowanie) w zadanym kolorze KOL punktu o określonych
współrzędnych X i Y.
LineTo (X, Y: Integer);
rysowanie odcinka od pozycji kursora graficznego do punktu określonego
współrzędnymi X i Y.
LineRel (DX, DY: Integer);
rysowanie odcinka od pozycji kursora graficznego do punktu odległego od niego o
DX w kierunku poziomym i o DY w kierunku pionowym.
Line (X1,Y1, X2,Y2: Integer);
rysowanie odcinka łączącego punkty o podanych współrzędnych (X1,Y1) i (X2,Y2).
Rectangle (X1,Y1, X2,Y2: Integer);
rysowanie prostokąta, którego przeciwległe wierzchołki, lewy górny i prawy dolny,
mają zadane współrzędne odpowiednio (X1,Y1) i (X2,Y2).
Bar (X1,Y1, X2,Y2: Integer);
wypełnianie prostokąta, którego przeciwległe wierzchołki, lewy górny i prawy
dolny, to punkty o podanych współrzędnych odpowiednio (X1,Y1) i (X2,Y2). Kontury
prostokąta nie są rysowane.
Bar3D (X1,Y1, X2,Y2: Integer; DLUG: Word; SZCZYT: Boolean);125
rysowanie prostopadłościanu, którego przednia ściana jest określona jako prostokąt, a
przeciwległe wierzchołki, lewy górny i prawy dolny, mają zadane współrzędne
odpowiednio (X1,Y1) i (X2,Y2). Parametr DLUG określa długość prostopadłościanu
(głębokość rzutu) liczoną w punktach ekranu. Parametr SZCZYT określa, czy górna
ściana prostopadłościanu ma być widoczna (SZCZYT = True), czy też nie (SZCZYT =
False).
Circle (X, Y: Integer; R: Word);
rysowanie okręgu o środku w punkcie o podanych współrzędnych (X,Y) i danym
promieniu R.
Arc (X,Y: Integer; ALFA,BETA, R: Word);
rysowanie łuku okręgu o środku w punkcie (X,Y) i promieniu R, wyznaczonego
kątami ALFA (początek łuku) i BETA (koniec łuku) określanymi w stopniach.
PieSlice (X,Y: Integer; ALFA,BETA, R: Word);
rysowanie i wypełnianie wycinka koła o środku w punkcie (X,Y) i o promieniu R,
wyznaczonego kątami ALFA (początek łuku) i BETA (koniec łuku) określanymi w
stopniach.
Ellipse (X,Y: Integer; ALFA,BETA, RX,RY: Word);
rysowanie łuku elipsy o środku w punkcie (X,Y) i o połówkach długości osi RX i osi
RY, wyznaczonego kątami ALFA (początek łuku) i BETA (koniec łuku) określanymi
w stopniach.
FillEllipse (X, Y: Integer; RX,RY: Word);
rysowanie wypełnionej elipsy o środku w punkcie (X, Y) i o połówkach długości osi
RX i osi RY.
126
Sector (X, Y: Integer; ALFA, BETA, RX, RY: Word);
rysowanie wypełnionego wycinka elipsy o środku w punkcie (X,Y) i o połówkach
długości osi RX i osi RY, wyznaczonego kątami ALFA (początek łuku) i BETA (koniec
łuku) określanymi w stopniach.
DrawPoly (NPKT: Word; Var TABPKT);
rysowanie wielokąta określonego przez kolejne punkty o podanych współrzędnych;
NPKT liczba punktów; TABPKT tablica rekordów współrzędnych wierzchołków
wielokąta.
FillPoly (NPKT: Word; Var TABPKT);
rysowanie wielokąta wypełnionego wzorem do wypełniania konturów. Wielokąt
określa się przez kolejne punkty o podanych współrzędnych (NPKT - liczba punktów,
TABPKT - tablica rekordów współrzędnych punktów).
FloodFill (X, Y: Integer; KOLK: Word);
wypełnienie (bieżącym wzorem) obszaru zawierającego punkt o podanych
współrzędnych (X,Y) i ograniczenie go konturem w podanym kolorze o numerze
KOLK.
OutTextXY (X, Y: Integer; TEKST: String);
rysowanie ciągu znaków (wartości parametru TEKST) z uwzględnieniem justowania
względem punktu (X,Y).
OutText (TEKST: String);
rysowanie ciągu znaków (wartości parametru TEKST) w sposób określony za
pomocą procedury SetTextJustify.
Procedury informujące o aktualnie obowiązujących parametrach graficznych:127
GetViewSettings (Var OKNO: ViewPortType);
przypisanie wartości parametrów bieżącego okna graficznego zmiennej OKNO
(rekordowi okna graficznego).
GetLineSettings (PARLIN: LineSettingsType);
przypisanie wartości składowym rekordu PARLIN określającego bieżące parametry
rysowanej linii (tzn. parametry RODZ, WZOR, GRUB określone powyżej).
GetPalette (Var PAL: PaletteType);
tablica PAL kolorów w bieżącej palecie.
GetAspectRatio (Var RX, RY: Word);
rozdzielczość ekranu w poziomie i w pionie (odpowiednie wartości są przypisywane
parametrom RX i RY).
Funkcje informujące o aktualnie obowiązujących parametrach graficznych:
GetX: Integer;
wartość współrzędnej X pozycji kursora graficznego.
GetY: Integer;
wartość współrzędnej Y pozycji kursora graficznego.
GetMaxX: Integer;
największa wartość współrzędnej X ekranu (wartość X w prawym dolnym rogu).
GetMaxY: Integer;
największa wartość współrzędnej Y ekranu (wartość Y w lewym górnym rogu).128
GetPixel (X,Y: Integer): Word;
numer koloru punktu o określonych współrzędnych (X,Y). Wartość zero oznacza
kolor tła.
GetBkColor: Word;
numer bieżącego koloru tła.
GetMaxColor: Word;
największy numer koloru dostępny w danym trybie graficznym.
GetPaletteSize: Integer;
liczba kolorów bieżącej palety.
Inne procedury:
MoveTo (X,Y: Integer);
przemieszczenie kursora graficznego do punktu o podanych współrzędnych (X,Y).
ClearViewPort;
oczyszczenie bieżącego okna graficznego.
ClearDevice;
oczyszczenie okna graficznego i przywrócenie wszystkich standardowych wartości.
Przykładem stosowania procedur i funkcji modułu GRAPH jest program 7.9.1
rozwiązujący następujące zadanie.
129
Zadanie 7.9.1. W trybie graficznym narysować o wspólnym środku następujące
figury: okręg, prostokąt, łuk elipsy, elipsę wypełnioną. Dokonać napisów (poziomo i
pionowo) różnymi krojami pisma, z ich zmniejszaniem i powiększaniem.
Program 7.9.1. Przykład stosowania trybu graficznego
Program DemGraf; { Przyklad pracy w trybie graficznym } Uses Crt,Graph; Const Dostep='C:\TP\BGI';{Sciezka dostepu do sterownika ekranu} Prom = 50; {Wybrana wartosc promienia okregu} Var Ster, Tryb, {Ster - numer sterownika ekranu,} X, Y : integer; {Tryb - numer trybu graficznego }Begin ClrScr; { Czyszczenie ekranu } DetectGraph(Ster,Tryb); {Przypisanie zmiennym Ster i Tryb danych o zainstalowanym sterowniku} InitGraph(Ster,Tryb,Dostep); {Zainstalow. trybu graficznego} X := GetMaxX div 2; Y := GetMaxY div 2; { (X,Y) - srodek ekranu } Circle(X,Y,Prom); { Rysowanie o k r e g u, } Rectangle(X-Prom-50,Y-Prom-15,X+Prom+50,Y+Prom+15); { Rysowanie prostokata ze srodkiem w (X,Y)} Ellipse(X,Y,0,360,Prom+140,Prom+60); {Luk elipsy} FillEllipse(X,Y,Prom-15,Prom-30); {Elipsa wypelniona} SetTextStyle(1,1,0); {Wykres tekstu z dolu do gory, kroj antykwa kreskowy potrojny} OutTextXY(5,60, 'TWP w Olsztynie'); SetTextStyle(2,1,0); {kroj kreskowy indeksowy } OutTextXY(45,80,'Towarzystwo wiedzy powszechnej'); OutTextXY(55,120,'w Olsztynie'); SetTextStyle(1,0,0); {tekst od lewej do prawej strony} OutTextXY(130,20,'T W P '); SetTextStyle(4,0,0); {k r e s k o w y g o t y c k i} OutTextXY(260,20,'w O l s z t y n i e.'); SetTextStyle(3,0,0); {kreskowy bezszeryfowy} OutTextXY(130,GetMaxY-50,'Dla wyjscia - nacisnij spacje.'); Repeat Until Keypressed; {Oczekiwanie nacisniecia klawisza} CloseGraph {Zakonczenie pracy w trybie graficznym}
130
End.
Przykładem stosowania procedury rekurencyjnej do budowy obrazów graficznych
jest program 7.9.2 rozwiązujący następujące zadanie.
Zadanie 7.9.2. Narysować w trybie graficznym spiralę, złożoną z odcinków prostych
linii, połączonych ze sobą pod kątem 90 stopni, w kierunku ruchu strzałki zegarowej.
Spirala musi być symetryczną i wypełniać cały ekran w pionie. Poziomo powinna
zajmować środkową część ekranu. Aby te dwa ograniczenia były spełnione podczas
wykonania programu na dowolnym komputerze, długość pierwszych dwóch odcinków
ustalić automatycznie, na podstawie aktualnej rozdzielczości ekranu. Każde dwa
odcinki kolejnej pary muszą być krótsze od odcinków pary poprzedzającej o pewną
wartość Delta odległość w pikselach pomiędzy liniami spirali. Warunkiem
zakończenia budowy spirali musi być spełnienie następującego ograniczenia: długość
odcinku staje się mniejsza od wartości Delta. Wartość Delta musi być jedyną wartością
wejściową dla rozwiązania tego zadania.
Program 7.9.2. Przykład stosowania rekurencji w grafice
Program Spirala_z_linii_prostych; Uses Graph, Crt; Const Dostep='C:\bp\bgi'; Var Ster,Tryb,x,y,dlug,delta : Integer;
Procedure linia(var x,y,dlug,delta : Integer); begin
line(x,y,x+dlug,y); line(x+dlug,y,x+dlug,y+dlug); line(x+dlug,y+dlug,x+delta,y+dlug); line(x+delta,y+dlug,x+delta,y+delta); x:=x+delta; y:=y+delta; dlug:=dlug-2*delta;
end; begin ClrScr; Write('Podaj liczbe pikseli miedzy ', 'odcinkami spirali = '); Readln(delta);
131
Clrscr; y:=delta; DetectGraph(Ster,Tryb); InitGraph(Ster,Tryb,Dostep); dlug:= GetMaxY - 2*delta; x := (GetMaxX - GetMaxY) div 2; Repeat linia(x,y,dlug,delta); Until dlug < delta; OutTextXY(15,GetMaxY-20,'Wcisnij'); OutTextXY(15,GetmaxY-10,'Enter'); readln; CloseGraph; end.
7.9.3. Zlecenia edytora tekstu systemu Turbo Pascal
Stosując podane w tabl. 7.6 zlecenia edytora systemu TP, skraca się czas pracy,
ułatwia się tworzenie oraz korekty tekstu. Pod terminem blok rozumiemy wyróżniony
fragment tekstu.
Tablica 7.6. Podstawowe zlecenia edytora tekstu systemu Turbo Pascal Przesuwanie kursora:
Ctrl - PageUp do początku tekstu Ctrl - PageDown do końca tekstu Ctrl - QB do początku bloku Ctrl - QK do końca bloku Ctrl - QW do wiersza, w którym wystąpił ostatni błąd.
Wstawianie i usuwanie tekstu:
Ctrl - N wstawianie pustego wiersza przed wierszem, w którym znajduje się kursor Ctrl - Y usunięcie wiersza, w którym znajduje się kursor.
Operacje związane z blokami tekstu:
Ctrl - KB zaznaczanie początku bloku Ctrl - KK zaznaczanie końca bloku Ctrl - KC skopiowanie bloku (początek skopiowanego bloku znajduje się w miejscu położenia kursora)
132
Ctrl - KY skasowanie bloku Ctrl - KH włączenie / wyłączenie podświetlania bloku Ctrl - KW zapisanie bloku na dysk Ctrl - KR odczytanie bloku z dysku Ctrl - KI przesunięcie bloku o jedną kolumnę w prawo Ctrl - KU przesunięcie bloku o jedną kolumnę w lewo.
133
7.9.4. Zadania do ćwiczeń
Napisz programy w języku Pascal rozwiązujące następujące zadania:
1) Na zielonym tle ekranu narysuj w kolorze czarnym współrzędne XoY o środku w
punkcie ekranu (300, 100) pikseli. W tych współrzędnych przedstaw w kolorze
czerwonym wykres funkcji:
2) Napisz program, który w środku ekranu, w okręgu o promieniu 100 pikseli
wyświetla po kolei duże cyfry od 1 do 9. Pojawieniu się każdej cyfry musi
towarzyszyć odpowiednia ilość sygnałów dźwiękowych.
134
8. WPROWADZENIE DO PROGRAMOWANIA W SYSTEMIE DELPHI
Delphi jest rozwinięciem Turbo Pascala firmy Borland przeznaczonym dla pracy w
graficznym środowisku Windows. Stąd, aby łatwiej zrozumieć zasady korzystania z
systemu Delphi, zaleca się pewna wiedza języka Pascal, a w szczególności znajomość
podstawowych instrukcji i struktur danych, a także takich pojęć jak procedura i funkcja.
Z drugiej strony, Delphi należy do grupy języków programowania obiektowego. W
porównaniu z Pascalem ma o wiele szersze możliwości, narzędzia do tworzenia
programów, operuje wieloma nowymi pojęciami. Łatwo zrozumieć, na ile Delphi
rozwija Turbo Pascal, jeśli przytoczymy następujące dane Turbo (Borland) Pascal
wersji 7 dla swojej instalacji potrzebuje około 24 Mb pamięci, natomiast Delphi-5 już
mocno po nad 120 Mb, w zależności od liczby zainstalowanych składowych systemu.
Za nim przejdziemy do opisania możliwości pracy w Delphi, zajżymy do krótkiej
historii języków programowania obiektowego oraz zapoznamy się z kilku nowymi
pojęciami.
8.1. Historia rozwoju, podstawowe pojęcia języków programowania obiektowego
Historia języków programowania obiektowego zaczyna się od języka Simula, w
opisie którego, w roku 1966 po raz pierwszy powstała idea „obiektów”, programowania
obiektowego. Natomiast zainteresowanie programowaniem obiektowym zaczęło
wzrastać dopiero w latach osiemdziesiątych, po opublikowaniu języka Smalltalk-80
[22], kiedy to zrozumiano korzyści, które można osiągnąć podczas opracowywania
złożonych systemów informatycznych wykorzystując tego typu języki.
Każdy z nas stosuje pojęcie „obiektu” w swoim życiu codziennym. Obiektami są
mieszkanie, samochód, drzewo. Cały świat spostrzegamy przez pryzmat obiektów. W
naszym myśleniu w stosunku do nich istnieje pewne podejście hierarchiczne.
Po pierwsze, oceniamy obiekty jako jednostki zbudowane z pewnych części np.,
samochód zawiera pewien zbiór składowych, z których go zmontowano, drzewo
zbudowano z pnia, gałęzi, liści.
135
Po drugie, rozróżniamy pomiędzy sobą poszczególne obiekty wraz z przypisanymi im
atrybutami. Na przykład, oceniamy drzewo jako obiekt pod kątem związanych z nim
atrybutów rozmiaru, wieku, pewnych cech jakościowych oraz relacji przestrzennych
odległości do innych drzew lub innych obiektów.
Po trzecie, z poszczególnych obiektów (jednostek) tworzymy klasy obiektów i
rozróżniamy je pomiędzy sobą np., klasa wszystkich drzew i klasa wszystkich
samochodów.
Te trzy poziomy organizacji obiektów i związane z nimi relacje pomiędzy obiektami
są właściwe dla większości języków programowania obiektowego. W takim
programowaniu badany system dzieli się na osobne jednostki – obiekty i z kolei dalej,
definiują się możliwe działania na takich jednostkach.
Damy definicję obiektu, jako pojęcia informatycznego – jednego z najważniejszych
elementów projektowania, analizy oraz programowania obiektowego, według
J.Górskiego (str. 122 w [22]): „Obiekty są to fizyczne lub logiczne (abstrakcyjne)
elementy występujące w dziedzinie problemowej. Służą one do dwóch celów:
umożliwiają zrozumienie problemu podlegającego rozwiązaniu oraz, w dalszej
perspektywie, są implementowane środkami sprzętowymi i programowymi, a więc stają
się częścią rozwiązania tego problemu”.
Podstawowym elementem konstrukcji programów w tradycyjnych algorytmicznych
(proceduralnych) językach programowania jest podprogram procedura lub funkcja.
Natomiast w językach programowania obiektowego tymi podstawowymi blokami, z
których budowane są złożone systemy programowania, są moduły. Przez moduł
rozumiemy tu połączenie logiczne między sobą pewnych obiektów i (lub) klas
obiektów. Pod tym względem struktura oprogramowania, stworzonego za pomocą
proceduralnych języków programowania, prawie zawsze może być przedstawiona w
postaci drzewa. Natomiast w programowaniu obiektowym, gdzie relacje pomiędzy
poszczególnymi składowymi systemu są bardziej złożone, konstrukcja oprogramowania
opisuje się za pomocą grafów.
136
Do dzisiaj stworzono kilkadziesiąt języków programowania obiektowego.
Wyróżniamy spośród nich dwie grupy: opracowane od podstaw jako obiektowe
(Smalltalk, Flavors, Trellis,...) oraz języki programowania tradycyjne, rozszerzone o
elementy obiektowości (C++, Turbo Pascal 7.0, ...).
W językach pierwszej grupy jedynym dostępnym stylem programowania jest styl
obiektowy. Dla tego języki tej grupy są często nazywane homogenicznymi. Używając
języków drugiej grupy możemy stosować zarówno klasyczny, jak i obiektowy styl
programowania. Języki drugiej grupy są nazywane heterogenicznymi.
Podstawowymi pojęciami związanymi ze stosowaniem języków programowania
obiektowego są instancje klas, dziedziczenie, polimorfizm, enkapsulacja, hermetyzacja.
Instancjami klasy określane są obiekty tworzone w programie zgodnie z definicją
danej klasy. Klasyczne języki programowania oferują programistom zbiory
wbudowanych typów danych, które mogą być wielokrotnie wykorzystywane w celu
tworzenia ich nowych instancji według potrzeb użytkownika. W językach obiektowych
instancje klas mogą być tworzone statycznie lub dynamicznie. Instancje statyczne są
tworzone przez kompilator i istnieją od początku do końca działania programu.
Instancje dynamiczne mogą być tworzone przez odpowiednie instrukcje języka
programowania w dowolnym momencie wykonywania programu w pamięci
operacyjnej. Oczywiście system programowania musi w tym przypadku udostępniać
użytkownikowi możliwości usuwania w pewnym momencie zbędnych dynamicznych
instancji.
Dziedziczenie określa mechanizmy wykorzystania kodu obiektów już stworzonych w
obiektach nowych. Dziedziczenie daje możliwość łatwego rozbudowania oraz
modyfikacji wcześniej opracowanych obiektów, zwiększa wielokrotność wykorzystania
już opracowanego kodu programowego.
Mechanizm dziedziczenia określa, co jest dziedziczone, jak jest dziedziczone oraz
kiedy podczas działania programu następuje dziedziczenie. Różnice między 137
stosowanymi modelami dziedziczenia zależą od tego, jak realizowane są następujące
aspekty dziedziczenia [22]:
1) Zbiory własności klas podlegających dziedziczeniu, takie na przykład, jak wartości
zmiennych, metody. W większości języków te własności podlegają dziedziczeniu.
2) Klasa może bezpośrednio dziedziczyć tylko z jednej klasy lub jednocześnie z wielu
klas. Pierwszy przypadek dziedziczenia opisuje się drzewem, natomiast drugi
skierowanym grafem. Zwykle jest dopuszczalne stosowanie dziedziczenia
wielokrotnego.
3) Sposób (moment stosowania) dziedziczenia statyczny (definiowany podczas
kompilacji) lub dynamiczny (określany w trakcie działania programu).
4) Widoczność dziedziczonych własności u klientów (wykorzystujących już stworzone
obiekty). Najczęściej widoczność jest nieograniczona, czyli, wszystkie dziedziczone
własności, bez względu na to, czy pochodzą z bezpośredniej, czy też z pośredniej
nadklasy, są traktowane w taki sam sposób, jak własności lokalne definiowanej
klasy. Nie ma zatem różnicy między wywołaniem metody lokalnej (odwołaniem się
do lokalnej zmiennej) a wywołaniem metody (zmiennej) odziedziczonej.
Polimorfizm określa możliwość indywidualizacji metod w poszczególnych obiektach.
Przez to pojęcie definiuje się metody (funkcje) opisujące pewne działania, które mogą
być wykonywane w różny sposób na różnych obiektach. Na przykład, metoda
(operacja) plus może być stosowana polimorficznie, z jednej strony, do liczb
całkowitych, rzeczywistych i zespolonych, z drugiej strony, operacja ta w inny sposób
realizuje się na instancjach klas: zbiorach, listach. Polimorfizm zwykle jest ściśle
związany z dziedziczeniem klas. Ta sama metoda (operacja) może być zastosowana
zarówno do instancji danej klasy, jak i do instancji wszystkich podklas, które ją
dziedziczą.
Enkapsulacja – połączenie danych i metod (procedur i funkcji obsługujących dane)
w zakresie obiektu.138
Hermetyzacja. Jest to zasada korzystania z pól obiektu nie inaczej jak za
pośrednictwem metod przez określenie w danym obiekcie odpowiednich procedur i
funkcji zapewniających inicjowanie pól wartościami oraz zwracających wyznaczone
wartości pól. Przestrzeganie zasady hermetyzacji zapewnia, że ewentualne zmiany cech
obiektu powodują konieczność poprawienia jedynie metod organizujących dostęp.
8.2. Podstawowe informacje o systemie programowania Delphi
Delhi jest bardzo rozbudowanym narzędziem umożliwiającym programowanie w
technice RAD (ang. Rapid Application Development). Przez ten skrót oznacza się styl
programowania oparty na tworzeniu szkieletu programu poprzez wskazywanie myszką
gotowych elementów graficznego interfejsu i umieszczenie ich na formatkach programu
w potrzebnym kontekście. Tradycyjna technika pisanie kodu źródłowego, ma nadal
istotne znaczenie, lecz polega jedynie na uzupełnieniu fragmentów kodu, który został
utworzony automatycznie. Po uzyskaniu pewnej praktyki posługiwania się systemem
Delhi pozwala on tworzyć proste aplikacje szybko, w sposób intuicyjny.
8.2.1. Interfejs użytkownika w Delphi
W systemie Delhi stworzono bardzo rozbudowane zintegrowane środowisko
programisty (oznaczano skrótem IDE od angielskich słów Integrated Development
Environment), zawierające wiele narzędzi do tworzenia aplikacji. Tymi narzędziami są:
Edytor kodu programu.
Obiektowy język programowania Object Pascal oraz jego kompilator.
Biblioteka komponentów graficznych VCL (ang. Visual Component Library).
Zbiór programów do obsługi baz danych i grafiki.
System pomocy.
Po uruchomieniu Delphi od razu staje się dostępnym graficzny interfejs użytkownika
tego systemu. Dla Delphi 5 przedstawiono go na rysunkach 8.1 i 8.2.
139
Rys. 8.1. Graficzny interfejs użytkownika Delphi-5
Rys. 8.2. Graficzny interfejs użytkownika Delphi-5 po odsłonięciu okienka formatki programu
140
Interfejs użytkownika składa się z czterech okien. Pierwszym, głównym oknem jest
wąski pasek u góry ekranu zawierający menu, paski narzędziowe oraz komponenty
udostępniające w sposób wizualny poszczególne elementy biblioteki VCL. Drugim
oknem jest umieszczony z lewej strony ekranu Inspektor Obiektów, który służy do
zarządzania wizualnymi elementami aplikacji oraz do ustalenia i zmiany ich cech.
Trzecie okno zajmuje większą część ekranu i na początku jest pustym oknem formatki
programu. Tworzenie interfejsu użytkownika aktualnego programu polega na
zapełnieniu tego trzeciego okna elementami graficznymi, wybieranymi spośród
komponentów pierwszego okna. Po odsłonięciu okienka formatki programu, otwiera się
czwarte okno – okno edytora kodu źródłowego.
8.2.2. Wykonanie programów pascalowych w Delphi
Praktyczne posługiwanie się Delphi najlepiej jest zaczynać od próby uruchamiania w
tym środowisku prostych programów pascalowych działających w systemie MS DOS.
Podstawowe zadanie Delphi to wspomaganie w projektowaniu i wykonywaniu
aplikacji dla środowiska Windows, dla tego tworzenie i (lub) uruchomianie programów
opracowywanych dla MS DOS potrzebuje pewnych przygotowań systemu. Są nimi
następujące działania:
1) Wykasowanie przygotowanego automatycznie modułu Unit1 poprzez wciśnięcie
przycisku Remove file from project.
2) Wybranie w oknie Remove from project pozycji Unit1 i wciśnięciu OK.
3) Wybranie z górnego paska menu opcji Project View Source.
Wynikiem tych działań będzie wyświetlenie okna głównego modułu programu. W
tym oknie należy usunąć zbędne deklaracje i instrukcje oraz wpisać (otworzyć z dysku)
swoje, np. takie:
Program Przywitanie;
{$AppType Console}
Begin
Writeln(‘Czesc, to moj pierwszy program w Delphi’);141
Writeln(‘Aby wrocic do programu wcisnij Enter’);
Readln;
End.
W tekście programu pascalowego dyrektywa {$AppType Console} jest niezbędna dla
informowania kompilatora o tym, że dla wykonania programu koniecznym będzie
utworzenie okna systemu MS DOS. W dyrektywie tej nie będzie potrzeby, jeśli zostanie
ustawiona odpowiednia opcja kompilatora Delphi. Jest to opcja Generate console
application. Udostępnia się ona do wstawiania (kasowania) symbolu „ptaszka” w
odpowiednim okienku przez wykonanie w górnym menu ciągu zleceń: Project
Options... Linker.
8.2.3. Struktura modułu w języku Pascal i jej uzupełnienia w Delphi
W rozdziale 7.9.1 wyjaśniliśmy możliwość tworzenia w języku Pascal modułów o
następującej strukturze:
UNIT nazwa_modułu ;
INTERFACE { rozpoczęcie części publicznej }
USES lista_używanych_Unit;
{ deklaracje części publicznej };
IMPLEMENTATION { rozpoczęcie części prywatnej };
{ deklaracje dotyczące części prywatnej };
Begin
{część wykonawcza}
End.
Język Object Pascal, stanowiący najważniejszą składowa systemu Delphi, rozszerza
możliwości Turbo Pascala, pozwalając na umieszczenie w definicji modułu dwu
dodatkowych dyrektyw: initialization oraz finalization. Pierwsza z nich jest
przeznaczona dla zaznaczania początku części inicjującej (operacyjnej) modułu. Jeśli
część taka w module występuje, to musi ona być zapisywana bezpośrednio za jego
częścią prywatną. Dyrektywa finalization jest przeznaczona dla zaznaczania początku 142
części zamykającej moduł. Część ta, jeśli występuje w module, zapisuje się na jego
końcu jako ostatnia. Jeżeli w module nie wykorzystuje się dyrektywy finalization, to w
roli dyrektywy initialization, rozpoczynającej część inicjującą, możemy użyć słowa
begin, tak jak w Pascalu.
143
10. DODATEK. PRZYKŁADOWE TEMATY ZADAŃ DO KOLOKWIUM I EGZAMINÓW
Dziesięć wariantów zadań, które przedstawiono w tym dodatku można rozważać jako
typowe do przeprowadzania kolokwia lub egzaminu.
Celem zadania pierwszego z każdego wariantu jest sprawdzanie umiejętności
opracowania, opisywania algorytmu oraz jego kodowanie w postaci programu
komputerowego. Zadanie drugie pozwala sprawdzić zrozumienie działania
poszczególnych instrukcji języka programowania (tym razem Pascala), a zadanie trzecie
umiejętność tworzenia prostych obrazów graficznych w środowisku wybranego języka
programowania.
Praktyka rozwiązywania przedstawionych zadań w różnych grupach studenckich,
wskazuje stosunkową łatwość rozwiązywania zadań 2 i 3. Świadczy to o dość szybkim
opanowaniu podstaw języka programowania.
Natomiast zadanie 1 wykonuje bezbłędnie tylko 10-30 procent sprawdzanych osób.
Jest to dość złym sygnałem większość uczących się informatyki ma bardzo
ograniczone logiczne myślenie. A właśnie ono jest najbardziej potrzebne dla
rozwiązywania problemów praktycznych z wykorzystaniem komputerów. Dla tego
zalecam Czytelnikowi spróbować swoje siły w rozwiązywaniu właśnie tego zadania.
Przede wszystkim w opracowaniu algorytmu i przedstawianiu go na piśmie. Niech mój
drogi Czytelnik nie uważa się za geniusza: „jest to dla mnie pestka, ja ten algorytm
trzymam w głowie, rozpocznę od razu od programu...”. Brak ćwiczeń z logicznym
układaniem swoich myśli na piśmie nigdy nie pozwoli zostać dobrym informatykiem. A
przecież dzisiaj, dotykając komputera każdy z nas chce się uważać za takiego.
144
Wariant 9.1.Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: m, n. Otrzymać w pamięci komputera i wydrukować wiersz po wierszu elementy macierzy A:
dla i=1,2,...,m, j=1,2,...,n.Wydrukować indeksy (i, j) oraz wartość największego elementu macierzy A.
Zad.2. Daj opis działania następującego programu.
PROGRAM Klawisze;uses crt; var i, n : integer;
T : array[0..100] of char;begin clrscr; Write('Wprowadz dowolny text, ',
' symbolem kaczacym tekst jest "#" : '); n := -1; Repeat n:=n+1; T[n] := Readkey; write(t[n]); Until t[n]='#'; writeln; Writeln(' == n = ',n,' =='); for i:=0 to n-1 do write(' ',t[i]);end.
Zad.3. Stwórz program rysujący w trybie graficznym: w niebieskim kolorze, pionowo grzebień o 20 zębach. Na dole ekranu napisz dużymi literami swoje imię i nazwisko.
Wariant 9.2.Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: n, T = (t(1),...,t(n)). Elementami tabeli T są litery ze zbioru {A,B,C,D}. Wydrukować indeksy elementów tablicy T zawierających litery B i D (oddzielnie dla każdej litery) oraz obliczyć, ile jest liter A i liter C w tym ciągu.
Zad.2. Daj opis działania następującego programu.
PROGRAM EG;Uses Crt;
Var R : real;
i, N1, N2 : integer;Begin
N1:=0; N2:=0;For i := 1 to 100 do
beginR:=Random; Write(R:6:3);
if R > 0.5 then N1:= N1+1
else N2:= N2+1;end;
WRITELN;Writeln('N1 = ',N1,' N2 = ',N2);Delay(4000)
End.
Zad. 3. Stwórz program rysujący w trybie graficznym: w kontach ekranu, rozpoczynając od lewego górnego rogu, duże cyfry, po kolei: 1, 2, 3, 4. Towarzyszyć temu powinni odpowiednie ilości sygnałów dźwiękowych. Tło ekranu żółte. Cyfry czerwone. Pośrodku ekranu czarnymi dużymi literami napisz swoje imię i nazwisko.
145
Wariant 9.3. Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: m, x(0), x(1). Stosując wzoryx(i) = 10* x(i-1)* x(i-2) + cos (x(i-1)+x(i-2)), dla i=2,...,m.,
i=0,...,m.,Obliczyć i wydrukować wartości x(i), Y(i), i=0,...,m.. Obliczanie wartości x(i), Y(i) zdefiniować w postaci dwóch procedur-funkcji. Połączyć odcinkami kolejno znalezione kropki i obliczyć długość zdefiniowanej w ten sposób trasy od (x(0), Y(0)) do (x(m), Y(m)).
Zad.2. Daj opis oraz wynik działania następującego programu.
Program TEST;Var s,n,n1,n2,n3 : integer;
Beginwrite('Podaj rok urodzenia (4 cyfry): ');readln(n);writeln('n = ',n);n1:= n div 1000;n2:= (n-n1*1000) div 100;n3:= (n-((n1*1000)+(n2*100))) div 10;s:= n1 + n2 + n3 + n mod 10;writeln('n1=',n1,' n2=',n2,' n3=',n3,' s=',s);
End.
Zad. 3. Stwórz program rysujący w trybie graficznym: na białym tle, po środku ekranu pięciokątną gwiazdę w czerwonym kolorze. W jej środku umieść czarne kółko dotykające wierzchołków wewnętrznych gwiazdy. Z lewej strony ekranu, w pionie napisz dużymi literami swoje imię i nazwisko.
Wariant 9.4. Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Stosując funkcję Random wylosować N kropek w prostokącie o współrzędnych lewego górnego i prawego dolnego kątów, odpowiednio (-15, 10) i (15, -10). W prostokącie tym znajduje się okręg ze współrzędnymi środka (0, 0) o promieniu r = 5. Obliczyć, ile kropek z N wylosowanych wpadnie na zewnątrz od okręgu.
2) Zad.2. Daj opis działania następującego programu.
PROGRAM EGz2;Uses Crt;var
T : String;R : Char;N : integer;
BeginWrite('Wprowadz dowolny text,',
' ostatni symbol tekstu jest "*" ');ReadLn(T);N:=0;Repeat
N:=N+1;R:=T[N];
Until R ='*';Writeln(' N = ',N-1);WRITELN('----------');Delay(2000)
End.
Zad.3. Stwórz program rysujący w trybie graficznym: na białym tle, po środku ekranu czarny zegar wskazujący godzinę 3-00. Pojawienie się na ekranie zegara sygnalizuje się trzema dźwiękami różnego poziomu. Z lewej strony ekranu, w pionie napisz dużymi literami swoje imię i nazwisko.
146
Wariant 9.5.Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: n, x(0), x(1). Stosując wzoryx(i) = sin(x(i-1)+1) x(i-1) - x(i-2), dla i= 2,...,n;
, i= 0, 1,..., n, obliczyć i wydrukować wartości
x(i), y(i), i=0,1,...,n, oraz sumę S= .
Obliczanie wartości x(i), y(i) zdefiniować w postaci dwóch procedur-funkcji.
Zad. 2. Daj opis działania programu ilustrując swoją odpowiedź rysunkiem. Program Losowanie; Uses Crt; Const n = 1.E4; a=-10; b=10; c=10; d=-10; Var i, x, y, s1, s2, s3, s4 : real;Begin Randomize; ClrScr; i:=0; s1:=0; s2:=0; s3:=0; s4:=0; Repeat i:=i+1.0; x:=random * (c-a) + a; y:=random * (b-d) + d;
if (x>=0.0) and (y>=0.0) then s1:=s1+1;if (x< 0.0) and (y>=0.0) then s2:=s2+1;if (x< 0.0) and (y< 0.0) then s3:=s3+1;if (x>=0.0) and (y< 0.0) then s4:=s4+1;
Until i=n; writeln (' s1=',s1:5:0,' s2=',s2:5:0, ' s3=',s3:5:0, ' s4=',s4:5:0,' n=',s1+s2+s3+s4:6:0);End.
Zad. 3. Stwórz program rysujący w trybie graficznym: na zielonym tle, po środku ekranu profil czarnego czołgu. Z prawej strony ekranu, w pionie napisz dużymi literami swoje imię i nazwisko.
Wariant 9.6.Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: n, x(0), x(1). Stosując wzory
x(i) = cos(x(i-2))+ x(i-1) , dla i= 2,...,n, i=0,
1,..., n, obliczyć i wydrukować wartości x(i),
Y(i), i=0,1,...,n, oraz sumę S= .
Obliczanie wartości x(i), Y(i) zdefiniować w postaci dwóch procedur-funkcji.
Zad. 2. Daj opis działania programu ilustrując swoją odpowiedź rysunkiem.
Program Losowanie2;Uses Crt;Const n = 1.E3; a = -10; b = 10;Var i, x, y, s1, s2 : real;
BeginRandomize;
ClrScr; i:=0; s1:=0; s2:=0; Repeat i:=i+1.0; x:=random * (b-a) + a; y:=random * (b-a) + a; if (sqr(x) + sqr(y)) <= sqr(b) then s1:=s1+1 else s2:=s2+1;
Until i=n; writeln (' s1=',s1:5:0,' s2=',s2:5:0);End.
Zad. 3. Stwórz program rysujący w trybie graficznym: po środku ekranu, na żółtym tle zamykające się powoli oko. Z lewej strony ekranu, w pionie napisz dużymi literami swoje imię i nazwisko.
147
Wariant 9.7.Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: M, N. Otrzymać w pamięci komputera i wydrukować wiersz po wierszu elementy macierzy A:
dla i=1,2,...,M, j=1,2,...,N.Wydrukować indeksy ( i, j ) oraz wartość największego elementu macierzy A. Ile elementów o tej wartości zawiera macierz?
Zad.2. Daj opis oraz wynik działania następującego programu.
PROGRAM Klaw2;uses crt;var i, n: integer; x: array[0..150] of char;
begin clrscr; n := 0; Write('Wprowadz dowolny text, symbolem ‘,
‘zakaczenia tekstu jest "@" : '); Repeat
x[n] := Readkey; write(x[n]); n:=n+1; Until t[n]='@'; writeln; Writeln(' n = ',n,' ); for i:= n-1 downto 0 do write(' ',x[i]);end.
Zad.3. Stwórz program rysujący w trybie graficznym: po środku ekranu, na żółtym tle, piramidę gwiazdek w niebieskim kolorze o zadanej ilości poziomów, jak np. poziomu 3: *
*****
Na dole ekranu napisz dużymi literami swoje imię i nazwisko.
Wariant 9.8.Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: m, C = (t(1),...,t(m)). Elementami tabeli C są cyfry ze zbioru {1,2,3,4}. Wydrukować indeksy elementów tablicy C zawierających cyfry 2 i 4 (oddzielnie dla każdej cyfry) oraz obliczyć, ile jest cyfr 1 i cyfr 3 w tym ciągu.
Zad.2. Daj opis działania następującego programu.
Program Los98;Uses Crt;
Var i, n1, n2, z : word;Begin
i := 0; n1:=0; n2:=0;While i < 500 do
begini := i+1;z:=Random (100); Write(z:4);
if z > 75 then n1:= n1+1
else n2:= n2+1;
end;WRITELN;Writeln('n1 = ',n1,' n2 = ',n2);Delay(1000)
End.
Zad. 3. Stwórz program rysujący po trybie graficznym w środku każdej z krawędzi ekranu, rozpoczynając od krawędzi górnej, pary liter, po kolei: do, re, mi, fa. Towarzyszyć temu powinni odpowiednie dźwięki. Tło ekranu żółte. Litery czerwone. Pośrodku ekranu czarnymi dużymi literami napisz swoje imię i nazwisko.
148
Wariant 9.9. Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe: m, a, b. Obliczyć i wydrukować wartości x(i),Y(i), i=1,...,m., gdzie x(i) = 10 (R1 - R2), R1 , R2 dwie kolejne wartości losowe, wybierane w przedziale (a, b) dla każdej wartości i,
.Obliczanie wartości x(i), Y(i) zdefiniować w postaci dwóch procedur-funkcji. Połączyć odcinkami kolejno znalezione kropki i obliczyć długość zdefiniowanej w ten sposób trasy od (x(1), Y(1)) do (x(m), Y(m)).
Zad.2. Daj opis oraz wynik działania następującego programu.
Program Test2;Var s,n,n1,n2,n3 : integer;
Beginwrite('Podaj swoj rok urodzenia (4 cyfry):
');readln(n);writeln('n = ',n);n1:= n div 1000;n2:= (n-n1*1000) div 100;n3:= (n-((n1*1000)+(n2*100))) div 10;s:= 2* n1 + 3*n2 + 4*n3 + 5*( n mod 10);writeln('n1=',n1,' n2=',n2,' n3=',n3,' s=',s);
End.
Zad. 3. Stwórz program rysujący w trybie graficznym: na niebieskim tle, po środku ekranu białą sześciokątną gwiazdę. W jej wierzchołkach mają być małe czerwone kółeczka. Z prawej strony ekranu, w pionie napisz dużymi literami swoje imię i nazwisko.
Wariant 9.10. Zad.1. Przedstaw algorytm i napisz program w języku Pascal rozwiązania następującego problemu:
Dane wejściowe n, a, b, c. Obliczyć i wydrukować objętości prostopadłościanów V(i) = X(i) * Y(i) * Z(i), i=1,2,...,n , gdzie odcinki X(i),Y(i),Z(i) są wartościami losowymi: X(i) (0, a), Y(i) (0, b), Z(i) (0, c). Znaleźć spośród obliczonych wartości V(i) wartość minimalną, średnią i maksymalną .
2) Zad.2. Daj opis działania następującego programu.
Program egz2;Uses Crt;Var T: String; i,n : word;
R:array[1..250] of Char;Begin clrscr;
Write('Wprowadz dowolny text,', ' ostatni symbol tekstu jest "#"
');ReadLn(T);n:=1;Repeat
R[n] := T[n]; n := n+1; Until R[n-1] ='#';
Writeln(' N = ',N-2); For i:=n-2 downto 1 do write(' ',R[i]);
Delay(2000);End.
Zad.3. Stwórz program rysujący w trybie graficznym: na zielonym tle, po środku ekranu czerwony zegar wskazujący godzinę 4-00. Pojawienie się na ekranie zegara sygnalizuje się czteroma dźwiękami różnego poziomu. Z lewej strony ekranu, w pionie napisz dużymi literami swoje imię i nazwisko.
149
LITERATURA
1. Abolrous S.A.: Pascal, podstawy programowania, MIKOM, Warszawa 1997.2. Banachowski L., Kreczmar A., Rytter W.: Analiza algorytmów i struktur danych, WNT, Warszawa 1987.3. Barkakati N.: Biblia Turbo C++, LT & P. sp. z o.o., Warszawa, 1988.4. Baron B.: Metody numeryczne w Delphi 4. Helion, Gliwice 1999.5. Bielecki J.: Jawa od podstaw, Intersoftland, Warszawa 1997.6. Błażewicz J.: Złożoność obliczeniowa problemów kombinatorycznych., WNT, Warszawa 1988.7. Borucki W., Ignasiak E., Marcinkowski J., Sikora W.: Badania operacyjne, PWE, Warszawa 1996.8. Brandt S.: Analiza danych (metody statystyczne i obliczeniowe), PWN, Warszawa 1999.9. Buczkowski L.: Programowanie w Turbo Pascal 7.0, PLJ, Warszawa 1993.10. Chabik J.: Praktyka skutecznego programowania, NAKOM, Poznań 1999.11. Cormen T.H., Leiserson C.H., Rivest R.L.: Wprowadzanie do algorytmów, WNT, Warszawa 1999.12. Curulak A.: Turbo Pascal 7.0, opis standardowej biblioteki, WSIiE TWP w Olsztynie, Olsztyn 1999.13. Elementy informatyki : Pakiet oprogramowania edukacyjnego pod redakcją Macieja M. Sysły, OFEK,
Wrocław i Poznań 1992.14. Elementy informatyki, podręcznik: pod red. Macieja M. Sysły, PWN, Warszawa 1995. 15. Elementy informatyki. Rozwiązania zadań: pod red. Macieja M.Sysły, PWN, Warszawa 1994.16. Elliot S.D., Miller P.L.: 3D Studio, Animacja, HELION, Gliwice 1997.17. Głaza D., Krasowska E.: Turbo Pascal w zadaniach z komentarzem, MIKOM, Warszawa 1998.18. Grzymkowski R., Kapusta A., Słota D.: „Mathematica”, narzędzia inżyniera, Pracownia Komputerowa
Jacka Skałmierskiego, Gliwice 1994.19. Harel D.: Rzecz o istocie informatyki, Algorytmika, WNT, Warszawa 2000.20. Iglewski M., Madej J., Matwin S.: Pascal, WNT, Warszawa 1992. 21. Informatyka dla ekonomistów: pod red. A. Nowickiego, PWN, Warszawa 1998.22. Inżynieria oprogramowania w projekcie informatycznym: pod red. J. Górskiego, MIKOM, Warszawa 1997.23. Jamsa K., Lalani S., Weaklej S.: Programowania stron WWW, MIKOM, Warszawa 1997.24. Janiak W.: Wstęp do „Mathematica” programu do obliczeń matematycznych, PLJ, Warszawa 1994. 25. Jarża R.: Turbo Pascal 7.0. Szkoła programowania, „Robomatic”, Lodź 2000.26. Jaszkiewicz A.: Inżynieria oprogramowania, HELION, Gliwice 1997.27. Kierzkowski A.: Tukbo Pascal. Ćwiczenia praktyczne, Helion, Gliwice 2000.28. Kniat J.: Programowanie w języku C++, NAKOM, Poznań 1999.29. Marciniak A.: Nowe elementy języka Object Pascal w pakiecie Borland Delpfi 3 i 4, NAKOM, Poznań
1999.30. Meyers G.: Projektowanie niezawodnego oprogramowania, WNT, Warszawa 1980.31. Microsoft Access 2000, wersja polska, krok po kroku, READ ME, Warszawa 1999.32. Nowakowski Z., Sikorski W.: Informatyka bez tajemnic, część 1,2,3, MIKOM, Warszawa 1997.33. Pirjanowicz W.: Wstęp do informatyki, WSIiE TWP w Olsztynie, Olsztyn 1999.34. Porębski W.: Pascal, wprowadzenie do programowania, HELP, Warszawa 1999.35. Strzałkowski K.: Podstawy Delphi, „Stachurski”, Kielce 2000.36. Sysło M.M., Deo N., Kowalik J.S.: Algorytmy optymalizacji dyskretnej z programami w języku Pascal,
PWN, Warszawa 1999.37. Turski W.M.: Propedeutyka informatyki, PWN, Warszawa 1989.38. Walkenbach J.: Excel 7 dla Windows 95 - Biblia, READ ME, Warszawa 1996.39. Webb J.: Użycie Visual Basic dla Aplikacji w Excelu, LT & P., Warszawa 1997.40. Weiskamp K.: Borland Pascal 7.0, WNT, Warszawa 1996.41. Wirth N.: Algorytmy + struktury danych = programy, WNT, Warszawa 1989.42. Wróblewski P.: Algorytmy, struktury danych i techniki programowania, Helion, Gliwice 1997.R1. Вельбицкий И.В.: Технология программирования, Техника, Киев 1984. R2. Пирьянович В.А., Дробушевич Л.Ф.: Использование Р-технологии программирования при обучении
150
проектированию и созданию программных комплексов. Деп. в НИИ ВШ, ном. 666-85, Минск 1985.R3. Пирьянович В.А., Дробушевич Л.Ф.: Методические указания к лабораторным работам по курсу
„Технология программирования”, БГУ, Минск 1988.R4. Дробушевич Л.Ф.: Методы программирования, обьектно-ориентированный метод проектирования,
части 1,2, БГУ, Минск 1999.
151