Ada95, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Programowanie strukturalne


JĘZYKI PROGRAMOWANIA OBIEKTOWEGO

Wiesław Porębski

Ada95

Program Wykładu

Wprowadzenie do języka Ada, struktura programu, instrukcje i wyrażenia. System typów: typy wbudowane i typy definiowane przez użytkownika. Moduły programowe, specyfikacja, implementacja i użycie pakietów. Dziedziczenie typów i hierarchie pakietów: typy abstrakcyjne, hierarchie pakietów. Polimorfizm statyczny i dynamiczny. Typy dostępowe. Zgłaszanie i obsługa wyjątków. Współbieżność.

WPROWADZENIE

Ada jest jednym z pierwszych języków o tzw. jakości produkcyjnej. W praktyce oznacza to, że ten właśnie język jest wybierany dla dużych i bardzo dużych przedsięwzięć programistycznych, ponieważ napisane w nim i przetestowane programy cechuje duża niezawodność. Inspiracją dla powstania Ady były wymagania opracowane przez Departament Obrony USA (Department of Defense * DoD) na nowy język wysokiego poziomu dla zastosowań militarnych. Miał to być język do tworzenia oprogramowania wbudowanych systemów komputerowych, tj. systemów, w których komputer jest połączony bezpośrednio z nadzorowaną albo sterowaną aparaturą lub instalacją. Wymagania te zostały opublikowane w roku 1978, a w kilka lat później powstał projekt języka w zespole kierowanym przez J. Ichbiah'a. W roku 1983 przyjęto normę ANSI dla języka, nazywanego Ada'83, zaś równoważny standard ISO został opublikowany w roku 1987. Język był używany nie tylko przez DoD, lecz także przez NASA i European Space Agency. Językowi nadano nazwę Ada na cześć Ady Augusty Byron, hrabiny Lovelace.

W swojej wersji z 1983 roku Ada nie była językiem w pełni obiektowym; można ją było zaliczyć do grupy języków opartych na obiektach (ang. object-based). Od czasu opublikowania standardu Ada'83 powstało szereg niestandardowych, w pełni obiektowych wersji języka dostępnych na rynku. Jedną z nich jest Classic-Ada; wersja ta została rozszerzona w stosunku do wersji standardowej o takie konstrukcje obiektowe jak hierarchie klas, dziedziczenie (pojedyn­cze), wiązanie dynamiczne i zasłanianie metod zarówno na poziomie klasy, jak i na poziomie wystąpienia. Dopiero jednak standard ISO/ANSI ustalony w roku 1995 pod nazwą Ada 95 wprowadził prawie wszystkie cechy, jakie są wymagane w obiektowym języku programowania. Tym niemniej Ada 95 jest, podobnie jak C++, językiem hybrydowym; można w nim programować zarówno w stylu proceduralnym, jak i czysto obiektowym.

  1. Struktura i elementy programu

Większość programów w języku Ada 95 posiada następującą strukturę:

With Nazwa_Pakietu; Use Nazwa_Pakietu;

procedure Nazwa_Procedury is

Zmienna : Typ_Zmiennej;

begin

Instrukcja_1;

Instrukcja_2;

----------------------

end Nazwa_Procedury;

Procedura (i funkcja) jest w języku Ada 95 nazywana podprogramem. Nazwa_Procedury jest nazwą programu.

W ogólności program Ady składa się z jednej lub więcej jednostek programowych. Jednostki programowe mogą być podprogramami (procedurami lub funkcjami,które zawierają algorytmy przetwarzania), pakietami (w których definiuje się wielkości podlegające przetwarzaniu), zadaniami (które definiują obliczenia współbieżne), jednostkami chronionymi (które definiują operacje dla skoordynowanego współdzielenia danych pomiędzy zadaniami), lub jednostkami rodzajowymi (tj. szablonami, które definiują pakiety lub podprogramy w postaci sparametryzowanej). Zazwyczaj każda jednostka programowa składa się z dwóch części: specyfikacji, zawierającej informacje, które muszą być widoczne dla innych jednostek oraz ciała (treści), zawierającego szczegóły implementacyjne, które nie powinny być widoczne dla innych jednostek. Specyfikacje jednostek przechowuje się w plikach z rozszerzeniem nazwy .ads, zaś treści jednostek w plikach z rozszerzeniem nazwy .adb.

Większość jednostek programowych może być oddzielnie kompilowana. Podział jednostek na specyfikacje i treści oraz możliwość oddzielnej kompilacji jednostek pozwala projektować, kodować i testować program jako zbiór niezależnych składników oprogramowania.

Programy Ady zwykle wykorzystują pakiety biblioteczne ogólnego przeznaczenia, zorganizowane w hierarchiczne biblioteki, co pozwala na ich logiczną dekompozycję. Użytkownik może też tworzyć swoje własne biblioteki i udostępniać je programowi. Wówczas w tekście programu należy zamieścić odpowiednie deklaracje importu, sygnalizowane słowami kluczowymi with i use.

    1. Elementarny program

Trywialny program proceduralny, który drukuje napis Hello, World! mógłby wyglądać następująco:

-- Program drukuje pewien napis

With Ada.Text_IO; Use Ada.Text_IO;

procedure Hello is

begin

Put_Line(*Hello, World!*);

end Hello;

Słowa with, use, procedure, is, begin, end są słowami zastrzeżonymi (kluczowymi). Pierwszy wiersz jest komentarzem. Wiersze drugi i trzeci zawierają dyrektywy: dyrektywa with.... określa wymagany dla działania programu pakiet biblioteczny (Ada.Text_IO), zaś dyrektywa use... wymusza szukanie tego pakietu przez kompilator. Wiersz czwarty rozpoczyna definicję nowej procedury (Hello). Zauważmy, że w języku Ada program jest zwykłą procedurą (w języku Ada 95 procedury i funkcje określa się wspólną nazwą podprogram) bez specjalnie zastrzeżonej nazwy, jak na przykład w językach Pascal (program), C, C++, czy Java (main). Ciało procedury wprowadza słowo kluczowe is; jest ono zawarte pomiędzy słowami kluczowymi begin i end, przy czym po end można (niekiedy trzeba) dodać nazwę procedury (jeżeli dodamy, to kompilator sprawdzi poprawność tej nazwy), zaś pomiędzy is i begin można deklarować zmienne lokalne, np. result: Integer; A,B: Float := 0; Response: Character; C: String(1..5);. Instrukcja Put_Line(*Hello, World!*); jest wywołaniem procedury bibliotecznej Put_Line z pakietu Ada.Text_IO, która drukuje łańcuch Hello, World! będący jej argumentem i dodaje znak nowego wiersza. Jest to niejawna postać kwalifikacji identyfikatora: gdyby w programie opuścić dyrektywę use, wówczas musielibyśmy napisać pełną nazwę kwalifikowaną: Ada.Text_IO.Put_Line(*Hello, World!*), używając notacji z kropką. Zwróćmy ponadto uwagę na średniki po każdej instrukcji: w języku Ada 95 są one, podobnie jak w C++, terminatorami instrukcji (a nie separatorami, jak na przykład w Pascalu).

Średniki są także terminatorami deklaracji. Na przykład deklaracje podprogramów, poprzedzające ich definicje, mogą mieć postać:

procedure Average(A,B:in Integer;Result:out Integer);

function Average_Two(A,B: in Integer) return Integer;

Zarówno procedura Average, jak i funkcja Average_Two przyjmują wartości parametrów A i B typu Integer, przy czym procedura zapewne przechowuje wartość średnią w zmiennej Result, zaś funkcja zwraca wartość wyrażenia typu Integer. (W języku Ada 95 dla każdego argumentu formalnego procedury lub funkcji musimy podać jego tryb, definiujący prawa operacji na argumentach aktualnych: słowo kluczowe in oznacza *tylko czytanie” - zakaz zmiany wartości argumentu , out - *tylko pisanie” oraz in out - zmiana wartości i pisanie. Przy braku słowa kluczowego domyślnym trybem jest in).

Przykładowa definicja funkcji Average_Two mogłaby mieć postać:

function Average_Two(A,B: in Integer) return Integer is

Average: Integer;

begin

Average := (A+B) / 2;

return Average;

end Average_Two;

      1. Kompilacja elementarnego programu

Jeżeli nasz elementarny program umieścimy w pliku Hello1.adb (rozszerzenie nazwy .adb mówi o tym, że w pliku umieszczono ciało pakietu Hello1), to kompilacja z wiersza rozkazowego obejmie następujące kroki:

  1. Krok 1: gcc -c Hello1.adb

Otrzymamy pliki hello1.o (plik obiektowy) i hello1.ali (rozszerzenie nazwy .ali mówi o tym, że jest to Ada Library Information file)

  1. Krok 2: gnatbind Hello1.ali Otrzymamy pliki b~hello1.ads i b~hello1.adb.

  2. Krok 3: gnatlink Hello1.ali Otrzymamy plik hello1.exe, a pliki b~hello1.ads i b~hello1.adb znikną.

Wymienione trzy kroki można zastąpić jednym: gnatmake Hello1.adb.

    1. Elementy leksykalne

Tekst programu jest sekwencją oddzielnych elementów leksykalnych. Każdy element leksykalny jest tworzony z sekwencji znaków według określonych reguł, i jest albo elementem oddzielającym, identyfikatorem, słowem zarezerwowanym (kluczowym), literałem numerycznym, literałem znakowym, literałem łańcuchowym, lub komentarzem. Szczególnymi elementami leksykalnymi są pragmy, które podają pewne informacje dla kompilatora. Kompilatory Ady muszą wspierać długości wierszy tekstu programu oraz identyfikatorów co najmniej 200-znakowych.

      1. Zbiór znaków

W tekście programu w języku Ada95 dopuszcza się używanie 8-bitowego zbioru znaków określonego przez standard ISO 8859-1 (Latin-1) oraz 16-bitowego zbioru znaków określonego przez standard ISO 10646. Pierwszy z nich reprezentuje zbiór wartości typu Character, zaś drugi - zbiór wartości typu Wide_Character.

      1. Elementy rozdzielające

