Opowieść o programowaniu w Adzie
"Jak to zrobić, żeby on (komputer) zrobił to, co ja chcę?!?" i "Dlaczego?" zadawane mniej lub
bardziej rozpaczliwym tonem to pytania bardzo często towarzyszące początkom nauki programowania.
Niniejszy rozdział nie stanowi opisu języka - wiele istotnych informacji zostało pominiętych - jest
natomiast, jak wskazuje tytuł, opowieścią o programowaniu i języku adresowaną do osób stykających
się z tym zagadnieniem po raz pierwszy. A celem jest - aby po przeczytaniu pytania postawione na
początku były zadawane tonem nieco mniej rozpaczliwym...
Algorytmy
Niezbędnym elementem nauki programowania jest umiejętność poprawnego sformułowania
rozwiązania problemu, czyli stworzenia odpowiedniego algorytmu. Algorytm składa się z ciągu
pojedynczych, prostszych operacji ustawionych w kolejności wykonywania. Powinien rozwiązywać
dany problem i kończyć swoje działanie po wykonaniu skończonej ilości kroków. Z algorytmami
spotykamy się w życiu codziennym - może to być na przykład opis montażu jakiegoś urządzenia czy
mebli dołączany jako instrukcja, czy przepis w książce kucharskiej:
1.
weź 4 jajka, szklankę mąki, szklankę cukru, proszek do pieczenia
2.
utrzyj żółtka z cukrem
3.
ubij pianę z białek
..... itd
lub algorytm rozwiązywania równania kwadratowego:
1.
podaj współczynniki trójmianu a,b,c
2.
sprawdź, czy a /= 0. Jeżeli a = 0, to stwierdzamy, że to nie jest trójmian i przechodzimy
do punktu 7
3.
oblicz wyróżnik trójmianu delta = b^2-4ac
4.
jeżeli delta < 0, to równanie nie ma rozwiązania w zbiorze liczb rzeczywistych,
przechodzimy do punktu 7
5.
jeżeli delta = 0, to równanie ma jeden pierwiastek podwójny x = -b/(2a), przechodzimy
do punktu 7
6.
jeżeli delta > 0, to równanie ma dwa pierwiastki x1 = (-b - pierwiastek(delta)) / (2a),
x2 = (-b + pierwiastek(delta)) / (2a), przechodzimy do punktu 7
7.
zakończenie
albo znajdowanie najmniejszej liczby w ciągu n-elementowym
1.
podaj n oraz a1, a2, ... , an
2.
min := a1
3.
m := 2
4.
porównaj am z min. Jeżeli am < min to min := am
5.
m := m+1
6.
jeżeli m <= n, to idź do punktu 4
7.
wypisz min
8.
koniec
Często rozwiązując bardziej skomplikowane zagadnienia rozkładamy je na kilka prostszych,
"mniejszych" problemów, te problemy - na jeszcze mniejsze itd - aż dojdziemy do etapu, gdy dla
każdego zagadnienia łatwo nam już utworzyć odpowiedni algorytm. Jest to tak zwane projektowanie
programu z góry w dół, czyli top-down design.
Kolejnym etapem pisania programu jest zapisanie utworzonego algorytmu za pomocą instrukcji
danego języka programowania. U nas będzie to Ada95.
81
Pierwsze programy
Pisanie programów rozpoczniemy od rzeczy najprostszych, na przykład od wypisania przez
komputer jakiegoś tekstu na ekranie:
-- pierwszy program w Adzie
-- wypisywanie "powitania" na ekranie
with ada.text_io;
procedure pr1 is
begin
ada.text_io.put(" Dzien dobry!!! ");
ada.text_io.new_line;
ada.text_io.put(" Milej nauki! ");
end pr1;
Program nosi nazwę pr1. Jego właściwa treść zawarta jest pomiędzy liniami
procedure pr1 is
a
end pr1;
Linia
with ada.text_io
stanowi tzw. specyfikację kontekstu. Co to takiego? Otóż poszczególne polecenia (funkcje i procedury,
będące właściwie małymi programami) zebrane są dla wygody w większe, łączące się tematycznie
grupy - pakiety, których może być bardzo wiele - zarówno standardowych, dołączanych do danej wersji
języka, jak i tworzonych przez samego programistę. Tak zwana klauzula with stanowi informację, które
z nich mają być widoczne dla programu. Tak więc nasz program "widzi" tylko funkcje i procedury
zawarte w pakiecie o nazwie ada.text_io. Nie są one jednak dostępne bezpośrednio - nazwę każdej
z nich musimy poprzedzić nazwą pakietu, z którego ona pochodzi, oddzieloną kropką (np.
ada.text_io.put).
Biorąc pod uwagę to, co powiedzieliśmy, można patrząc na nazwę ada.text_io postawić
sobie pytanie, skąd w takim razie kropka pomiędzy ada i text_io - czyżby text_io pochodził
z ada? Stwiedzenie to nie jest pozbawione racji. W Adzie istnieje możliwość tworzenia jednostek -
"dzieci" (child units). Nazwa jednostki - rodzica oddzielana jest wówczas kropką od nazwy jednostki -
dziecka. Zależność ta może być kilkustopniowa (np. ada.text_io.integer_io,
ada.numerics.generic_elementary_functions). O mechanizmie child units dowiemy się
więcej w dalszej części książki. Na razie używać będziemy pełnych nazw pakietów - dzieci, czyli nazw
w postaci rodzic.dziecko, jak w powyższym programie.
Pakiet ada.text_io ma drugą, równoważną nazwę - text_io, nadaną mu za pomocą tzw.
przemianowywania. Możemy więc używać w programach nazwy krótszej bądź dłuższej, należy jednak
robić to konsekwentnie. Program pr1 może mieć postać
with text_io;
procedure pr1 is
begin
text_io.put(" Dzien dobry!!! ");
text_io.new_line;
text_io.put(" Milej nauki! ");
end pr1;
ale niedopuszczalne jest napisanie na przykład
81
with ada.text_io;
procedure pr1 is
begin
text_io.put(" Dzien dobry!!! ");
end pr1;
gdyż klauzulą with udostępniamy zasoby pakietu ada.text_io, a nie text_io.
Jeszcze słowo o nazewnictwie. Nasz program nazwaliśmy pr1, używany w nim pakiet nosi
nazwę text_io. W Adzie nazwa - czy to programu, czy pakietu, zmiennej, funkcji, procedury (co to
jest - dowiemy się później, w każdym razie każda nadawana nazwa) musi spełniać pewne warunki.
Może być dowolnej długości, ale może zawierać tylko litery, cyfry i znaki podkreślenia, musi zaczynać
się od litery i nie może kończyć się znakiem podkreślenia ani zawierać dwóch znaków podkreślenia
następujących bezpośrednio po sobie. Oczywiście nie może być żadnym ze słów kluczowych (są to
takie słowa jak np. begin, end, ich spis znajduje się w drugim rozdziale tej książki). Wielkie i małe
litery nie są przez Adę rozróżniane (tak więc Pierwszy_Program i pierwszy_program to ta
sama nazwa). Oczywiście dobrze jest, gdy nadawana nazwa z czymś się kojarzy - ułatwia to czytanie
programu i nanoszenie ewentualnych poprawek nawet po upływie dłuższego czasu (można nazwać
zmienną np. xxppbrk12 zamiast podatek, ale kto później odgadnie, co się pod nią kryje?...) Np.
nazwa text_io pochodzi od text input/output - a więc nietrudno się domyślić, do czego służą
zgromadzone w pakiecie "narzędzia".
Wróćmy do naszego programu. Pochodząca z powyższego pakietu procedura put powoduje
wypisanie na ekranie tekstu będącego jej argumentem. Argumenty (inaczej parametry) funkcji
i procedur umieszczamy w nawiasach. Ada.text_io.put wymaga jednego argumentu będącego
tekstem. Jako tekst traktowany jest ciąg znaków umieszczony w cudzysłowach, np.
"Ala ma kota"
"procedure"
"1996"
są tekstami, natomiast
procedure
to już nie tekst, lecz słowo kluczowe, zaś
1996
jest liczbą.
W programie znajduje się również instrukcja new_line (także pochodząca z pakietu
ada.text_io), która powoduje przejście kursora do następnego wiersza - raz, gdy jest używana bez
parametru, lub kilkakrotnie, jeśli ma postać
new_line(n);
(n jest liczbą naturalną)
co w praktyce oznacza wypisanie n pustych linii.
Tak więc wynikiem wykonywania kolejnych linii programu będą odpowiednio:
Dzien dobry!!!_ (kursor na końcu tekstu)
Dzien dobry!!!
_ (kursor w następnej linii)
Dzien dobry!!!
81
Milej nauki!_
Zauważmy, że wykonane zostały instrukcje zawarte w tzw. części wykonywalnej programu, a
więc linie zawarte pomiędzy słowem begin a end pr1. Część wykonywalna programu musi
zawierać zawsze przynajmniej jedna instrukcję, nawet jeżeli miałaby to być "nic nie robiąca" instrukcja
null. Oprócz części wykonywalnej program może zawierać także część deklaracyjną, zawartą
pomiędzy linią procedure ... is a begin. Umieszczamy w niej np. deklaracje zmiennych
(będzie o tym mowa w dalszej części tego rozdziału). Wcześniej - przed linią procedure ... is
znajdować się może specyfikacja kontekstu. Oprócz klauzuli with w jej skład wchodzić może także
klauzula use powodująca, że zasoby danego pakietu są widziane bezpośrednio (zobaczymy to w
następnym programie). Ponadto w dowolnym miejscu programu możemy umieszczać komentarze, a
więc teksty służące tylko jako informacja dla osoby czytającej program, nie mające jednak wpływu na
jego działanie. Komentarze poprzedzane są dwoma kolejno następującymi myślnikami i obejmują tekst
od tychże myślników do końca linii.
A oto program stanowiący modyfikację programu pr1. Dzięki zastosowaniu klauzuli use nie
musimy już w części wykonywalnej używać nazw pakietów, z których pochodzą procedury i funkcje.
-- modyfikacja programu pr1
-- dzieki umieszczeniu w programie linii
-- "use ada.text_io"
-- nie musimy uzywac nazw pakietow, z ktorych pochodza
-- procedury i funkcje
with ada.text_io;
use ada.text_io;
procedure pr2 is
begin
put(" Dzien dobry!!! ");
new_line;
put(" Milej nauki! ");
end pr2;
W niektórych przypadkach możemy mieć do czynienia nie z jednym, lecz z kilkoma pakietami,
używanymi w jednym programie, w których występują procedury i funkcje o tych samych nazwach.
Przykładem tego mogą być - używane w poniższym programie - put wypisujące tekst, zawarte w
pakiecie text_io oraz put wypisujące liczby całkowite, zawarte w pakiecie int_io.
-- Program demonstrujacy dzialania matematyczne:
-- dodawanie, odejmowanie, mnozenie, dzielenie
-- i potegowanie.
-- Dzialania wykonujemy na liczbach calkowitych
-- (uwaga na wynik dzielenia!)
with ada.text_io;
-- tym razem bez "use" zeby zobaczyc, co jest skad
procedure pr3 is
package int_io is new ada.text_io.integer_io(integer);
a,b : integer;
begin
ada.text_io.put_line("Podaj dwie liczby calkowite");
ada.text_io.put("liczba a : ");
int_io.get(a);
ada.text_io.put("liczba b : ");
81
int_io.get(b);
ada.text_io.new_line(4);
ada.text_io.put("a+b = "); int_io.put(a+b);
ada.text_io.new_line;
ada.text_io.put("a-b = ");int_io.put(a-b);
ada.text_io.new_line;
ada.text_io.put("a*b = ");int_io.put(a*b);
ada.text_io.new_line;
ada.text_io.put("a/b = ");int_io.put(a/b);
ada.text_io.new_line;
ada.text_io.put("a*a*a = ");int_io.put(a**3);
ada.text_io.new_line;
ada.text_io.put("a mod b = ");int_io.put(a mod b);
ada.text_io.new_line;
ada.text_io.put("a rem b = ");int_io.put(a rem b);
ada.text_io.new_line;
ada.text_io.put("abs b = ");int_io.put(abs b);
end pr3;
Pakiet int_io pojawił sie w programie w nie spotykany dotychczas sposób, bo dopiero w
części deklaracyjnej. Dlaczego? Otóż nie wszystkie pakiety są od razu w postaci "gotowej do użycia".
Jeżeli są, możemy napisać with nazwa_pakietu i korzystać z ich zasobów. Czasami jednak mają
one postać ogólniejszą, jak gdyby "szkieletu", jakichś ogólnych, dość uniwersalnych ram, które
przybieraja postać pakietu zawierającego możliwe do użycia funkcje i procedury dopiero po dokonaniu
tzw. konkretyzacji. Takie pakiety - "szkielety" noszą nazwę pakietów rodzajowych. W naszym
przykładzie używamy pakietu rodzajowego o nazwie ada.text_io.integer_io. Zawiera on
procedury wejścia/wyjścia (czyli np. wprowadzania z klawiatury i wyprowadzania na ekran) dla liczb
dowolnego typu całkowitego. Konkretyzacji dokonujemy poleceniem
package int_io is new ada.text_io.integer_io (integer);
(w wolnym przekładzie pakiet int_io jest nowym (pakietem) ada.text_io.integer_io dla
integerów), które powoduje utworzenie pakietu (nazwaliśmy go int_io) zawierającego operacje
wejścia/wyjścia takie same, jak text_io.integer_io, ale już dla konkretnego typu - typu
integer.
Istnieje
również
gotowy
pakiet
będący
taką
właśnie
konkretyzacją -
ada.integer_text_io. Podobny do pr3 program używający takiego gotowego pakietu bedzie
miał postać
with ada.text_io,ada.integer_text_io;
procedure pr4 is
a,b:integer;
begin
ada.text_io.put_line("Podaj dwie liczby calkowite");
ada.text_io.put("liczba a : ");
ada.integer_text_io.get(a);
ada.text_io.put("liczba b : ");
ada.integer_text_io.get(b);
ada.text_io.new_line(4);
ada.text_io.put("a+b = ");
ada.integer_text_io.put(a+b);
-- ... i tak dalej
end pr4;
W programie pr3 nie zastosowaliśmy klauzuli use, więc każdą procedure poprzedzamy nazwą
pakietu, z którego ona pochodzi. Unikamy w ten sposób niejednoznaczności - mimo tych samych nazw
procedur kompilator "wie", która z nich (z którego pakietu pochodzącą) chcemy zastosować. Nie jest to
jednak konieczne w przypadku, gdy zastosujemy klauzulę use, a procedury - mimo iż mają te same
81
nazwy - operują na argumentach różnego typu bądź na różnej ilości agrumentów. Ada "rozpozna" na tej
podstawie odpowiednią procedurę (odpowiedni pakiet). I tak na przykład ada.text_io.put
operuje na tekście, zaś int_io.put - na liczbach typu integer. Właściwie istnieje kilka sposobów
użycia procedury put, z których na razie poznamy dwa:
put ( liczba_całkowita );
oraz
put ( item => liczba_całkowita, width => szerokość );
W pierwszym przypadku po prostu wypisujemy liczbę (tu - typu integer) używając domyślnych
ustawień, w drugim natomiast programista ma możliwość określenia, ile kolumn - przynajmniej - ma
zajmować wypisywana liczba. Odpowiada za to parametr width (ang. szerokość). Jeżeli wypisywana
liczba mieści się w zaplanowanej ilości kolumn, to umieszczana jest z prawej strony tej przestrzeni, zaś
reszta wypełniana jest spacjami. W przeciwnym wypadku używana jest minimalna ilość kolumn
potrzebnych do wypisania żądanej liczby. Tak więc aby zapewnić sobie maksymalne wykorzystanie
miejsca, korzystnie jest użyć parametru width o wartości 0. Ilustruje to poniższy przykład.
Wykonanie instrukcji
ada.text_io.put("Ala ma ");
int_io.put(7);
ada.text_io.put(" lat.");
da wynik
Ala ma 7 lat.
natomiast instrukcje
ada.text_io.put("Ala ma ");
int_io.put(item => 7, width = > 0);
ada.text_io.put(" lat.");
spowodują wypisanie tekstu
Ala ma 7 lat.
Item i width są nazwami parametrów procedury int_io.put. Przy wywołaniu procedury
można się nimi posługiwać lub nie - dozwolone jest zarówno napisanie
int_io.put ( item => 7, width => 0 );
(jest to tzw. notacja nazywana), jak i
int_io.put (7,0);
(jest to tzw. notacja pozycyjna). W drugim przypadku należy jednak pamiętać o zachowaniu
prawidłowej kolejności argumentów. Jeżeli argumenty "nazywamy", tak jak w pierwszym przypadku,
to moga one występować w dowolnej kolejności. Można łączyć oba sposoby, np.
int_io.put ( 7, width => 0 );
ale wówczas notacja pozycyjna musi występować przed nazywaną.
Trochę matematyki
Wróćmy do programu pr3. Od dłuższego czasu wspominamy o liczbach typu integer. Skąd
jednak wiemy, że działamy na liczbach jakiegoś typu i jakie to powoduje konsekwencje?
Za pomocą linii
81
a,b : integer;
umieszczonej w części deklaracyjnej programu zadeklarowaliśmy zmienne o nazwach a i a jako będące
typu integer. Deklaracja zmiennych powoduje dla każdej zmiennej zarezerwowanie w pamięci
komputera obszaru o ściśle określonym, wymaganym dla danego typu rozmiarze. Zmiennym tym
nadajemy następnie konkretne wartości, które podajemy w trakcie wykonywania instrukcji
int_io.get(nazwa_zmiennej). Liczby te nie mogą zawierać kropki dziesiętnej, dozwolony jest
natomiast znak podkreślenia w roli separatora (5_345_566 jest bardziej czytelne niż 5345566). Na
podanych liczbach wykonujemy działania: + (dodawanie), - (odejmowanie), * (mnożenie), /
(dzielenie), ** (potęgowanie), abs (wartość bezwzględna), rem i mod. Możemy stosować także
jednoargumentowe operatory + i - dające liczbę o określonym znaku. Wynik działań na liczbach typu
integer jest również tego samego typu. Zauważmy, że dzieje się tak nawet w przypadku dzielenia.
Jest to w tym wypadku dzielenie całkowite (np. 5/2 daje wynik 2). Operacje rem i mod dają w
wyniku liczby spełniające odpowiednio równości:
A = (A/B)*B + ( A rem B )
( |A rem B| < |B|, A rem B ma ten sam znak, co A)
oraz
A = B * N + ( A mod B ) dla pewnego N całkowitego
( |A mod B| < |B|, A mod B ma ten sam znak, co B).
Operacja rem (od remainder) to reszta z dzielenia, operacja mod (czyt. modulo) jest podobna. Oto
przykładowe wyniki działań:
i j i rem j i mod j
12 5 2 2
12 -5 2 -3
-12 5 -2 3
-12 -5 -2 -2
Jak widać, jeśli A i B są tych samych znaków, to wyniki operacji rem i mod są takie same (reszta z
dzielenia |A| przez |B|, ze znakiem takim, jak A i B). Różnica występuje dopiero dla argumentów
przeciwnych znaków. Wówczas wynik działania jest różnicą między liczbą A a sąsiadującą z nią
wielokrotnością B, przy czym wielokrotność tę wybieramy dla mod bardziej "na zewnątrz", czyli dalej
od zera, a dla rem - bliżej zera.
W przypadku zapisywania bardziej złożonych wyrażeń kolejność działań (ich priorytet) jest
następująca: najpierw abs i **, następnie *, /, rem i mod, a na końcu + i -. Tworząc wyrażenia
możemy również używać nawiasów "(" i ")" , czyli okrągłych.
Jeżeli potrzebne są nam ułamki, musimy użyć typu float (zmiennoprzecinkowego). Liczby
tego typu posiadają kropkę dziesiętną. (7.1, -12.34, 4.0). Mogą być również zapisywane w
postaci wykładniczej, np. -1.35330E+02 czy 4.23567E-04, co oznacza -135.33 i
0.000423567 (litera E - duża lub mała - i następująca po niej liczba są odpowiednikiem pomnożenia
przez 10 podniesione do potęgi liczba_napisana_po_E ). Na liczbach typu float wykonujemy takie
same działania, jak na liczbach całkowitych. Zabronione jest jednak używanie ich jako wykładnika
potęgi. Napisanie
3.2 ** 1.1
81
spowoduje zgłoszenie błędu (zob. jednak podrozdział Funkcje matematyczne).
W celu wykonywania operacji wejścia/wyjścia na liczbach typu float musimy, podobnie jak w
poprzednim przypadku, skonkretyzować odpowiedni pakiet:
package float_io is new ada.text_io.float_io(float);
lub użyć pakietu ada.float_text_io.
Ada nie pozwala na łączenie w jednym wyrażeniu różnych typów. Napisanie np.
1.2 + 4.0 * 2 + 7
spowoduje zgłoszenie błędu, gdyż mnożymy i sumujemy ze sobą liczby typów float i integer.
Prawidłowy zapis tego wyrażenia ma postać:
1.2 + 4.0 * 2.0 + 7.0
Problem łączenia różnych typów w jednym wyrażeniu możemy rozwiązać również tak, jak w
poniższym programie:
-- program demonstrujacy konwersje typow
-- i konkretyzacje pakietow potrzebnych do operacji
-- wejscia/wyjscia dla liczb typu float i integer
with ada.text_io;
use ada.text_io;
procedure pr5 is
package int_io is new ada.text_io.integer_io(integer);
package flt_io is new ada.text_io.float_io(float);
x,y:integer;
z,wynik:float;
begin
put("Podaj pierwsza liczbe calkowita :");
int_io.get(x);
put("Podaj druga liczbe calkowita :");
int_io.get(y);
new_line;
put("Podaj liczbe rzeczywista :");
flt_io.get(z);
new_line(4);
wynik:=float(x)*float(y)*z;
put("Oto iloczyn tych trzech liczb : ");
flt_io.put(wynik,fore=>4,aft=>5,exp=>0);
end pr5;
Powyższy przykład ilustruje sposób dokonywania konwersji typów - zamiany danych pewnego
typu na inny typ instrukcją postaci
typ_żądany ( wartość_zamieniana )
np.
integer (5.0)
-- daje 5
float (5)
-- daje 5.0
integer (4.1)
-- daje 4
integer(4.6)
-- daje 5
81
Zwróćmy jeszcze uwagę na postać instrukcji put. Dla liczb typu float może on przybrać
postać
put (liczba, fore => ilość_cyfr_przed_kropką,
aft => ilość_cyfr_po_kropce,
exp => ilość_cyfr_w_wykładniku );
Nadanie parametrowi exp wartości 0 powoduje, iż liczby są wyprowadzane nie w postaci
wykładniczej, lecz w "zwykłej", jako ułamek dziesiętny.
Jak sama nazwa wskazuje, zmienna może zmieniać swoją wartość w trakcie wykonywania
programu. Konkretną wartość możemy jej nadać wykonując instrukcję
get (nazwa_zmiennej)
lub stosując instrukcję podstawienia := , tak jak to widzimy w programach pr3 i pr4. Na przykład
w programie pr4 mamy linię
wynik:=float(x)*float(y)*z;
gdzie za pomocą instrukcji := zmiennej wynik nadajemy wartość obliczoną po prawej stronie.
Ponadto jeśli potrzebujemy nadać naszej zmiennej wartość początkową, możemy to uczynić już w
części deklaracyjnej programu, równocześnie z deklaracją zmiennej, pisząc
nazwa_zmiennej : typ := wartość;
czyli np.
x : float := 12.1;.
Oto program przykładowy:
-- przyklad, w ktorym nadajemy zmiennej
-- wartosc poczatkowa
-- obliczenie wagi zaladowanego samochodu
with ada.text_io;
use ada.text_io;
procedure pr6 is
package int_io is new ada.text_io.integer_io(integer);
use int_io;
masa_samochodu:integer:=520;
waga_osob,waga_bagazu:integer;
begin
put("Obliczamy mase zaladowanego 'malucha'...");
new_line(2);
put("Podaj mase pasazerow - zaokraglona
do pelnych kilogramow ");
get(waga_osob);
new_line;
put("Podaj mase wlozonego bagazu,
takze w pelnych kilogramach ");
get(waga_bagazu);
masa_samochodu:=masa_samochodu+waga_osob+waga_bagazu;
new_line(3);
put("Zaladowany 'maluch' wazy ");
put(masa_samochodu,0);
81
put(" kilogramow");
end pr6;
Oprócz zmiennych - zmieniających swoją wartość - możemy deklarować także stałe, które
nie mogą zmieniać swej wartości podczas podczas wykonywania programu. Stałej nadajemy wartość
już w momencie jej deklarowania:
nazwa_stałej : constant typ := wartość_stałej ;
np.
y : constant integer:=12;
Ponieważ, jak wiemy, stała nie zmienia swojej wartości podczas działania programu, nie może
występować z lewej strony instrukcji podstawienia. Użycie stałej ilustruje następujący przykład:
-- program demonstrujacy uzycie stalych
with ada.text_io;
use ada.text_io;
procedure pr7 is
package flt_io is new ada.text_io.float_io(float);
use flt_io;
procent_podatku : constant float:=0.21;
procent_premii : constant float:=0.20;
premia,placa,podatek,do_wyplaty:float;
begin
put("Podaj place zasadnicza brutto : ");
get(placa);
new_line(3);
put_line(" WYPLATA");
new_line;
premia:=placa*procent_premii;
podatek:=procent_podatku*(placa+premia);
do_wyplaty:=placa+premia-podatek;
put(" placa zasadnicza : ");
put(placa,fore=>5,aft=>2,exp=>0);
new_line;
put(" premia : ");
put(premia,fore=>5,aft=>2,exp=>0);
new_line;
put(" potracony podatek : ");
put(podatek,fore=>5,aft=>2,exp=>0);
new_line;
new_line;
put(" do wyplaty : ");
put(do_wyplaty,fore=>5,aft=>2,exp=>0);
end pr7;
Można by postawić pytanie, po co deklarować stałe, jeżeli można po prostu w odpowiednim miejscu
w programie pomnożyć przez 0.21 czy 0.20 - i już. Oczywiście - można, jednak stosowanie stałych
ułatwia na przykład wprowadzanie zmian w razie konieczności modyfikacji programu - przerabiamy
wtedy tylko część deklaracyjną, nie musząc przeglądać całej treści.
Funkcje matematyczne
Wykonywanie działań na liczbach wiąże się często z koniecznością użycia różnego rodzaju
funkcji matematycznych, jak na przykład pierwiastek, logarytm czy funkcje trygonometryczne. Dostęp
81
do nich uzyskujemy poprzez konkretyzację pakietu rodzajowego ada.numerics.generic_
elementary_functions dla typu liczbowego, dla którego będziemy używać tych funkcji.
Konkretyzacja
dla
typu
float
jest
już
"gotowa"
-
jest
to
pakiet
ada.numerics.elementary_functions. Zdefiniowane są w nim następujące funkcje:
sqrt(x) (pierwiastek kwadratowy), log(x) (logarytm naturalny x), log(x,p) (logarytm x przy
podstawie p), exp(x) (e do potęgi x), funkcje trygonometryczne - sin, cos, tan, cot, a także
arcsin, arccos, arctan, arccot, sinh, cosh, tanh, coth, arcsinh, arccosh,
arctanh, arccoth - wszystkie operujące na argumentach typu float, oraz potęga o podstawie i
wykładniku typu float. W jego pakiecie rodzicielskim - ada.numerics zadeklarowane są ponadto
dwie stałe - e i pi. Oto przykładowe programy:
-- uzycie funkcji matematycznych
--
-- pierwszy program z tej serii
--
-- w trojkacie o podanych trzech bokach
-- obliczamy wysokosc
with ada.text_io,ada.numerics.generic_elementary_functions;
use ada.text_io;
procedure pr8 is
package flt_io is new ada.text_io.float_io(float);
use flt_io;
package fun_mat is new
ada.numerics.generic_elementary_functions(float);
a,b,c,h,p,pole:float;
begin
put_line("Podaj boki trojkata : ");
put("a : ");get(a);new_line;
put("b : ");get(b);new_line;
put("c : ");get(c);new_line(2);
put_line(" UWAGA !!! ");
put("Nie sprawdzam, czy taki trojkat moze w ogole istniec!");
new_line(3);
-- obliczamy pole trojkata za wzoru Herona
p:=(a+b+c)/2.0;
pole:=fun_mat.sqrt(p*(p-a)*(p-b)*(p-c));
-- wykorzystujemy obliczone pole do znalezienia wysokosci
put("wysokosc opuszczona na bok a : ");
h:=2.0*pole/a;
put(h,5,3,0);
new_line;
put("wysokosc opuszczona na bok b : ");
h:=2.0*pole/b;
put(h,5,3,0);
new_line;
put("wysokosc opuszczona na bok c : ");
h:=2.0*pole/c;
put(h,5,3,0);
end pr8;
Kolejny przykład:
-- uzycie funkcji matematycznych
--
-- drugi program z tej serii
81
--
-- podajemy przyprostokatne trojkata
-- obliczamy dlugosc przeciwprostokatnej
with ada.text_io,ada.numerics.generic_elementary_functions;
use ada.text_io;
procedure pr9 is
package flt_io is new ada.text_io.float_io(float);
use flt_io;
package fun_mat is new
ada.numerics.generic_elementary_functions(float);
a,b,c : float;
begin
put_line("Podaj dlugosci przyprostokatnych trojkata : ");
put("a : ");get(a);new_line;
put("b : ");get(b);new_line;
new_line(3);
c:=fun_mat.sqrt(a**2+b**2);
put("przeciwprostokatna c : ");
put(c,5,3,0);
end pr9;
I jeszcze jeden przyklad - tym razem funkcje trygonometryczne:
-- Program sprawdzajacy, czy tzw. jedynka trygonometryczna
-- rzeczywiscie daje 1.
-- Pojawiaja sie tu funkcje matematyczne, a wiec
-- konkretyzacja pakietu rodzajowego
-- ada.numerics.generic_elementary_functions
-- oraz typ float.
-- Trzeci program z serii "uzycie funkcji matematycznych".
with text_io,ada.numerics.generic_elementary_functions;
use text_io;
procedure pr10 is
package fun_mat is new
ada.numerics.generic_elementary_functions(float);
use fun_mat;
package flt_io is new text_io.float_io(float);
use flt_io;
x,jedynka:float;
begin
put_line("Podaj dowolna liczbe rzeczywista x");
put_line("obliczymy sin(x)^2 + cos(x)^2 ");
put("x = ");
get(x);
jedynka:=sin(x)**2+cos(x)**2;
new_line(3);
put_line("obliczamy tzw. jedynke trygonometryczna.....");
put("otrzymalismy ");put(jedynka,exp=>0);
new_line;
set_col(10);
put("....no i co Ty na to?...");
81
end pr10;
W programie pr10 pojawiła się po raz pierwszy instrukcja
set_col(n);
powodująca ustawienie kursora w kolumnie o podanym numerze. Stanowi ona część pakietu
ada.text_io.
Czwarty przykład dotyczący funkcji matematycznych ilustruje użycie stałych zadeklarowanych
w pakiecie ada.numerics:
-- program z uzyciem stalych ada.numerics.pi
-- i ada.numerics.e.
-- Uzywamy pakietow ada.numerics.elementary_functions
-- oraz ada.float_text_io zamiast konkretyzacji
-- pakietow rodzajowych w czesci deklaracyjnej programu.
--
-- Czwarty program z serii funkcje matematyczne
with ada.text_io,ada.numerics,ada.numerics.elementary_functions;
with ada.float_text_io;
use ada.text_io,ada.float_text_io;
use ada.numerics,ada.numerics.elementary_functions;
procedure pr11 is
begin
new_line(2);
put("liczba pi wynosi ");
put(ada.numerics.pi);
new_line;
put("liczba e wynosi ");
put(ada.numerics.e);
new_line;
put("logarytm naturalny liczby e : ");
put(log(e));
new_line;
put("logarytm naturalny liczby 10 : ");
put(log(10.0));
new_line;
put("logarytm przy podstawie 10 liczby 10 : " );
put(log(10.0,10.0));
new_line;
put("2 ^ 3.1 = ");
put(2.0**3.1);
end pr11;
Typy liczbowe i ich zakresy
Dotychczas zapoznaliśmy się z typami integer i float. Nie są to jedyne predefiniowane
typy liczbowe. Dostępne są na przykład typy natural i positive (są to właściwie podtypy typu
a), a także (w większości implementacji) typy całkowite i zmiennoprzecinkowe o szerszych bądź
węższych zakresach - short_short_integer, short_integer, long_integer,
long_long_integer, short_float, long_float i long_long_ float. Jakie są ich
zakresy? Do tej pory nie znamy zresztą również zakresów typów integer i float. śądaną
informację uzyskamy wykonując poniższy program:
-- program przedstawia typy liczbowe (podstawowe)
-- oraz ich zakresy
-- pojawiaja sie atrybuty typow
with ada.text_io;
81
use ada.text_io;
procedure pr12 is
package flo_io is new ada.text_io.float_io(long_float);
package fl_io is new ada.text_io.float_io(float);
package floa_io is new ada.text_io.float_io(long_long_float);
package f_io is new ada.text_io.float_io(short_float);
package int_io is new ada.text_io.integer_io(integer);
package inte_io is new ada.text_io.integer_io(long_integer);
package integ_io is new
ada.text_io.integer_io(long_long_integer);
package in_io is new ada.text_io.integer_io(short_integer);
package i_io is new
ada.text_io.integer_io(short_short_integer);
begin
put_line("Mamy nastepujace typy liczbowe : ");
set_col(5);
put_line("1. short_float - typ zmiennoprzecinkowy
o zakresie : ");
set_col(20);
f_io.put(short_float'first);
put(" .. ");f_io.put(short_float'last);
new_line;
set_col(5);
put_line("2. float - typ zmiennoprzecinkowy o zakresie : ");
set_col(20);
fl_io.put(float'first);put(" .. ");fl_io.put(float'last);
new_line;
set_col(5);
put_line("3. long_float - typ zmiennoprzecinkowy
o zakresie : ");
set_col(20);
flo_io.put(long_float'first);
put(" .. ");flo_io.put(long_float'last);
new_line;
set_col(5);
put_line("4. long_long_float - typ zmiennoprzecinkowy
o zakresie : ");
set_col(20);
floa_io.put(long_long_float'first);
put(" .. ");floa_io.put(long_long_float'last);
new_line;
set_col(5);
put_line("5. short_short_integer - typ calkowity
o zakresie : ");
set_col(20);
i_io.put(short_short_integer'first);
put(" .. ");i_io.put(short_short_integer'last);
new_line;
set_col(5);
put_line("6. short_integer - typ calkowity o zakresie : ");
set_col(20);
in_io.put(short_integer'first);
put(" .. ");in_io.put(short_integer'last);
new_line;
set_col(5);
81
put_line("7. integer - typ calkowity o zakresie : ");
set_col(20);
int_io.put(integer'first);
put(" .. ");int_io.put(integer'last);
new_line;
set_col(5);
put_line("8. long_integer - typ calkowity o zakresie : ");
set_col(20);
inte_io.put(long_integer'first);
put(" .. ");inte_io.put(long_integer'last);
new_line;
set_col(5);
put_line("9. long_long_integer - typ calkowity
o zakresie : ");
set_col(20);
integ_io.put(long_long_integer'first);
put(" .. ");integ_io.put(long_long_integer'last);
new_line;
put_line("oraz nastepujace podtypy typu integer :");
set_col(5);
put("1. positive - typ calkowity o zakresie : ");
int_io.put(positive'first);
put(" .. ");int_io.put(positive'last);
new_line;
set_col(5);
put("2. natural - typ calkowity o zakresie : ");
int_io.put(natural'first);
put(" .. ");int_io.put(natural'last);
new_line;
end pr12;
W celu otrzymania najmniejszej i największej liczby danego typu użyliśmy tzw. atrybutów
typów - typ'first i typ'last wypisujących pierwszy (najmniejszy) i ostatni (największy)
element należący do danego typu. Jak widać, podtyp positive obejmuje liczby od 1 do
integer'last, a natural - od 0 do integer'last. Istnieją jeszcze inne atrybuty typów, które
poznamy nieco później. Zauważmy ponadto, że w celu wyprowadzania liczb dokonaliśmy w części
deklaracyjnej programu dużej ilości konkretyzacji. Dla typów całkowitych (dla każdego oddzielnie)
konkretyzujemy pakiet ada.text_io.integer_io, a dla typów zmiennoprzecinkowych -
ada.text_io.float_io. Zauważmy też, że positive i natural nie wymagały odrębnego pakietu,
zadowalając się pakietem przeznaczonym dla ich typu bazowego - integer.
Instrukcja warunkowa
Podczas pisania programu musimy czasami uzależnić sposób jego działania od pewnych
okoliczności. Służy do tego tzw. instrukcja warunkowa o składni:
[1] if warunek then
instrukcje
end if;
[2]. if warunek then
instrukcje_1
else
instrukcje_2
81
end if;
[3]. if warunek_1 then
instrukcje_1
elsif warunek_2 then
instrukcje_2
elsif warunek_3 then
instrukcje_3
........
elsif warunek_n then
instrukcje_n
else
instrukcje_dalsze
end if;
Instrukcja if w przypadku [1] powoduje, iż razie spełnienia warunku wykonywany jest ciąg
poleceń wewnątrz tej instrukcji, a więc przed słowami end if. W przeciwnym przypadku instrukcje
te są pomijane. Polecenie [2] powoduje, że w sytuacji, gdy warunek jest spełniony, wykonywane są
instrukcje_1, a gdy nie jest - instrukcje_2. Przypadek [3] jest jak gdyby połączeniem wielu
instrukcji typu [1] i instrukcji [2]. Stanowi uproszczoną wersję ciągu instrukcji
if warunek_1 then
instrukcje_1
else
if warunek_2 then
instrukcje_2
else
if warunek_3 then
instrukcje_3
else
........
if warunek_n then
instrukcje_n
else
instrukcje_dalsze
end if;
........
end if;
end if;
end if;
Jeżeli zachodzi warunek_1 - wykonują sie instrukcje_1, jeśli nie - sprawdzany jest
warunek_2. Jeśli zachodzi, wykonywane wykonywane są instrukcje_2, jeśli nie - sprawdzany
jest kolejny warunek itd. Jeśli nie zachodzi żaden z warunków, wykonywane są instrukcje następujące
po else.
Oto przykłady zastosowania poznanych instrukcji:
-- wykorzystanie instrukcji warunkowej
-- program przykladowy
-- podajemy dwie liczby
-- wyprowadzany jest ich iloczyn i iloraz, jezeli
-- mozna go obliczyc
with ada.text_io;
use ada.text_io;
procedure pr13 is
package flt_io is new ada.text_io.float_io(float);
use flt_io;
a,b:float;
81
begin
put("Podaj dowolna liczbe - a = ");
get(a);
put("Podaj nastepna liczbe - b = ");
get(b);
new_line(2);
put("a*b = ");put(a*b,exp=>0);
new_line;
if b/=0.0 then
put("a/b = ");put(a/b,exp=>0);
end if;
end pr13;
I kolejny przykład:
-- kolejny przyklad programu z wykorzystaniem
-- instrukcji warunkowej
-- rozwiazywanie rownania kwadratowego
with ada.text_io,ada.numerics.generic_elementary_functions;
use ada.text_io;
procedure pr14 is
package flt_io is new ada.text_io.float_io(float);
package fun_mat is new
ada.numerics.generic_elementary_functions(float);
use flt_io,fun_mat;
a,b,c,delt : float;
begin
put_line("Rownanie kwadratowe ma postac a*x^2 + bx + c = 0");
new_line;
put_line("Podaj wspolczynniki : ");
put("a = "); get(a);
put("b = "); get(b);
put("c = "); get(c);
new_line(2);
if a=0.0 then
put_line("To nie jest rownanie kwadratowe !!!");
if b/=0.0 then
put("Rozwiazanie podanego rownania
liniowego : x = ");
put(-c/b,exp=>0);
elsif b=0.0 and c/=0.0 then
put("Podane rownanie jest sprzeczne");
else
put("Podane rownanie jest tozsamosciowe");
end if;
else
delt:=b**2-4.0*a*c;
if delt<0.0 then
put("Podane rownanie nie ma pierwiastkow
rzeczywistych");
elsif delt=0.0 then
put("Rownanie ma jeden pierwiastek
rzeczywisty x = ");
put(-b/(2.0*a),exp=>0);
else
put_line("Rownanie ma dwa pierwiastki
rzeczywiste");
81
set_col(5);
put("x1 = ");
put((-b-sqrt(delt))/(2.0*a),exp=>0);
new_line;
set_col(5);
put("x2 = ");
put((-b+sqrt(delt))/(2.0*a),exp=>0);
end if;
end if;
end pr14;
Operatory logiczne. Kolejność działań.
Konstruowanie warunków logicznych wymaga odpowiednich operatorów. Są to na przykład <,
>, =, <= (mniejsze lub równe), >= (większe lub równe), /= (różne) oraz operatory logiczne - not
(zaprzeczenie), A (koniunkcja - "i"), or (alternatywa - "lub"), xor (tzw. exclusive or - "albo"), and
then i or else. Niektóre z nich widzieliśmy juz w zamieszczonych powyżej programach. Niech W1
i W2 oznaczają warunki. Wówczas
W1 and W2 daje prawdę, gdy zachodzą oba warunki
W1 or W2
daje prawdę, gdy zachodzi przynajmniej jeden z warunków
W1 xor W2
daje prawdę, gdy zachodzi dokładnie jeden z warunków
not W1
daje prawdę, gdy warunek W1 nie zachodzi.
Operatory and then i or else podobne są w swoim działaniu do and i or, z tą jednak
różnicą, że w poprzednich przypadkach obliczana była zawsze wartość logiczna obu warunków.
Natomiast napisanie
W1 and then W2
powoduje obliczenie wartości W2 tylko wtedy, gdy W1 zachodzi, zaś
W1 or else W2
powoduje obliczenie wartości W2 tylko wtedy, gdy W1 nie zachodzi.
Operatory te są niejednokrotnie bardzo użyteczne. Jako przykład posłużyć może poniższa
sytuacja:
Napisanie
a/=0 and b/a>c
spowoduje zgłoszenie błędu podczas wykonywania programu, jeżeli podamy a=0, ponieważ
sprawdzona będzie wartość logiczna warunku b/a>c, a przez 0 dzielić nie wolno. Natomiast
a/=0 and then b/a>c
spowoduje obliczenie wartości logicznej warunku b/a>c tylko wtedy, gdy a/=0, a więc błąd podczas
dzielenia nie wystąpi.
Działania - logiczne i matematyczne - wykonywane są w następującej kolejności:
**
abs not
* /
rem mod
+
-
(jednoargumentowe)
+
-
(dwuargumentowe)
=
/=
<
<=
>
>=
and or
xor
81
(operatory wymienione na początku, a więc **, abs i not maja najwyższy priorytet).
Z konstruowaniem warunków logicznych związany jest także test członkostwa - sprawdzenie,
czy dana wartość należy do podanego zakresu. Możemy oczywiście dokonać tego za pomocą
poznanych dotychczas operatorów, ale możemy też użyć słowa kluczowego in. Zamiast sprawdzać np.
warunek
liczba >= 2 and liczba <=8
możemy napisać po prostu
liczba in 2..8
a zamiast
liczba < 2 or liczba > 8
liczba not in 2..8
Typ boolean
Z zagadnieniem prawdy i fałszu związany jest także typ logiczny - boolean, przyjmujący dwie
wartości - false i true. W celu wprowadzania i wyprowadzania wartości tego typu musimy znów
skonkretyzowac odpowiedni pakiet - tym razem ada.text_io.enumeration_io:
package boolean_io is new ada.text_io.enumeration_io(boolean);
Oto przykład zastowania:
-- tabelka logiczna
-- przyklad uzycia zmiennych logicznych
-- a przy okazji - opis dzialania operatorow
with ada.text_io;
use ada.text_io;
procedure pr15 is
package b_io is new ada.text_io.enumeration_io(boolean);
p,q:boolean;
begin
put_line(" TABELKA WARTOSCI LOGICZNYCH");
new_line(3);
put(" | p | q | not p | p and q | p or q |
p xor q | ");
new_line;
p:=true;
q:=true;
put(" | ");b_io.put(p,width=>5);
put(" | ");b_io.put(q,width=>5);
put(" | ");b_io.put(not p,width=>7);
put(" | ");b_io.put(p and q,width=>7);
put(" | ");b_io.put(p or q,width=>6);
put(" | ");b_io.put(p xor q,width=>7);
put(" |");
new_line;
q:=false;
put(" | ");b_io.put(p,width=>5);
81
put(" | ");b_io.put(q,width=>5);
put(" | ");b_io.put(not p,width=>7);
put(" | ");b_io.put(p and q,width=>7);
put(" | ");b_io.put(p or q,width=>6);
put(" | ");b_io.put(p xor q,width=>7);
put(" |");
new_line;
p:=false;
q:=true;
put(" | ");b_io.put(p,width=>5);
put(" | ");b_io.put(q,width=>5);
put(" | ");b_io.put(not p,width=>7);
put(" | ");b_io.put(p and q,width=>7);
put(" | ");b_io.put(p or q,width=>6);
put(" | ");b_io.put(p xor q,width=>7);
put(" |");
new_line;
q:=false;
put(" | ");b_io.put(p,width=>5);
put(" | ");b_io.put(q,width=>5);
put(" | ");b_io.put(not p,width=>7);
put(" | ");b_io.put(p and q,width=>7);
put(" | ");b_io.put(p or q,width=>6);
put(" | ");b_io.put(p xor q,width=>7);
put(" |");
new_line;
end pr15;
Wynikiem wykonania powyższego programu będzie następująca tabelka:
TABELKA WARTOSCI LOGICZNYCH
Typy znakowe
Kolejnymi typami - już nie liczbowymi, lecz znakowymi - są character zawierający znaki ze
zbioru znaków o nazwie ISO 8859-1, czyli latin1, oraz wide_character ze znakami ze zbioru
ISO 10646 BMP. Typ character zawiera 256 znaków - drukowalnych i niedrukowalnych , zaś
wide_character - 65536 znaków. 256 początkowych znaków wide_character jest takie samo,
jak w typie character. Znaki drukowalne obu typów ujmujemy w apostrofy (np. 'a', '$',
'+'). Możliwość używania znaków niedrukowalnych (kontrolnych) należących do typu character,
takich
jak
znak
końca
linii
czy
dźwięk,
zapewnia
nam
pakiet
o
nazwie
ada.characters.latin_1. Poszczególne znaki należące do tego typu otrzymały tam swoje
nazwy (zostały zadeklarowane jako stałe), i przez te nazwy możemy się do nich odwoływać. Oto
fragmenty tego pakietu:
------------------------
-- Control Characters --
------------------------
NUL : constant Character := Character'Val (0);
SOH : constant Character := Character'Val (1);
p
q
not p
p and q
p or q
p xor q
TRUE
TRUE
FALSE
TRUE
TRUE
FALSE
TRUE
FALSE
FALSE
FALSE
TRUE|
TRUE
FALSE
TRUE
TRUE
FALSE
TRUE
TRUE|
FALSE
FALSE
TRUE
FALSE
FALSE
FALSE
81
STX : constant Character := Character'Val (2);
ETX : constant Character := Character'Val (3);
EOT : constant Character := Character'Val (4);
ENQ : constant Character := Character'Val (5);
ACK : constant Character := Character'Val (6);
BEL : constant Character := Character'Val (7);
BS : constant Character := Character'Val (8);
HT : constant Character := Character'Val (9);
LF : constant Character := Character'Val (10);
VT : constant Character := Character'Val (11);
FF : constant Character := Character'Val (12);
CR : constant Character := Character'Val (13);
SO : constant Character := Character'Val (14);
SI : constant Character := Character'Val (15);
(....)
Space : constant Character := ' '; -- Character'Val(32)
Exclamation : constant Character := '!'; -- Character'Val(33)
Quotation : constant Character := '"'; -- Character'Val(34)
Number_Sign : constant Character := '#'; -- Character'Val(35)
Dollar_Sign : constant Character := '$'; -- Character'Val(36)
Percent_Sign : constant Character := '%'; -- Character'Val(37)
Ampersand : constant Character := '&'; -- Character'Val(38)
Apostrophe : constant Character := '''; -- Character'Val(39)
Left_Parenthesis : constant Character := '('; -- Character'Val(40)
Right_Parenthesis : constant Character := ')'; -- Character'Val(41)
Asterisk : constant Character := '*'; -- Character'Val(42)
Plus_Sign : constant Character := '+'; -- Character'Val(43)
i tak dalej. Znaku o nazwie BEL powodującego dźwięk możemy więc używać pisząc w programie
ada.characters.latin_1.bel, tak jak to widać niżej:
-- program demonstrujacy uzycie znakow ASCII
with ada.text_io,ada.characters.latin_1;
use ada.text_io;
procedure pr16 is
znak:character:='d';
begin
new_line(7);
set_col(25);
-- tak wyprowadzamy znaki kontrolne ...
put("!!! Pora na kolacje !!!");
put(ada.characters.latin_1.bel);
new_line(2);
-- ... a tak znaki drukowalne
put(ada.characters.latin_1.asterisk);
new_line;
put('a');
new_line;
put(znak);
new_line;
put('a');
81
end pr16;
Oczywiście do każdego ze znaków można byłoby się odwołać tak, jak to widzieliśmy w pakiecie
ada.characters.latin_1, pisząc character'val(n), ale jest to sposób nieco mniej
naturalny. Co oznacza owo tajemnicze val, dowiemy się już wkrótce. Na razie zauważmy jeszcze, że za
wyprowadzanie znaków typu character odpowiada pakiet ada.text_io. Na przykład do
wyprowadzenia znaku używana jest procedura put. Nie jest to jednak to samo put, którego
uzywaliśmy dotychczas - pakiet text_io zawiera odrębne procedury dla pojedynczych znaków i dla
ich ciągów. Nie możemy na przykład używać dla znaków procedury put_line - istnieje tylko dla
ciągu znaków, natomiast dla pojedynczego znaku nie jest zdefiniowana. Za wyprowadzanie znaków
typu wide_character odpowiedzialny jest z kolei pakiet ada.wide_text_io. Znaki typu
wide_character nie mogą pojawiać się w identyfikatorach, czyli w nazwach procedur, zmiennych i
stałych, natomiast mogą pojawiać się w wyprowadzanych tekstach i komentarzach.
Typ wyliczeniowy
Character, wide_character i boolean są przykładami predefiniowanych typów
wyliczeniowych. Typ wyliczeniowy to - jak sama nazwa wskazuje - taki typ, którego elementy można
wyliczyć. Jego elementy ustawione są w określonej kolejności, można więc znaleźć element pierwszy
(najmniejszy), ostatni (największy), a dla danego elementu - poprzedzający go i następny po nim, o ile
takie istnieją. Zdefiniowana jest także relacja mniejszości - element mniejszy to taki, który stoi
wcześniej w deklaracji typu. Tak na przykład typ boolean określony jest następująco:
type boolean is (false, true);
więc mamy false<true. (Dokładniej mówiąc, elementy typu wyliczeniowego możemy porównywać
na wszelkie możliwe sposoby, a więc używać operatorów >, <, <=, >=, =, /=).
Typy wyliczeniowe możemy definiować sami, pisząc w części deklaracyjnej programu
type nazwa_typu is (element_1, ..., element_n);
Elementy typu wyliczeniowego nazywamy literałami wyliczeniowymi. Mogą być nimi identyfikatory
lub znaki (ujęte w apostrofy) np.
-- identyfikatory
type dni is (pon, wt, sr, czw, pt, so, nie);
-- znaki
type liczby_rzymskie is ( 'I', 'V', 'X', 'L', 'C', 'D', 'M');
-- znaki i identyfikatory
type xxx is (r2, 'a', xxxd12);
W celu wprowadzania i wyprowadzania wartości typu wyliczeniowego musimy skonkretyzować
pakiet ada.text_io.enumeration_io dla danego typu, np.
package dni_io is new ada.text_io.enumeration_io(dni);
package rzymskie_io is new
ada.text_io.enumeration_io(liczby_rzymskie);
Zauważmy, że konkretyzacji trzeba dokonać nawet wtedy, gdy elementami typu wyliczeniowego są
same znaki (wyprowadzane i wprowadzane są wtedy znaki ujęte w apostrofy).
Oto przykład deklaracji i użycia takiego typu:
-- przyklad uzycia typow wyliczeniowych
-- wprowadzanie i wyprowadzanie elementow tego typu
with ada.text_io;
use ada.text_io;
81
procedure pr17 is
type pierwiastki is (H, He, Li, Be, B, C, N, O, F, Ne,
Na, Mg, Al);
-- pierwiastki sa uporzadkowane rosnaco wg mas atomowych
package chemia_io is new
ada.text_io.enumeration_io(pierwiastki);
p1,p2:pierwiastki;
begin
put_line("Nasza lista obejmuje nastepujace pierwiastki
chemiczne :");
put_line("Al, B, Be, C, F, H, He, Li, N, Na, Ne, Mg, O");
new_line;
put("podaj nazwy dwoch pierwiastkow, a dowiesz sie, ktory ma
wieksza mase atomowa");
new_line;
put(" 1 : ");chemia_io.get(p1);
put(" 2 : ");chemia_io.get(p2);
if p1>p2 then
chemia_io.put(p1);
put(" ma wieksza mase atomowa niz ");chemia_io.put(p2);
elsif p1<p2 then
chemia_io.put(p1);
put(" ma mniejsza mase atomowa niz");chemia_io.put(p2);
else
put("podane pierwiastki maja rowne masy atomowe");
end if;
end pr17;
Wykonując program zobaczymy, że symbole pierwiastków wyprowadzane są trochę
nieprawidłowo, bo wielkimi literami. W wypadku literałów wyliczeniowych Ada, podobnie jak w
wypadku identyfikatorów, nie rozróżnia rozmiarów liter. Za sposób wyprowadzania natomiast
odpowiada parametr procedury chemia_io.put (tzn. procedury put dla konkretnego typu
wyliczeniowego) o nazwie Set. Może on przyjmować dwie wartości - Upper_Case (ustawioną jako
domyślną - wtedy elementy typu wyprowadzane są wielkimi literami) i Lower_Case. Procedura put
ma jeszcze jeden parametr - width, odpowiadający za szerokość wyprowadzanego tekstu, lecz
ponieważ ma on domyślnie wartość 0, więc wyprowadzany element typu zajmuje tylko tyle miejsca, ile
musi.
Podtypy
Jeżeli mówimy o definiowaniu własnych typów, wypada wspomnieć także o definiowaniu
podtypów. Podtyp nie jest nowym typem, stanowi jedynie podzbiór wartości pewnego już znanego typu
(nazywanego tutaj typem bazowym). Definiujemy go pisząc w części deklaracyjnej programu
subtype nazwa_podtypu is typ_bazowy range
ograniczenie_dolne .. ograniczenie_górne;
gdzie ograniczenie_dolne i ograniczenie_górne należą do typu bazowego, np.
subtype dni_robocze is dni range pon .. pt;
subtype wiek_ludzki is integer range 0..120;
Granice zakresu mogą być także obliczane:
granica_gorna:=12*5+3;
81
subtype ograniczony_integer is
integer range 1 .. granica_gorna;
n:integer:=4;
subtype wiekszy_integer is integer range 1 .. 12*n+3;
Podanie jako element_k lub element_n elementów spoza zakresu typu bazowego spowoduje
podczas wykonywania programu zgłoszenie błędu constraint_error - przekroczenia zakresu.
Można oczywiście definiować podtypy tak, aby obejmowały cały zakres typu bazowego, np.
subtype calkowite is integer;
co powoduje, że calkowite jest właściwie inną nazwą typu integer; a także podtypy będące
podtypami pewnego podtypu, np.
subtype dni_robocze is dni range pon .. pt;
subtype srodek_tygodnia is dni_robocze range wt .. czw;
Podtyp nie jest nowym typem, a więc dziedziczy właściwości swojego typu bazowego. I tak na
przykład
jeżeli
odpowiednią
konkretyzacją
pakietu
ada.text_io.enumeration_io
zapewniliśmy wykonywanie operacji wejścia/wyjścia dla elementów typu dni, to nie musimy już
dokonywac kolejnej konkretyzacji dla typów dni_robocze ani srodek_tygodnia - odpowiadać
za to będzie ten sam pakiet, co w przypadku typu dni. Jak wiadomo, nie wolno w jednym wyrażeniu
łączyć ze sobą elementów należących do różnych typów. Jednak łączenie w jednym wyrażeniu
elementów należących do różnych podtypów tego samego typu bazowego jest dozwolone.
Nazw podtypów możemy używać zamiast zakresów w teście członkostwa. Zamiast pisać np. w
instrukcji warunkowej
wiek in 0..120
możemy napisać
wiek in wiek_ludzki.
Oprócz poznanej definicji podtypu możemy definiować podtypy w sposób niejawny. Nie mają
one wówczas nazwy, stanowia jedynie zawężenie zakresu pewnego typu. Dokonujemy tego
równocześnie z deklaracją zmiennej, np.
wiek_dziecka: integer range 0..18;
Zmienna o nazwie wiek_dziecka może przyjmować wartości typu integer, ale z określonego
przedziału - od 0 do 18. Napisanie np.
wiek_dziecka:=19;
spowoduje zgłoszenie błędu constraint_error.
W Adzie istnieją predefiniowane podtypy typu integer - positive i natural. Określone
są one następująco:
subtype positive is integer range 1..integer'last;
subtype positive is integer tange 0..integrer'last;
Wspominaliśmy już o nich przy okazji omawiania zakresów typów liczbowych.
A oto przykładowy program:
-- program przedstawiajacy definiowanie podtypow
81
with ada.text_io;
use ada.text_io;
procedure pr18 is
type szczyty_tatrzanskie is ( Bobrowiec, Giewont, Kasprowy,
Wolowiec, Bystra, Swinica, Krywan,
Rysy, Lomnica, Gerlach);
subtype Tatry_Zachodnie is szczyty_tatrzanskie
range Bobrowiec .. Bystra;
subtype Tatry_Wysokie is szczyty_tatrzanskie
range Swinica .. Gerlach ;
package szczyty_io is new
ada.text_io.enumeration_io (szczyty_tatrzanskie);
use szczyty_io;
package int_io is new ada.text_io.integer_io(integer);
use int_io;
najwyzszy_Tatry: constant szczyty_tatrzanskie:=Gerlach;
najwyzszy_Polska: constant szczyty_tatrzanskie:=Rysy;
najwyzszy_Tatry_Wysokie:constant Tatry_Wysokie
:=najwyzszy_Tatry;
najwyzszy_Tatry_Zachodnie: constant Tatry_Zachodnie :=Bystra;
gora1,gora2:szczyty_tatrzanskie;
punkty:integer range 0..10;
begin
punkty:=0;
set_col(20);
put_line("*** SZCZYTY TATRZANSKIE ***");
new_line;
set_col(5);
put_line("Bobrowiec, Bystra, Gerlach, Giewont,
Kasprowy, Krywan, ");
set_col(15);
put_line("Lomnica, Rysy, Swinica, Wolowiec");
new_line;
put_line("Test znajomosci Tatr - mozesz podawac tylko
powyzsze szczyty");
put("Podaj nazwe najwyzszego szczytu Tatr : ");
get(gora1);
if gora1=najwyzszy_Tatry then punkty:=punkty+1;end if;
put("Podaj nazwe najwyzszego szczytu w Polsce : ");
get(gora1);
if gora1=najwyzszy_Polska then punkty:=punkty+1;end if;
put_line("Podaj nazwy dwoch szczytow w Tatrach Zachodnich
- najpierw nizszy ");
put(" 1 : ");get(gora1);
put(" 2 : ");get(gora2);
if gora1 in Tatry_Zachodnie then punkty:=punkty+1;end if;
if gora2 in Tatry_Zachodnie'first..Tatry_Zachodnie'last then
punkty:=punkty+1;
end if;
if gora1<gora2 then punkty:=punkty+1;end if;
put_line("Podaj nazwy dwoch szczytow w Tatrach Wysokich
- najpierw nizszy ");
81
put(" 1 : ");get(gora1);
put(" 2 : ");get(gora2);
if gora1 in Tatry_Wysokie then punkty:=punkty+1;end if;
if gora2 in Tatry_Wysokie then punkty:=punkty+1;end if;
if gora1<gora2 then punkty:=punkty+1;end if;
put("Podaj nazwe najwyzszego szczytu Tatr Zachodnich : ");
get(gora1);
if gora1=najwyzszy_Tatry_Zachodnie then punkty:=punkty+1;
end if;
put("Podaj nazwe najwyzszego szczytu Tatr Wysokich : ");
get(gora1);
if gora1=najwyzszy_Tatry_Wysokie then punkty:=punkty+1;end if;
new_line;
put(ascii.bel);
if punkty>7 then
put_line("Gratulacje - Tatry znasz bardzo dobrze...
albo dobrze czytasz mapy");
put("Zdobyles ");put(punkty,width=>0);put(" pkt");
else
put("Nie znasz geografii Tatr - przykro mi ...");
put("Zdobyles tylko ");put(punkty,width=>0);put(" pkt");
end if;
end pr18;
Definiowanie nowych typów liczbowych
Ada oferuje możliwość definiowania własnych, nowych typów liczbowych, zarówno
całkowitych, jak i zmiennoprzecinkowych (rzeczywistych). Typ całkowity definiujemy poleceniem
postaci
type typ_całkowity is range
ograniczenie_dolne .. ograniczenie_górne;
zaś typ zmiennoprzecinkowy poleceniem
type typ_zmiennoprzecinkowy is digits ilość_cyfr_znaczących;
lub
type typ_zmiennoprzecinkowy is digits ilość_cyfr_znaczących
range ograniczenie_dolne .. ograniczenie_górne;
gdzie ograniczenie_dolne i ograniczenie_górne są odpowiednio liczbami całkowitymi
lub zmiennoprzecinkowymi. Ilość_cyfr_znaczących stanowi ograniczenie dokładności. Na
przykład w typie zdefiniowanym
type do_trzech is digits 3;
liczby 2.123 i 2.124 są nierozróżnialne - widziane jako 2.12 (2.123 i 2.128 będą jednak rozróżniane ze
względu na zaokrąglenie). Zauważmy, że wszystkie z poniższych literałów rzeczywistych mają po trzy
cyfry znaczące:
3.11 2.22E8 0.0000456
12.6E-8
7650000.0
Oto przykłady definicji typów liczbowych:
type male_calkowite is range -10 .. 10;
81
type sekundy_po_polnocy is range 0..86400;
type do_trzech is digits 3;
type male_do_trzech is digits 3 range 0.0 .. 2.0;
Polecenie definiujące nowy typ jest nieco podobne do deklaracji podtypu (zauważmy, że nie
określamy tu typu bazowego), powoduje jednak zupełnie inne skutki. W wyniku jego wykonania
tworzony jest nowy typ (male_calkowite, sekundy_po_polnocy, do_trzech). Nie możemy
więc go łączyć w wyrażeniach z elementami innego typu, nie istnieje też pakiet odpowiadający za
wykonywanie operacji wejścia/wyjścia na jego elementach. Po co więc właściwie takie typy, skoro
mamy z nimi same kłopoty? Po pierwsze - wykorzystywane są one na przykład w sytuacjach, kiedy
chcemy zapobiec omyłkowemu łączeniu wartości jednego typu, lecz o różnym znaczeniu. W poniższym
przykładzie wykonujemy działania na danych całkowitych, oznaczających wiek i wzrost. Gdybyśmy
zadeklarowali wiek i wzrost np. jako podtypy typu integer, kompilator pozwoliłby nam na
zsumowanie czy porównanie wieku i wzrostu, co oczywiście doprowadziłoby do błędnych wyników,
których przyczyny musielibyśmy dopiero szukać. Ponieważ jednak są one nowymi typami, kompilator
sam znajdzie wszystkie miejsca, w których pomyliliśmy wiek ze wzrostem. Po drugie zaś -
uniezależniamy się w ten sposób od implementacji Ady. W różnych implementacjach typy standardowe
mogą mieć różny rozmiar - w jednych np. integer może być 32-bitowy, w innych krótszy; w jednych
może być zdefiniowany typ long_integer, w innych nie... Tak więc próbując zdefiniować pewien
podtyp - na przykład, tak jak wyżej, sekundy_po_polnocy, możemy nie być w stanie określić
jednoznacznie odpowiedniego dla każdej implementacji typu bazowego. Wprowadzając zamiast
podtypu nowy typ pozwalamy kompilatorowi zdecydować, w jaki sposób elementy tego typu będą
reprezentowane.
Zauważmy, że w celu wypisywania wartości należących do typów zdefiniowanych w ponizszym
programie skonkretyzowaliśmy pakiet ada.text_io.integer_io - są to przecież także typy
całkowite. Podobnie należałoby skonkretyzować pakiet ada.text_io.float_io w przypadku
zdefiniowania nowego typu zmiennoprzecinkowego.
-- program ktorego nie mozna skompilowac
--
-- zdefiniowanie nowych typow calkowitych
-- nie pozwala na popelnienie kilku bledow
with ada.text_io;
use ada.text_io;
procedure pr19 is
-- definiujemy nowe typy calkowite
type wiek is range 0..120;
type wzrost is range 0..250;
-- konkretyzujemy dla nich odpowiedni pakiet
package wi_io is new ada.text_io.integer_io(wiek);
package wz_io is new ada.text_io.integer_io(wzrost);
osoba1_wi:wiek:=15;
osoba2_wi:wiek:=16;
osoba1_wz:wzrost:=158;
osoba2_wz:wzrost:=164;
sr_wz:wzrost;
sr_wi:wiek;
begin
put("sredni wzrost : ");
sr_wz:=(osoba1_wi+osoba2_wz)/2;
-- wystapi blad
-- pomylilismy _wz i _wi
wz_io.put(sr_wz);
new_line;
put("sredni wiek : ");
sr_wi:=(osoba1_wz+osoba2_wi)/2;
-- wystapi blad - jw.
wi_io.put(sr_wi);
81
new_line;
if osoba1_wz>osoba2_wi then
-- wystapi blad - jw.
put_line("osoba1 jest wyzsza niz osoba2");
else
put_line("osoba1 nie jest wyzsza niz osoba2");
end if;
end pr19;
Atrybuty typów
Wspomnieliśmy wcześniej o atrybutach typów. Dotychczas poznaliśmy dwa spośród nich -
t'first i t'last, gdzie t oznacza typ, zwracające najmniejszą i największą wartość w danym
typie (bądź podtypie). Oprócz tego istnieją następujące atrybuty: t'pos, t'val, t'pred, t'succ,
t'image, t'value, t'base, t'max, t'min. Wszystkie one, oprócz t'first, t'last i
t'base wymagają parametrów. Wszystkie, oprócz pos i val, określone są dla typów rzeczywistych i
dyskretnych (tzn. całkowitych lub wyliczeniowych). Pos i val określone są tylko dla typów
dyskretnych. Oto przykład ilustrujący użycie atrybutów:
--program demonstrujacy dzialanie atrybutow typow
with ada.text_io;
use ada.text_io;
procedure pr20 is
type dni is (pon, wt, sr, czw, pt, so, nie);
subtype dni_robocze is dni range pon..pt;
subtype srodek_tygodnia is dni_robocze range wt..czw;
subtype do_stu is integer range 0..100;
package dni_io is new ada.text_io.enumeration_io(dni);
use dni_io;
package int_io is new ada.text_io.integer_io(integer);
use int_io;
package flt_io is new ada.text_io.float_io(float);
use flt_io;
x:constant do_stu:=do_stu'first;
begin
-- atrybuty FIRST i LAST
put("dni'first : ");
put(dni'first);
-- wypisze PON
set_col(40);
put("dni'last : ");
put(dni'last);
-- wypisze NIE
new_line;
put("dni_robocze'first : ");
put(dni_robocze'first);
-- wypisze PON
set_col(40);
put("dni_robocze'last : ");
put(dni_robocze'last);
-- wypisze PT
new_line;
put("integer'first : ");
put(integer'first,0);
-- wypisze najmniejszy integer
-- atrybuty PRED i SUCC
new_line(2);
put("dni'pred(wt) : ");
81
put(dni'pred(wt));
-- wypisze PON
set_col(40);
put("dni'succ(wt) : ");
put(dni'succ(wt));
-- wypisze SR
new_line;
put("dni robocze'pred(pt) : ");
put(dni_robocze'pred(pt));
-- wypisze CZW
set_col(40);
put("dni robocze'pred(nie) : ");
put(dni_robocze'pred(nie));
-- wypisze SO
new_line;
put("do_stu'pred(10) : ");
put(do_stu'pred(10),0);
-- wypisze 9
set_col(40);
put("integer'pred(x) (x: do_stu := 0) : ");
put(integer'pred(x),0);
-- wypisze -1
new_line;
put("dni'robocze'succ(pt) : ");
put(dni_robocze'succ(pt));
-- wypisze SO
set_col(40);
put("do_stu'pred(x) (x: do_stu := 0) : ");
put(do_stu'pred(x));
-- wypisze -1
new_line;
put("float'pred(0.0) : ");
put(float'pred(0.0));
set_col(40);
put("float'succ(0.0) : ");
put(float'succ(0.0));
new_line(2);
-- atrybuty POS i VAL
put("dni'pos(pon) : ");
put(dni'pos(pon),0);
-- wypisze 0
set_col(40);
put("dni'val(0) : ");
put(dni'val(0),0);
-- wypisze PON
new_line;
put("srodek_tygodnia'pos(wt) : ");
put(srodek_tygodnia'pos(wt),0);
-- wypisze 1
set_col(40);
put("srodek_tygodnia'val(1) : ");
put(srodek_tygodnia'val(1));
-- wypisze WT
new_line;
put("srodek_tygodnia'val(0) : ");
put(srodek_tygodnia'val(0));
-- wypisze PON
set_col(40);
put("integer'pos(-7) : ");
put(integer'pos(-7),0);
-- wypisze -7
new_line(2);
-- atrybuty IMAGE i VALUE
put("integer'image(12) : ");
put(integer'image(12));
-- wypisze 12 (tekst)
set_col(40);
put("do_stu'image(10) : ");
put(do_stu'image(10));
-- wypisze 10 (tekst)
new_line;
put("dni'image(wt) : ");
put(dni'image(wt));
-- wypisze WT (tekst)
set_col(40);
put("dni_robocze'image(so) : ");
put(dni_robocze'image(so));
-- wypisze SO (tekst)
new_line;
put("integer'value(""12"") : ");
81
put(integer'value("12"),0);
-- wypisze 12 (liczbę)
set_col(40);
put("integer'image(x) (x : do_stu :=0) : ");
put(integer'image(x));
-- wypisze 0 (tekst)
new_line;
put("float'value(""12"") : ");
put(float'value("12"));
-- wypisze 1.20000E+01
set_col(40);
put("float'image(12.2) : ");
put(float'image(12.2));
-- wypisze 1.22000E+001
new_line(2);
-- atrybut BASE
put("integer'base'first : ");
put(integer'base'first);
-- wypisze to, co integer'first
set_col(40);
put("do_stu'base'first : ");
put(do_stu'base'first);
--wypisze to, co integer'first
new_line;
put("dni_robocze'base'last : ");
put(dni_robocze'base'last);
-- wypisze NIE
set_col(40);
put("srodek_tygodnia'base'last : ");
put(srodek_tygodnia'base'last);
-- wypisze NIE
new_line(2);
--atrybuty MIN i MAX
put("dni'min(so,pon) : ");
put(dni'min(so,pon));
-- wypisze PON
set_col(40);
put("dni_robocze'min(so,pon) : ");
put(dni_robocze'min(so,pon));
-- wypisze PON
new_line;
put("dni_robocze'min(so,nie) : ");
put(dni_robocze'min(so,nie));
-- wypisze SO
end pr20;
Atrybuty t'pred(element) i t'succ(element) zwracają elementy, które w danym
typie poprzedzają element i następują po nim (ang. predecessor i successor). Tak więc
dni'pred(wt) da czw, integer'pred(-7) da -8 itd. Nie można jednak próbować określić
elementu poprzedzającego pierwszy element ani elementu następującego po ostatnim elemencie typu.
Nie istnieje mechanizm "chodzenia w kółko", czyli podawania np. jako elementu pierwszego jako
następnego po ostatnim. Próba wykonania powyższych operacji spowoduje wystąpienie błędu
constraint_error podczas wykonywania programu. Zauważmy, że atrybuty pred i succ
określone są także dla typów zmiennoprzecinkowych, a więc dla liczb rzeczywistych, dla których
przecież nie istnieje "liczba następna po danej". To prawda, ale komputer jest w stanie reprezentować
w sposób dokładny jedynie skończoną ilość liczb danego typu - ogranicza go na przykład rozmiar typu
- a więc w tym wypadku jako liczbę następną bądź poprzednią rozumie się najbliższą liczbę możliwą
do uzyskania (tzw. liczbę maszynową) większą lub mniejszą od danej. W przypadku podtypów atrybuty
pred i succ operują na typie bazowym danego podtypu, dlatego możliwe jest napisanie
dni_robocze'pred(nie) - otrzymamy so - chociaż ani so, ani nie nie należą do podtypu
dni_robocze.
Atrybuty t'pos(element) i t'val(numer) wykonują operacje odwrotne -
t'pos(element) zwraca pozycję (ang. position), na której stoi dany element w typie t, zaś
t'val(numer) zwraca element (wartość - ang. value) stojący w typie t na pozycji o numerze
numer. Atrybuty te są określone tylko dla typów dyskretnych. Elementy każdego typu wyliczeniowego
są ponumerowane - pierwszy element stoi na pozycji 0, następny 1 itd. Tak więc np. dni'pos(pon)
81
daje 0, dni'val(4) da pt. Natomiast w typach całkowitych pozycja jest równa wartości liczby
(integer'pos(-3) da -3, integer'pos(0) da 0). W przypadku podtypów atrybuty pos i val
operują na typie bazowym. Srodek_tygodnia'pos(wt) daje 1, choć wt jest najmniejszym
elementem w tym podtypie - zwracana jest jednak pozycja wt w typie bazowym - dni. Podobnie
możemy
napisać
srodek_ tygodnia'val(0) lub srodek_tygodnia'pos(pon)
otrzymując pon lub 0, mimo że element ten leży poza zakresem podtypu.
Atrybuty t'image(element) i t'value(ciąg_znaków) również mają przeciwstawne
działanie. T'image(element) powoduje przekształcenie elementu w jego reprezentację w postaci
ciągu znaków, czyli łańcucha (jest to przekształcenie na typ string, ale czegoś więcej o tym typie
dowiemy się później). T'value(ciąg_znaków) zamienia ciąg_znaków, czyli element typu
string, na element typu T, odpowiadający danemu napisowi. I tak na przykład
integer'image(12) daje "12" - ciąg znaków, zaś integer'value("12") da 12 - liczbę.
Jeżeli operujemy na elementach typu wyliczeniowego, to odpowiadający im ciąg znaków zostanie
wypisany wielkimi literami. W przypadku liczb typu zmiennoprzecinkowego otrzymany łańcuch będzie
przedstawiał liczbę zapisaną w formie wykładniczej, ze spacją lub minusem na początku i jedną cyfrą
przed kropką dziesiętną.
Ze względu na obecność typu wide_character w Adzie istnieją także atrybuty
wide_image i wide_value, wykonującym takie same operacje jak image i value, ale na
łańcuchach typu wide_string złożonych ze znaków typu wide_character. Wyprowadzanie
takich napisów wymaga umieszczenia w klauzuli with pakietu ada.wide_text_io.
Atrybut t'base odwołuje się do typu bazowego danego podtypu (lub typu, ale wówczas jest
on sam dla siebie typem bazowym). Tak więc na przykład napisanie positive'base'first jest
równoznaczne z napisaniem integer'first, a srodek_tygodnia'base'last - z napisaniem
dni'last. Atrybut ten może być wykorzystywany także w teście członkostwa - możemy napisać na
przykład
x in do_stu'base
co jest równoważne z napisaniem
x in integer.
Atrybuty t'min i t'max wymagają dwóch parametrów należących do typu t. Zwracają one
odpowiednio mniejszą bądź większą z podanych wartości (integer'max(10,2) daje 10,
dni'min(pon,pt) daje pon).
A oto program przykładowy z zastosowaniem atrybutów:
-- program dokonuje tlumaczenia podanej
-- nazwy dnia tygodnia
-- z jez. polskiego na angielski
with ada.text_io;
use ada.text_io;
procedure pr21 is
type dni is (poniedzialek, wtorek, sroda, czwartek, piatek,
sobota, niedziela);
type days is (Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday);
package dni_io is new ada.text_io.enumeration_io(dni);
package days_io is new ada.text_io.enumeration_io(days);
d:dni;
begin
put_line("Podaj nazwe dnia po polsku - otrzymasz nazwe dnia
po angielsku ");
81
new_line;
put("nazwa polska : ");
dni_io.get(d);
put("nazwa angielska : ");
days_io.put(days'val(dni'pos(d)));
end pr21;
Instrukcja wyboru
Z typami wyliczeniowymi oraz tworzeniem podtypów związana jest kolejna instrukcja
powodująca, że program działa w różny sposób w zależności od wartości pewnego parametru -
instrukcja wyboru. Ma ona postać
[1] case selektor is
when lista_możliwości_1 => instrukcje_1
when lista_możliwości_2 => instrukcje_2
..........
when lista_możliwości_n => instrukcje_n
end case;
lub też
[2] case selektor is
when lista_możliwości_1 => instrukcje_1
when lista_możliwości_2 => instrukcje_2
..........
when lista_możliwości_n => instrukcje_n
when others => instrukcje_dalsze
end case;
Selektor musi być zmienną typu dyskretnego (czyli całkowitego lub wyliczeniowego). Jeżeli
przybierze on wartość znajdującą się na liście_możliwości_1, wykonywane są
instrukcje_1, jeżeli na liście_możliwości_2, wykonywane są instrukcje_2 itd. Jeżeli
wartość selektora nie znajduje się na żadnej z list, a instrukcja ma postać [2], to wykonywane są
instrukcje następujące po when others. Instrukcja case musi zawierać odniesienie do każdej z
możliwych (teoretycznie) wartości selektora, a więc do wszystkich elementów typu (lub podtypu), do
którego on należy. Użyteczne jest przy tym napisanie when others => lub zdefiniowanie podtypu
obejmującego wszystkie rzeczywiście przyjmowane przez selektor wartości. Przypuśćmy na przykład,
że zmienna będąca selektorem jest liczbą całkowitą (np. typu integer) i oznacza numer dnia
tygodnia, a więc przyjmuje wartości od 1 do 7. Instrukcja wyboru musi obsłużyć wszystkie te
przypadki. Jeśli napiszemy
case selektor is
when 1 => ...
when 2 => ....
...........
when 7=> ....
end case;
to podczas kompilowania programu wystąpi błąd. Nie obsłużyliśmy całości typu integer, a selektor
teoretycznie może przyjąć każdą wartość całkowitą. W celu rozwiązania tego problemu możemy dodać
w powyższej instrukcji jeszcze jedną, ostatnią możliwość
when others => null;
- wówczas w razie przyjęcia przez selektor innej wartości po prostu nie robimy nic, lub zadeklarować
podtyp typu integer
subtype nr_dnia is integer range 1..7;
selektor:nr_dnia;
81
ograniczając zakres typu selektora do siedmiu rzeczywiście możliwych wartości. Wówczas
wystarczająca jest instrukcja w postaci napisanej na początku.
A jak może wyglądać lista_możliwości? Jeżeli jest ona jednoelementowa, na przykład
tak:
when 1 =>
when 'a' =>
when pon =>
-- pon jest elementem typu wyliczeniowego
Jeżeli lista ma mieć więcej elementów, możemy ja zapisać w postaci
when 1| 2 | 7 =>
co oznacza, że zapisane dalej instrukcje wykonujemy, gdy selektor przyjmie wartość 1, 2 lub 7, albo w
postaci
when 1..4 =>
Podane instrukcje będą wówczas wykonywane w przypadku przyjęcia przez selektor wartości z
powyższego przedziału . W razie potrzeby można łączyc obie formy zapisu, np.
when 1 | 2 | 5..7 =>
Oto przykłady zastosowania instrukcji case:
-- przyklad uzycia instrukcji wyboru
-- program oblicza - w zaleznosci od zyczenia -
-- pole trojkata, prostokata badz kola
with ada.text_io,ada.numerics;
use ada.text_io;
procedure pr22 is
package int_io is new ada.text_io.integer_io(integer);
package flt_io is new ada.text_io.float_io(float);
use int_io, flt_io;
subtype mozliwosc is integer range 1..3;
nr:mozliwosc;
a,b:float;
begin
put_line(" Program oblicza pola nastepujacych figur :");
set_col(10);put_line("1 : trojkat ");
set_col(10);put_line("2 : prostokat");
set_col(10);put_line("3 : kolo");
new_line;
put("Podaj numer wybranej figury : "); get(nr);
new_line;
case nr is
when 1 => put("podaj dlugosc podstawy : ");
get(a);
put("podaj dlugosc wysokosci opuszczonej na te
podstawe : ");
get(b);
new_line;
put("pole trojkata wynosi ");
81
put(a*b/2.0,exp=>0);
put(" j.kw");
when 2 => put_line("podaj dlugosci bokow : ");
put("a : ");get(a);
put("b : ");get(b);
new_line;
put("pole prostokata wynosi ");
put(a*b,exp=>0);
put(" j.kw");
when 3 => put("podaj promien kola : ");
get(a);
new_line;
put("pole kola wynosi ");
put(ada.numerics.pi*a**2,exp=>0);
put(" j.kw");
end case;
end pr22;
Kolejny przykład - tym razem z selektorem należącym do typu wyliczeniowego:
-- kolejny przyklad uzycia instrukcji wyboru
--
-- wazymy caly samochod
-- otrzymujemy wage ladunku
with ada.text_io;
use ada.text_io;
procedure pr23 is
type ciezarowka is (zuk, star_5t, jelcz_14t, steyer, inne);
package pod_sam is new ada.text_io.enumeration_io(ciezarowka);
use pod_sam;
package int_io is new ada.text_io.integer_io(integer);
use int_io;
waga_zuk : constant integer:=3000;
waga_star_5t : constant integer:=4000;
waga_jelcz_14t : constant integer:=10000;
waga_steyer : constant integer:=14000;
sam:ciezarowka;
mc,m:integer;
begin
new_line;
set_col(5);
put_line(" *** WAGA DLA SAMOCHODOW CIEZAROWYCH ***");
new_line;
put_line("znane masy : zuk, star_5t, jelcz_14t, steyer ");
put_line("wazony samochod (wybierz z listy lub wpisz ""inne"" ");
set_col(20);put("> ");
get(sam);
case sam is
when zuk => m:=waga_zuk;
when star_5t => m:=waga_star_5t;
when jelcz_14t => m:=waga_jelcz_14t;
when steyer => m:=waga_steyer;
when inne => put("podaj mase wlasna samochodu (w kg) : ");
get(m);
81
end case;
put("podaj mase calosci : ");get(mc);
new_line;
put("masa ladunku wynosi : "); put(mc-m,0);
end pr23;
Instrukcje pętli
Stworzyliśmy program do obsługi wagi dla ciężarówek. No dobrze - możemy zapytać - ale czy
koniecznie osoba pracująca przy tej wadze musi uruchamiać program od nowa, aby zważyc kolejny
samochód? Czy nie można byłoby przerobić tego programu tak, aby po otrzymaniu wyniku znów
pojawiała się plansza "menu", gdzie wybieramy markę ważonego pojazdu, a działanie programu
zakańczamy przez wpisanie np. zera? Oczywiście jest to możliwe. W tym celu musimy zapoznać się z
kolejnym rodzajem instrukcji, a mianowicie z instrukcjami pętli. Jest ich kilka - pętla "prosta", pętla
while i pętla for. Najmniej skomplikowana jest pierwsza z nich o składni
loop
instrukcje
end loop;
Instrukcje umieszczone wewnątrz pętli będą w tym wypadku wykonywane nieskończenie wiele razy. Po
dojściu do linii end loop następuje powrót do początku pętli, czyli do linii zawierającej słowo
loop, kolejne wykonanie itd. Teoretycznie program zawierający taką pętlę mógłby działać
nieprzerwanie. Na przykład program
with ada.text_io;
use ada.text_io;
procedure pr24 is
begin
loop
put("Ada ");
end loop;
end pr24;
będzie zapisywał ekrany słowem "Ada", dopóki nie przerwiemy jego działania klawiszami Ctrl-Break
lub Ctrl-C. Jeżeli jednak wsród instrukcji wewnątrz pętli znajdzie się słowo
exit;
lub
exit warunek;
to po dojściu do tej linii (w przypadku pierwszym), lub po dojściu do tej linii i stwierdzeniu, że
warunek zachodzi, nastąpi wyjście z pętli i wykonanie pierwszej z instrukcji następujących po end
loop.
-- modyfikacja programu pr22
--
-- mozliwosc wielokrotnych obliczen
with ada.text_io,ada.numerics;
use ada.text_io;
procedure pr25 is
package int_io is new ada.text_io.integer_io(integer);
package flt_io is new ada.text_io.float_io(float);
use int_io, flt_io;
81
subtype mozliwosc is integer range 1..3;
nr:mozliwosc;
a,b:float;
odp:character;
begin
loop
put_line(" Program oblicza pola nastepujacych figur :");
set_col(10);put_line("1 : trojkat ");
set_col(10);put_line("2 : prostokat");
set_col(10);put_line("3 : kolo");
new_line;
put("Podaj numer wybranej figury : "); get(nr);
new_line;
case nr is
when 1 => put("podaj dlugosc podstawy : ");
get(a);
put("podaj dlugosc wysokosci opuszczonej na te
podstawe : ");
get(b);
new_line;
put("pole trojkata wynosi ");
put(a*b/2.0,exp=>0);
put(" j.kw");
when 2 => put_line("podaj dlugosci bokow : ");
put("a : ");get(a);
put("b : ");get(b);
new_line;
put("pole prostokata wynosi ");
put(a*b,exp=>0);
put(" j.kw");
when 3 => put("podaj promien kola : ");
get(a);
new_line;
put("pole kola wynosi ");
put(ada.numerics.pi*a**2,exp=>0);
put(" j.kw");
end case;
new_line(2);
put("czy liczymy dalej? (n - koniec) ");get(odp);
exit when odp='n';
new_line(5);
end loop;
end pr25;
Dzięki zastosowaniu powyższej pętli nie musimy wielokrotnie uruchamiać programu, jeśli
chcemy obliczyc pola kilku figur. Program pyta, czy chcemy jeszcze coś obliczyć, i jeżeli wciśniemy
klawisz n, zakańcza działanie. Wciśnięcie każdego innego klawisza powoduje powtórne wykonanie
programu. W podobny sposób możemy zmodyfikować program pr23, co będzie rozwiązaniem
przedstawionego na początku problemu.
Kolejną pętlą jest pętla while postaci
81
while warunek loop
instrukcje
end loop;
która jest właściwie podobna do prostej pętli zawierającej instrukcję exit. Instrukcje wewnątrz pętli
wykonywane są tak długo, jak długo zachodzi warunek. Po raz pierwszy warunek sprawdzany jest
jeszcze przed wejściem do pętli, a więc istnieje możliwość, że instrukcje wewnątrz niej nie będą w
ogóle wykonane. Tak na przykład instrukcje wewnątrz poniższej pętli
get(a);
while a<100 loop
.....
a:=a+2;
...
end loop;
mogą nie byc wykonane, jeśli podamy a większe bądź równe 100.
Oto przykład wykorzystania tej instrukcji - program obliczający, ile kolejnych liczb całkowitych
dodatnich należy zsumować, aby ich suma była większa bądź równa podanej liczbie:
-- przyklad wykorzystania petli while
-- obliczamy, ile kolejnych liczb
-- calkowitych dodatnich nalezy dodac,
-- aby ich suma byla wieksza lub rowna
-- podanej liczbie
with ada.text_io;
use ada.text_io;
procedure pr26 is
package int_io is new ada.text_io.integer_io(integer);
use int_io;
liczba,suma:integer;
n:natural;
begin
loop
put("Podaj liczbe calkowita dodatnia : ");
get(liczba);
exit when liczba>0;
put("To nie jest liczba dodatnia!!! ");
end loop;
suma:=0;n:=0;
while suma<liczba loop
n:=n+1;
suma:=suma+n;
end loop;
put("Zsumowalismy ");put(n,0);
if n mod 10 in 2..4 then
put(" kolejne liczby i otrzymalismy ");
else
put(" kolejnych liczb i otrzymalismy ");
end if;
put(suma,0);
end pr26;
A jeżeli chcemy, aby instrukcje wewnątrz pętli wykonały się pewną, ściśle określoną ilość razy?
W tym celu możemy wykorzystać pętlę for o składni:
[1] for licznik_pętli in zakres loop
81
instrukcje
end loop;
albo
[2] for licznik_pętli in reverse zakres loop
instrukcje
end loop;
Program pr27 ilustruje użycie obu postaci tej pętli:
with ada.text_io;
use ada.text_io;
-- przyklad dzialania petli for
procedure pr27 is
package int_io is new ada.text_io.integer_io(integer);
use int_io;
begin
for i in 1..5 loop
put("Petla wykonuje sie po raz ");
put(i,0);
new_line;
end loop;
new_line(2);
for i in reverse 1..5 loop
put("Petla wykonuje sie dla liczby ");
put(i,0);
new_line;
end loop;
end pr27;
Wynikiem jego wykonania będzie napisanie
Petla wykonuje sie po raz 1
Petla wykonuje sie po raz 2
Petla wykonuje sie po raz 3
Petla wykonuje sie po raz 4
Petla wykonuje sie po raz 5.
Petla wykonuje sie dla liczby 5
Petla wykonuje sie dla liczby 4
Petla wykonuje sie dla liczby 3
Petla wykonuje sie dla liczby 2
Petla wykonuje sie dla liczby 1.
Jak widać, w pętli postaci [1] licznik przyjął na początku wartość 1 - dolne ograniczenie zakresu.
Każde następne wykonanie pętli powoduje automatyczne zwiększenie wartości licznika o 1. Dzieje się
tak dopóki licznik nie osiągnie wartości 5 - górnego ograniczenia zakresu. Pętla w postaci [2] działa w
bardzo podobny sposób, ale jakby "od tyłu" - na początku licznik przyjmuje wartośc górnego
ograniczenia zakresu, przy każdym przejściu pętli jest zmniejszany o 1 aż do osiągnięcia wartości
dolnego ograniczenia zakresu. Licznika pętli nie musimy (i nie możemy) deklarować ani nadawać mu
początkowej ani kolejnych wartości (pisząc na przykład wewnątrz pętli i:=i+1), a jeżeli w programie
zadeklarowaliśmy i użyliśmy zmiennej o tej samej nazwie, to zostanie ona przesłonięta przez licznik
pętli, a więc będzie wewnątrz pętli niemożliwa do użycia. Nie możemy sami zmieniac wartości licznika
- napisanie np.
81
for i in 1..10 loop
.....
i:=5;
end loop;
jest niedozwolone (licznik jest traktowany podobnie jak stała). Jego wartość i zgodność z zakresem jest
rozpatrywana na początku pętli, zatem napisanie
for i in 2..1 loop
put("Napis");
end loop;
spowoduje, że napis taki nigdy się nie ukaże, gdyż licznik przyjmie na początek wartość 2, a więc od
razu przekroczy górne ograniczenie zakresu (tzn. 1). Licznik pętli może przyjmowac wartości należące
do typu dyskretnego. Nie możemy napisać na przykład
for i in 1.2 .. 4.5 loop
możemy natomiast - dla zadeklarowanego wcześniej typu wyliczeniowego dni napisać
for i in pon..czw loop
albo
for i in dni loop
Widać więc, że w roli zakresu może wystąpić nazwa typu bądź podtypu.
Zakres licznika pętli nie musi byc ustalony raz na zawsze - może się zmieniać, jednak jego
wartość musi być znana już przed wejściem do pętli. Można napisać na przykład
get(n);
for i in 1..n loop
...
end loop;
lub też
x:=150;
for j in 1..x loop
...
end loop;
czy
for k in 100..integer'last loop
...
end loop;
nie ma sensu natomiast
x:=3;
for i in 1..x loop
...
x:=20;
end loop;
Nie spowoduje to zgłoszenia błędu, ale pętla zostanie wykonana tylko trzy razy - zakres licznika pętli
został ustalony na początku, przed wejściem do niej.
Oto kilka przykładów zastosowania pętli:
-- przyklad zastosowania petli for
81
-- kilkakrotne narysowanie "mordki"
with ada.text_io;
use ada.text_io;
procedure pr28 is
package int_io is new ada.text_io.integer_io(integer);
use int_io;
n:integer;
begin
put("Ile rysunkow chcesz wykonac? ");get(n);
for i in 1..n loop
put(i,0);put_line(" : ");
put_line("\\\//");
put_line(" 0 0 ");
put_line(" ~_ ");
new_line;
end loop;
end pr28;
i modyfikacja programu pr15:
-- tabelka logiczna
-- modyfikacja programu pr15
-- przyklad uzycia petli
with ada.text_io;
use ada.text_io;
procedure pr29 is
package b_io is new ada.text_io.enumeration_io(boolean);
p,q:boolean;
begin
put_line(" TABELKA WARTOSCI LOGICZNYCH");
new_line(3);
put(" | p | q | not p | p and q | p or q | p xor q |");
new_line;
for p in boolean loop
for q in boolean loop
put(" | ");b_io.put(p,width=>5);
put(" | ");b_io.put(q,width=>5);
put(" | ");b_io.put(not p,width=>7);
put(" | ");b_io.put(p and q,width=>7);
put(" | ");b_io.put(p or q,width=>6);
put(" | ");b_io.put(p xor q,width=>7);
put(" |");
new_line;
end loop;
end loop;
end pr29;
Wróćmy jeszcze do przesłaniania przez licznik pętli wartości zmiennych lub stałych
zadeklarowanych w programie, a mających tę samą nazwę. Czy to oznacza, że nie możemy w ogóle
81
skorzystać z przesłoniętej zmiennej? Na szczęście nie. Musimy jedynie poprzedzic ją nazwą procedury,
z której pochodzi, i kropką, jak w poniższym programie:
-- program demonstrujacy przeslanianie
-- zmiennych przez licznik petli
with ada.text_io;
use ada.text_io;
procedure pr30 is
package int_io is new ada.text_io.integer_io(integer);
use int_io;
i:integer:=8;
begin
put(i,0);
-- wypisze 8
new_line;
for i in 1..3 loop
put(i,0);
-- wypisze 1, 2 lub 3
new_line;
put(pr30.i,0);
-- wypisze 8
new_line;
end loop;
end pr30;
Pętle możemy także w sobie zagnieżdżać (czyli umieszczać jedną pętlę w drugiej). Przykład
tego widzieliśmy już w programie pr29. Jeżeli jednak przypadkiem nazwiemy liczniki obu pętli
jednakowo, to licznik pętli wewnętrznej przesłoni licznik pętli zewnętrznej, na przykład
-- program demonstrujacy przeslanianie licznika petli
-- przez drugi licznik (petli zagniezdzonej)
-- o tej samej nazwie
with ada.text_io;
use ada.text_io;
procedure pr31 is
package b_io is new ada.text_io.enumeration_io(boolean);
use b_io;
package int_io is new ada.text_io.integer_io(integer);
use int_io;
begin
for i in 1..3 loop
for i in boolean loop
put(i);
-- wypisze wartosc logiczna
new_line;
end loop;
put(i,0);
-- wypisze liczbe
new_line;
end loop;
end pr31;
Jeżeli zaś chcemy w wewnętrznej pętli wypisać liczbę będącą wartością licznika pętli zewnętrznej, to
musimy najpierw nadać nazwę pętli zewnętrznej (lub obu pętlom) przez napisanie
nazwa_pętli:
....... loop
........ end loop nazwa_pętli;
81
Od tej pory możemy odwoływać się do potrzebnej wartości pisząc
nazwa_pętli.nazwa_licznika_pętli
Przykład takiego nazywania pętli znajduje się w poniższym programie
-- program demonstrujacy nazywanie petli
-- oraz jego skutki
with ada.text_io;
use ada.text_io;
procedure pr32 is
package b_io is new ada.text_io.enumeration_io(boolean);
use b_io;
package int_io is new ada.text_io.integer_io(integer);
use int_io;
begin
petla_duza:
for i in 1..3 loop
for i in boolean loop
put(i,0);
-- wypisze wartosc logiczna
new_line;
put(petla_duza.i,0); -- wypisze liczbe
new_line;
end loop;
put(i,0);
-- wypisze liczbe
new_line;
end loop petla_duza;
end pr32;
****************************************************************
Typ łańcuchowy
Zapoznaliśmy się już z typami znakowymi character i wide_character. W praktyce jednak
częściej niż pojedynczych znaków używamy ich ciągów. Ciągiem takim jest np. imię, nazwisko...
Przydatny byłby więc typ pozwalający na przechowanie danych tego rodzaju w postaci jednej
zmiennej, a nie np. wielu pojedynczych zmiennych typu character. Powyższe oczekiwania spełniają
dwa zdefiniowane w Adzie typy: string obejmujący ciągi znaków (inaczej łańcuchy) złożone z
elementów typu character oraz wide_string obejmujący ciągi znaków należących do typu
wide_character. Zmienne tych typów deklarujemy następująco:
nazwisko : string(1..20);
imie : string(5..21);
PESEL : string(1..11);
jakis_wide_string : wide_string(1..10);
czyli, mówiąc ogólnie, piszemy:
zmienna_łańcuchowa :
string (dolne_ograniczenie_indeksu .. górne_ograniczenie_indeksu);
lub
zmienna_łańcuchowa : wide_string
(dolne_ograniczenie_indeksu .. górne_ograniczenie_indeksu);
81
gdzie ograniczenia indeksu muszą byc liczbami typu positive, przy czym ograniczenie dolne
powinno być mniejsze od ograniczenia górnego (jeżeli nie jest, deklarowany jest łańcuch pusty).
Jako łańcuch traktowany jest dowolny ciąg znaków ujęty w apostrofy, np.
"Ada95"
"1134"
"A+B=C"
"Jan Kowalski"
""
Ostatni z łańcuchów jest łańcuchem pustym (zapisany został jako dwa następujące po sobie
cudzysłowy, między którymi nie stoi żaden znak). Jeżeli natomiast chcemy, aby znak cudzysłowu był
elementem łańcucha, musimy oznaczyć go przez dwa cudzysłowy nastepujące po sobie, np.
"Adam Mickiewicz jest autorem ""Pana Tadeusza"".".
Występujące w deklaracji ograniczenia indeksu określają długość łańcucha oraz sposób numerowania
jego elementów. Obrazowo zmienną zadeklarowaną
imie : string (1..8):="Karolina";
możemy przedstawić
Chcąc nadać zmiennej imie nową wartość musimy pamiętać, że wolno nam podstawić jedynie łańcuch
o długości takiej, jak podana w deklaracji. Zatem nie możemy napisac na przykład
imie := "Anna";
imie := "Klementyna";
ale możemy
imie := "Polikarp";
imie := "Anna ";
O zadeklarowanych rozmiarach łańcucha mogą nas poinformowac odpowiednie atrybuty. I tak:
zmienna_łańcuchowa'first
zwraca liczbę będącą dolnym ograniczeniem indeksu
łańcucha
zmienna_łańcuchowa'last
zwraca liczbę będącą górnym ograniczeniem indeksu
łańcucha
zmienna_łańcuchowa'range
zwraca zakres indeksu łańcucha (ograniczenie_dolne ..
ograniczenie_górne) - jest odpowiednikiem napisania
zmienna_łańcuchowa'first ..
zmienna_łańcuchowa'last
zmienna_łańcuchowa'length
zwraca długość danego łańcucha (zadeklarowaną ilość
znaków, czyli ograniczenie_górne minus
ograniczenie_dolne).
Mając łańcuch możemy wykonywac działania na jego elementach - znakach, oraz na fragmentach -
podłańcuchach. Przy wyodrębnianiu ich z łańcucha wykorzystujemy fakt istnienia indeksu
(ponumerowania znaków). Do znaku stojącego w łańcuchu odwołujemy się pisząc
zmienna_łańcuchowa (pozycja_w_łańcuchu),
natomiast do podłańcucha
zmienna_łańcuchowa
(pozycja_znaku_początkowego .. pozycja_znaku_końcowego).
Daje nam to możliwość zamiany czy wypisywania fragmentów łańcucha. Mając na przykład
imie : string (1..8) := "Karolina";
i podstawiając
'K'
'a'
'r'
'o'
'l'
'i'
'n'
'a'
1
2
3
4
5
6
7
8
81
imie(1):='C';
imie(8):= 'e';
dostaniemy łańcuch "Caroline", natomiast podstawiając
imie(3 ..5):="cia"
zmienimy łańcuch wyjściowy na "Karolcia".
Należy zwrócić uwagę na fakt, iż napisanie imie(1) oraz imie(1..1) oznacza dwie różne rzeczy.
Pierwsze - to litera 'K', czyli znak (element typu character), drugie natomiast - "K" - łańcuch o
długości jeden (należący do typu string).
Jeżeli przy wybieraniu fragmentów łańcucha (zarówno znaków, jak i podłańcuchów) przekroczymy
dolne bądź górne ograniczemie indeksu, to zgłoszony będzie błąd - Constraint_Error.
Możliwe jest łączenie łańcuchów ze sobą. Operacja ta nosi miano konkatenacji. Możemy np. napisać
s1 : string (1..3) := "Jan";
s2 : string (1..5) := "Nowak";
s3 : string (1..9) := s1&' '&s2;
co spowoduje nadanie łańcuchowi s3 wartości "Jan Nowak".
Do łączenia łańcuchów służy operator &. Zezwala on na dodawanie do siebie nie tylko łańcuchów, ale i
znaków (w powyższym przykładzie dołączyliśmy spację), jednak wynik zawsze jest typu string:
"Jan" & "Kowalski"
daje
"JanKowalski"
"Jan" & ‘K’
daje
"JanK"
'a' & 'b'
daje
"ab"
'+' & "12"
daje
"+12".
Łańcuch będący wynikiem konkatenacji ma długość równą sumie długości jego składników (tzn.
łączonych łańcuchów), można go podstawić jedynie pod zmienną łańcuchową odpowiedniej długości.
Natomiast numeracja znaków w poszczególnych łańcuchach nie ma żadnego znaczenia:
l1 : string (1..3) := "Ala";
l2 : string (10 .. 11) := "ma";
l3 : string(2 .. 5) := "kota";
l4 : string(121 .. 132):= l1 & ' ' & l2 & ' ' & l3;
Za wprowadzanie i wyprowadzanie znaków odpowiadają procedury umieszczone w pakiecie
ada.text_io. Procedura put wypisuje łańcuch lub jego część
imie : string(1..8) := "Karolina";
...
put (imie);
-- wypisze Karolina
put(imie(1..5));
-- wypisze Karol
natomiast do wczytania zmiennej łańcuchowej używamy procedur get lub get_line. Napisanie
get(zmienna_łańcuchowa)
wymaga podania łańcucha o długości dokładnie takiej, jak zadeklarowana zmienna, natomiast
get_line(zmienna_łańcuchowa, zmienna_całkowita)
pozwala na podanie łańcucha o długości mniejszej bądź równej długości zadeklarowanej.
Wprowadzanie trwa do naciśnięcia klawisza <Enter> (lub do momentu, gdy wprowadzany ciąg znaków
osiągnie zadeklarowaną długość). Pod zmienną_całkowitą podstawiana jest rzeczywista długość
wprowadzonego łańcucha.
81
Łańcuchy można porównywać ze sobą używając operatorów <, <=, >, >=, =, /=. Operacja ta jest
wykonywana zarówno dla łańcuchów o jednakowej, jak i o różnej długości, a porównywane są kolejne
ich znaki . Prawdziwe są następujące zależności:
string'("Jan") <= string'("Jaroslaw")
wide_string'("Ewa") >wide_string'("Anna")
string'("Jan") < string'("Janusz")
Jak widać, w przypadku porównywania łańcuchów o których nie wiemy, do jakiego typu należą,
konieczne jest określenie tegoż typu w celu uniknięcia niejednoznaczności.
Warto pamiętać również o możliwości zamiany liczby (lub danej innego typu) na typ string i
odwrotnie. Służą do tego (omawiane juz w podrozdziale “Atrybuty typów”) atrybut T’image,
zamieniający element typu T na odpowiadający mu łańcuch, oraz T’value, zamieniający łańcuch na
element należący do typu T.
-- program wypisuje pewne dane osobowe
-- na podst. podanego numeru PESEL
with ada.text_io;
use ada.text_io;
procedure pr33 is
PESEL : string(1..11);
begin
put("Podaj swoj numer PESEL > ");
get(PESEL);
new_line;
put_line("Powiem Ci, czego dowiedzialem sie o Tobie:");
put(" ...urodziles sie " & pesel(5..6) & '.' & pesel(3..4) &
'.' & "19" & pesel(1..2) & " roku" );
new_line;
put(" ...jestes ");
if integer'value(pesel(10..10)) mod 2 = 0 then
put("kobieta");
else
put("mezczyzna");
end if;
end pr33;
-- przyklady operacji na stringu
-- na przyklad dopelnianie
-- koncowki lancucha spacjami
-- oraz zamiana malych liter na duze
with ada.text_io,ada.integer_text_io;
use ada.text_io,ada.integer_text_io;
procedure pr34 is
imie, nazwisko : string(1..40);
d1,w1,poz : integer;
begin
put_line("Program wypisze ozdobna wizytowke ");
-- wczytanie imienia i umieszczenie go
-- w srodku lancucha
81
put("Podaj imie : ");
get_line(imie,d1);
w1:=(imie'length-d1)/2;
imie(w1+1..w1+d1):=imie(1..d1);
for i in 1..w1 loop
imie(i):=' ';
end loop;
for i in w1+d1+1 .. imie'length loop
imie(i):=' ';
end loop;
--wczytanie nazwiska i umieszczanie go
-- w srodku lancucha
put("Podaj nazwisko : ");
get_line(nazwisko,d1);
w1:=(nazwisko'length-d1)/2;
nazwisko(w1+1..w1+d1):=nazwisko(1..d1);
for i in 1..w1 loop
nazwisko(i):=' ';
end loop;
for i in w1+d1+1 .. nazwisko'length loop
nazwisko(i):=' ';
end loop;
-- zamiana malych liter na duze
for i in 1..nazwisko'length loop
poz:=character'pos(nazwisko(i));
if poz>=character'pos('a') and poz<=character'pos('z') then
nazwisko(i):=character'val(poz-32);
end if;
end loop;
-- wypisanie wizytowki
for i in 1..42 loop put('*');end loop;
new_line;
put('*'& imie & '*');
new_line;
put('*' & nazwisko & '*');
new_line;
for i in 1..42 loop put('*');end loop;
end pr34;
Zwróćmy uwagę na występujący w programie pr33 zapis łańcucha o długości przekraczajacej długość
jednej linii:
put ("To jest baaaaaaaaaaaaaaaaaaaaaaaaaaaaaardzo dlugi lancuch " &
"a moze nawet jeszcze dluuuuuuuuuuuuuuuuuuuuuuzszy lancuch " &
"i jak widac jego zapis nie zmiescil się w jednej linijce " &
"ale i tak na ekranie bedzie wygladal inaczej niz tutaj...");
Dotychczas używaliśmy łańcuchów o konkretnej, ustalonej długości. Niejednokrotnie jednak przydatna
byłaby możliwośc użycia łańcuchów, których długość zostanie określona dopiero w trakcie
wykonywania programu. Istnieje kilka sposobów rozwiązania tego problemu. Jednym z nich jest
zadeklarowanie zmiennej łańcuchowej jako należącej do typu string (bez podawania ograniczeń
indeksu) i przypisanie jej żądanej wartości, np.
x : string := integer’image(N);
gdzie N jest zmienną typu całkowitego o wcześniej przypisanej wartości (zob. rozdział “Podprogramy”
w dalszej części książki). Zmienna łańcuchowa x w niejawny sposób przyjmuje długość taką, jak
łańcuch będący zapisem liczby N. Niestety, długości tej nie można już zmienić w trakcie działania
programu:
81
N : integer := 10;
x : string := integer’image (N);
-- x przyjmie wartość " 10"
-- (wiodąca spacja! - liczba jest dodatnia)
....
N := 120;
x:= integer’image(N);
Ostatnia instrukcja spowoduje wystąpienie błędu Constraint_Error (próbujemy wstawić łańcuch
czteroznakowy - " 120" do zmienne łańcuchowej mogącej się składać z co najwyżej trzech znaków).
Przykład ten może zrazu wydawać się dziwny, ale zobacz rozdział “Bloki” w dalszej części książki.
Inną - mniej elegancką - metodą pozwalającą na używanie łańcuchów o “określanej w czasie
wykonania programu długości” jest wstawianie wprowadzanego ciągu znaków do zmiennej
łańcuchowej o określonej długości i dopełnianie reszty spacjami (ich ilość przeliczana jest podczas
wykonywania programu). Podobną operację widzieliśmy w programie pr34.
-- przyklady wypisywania lancucha
-- jak wypisac ladnie, a jak brzydko
with ada.text_io;
use ada.text_io;
procedure pr35 is
str:string(1..60);
n:integer;
begin
put("Podaj napis (do 60 znakow) >");
get_line(str,n);
new_line;
put_line("Lancuch od 1-go do " &
integer'image(n) &"-go znaku: ");
put_line(str(1..n));
put_line("Caly lancuch: ");
put_line(str);
put_line("Teraz dopelniam koncowke spacjami... ");
for i in n+1..str'last loop
str(i):=' ';
end loop;
put_line("I ponownie caly lancuch - zakoncze go [*]: ");
put(str);put_line("[*]");
put("Prawda, ze ladniejszy?...");
end pr35;
W powyższym programie wypisywanie podłańcucha str(1..n) i - za drugim razem - str daje (na
ekranie) poniekąd ten sam efekt, jednak w drugim przypadku po wypełnieniu reszty łańcucha spacjami
nie musimy pamiętać ilości wprowadzonych znaków, co w dłuższych programach nie jest bez
znaczenia. Bez dopisania spacji końcówka łańcucha str wypełniona będzie przypadkowymi znakami
stanowiącymi interpretację aktualnej zawartości odpowiednich komórek pamięci. Po napisaniu
put(str);
te znaki również zostałyby wyświetlone na ekranie.
Omówiliśmy tutaj jedynie podstawowe operacje na typie string. Rozszerzenie przedstawionych
możliwości oferują pakiety Ada.Strings i Ada.Vstrings (zob. rozdział... )
@?
Tablice
W poprzednim rozdziale ustawialiśmy znaki na ponumerowanych miejscach tworząc łańcuchy. Obecnie
poznamy podobną złożoną strukturę danych - tablicę. Tablica jest obiektem złożonym z wielu
81
elementów (tzw. składowych) należących do tego samego typu. Do całej tej struktury odwołujemy się
za pomocą pojedynczego identyfikatora. Możemy również odwoływać się do poszczególnych jej
składników.
Tablice jednowymiarowe
Tablice mogą byc jedno- i wielowymiarowe. Na początek zajmiemy się tablicami jednowymiarowymi.
Mogą być one zadeklarowane jako tzw. anonimowy typ tablicowy
zmienna : array (określenie_indeksu) of typ_składowych;
lub jako typ tablicowy posiadający własną nazwę:
type typ_tablicowy is array (okreslenie_indeksu) of typ_składowych;
co pozwala następnie na deklarowanie zmiennych lub stałych należących do typu_tablicowego.
Określenie_indeksu może mieć jedną z postaci:
dolne_ograniczenie_zakresu .. górne_ograniczenie_zakresu
typ_indeksu
typ_indeksu range
dolne_ograniczenie_zakresu .. górne_ograniczenie_zakresu
przy czym indeks - niezależnie od sposobu jego zadeklarowania - musi należeć do typu dyskretnego
(jeśli typ nie jest podany bezpośrednio, przyjmowana jest wartość uniwersalna, np.
Universal_Integer).
Oto przykłady deklaracji tablic:
ciag1 : array (1..10) of integer;
ciag : array (integer range 1..10) of float;
n: integer := 7;
type xx is array (n .. 10+n) of float;
x1: xx;
type miesiace is (styczen, luty, marzec, kwiecien, maj, czerwiec,
lipiec, sierpien, wrzesien, pazdziernik, listopad, grudzien);
opady_Polska_97 : constant array (miesiace) of float:=(others=>0.0);
type opady is array (miesiace) of float;
opady_Warszawa_98, opady_Lodz_98 : opady;
subtype miesiace_wakacyjne is miesiace range lipiec..wrzesien;
type miejsca is (morze, gory, jeziora, wlasny_dom);
wakacje : array (miesiace_letnie) of miejsca;
type ciag_liczbowy is array (integer range 0..100) of integer;
ciag_A, ciag_B : ciag_liczbowy;
Do elementów tablicy odwołujemy się poprzez nazwę zmiennej tablicowej i wartość indeksu
umieszczoną w nawiasach okrągłych. Indeks może być wyrażeniem, stałą lub zmienną.
ciag_A(1) := 12;
put(opady_Lodz_98(styczen));
put(opady_Polska_97(wrzesien));
k:=7;
put(ciag_A(1+k));
Tablica może zostać wypełniona poprzez kolejne przypisywanie wartości poszczególnym jej
elementom, jak w przedstawionym przykładzie:
for i in miesiace loop
opady_Warszawa_98(i):=0.0;
end loop;
81
lub przez użycie tzw. agregatu tablicy (ang. array aggregate), tzn. wartości przypisywanej całej tablicy
od razu, a złożonej z wartości poszczególnych jej składowych, np.
wakacje := (morze, gory, jeziora);
Jest to tzw. notacja pozycyjna (ang. positional notation). Poszczególne liczby z agregatu tablicy
przypisywane są kolejnym składowym. Stanowi to odpowiednik ciągu instrukcji
wakacje (lipiec) := morze;
wakacje (sierpien) := gory;
wakacje (wrzesien) := jeziora;
Oczywiście ilość elementów w agregacie tablicy musi być równa ilości elementów w samej tablicy.
Oprócz notacji pozycyjnej możemy używać notacji nazywanej (ang. named notation), w której
określamy wyraźnie, na które miejsce w tablicy ma być wstawiona dana wartość:
wakacje := (lipiec => morze, wrzesien=>jeziora, sierpien => gory);
Unikamy w ten sposób pomyłek (porządek podstawiania nie ma znaczenia), a poza tym łatwo jest
odczytać, jaką wartość przypisaliśmy poszcególnym elementom tablicy. Notacja pozycyjna zezwala
ponadto na uzywanie słowa others pozwalającego na nadanie wartości wszystkim elementom,
którym nie została ona nadana bezpośrednio:
wakacje : (lipiec => morze, others => wlasny_dom);
Podstawienie takie oznacza, że w lipcu byliśmy nad morzem, zaś pozostałe miesiące wakacji
spędziliśmy w domu... Używając tego sposobu można również przypisać jednakową wartość wszystkim
elementom tablicy:
wakacje := (others => wlasny_dom);
Można to również zrobić pisząc
wakacje := (miesiace_wakacyjne => wlasny_dom);
albo
wakacje := (lipiec.. wrzesien => wlasny_dom);
Notacja pozycyjna oferuje jeszce kilka możliwości:
ciag_A := (1..3 => 2, others => 0);
(elementy o indeksie 1, 2 i 3 otrzymają wartość 2, pozostałe - wartość 0),
ciag_A := (2|4|6|8|10 => 1, others => 0);
(wyrazy ciągu o numerach parzystych otrzymują wartość 1, pozostałe - 0).
Notację pozycyjną i nazywaną można łączyć tylko w jednym przypadku: gdy w agregacie tablicy
wymienimy kolejno pewną ilość wartości składowych (w notacji pozycyjnej), a następnie pozostałym
elementom (mającym wyższe indeksy) nadamy taką samą wartość używając others (notacja
nazywana):
ciag_A := (1, 12, 0, 3, others => 9);
Zauważmy, że słowo others, jeśli występuje, musi być ostatnią pozycją w agregacie tablicy. Należy
również pamiętać, że elementami agregatu tablicy nie muszą byc konkretne wartości, jak w powyższych
przykładach, lecz również zmienne, stałe i wyrażenia.
81
Z tablic możemy "wydobywać" nie tylko pojedyncze elementy, ale również podtablice - fragmenty
tablicy wyjściowej (podobnie jak podłańcuchy z łańcucha). Tablice i ich fragmenty wolno nam (o ile
należą do tego samego typu) podstawiać, porównywać przy użyciu operatorów = i /=, a także łączyć
(operatorem jest wtedy &, podobnie jak dla typu string). Oczywiście podstawiając czy łącząc
fragmenty tablic musimy zwracać uwagę na ich rozmiary, w przeciwnym wypadku otrzymamy
Constraint_Error. Jeżeli składowe tablic są typu dyskretnego, dozwolone jest również
porównywanie ich przy użyciu operatorów <, <=, >, >=.
-- rozne eksperymenty na ciagach:
-- porownywanie, podstawianie...
with ada.text_io,ada.integer_text_io,ada.float_text_io;
use ada.text_io, ada.integer_text_io,ada.float_text_io;
procedure pr36 is
type dni is (pon, wt, sr, czw, pt, sob, nie);
type ciag_dluzszy is array(1..10) of integer;
type ciag_krotszy is array(1..5) of integer;
type ciag_rzeczywisty is array(1..5) of float;
type ciag_wyliczeniowy is array(1..10) of dni;
cd : ciag_dluzszy;
ck1,ck2 : ciag_krotszy;
cr1,cr2: ciag_rzeczywisty;
cw1, cw2: ciag_wyliczeniowy;
begin
cd := (2|4|6|8|10 => 1, others => 0);
ck1:=(others=>7);
ck2:=(others=>7);
cr1:=(others=>3.2);
cr2:=(others=>4.1);
cw1:=(others=>pt);
cw2:=cw1;
-- ck1(1..2):=cd(3..4);
-- nie da sie zrobic takiego
-- podstawienia (ciagi roznego typu)
ck1(1):=cd(3);
-- tak sie da (elementy w ciagach sa
-- tego samego typu)
ck1(1..3):=ck2(2..4);
-- tak sie da (ten sam typ tablicowy)
-- tak mozna porownac tablice tego
-- samego typu
if ck1=ck2 then
put_line("tablice - ""krotsze ciagi calkowite"" - rowne ");
else
put_line("tablice - ""krotsze ciagi calkowite"" - rozne");
end if;
if cr1=cr2 then
put_line("tablice - ""ciagi rzeczywiste"" - rowne ");
else
put_line("tablice - ""ciagi rzeczywiste"" - rozne");
end if;
if cw1=cw2 then
put_line("tablice - ciagi dni - rowne ");
else
81
put_line("tablice - ciagi dni - rozne");
end if;
-- te tablice nie sa tego samego typu,
-- zatem nie mozna ich porownac
-- if ck1(1..2)=cd(1..2) then put("kawalki rowne"); end if;
-- takie porownywanie mozna zrobic
-- tylko dla tablic o skladowych dyskretnych
if ck1>ck2 then
put_line("tablica liczb calkowitych ck1 jest ""wieksza""");
else
put_line("tablica liczb cakowitych ck1 nie jest ""wieksza""");
end if;
if cw1>cw2 then
put_line("tablica dni cw1 jest ""wieksza""");
else
put_line("tablica dni cw1 nie jest ""wieksza""");
end if;
-- niewykonalne - typ float nie jest dyskretny
--if cr1>cr2 then
--
put_line("tablica liczb rzeczywistych cr1 jest ""wieksza""");
--else
--
put_line("tablica
liczb
rzeczywistych
cr1
nie
jest
""wieksza""");
--end if;
cr2:=cr1(1..2) & cr2(2..4); -- wykonalne
cw1:=cw1(1..5) & cw2(6..10);
--cw1:=cw1(2..4) & cw2(3..6); -- niewykonalne - niezgodne rozmiary
--ck1:=ck2(1..2) & cd(3..5); -- niewykonalne - niezgodne typy tablic
end pr36;
Dla tablic określone są następujące atrybuty:
zmienna_tablicowa'first
zwracający najmniejszą wartość indeksu wdanej tablicy
zmienna_tablicowa'last
zwracający największą wartość indeksu wdanej tablicy
zmienna_tablicowa'range
zwracający zakres indeksu tablicy (odpowiednik
tablica'first .. tablica'last)
zmienna_tablicowa'length
zwracający długość tablicy (ilość jej elementów).
Oto przykład zastosowania atrybutów w praktyce. Chcąc działać na ciągu innej długości wystarczy
jedynie zmienić deklarację typu ciag, cała reszta może pozostać bez zmian.
-- sortowanie ciagu liczb calkowitych
-- oraz znajdowanie najwiekszego elementu
-- tego ciagu
with ada.text_io,ada.integer_text_io;
use ada.text_io,ada.integer_text_io;
procedure pr37 is
type ciag is array(1..10) of integer;
81
c,d:ciag;
pom,max,miejsce_maxa:integer;
begin
put("mozesz wprowadzic ciag liczb calkowitych o dlugosci ");
put(c'length,0);new_line;
put_line("podaj wyrazy ciagu : ");
for i in c'range loop
put("c(");put(i,0);put(")= ");
get(c(i));
end loop;
-- znajdowanie najwiekszej liczby w ciagu
max:=c(c'first);
miejsce_maxa:=c'first;
for i in c'first+1..c'last loop
if c(i)>max then
max:=c(i);
miejsce_maxa:=i;
end if;
end loop;
put("Najwiekszy wyraz w ciagu to "); put(max,0); new_line;
put("Stoi on na miejscu "); put(miejsce_maxa,0);
new_line(3);
-- sortowanie ciagu
d:=c;
for i in d'first+1..d'last loop
for j in reverse i..d'last loop
if d(j)<d(j-1) then
pom:=d(j);
d(j):=d(j-1);
d(j-1):=pom;
end if;
end loop;
end loop;
put_line("Oto Twoj ciag: ");
for i in c'range loop
put(c(i),0);put(' ');
end loop;
new_line;
put_line("A oto Twoj ciag posortowany: ");
for i in d'range loop
put(d(i),0);put(' ');
end loop;
end pr37;
Szczególnym przypadkiem tablic jednowymiarowych są wektory logiczne (boolowskie) -
jednowymiarowe tablice złożone z elementów typu boolean. Oprócz podstawiania i porównywania
możemy dokonywać na nich operacji logicznych, używając not, and, or , xor. Wszystkie powyższe
operatory (prócz not) wymagają jako argumentów dwóch tablic tego samego typu i długości (not
operuje na jednej tablicy). Otrzymujemy w ten sposob agregat tablicy, którego składowe są wynikami
wykonania odpowiedniej operacji logicznej na odpowiednich składowych tablic będących
argumentami.
-- program ilustruje dzialania
-- na wektorach logicznych
81
with ada.text_io,ada.integer_text_io;
use ada.text_io,ada.integer_text_io;
procedure pr38 is
package b_io is new ada.text_io.enumeration_io(boolean);
use b_io;
type wektor_logiczny is array(1..4) of boolean;
p,q: wektor_logiczny;
w_and,w_or,w_xor,w_not:wektor_logiczny;
k:positive_count;
begin
p:=(others=>true);
put_line("Podaj wektor q - mozliwe wartosci jego skladowych" &
" to TRUE i FALSE");
for i in q'range loop
put("q(");put(i,0);put(")= ");
get(q(i));
end loop;
w_and:=p and q;
w_or:=p or q;
w_xor:=p xor q;
w_not:=not q;
put_line("A oto wyniki dzialan na wektorach: ");
put("wektor p : ");
k:=13;
for i in p'range loop k:=k+7; set_col(k); put(p(i)); end loop;
new_line;
put("wektor q : ");
k:=13;
for i in q'range loop k:=k+7; set_col(k); put(q(i));end loop;
new_line;
put("wektor not q : ");
k:=13;
for i in q'range loop k:=k+7; set_col(k); put(w_not(i));end loop;
new_line;
put("wektor p and q : ");
k:=13;
for i in w_and'range loop k:=k+7; set_col(k); put(w_and(i));end loop;
new_line;
put("wektor p or q : ");
k:=13;
for i in w_or'range loop k:=k+7; set_col(k); put(w_or(i));end loop;
new_line;
put("wektor p xor q: ");
k:=13;
for i in w_xor'range loop k:=k+7; set_col(k); put(w_xor(i));end loop;
end pr38;
Tablice wielowymiarowe
Oprócz poznanych dotąd tablic jednowymiarowych istnieją również tablice wielowymiarowe.
Analogicznie jak tablice jednowymiarowe przechowują one dane należące do jednego typu, bardzo
podobna jest również ich deklaracja:
zmienna_tablicowa :
array (określenie_indeksu_1, ... , określenie_indeksu_n)
81
of typ_elementów;
(jest to anonimowy typ tablicowy), lub
type typ_tablicowy is
array (określenie_indeksu_1, ... , określenie_indeksu_n)
of typ_elementów;
Określenia_indeksów mają taką samą postać, jak w przypadku tablic jednowymiarowych, może ich być
dowolnie dużo. Poszczególne indeksy nie muszą być tego samego typu. Oto przykłady deklaracji tablic
wielowymiarowych:
dwa_na_trzy : array (1..2, 1..3) of integer;
type macierz_kwadratowa is array (integer range 1..2 ,
integer range 1..2 ) of float;
m1: macierz_kwadratowa;
type miesiace is (styczen, luty, marzec, kwiecien, maj czerwiec,
lipiec, sierpien, wrzesien, pazdziernik, listopad,
grudzien);
subtype miesiace_wakacyjne is miesiace range lipiec .. wrzesien;
type miejsca is (morze, gory, jeziora, wlasny_dom);
type wakacje is array (1996..1998, miesiace_wakacyjne) of miejsca;
wakacje_Ani, wakacje_Piotra: wakacje;
type miasta is (Warszawa, Moskwa, Praga, Londyn, Paryz);
type opady_w_miastach is array (miasta, miesiace_wakacyjne,
1987 .. 1997) of float;
pomiar : opady_w_miastach;
x: integer:=3;
type xx is array (x..x+2, 2*x..3*x, 4..x+2) of miesiace;
xxx: xx;
Do składowych tablic wielowymiarowych odwołujemy się poprzez nazwę tablicy i wartości indeksu
umieszczone w nawiasach okrągłych:
m1(1,1):=12;
wakacje_Ani(1998,lipiec):=morze;
Nadawanie wartości poszczególnym składowym tablicy wielowymiarowej może odbyć się w pętli
(a raczej kilku pętlach):
for i in 1..2 loop
for j in 1..2 loop
m1 (i,j) := 2;
end loop;
end loop;
for i in miasta loop
for m in miesiace_wakacyjne loop
for r in 1987 .. 1997 loop
pomiar (i, m, r) := 12.0;
end loop;
end loop;
end loop;
lub przy użyciu agregatu tablicy:
wakacje_Piotra := (1996 .. 1998 => (lipiec..wrzesien => wlasny_dom));
wakacje_Piotra := (1996..1998 => (miesiace_wakcyjne =. wlasny_dom));
wakacje_Piotra := (others => (others => wlasny_dom));
81
Dozwolone jest używanie zarówno notacji pozycyjnej, jak i nazywanej dla każdego z wymiarów.
Można je również dowolnie ze sobą łączyć, choć notacja nazywana jest bardziej czytelna. Ograniczenia
dotyczące łączenia notacji pozycyjnej i nazywanej przedstawione przy okazji tablic jednowymiarowych
obowiązują nadal w obrębie pojedynczego podagregatu ujętego w nawiasy. Oto przykłady:
wakacje_Ani := ((morze, jeziora, morze), (gory, jeziora, wlasny_dom),
(wlasny_dom, morze, wlasny_dom));
wakacje_Ani := ( 1996 => (morze, jeziora, morze),
1997 => (gory, jeziora, wlasny_dom),
1998 => (wlasny_dom, morze, wlasny_dom) );
wakacje_Ani :=(
(lipiec => morze, sierpien => jeziora,
wrzesien => morze),
(lipiec =>gory, sierpien => jeziora,
wrzesien => wlasny_dom),
(lipiec =>wlasny_dom, sierpien => morze,
wrzesien => wlasny_dom) );
wakacje_Ani := (
1996 => (lipiec => morze, sierpien => jeziora, wrzesien =>morze),
1997 => (lipiec =>gory, sierpien => jeziora, wrzesien => wlasny_dom),
1998 =>(lipiec =>wlasny_dom, sierpien => morze,
wrzesien => wlasny_dom) );
m1 := ((0,1), (7,8));
m1 := ( 1=> (0,1), 2=> (7,8));
m1 := ( (1=>0, 2=>1), (1=>7, 2=>8));
m1 := (1=> (1=>0, 2=>1), 2=> (1=>7, 2=>8));
Z tablic wielowymiarowych nie możemy "wycinać kawałków", tak jak z tablic jednowymiarowych.
Możemy jednak sprawdzać, czy tablice te są równe, czy nie (używając =, /=), o ile należą one do tego
samego typu.
Dla tablic wielowymiarowych, analogicznie jak dla jednowymiarowych, określone są atrybuty
'First, 'Last, 'Length i 'Range, dotyczą jednak nie całej tablicy, lecz określonego indeksu
("wymiaru"):
subtype miesiace_wakacyjne is miesiace range lipiec..wrzesien;
type miejsca is (morze, gory, jeziora, wlasny_dom);
type wakacje is array (1996..1998, miesiace_wakacyjne) of miejsca;
wakacje_Ani: wakacje;
wakacje_Ani'first(1)
-- zwróci 1996
wakacje_Ani'last(2)
-- zwróci wrzesien
wakacje_Ani'range(1)
-- 1996..1998
wakacje_Ani'range(2)
-- lipiec..wrzesien
wakacje_Ani'length(1)
-- 3
wakacje_Ani'length(2)
-- 4
Jeżeli używając atrybutu pominiemy określenie wymiaru, domyślnie przyjmowane będzie jeden. Zatem
wakacje_Ani'first i wakacje_Ani'first(1) zwrócą tę samą wartość.
-- program oblicza srednia ilosc dni slonecznych
-- w miesiacach letnich w poszczegolnych miastach
with ada.text_io,ada.integer_text_io;
use ada.text_io,ada.integer_text_io;
procedure pr39 is
type miasta is (Berlin,Londyn, Paryz, Warszawa, Rzym);
type miesiace is (styczen, luty, marzec, kwiecien, maj,
czerwiec, lipiec, sierpien, wrzesien,
pazdziernik, listopad, grudzien);
81
package mies_io is new ada.text_io.enumeration_io(miesiace);
package miasta_io is new ada.text_io.enumeration_io(miasta);
use mies_io,miasta_io;
type ilosc_dni is array
(miasta, miesiace range czerwiec..sierpien) of integer;
sloneczne : ilosc_dni;
s:integer;
begin
put("Podaj ilosc dni slonecznych w poszczegolnych miastach" &
" i miesiacach: ");
new_line(2);
for i in sloneczne'range(1) loop
for j in sloneczne'range(2) loop
put(i);put(" / ");put(j);put(" : ");
get(sloneczne(i,j));
end loop;
end loop;
new_line(3);
put("W okresie ");put(sloneczne'first(2));
put(" - ");put(sloneczne'last(2));
put_line(": ");
for i in miasta loop
s:=0;
for j in sloneczne'range(2) loop
s:=s+sloneczne(i,j);
end loop;
s:=s/sloneczne'length(2);
put("srednia ilosc dni slonecznych w miesiacu w miescie ");
put(i);
put(" wyniosla ");put(s,0);
new_line;
end loop;
new_line;
put("Wyniki zostaly zaokraglone do wartosci calkowitych.");
new_line;
end pr39;
Tablice anonimowe (anonimowy typ tablicowy)
Zarówno w przypadku tablic jedno-, jak i wielowymiarowych widzieliśmy przykłady tzw.
anonimowego typu tablicowego. Typ taki nie ma własnej nazwy i nie istnieje jako coś odrębnego -
zdefiniowany jest niejako na użytek konkretnej zmiennej. Każda zmienna będąca anonimową tablicą
należy do innego typu, niemożliwe jest zatem ich porównywanie czy przypisywanie:
Oto dwa fragmenty programów. W pierwszym z nich deklarujemy odrębny typ tablicowy, zaś w drugim
używamy typu anonimowego.
type towary is (garnitury, marynarki, spodnie);
type produkcja is array (towary) of natural;
prod_IV, prod_V : produkcja;
...
prod_IV := (garnitury => 3_000, marynarki => 2_000,
spodnie => 5_000);
prod_V := prod_IV;
-- możliwe do wykonania
prod_V(garnitury) := prod_IV(spodnie);
-- również możliwe - składniki
-- są tego samego typu
if prod_V > prod_IV then
...
-- także wykonalne,
-- tablice są jednowymiarowe
-- i tego samego typu
81
type towary is (garnitury, marynarki, spodnie);
prod_IV, prod_V : array (towary) of natural;
-- tablice anonimowe
...
prod_IV := (garnitury => 3_000, marynarki => 2_000,
spodnie => 5_000);
prod_V := prod_IV;
-- błąd, tablice nie należą do tego samego typu
prod_V(spodnie):=prod_IV(garniury); -- prawidłowo - elementy tablic są
-- tego samego typu
if prod_V > prod_IV then ...
-- błąd - tablic różnych typów nie można
-- porównać
Jak widać, stosowanie tablic anonimowych pozbawione jest sensu, jeżeli w programie używamy kilku
tablic o takim samym wyglądzie (a więc mogących należeć do tego samego typu).
Tablice dynamiczne
Tablicą dynamiczną nazywamy tablicę, której rozmiar staje się znany dopiero w momencie
wykonywania programu. Można zdefiniować w ten sposób zarówno tablicę anonimową, jak i typ
posiadający nazwę. Do tego celu używamy tzw. bloku programu (więcej informacji na ten temat można
będzie znaleźć w dalszej części książki):
procedure xxx is
n: integer;
begin
get(n);
declare
-- początek bloku
x: array (1..n) of integer;
begin
-- tu używamy tablicy x
end;
-- koniec bloku
end xxx;
W trakcie kompilacji programu rozmiar tablicy x nie jest jeszcze znany. Właściwa deklaracje tablicy x,
a więc zarezerwowanie dla niej odpowiednio dużego obszaru pamięci, ma miejsce po wczytaniu n
(czyli po uruchomieniu programu). Wykonywane są wówczas instrukcje zawarte w części deklaracyjnej
bloku - między declare a begin. Tablica x "żyje" tylko wewnątrz bloku, w którym została
zadeklarowana, a więc do słowa end (oczywiście jeśli słowo end kończące blok poprzedza
bezpośrednio end kończący procedurę, tablica taka funkcjonuje od momentu zadeklarowania do końca
programu). Możliwość deklarowania takich tablic jest wielkim udogodnieniem - napisany przez nas
program może obsługiwać dowolnie dużo danych, wykorzystując za każdym razem inną,
odpowiadającą potrzebom ilość pamięci.
-- dodawanie macierzy kwadratowych
-- o dowolnym rozmiarze
with ada.text_io,ada.float_text_io,ada.integer_text_io;
use ada.text_io,ada.float_text_io,ada.integer_text_io;
procedure pr40 is
n: integer;
begin
put_line("Program wykonuje dodawanie macierzy kwadratowych. ");
put("Podaj rozmiar macierzy> ");
get(n);
declare
type macierz_kwadratowa is array(1..n,1..n) of float;
t1,t2,tw: macierz_kwadratowa;
k: positive_count;
81
begin
-- wczytywanie tablic
put_line("Podaj wyrazy pierwszej macierzy: ");
for i in t1'range(1) loop
for j in t1'range(2) loop
put('[');put(i,0);put(',');put(j,0);put("]=");
get(t1(i,j));
end loop;
end loop;
put_line("Podaj wyrazy drugiej macierzy: ");
for i in t2'range(1) loop
for j in t2'range(2) loop
put('[');put(i,0);put(',');put(j,0);put("]=");
get(t2(i,j));
end loop;
end loop;
-- obliczanie sumy
for i in tw'range(1) loop
for j in tw'range(2) loop
tw(i,j):=t1(i,j)+t2(i,j);
end loop;
end loop;
-- wypisywanie wyniku
-- w wypadku duzych rozmiarow
-- moze byc niezbyt udane
new_line(2);
for i in t1'range(1) loop
k:=1; set_col(k);
for j in t1'range(2) loop
put(t1(i,j),aft=>2,exp=>0);k:=k+10; set_col(k);
end loop;
end loop;
new_line; set_col(k/2);put('+');new_line;
for i in t1'range(1) loop
k:=1; set_col(k);
for j in t2'range(2) loop
put(t2(i,j),aft=>2,exp=>0);k:=k+10; set_col(k);
end loop;
end loop;
new_line;set_col(k/2);put('=');new_line;
for i in t1'range(1) loop
k:=1; set_col(k);
for j in tw'range(2) loop
put(tw(i,j),aft=>2,exp=>0);k:=k+10; set_col(k);
end loop;
end loop;
end; -- koniec bloku
end pr40;
Typy tablicowe bez okeślenia rozmiaru
Istnienie typów tablicowych bez określenia rozmiaru (ang. unconstrained array types) umożliwia
tworzenie tablic należących do jednego typu, lecz mających różne rozmiary i zakresy indeksów. Typ
taki definiujemy następująco:
type nazwa_typu is array (typ_indeksu range <>) of typ_elementów;
np.
type sznur_cyfr is array (integer range <>) of integer;
Jak widać, w deklaracji tej w miejscu określenia zakresu indeksu znajduje się symbol <> (ang. box),
oznaczający brak określenia zakresu indeksu. Zdefiniowany jest natomiast typ indeksu i typ elementów
81
należących do tej tablicy. Rozmiar tablicy (i zakres indeksu dla danej tablicy) określamy dopiero w
momencie deklarowania zmiennej należącej do danego typu:
sznur10: sznur_cyfr(1..10);
sznur6: sznur_cyfr(0..5);
sznur2: sznur_cyfr(-3..-2);
Oczywiście podawane granice zakresu indeksu nie mogą przekraczać granic zakresu typu indeksu.
Każdy z zadeklarowanych powyżej "sznurów" jest tablicą o innej długości, innym zakresie i wartości
indeksu, należy jednak do tego samego typu - sznur_cyfr. Typ tablicowy bez określenia rozmiaru
umożliwia również tworzenie podtypów:
type sznur_liczb_calkowitych is array (integer range <>) of integer;
subtype sznurek is sznur_liczb_calkowitych (1..10);
s1,s2:sznurek;
Indeks każdej z tablic s1, s2 przyjmuje wartości od 1 do 10. Zauważmy, że kolejny typ tablicowy - już
o określonym rozmiarze - jest tutaj zdefiniowany jako podtyp typu o rozmiarze nie określonym. w
poprzednim przykładzie - gdy deklarowaliśmy tablice pisząc np.
s1: sznur_cyfr(1..10);
deklarowaliśmy w zasadzie anonimowy typ tablicowy będący podtypem typu sznur_cyfr.
Porównajmy oba sposoby deklarowania tablic oraz użyteczność typu tablicowego bez określenia
rozmiaru:
with ada.text_io;
use ada.text_io;
procedure pr41_1 is
type tab1 is array (integer range 1..10) of integer;
type tab2 is array (integer range 1..5) of integer;
t1:tab1:=(others=>0);
t2:tab2:=(others=>2);
begin
t1(1):=t2(1);
-- t1(1..3):=t2(1..3); -- niewykonalne
-- niewykonalne
-- if t1(1..5)=t2(1..5) then
-- put("poczatkowe kawalki tablic sa rowne");
-- end if;
-- niewykonalne
-- if tab1=tab2 then
-- put("cale tablice tez sa rowne");
-- end if;
-- niewykonalne
-- if t1(1..5)>t2(1..5) then
-- put("poczatkowy kawalek pierwszej tablicy jest wiekszy");
-- end if;
-- niewykonalne
-- if tab1>tab2 then
-- put("pierwsza tablica jest wieksza");
-- end if;
end pr41_1;
with ada.text_io;
use ada.text_io;
81
procedure pr41_2 is
type typ1 is array (integer range <>) of integer;
subtype tab1 is typ1(1..10);
subtype tab2 is typ1(1..5);
t1:tab1:=(others=>0);
t2:tab2:=(others=>2);
begin
t1(1):=t2(1);
t1(1..3):=t2(1..3);
if t1(1..5)=t2(1..5) then
put("poczatkowe kawalki tablic sa rowne");
end if;
-- niewykonalne
-- if tab1=tab2 then
-- put("cale tablice sa rowne");
-- end if;
if t1(1..5)>t2(1..5) then
put("poczatkowy kawalek pierwszej tablicy jest wiekszy");
end if;
-- niewykonalne
-- if tab1>tab2 then
-- put("cale - pierwsza wieksza");
-- end if;
end pr41_2;
Zmienne i stałe należące do typu tablicowego bez określenia rozmiaru możemy deklarować również
ograniczając zakres indeksu niejawnie - poprzez nadanie tablicy wartości początkowej przy użyciu
agregatu:
type tab1 is array (positive range <>) of integer;
t1:tab1 := (4,2,0);
type tab2 is array (integer range <>) of integer;
t2:tab2 := (29.30,30);
Indeks tablicy t1 przyjmuje wartości od 1 do 3, zaś tablicy t2 - od integer'first do
(integer'first+2). Jak widać, jako początek zakresu indeksu przyjmowana jest najmniejsza
wartość w typie danych wyspecyfikowanym jako typ indeksu. Można tego uniknąć stosując w agregacie
tablicy notację nazywaną:
t2: tab2 := (1=>29, 2=>30, 3=>30);
Zakres indeksu przyjmuje wówczas żądaną wartość (tutaj - od 1 do 3).
Jak zauważyliśmy poprzednio, każda z tablic należąca do typu tablicowego bez określenia rozmiaru
należy właściwie do pewnego jego podtypu określonego przez ograniczenie zakresu indeksu.
Przypisując tablicy wartość przy użyciu agregatu możemy mieć do czynienia z niejawną konwersją
tegoż agregatu do odpowiedniego podtypu. Zjawisko to nosi nazwę przesuwania (ang. sliding). Ma
miejsce tylko w niektórych przypadkach, nie występuje na przykład przy agregatach zawierających
słowo kluczowe others:
with ada.text_io,ada.integer_text_io;
use ada.text_io,ada.integer_text_io;
procedure pr42 is
type tab is array (positive range <>) of integer;
t: tab(1..5);
81
begin
t:=(10..11=>1, 12..14=>4);
-- agregat przesuwa sie tak, ze t(1) i t(2) maja wartosc 1
-- pozostale - wartosc 4
for i in 1..5 loop put(t(i),0);put(' ');end loop; new_line;
t:=(5..6=>9, others=>0);
-- nie ma przesuniecia - t(5) ma wartosc 9
-- pozostale - wartosc 0
for i in 1..5 loop put(t(i),0);put(' ');end loop; new_line;
t:=(3..5=>9, others=>0);
-- nie ma przesuniecia - t(3) do t(5) maja wartosc 9
-- pozostale - wartosc 0
for i in 1..5 loop put(t(i),0);put(' ');end loop; new_line;
t:=(7..9=>0, others=>4);
-- nie ma przesuniecia - wszystkie skladowe tablicy maja
-- wartosc 4
for i in 1..5 loop put(t(i),0);put(' ');end loop; new_line;
t:=(8,8,8,others=>0);
-- nie ma przesuniecia - t(1) do t(3) maja wartosc 8,
-- pozostale - wartosc 0
for i in 1..5 loop put(t(i),0);put(' ');end loop; new_line;
end pr42;
Tablice nie mające określenia rozmiaru mogą być zarówno jedno-, jak i wielowymiarowe. w przypadku
tablic wielowymiarowych brak określenia zakresu indeksu występować musi we wszystkich
wymiarach. Oto przykład prawidłowego użycia typu nie majacego określenia rozmiaru:
type macierz is array (positive range <>,
positive range <>) of float;
m2: macierz(1..2,1..2);
subtype macierz_3_na_3 is macierz(1..3,1..3);
m3_1, m3_2: macierz_3_na_3;
i przykłady nieprawidłowych definicji typów:
type inna_macierz is array (positive range <>,1..3) of float;
type inna_macierz1 is array (1..5,positive range <>) of float;
Warto wiedzieć, że typy string i wide_string są zdefiniowane jako typy tablicowe nie mające
określenia rozmiaru:
-- zdefiniowane w pakiecie Standard
type String is array (Positive range <>) of Character;
type Wide_String is array (Positive range <>) of Wide_Character;
Operacje na tablicach
Dla tablic określone są następujące operacje:
•
Atrybuty 'first, 'last, 'length i 'range (opisane w poprzednich rozdziałach). w
przypadku typów tablicowych bez okreslenia rozmiaru zwracają odpowiednie wartości indeksu
podtypu typu rodzicielskiego.
•
Operacje logiczne (not, and, or, xor), wykonywalne jedynie dla jednowymiarowych tablic
złożonych z elementów typu boolean; tablice te muszą być tego samego typu i tej samej długości
(zob. program pr38).
•
Konkatenacja (&) określona dla jednowymiarowych tablic tego samego typu.
81
•
"Wycinanie" fragmentów z tablicy (ang. array slicing) określone dla tablic jednowymiarowych
type tablica is array(integer range 1..10) of integer;
t1:tablica:=(others=>6);
t2:tablica:=(others=>8);
t1(1..3):=t1(2..4);
--tu "wycinamy" fragmenty
put(t1(2..8));
•
Operacja przypisania (:=) - przypisać sobie można tablice tego samego typu i rozmiaru
•
Konwersja typów. Tablica może być przekonwertowana do tablicy innego typu tylko wówczas, gdy
obie mają ten sam rozmiar, typ składowych oraz te same lub konwertowalne odpowiednie typy
indeksów.
type typ1 is array (positive range 1..10) of integer;
type typ2 is array (integer range 1..10) of integer;
t1 : typ1 := (others=>0);
t2 : typ2;
t2:=t1;
-- niewykonalne - niezgodność typów
t2:=typ2(t1); -- wykonalne dzieki konwersji
t1:=typ1(t2); -- wykonalne dzieki konwersji
•
Relacje <, <=, >, >= - wykonalne dla jednowymiarowych tablic o elementach typu dyskretnego.
Tablice muszą być tego samego typu. Porównywanie odbywa się element po elemencie, od lewej
do prawej, do momentu wykrycia składowych o różnych wartościach lub do wyczerpania sie
elementów w którejś tablicy. Jeżeli napotkana została różniąca tablice składowa, za "większą"
uznawana jest ta z tablic, w ktorej wymieniona wyżej składowa ma większą wartość. Jeżeli nie
wykryto różnic, natomiast składowe jednej z tablic zostały wyczerpane, jako "większa"
przyjmowana jest tablica dłuższa.
type typ1 is array (positive range 1..3) of integer;
t1,t11 : typ1;
t1 := (1,2,4);
t11 := (1,2,0);
put( t1>t11 );
-- wypisze TRUE
put( t1(1..2) > t1(1..3));
-- wypisze FALSE
•
Test równości (=, /=). Można porównywać tablice tego samego typu. Dwie tablice są równe, gdy
mają tę samą ilość składowych, a odpowiednie składowe obu tablic są jednakowe.
•
Test przynależności tablicy do podtypu (in). Testowane w ten sposób tablica i podtyp muszą
należeć do tego samego typu bazowego. Wynik jest typu boolean - true, gdy testowana tablica
ma dla mażdego z wymiarów taki sam zakres indeksu jak podtyp. do którego przynależność
testujemy, false w przeciwnym przypadku.
type macierz is array (positive range <>,
positive range <>) of float;
subtype macierz_3_na_3 is macierz(1..3,1..3);
subtype macierz_2_na_2 is macierz(1..2,1..2);
subtype tez_macierz_2_na_2 is macierz(2..4,1..3);
m2: macierz(1..2,1..2);
m3_1, m3_2: macierz_3_na_3;
put(m2 in macierz_2_na_2);
-- wypisze TRUE
put(m3_1 in macierz_2_na_2);
-- wypisze FALSE
put(m2 in tez_macierz_2_na_2);
-- wypisze FALSE
Rekordy
Poznana w poprzednich rozdziałach złożona struktura danych - tablica - składała się z elementów
jednego typu. Możliwe jest również tworzenie struktur, w których typ każdej ze składowych może być
81
inny. Taką złożoną strukturą danych jest rekord. Podobnie jak w przypadku tablic, do rekordu
(przechowującego np. imię i nazwisko osoby - dane typu łańcuchowego, jej rok urodzenia i wzrost -
dane typu positive itp) możemy zarównoodwoływać się za pomocą pojedynczego identyfikatora,
jak i działać na poszczególnych jego składnikach. Kolejną analogią są agregaty rekordów, które -
podobnie jak w przypadku tablic - pozwalają na przypisanie wartości równocześnie wszystkim
składowym (tu zwanym polami). Także i tu można - jak w tablicach stosować notację pozycyjną lub
nazywaną.
Rekordy "zwykłe" (bez wyróżników)
Typ rekordowy definiujemy następująco:
type nazwa_typu_rekordowego is record
pole_1 : typ_pola_1 ;
...
pole_n : typ_pola_n ;
end record;
Pola jednego typu możemy zadeklarować równocześnie, oddzielając ich nazwy przecinkami, np.
type punkt_plaszczyzny is record
x,y : float;
end record;
type osoba is record
imie: string (1..15);
nazwisko: string(1..30);
rok_urodzenia: positive;
wzrost: positive;
stanu_wolnego: boolean;
end record;
Zmienne danego typu deklarujemy w zwykly sposób:
student : osoba;
punkt_A : punkt_plaszczyzny;
zaś wartości przypisujemy im bądź używając kolejno poszczególnych pól rekordu - do których
odwołujemy się poprzez nazwę zmiennej rekordowej i oddzieloną od niej kropką nazwę pola rekordu
(student.imie, punkt_A.x) - jak w poniższym przykładzie,
student.imie:="Grzegorz ";
student.nazwisko:="Brzeczyszczykiewicz ";
student.rok_urodzenia:=1974;
wzrost:=201;
stanu_wolnego:=true;
punkt_A.x:=2.0;
punkt_A.y:=-1.1;
bądź za pomocą agregatu rekordu, w którym możemy zastosować notację pozycyjną (wartości
poszczególnych pól wpisujemy w kolejności występowania tych pól w definicji typu rekordowego)
punkt_A:=(2.0,-1.1);
student:=("Grzegorz ", "Brzeczyszczykiewicz ", 1974,
201, true);
lub notację nazywaną:
punkt_A:=(x=>2.0, y=>-1.1);
student:=
81
(imie=>"Grzegorz ", nazwisko=>"Brzeczyszczykiewicz ",
wzrost=> 201, rok_urodzenia=>1974, stanu_wolnego=>true);
Wówczas, jak widać, kolejność składników nie ma znaczenia.
Podobnie jak w przypadku agregatów tablic, w niektórych sytuacjach można w agregatach rekordow
używać znaku | oraz słowa others . Oczywiście odnosić się one mogą tylko do komponentów
jednego typu:
punkt_A:=(others=>0.0);
punkt_A:=(x|y=>0.0);
Poprawne są również agregaty rekordów użyte w poniższym przykładzie:
type miasto is record
wojewodztwo:string(1..2);
odleglosc_od_Warszawy,
odleglosc_od_stolicy_wojewodztwa:natural;
ilosc_mieszkancow_w_tys:natural;
end record;
type miasto1 is record
wojewodztwo:string(1..2);
nadawana_rejestracja:string(1..2);
odleglosc_od_Warszawy, odleglosc_od_stolicy_wojewodztwa,
il_mieszkancow_w_tys:natural;
end record;
m1,m2:miasto;
m:miasto1;
...
-- przyklady dla typu miasto
m1:=(wojewodztwo=>"WA",others=>50);
m1:=(wojewodztwo=>"WR", odleglosc_od_stolicy_wojewodztwa=>10,
others=>300);
m1:=(wojewodztwo=>"RA",
odleglosc_od_Warszawy|odleglosc_od_stolicy_wojewodztwa=>80,
others=>20);
m1:=("WA",10,others=>20);
-- przyklady dla typu miasto1
m:=(wojewodztwo|nadawana_rejestracja=>"PL", others=>40);
m:=(odleglosc_od_Warszawy|odleglosc_od_stolicy_wojewodztwa=>0,
il_mieszkancow_w_tys=>10,others=>"WA");
Jak widać słowo others występuje zawsze na końcu agregatu rekordu.
W agregatach rekordów, inaczej niż w przypadku tablic, niedozwolone jest natomiast używanie
zakresów (..). Tak więc
punkt_A:=(1..2=>3.0);
jest zapisem nieprawidłowym.
Możliwe jest łączenie w jednym agregacie notacji pozycyjnej i nazywanej (przy czym występować one
muszą w tej właśnie kolejności):
type xxx is record
x,y,z:integer;
end record;
81
a:xxx;
a:=(1,2,z=>3);
-- zapis prawidłowy
a:=(x=>1,2,3);
-- błąd
Nadawanie wartości zmiennym rekordowym może odbywać się również w inny sposób. Otóż typom
rekordowym (jako jedynym w Adzie95) można nadać wartość początkową już w momencie
definiowania tych typów:
type punkt_ekranu is record
x : natural range 0..80 := 0;
y : natural range 0..120 := 0;
end record;
type osoba is record
imie,nazwisko : string(1..20) := (others=>' ');
wiek : integer := 0;
end record;
(możliwe jest również nadanie wartości początkowych tylko niektórym polom rekordu):
type pracownik is record
imie, nazwisko : string(1..20) := (others=>' ');
staz_pracy : integer;
end record;
I tak na przykład każda z deklarowanych zmiennych typu punkt_ekranu będzie miała składowe
o wartości 0, chyba że postanowimy inaczej:
p : punkt_ekranu;
-- m.x i m.y mają wartość 0
q : punkt_ekranu := (1,2);
-- m.x=1, m.y=2
Należy pamiętać, że zmienne (dowolnego typu), którym nie została nadana wartość początkowa,
posiadają już w chwili rozpoczęcia wykonywania programu pewną wartość, stanowiącą interpretację
zawartości komórek pamięci przeznaczonych do przechowywania tej zmiennej. Nieznajomość tego
faktu może być przyczyną pisania programów dających bardzo dziwne wyniki...
Predefiniowanymi operacjami określonymi dla typów rekordowych są podstawienie (:=)
i porównywanie (=, /=). Oczywiście operacje te możemy wykonywać jedynie na elementach
należących do tego samego typu.
Dla zmiennych p1, p2 typu punkt_ekranu podstawienie
p1:=p2;
jest odpowiednikiem wykonania kolejno instrukcji
p1.x:=p2.x;
p1.y:=p2.y;
Podczas operacji porównywania rekordów porównywane są ich kolejne pola.
Oprócz deklarowania zmiennych możemy deklarować stałe należące do danego typu rekordowego.
Czynimy to w standardowy sposób:
type punkt_ekranu is record
x: natural range 0..80:=0;
y: natural range 0..120:=0;
end record;
type punkt_plaszczyzny is record
x,y:float;
end record;
81
gorny_rog_ekranu : constant punkt_ekranu := (0,0);
poczatek_ukladu_wspolrzednych : constant punkt_plaszczyzny
:=(0.0, 0.0);
(zauważmy, że wartość stałej należy podać niezależnie od tego, czy określiliśmy wartość początkową
dla danego typu).
W zadeklarowanych w ten sposób stałych typu rekordowego każda ze składowych jest traktowana jako
stała:
gorny_rog_ekranu.x := 3;
-- błąd, x jest stałą
Niemożliwe jest natomiast zadeklarowanie jako stałych poszczególnych składowych rekordu:
type jakis_typ is record
a: constant integer := 3; -- niedozwolone
b: float;
end record;
(zobacz jednak podrozdział Rekordy z wyróżnikami).
A oto praktyczny przykład wykorzystania typów rekordowych:
-- dzialania na liczbach zespolonych
with ada.text_io,ada.float_text_io;
use ada.text_io,ada.float_text_io;
procedure pr43 is
type zespolona is record
re,im:float;
end record;
i: constant zespolona := (re=>0.0, im=>1.0);
a,b : zespolona;
sprzezenie_a,sprzezenie_b,suma,roznica,iloczyn,
iloraz :zespolona;
mian:float;
begin
put_line("Podaj liczbe zespolona a : ");
put("a.re : ");get(a.re);
put("a.im : ");get(a.im);
put_line("Podaj liczbe zespolona b : ");
put("b.re : ");get(b.re);
put("b.im : ");get(b.im);
suma:=(a.re+b.re,a.im+b.im);
roznica.re:=a.re-b.re;
roznica.im:=a.im-b.im;
iloczyn:=(re=>a.re*b.re-a.im*b.im, im=>a.re*b.im+a.im+b.re);
mian:=b.re**2+b.im**2;
iloraz.re:=(a.re*b.re+a.im*b.im)/mian;
iloraz.im:=(b.re*a.im-a.re*b.im)/mian;
sprzezenie_a:=(re=>a.re,im=>-a.im);
sprzezenie_b:=(b.re,-b.im);
81
new_line(2);
if a=b then
put_line("Liczby a i b sa rowne");
else
put_line ("Liczby a i b nie sa rowne");
end if;
new_line;
put_line("Liczby sprzezone do : ");
put("a : "); put(sprzezenie_a.re,aft=>2,exp=>0);
if sprzezenie_a.im>=0.0 then put(" +");
else put(" -");end if;
put(abs(sprzezenie_a.im),aft=>2,exp=>0);
put_line(" i");
put("b : "); put(sprzezenie_b.re,aft=>2,exp=>0);
if sprzezenie_b.im>=0.0 then put(" +");
else put(" -");end if;
put(abs(sprzezenie_b.im),aft=>2,exp=>0);
put_line(" i");
new_line;
put_line("Wyniki dzialan : ");
new_line;
put("a + b = ");put(suma.re,aft=>2,exp=>0);
if suma.im>=0.0 then put(" +");
else put(" -");end if;
put(abs(suma.im),aft=>2,exp=>0);put_line(" i");
put("a - b = ");put(roznica.re,aft=>2,exp=>0);
if roznica.im>=0.0 then put(" +");
else put(" -");end if;
put(abs(roznica.im),aft=>2,exp=>0);
put_line(" i");
put("a * b = ");put(iloczyn.re,aft=>2,exp=>0);
if iloczyn.im>=0.0 then put(" +");
else put(" -");end if;
put(abs(iloczyn.im),aft=>2,exp=>0);
put_line(" i");
put("a / b = ");put(iloraz.re,aft=>2,exp=>0);
if iloraz.im>=0.0 then put(" +");
else put(" -");end if;
put(abs(iloraz.im),aft=>2,exp=>0);
put_line(" i");
end pr43;
Struktury danych "wielokrotnie złożone"
Nie ma właściwie żadnych ograniczeń w łączeniu ze sobą poznanych już złożonych struktur danych -
tablic i rekordów. Możemy zadeklarować tablicę złożoną z tablic, rekord o polu będącym innym
rekordem czy tablicą, tablicę złożoną z rekordów... Ilustrują to poniższe przykłady. Zwróćmy uwagę na
przedstawiony w nich sposób odwoływania się do elementów danej struktury:
-- przyklad rekordu, ktorego polem jest rekord
with ada.text_io,ada.integer_text_io;
use ada.text_io,ada.integer_text_io;
procedure pr44 is
81
type data is record
dzien: integer range 1..31;
miesiac: integer range 1..12;
rok: integer;
end record;
type osoba is record
imie:string(1..20):=(others=>' ');
nazwisko: string(1..30):=(others=>' ');
data_urodzenia:data;
end record;
ktos1, ktos2 : osoba;
rr,rm,rd,n:integer;
begin
put_line("Podaj dane dwoch osob:");
new_line;
put_line("OSOBA PIERWSZA");
put("Imie: "); get_line(ktos1.imie,n);
put("Nazwisko: "); get_line(ktos1.nazwisko,n);
put_line("Data urodzenia: ");
put("dzien: "); get(ktos1.data_urodzenia.dzien);
put("miesiac (liczba): ");get(ktos1.data_urodzenia.miesiac);
put("rok: "); get(ktos1.data_urodzenia.rok);
skip_line;
new_line(2);
put_line("OSOBA DRUGA");
put("Imie: "); get_line(ktos2.imie,n);
put("Nazwisko: "); get_line(ktos2.nazwisko,n);
put_line("Data urodzenia: ");
put("dzien: "); get(ktos2.data_urodzenia.dzien);
put("miesiac (liczba): ");get(ktos2.data_urodzenia.miesiac);
put("rok: "); get(ktos2.data_urodzenia.rok);
new_line(4);
put(ktos1.imie);set_col(40);put_line(ktos2.imie);
put(ktos1.nazwisko);set_col(40);put_line(ktos2.nazwisko);
rr:=ktos1.data_urodzenia.rok-ktos2.data_urodzenia.rok;
rm:=ktos1.data_urodzenia.miesiac-ktos2.data_urodzenia.miesiac;
rd:=ktos1.data_urodzenia.dzien-ktos2.data_urodzenia.dzien;
if rr=0 and rm=0 and rd=0 then
put("urodzili sie tego samego roku, miesiaca i dnia");
else
if rr=0 then
put("urodzili sie w tym samym roku");
if rm=0 then
put(", miesiacu ");
if rd=0 then
put("i dniu");
elsif rd>0 then
put("i pierwsza z tych osob jest starsza o ");
put(rd,0);put(" dni");
else
put("i pierwsza z tych osob jest mlodsza o ");
put(abs(rd),0);put(" dni");
end if;
end if;
else
put("roznica rocznikow tych osob: ");put(abs(rr),0);
81
end if;
end if;
end pr44;
A oto przykład zastosowania tablicy, której składowymi są tablice innego typu:
-- przyklad tablicy tablic
with ada.text_io,ada.integer_text_io,ada.float_text_io;
use ada.text_io,ada.integer_text_io,ada.float_text_io;
procedure pr45 is
type odpowiedzi_do_pytania is array(1..4) of boolean;
type tablica_odpowiedzi is array(1..5) of odpowiedzi_do_pytania;
type pytanie_i_odpowiedzi is array (0..4) of string(1..100);
type tablica_pytan is array(1..5) of pytanie_i_odpowiedzi;
test : tablica_pytan;
odpowiedzi : tablica_odpowiedzi;
podane_odp : tablica_odpowiedzi:=(others=>(others=>false));
op : integer;
punkty
: float:=0.0;
begin
--nadanie początkowych wartości
--pytaniom i odpowiedziom
test:=(others=>(others=>(others=>' ')));
test(1)(0)(1..33):="Swiatla drogowe moga byc uzywane:";
test(1)(1)(1..25):="poza obszarem zabudowanym";
test(1)(2)(1..22):="w obszarze zabudowanym";
test(1)(3)(1..53):="poza obszarem zabudowanym, na drogach "&
"szybkiego ruchu";
test(1)(4)(1..85):="w obszarze zabudowanym, lecz tylko na "&
"drogach o dopuszczalnej " &
" predk. powyzej 60 km/h";
odpowiedzi(1):=(true,false,false,false);
test(2)(0)(1..91):="Jadac pasem ruchu dla pojazdow powolnych "&
"i zblizajac sie do jego konca, kierujacy"&
" pojazdem";
test(2)(1)(1..53):="powinien zmienic pas zachowujac szczegolna"&
" ostroznosc";
test(2)(2)(1..52):="posiada pierwszenstwo przed jadacymi pasem"&
" sasiednim";
test(2)(3)(1..35):="powinien wlaczyc lewy kierunkowskaz";
test(2)(4)(1..44):="zmieniajac pas jest wlaczajacym sie do "&
"ruchu";
odpowiedzi(2):=(true,false,true,true);
test(3)(0)(1..28):="Wyprzedzanie jest zabronione";
test(3)(1)(1..41):="bezposrednio przed przejazdami kolejowymi";
test(3)(2)(1..40):="na przejazdach kolejowych i tramwajowych";
test(3)(3)(1..43):="bezposrednio przed przejsciami dla pieszych";
test(3)(4)(1..27):="na przejsciach dla pieszych";
odpowiedzi(3):=(true,true,true,true);
81
test(4)(0)(1..43):="W ktorych miejscach cofanie jest dozwolone:";
test(4)(1)(1..26):="na drodze jednokierunkowej";
test(4)(2)(1..21):="na drodze ekspresowej";
test(4)(3)(1..24):="na jezdni dwukierunkowej";
test(4)(4)(1..15):="na autostradzie";
odpowiedzi(4):=(true,false,true,false);
test(5)(0)(1..69):="Wjazd na skrzyzowanie jest zabroniony, " &
"jezeli wyswietlany jest sygnal";
test(5)(1)(1..8):="czerwony";
test(5)(2)(1..14):="zolty migajacy";
test(5)(3)(1..12):="zolty ciagly";
test(5)(4)(1..81):="zielony, ale warunki ruchu uniemozliwiaja"&
" dalsza jazde i opuszczenie skrzyzowania";
odpowiedzi(5):=(true,false,true,true);
--test
put_line(" ------ TEST - PRAWO JAZDY ----- ");
new_line;
put_line("Za kazda prawidlowa odpowiedz czastkowa dostajesz"&
" 0.25 pkt. ");
put_line("Jesli poprawnie odpowiesz na cale pytanie, dostajesz"&
" dodatkowo 1 pkt.");
new_line;
set_col(45);
put_line("Powodzenia!");
new_line(2);
for i in test'range loop
for j in 0..4 loop
if j/=0 then put(j,0);put(')');end if;
put_line(test(i)(j));
end loop;
new_line;
put_line("podaj numery poprawnych odpowiedzi," &
" 0 zakancza podawanie");
for k in podane_odp(2)'range loop
--1..4 (ilosc odp. w pytaniu)
put("> "); get(op);
exit when op=0;
podane_odp(i)(op):=true;
end loop;
new_line;
end loop;
-- zliczanie punktow
for i in odpowiedzi'range loop
for j in odpowiedzi(2)'range loop
if odpowiedzi(i)(j)=podane_odp(i)(j) then
punkty:=punkty+0.25; end if;
end loop;
if odpowiedzi(i)=podane_odp(i) then
punkty:=punkty+1.0;
end if;
end loop;
81
-- wyniki
put("Uzyskales ");put(punkty,exp=>0,aft=>1);put(" punktow");
end pr45;
A teraz tablica, której elementami są rekordy. Jednym z pól rekordu jest tablica:
-- tablica rekordow
with ada.text_io,ada.float_text_io,ada.integer_text_io;
use ada.text_io,ada.float_text_io,ada.integer_text_io;
procedure pr46 is
type przedmioty is (polski, niemiecki, angielski, matematyka, fizyka,
chemia, biologia, geografia, historia, wf);
package przedmioty_io is new enumeration_io(przedmioty);
subtype ocena is integer range 1..6;
type tabela_ocen is array (przedmioty) of ocena;
type uczen is record
imie : string(1..20) := (others=>' ');
nazwisko : string(1..30) := (others=>' ');
oceny : tabela_ocen;
srednia : float;
end record;
klasa : array(1..3) of uczen;
s, sr_klasy : float;
n : integer;
begin
-- wprowadzanie danych uczniow
sr_klasy:=0.0;
for i in klasa'range loop
put_line("Uczen nr " & integer'image(i));
put("Imie: ");get_line(klasa(i).imie,n);
put("Nazwisko: ");get_line(klasa(i).nazwisko,n);
s:=0.0;
for j in przedmioty loop
przedmioty_io.put(j);put(" ");
get(klasa(i).oceny(j));
skip_line;
s:=s+float(klasa(i).oceny(j));
end loop;
klasa(i).srednia:=s/float(przedmioty'pos(przedmioty'last)+1);
sr_klasy:=sr_klasy+klasa(i).srednia;
new_line;
end loop;
sr_klasy:=sr_klasy/float(klasa'length);
for i in klasa'range loop
put(klasa(i).imie);put(klasa(i).nazwisko);
put("Srednia ocen: "); put(klasa(i).srednia,exp=>0); new_line;
81
end loop;
new_line(2);
put("Srednia ocen klasy: "); put(sr_klasy,aft=>2,exp=>0);
end pr46;
Możliwe jest skrócenie sposobu odwołania do składowej struktury złożonej poprzez jej
przemianowanie (ang. renaming). Robimy to w części deklaracyjnej programu pisząc na przykład:
rok1:integer renames ktos1.data_urodzenia.rok;
(deklarujemy nową zmienną wiążąc ją ze składową rekordu - oczywiście muszą być one tego samego
typu). Odtąd do roku urodzenia osoby ktos1 mamy dostęp zarówno przez pełną jej nazwę
(ktos1.data_urodzenia.rok), jak i przez zmienną rok1.
Rekordy z wyróżnikami
Rekordy z wariantami
Rekordy z wariantami to takie rekordy, których zawartość, to znaczy ilość i rodzaj składowych, różni
się w zależności od wartości jednego z pól - wyróżnika. Wyobraźmy sobie na przykład, że chcemy
utworzyć bazę danych przechowującą imiona, nazwiska i daty urodzenia osób, a w przypadku, gdy
dana osoba posiada prawo jazdy - również jego kategorie. Można to zrobić tworząc dwa odrębne typy
rekordowe (osoby z prawem jazdy i bez niego), ale wówczas podczas pisania programów
wykorzystujących takie typy napotykamy na wiele istotnych ograniczeń. Nie można umieścić
wszystkich osób w jednej tablicy, zapisać do jednego pliku itp... Problem ten może rozwiązać
zadeklarowanie typu rekordowego w następujący sposób:
type kategoria is (A, B, C, D, E, T);
type tablica_kategorii is array (kategoria) of boolean;
type osoba (kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
Typ rekordowy osoba składa się z wyróżnika (ang. discriminant), traktowanego jako normalne pole
rekordu, oraz dwóch części - stałej (ang. invariant part), tzn. pól imię, nazwisko i
rok_urodzenia, które występują we wszystkich rekordach tego typu, oraz części wariantowa (ang.
variant part), mającej postać znanej nam instrukcji wyboru - case. W zależności od wartości
wyróżnika - pola kierowca - rekord zawiera pole o nazwie prawo_jazdy lub nie zawiera nic
więcej. W definicji typu rekordowego część wariantowa znajdować się musi po części stałej. Jej
tworzeniem rządzą zasady podobne, jak dla instrukcji case - musimy wyczerpać wszystkie możliwe
wartości wyróżnika, w przypadku gdy któraś z tych wartości nie powoduje dodania do rekordu nowych
składowych używamy słowa kluczowego null. Jeżeli dla kilku wartości dodajemy takie same pola,
możemy napisać
when wartość1 | wartość2 | ... => pole : typ_pola;
when wartość1 .. wartość2 => pole : typ_pola;
a także
when others => pole : typ_pola;
81
czy
when others => null;
"When others" musi być ostatnią pozycją w instrukcji wyboru.
Nie można deklarować pól o takich samych nazwach w różnych miejscach instrukcji wyboru. Poniższa
deklaracja
type dni_tyg is (pon, wt, sw, czw, pt, sob, nie);
type jakis_typ (k:dni_tyg) is record
pole1,pole2:integer;
case k is
when sob|nie => pole3 : float;
when pon => pole3 : float;
pole4 : integer;
when others => null;
end case;
end record;
jest nieprawidłowa (pole o nazwie pole3 występuje w dwóch miejscach części wariantowej).
Zmienne należące do zdefiniowanego wcześciej typu osoba deklarujemy określając wartość
wyróżnika:
sasiadka : osoba (false);
-- notacja pozycyjna
sasiad : osoba (kierowca => true);
-- notacja nazywana
Dany rekord (czyli dana zmienna czy stała) zostaje w ten sposób niejako "ograniczona" (ang.
constrained) - wyróżnik zachowuje się tutaj jak stała; nie można już zmienic jego wartości, a zatem
zmienna sasiadka pozostanie zawsze rekordem złożonym z czterech pól, a sasiad - z pięciu.
Powyższy sposób deklaracji przypomina nieco deklarowanie zmiennych należących do anonimowego
typu tablicowego. Podobnie jak tam zmienna, dla której określamy wartość wyróżnika, zostaje
zaliczona automatycznie do jednego z podtypów typu osoba (tutaj ma on dwa podtypy , dla wartości
wyróżnika kierowca odpowiednio true lub false). Oczywiście - znów podobnie jak w
przypadku tablic - możemy określić te podtypy w sposób bezpośredni, pisząc:
subtype osoba_bez_prawa_jazdy is osoba(kierowca=>false);
subtype osoba_z_prawem_jazdy is osoba(kierowca=>true);
sasiadka: osoba_bez_prawa_jazdy;
sasiad: osoba_z_prawem_jazdy;
Nadawanie wartości poszczególnym składowym rekordu przebiega w standardowy sposob:
sasiadka.imie:="Kazimiera "
sasiadka.nazwisko:="Kowalska ";
lub przy użyciu agregatu rekordu z notacją pozycyjną czy nazywaną (jak dla rekordów bez wariantów).
sasiadka:=(false, "Kazimiera ", "Kowalska ", 1955);
sasiad:=(kierowca=>true, imie=>"Jan ", nazwisko=>"Kowalski ",
rok_urodzenia=>1954,prawo_jazdy=>(true,true,others=>false));
Zauważmy, że mimo określenia wartości wyróżnika w momencie deklaracji występuje on jeszcze raz w
agregacie rekordu (oczywiście jego wartość w agregacie i deklaracji musi być taka sama, w
przeciwnym razie wystąpi Constraint_Error). Agregat rekordu może być również użyty podczas
deklaracji zmiennej, powodując "określenie" rekordu:
sasiadka : osoba := (false, "Kazimiera ", "Kowalska ", 1955);
W ten sposób również otrzymujemy rekord o stałej wartości wyróżnika.
81
Wyróżnik rekordu traktowany jest jak zwykłe pole (tutaj - o stałej wartości) i możemy się do niego
odwoływać:
if sasiad.kierowca=true then ...
Próby odwołania się do nieistniejących pól rekordu (np. sasiadka.prawo_jazdy) powodują
wystąpienie błędu Constraint_Error.
Gdyby powyższy sposób korzystania z rekordów z wyróżnikami był jedynym możliwym, byłoby to
bardzo niewygodne. Sąsiadka skazana byłaby na życie bez prawa jazdy - a co miałby zrobić posiadacz
bazy danych, gdyby udało jej się uzyskać ten dokument?... Możliwe jest więc deklarowanie rekordów,
których wyróżniki mogą zmieniać swoją wartość. Są to rekordy tzw. "nie określone" (ang.
unconstrained) - deklarowane bez ustalania wartości wyróżnika. Jest to możliwe dzięki nadaniu
wyróżnikowi wartości domyślnej podczas definiowania typu rekordowego (wiadomo wówczas, ile
pamięci zarezerwować):
type osoba (kierowca:boolean:=false) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
Jeśli zadeklarujemy teraz zmienną pisząc
teściowa : osoba;
otrzymamy rekord "nie określony", dla którego została przyjęta domyślna wartość pola kierowca -
false. Zawartość tego pola możemy jednak wielokrotnie zmieniać, używając agregatu rekordu
(użycie tylko pojedynczego pola - wyróżnika - jest nieprawidłowe):
-- prawidłowy sposób zmiany:
tesciowa:=(kierowca=>true, imie=>"Kleopatra ",
nazwisko=> "Kowalska ", rok_urodzenia=> 1930,
prawo_jazdy=>(C|D|T=>true, others=>false));
-- nieprawidłowo:
tesciowa.kierowca :=true;
tesciowa.imie:="Kleopatra ";
Oczywiście można takiemu rekordowi nadac wartość początkową już w chwili deklaracji
ciotka_sasiada : osoba := (kierowca=>true, imie=>"Eleonora ",
nazwisko=>"Kowalska ", rok_urodzenia=>1950,
prawo_jazdy=>(B=>true,others=>false));
a mimo to pozostaje on rekordem "nie określonym", z możliwością zmiany wartości wyróżnika:
ciotka_sasiada := (kierowca=>false, imie=>ciotka_sasiada.imie,
nazwisko=>ciotka_sasiada.nazwisko,
rok_urodzenia=>ciotka_sasiada.rok_urodzenia);
Możliwe jest też nadawanie wartości (i ewentualna zmiana typu wyrożnika) poprzez podstawianie
całych rekordów:
narzeczony_Jasi : osoba := (true, "Jan ", "Iksinski ",
(A|B=>true,others=>false));
81
narzeczony_Kasi : osoba;
-- domyślnie - nie ma prawa jazdy
-- ale że wszystko się zmienia...
narzeczony_Kasi := narzeczony_Jasi;
-- nastąpiła nie tylko zmiana narzeczonego,
-- ale i wyróżnika rekordu...
Posiadanie przez wyróżnik wartości domyślnej nie oznacza, że wszystkie deklarowane zmienne tego
typu będą "nie określone". Zawsze możliwe jest ustalenie obowiązującej dla danej zmiennej wartości
wyróżnika i "ograniczenie" rekordu, podobnie jak w przypadku typów rekordowych nie mających
wartości domyślnej wyróżnika:
niemowlak : osoba (kierowca => false);
Reguły te mogą wydawać się trudne do zapamiętania. Istnieje na szczęście atrybut 'Constrained
(sposób użycia: zmienna'Constrained) przyjmujący wartość true, gdy dany rekord jest
"określony" (constrained - nie można zmienić wartości jego wyróżnika), a false - w przeciwnym
przypadku.
if not ktos'constrained then
ktos:=(kierowca=>...,...);
end if;
-- zmiana wartości wyróżnika tylko wtedy, gdy jest to możliwe,
-- mimo ew. ostrzeżeń w czasie kompilacji
-- nie będzie błędu wykonania
Rekordy należące do tego samego typu (niezależnie od tego, czy są ograniczone, czy nie) możemy
porównywać ze sobą przy pomocy operatorów = i /= (nawet jeśli mają różne wartości wyróżnika).
Możliwe jest definiowanie typów rekordowych posiadających więcej niż jeden wyróżnik. Rekord może
posiadać jednak tylko jedną część wariantową, umieszczoną na końcu struktury rekordu. Kolejne
konstrukcje case mogą jedynie być w niej zagnieżdżone. Przy ich tworzeniu obowiązują
dotychczasowe zasady:
type kategoria is (A, B, C, D, E, T);
type tablica_kategorii is array (kategoria) of boolean;
type p is (K, M);
-- nieprawidłowa definicja
-- dwie części wariantowe
type osoba1 (plec:p; kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case plec is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
when M =>null;
end case;
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
-- nieprawidłowa definicja
-- powtarzające się nazwy pól
type osoba2 (plec:p; kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case plec is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
case kierowca is
81
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
when M =>
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end case;
end record;
-- definicja prawidlowa
type osoba2 (plec:p; kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case plec is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
when M =>
case kierowca is
when true => pj: tablica_kategorii;
when false => null;
end case;
end case;
end record;
Jak widać w sytuacji, gdy wyróżniki są od siebie niezależne, trudno jest w elegancki sposób
zdefiniować typ rekordowy - musimy powtarzać pole, a to wymaga użycia w każdym przypadku
instrukcji case innej nazwy. Może pomóc tutaj deklaracja dwustopniowa (jak poniżej, zobacz "Inne
zastosowania wyróżników").
type os (pl: p) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case pl is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
when M =>null;
end case;
end record;
type osoba1 (plec:p; kierowca:boolean) is record
dane : os(plec);
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
Gdy wartości wyróżnika nie są od siebie zależne, definicja wygląda dużo ładniej:
type rodzaj_psa is (obronny, pasterski, mysliwski, pokojowy);
type pies (rasowy:boolean; medalista:boolean) is record
imie:string(1..10);
wlasciciel:string(1..20);
case rasowy is
when true => rodzaj: rodzaj_psa;
rasa: string(1..10);
case medalista is
when true => ilosc_medali:positive;
when false => null;
81
end case;
when false=> null;
end case;
end record;
Agregaty rekordów należących do typu pies mogą mieć jedną z czterech postaci:
pies1 : pies := (rasowy=>false, medalista=>false,
imie=>"Azor ", wlasciciel=>"Jan Kowalski ");
pies2 : pies := (rasowy=>false, medalista=>true,
imie=>"Burek ", wlasciciel=>"Jan Kowalski ");
pies3 : pies := (rasowy=>true, medalista=>false,
imie=>"Cezar ", wlasciciel=>"Jan Kowalski ",
rodzaj=>mysliwski, rasa=>"foksterier");
pies4 : pies:= (rasowy=>true, medalista=>true,
imie=>"Funia ", wlasciciel=>"Jan Kowalski ",
rodzaj=>pokojowy, rasa=>"pekinczyk ",
ilosc_medali=>10);
Podobnie jak przedtem, także i w przypadku rekordów o kilku wyróżnikach możemy deklarować
rekordy "określone" i "nie określone", jeżeli wyróżnikowi została nadana wartość domyślna. Jeśli
wartość domyślną posiada jeden z wyróżników, muszą ją mieć również pozostałe.
Rekordy ze składowymi o zmiennym rozmiarze
Innym sposobem wykorzystania wyróżników rekordu jest określanie za ich pomocą rozmiaru
składowych, na przykład długości stringa czy rozmiaru tablicy, jak w poniższym przykładzie:
type dane (dl_nazwy:positive) is record
zaklad_pracy:string(1..dl_nazwy);
rok_zalozenia: positive;
end record;
type osoba is record
imie, nazwisko:string(1..10);
rok_ur:positive;
end record;
type tabela_pracownikow is array (positive range <>) of osoba;
type firma (il_pracownikow:positive) is record
nazwa_firmy : string(1..20);
zatrudnieni : tabela_pracownikow (1..il_pracownikow);
end record;
W obu przypadkach jedno z pól rekordu należy do typu tablicowego o nie określonym rozmiarze
(string jest, jak pamiętamy, zdefiniowany jako taka tablica o elementach typu character). Jeżeli
wyróżnik nie ma nadanej wartości domyślnej, musimy podać ją w chwili dekaracji zmiennej.
zaklad1 : dane(dl_nazwy=>50);
zaklad2 : dane(20);
zaklad3 : dane := (dl_nazwy=>13, zaklad_pracy=>"WKS Mostostal",
rok_zalozenia=> 1974);
Jak pamiętamy, zmienna należąca do typu tablicowego o nie określonym rozmiarze musi być
elementem jakiegoś jego podtypu, określonego przez rozmiar. Zatem zmienne zadeklarowane powyżej
są "określone" i nie mogą zmieniać wartości wyróżnika, a więc i rozmiaru swoich składowych. Jeśli
natomiast wyróżnik ma wartość domyślną:
81
type dane (dl_nazwy:positive:=20) is record
zaklad_pracy:string(1..dl_nazwy);
rok_zalozenia: positive;
end record;
to zmienne deklarujemy bądź podobnie jak poprzednio
zaklad1 : dane(dl_nazwy=>50);
zaklad2 : dane(20);
- wówczas są one rekordami "określonymi", a długość nazwy określiliśmy na zawsze (podobnie jak w
przypadku rekordów z wariantami), bądź też
zaklad3 : dane;
-- domyślna wartość wyróżnika - 20
zaklad4 : dane:= (dl_nazwy=>13, zaklad_pracy=>"WKS Mostostal",
rok_zalozenia=> 1974);
otrzymując rekord "nie określony":
zaklad3 := zaklad4;
-- zmiana długości z 10 na 13
Oczywiście wyróżników może być więcej niż jeden. Mogą odnosić się one do tego samego lub do
różnych pól rekordu:
type tabela_pracownikow is array (positive range <>) of osoba;
type firma (dl_nazwy,il_pracownikow:positive) is record
nazwa_firmy : string(1..dl_nazwy);
zatrudnieni : tabela_pracownikow (1..il_pracownikow);
end record;
f1 : firma(dl_nazwy=>3, il_pracownikow=>10);
f2 : firma(12,5);
Zdefiniowaliśmy tu dwa typy - tablicowy o nie określonym rozmiarze (tabela_pracownikow) i
rekordowy. Niestety nie można utworzyć tylko jednego typu, pisząc
type firma2 (dl_nazwy,il_pracownikow:positive) is record
nazwa_firmy : string(1..dl_nazwy);
zatrudnieni : array (1..il_pracownikow) of osoba;
end record;
ponieważ żadne z pól rekordu nie może być zadeklarowane jako tablica anonimowa.
Wyróżniki rekordu nie mogą występować w deklaracji typu jako elementy jakiegoś wyrażenia.
Napisanie na przykład
type firma (dl_nazwy,il_pracownikow:positive) is record
...
zatrudnieni : tabela_pracownikow1 (1..il_pracownikow+1);
end record;
jest niedozwolone.
Inne zastosowania wyróżników.
Poznaliśmy dotychczas następujące zastosowania wyróżników rekordów:
−
do tworzenia rekordów z wariantami (wyróżnik określa wówczas zawartość rekordu),
−
do tworzenia rekordów, których pola mają rozmiar zależny od wartości wyróżnika.
Istnieją jeszcze trzy inne zastosowania :
−
tworzenie rekordów, w których pewne pola zachowują się jak stałe,
−
inicjowanie wartości pewnych pól rekordu,
81
−
"określanie" rekordu z wyróżnikiem zagnieżdżonego w danym rekordzie jako jedno z jego pól.
Oto przykłady:
Inicjowanie wartości pola rekordu:
type ograniczenie (predkosc:natural) is record
max_predkosc : natural := predkosc;
end record;
obszar_zabudowany : ograniczenie(60);
...
put (obszar_zabudowany.max_predkosc);
-- wypisze 60
obszar_zabudowany.max_predkosc:=50;
put (obszar_zabudowany.max_predkosc);
-- wypisze 50
Jak widać, wartość wyróżnika można tutaj zmieniać (nawet jeśli zadeklarowaliśmy rekord jako
"określony").
Tworzenie w rekordzie pól zachowujących się jak stałe:
type plec is (M, K);
-- plec i rok urodzenia nie moga sie zmienic,
-- waga i wzrost - tak
type czlowiek (p: plec;rok_urodzenia:positive) is record
waga, wzrost: positive;
end record;
Iksinski:czlowiek(p=>M, rok_urodzenia=>1966);
...
-- niedozwolone
Iksinski:=(p=>M, rok_urodzenia=>1965, wzrost=>187, waga=>99);
Wyróżnik używany do "określenia" rekordu będącego polem definiowanego rekordu:
type tabela_pracownikow is array (positive range <>) of osoba;
type firma (il_pracownikow:positive) is record
nazwa_firmy : string(1..20);
zatrudnieni : tabela_pracownikow (1..il_pracownikow);
end record;
type pracodawca (ilosc_zatrudnionych:natural) is record
imie,nazwisko : string(1..10);
jego_firma : firma(ilosc_zatrudnionych);
end record;
********************************************************
Algorytmy................................................................................................................................................. 1
Pierwsze programy ................................................................................................................................... 2
Trochę matematyki ................................................................................................................................... 7
Funkcje matematyczne........................................................................................................................ 11
Typy liczbowe i ich zakresy ................................................................................................................... 14
Instrukcja warunkowa............................................................................................................................. 16
Operatory logiczne. Kolejność działań. .................................................................................................. 18
Typ boolean............................................................................................................................................ 19
Typy znakowe......................................................................................................................................... 21
Typ wyliczeniowy................................................................................................................................... 22
Podtypy................................................................................................................................................... 24
81
Definiowanie nowych typów liczbowych ............................................................................................... 27
Atrybuty typów ....................................................................................................................................... 28
Instrukcja wyboru ................................................................................................................................... 32
Instrukcje pętli ........................................................................................................................................ 35
Typ łańcuchowy...................................................................................................................................... 43
Tablice .................................................................................................................................................... 49
Tablice jednowymiarowe.................................................................................................................... 49
Tablice wielowymiarowe.................................................................................................................... 55
Tablice anonimowe (anonimowy typ tablicowy) ................................................................................ 58
Tablice dynamiczne ............................................................................................................................ 58
Typy tablicowe bez okeślenia rozmiaru.............................................................................................. 60
Operacje na tablicach.......................................................................................................................... 63
Rekordy .................................................................................................................................................. 64
Rekordy "zwykłe" (bez wyróżników) ................................................................................................. 64
Struktury danych "wielokrotnie złożone" ............................................................................................... 69
Rekordy z wyróżnikami .......................................................................................................................... 74
Rekordy z wariantami ......................................................................................................................... 74
Rekordy ze składowymi o zmiennym rozmiarze ................................................................................ 79
Inne zastosowania wyróżników. ......................................................................................................... 80