ada polrola

background image

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.

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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?...");

background image

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;

background image

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

background image

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

background image

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;

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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;

background image

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;

background image

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

background image

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

background image

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;

background image

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

background image

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

background image

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

background image

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)

background image

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

background image

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;

background image

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

background image

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

background image

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;

background image

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

background image

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

background image

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.

background image

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

background image

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

background image

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;

background image

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

background image

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

background image

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.

background image

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

background image

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:

background image

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

background image

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;

background image

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.

background image

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

background image

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;

background image

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

background image

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)

background image

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

background image

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

background image

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

background image

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;

background image

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

background image

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;

background image

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

background image

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.

background image

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ć

background image

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:=

background image

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;

background image

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;

background image

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

background image

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

background image

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

background image

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

background image

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;

background image

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;

background image

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;

background image

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.

background image

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

background image

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

background image

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;

background image

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

background image

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,

background image

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

background image

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


Wyszukiwarka

Podobne podstrony:
ADA wyjatki przerw3
Ada Styk Projekt4
ADA wspolbiezn 24
Ada Opala
FCZ-KT by Ada, Psychologia, testy
APIS-Z by Ada(2), diagnostyka
Ada czy to wypada, Przedszkole 3 latki
Ada Styk Projekt1
ADA wyjatki przerw6
testy z administ odp-Ada[1], UAM administracja, P. Administracyjne, Nowy folder
ada
Ada Styk Projekt3
ada bom 7 (1)
2015 Styk Ada 2rok AW sketching
ADA wspolbiezn 26
18. J. Kochanowski, Z łacińska śpiewa Słowian Muza - Foricenia, oprac. Ada Fischgrund
wszystkie Imiona żeńskie, A, Ada - Osoba delikatna, kulturalna, powabna, lubiąca piękne otoczenie i

więcej podobnych podstron