Element oddzielający (separator lub ogranicznik) jest jednym ze znaków specjalnych

& ' ( ) * + , - . / : ; < = > |

lub jednym z następujących symboli dwuznakowych

=> .. ** := /= >= <= << >> <>

Symbole dwuznakowe mają następujące nazwy:

symbol nazwa

=> strzałka, symbol asocjacji (skojarzenia)

.. podwójna kropka, symbol zakresu, podzakresu

** podwójna gwiazdka, potęgowanie

:= przypisanie

/= różne

>= większe lub równe

<= mniejsze lub równe

<< lewy nawias kątowy etykiety

>> prawy nawias kątowy etykiety

<> skrzynka

      1. Komentarze

Komentarz zaczyna się od dwóch kolejnych znaków minus i rozciąga się do końca wiersza, w którym występuje. Nie ma wpływu na poprawność programu. Dłuższe komentarze można umieszczać w dwóch lub więcej wierszach.

      1. Identyfikatory

Identyfikatory są używane jako nazwy. Identyfikator nie może być słowem zarezerwowanym (kluczowym). Zaleca się, aby pierwszy znak identyfikatora był dużą literą. Jeżeli identyfikator składa się z więcej niż jednego słowa, poszczególne słowa powinny się zaczynać z dużej litery, a pomiędzy słowami powinny być znaki podkreślenia.

      1. Słowa kluczowe

Standard Ada 95 wprowadził 69 słów kluczowych:

abort

abs

abstract

accept

access

aliased

all

and

array

at

begin

body

case

constant

declare

delay

delta

digits

do

else

elsif

end

entry

exception

exit

for

function

generic

goto

if

in

is

limited

loop

mod

new

not

null

of

or

others

out

package

pragma

private

procedure

protected

raise

range

record

rem

renames

requeue

return

reverse

select

separate

subtype

tagged

task

terminate

then

type

until

use

when

while

with

xor

      1. Literały numeryczne

Istnieją dwa rodzaje literałów numerycznych: literały rzeczywiste i całkowite. Literał rzeczywisty zawiera kropkę dziesiętną; literał całkowity nie zawiera kropki. Wyróżnia się literały numeryczne decymalne i bazowe.

Literały decymalne

Literał decymalny jest literałem numerycznym (rzeczywistym lub całkowitym) w konwencjonalnej notacji dziesiętnej (tj. z podstawą liczenia 10). W literałach całkowitych wykładnik nie może mieć znaku minus. Jeżeli literał jest dużą liczbą, poszczególne grupy cyfr można przedzielać pojedynczymi znakami podkreślenia.

Przykłady literałów decymalnych:

4.12 0 1E6 123_456 345_23_76 -- literały całkowite

12.0 12.0E1 0.0 0.456 845.4e-1 3.14159_26 -- literały rzeczywiste

Literały bazowe

Literał bazowy jest literałem numerycznym o postaci:

podstawa_liczenia#literał_decymalny#

Podstawa liczenia może być liczbą od 2 do 16. Literał decymalny musi być zapisany w systemie z daną podstawą liczenia. Po drugim # można umieścić znak `E' lub `e' (±), który reprezentuje wtedy podstawę potęgi równą podstawie liczenia, a po nim wykładnik potęgi, zapisany w systemie dziesiętnym. Przykłady literałów bazowych:

2#1111_1111#, 16#FF#, 016#0ff# -- literały całkowite wartości 255

16#E#E1, 8#340#, 2#1110_0000#, -- literały całkowite wartości 224

16#F.FF#E+2, 2#1.1111_1111_1110#E11 -- literały rzeczywiste wartości 4095.0

Literały znakowe

Literał znakowy jest znakiem ujętym w dwa apostrofy. Przykłady: 'A' '*' "' ' '

Literały łańcuchowe

Literały łańcuchowe są sekwencjami zero lub więcej znaków ujętymi w podwójne apostrofy. Jeżeli w łańcuchu ma wystąpić jeden podwójny apostrof, piszemy wówczas dwa kolejne podwójne apostrofy. W łańcuchu nie może wystąpić znak końca wiersza. Przykłady:

"Message of the day:"

"" -- zerowy literał łańcuchowy

