1.19k likes | 1.41k Vues
Programowanie rozproszone. ADA 95. Wstęp. Ada 95 jest uniwersalnym j ęz ykiem oprogramowania przeznaczonym do tworzenia oprogramowania dużej skali. Rozszerzenie wersji Ada 83. Tworzenie języka Ada 83 na zlecenie Departamentu Obrony USA.
E N D
Programowanie rozproszone ADA 95
Wstęp Ada 95 jest uniwersalnym językiem oprogramowania przeznaczonym do tworzeniaoprogramowania dużej skali. Rozszerzenie wersji Ada 83. Tworzenie języka Ada 83 na zlecenie Departamentu Obrony USA. Uznana przez ANSI (American National Standards Institute). Schemat procesu wytwarzania oprogramowania Nieformalny opis problemu Analiza problemu Specyfikacja problemu Analiza wymagań na oprogramowanie Specyfikacja oprogramowania Projekt Implementacja Oprogramowanie Wdrażanie i eksploatacja
Ada jest związana z: • bezpośrednio z fazą implementacji, • pośrednio z fazą projektowania (w części język projektowania – obiektowość (języka), • fazą wdrażania i eksploatacji- łatwość modyfikacji oprogramowania w zależności od • zmieniających się wymogów użytkowych. Własności języka ADA 95 • obiektowość – usprawnia fazę implementacji, możliwość implementacji fragmentów programu przez niezależne zespoły programistyczne • mechanizm rozłącznej kompilacji, • - możliwość łączenia programów napisanych w innych językach, • własność polimorfizmu statycznego i dynamicznego (Ada 95), • mechanizmy tworzenia programów równoległych i programów • rozproszonych (Ada 95).
Zastosowania • asynchroniczne i synchroniczne systemy współbieżne rozproszone • tworzenie systemów czasu rzeczywistego, • specyficzną klasą zastosowań są systemy związane z bezpieczeństwem: • systemy nawigacyjne w lotnictwie, • systemy sygnalizacyjne w kolejnictwie • - systemy medyczne. Struktura logiczna programu Podstawowe jednostki programowe: • podprogramy (procedury i funkcji) • Pakiety • jednostki rodzajowe (rodzajowe podprogramy i pakiety) • Zadania • obiekty chronione
Struktura fizyczna programu Strukturyzacja fizyczna programu wiąże się z potrzebami programowania w dużej skali: • - mechanizmy rozdzielnej kompilacji • wielokrotne używanie komponentów oprogramowania poprzez tworzenie własnych • bibliotek Jednostkami kompilacji mogą być dowolne jednostki programowe. Jednostkami bibliotecznymi mogą być podprogramy (procedury i funkcji), pakiety, jednostki rodzajowe (rodzajowe podprogramy i pakiety) Jednostki biblioteczne -> potomne jednostki biblioteczne Specyfikacja i treść jednostek bibliotecznych są oddzielnymi jednostkami kompilacyjnymi. Pomiędzy jednostkami kompilacyjnymi można określać hierarchiczną relacje, która określa wzajemną widzialność jednostek. Zestaw standartowych pakietów. Ada 95 w stosunku do wersji Ada 83 wprowadza jeszcze jedną formę strukturyzacji programu – rozproszenie programu. Podział programu na partycje, z których każda może być wykonywana na osobnym komputerze.
Przykład – pierwszy program with text_io; use text_io; -- specyfikacja kontekstu procedure pierwszy is -- procedura główna programu begin put_line(“pierwszy program!”); --standardowa procedura z end pierwszy; --pakietu Text_IO Oznaczenia w specyfikacji składni języka bold - słowo kluczowe { } - dowolny ciąg powtórzeń [ ] - element opcjonalny \ \ - wybór jednego z elementów składowych | - alternatywa ::= - reguła produkcji ... - składnik identyczny
Identyfikatory W identyfikatorze małe i duże litery są nierozróżnialne. Identyfikator:= Litera { [Podkreślnik] litera | cyfra} Tablica a b1 a_1 lancuch_1 Liczby 987_657_333 równoważne 987657333 3.14_592 2E5 -- 200000 0.124e-2 -- 0.00125 Literały znakowe i napisowe ‘A’, ‘F’, ‘1’, ”ada 95”, ”lancuch”
Typy Typ składa się z dwóch elementów: • skończonego zbioru wartości A1, ..., An • - zbioru funkcji f1, ..., fk Definicja typu type Identyfikator_typu is definicja typu type tydzien is (pn, wt, sr, cz, pt, so, ni) Zawężenie użytkowania typu type T1 isprivate type T1 is limited private --ograniczenie widoku typu – użytkowniknie zna struktury typu Definicja podtypu subtype Identyfikator_podtypu is Identyfikator_typu range [zawężenie] subtype dni_robocze is tydzien range pn .. pt subtype Litera is Character range ‘a’..’h’ subtype godz is integer range 0..24
Uwaga: definicja podtypu nie wprowadza nowego typu. Ma to znacznie dla pojęcia zgodności typów. Dla dowolnego typu lub podtypu są określone dwa atrybuty: T’Base – dla danego podtypu T zwraca nazwę tego typu bazowego, dla typu T jego nazwę. T’Size – określa najmniejszą liczbę bitów, wymaganą do przedstawienia wartości typu T. put( "Najmniejszy integer: " ); put( Integer'First ); new_line; put( "Najwiekszy integer: " ); put( Integer'Last ); new_line; put( "Integer (w bitach): " ); put(Integer'Size ); new_line;
Trzy sposoby definiowania typu: • - zawężenie zbioru wartości zdefiniowanego typu • - rozszerzenie zbioru wartości zdefiniowanego typu (tylko typy znakowe) • zdefiniowanie nowego typu jako złożenie wartości typów wcześniej zdefiniowanych. • (typy tablicowy i typ rekordowy) Typ całkowity ze znakiem Schemat definicji typu jest następujący: rangeproste_wyrażenie_statyczne ..proste_wyrażenie_statyczne proste wyrażenie statyczne – to wyrażenie, którego wartość może być obliczona w trakcie kompilacji. proste wyrażenie statyczne musi być wyrażeniem statycznym dowolnego typu całkowitego z zakresu System.Min_Int, System.Max_Int gdzie System.Min_Int, System.Max_Int są stałymi,których wartość zależą od implementacji. W Adzie istnieje predefiniowany podtyp całkowity ze znakiem - Integer Typ całkowity resztowy Typ całkowity resztowy jest typem całkowitym bez znaku, w którym jest stosowana arytmetyka resztowa.
Schemat definicji typu jest następujący: modWyrażenie_statyczne; Wartość elementu Wyrażenie_statyczne jest nazywana modułem i musi być statyczną dodatnią wartością dowolnego typu całkowitego, nie większą niż wartości określone w pakiecie System. Typ całkowity resztowy oznacza typ całkowity z zakresem bazowym od zera do wartości (moduł-1). Przykłady deklaracji typów całkowitych resztowych : type Bajt_bez_znaku is mod 8; -- zakresem są liczby 0..7 type Słowo is mod 16; -- zakresem są liczby O .. 15 Jeśli wartości typu resztowego są argumentami binarnego operatora logicznego, to operatory binarne stosowane do tych wartości traktują je jako ciągi bitów, tzn. wykonuje się operację kolejno na każdej parze bitów np. A: Bajt_bez_znaku := 6;-- A jest ciągiem 0110 B: Bajt_bez_znaku := 5; -- B jest ciągiem 0101 C, D: Bajt_bez_znaku; C := A or B; -- wartość C będzie równa 7 D : = A and B; -- wartość D będzie równa 4
Typy rzeczywiste Typy rzeczywiste są typami pochodnymi pierwotnego typu universal_real. W związku zniemożnościąreprezentowania wszystkich wartości zakresu wskazanego w definicji typu rzeczywistego stosuje się zawężenie dokładności, które określa: • - liczbę cyfr dziesiętnych wymaganą do zapisu wartości typu (dokładność względna), lub • parametr delta, który określa różnicę pomiędzy kolejnymi wartościami typu (dokładność • bezwzględna). Typy zmiennopozycyjne Schemat definicji typu zmiennopozycyjnego: digits wyrażenie_statyczne [ range proste_wyrażenie_statyczne_1 .. proste_wyrażenie_statyczne_2 ] gdzie: • wyrażenie_statyczne określa wymaganą dokładność dziesiętną (minimalna liczba znaczących • cyfr dziesiętnych). Wartość tego wyrażenia musi być liczbą typu Integer. • proste_wyrażenie_statyczne_i (i=l,2) powinno przyjmować wartość rzeczywistą. • dla każdego podtypu zmiennopozycyjnego Z jest definiowany atrybut Z'Digits, podający • wymaganą dokładność dziesiętną dla elementów tego typu. Wartość tego atrybutu jest typu • universal_integer.
W Adzie istnieje predefiniowany podtyp zmiennopozycyjny ze znakiem, nazwany Float, deklarowany w pakiecie Standard. Przykłady deklaracji typów zmiennopozycyjnych: type Temperatura is digits 3 range 16.4 .. 20.7; -- wartości będą zawierały 3 cyfry --dziesiętnych z podanego zakresu type Real is digits 8; -- obliczenia dla wartości tego typu -- będą prowadzone z dokładnością -- do 8-miu cyfr dziesiętnych subtype Zakres is Real range 0.0 .. 3.0; -- wartości typu Zakres należą do --przedziału [0.0,3.0] Typy stałopozycyjne typy stałopozycyjne: mają określoną stałą dokładność reprezentacji liczby. Jest to wartość absolutna, nazywana parametrem delta typu stałopozycyjnego. zbiór wartości typu stałopozycyjnego składa się z liczb będących całkowitą wielokrotnością liczby nazywanej ziarnem typu.
Przykłady deklaracji typów stałopozycyjnych: typePomiar is delta 0.125 range 0.0 .. 125.0; -- wartości typu Pomiar będą -- zapisywane co 0.125 -- w podanym zakresie type Pieniądze is delta 0.01 digits 9; -- pozwala zapisać spotykane -- zlotowe kwoty pieniężne' -- z dogodnością do groszy
Typy tablicowe Składowe typu są jednoznacznie identyfikowane przez jeden lub więcej wartości indeksów należących do podanych typów dyskretnych. Typ tablicowy może miećzawężenie indeksów, co oznacza, że górne i dolne granice każdego z indeksów są określone. Element takiego typu jest nazywany tablicązawężoną, a schemat deklaracji takiego typu tablicowego przyjmuje postać: type Nazwa typu tablicowego is array (Wskazanie_podtypu _dyskretnego I Zakres {Wskazanie podtypu _dyskretnego l Zakres}) of [aliased] Wskazanie_podtypu Można definiować także typ tablicowy niezawężony, w którym granice indeksów nie są określone. Granice te wprowadza się deklarując obiekt (tablicę) tego typu (podaje się zawężenie indeksu lub wartości początkowe składowych tablicy). Schemat definicji dla typu tablicowego niezawężonego jest następujący: type Nazwa typu tablicowego is array (Oznaczenie Podtypu range<> {,Oznaczenie Podtypu range <>}) of [aliased] Wskazanie Podtypu przy czym Oznaczenie_podtypu powinno wskazywać typ dyskretny.
Typy predefiniowane - pakiet Standard type Boolean is (False, True) -- predefiniowane operacje =, /=, <, >, <=, >=, and, or, xor, not type Integer is zdefiniowany przez implementacje subtype Natural is Integer range 0 .. Integer’Last; subtype Positive is Integer range 1 .. Integer’Last; -- predefiniowane operacje dwuargumentowe =, /=, <, >, <=, >=, abs, +, -, *, /, ** --predefiniowane operacje jednoargumentowe +, - Spotkania w Adzie Komunikacja w Adzie jest: SYNCHRONICZNA NIEBUFOROWANA DWUKIERUNKOWA
Zadanie wywołujące Zadanie przyjmujące Wejście 1 Zadanie 1 Wejście 2 Wejście 3 Zadanie 2 Wejście 4 Zadanie wywołujące przyjmujące musi znać: -nazwę zadania przyjmującego - nazwę miejsca spotkania Zadanie przyjmujące nie zna nazwy zadania wywołującego.
Zaleta modelu asymetrycznego – łatwość programowania serwerów i nie musi być zmieniana treśćzadania serwera podczas dodawania nowego zadania wywołującego. Zadanie składa się z: specyfikacji – zawierać może jedynie deklaracje wejść, każde zadanie musi posiadać specyfikacje nawet jeśli jest ona pustej treści. Przykład specyfikacji: task Bufor is entry Wstaw (I : in integer); entry Pobierz (I : in integer) end Bufor; Wywołanie wejścia przez zadanie: BuforWstaw(I); task body Bufor is begin ........ select accept Wstaw (I : in integer) do --instrukcje end Wstaw; accept Sygnalizuj; ............ end Bufor;
Semantyka spotkania jest następująca: Zadanie wywołujące przekazuje swoje parametry wejściowe (in) do zadania przyjmującego i zostaje zawieszone do momentu zakończenia spotkania. Zadanie przyjmujące wykonuje instrukcje z treści accept Parametry wyjściowe (out) są przekazywane do zadania wywołującego Spotkanie jest teraz zakończone i żaden z procesów nie jest już wstrzymywany. with Ada.Text_IO; use Ada.Text_IO; procedure pierwsza is czyja_kolej : Integer:=1; Task P1; task body P1 is begin loop Put_line("sekcja lokalna zadania 2"); loop exit when czyja_kolej =1; end loop; Put_line("sekcja krytyczna zadania 1"); czyja_kolej:=2; end loop; end P1;
Task P2; task body P2 is begin loop Put_line("sekcja lokalna zadania 2"); loop exit when czyja_kolej =2; end loop; Put_line("sekcja krytyczna zadania 2"); czyja_kolej:=1; endloop; end P2; begin null; end Pierwsza; package pakiet_z_semaforami is task type Semafor_binarny is entry Czekaj; entry Sygnalizuj; end Semafor_binarny; task type semafor is entry Inicjalizuj(N : in Integer); entry Czekaj; entry Sygnalizuj; end Semafor; end pakiet_z_semaforami; /////////////////////////////////////////////////////////////////////
package body pakiet_z_semaforami is task body Semafor_binarny is begin loop select accept Czekaj; accept Sygnalizuj; or terminate; end select; end loop; end Semafor_binarny; task body semafor is licznik : integer; begin accept Inicjalizuj(N : in Integer) do licznik:=N; end Inicjalizuj; loop select when Licznik >0 => accept Czekaj dolicznik:=licznik-1; end Czekaj; or accept Sygnalizuj dolicznik:=licznik+1; end Sygnalizuj; or terminate; end select; end loop; end Semafor; end pakiet_z_semaforami; ////////////////////////////////////////////////////////////
with Ada.Text_IO; use Ada.Text_IO; with pakiet_z_semaforami; use pakiet_z_semaforami; procedure wzajemne_wykluczanie is s: Semafor_binarny; task Z1; task body Z1 is begin loop Put_line("sekcja lokalna zadania 1"); S.Czekaj; Put_line("sekcja krytyczna zadania 1"); S.sygnalizuj; end loop; end z1; task z2; task body z2 is begin loop Put_line("sekcja lokalna zadania 2"); S.Czekaj; Put_line("sekcja krytyczna zadania 2"); S.sygnalizuj; end loop; end z2; begin null; end wzajemne_wykluczanie;
Synchronizacja trzech i więcej spotkań Instrukcja zagnieżdżona accept taskbody Z1 is begin ................ accept Synch_2 do -- Z2 wywołuje to wejście accept Synch_3 -- Z3 wywołuje to wejście end Synch_2; .................. end Z1; Select w zadaniu wywołującym Zadanie wywołujące może się zawiesić jedynie czekając na jedno wejście. taskbody Z is begin loop select procesX.wejscie(... ); or delay 1.0 ; ------sekunda ciąg instrukcji; end select; end loop; end Z;
Jeśli wywołanie nie będzie przyjęte w zadanym czasie, to próba spotkania będzie zaniechana i wykonają się instrukcje po delay. Ankietowanie jednego lub wielu serwerów : taskbody Z is begin loop select serwer_1.wej(...); else null; end select; select serwer_2.wej(...); else null; end select; ............ end loop; end Z; Ankietowanie co najwyżej jednego serwera : taskbody Z is begin loop select serwer_1.wej(...); else select serwer_2.wej(...); else end select; end select; end loop; end Z;
Dynamiczne tworzenie zadań deklaracja typu zadaniowego, tworzenie za pomocą typu struktury danych zawierającej zadania, task type typ_bufora is entry Wstaw (I : in integer); entry Pobierz (I : out integer) end typ_bufora; Bufory : array(1..10) of typ_bufora; procedure P (buf : in typ_bufora) is I : Integer; begin buf.Wstaw(I); -- wywołanie wejścia z parametrem end P; Bufory(I+1).Wstaw(E) -- bezpośrednie wywołanie wejścia P(Bufory(J)); -- parametr będący zadaniem Priorytety i rodziny wejść pragma priority(N) N – literał całkowity
Priorytety wpływają na szeregowanie zadań. Nie mają wpływu na wybór gałęzi w instrukcji select. Jeśli jest kilka zadań o tym samym priorytecie to wybór jest losowy. Implementacja musi używać zarządcy z wywłaszczeniem, – jeśli zakończy się okres zawieszenia zadania o wysokim priorytecie, to zarządca musi na jego korzyść przerwać zadanie o niższym priorytecie. Rodzina wejść: type Priorytety is (niski, średni, wysoki); task Serwer is entryŻądanie(Priorytety) (.....); end Serwer; taskbody Serwer is begin loop select acceptŻądanie(wysoki) (...) ....; or when Żądanie(wysoki) ‘ Count = 0 => --liczba zadań czekających w kolejce acceptŻądanie(średni) (...) ....; or when Żądanie(wysoki) ‘ Count = 0 => Żądanie(średni ) ‘ Count = 0 => acceptŻądanie(niski) (...) ....; end select; end loop; end Serwer;
Jeśli rodzina wejść jest duża. type Priorytety isrange 1..100; for I in Priorytety loop select accept Żądanie(I) (...) ....; else null; end select; end loop;
Podprogramy Podprogramy są podstawowymi jednostkami programowymi Ady. W Adzie istnieją dwa rodzaje podprogramów: procedury i funkcje. Procedury określają działania, zmierzające do uzyskania żądanego efektu. Funkcje określają działania, zmierzające do obliczenia pewnej wartości. Podprogramy — pojęcia podstawowe Definicja podprogramu może składać się z dwóch części: deklaracji podprogramu, opisującej jego interfejs oraz treści, opisującej działanie podprogramu. [specyfikacja _ podprogramu;] -- deklaracja podprogramu specyfikacja _podprogramu is -- treść podprogramu; [ część deklaracyjna] begin ciąg_ instrukcji end; Specyfikacja opisuje interfejs podprogramu, tj. określa rodzaj podprogramu(procedura, funkcja), jego nazwę oraz parametry podprogramu. Specyfikacja funkcji dodatkowa wskazuje nazwę typu wartości obliczanej przez funkcję. Deklaracja oraz treść podprogramu muszą wystąpić w tej samej części deklaracyjnej jednostki otaczającej dany podprogram (wyjątek stanowią podprogramy deklarowane w obrębie pakietów, zadań, obiektów chronionych).
Funkcje Treść funkcji ma postać: function Nazwa funkcji[(Parametry_formalne)] return Nazwa_podtypu is -- specyfikacja [Część_deklaracyjna]begin ... -- ciąg instrukcji return Wyrażenie; end |Nazwa funkcji]; Deklaracja parametrów funkcji ma postać: Dok_1 ; |Dok_2;. . . ; Dok_N Gdzie Dok_i wygląda następująco: Nazwa: (in) Typ Jeżeli parametry są tego samego typu, dopuszczalny jest zapis: Narwal, Nazwa_2, ..., Nazwa_K: [in] Typ W treści funkcji może wystąpić wiele instrukcji return (np. wewnątrz instrukcji wyboru). Wykonanie dowolnej z nich kończy działanie funkcji.
Przykładem treści funkcji jest: function Rok przestępny(Rok: Positive) return Boolean is - Funkcja Rok przestępny ma jeden parametr formalny o nazwie Rok. - Funkcja zwraca wartość True, jeżeli rok jestrokiem przestępnym oraz - wartość False w przeciwnym przypadku begin if Rok mod 4 /= O then return False; elsif Rok mod 100 /= O then return True; elsif Rok mod 400 /= O then return False; else return True; end if; end Rok _ przestępny; Treść podprogramu (w tym również funkcji) może być poprzedzona deklaracją. Deklaracja funkcji ma postać: function Nazwa funkcji [(Parametry formalne)] return Nazwa podtypu;
Wywołanie funkcji Przykład 1 : Wynik: Boolean; . . . Wynik:= Rok__przestepny (1996) ; --zmiennaWynik przyjmie wartośćTrue Przykład 2 : function Silnia (N: Integer) return Integer is begin if N=0 then return l ; else return N * Silnia(N - l); end if; end Silnia; . . . S: Integer; . . . S := Silnia (5); -- wartość zmiennej S, po wykonaniu funkcji Silnia. -- będzie równa 120
Formalne parametry funkcji mogą być dowolnego ,nazwanegotypu, również nie zawężonego .Gdy typ parametru jest typem nie zawężonym, zakres tego parametru jest ustalony na podstawie zawężonej wartości parametru aktualnego, np. Type Wektor is array ( Integer range <>) of Integeer; -- nie zawężona tablica liczb . . . function Suma (W: Wektor) return Integer is S: intager : = 0; Begin for j in W'Rangę loop -- J przyjmuje kolejne wartości z zakresu S := S + W (J) ; -- indeksów tablicy W ( atrybut Rangę ) end loop; return S; end Suma; . . . W: Wektor (1. .3):= (1 ,2 ,3) ; S: Positive; . . . S := suma(W) ; -- wartość S, po wykonaniu funkcji Suma, -- będzie równa 6
Dany podprogram może zostać przemianowany, tzn. można mu nadać dodatkowo nową nazwę, obowiązującą w danym kontekście. Nowa nazwa nie przesłania poprzedniej, pozwala jedynie łatwiej korzystać z podprogramu. Ogólny schemat przemianowania funkcji jest następujący: function Nowa_nazwa_funkcji[(Parametry_formalne)] returnNazwa podtypu renames Poprzednia nazwa funkcji; Poniżej umieszczono przykład definicji funkcji uzyskanej w wyniku przemianowania: function Nowa_suma(W : Wektor) return Integer renames Suma; Wywołanie funkcji Nowa_suma (W) obliczy taką samą wartość jak wywołanie funkcji Suma (W).
Operatory Operatory są jedno lub dwuargumentowymi wbudowanymi funkcjami języka .W odróżnieniu od innych Funkcji, operatory mogą być używane w notacji wrostkowej i przedrostkowej. Oznacza to, że zapis X+1 jest równoważny wywołaniu funkcji "+" (X, l). W notacji przedrostkowej symbol operatora musi być ujęty w cudzysłów. Programista może używać symboli operatorów do nazywania własnych funkcji. Poniżej przedstawiono przykład definicji operatora "+" dla dodawania wektorów: type Wektor is array (Integer rangę o) of Float; function "+" (L, R: Wektor) return Wektor is W: Wektor (L' Rangę) ; -- tablica o dynamicznie ustalanym rozmiarze! begin for I in L' Range loop W(I) :=L(I) +R(I); --operator "+" oznacza dodawanie liczb -- typu Float end loop; return W; end " + " ; L Wektor(1..3) := (1.0, 2.0, 3.0); R Wektor(1..3) := (-1.0, -2.0, -3.0); W Wektor (l. . 3) ; -- operator "+" jest używany do dodawania wektorów L ;= L + R; -- po Wykonaniu instrukcji, L będzie wektorem zerowym W ;="+"( L, R); -- alternatywne użycie operatora
Deklaracja własnych operatorów jest jednym z przykładów przeciążania podprogramów. Operator "/=", który zwraca wartość Boolean, może być zdefiniowany jedynie przez definicję komplementarnego operatora "=", tzn. zdefiniowanie operatora "=" oznaczają również niejawne zdefiniowanie operatora "/=". Procedury Treść procedury tworzy się według schematu: procedure Nazwa_procedury[(Parametry_formalne)] -- specyfikacja procedury is [Część_deklaracyjna] begin ...--ciąg instrukcji, opisujący działanie procedury [return;] end [Nazwa_procedury); Deklaracja parametrów ma postać: Dok_1; Dok_2;. . .; Dok_N; Gdzie Dok_1 wygląda następująco : Nazwa: (Tryb) Typ Jeżeli parametry są tego samego typu, to dopuszczalny jest zapis: Nazwa_1, Nazwa_2, ..., Nazwa_K: (Tryb} Typ
Każdy parametr formalny może wystąpić w jednym z trzech tzw. trybów przekazywania parametrów in, in out, out. Tryb przekazywania parametru oznacza kierunek przekazania danych podczas wywołania podprogramu. W przypadku , gdy tryb przekazywania parametrów nie jest ustalony jawnie, tryb in przyjmuje się za domyślny. W treści procedury może znaleźć się bezparametrowa instrukcja return. Jej wykonanie kończy wykonanie procedury, po czym sterowanie jest przekazane do jednostki, która wywołała procedurę. Przykład: procedure Zamień (X, Y; in out Float) is Tmp: Float; begin Tmp:= X; X := Y; Y := Tmp ; end Zamień ; Deklaracja procedury jest tworzona według schematu: procedure Nazwa_procedury [(Parametry_formalne)] ; Deklaracja procedury Zamień ma zatem postać: procedure Zamień(X, Y: in out Float); Wywołanie procedury jest instrukcją o postaci: Nazwa_procedury [(Parametry_aktualne)]; Poniżej pokazano przykład wywołania procedury Zamień: A: Float := 1.0; B: Float := -1.0; . . . Zamień (A, B) ; --po wykonaniu procedury wartość zmiennej A= -1.0, B = 1. 0
Przekazywanie parametrów do podprogramów Parametry aktualne mogą być przekazywane do podprogramu przez położenie lub przez nazwę. Przekazywanie parametrów przez położenie polega na ustawieniu parametrów aktualnych w listą deklaracji parametrów formalnych, np. : Function Suma (A, B : Integer) return Integer; . . . W, S: Integer; . . . W : = Suma (5 ,6) ; -- parametr formalny A zostanie powiązany z wartością 5, -- a parametr B z wartością 6 S : = W ; W : = Suma (S ,6); -- parametr A zostanie powiązany z wartością zmiennej S, -- a parametr B z wartością 2 Przekazywanie parametrów przez nazwę polega na tym, że parametr aktualny jest poprzedzony nazwą powiązanego z nim parametru formalnego oraz symbolem "=>", np.: S : = Suma(A=>5, B=>6) ; -- parametr A zostanie powiązany z wartością 5, -- a parametr B z wartością 6 S : =Suma(A=>W, B=>2) ; -- parametr A zostanie powiązany z wartością -- zmiennej W, a parametr B z wartością 2 S : = Suma(B=>6, A=>5) ; S : = Suma(B=>2, A=>W) ;
Przykład: procedure P(X: Integer; Y, Z: in out Float); A: Float := 2.0; B: Float := -3.5; P(3, Z=>A, Y=>B); Dla parametrów przekazywanych w trybie in można określić domyślne wartości początkowe (analogicznie do początkowych wartości deklarowanych zmiennych). Parametry, dla których określono wartości domyślne, mogą zostać opuszczone w wywołaniu podprogramu. Można jednak w zwykły sposób zmienić ich wartości. W definicjach operatorów (np. "+") nie mogą wystąpić deklaracje parametrów domyślnych. Przykład: function Suma(A: Float := 3.14; B: Float := 0.0) return Float; W: Float; . . . W = Suma; -- parametr A przyjmie wartość 3.14, B wartość 0.0 W = Suma(2.0); -- parametr A przyjmie wartość 2.0, B wartość domyślną 0.0 W = Suma(B=>2.0); -- parametr A przyjmie wartość domyślną 3.14, -- B nową wartość 2.0
Przeciążanie podprogramów Użytkownik może zdefiniować wiele podprogramów o tej samej nazwie i różnym działaniu. Jeśli różne podprogramy mają takie same nazwy i różne parametry lub w przypadku funkcji różne typy wyników, mamy do czynienia z przeciążaniem podprogramów. nazywanym również statycznym polimorfizmem. Jeśli podprogramy mają identyczne nazwy, typy parametrów i wyniku (w przypadku funkcji) mówimy o przesłanianiu podprogramów. Wybór przeciążonego podprogramu jest dokonywany statycznie, na podstawie typów. parametrów aktualnych. W przypadku funkcji, których specyfikacje różnią się jedynie typem zwracanego wyniku, o wyborze podprogramu decyduje kontekst wywołania. Przykładem przeciążania podprogramów jest: type Dni_tygodnia is (pn, wt, sr, cz, pt, sb, nd) ; type Dni robocze is (pn, wt, sr, cz, pt); procedure Oblicz(D: Dni_tygodnia); -- 1) procedura Oblicz procedure Oblicz (D: Dni_robocze) ; -- 2) przeciążona procedura Oblicz Wywołania procedury Oblicz mogą być następujące: Oblicz(sb); -- wywołanie procedury 1) Oblicz (Dni robocze ' (wt) ); -- wywołanie procedury 2) Kwalifikacja typu parametru aktualnego w drugim wywołaniu procedury Oblicz jest niezbędna w celu ustalenia, którą z przeciążonych procedur wywołać (tu zostanie wywołana procedura oznaczona 2).
Ilustracją jest przykład przeciążenia operatora”+” dla typów pochodnych; type T1 is new integer; type T2 new integer; . . . A1 T1; B1 T2; ... A := A + 1; -- przeciążony “ +” dla T1 B := B + 1; -- przeciążony “ +” dla T2 A := B; -- niepoprawne przypisanie (T1 jest innym typem, niż T2) Podprogramy rodzajowe Każdy podprogram może mieć dwa rodzaje parametrów — parametry zwykłe, takie jak w Pascalu, czy C, wykorzystywane w trakcie wykonania programu, deklarowane za nazwą podprogramu, oraz parametry rodzajowe, wykorzystywane na etapie kompilacji. Podprogramy z parametrami rodzajowymi nazywa się podprogramami rodzajowymi. Parametry rodzajowe zwiększają możliwość wielokrotnego użycia tego samego podprogramu w różnych kontekstach. Podprogram rodzajowy pełni rolę wzorca, na podstawie którego, w czasie kompilacji, są konkretyzowane różne, zależne od parametrów, deklaracje danego podprogramu. Definicja programu rodzajowego składa się z deklaracji oraz treści. Deklaracja podprogramu rodzajowego ma postać generic --deklaracja podprogramu rodzajowego [Formalne_parametry_rodzajowe] Specyfikacja_podprogramu_rodzajowego;
Schemat składni, opisujący treść podprogramu rodzajowego jest identyczny jak dla zwykłego podprogramu. Formalnymi parametrami rodzajowymi podprogramu mogą być: • obiekty, • typy. • podprogramy. Poniżej podano przykład definicji podprogramu rodzajowego uzyskanegoprzez dodanie parametru rodzajowego do procedury Zamień: generic type Typ rodź is private; -- deklaracja parametrów procedurę Zamień(X, Y: in out Typ_rodz); -- rodzajowych procedurę Zamień(X, Y: in out Typ_rodz) is -- specyfikacja Tmp: Typ__rodz ; -- podprogramu Begin -- rodzajowego Tmp := X; -- treść podprogramu X := Y; -- rodzajowego Y := Tmp; end Zamień;
Aby wykorzystać procedurę Zamień, należy ją ukonkretnić. Konkretyzacja podprogramu parametryzowanego obiektem i (lub) typem ma postać: procedurę Nazwa__podprogramu ukonkretnionego is new Nazwa_podprogramu_rodzajowego[(Parametry_aktualne)] ; Aktualne parametry rodzajowe mogą być kojarzone z parametrami formalnymi zarówno" przez położenie jak i przez nazwę. Przykładowe konkretyzacje procedury Zamień mogą być następujące: procedure Z1 is new Zamień(Integer); --konkretyzacja procedury typem Integer procedure Z2 is new Zamień(Float); -- konkretyzacja procedury typem Float procedure Z3 is new Zamień(Typ_rodz => Moj_typ) ; -- konkretyzacja procedury Zamieńtypem Mój typ Obiekty rodzajowe mogą być wykorzystywane do przekazania wartości, czy zmiennej do lub z podprogramu rodzajowego. Deklaracja obiektów, będących parametrami rodzajowymi podprogramu ma postać: Nazwa_l, Nazwa_2, ..., Nazwa_N : [Tryb] Typ [:= Wartość_domyślna] ;
Wyjątki Zgłoszeniewyjątku powoduje zaniechanie (przerwanie) normalnego wykonania programu Programista może jednak zdefiniować pewne akcje (obsługą wyjątku), które mają być wykonane jako reakcja na wystąpienie danego wyjątku. Niektóre wyjątki są predefiniowane w języku, inne mogą być deklarowane przez programistę. Wyjątki predefiniowane zgłaszane są automatycznie, jeżeli nastąpi naruszenie zasad określonych przez semantykę języka, np. gdy zostanie przekroczony zakres wartości danego typu. Wyjątki zadeklarowane w programie zgłaszane są instrukcją raise w warunkachokreślonych przez programistę. Wyjątki predefiniowane W Adzie istnieją cztery predefiniowane wyjątki (zadeklarowane w pakiecie standard): Constraint Error, Program Error, Storage Error i Tasking_Error . Wyjątek Constraint_Errorjestzgłaszany, m.in., gdy: • nastąpi wywołanie podprogramu, w którym wartość aktualna parametru typu wskaźnikowego (przekazywanego w trybie in lub in out) jest równa null, • drugi parametr operacji /, mod, rem jest równy zero, • wartość indeksu tablicy przekracza zakres dopuszczalnych wartości, • wartość skalarna przekroczy zakres bazowy swojego typu. Wyjątek Program_Errorjestzgłaszany, m.in., gdy: • w programie następuje odwołanie do niedostępnego bytu lub widoku, • wywoływany jest podprogram lub wejście obiektu chronionego, którego deklaracja nie została jeszcze opracowana, • wykonanie funkcji nie zakończyło się instrukcją return.
Wyjątek Storage_Error jest zgłaszany wtedy, gdy brakuje pamięci do opracowania deklaracji lub wykonania instrukcji języka (np. do obliczenia alokatora). Wyjątek Tasking_Error jest zgłaszany wtedy, gdy komunikacja z jakimś zadaniem jest niemożliwa (zadanie zostało już usunięte). Programista ma możliwość wyłączenia niektórych predefiniowanych sprawdzeń wykonywanych w czasie wykonania programu za pomocą pragmy Suppress Użycie pragmy Suppress poprawia efektywność wykonania programu (eliminuje dodatkowe czynności sprawdzające — kod wynikowy jest krótszy), przerzuca jednak odpowiedzialność za poprawność programu na programistę. Deklarowanie i zgłaszanie wyjątków Deklaracja wyjątków ma postać: Nazwa _wyjątku l. Nazwa _wyjątku 2,. . . , Nazwa _wyjątku N.; Exception; Przykładami deklaracji wyjątków są: Blad: exception; Package Stos is Przepełnienie_bufora: exception; Begin . . . end Stos; Blad_odczytu, Blad_zapisu: exception;
Przemianowania wyjątku Przepełnienie _bufora zadeklarowanego pierwotnie w pakiecie o nazwie Stos można dokonać następująco: Declare use Stos Przepełnienie: exception renames Stos.Przepełnienie Bufora; Begin . . . end; Wyjątki zadeklarowane przez użytkownika są zgłaszane instrukcją raise, po której występuje nazwa wyjątku. Programista określa, jakie warunki muszą zostać spełnione, aby dany wyjątek został zgłoszony. Poniżej umieszczono przykład zgłoszenia wyjątku Blad przy próbie dzielenia przez zero: procedure Dzielenie(Dzielna, Dzielnik: Float; Wynik: out Float) is Bląd: exception; begin if Dzielnik =0.0 then raise Błąd; -- zgłoszenie wyjątku Błąd, gdy Dzielnik jest równy 0.0 else Wynik := Dzielna/Dzielnik; end if; end Dzielenie;
Obsługa wyjątków Wykonanie instrukcji lub opracowanie deklaracji może spowodować zgłoszenie wyjątku Po zgłoszeniu wyjątku normalne wykonywanie programu jest zaniechane, a sterowanie jest przekazywane do zdefiniowanej przez programistę strefy obsługi wyjątków. Strefa obsługi wyjątków może wystąpić w dowolnym bloku instrukcji, w zasięgu deklami |i danego wyjątku. Swoje strefy obsługi wyjątków mogą mieć: podprogramy, treści pakietów, instrukcje bloku, treści zadań, treści wejść w obiektach chronionych i instrukcje accept. Strefę obsługi umieszcza się zawsze na końcu bloku, po ostatniej normalnie wykonywanej instrukcji. Ilustruje to poniższy schemat: Instrukcja [exception -- strefa obsługi wyjątków Segment_obslugi_wyjątków l Segment_obsługi_wyjątków_N ] Każda strefa obsługi wyjątków musi zawierać co najmniej jeden segment obsługi wyjątków. Segment obsługi wyjątków ma postać: when Nazwa_wyjątku => Instrukcja (...} lub when Nazwa wyjątku l l Nazwa wyjątku 2 | ... i Nazwa wyjątku N Instrukcja {...}
Nazwy wyjątków umieszczone w jednej strefie obsługi wyjątków muszą być unikatowe. Zbiory wyjątków należących do różnych segmentów muszą być rozłączne. Ostatni segment obsługi wyjątków może zawierać kluczowe słowo others, które oznaczą wszystkie wyjątki zgłoszone w danym bloku, lecz nie wymienione w poprzednich segmentach obsługi, włącznie z wyjątkami nie nazwanymi w bieżącym kontekście: when others => Instrukcja {...) W segmencie obsługi others obsłużone zostaną wyjątki: • predefiniowane, • nazwane w danym kontekście, lecz nie wymienione w innych segmentach obsługi wyjątków, • wyjątki propagowane z jednostek dynamicznie zagnieżdżonych w bloku, w którym wystąpił segment obsługi others Obsługę wyjątku można wykorzystać do przywrócenia stanu systemu (o ile to możliwe) do stanu sprzed pojawienia się wyjątku lub do zapobiegania skutkom wyjątku. Na przykład obsługa wyjątku Blad dla nieco zmodyfikowanej procedury Dzielenie musiałaby być następująca:
procedure Dzielenie(Dzielna, Dzielnik: Float; Wynik: out float; OK: in out Boolean) is -- zakłada się. że parametrowi aktualnemu, podstawianemu za parametr formalny OK -- będzie przypisana wartość Trueprzed wywołaniem procedury Dzielenie Błąd: exception; Begin . . . raise Błąd; exception when Błąd => OK := Faise; end Dzielenie; X, Y, W: Float; OK: Boolean := True; . . . Dzielenie(X, Y, W, OK) ; if OK then ...-- wynik W obliczony poprawnie Po wykonaniu segmentu obsługi wyjątku, sterowanie przekazywanejest na koniecbloku, który obsłużył dany wyjątek.
Przykład: procedurę Dzielenie(Dzielna, Dzielnik: Float; Wynik: out Float; OK: in out Boolean) is -- zakłada się, że parametrowi aktualnemu, podstawianemu za parametr formalny OK -- będzie przypisana wartość T'rue przed wywołaniem procedury Dzielenie begin Wynik ; " Dzielenia/dzielnik; exception when Constraint_Error => ok.:= false; end dzielenie; W przypadku, gdyby w procedurze Dzielenie nie wystąpiła strefa obsługi wyjątków, wyjątek Constraint_Error byłby propagowany do jednostki, w której ta procedura została wywołana.
Propagacja wyjątków W przypadku zgłoszenia wyjątku sterowanie jest przekazywane do strefy zawierającej obsługi wyjątku, zadeklarowanego w bloku, którego wykonanie spowodowało pojawienie się wyjątku. Jeżeli w danym bloku nie ma segmentu obsługi zgłoszonego wyjątku lub jeżeli w czasie wykonania segmentu obsługi zgłoszony zostanie inny wyjątek, to następuje automatyczne zgłoszenie wyjątku w bloku dynamicznie otaczającym blok zawierający wyjątek. Zgłoszenie danego wyjątku w nowym kontekście nazywa się propagacją wyjątku. Oznacza to np., że wyjątek nie obsłużony w danym programie, będzie propagowany do jednostki, w której ten podprogram był wywołany Propagacja wyjątku trwa tak długo, aż zostanie znaleziony odpowiedni segment obsługa wyjątku. Jeżeli poszukiwanie segmentu obsługi nie powiedzie się, wykonanie programu zostanie przerwane z komunikatem o błędzie (np. nie obsłużony wyjątek Program Error). Wyjątki zgłoszone i nie obsłużone w treści zadania nie podlegają propagacji. Zgłoszenie wyjątku może nastąpić nie tylko podczas wykonywania instrukcji, lecz także podczas opracowywania deklaracji. W tym przypadku przyjmuje się zasadę propagacji wyjątku do jednostki dynamicznie otaczającej deklarację, której opracowanie nie po wiodło się. Programista może, w ramach obsługi danego wyjątku, wymusić jego propagację. Do propagacji wyjątku służy bezparametrowa instrukcja raise. Instrukcja ta pozwala obsługiwać ten sam wyjątek na wielu poziomach, tzn. przez wiele dynamicznie zagnieżdżonych jednostek programowych.