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;
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);
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) : ");
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"") : ");
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) 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 ");
new_line;
put("nazwa polska : ");
dni_io.get(d);
put("nazwa angielska : ");
days_io.put(days'val(dni'pos(d)));
end pr21;
|