" " "A" """" -- trzy literały łańcuchowe o długości 1

      1. Pragmy

Pragma jest dyrektywą dla kompilatora. Istnieją pragmy predefiniowane, które podają instrukcje dla optymalizacji, kontroli listingów, etc. Zapis pragmy zaczyna się od słowa kluczowego pragma, po którym następuje jej nazwa i - opcjonalnie - jeden lub więcej argumentów. Istnieje szereg pragm predefiniowanych, np. pragma Inline zaleca kompilatorowi zastąpić każde wywołanie podprogramu, o ile to możliwe, kodem jego instrukcji. Nierozpoznane pragmy nie mają wpływu na program. ale ich obecność musi być sygnalizowana jako ostrzeżenie.

Przykłady pragm z argumentami:

pragma List(Off); -- wyłącza generację listingu

pragma Optimize(Off); -- wyłącza opcjonalne optymalizacje

pragma Inline(Set_Mask); -- generuj kod dla Set_Mask inline

pragma Suppress(Range_Check, On => Index); -- wyłącza sprawdzanie zakresu -- indeksów.

      1. Operatory i wyrażenia

Operator jest symbolem, który operuje na jednym lub wielu argumentach dla otrzymania pewnego wyniku. W języku Ada 95 zdefiniowano sześć kategorii operatorów, które podajemy według rosnącego priorytetu.

  1. Logiczne: and (koniunkcja), or (alternatywa), xor (alternatywa wyłączna).

  2. Relacyjne: = (przyrównanie), /= (nierówność), < (mniejsze), <= (mniejsze lub równe), > (większe), >= (większe lub równe).

  3. Binarne arytmetyczne: + (dodawanie), - (odejmowanie), & (konkatenacja)

  4. Unarne arytmetyczne: + (plus jednoargumentowy), - (minus jednoargumentowy)

  5. Mnożenia: * (mnożenie), / (dzielenie), mod (modulus), rem (reszta z dzielenia).

  6. Najwyższego priorytetu: ** (potęgowanie), abs (wartość bezwzględna), not (negacja).

Ponadto wprowadzono warunkowe operatory logiczne and then i or else. Dają one takie same wyniki, jak predefiniowane operatory and i or dla wyrażeń logicznych, za wyjątkiem tego, że lewy argument operatora jest zawsze wartościowany jako pierwszy; jeżeli wartość lewego argumentu określa wynik, to prawy argument nie będzie wartościowany.

Wyrażenie jest to formuła, która definiuje obliczenie lub wyznaczenie wartości. Język Ada95 zawiera następujące kategorie wyrażeń: termy (np. 2*Line_Count), wyrażenia pierwotne (np. literały, stałe, zmienne, atrybuty, wywołania funkcji), wyrażenia proste (np. B**2 - 4.0*A*C) i relacje (np. A xor B).

Każde wyrażenie jest określonego typu, który określa rodzaj obliczenia lub wyznaczenia wartości tego typu.

    1. Instrukcje

Instrukcje są poleceniami do wykonania. Przypomnijmy, że każda instrukcja kończy się średnikiem, który pełni rolę symbolu terminalnego. Instrukcje dzielą się na proste i złożone. Instrukcja prosta nie zawiera żadnych innych instrukcji; instrukcja złożona może zawierać inne instrukcje proste oraz instrukcje złożone.

Najprostsze instrukcje proste to instrukcja pusta o składni: null; oraz instrukcja przypisania. Pośród instrukcji złożonych wyróżnia się: instrukcję blokową, instrukcję if, instrukcję case, instrukcje pętli, instrukcję accept oraz instrukcję select.

      1. Instrukcja przypisania

Instrukcja przypisania zastępuje bieżącą wartość zmiennej wynikiem opracowania wyrażenia. Składnia instrukcji jest następująca:

nazwa_zmiennej := wyrażenie;

Nazwana zmienna i wyrażenie po prawej stronie instrukcji przypisania muszą być tego samego typu lub wyrażenie musi być podtypem typu zmiennej. Przykłady:

x,y: Integer; x := 75; y := x - 1;

      1. Instrukcje blokowe

Instrukcja blokowa zawiera sekwencję instrukcji, ewentualnie poprzedzoną przez część deklaracyjną. Składnia instrukcji blokowej ma postać:

[identyfikator:]

[declare

-- lokalne deklaracje]

begin

-- sekwencja instrukcji

end [identyfikator];

Frazy ujęte w nawiasy prostokątne są opcjonalne. Jeżeli instrukcja blokowa posiada identyfikator, to musimy go umieścić po end. Zakładając, że zmienne U i V są typu Integer, możemy zapisać następujący przykład:

swap:

declare

Temp: Integer;

begin

Temp := V; V := U; U := Temp;

end swap;

Uwaga. Wszelkie deklaracje zmiennych lokalnych są widoczne tylko w zasięgu danej instrukcji blokowej.

      1. Instrukcje warunkowe

Instrukcje warunkowe (if) służą do wyboru działania w zależności od spełnienia określonego warunku logicznego. Warunkami w instrukcji if mogą być wyrażenia z operatorami relacji (=, /=, <, >, <=, >=), z operatorami logicznymi (and, not, or, xor) oraz z warunkowymi operatorami logicznymi (and then, or else). Składnia najprostszej instrukcji if ma postać:

if Warunek then

Instrukcja (-e)

end if;

Jeżeli chcemy podjąć alternatywne działania przy niespełnionym warunku, możemy zastosować instrukcję if o składni:

if Warunek then

Instrukcje_po_then

else

Instrukcje_po_else

end if;

Instrukcje if można dowolnie zagnieżdżać. Można tworzyć wielokrotnie złożone instrukcje if, wykorzystując słowo kluczowe elsif, np.

if Warunek_1 then

Instrukcje_1

elsif Warunek_2 then

Instrukcje_2

else

Instrukcje_3

----------------

{elsif Warunek_n then Instrukcje_n}

end if;

Należy przy tym pamiętać, że opcja else może wystąpić tylko jeden raz, natomiast elsif wielokrotnie.

      1. Instrukcja wyboru

Jeżeli liczba alternatywnych działań w instrukcji if prowadzi do zbyt głębokich zagnieżdżeń, można zastosować instrukcję wyboru case o składni:

case W is

when Wartość_1 => sekwencja_instrukcj1_1;

when Wartość_2 => sekwencja_instrukcj1_2;;

...

when Wartość_n => sekwencja_instrukcji_n;;

[when others => Instrukcje_o;]

end case;

Dyskusja. W jest pewnym wyrażeniem o dyskretnym zbiorze wartości (np. typu Integer lub wyliczeniowego). Instrukcja wyboru działa następująco: po obliczeniu W następuje porównanie otrzymanego wyniku z każdą z wartości podanych po słowie kluczowym when; po stwierdzeniu zgodności wykonywana jest odpowiednia sekwencja instrukcji; przy braku zgodności wykonywana jest sekwencja instrukcji po others (pozostałe przypadki). Złożony symbol asocjacji '=>' pozwala kojarzyć wartości z sekwencjami instrukcji dla wszystkich przypadków, w tym i others.

Ada 95 pozwala również grupować alternatywne wartości, jak pokazano w poniższym przykładzie.

With Ada.Text_IO; Use Ada.Text_IO;

procedure Case1 is

Time : Float;

Route : Character range 'A'..'z';

begin --Case1

Get(Route);

case Route is

when 'A' => Time := 3.0; Put("Very short trip.");

when 'B' | 'N' => Time := 4.0;

when 'C' .. 'D' => Time := 4.5;

when others => Put("Stay home!");

end case;

end Case1;

Uwaga 1. Symbol '|' odziela przypadki alternatywne. Złożony symbol asocjacji '=>' został tutaj wykorzystany do wywołania podprogramu; w tym przypadku parametr aktualny podprogramu ("Stay home!") został jawnie skojarzony z argumentem (parametrem) formalnym.

Uwaga 2. Ada 95 wymaga umieszczenia instrukcji dla każdego przypadku; zatem, jeżeli dla pewnego przypadku nie przewidujemy żadnego działania, musimy wtedy umieścić instrukcję pustą, np.: when 'N' => null;.

    1. Instrukcje pętli

Najprostsza instrukcja pętli posiada następującą składnię:

loop

Sekwencja instrukcji do powtarzania

end loop;

Instrukcje pętli można poprzedzać identyfikatorem; wtedy po end loop należy umieścić identyfikator:

[identyfikator:]

loop

Sekwencja instrukcji do powtarzania

end loop [identyfikator];

Pętla while posiada następującą składnię:

while Warunek

loop

Instrukcja (-e)

end loop;

Podobnie, jak dla pętli prostej, pętlę while można poprzedzić identyfikatorem.

Pętlę for stosuje się w przypadku, gdy liczba powtórzeń jest a priori znana i jest podana w predykacie pętli. Składnia pętli for jest następująca:

for Parametr_Pętli in [reverse] Zakres_Iteracji loop

Sekwencja instrukcji

end loop;

Podobnie jak inne instrukcje pętli, instrukcję for można poprzedzić identyfikatorem:

[identyfikator:]

for Parametr_Pętli in [reverse] Zakres_Iteracji loop

Sekwencja instrukcji

end loop [identyfikator];

Parametr_Pętli jest zmienną lokalną niejawnie zadeklarowaną przez jej obecność w pętli. Istnieje ona tylko w zasięgu pętli, i przyjmuje typ określony przedziałem Zakres_Iteracji. Opcja reverse pozwala przebiegać pętlę for w odwrotnym kierunku przy tym samym zakresie iteracji.

      1. Instrukcja exit

Niekiedy istnieje konieczność wcześniejszego zakończenia pętli, mimo że nadal jest spełniony warunek wejścia do pętli. Stosuje się wtedy instrukcję exit. Dla pętli while może ona wystąpić w zagnieżdżonej w niej instrukcji if:

while Warunek loop

if Warunek2 then exit;

end if;

end loop;

Powyższy kod można uprościć do postaci:

while Warunek loop

exit when Warunek2;

end loop;

Instrukcja exit pozwala tworzyć pętle z badaniem warunku wyjścia z pętli, wykonywane co najmniej jeden raz; składnia takiej pętli może mieć postać:

loop

Instrukcja (-e)

exit when Warunek;

end loop;

  1. System typów

Każdy obiekt programu ma swój typ, który charakteryzuje pewien zbiór wartości oraz zbiór operacji dopuszczalnych na tych wartościach. Typy języka Ada95 można podzielić na elementarne (wyliczeniowy, numeryczny, i dostępowy) oraz złożone (tablice, rekordy, typy chronione i zadania). Pewne typy są predefiniowane (np. Boolean, Character i Wide_Character, Integer, Float, Duration, String i Wide_String); użytkownik może definiować własne typy oraz typy od nich pochodne lub typy pochodne od typów predefiniowanych. Typ, łącznie z typami od niego pochodnymi, tworzy tzw. klasę wyprowadzeń. Pokazany niżej rysunek ilustruje hierarchię typów języka Ada95.

0x01 graphic

Rysunek 2-1. Hierarchia typów języka Ada95

    1. Typy całkowite

Typy całkowite obejmują zbiór wartości całkowitych ze znakiem (predefiniowany w widzialnej części pakietu Standard podtyp o nazwie Integer), lub typ modularny. Wartości typu Integer mieszczą się w zakresie System.Min_Int .. System.Max_Int. Zmienne i stałe typu Integer powinny zawierać się w zakresie -2**15+1 .. +2**15-1. Jeżeli implementacja zawiera predefiniowany typ Long_Integer, to jego wartości powinny mieścić się w zakresie -2**31+1 .. +2**31-1. Przykłady definicji stałych i zmiennych typu Integer:

My_Constant constant Integer := 1000;

Var1,Var2: Integer;

Var3: Integer range 1900..2002; --Var3 jest typu

--okrojonego z Integer

Typ modularny (resztowy) jest typem całkowitym bez znaku; definicja tego typu określa jego maksymalną wartość, która nie może być większa niż System.Max_Binary_Modulus (co najmniej 2**16) jeżeli modulus jest potęgą 2, lub nie większą niż System.Max_Nonbinary_Modulus w pozostałych przypadkach. Dla wartości tego typu dostępne są wszystkie operacje arytmetyczne, ale są one wykonywane modulo N, gdzie N jest liczbą dodatnią; dzięki temu nie może wystąpić ani niedomiar, ani nadmiar. Np. definicja

type Byte is mod 256;

lub

type Byte is mod 2**8;

oznacza bajt bez znaku, przy czym druga jej postać jest często używana do wyznaczenia masek bitowych. Zauważmy, że jeżeli zdefiniujemy zmienną typu Byte, np.

A_byte: Byte := 255;

i wykonamy instrukcję:

A_byte := A_byte + 1;

to bieżąca wartość zmiennej A_byte będzie wynosić 0.

Pewne własności typów można identyfikować przy pomocy tzw. atrybutów. Np. zmiennej Var1 możemy przypisać atrybut Integer'Size, oznaczający liczbę bajtów w reprezentacji liczb typu Integer:

Var1 := Integer'Size;

innymi użytecznymi atrybutami są First, Last, i Digits; dla typu Integer zakres możliwych wartości jest zawarty pomiędzy Integer'First a Integer'Last.

    1. Typy rzeczywiste

Dane typów rzeczywistych (stało- i zmiennopozycyjne) są aproksymacjami liczb rzeczywistych. Dla danych typu zmiennopozycyjnego obliczenia można prowadzić z dokładnością do określonych przez implementację lub przez własną definicję liczby cyfr znaczących, zaś dla danych typu stałopozycyjnego z określoną dokładnością bezwzględną (delta). Własności typów rzeczywistych można także identyfikować za pomocą atrybutów Size, First , Last i Digits.

      1. Typy zmiennopozycyjne

Predefiniowany typ zmiennopozycyjny Float jest zadeklarowany w widocznej części pakietu Standard. Własna definicja użytkownika ma następującą postać:

type nazwa_typu is [digits] cyfry

lub

type nazwa_typu is [digits] cyfry [range zakres]

Przykłady:

type FP1 is digits 7;-- dokładność minimum 7 cyfr

type FP2 is digits 10 range -1.0..1.0;

type FP3 is digits 8 range 0.0..1.0E34;

      1. Typy stałopozycyjne

Typ stałopozycyjny jest albo zwykłym, albo decymalnym typem stałopozycyjnym. Dokładność podaje się po słowie kluczowym delta. Implementacje powinny wspierać dokładność co najmniej 24-bitową (włącznie z bitem znaku). Definicja typu stałopozycyjnego ma postać:

type nazwa_typu is delta dokładność range zakres

lub

type nazwa_typu is delta dokładność digits liczba_cyfr [range zakres]

Przykłady:

type Fixed is delta 0.1 range -1.0..1.0;

type Decimal is delta 0.01 digits 10;

Zakres można także zdefiniować dwuetapowo, np.type FP4 is delta 0.1;Zmienna: FP4 range -1.0..1.0;

Wartości, podawane po słowach kluczowych delta i digits muszą być dodatnie. Wartość delta musi być potęgą 10. Jeżeli w definicji typu decymalnego podano zakres, musi się on mieścić pomiędzy -(10**digits-1)*delta a +(10**digits-1)*delta.

    1. Typy wyliczeniowe

Typ wyliczeniowy definiuje uporządkowany zbiór rozróżnialnych literałów wyliczenia, np. zbiór nazw miesięcy, czy też znaki alfabetu. Uporządkowanie oznacza, że kolejne literały mają przypisane numery porządkowe; jeżeli w wyliczeniu występuje N literałów, wtedy pierwszy z nich ma numer 0, a ostatni N-1. Predefiniowanymi typami wyliczeniowymi są Boolean i Character. Literały definiowanego typu wyliczeniowego są umieszczane w nawiasach okrągłych. Literałami mogą być identyfikatory lub literały znakowe (można je mieszać). Przykłady:

type Days is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);

type Color is (White, Red, Yellow, Green, Blue, Brown, Black);

type Light is (Red, Amber, Green); -- Red and Green

--are overloaded

type Hexa is ('A', 'B', 'C', 'D', 'E', 'F');

type Mixed is ('A', 'B', '*', B, None, '?', '%');

Uporządkowanie literałów wyliczenia oznacza, że każdy literał ma inny numer pozycyjny: numerem pierwszego literału jest zero, a każdy następny ma numer o 1 większy niż jego poprzednik. Jeżeli ten sam literał występuje w więcej niż jednej definicji typu wyliczeniowego, wtedy mówimy o jego przeciążeniu. Typy wyliczeniowe można również definiować, podając zakres, w jakim zawierają się wartości wyliczenia, np.

type Hours is range 0 .. 23;

type Minutes is range 1 .. 60;

      1. Atrybuty typów wyliczeniowych

Podstawowymi atrybutami typów wyliczeniowych są atrybuty First i Last. Ponadto mamy do dyspozycji atrybuty Succ (następnik), Pred (poprzednik), Val (wartość elementu w wyliczeniu) i Pos (pozycja elementu w wyliczeniu). Np. dla typu Day wyrażenie Day'Succ(Mon) ma wartość Tue, Day'Pred(Tue) ma wartość Mon, Day'Pos(Sat) ma wartość 5, a Day'Val(4) ma wartość Fri.

    1. Typ tablicowy

Tablica jest złożonym obiektem, zawierającym składowe tego samego typu. Definicja typu tablicowego ma następującą składnię:

type nazwa_tablicy is array(rozmiar) of typ_elementów_tablicygdzie rozmiar jest zakresem indeksów określającym rozmiar tablicy. Zakres tablicy może być podany bezpośrednio w jej definicji, lub przy deklarowaniu zmiennej. Jeżeli przy definiowaniu typu tablicowego nie chcemy podawać zakresu, wtedy fraza (rozmiar) ma postać (typ_indeksowy range <>). Przykłady:

type Wektor100 is array(1..100) of Float;type Wektor is array(Integer range <>) of Float;Dla tak zdefiniowanych typów można deklarować zmienne tablicowe (lub po prostu tablice):

V1: Wektor100; V2: Wektor(1..100);

Ponieważ w definicji typu Wektor nie podano rozmiaru tablicy, zapis V2: Wektor; byłby błędny. Odwołania do elementów tablicy mają postać zmiennych indeksowanych:

V1(1) := 10;V2(7) := V1(1);Zmienne tablicowe można także deklarować bez uprzedniego definiowania typu, np.

Example: array(1..10) of Integer;

Dla typu tablicowego przewidziano operacje testowania na przynależność elementu do tablicy, kwalifikacji, i jawnej konwersji. Jeżeli typ tablicowy zdefiniowano bez podawania zakresów, można także używać predefiniowanych operatorów przyrównania; do tablicy jednowymiarowej stosuje się predefiniowane operatory konkatenacji. Jeżeli elementy tablicy są dyskretne (np. typu wyliczeniowego lub całkowitego), można używać operatorów relacyjnych; jeżeli typ elementów jest Boolean, także operatorów logicznych.

Tablice wielowymiarowe deklaruje się podobnie. Możemy np. zdefiniować typ tablicowy:

type Tab1 is array(Integer range <>) of Float;

type Tab2 is array(Integer range <>) of Tab1;

lub jedną definicją:

type Tab2 is array(Integer range<>,Integer range<>) of Float;

T: Tab2(1..2,1..2);

W implementacjach tablice wielowymiarowe są reprezentowane jako kolejne wiersze.

      1. Atrybuty rozmiaru tablic

Dla typu tablicowego i zmiennych tablicowych można stosować atrybuty zakresu (A oznacza typ tablicowy):

A'First oznacza dolną granicę pierwszego zakresu indeksów; typem wyniku jest odpowiedni typ indeksowy.

A'Last oznacza górną granicę pierwszego zakresu indeksów; typem wyniku jest odpowiedni typ indeksowy.

A'Last(N) oznacza górną granicę N-tego zakresu indeksów; typem wyniku jest odpowiedni typ indeksowy.

A'Range jest równoważne zakresowi A'First .. A'Last, jednak prefix A jest wartościowany tylko jeden raz.

A'Range(N) jest równoważne zakresowi A'First(N) .. A'Last(N), jednak prefix A jest wartościowany tylko jeden raz.

A'Length oznacza liczbę wartości pierwszego zakresu indeksów; typem wyniku jest liczba całkowita.

A'Length(N) oznacza liczbę wartości N-tego zakresu indeksów; typem wyniku jest liczba całkowita.

Przykłady.

Dla zmiennej tablicowej Example: array(1..10) of Integer; możemy tworzyć pętle:

for i in Example'First .. Example'Last loop

--instrukcje, np. Put(Example'Length);

end loop;

albo też

for i in Example'Range loop

--instrukcje

end loop;

      1. Inicjowanie tablic

Zmiennej tablicowej można nadać wartości początkowe bezpośrednio, np:

V3: Wektor(1..3) := (1.5, 2.6, 3.7);

Jeżeli wszystkie wartości początkowe mają być jednakowe, tablicę można zainicjować instrukcją:

V3 := (others => 1.9);

Słowo kluczowe others obejmuje wszystkie pozostałe przypadki; dla tego przykładu oznacza przypisanie jednakowych wartości każdej zmiennej indeksowanej tablicy V2. Przypisanie realizuje symbol asocjacji =>. Przy inicjowaniu tablic różnymi wartościami można się także posłużyć symbolem alternatywy '|' i/lub symbolem zakresu '..', np:

V4: Wektor(0..9):=(0|9=>42.0,3..4=>451.0,others=>-473.0);

V5:Wektor(38..87):=(38..48|68..78 => 0.0,others=>0.00012);

Inicjowanie tablicy wielowymiarowej można także prowadzić na wiele sposobów, np.

T1: array(1..2,1..2) of Float :=

(1 => (1 => 1.3, 2 => 1.4), 2 => (1.5,1.6));

Tablica T1 jest inicjowana wierszami: w wyrażeniu inicjującym pierwsza jedynka z lewej (przed symbolem asocjacji) odnosi się do pierwszego wiersza; jedynka w nawiasie okrągłym oznacza T(1,1)=1.3, dwójka T(1,2)=1.4 (skojarzenia za pomoca asocjacji nazwanych). Po zamykającym nawiasie okrągłym i przecinku jest inicjowany drugi wiersz za pomocą asocjacji nienazwanych: T(2,1)=1.5, T(2,2)=1.6.

      1. Typ łańcuchowy

Typem łańcuchowym nazywa się typ tablicy jednowymiarowej, w której typem elementów jest typ znakowy. W języku Ada95 mamy dwa predefiniowane typy łańcuchowe: String i Wide_String. Każdy z nich jest indeksowany wartościami predefiniowanego podtypu Positive (o wartościach z przedziału 1 ..Integer'Last). Typy te, zdefiniowane w pakiecie Standard, mają następujące definicje:

type String is array(Positive range <>) of Character;

type Wide_String is array(Positive range <>) of Wide_Character;

Dla typów łańcuchowych przewidziano operatory: konkatenacji (&) oraz operatory porządkujące (<, <=, >, >=). Przykłady deklaracji obiektów typu łańcuchowego:

Line: String(1..80);

ASK: constant String := "What is your name?";

Asterisks: String(1 .. 120) := (1 .. 120 => '*' );

Ask_Twice : String := ASK & ASK;

Podobnie jak dla tablic możemy wyznaczać wartości atrybutów, np. ASK'First = 1, ASK'Last = 18.

    1. Typ rekordowy

Rekord jest złożonym obiektem, służącym do grupowania nazwanych elementów, które mogą być różnych typów. Elementy te nazywa się polami. Pola mogą być inicjowane w deklaracji typu, podczas deklaracji zmiennych, bądź po zadeklarowaniu zmiennych typu rekordowego. Przy inicjowaniu rekordu można używać tzw. agregatu, tj. konstrukcji, która grupuje wszystkie elementy. Dostęp do poszczególnych pól można uzyskać, rozdzielając kropką nazwę rekordu od nazwy elementu. Składnia deklaracji typu rekordowego ma postać:

type nazwa_typu is

record

--lista składowych (pól)

end record;

Dla typu rekordowego predefiniowano trzy operacje: :=, =, i /=. Przykładowy program z deklaracją recordów może mieć postać:

With Ada.Text_IO; Use Ada.Text_IO;

With Ada.Integer_Text_IO; Use Ada.Integer_Text_IO;

procedure Record1 is

type Date is

record

Day: Integer range 1..31 := 13;

Month: Integer range 1..12;

Name: String(1..10) := "Old_Date ";

end record;

Ob1, Ob2, Ob3: Date := (25,9,"Date0 ");

begin

Put(Ob1.Day); New_Line;

Put(Ob2.Day,2);New_Line;

Ob1 := (21,10,"Date1 ");

Ob2.Day := 28;

Put(Ob1.Day,3);New_Line;

Put(Ob2.Day,4);

New_Line;

Ob3 := (Month => 11,Day =>9, Name => "Some_Date ");

end Record1;

Dyskusja. Program zawiera niezbędne deklaracje importu, sygnalizowane słowami kluczowymi with i use. Pola Day i Month są typów okrojonych z typu Integer. Polom Day i Name nadano wartości domyślne. Zadeklarowane zmienne (obiekty) Ob1, Ob2, i Ob3 zostały zainicjowane wartościami (tymi samymi dla wszystkich pól) agregatu (25,9,"Date0 "). W części instrukcyjnej, oprócz znanych instrukcji Put, mamy przypisanie wartości z agregatu (21,10,"Date1 ") do zmiennej Ob1 (asocjacja pozycyjna) oraz przypisanie wartości poszczególnym polom obiektu Ob3 za pomocą asocjacji nazwanej.

Odpowiednikiem klasy w innych językach obiektowych jest umieszczony w pewnym pakiecie znakowany typ rekordowy; nazwa pochodzi stąd, że w deklaracji i definicji takiego typu występuje słowo kluczowe tagged (znacznik). Zmienne (obiekty) takiego typu nazywa się rekordami znakowanymi. Przykładowa definicja rekordowego typu znakowanego może mieć postać:

type Point is tagged

record

X,Y: Float;

end record;

Dla tak zdefiniowanego typu rekordowego można definiować typy od niego pochodne, przy czym każdy typ pochodny można rozszerzać o nowe pola. Znacznik (tag) jest dostępny poprzez atrybut 'Tag. Atrybut ten można jednak wykorzystywać tylko dla porównań, np. w relacji przyrównania pomiędzy dwoma obiektami:

P1, P2 : Point;

if P1'Tag = P2'Tag then ...

Innym ważnym atrybutem jest 'Class, używany w deklaracjach typów dla oznaczenia tzw. typu klasowego.

    1. Definiowanie nowych typów i podtypów

Jak już wspomniano, użytkownik może definiować typy pochodne oraz podtypy od typów predefiniowanych i własnych. Składnia definicji typu pochodnego ma postać:

type nazwa_ typu_pochodnego is new nazwa_ typu_istniejącego;

lub

type nazwa_ typu_pochodnego is new nazwa_ typu_istniejącego range zakres;

Przykłady definiowania typów pochodnych:

type INT is new Integer;

type Floating_Point is new Float;

type Hours is new Integer range 1..12;

Dla typu zdefiniowanego przez użytkownika, np.

type Days is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);

type Work_Days is new Days range Mon .. Fri;

Istotnym słowem kluczowym jest tutaj new,

Składnia definicji podtypu ma postać:

subtype nazwa_ typu_pochodnego is nazwa_ typu_istniejącego;

lub

subtype nazwa_ typu_pochodnego is nazwa_ typu_istniejącego range zakres;

Przykłady definiowania podtypów:

subtype INT1 is Integer;

subtype Data_Range is Integer range 0 .. 1000;

subtype Message is String;

subtype Week_Days is Days range Mon .. Fri;

subtype String_6 is String(1 .. 6);

subtype Natural_8 is Integer_8 range 0 .. 2**7-1;

subtype Positive_8 is Integer_8 range 1 .. 2**7-1;

Konieczność definiowania podtypów wynika stąd, że w języku Ada95 mamy typizację silną, która nie dopuszcza używania "arytmetyki mieszanej", tj. sytuacji, gdy w instrukcjach i wyrażeniach argumenty operatorów są różnych typów. Tak więc, dla a: INT; b: Integer; przypisanie a:= b; jest błędem syntaktycznym. Natomiast dla a: INT1; przypisanie a = b; będzie poprawne.

  1. Podprogramy

Jak już wspomniano, w języku Ada 95 istnieją dwa rodzaje podprogramów: procedury i funkcje. Wywołanie procedury jest instrukcją; wywołanie funkcji jest wyrażeniem i zwraca pewną wartość. Definicja podprogramu może być podana w dwóch częściach: deklaracji podprogramu, która definiuje jego interfejs, i definicji podprogramu, która określa jego działanie.

    1. Przeciążanie funkcji i procedur

Ada 95 pozwala na bezkolizyjne istnienie więcej niż jednej funkcji/procedury o tej samej nazwie; warunkiem jest możliwość jednoznacznej ich identyfikacji poprzez sygnatury. Na sygnaturę składa się: nazwa podprogramu plus nazwy, typy, tryby, i kolejność parametrów oraz typ wyniku. Jeżeli np. zdefiniujemy typ wyliczeniowy

type All_Days is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);

to poprawne będą deklaracje dwóch różnych funkcji o takiej samej nazwie:

function Day return All_Days;

function Day(a_date: in Date_Type) return All_Days;

Pierwsza funkcja zwraca dzień tygodnia (dzisiejszy), zas druga dzień tygodnia z podanej daty. Przy wołaniu kompilator decyduje, którą z nich wywołać, sprawdzając argumenty wywołania.

    1. Przeciążanie operatorów

Podobnie jak w C++, w języku Ada 95 można redefiniować operatory standardowe; jednak inaczej niż w C++ wykonuje się te redefinicje poza klasami, i dla dowolnego operatora z dowolnym typem. Składnia deklaracji polega na zastąpieniu nazwy funkcji (operatory są zawsze funkcjami) nazwą operatora w podwójnych apostrofach, np.

function "+"(Left, Right: in Integer) return Integer;

function "*"(Left, Right : Matrix) return Matrix;

Operatory, które wolno przeciążać, zestawiono w tablicy poniżej.

=

<

<=

>

>=

+

-

&

abs

not

*

/

mod

rem

**

And

or

xor

  1. Moduły programowe

Ada jest językiem wysoce modularnym. Struktura logiczna programu w Adzie składa się z modułów, nazywanych jednostkami programowymi. Jednostka programowa może być podprogramem, pakietem, zadaniem, modułem chronionym, lub modułem parametryzowanym. Jeżeli jednostkę programową poprzedzimy dyrektywą with.... i/lub use..., to staje się ona jednostką kompilacji.

Podstawową jednostką programową jest w języku Ada 95 pakiet (package). Pakiet zwykle zawiera deklaracje (nazywane też specyfikacją) i ciało (implementację). Deklaracje określają, co pakiet będzie robił; mogą one zawierać część publiczną i prywatną. Część publiczna deklaracji stanowi interfejs użytkownika; zawiera ona deklaracje typów prywatnych i deklaracje podprogramów, które podają wszystkie informacje niezbędne do ich wywołania. Na przykład fragment publicznej części specyfikacji mógłby mieć postać deklaracji funkcji:

function item(s: STACK) return X;

Część prywatną wykorzystuje translator, a nie użytkownik pakietu. Ta część specyfikacji określa szczegóły typów prywatnych i wartości używanych stałych, a także dane dotyczące reprezentacji. Specyfikacja może także obejmować deklaracje podprogramów i pakietów zewnętrznych w stosunku do danego pakietu. Ciało (treść) pakietu (package body) zawiera szczegóły implementacji wielkości zadeklarowanych w specyfikacji i jako opcję część wykonawczo-inicjującą. Treść pakietu może występować bezpośrednio po jego specyfikacji lub może być napisana jako oddzielna jednostka kompilacji.

Pakiet może wystąpić jako deklaracja w programie lub jako jednostka biblioteczna oddzielnie kompilowana. W tym drugim przypadku dowolna jednostka kompilacji może odwoływać się do jednostki bibliotecznej przez podanie jej nazwy, poprzedzonej słowem kluczowym with lub use. Deklaracje pakietu są zawsze poprzedzone słowem kluczowym package, zaś implementacja słowami kluczowymi package body. Niżej pokazano prostą specyfikację stosu.

package REAL_STACKS is

type STACK(capacity: Positive) is private;

procedure put(s: in out STACK; x: in Float);

procedure remove(s: in out STACK);

function item(s: STACK) return Float;

function empty(s: STACK) return Boolean;

Overflow, Underflow: Exception;

private

type STACK_CONTENTS is array(Positive range<>) of Float;

type STACK(capacity: Positive) is

record

table: STACK_CONTENTS(1..capacity);

count: Natural := 0;

end record;

end REAL_STACKS;

Specyfikacja REAL_STACKS ma część publiczną i prywatną, przy czym w części publicznej typ STACK zadeklarowano jako prywatny, a jego definicję umieszczono w prywatnej części specyfikacji. Postąpiono więc zgodnie z zasadą ukrywania informacji: klient pakietu może mieć dostęp do zmiennych typu STACK za pomocą procedur put i remove oraz funkcji item i empty, ale nie ma bezpośredniego dostępu do prywatnej reprezentacji stosu STACK, w szczególności do pól rekordu. Zawartości tych pól mogą być zmieniane jedynie poprzez wywołania odpowiednich funkcji lub procedur.

Identyfikatory Positive i Natural oznaczają podtypy Integer; specyfikacja typu o postaci array(Positive range<>) opisuje szablon (typ parametryzowany) dla typów array, gdzie para nawiasów kątowych “<>”, nazywana Box symbol jest parametrem formalnym; w ogólności szablon ten ma postać: array(TYP range<>). Dla wyprowadzenia konkretnego typu tablicowego z takiego szablonu należy podać faktyczny zakres indeksów, będący podzakresem wartości typu TYP, jak na przykład w STACK_CONTENTS(1..capacity). W rezultacie STACK jest typem parametryzowanym i każda deklaracja zmiennej tego typu musi podawać wartość dla capacity, np.

s: STACK(1000)

Nazwy wyjątków (Overflow, Underflow) są tymi, które mogą być zgłaszane (instrukcją raise) przez podprogramy pakietu i propagowane do klientów.

    1. Użycie pakietów

Pokazany wyżej pakiet może być używany przez kod klienta, np. przez inny pakiet, który potrzebuje stosu liczb rzeczywistych:

s: REAL_STACKS.STACK(1000);

REAL_STACKS.put(s,3.5);

If REAL_STACKS.empty(s) then ...

Zauważmy też, że możemy tutaj uniknąć notacji z kropką, jeżeli dodamy dyrektywę use:

use REAL_STACKS:

s: STACK(1000);

put(s,3.5);

If empty(s) then ...

Jednak ze względu na możliwe konflikty nazw zwykle zaleca się używanie pełnych nazw kwalifikowanych.

    1. Implementacja pakietów

Ciało pakietu zawiera szczegóły implementacyjne, które nie powinny być widoczne dla klienta danego pakietu. Dlatego zwykle specyfikacja i ciało pakietu są umieszczane w oddzielnych plikach. Jednak istnieją przypadki, gdy pakiet, na przykład zawierający pewną procedurę, nie ma oddzielnej specyfikacji; wówczas ciało pakietu automatycznie podaje swoją specyfikację. Innym przypadkiem szczególnym może być pakiet zawierający wyłącznie deklaracje stałych (np. pi, pierwiastek kwadratowy z dwóch, itp.). Taki pakiet nie może mieć ciała, ponieważ nie jest ono potrzebne. Jednak większość pakietów wymaga zarówno deklaracji, jak i ciała. Kontynuując nasz przykład z pakietem REAL_STACKS zawierającym typ parametryzowany STACK, moglibyśmy zapisać jego ciało z jedną pełną definicją podprogramu:

package body REAL_STACKS is

procedure put(s: in out STACK; x: in Float) is

begin

if s.count = s.capacity then

raise Overflow

endif;

s.count := s.count + 1;

s.table(count) := x;

end put;

procedure remove(s: in out STACK) is

-implementacja remove

end remove;

function item(s: STACK) return Float is

--implementacja item

end item;

function empty(s: STACK) return Boolean is

--implementacja empty

end empty;

end REAL_STACKS;

      1. Kompilacja programu wieloplikowego

Jeżeli specyfikację pakietu Real_Stacks umieścimy w pliku Real_Stacks.ads (rozszerzenie nazwy .ads mówi o tym, że w pliku umieszczono specyfikację pakietu Real_Stacks), ciało w pliku Real_Stacks.adb, zaś procedurę, która wykorzystuje ten pakiet, w pliku Stacks _main.adb, to kompilacja obejmie następujące kroki:

  1. Krok 1: gcc -c Stacks _main.adb

  2. Krok 2: gcc -c Real_Stacks.adb

  3. Krok 2: gnatbind Stacks _main

  4. Krok 2: gnatlink Stacks _main

Wymienione trzy kroki można zastąpić jednym: gnatmake Stacks _main.adb.

    1. Parametryzacja pakietów

Zdefiniowany wyżej pakiet REAL_STACKS pozwalał deklarować struktury stosowe o elementach typu Float. Stosując przyjętą w języku Ada metodę parametryzacji (genericity) można go przekształcić w pakiet GENERIC_STACKS parametryzowany dowolnym typem, stosując następującą składnię:

generic

type Item is private;

package GENERIC_STACKS is

type STACK(capacity: Positive) is private;

procedure put(s: in out STACK; x: in Item);

procedure remove(s: in out STACK);

function item(s: STACK) return Item;

function empty(s: STACK) return Boolean;

Overflow, Underflow: Exception;

private

type STACK_CONTENTS is array(Positive range<>) of Item;

type STACK(capacity: Positive) is

record

table: STACK_CONTENTS(1..capacity);

count: Natural := 0;

end record;

end GENERIC_STACKS;

Definicja ciała pakietu GENERIC_STACKS jest podobna do REAL_STACKS, przy czym nie poprzedzamy jej klauzulą generic:

package body GENERIC_STACKS is

--Definicje jak poprzednio, zastępując wszędzie typ

--Float przez Item

end GENERIC_STACKS;

Tak zdefiniowany pakiet GENERIC_STACKS jest faktycznie szablonem pakietów, ponieważ jego użycie wymaga podania parametrów aktualnych. Jeżeli np. chcemy mieć pakiet zawierający stos liczb całkowitych, to możemy go zadeklarować instrukcją

package Stack_Int is new GENERIC_STACKS(Integer);

i używać, jak pokazano poprzednio.

Kreacja rzeczywistego pakietu z szablonu jest nazywana w języku Ada tworzeniem wystąpienia (instantiating), a rezultat wykonania instrukcji, jak np. package REAL_STACKS_1 is new GENERIC_STACKS(Integer);

wystąpienie (instance).

Użyte w kontekście generic type Item is private;” słowo kluczowe private oznacza tutaj, że typ Item może być dowolnym typem Ady, ale inne pakiety mogą wykonywać operacje na zmiennych tego typu jedynie za pomocą zadeklarowanych w pakiecie STACKS operacji publicznych oraz operacje przypisania :=, przyrównania = i przekazania jako argumentu aktualnego innej operacji. Dopuszczalne operacje na typie Item mogą być jeszcze bardziej ograniczone, gdy zadeklarujemy go jako “limited private”; oznacza to, że dla typu Item mogą nie istnieć operacje przypisania i przyrównania.

    1. Dziedziczenie typów i hierarchie pakietów

W języku Ada 95 dziedziczenie jest rozumiane jako rozszerzenie istniejących typów o nowe pola rekordu i podprogramy, przy czym prawo do rozszerzenia istniejącego typu o nowe składowe (pola rekordu) i nowe operacje mają jedynie tak zwane “typy znakowane”, których deklaracje i definicje zawierają słowo kluczowe tagged. Powstałe przez dziedziczenie typy nazywa się potomnymi lub pochodnymi (derived types), zaś typy podlegające rozszerzaniu określa się jako typy rodzicielskie lub bazowe (base types). Jeżeli typ bazowy nie jest znakowany, to typ pochodny dziedziczy operacje typu bazowego, natomiast nie może dodawać nowych składowych. Mechanizm znakowania jest użyteczny także w tym, że umożliwia wiązanie późne (w fazie wykonania) i polimorfizm. Jak implikuje jego nazwa, z każdym typem znakowanym jest skojarzony pewien znacznik, który w języku Ada 95 identyfikuje dany typ.

Typy pochodne w języku Ada deklaruje się z reguły w części publicznej specyfikacji instrukcją:

type DERIVED is new BASE with private;

Użyta w tej deklaracji fraza with private oznacza, że faktyczna implementacja takiego typu będzie ukryta w prywatnej części specyfikacji i nie może być bezpośrednio używana przez kody klientów. Oznacza to również możliwość późniejszej zmiany implementacji bez wpływu na kod użytkownika obiektów typu DERIVED.

Dziedziczenie w Adzie jest pojedyncze; jeżeli liczba poziomów drzewa dziedziczenia jest stosunkowo niewielka, to całą hierarchię umieszcza się w jednym pakiecie, jak pokazano niżej.

package Accounts is

type MONEY is digits 12 delta 0.01;

type ACCOUNT is tagged private;

procedure deposit(a: in out ACCOUNT; amount: in MONEY);

procedure withdraw(a: in out ACCOUNT; amount: in MONEY);

function balance(a: in ACCOUNT) return MONEY;

type CHECKING_ACCOUNT is new ACCOUNT with private;

function balance(a: in CHECKING_ACCOUNT) return MONEY;

type SAVINGS_ACCOUNT is new ACCOUNT with private;

procedure compound(a:in out SAVINGS_ACCOUNT;period:in Positive);

private

type ACCOUNT is tagged

record

initial_balance: MONEY := 0.0;

owner: String(1..30);

end record;

type CHECKING_ACCOUNT is new ACCOUNT with null record;

type SAVINGS_ACCOUNT is new ACCOUNT with record

rate: Float;

end record;

end Accounts;

W pakiecie Accounts zadeklarowano typ znakowany ACCOUNT o prywatnej implementacji. Na zmiennych tego typu i typów pochodnych mogą operować podprogramy publiczne: procedury deposit i withdraw oraz funkcja balance.

Typy pochodne CHECKING_ACCOUNT i SAVINGS_ACCOUNT dziedziczą atrybuty i metody od typu ACCOUNT, przy czym typ SAVINGS_ACCOUNT rozszerza typ ACCOUNT o atrybut rate i procedurę compound, zaś typ CHECKING_ACCOUNT ma te same atrybuty, co typ bazowy (mówi o tym fraza with null record). Zwróćmy również uwagę na ponowną deklarację funkcji balance dla typu CHECKING_ACCOUNT, co sygnalizuje jej redefinicję (tutaj nie pokazaną). Oznacza to, że w języku Ada 95 istnieje możliwość przeciążania procedur i funkcji.

    1. Typy abstrakcyjne

Podobnie, jak w innych językach obiektowych, hierarchia dziedziczenia w Adzie 95 może się zaczynać od typu abstrakcyjnego, to jest takiego, który ma pełną specyfikację, ale niepełną implementację.

Typy abstrakcyjne muszą być typami znakowanymi, ponieważ tylko takie typy mogą być rozszerzane. Dla typu abstrakcyjnego można deklarować abstrakcyjne podprogramy. Przykładowa deklaracja pakietu z typem abstrakcyjnym może mieć postać:

package Sets is

subtype El_Type is Natural; --podtyp typu Integer.

type Set is abstract tagged null record;

function Empty return Set is abstract;

function Union(Left, Right: Set) return Set is abstract;

function Intersection(Left, Right: Set) return Set is abstract;

procedure Take(Element:out El_Type;From:in out Set)is abstract;

end Sets;

Oczywiste jest, że tworząc typy pochodne od typu abstrakcyjnego, musimy podawać definicje wszystkich jego podprogramów; widać to choćby stąd, że funkcje Union i Intersection nie mogą zwrócić obiektu typu Set (typ Set nie ma wystąpień) musi to być obiekt jednego z typów potomnych.

    1. Hierarchie pakietów

W języku Ada 95 oprócz hierarchii dziedziczenia typów można budować hierarchie pakietów potomnych (child units). Jest to koncepcja bliska dziedziczeniu, ale różna od niego. Konceptualnie pakiet potomny jest częścią swojego pakietu rodzicielskiego, ale pakiet potomny może być kompilowany oddzielnie (po kompilacji swojego pakietu rodzicielskiego) bez rekompilacji , czy modyfikacji pakietu rodzicielskiego.

Ogólna postać deklaracji pakietu potomnego jest następująca:

nazwa_pakietu_ rodzicielskiego. nazwa_pakietu_potomnego;

Przykładowe deklaracje pakietów mogą mieć postać:

package A is ... end A; package A.B is ... end A.B; package A.C is ... end A.C;

package A.C.E is ... end A.C.E; package A.C.E.Y is ... end A.C.E.Y;

Pakiet potomny otrzymuje wszystkie atrybuty i operacje pakietu rodzicielskiego i może dodawać swoje własne. Ta cecha jest szczególnie użyteczna, jeżeli w pakiecie rodzicielskim (np. A) definiuje się prywatne typy znakowane. Wtedy część publiczna pakietu A jest dostępna dla części publicznej pakietu potomnego (np. A.B), a część prywatna A jest dostępna dla części prywatnej A.B.

Wymienioną własność wykorzystuje się często dla dekompozycji złożonego systemu na podsystemy. Na przykład zamiast deklarować trzy typy w pokazanym wcześniej pakiecie Accounts lepiej byłoby go podzielić na trzy: Accounts wprowadzający typ znakowany ACCOUNT, Accounts.Checking wprowadzający typ pochodny CHECKING_ACCOUNT i Accounts.Saving wprowadzający typ pochodny SAVINGS_ACCOUNT.

    1. Polimorfizm statyczny i dynamiczny

Polimorfizm statyczny to termin używany (w kręgach związanych z językiem Ada) na określenie przeciążania operatorów, procedur i funkcji. Przy tym rodzaju polimorfizmu wszystkie wywołania operacji i rozróżnienie metod (method resolution) są wykonywane w fazie kompilacji. Polimorfizm dynamiczny oznacza, że proces ten zachodzi w fazie wykonania, a wiązanie operacji z metodą jest wiązaniem późnym. W języku Ada 95 dynamiczne dopasowanie metody do operacji (dispatch) jest możliwe tylko dla typów znakowanych i tylko dla operacji prymitywnych, którą to nazwą określa się operacje zdefiniowane w bezpośrednim zasięgu typu znakowanego. Wynika to stąd, że każdemu typowi znakowanemu towarzyszy tablica wskaźników do jego operacji prymitywnych (dispatch table), a każdy obiekt typu znakowanego zawiera znacznik (Tag”), który de facto jest adresem tej tablicy. Jeżeli więc w danym pakiecie zdefiniowano typ znakowany i typy od niego pochodne, to dopasowanie dynamiczne nie jest możliwe dla operacji, które zostały dodane w definicji typu pochodnego. Podobnie, jeżeli dany pakiet ma pakiety potomne, to dodane w nim operacje również nie są operacjami prymitywnymi.

Typy znakowane pozwalają tworzyć hierarchie powiązanych mechanizmem dziedziczenia typów pochodnych, nazywanych także rodzinami typów. W programowaniu obiektowym częsta jest sytuacja, gdy chcemy, aby pewna część programu operowała na tej hierarchii, ale bez decydowania a priori, który typ z hierarchii ma być przetwarzany w danej chwili. Ponieważ Ada jest językiem o typizacji silnej, potrzebny był mechanizm, dzieki któremu określone wystąpienie (obiekt) z dowolnego poziomu hierarchii może być przekazane jako parametr. Wprowadzony dla tego celu przez twórców języka Ada 95 mechanizm nazwano programowaniem klasowym. Każdy podprogram w programowaniu klasowym operuje na tzw. klasie wyprowadzeń. W języku Ada 95 klasą wyprowadzeń nazywa się zbiór typów, pochodnych od pewnego znakowanego typu bazowego T; elementy tego zbioru identyfikuje się za pomocą notacji TClass (czytamy: "T tick class"), a T'Class nazywa się typem klasowym. Tak więc każdy typ pochodny od typu bazowego (bezpośrednio lub pośrednio) jest elementem tego zbioru, jak pokazano na rysunku poniżej.

0x01 graphic

Rys. 4-1. Klasy wyprowadzeń

Z rysunku odczytujemy, że typ klasowy Building'Class obejmuje typy:

Building, House, Block, Home, Flat oraz Cottage

zaś np. typ klasowy Block'Class zawiera typy: Block i Flat.

Użyteczność klasy wyprowadzeń dla osiągnięcia polimorfizmu pokażemy na następującym przykładzie. Załóżmy, że od typu bazowego Figure dziedziczą typy Rectangle i Circle, a Square jest typem pochodnym od Rectangle; typ Figure zawiera funkcję Area obliczającą pole figury, a typy pochodne zawierają redefinicje tej funkcji. Wówczas łatwo jest napisać procedurę (np. w pakiecie potomnym Figure.Dispatcher), której parametrem formalnym będzie dowolna z tych figur:

procedure Print_Area(F: in FigureClass) is

begin

Put(Area = );

Put(Area(F)); --wywołanie właściwej funkcji dla F

New_Line;

end Print_Area;

Jeżeli parametr aktualny F będzie np. typu Circle, to instrukcja Put(Area(F)); zostanie wykonana dla obiektu tego typu.

Zauważmy, że konstrukcja z klasą wyprowadzeń jest podobna do obligatoryjnej w C++ deklaracji dynamicznie wiązanej funkcji jako wirtualnej, za wyjątkiem tego, że tutaj klient (procedura Print_Area) musi jawnie wybierać pomiędzy wiązaniem statycznym a dynamicznym.

    1. Typy dostępowe

W języku Ada 95 wprowadzono tak zwane typy dostępowe (access types). Wystąpienia (zmienne) tych typów mogą przechowywać wskazania lub odnośniki do obiektów innych typów. Konieczność wprowadzenia typów dostępowych wynika stąd, że bez nich byłoby prawie niemożliwe konstruowanie rekurencyjnych struktur danych, jak np. listy jedno- i dwukierunkowe, drzewa, grafy i macierze rzadkie. Cechą charakterystyczną takich struktur jest zdolność przechowywania zmiennej ilości informacji. Przykładem może być deklaracja elementu listy jednokierunkowej

type List_Node;

type List_Node_Access is access List_Node;

type List_Node is

record

Data: Integer;

Next: List_Node_Access;

end record;

gdzie type List_Node; jest niekompletną deklaracją typu, która pozwala uniknąć błędnego koła w definicji (typ List_Node zależy od definicji typu List_Node_Access, a typ List_Node_Access zależy od definicji List_Node).

Zmienne typu dostępowego mogą odwoływać się do pewnego tworzonego dynamicznie obiektu, albo mieć wartość null. Są one podobne do wskaźników w językach C lub C++. Jednakże Ada 95 nakłada na typy dostępowe pewne ograniczenia, które czynią je bardziej bezpiecznymi, niż wskaźniki C/C++. W szczególności:

Dostęp do zawartości obiektu wskazywanego uzyskuje się dodając po nazwie zmiennej dostępowej kropkę i przyrostek all, jak pokazano w instrukcji (6) na rysunku 4-2.

0x01 graphic

Rys. 4-2. Operacje na listach

Instrukcja (6) kopiuje całą zawartość obiektu wskazywanego przez Current do obiektu wskazywanego przez Root. Natomiast instrukcja (7) zmienia wskazanie: zmienna Root zacznie od tej chwili wskazywać na węzeł Current. Zauważmy, że wykonanie instrukcji (7) wytworzyło niekorzystną sytuację, ponieważ węzeł typu List_Node, na który poprzednio wskazywała zmienna Root, stał się niedostępny. Powstał więc nieużytek w pamięci dynamicznej. Jeżeli kompilator Ady jest wyposażony w kolektor nieużytków (co nie zawsze ma miejsce), to pamięć ta zostanie odzyskana. Jeżeli nie, to należałoby przed instrukcją (7) umieścić wywołanie odpowiedniej procedury dealokacji. W języku Ada 95 istnieje procedura parametryzowana Unchecked_Deallocation

generic

type Object(<>) is limited private;

type Name is access Object;

procedure Unchecked_Deallocation(X: in out Name);

do której przekazuje się dwa parametry: typ i dostęp do tego typu. Jeżeli utworzymy wystąpienie tej procedury o nazwie Free”:

procedure Free is new Unchecked_Deallocation(List_Node, List_Node_Access);

to możemy wywołać procedurę Free dla węzła Root

Free(Root);

Wykonanie Free(Root) spowoduje przypisanie do zmiennej Root wartości null i zwolnienie wskazywanej przez Root pamięci.

Zmienne dostępowe mogą pełnić rolę parametrów, przesyłanych do operacji definiowanych dla typów znakowanych. W takich przypadkach w języku Ada 95 stosuje się nowy pseudo-tryb access (oprócz in, out i in out), jak np. w deklaracji:

procedure Get(Agent: access Occupant; Direct_Object: access OccupantClass);

Poprzedzenie drugiego parametru formalnego Occupant'Class słowem kluczowym access oznacza tutaj, że parametr wejściowy (Direct_Object) musi być typu Occupant lub dowolnym z jego typów pochodnych.

    1. Zgłaszanie i obsługa wyjątków

Obsługa wyjątków była od początku integralną częścią języka. W języku Ada 95 wyjątek reprezentuje nieoczekiwane zdarzenie (błąd), które może spowodować zakończenie wykonywania programu lub jego nieprzewidziane zachowanie. Implementację obsługi wyjątków oparto na modelu z terminacją, który zakłada następującą sekwencję zdarzeń:

Przy tym wyjątek może być zgłaszany jawnie przez instrukcje kodu źródłowego, lub przez pewne nieoczekiwane zdarzenie podczas wykonywania programu.

Wyjątki deklaruje się w podprogramach jako zmienne typu exception, np. Singularity: exception; a zgłasza się (jawnie) w instrukcji raise: raise Singularity;. Jeżeli dany podprogram wywołuje inny, który zgłosił wyjątek, to środowisko wykonawcze opuszcza bieżącą sekwencję instrukcji (pomiędzy słowami kluczowymi begin i end) wywołanego podprogramu i zaczyna szukać odpowiedniej procedury obsługi. Jeżeli nie znajduje, to opuszcza wywołany podprogram, zwija jego stos i wraca do podprogramu wołającego. Jeżeli i tam nie znajdzie procedury obsługi, kończy wykonanie programu.

Niżej pokazano prosty przykład procedury, która próbuje otworzyć plik, a jeśli plik nie istnieje, tworzy go:

Procedure Open_Or_Create(File: in out File_Type;

Mode: File_Mode; Name: String) is

Begin

Open(File, Mode, Name);

exception

when Name_Error => Create(File, Mode, Name);

end Open_Or_Create;

    1. Współbieżność

Już w swojej starszej wersji językAda był wyposażony w mechanizmy współbieżnego wykonywania fragmentów programu, nazywanych zadaniami (tasks). Ada 95 zapewnia współbieżne wykonywanie zadań na poziomie wątków (threads) programowych. Przypomnijmy, że wątki są jednostkami wykonania w obrębie jednego procesu; każdy wątek ma własny stos wykonania, ale współdzieli z procesem tę samą przestrzeń adresową. Zadanie Ady definiuje się jako specjalny typ; niżej pokazano fragmenty deklaracji i definicji.

package Threads is

task type Zadanie is

entry Start(Message: String; Count: Natural);

end Zadanie;

end Threads;

package body Threads is

task body Zadanie is

Maximum_Count: Natural;

R: String;

begin

accept Start(Message: String; Count: Natural) do

R := Message; --kopiuj dane rendezvous

Maximum_Count := Count; --do zmiennych lokalnych

end Start;

--Dalsze instrukcje

end Zadanie;

end Treads;

Instrukcja entry, nieco podobna do deklaracji procedury, deklaruje, jaki rodzaj żądania (komunikat) może być kierowany do danego zadania. Ciało zadania definiuje czynności, jakie będzie wykonywać zadanie po jego uruchomieniu (aktywacji). Instrukcja accept czeka na żądanie od innego zadania; jeżeli otrzyma odpowiednie parametry aktualne, zostanie uruchomiona (fragment kodu pomiędzy do i end Start), a zadanie, które przesłało komunikat, zostaje na ten czas zawieszone. Fragment procesu, w którym zadania komunikują się pomiędzy sobą, nosi nazwę rendezvous (spotkanie). W pokazanym przykładzie w trakcie spotkania następuje kopiowanie danych, przesłanych przez inne zadanie do zmiennych lokalnych. Po zakończeniu spotkania mogą być wykonywane obydwa zadania.

Ponieważ zadania są typami, tworzy się je i używa tak samo, jak zmienne innych typów, np.

Zadanie_1 : Zadanie;

Zadanie_2 : Zadanie;

Zadanie_1.Start(Hej, tu zadanie 1, 10);

Zadanie_2.Start(Hej, tu zadanie 2, 20);

Sterowanie dostępem do zasobów przydzielonych zadaniom odbywa się za pomocą tzw. typów ochronnych, których obiekty zapobiegają utracie spójności danych. Typ ochronny można traktować jako zaawansowaną formę semafora” lub monitora”. Zwykle zawiera on dane, do których dostęp jest kontrolowany przez pewien zbiór operacji. Przykładowa deklaracja i definicja takiego typu może mieć postać:

protected type Zasoby is

entry Zajmij;

procedure Zwolnij;

private

Zajety: Boolean := False;

end Zasoby;

protected body Zasoby is

entry Zajmij when not Zajety is

begin

Zajety := True;

end Zajmij;

procedure Zwolnij is

begin

Zajety := False;

end Zwolnij;

end Zasoby;

Składnia dla tworzenia i użycia wystąpienia typu ochronnego jest taka sama, jak dla innych typów:

Sterowanie: Zasoby;

Sterowanie.Zajmij;

--Instrukcje

Sterowanie.Zwolnij;

    1. Podsumowanie

Język Ada, mimo swego wojskowego rodowodu, nadaje się do zastosowań naukowych, przemysłowych i handlowych. Ze względu na osiągalną niezawodność, Ada 95 jest wykorzystywana w implementacjach systemów informacyjnych dużej skali, systemów rozproszonych i w obliczeniach naukowych. Tym niemniej, nadal dominują zastosowania wojskowe. Jako charakterys­tyczne przykłady można podać wykorzystanie środowiska programowego Ady i zintegrowanej z nim metody projektowania HOOD (ang. Hierarchical Object-Oriented Design) w projekcie stacji kosmicznej European Space Agency (ESA) oraz w konstrukcji urządzeń ostrzegaw­czych obrony powietrznej.

Ada jest językiem o silnej typizacji statycznej. Typy Ady nie są klasami w sensie używanym w technologii obiektowej, ponieważ składniowo nie zapewniają hermetyzacji, chociaż zapewniają ukrywanie informacji. Można definiować i używać metody polimorficzne wykorzystując pojęcie klasy wyprowadzeń.

Ada zawiera dogodne mechanizmy dla tworzenia fragmentów oprogramowania, które mogą być wielokrotnie używane w różnych projektach. Można w tym celu wykorzystywać dziedziczenie, parametryzowane jednostki programowe (pakiety i/lub podprogramy) oraz pakiety potomne.

Oprócz ewidentnych zalet, Ada 95 nie jest wolna od wad. Zasadniczą wadą jest duża złożoność języka: Ada 95 dodała do skomplikowanych konstrukcji języka Ada 83 całkowicie nowy zbiór konstrukcji, z wielu potencjalnymi interakcjami zarówno pomiędzy nimi samymi, jak i z konstrukcjami starymi. Inna jest także, a przy tym bardziej złożona, interpretacja podstawowych pojęć, które zdecydowały o powodzeniu języków obiektowych. Jako przykład można wymienić co najmniej pięć dość zawikłanych koncepcji, z których każda pokrywa pewne aspekty klas:

  1. Pakiety, które są modułami, ale nie typami, mogą być parametryzowane i oferować coś podobnego do dziedziczenia w postaci pakietów potomnych.

  2. Znakowane typy rekordowe, które są typami, ale nie modułami i oferują pewną postać dziedziczenia, choć inaczej niż klasy nie pozwalają na syntaktyczną inkluzję metod w deklaracjach typu.

  3. Zadania, które są modułami, ale nie typami i nie pozwalają na dziedziczenie.

  4. Typy zadaniowe (task types), które są modułami i typami, ale nie mogą być parametryzowane (choć mogą być włączane do typów parametryzowanych) i nie mają dziedziczenia.

  5. Typy ochronne (protected types), które są typami i mogą zawierać metody.

Standard języka Ada 95 jest oficjalnie zdefiniowany w dokumencie Ada Language Reference Manual (http://www.adahome.com/rm95/). Publicznie dostępny jest kompilator GNAT (GNU Ada Translator) na platformy MS DOS, OS/2, Windows 95/NT i Linux (ftp ftp://cs.nyu.edu/pub/gnat/), oraz Free Aonix Ada 95 Compiler na platformy Windows 95/NT, SUN Solaris, HP-UX. Z biblioteki publicznej Aonix (ftp://ftp.aonix.com/pub/ada/public/pal) można także otrzymać Ada to Java Compiler. Kompilacja skrośna z Ady do Javy pozwala pisać aplety (aplet jest programem w języku Java, który jest uruchamiany automatycznie, gdy użytkownik WWW przegląda stronę” WWW zawierającą ten program) w języku Ada 95. Istnieje też wiele kompilatorów komercyjnych i programów narzędziowych ułatwiających projektowanie systemów implementowanych w języku Ada.

Ada 95 zawiera szereg pakietów i pewne specjalne dyrektywy kompilatora (pragmy) dla łączenia fragmentów programów napisanych w C, C++, COBOL i Fortran z programami Ady. Dyrektywa Import importuje” podprogram z innego języka do programu Ady, dyrektywa Export eksportuje” podprogram Ady do innego języka, zaś dyrektywa Convention określa model pamięci w obcym” języku dla implementacji danego typu.Dostępne są także interfejsy języka Ada 95 do pewnych środowisk programowych, w tym: Win32 API, X11Ada, SAMeDL (interfejs do SQL) oraz narzędzia wiążące programy Ady z językiem IDL (Interface Definition Language) specyfikacji CORBA (Common Object Request Broker Architecture).

LITERATURA

  1. Pyle I.C. Ada. Wyd. Naukowo-Techniczne, Warszawa, 1986.

  2. Caverly P., Goldstein P. Introduction to Ada: A Top-Down Approach for Programmers. Wadsworth Inc., 1986.

  3. Texel P. Introductory Ada. Packages for Programming. Wadsworth Inc., 1986.

  4. Rosen J.P. What Orientation Should Ada Objects Take?. Communications of the ACM, vol. 35, No. 11(November 1992), str. 71-76.

  5. Riehle R. Inaugurating an Ada Column. JOOP, vol. 8/No. 4, pp.62-69, August 1995.

  6. Riehle R. Satisfying Software Requirements Extensions. JOOP, vol. 8/No. 5, pp.78-84, September 1995.

  7. Riehle R. Reuse through Generic Templates. JOOP, vol. 8/No. 7, pp.66-70, Nov-Dec 1995.

  8. Riehle R. Reliability: Does Language Matter?. JOOP, vol. 9/No. 1, pp.87-92, March-April 1996.

  9. Riehle R. Dynamic Polymorphism. JOOP, vol. 9/No. 3, pp.60-74, June 1996.

  10. Riehle R. Managing Runtime Faults. JOOP, vol. 9/No. 5, pp.73-77, Sept. 1996.

  11. Riehle R. Ada Pointers: Access to Information. JOOP, vol. 9/No. 6, pp.76-81, October 1996.

  12. Riehle R. Ada and the Notion of Class. JOOP, vol. 9/No. 7, pp.66-74, Nov/Dec 1996.

  13. Smyda J. Ada95. http://www.republica.pl/korcala/ada/

  14. Wheeler D.A. Ada 95 Lovelace Tutorial. http://www.adahome.com/Tutorials/Lovelace/ lovelace.html, 1998.

  15. Ada Reference Manual, v. 6.0. http://www.adahome.com/

  16. Johnston S. Ada-95: A guide for C and C++ programmers. http://www.ankh-morpork.com/.

  17. Huzar Z. i in. Ada 95. Wyd. Helion, 1998.

  18. ftp://ftp.cs.tu-berlin.de/pub/gnat/gnat3.13p/winnt/

  19. http://www.pwr.wroc.pl/POLITECHNIKA/oprogramowanie

36

37



Wyszukiwarka

Podobne podstrony:
1a, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Systemy operacyjne, Wykład, Systemy, Sy
arch02, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch05, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch07, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch06, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch01, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch08, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch10, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch09, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
TAM GDZIE PLUS TO ODPOWIEDŹ POPRAWNA, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Syste
arch03, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
arch04, UŁ Sieci komputerowe i przetwarzanie danych, Semestr II, Architektura systemów komputerowych
Programowanie opracowanie, Informatyka, Informatyka semestr II, 3.Programowanie strukturalne i obiek
Moduł Crt, Informatyka, Informatyka semestr II, 3.Programowanie strukturalne i obiektowe
Teoria (troche), Studia, Semestr II, Algorytmy i struktury danych

więcej podobnych podstron