Wprowadzenie
Celem niniejszego skryptu jest ułatwienie studentom śledzenia wykładu ze „Wstępu do programowania”. Przedstawiona tu I część skryptu zawiera opis języka Ada w jego części strukturalnej. Odpowiada to zakresem materiału pierwszemu semestrowi wspomnianego wyżej wykładu.
Przedstawione tu opracowanie nie pretenduje do orginalności. Podstawą opracowania było „Ada Reference Manual”, ale wykorzystane jest tu kilka książek poświęconych programowaniu lub opisowi używanego języka programowania.
Bardzo proszę o zgłaszanie wszelkich uwag na temat skryptu autorowi: goldstei@math.uni.lodz.pl.
Rozdział 1: Ogólne
W tej części podręcznika przedstawiony jest systematyczny opis języka Ada. Jest on wzorowany na opisie standardu języka.
Standard języka opisany jest w Ada 95 Reference Manual (w skrócie RM). Rdzeń (zasadnicza część) języka podany jest w rozdziałach 1-13 oraz aneksach A („Predefiniowane Otoczenie Języka”), B („Interfejs do innych języków”) i J („Cechy wychodzące z użycia”); Następujące Aneksy Specjalnych Potrzeb definiują cechy wymagane w specjalnych obszarach zastosowań: C („Programowanie Systemowe”), D („Systemy Czasu Rzeczywistego”), E („Systemy rozproszone”), F („Systemy informacyjne”), G („Numeryczny”), H („Pewność i bezpieczeństwo”). Implementacja musi realizować rdzeń języka, może zaś jeden lub więcej specjalizowanych aneksów. Następujące aneksy są informacyjne: K („Atrybuty zdefiniowane w języku”), L („Pragmy zdefiniowane w języku”), M („Cechy zależne od implementacji”), N („Objaśnienia terminów”) i P („Zestawienie składni”).
Reguły języka dzielą się na:
sprawdzane w czasie kompilacji, zarówno reguły składniowe podane za pomocą rozszerzonej notacji Backusa-Naura, jak i dodatkowe reguły legalności. Program jest legalny jeśli spełnia wszystkie te reguły.
reguły semantyczne, opisujące wynik wykonania każdej konstrukcji języka (zarówno w czasie kompilacji, jak i w czasie wykonania).
reguły opisujące sytuacje prowadzące do błędów.
4. Sposób opisu składni w Ada 95 RM (Reference Manual): notacja BNF ulepszona
a) słowa zarezerwowane - wyróżnione tłustym drukiem (boldem)
b) [...] - elementy opcjonalne (0 lub 1)
c) {...} - elementy powtarzalne (0 lub więcej)
d) | - elementy do wyboru (chyba, że | jest po {, wtedy oznacza siebie)
e) reguły wskazują właściwy styl indentacji (to znaczy wcięć):
instrukcja_if ::=
if warunek then
ciąg_instrukcji
{ elsif warunek then
ciąg_instrukcji }
[ else
ciąg_instrukcji ]
end if;
f) dopuszczalny styl to umieszczenie całej konstrukcji na jednej linii (jeśli się mieści).
5. Klasyfikacja błędów
a) Błędy, które muszą być wykryte w czasie kompilacji - program z takim błędem jest nielegalny.
b) Błędy, które muszą być wykryte w czasie wykonania - musi być spowodowany wyjątek.
c) Błędy ograniczone (bounded errors) lub niepoprawne wykonanie (erroneous execution) - naruszenie reguł języka, ale kompilator nie musi tego wykryć ani w czasie kompilacji, ani wykonania. Efekt jest ograniczony dla błędów ograniczonych i nie do przewidzenia dla niepoprawnego wykonania.
W przypadku c) może wystąpić PROGRAM_ERROR.
Rozdział 2: Elementy leksykalne
Tekst programu składa się z tekstów jednej lub więcej kompilacji. Tekst kompilacji jest ciągiem elementów leksykalnych, składających się ze znaków. Sposoby ich składania są opisane w tym rozdziale. Są tu również opisane pragmy, które dostarczają pewnych informacji kompilatorowi.
Jedyne znaki dopuszczalne poza komentarzami to znaki_graficzne i efektory_formatu.
znak ::= znak_graficzny | efektor_formatu | inna_funkcja_kontrolna
znak_graficzny ::= litera_identyfikatora | cyfra | znak_spacji |znak_specjalny
Zestaw znaków możliwych do użycia w tekście programu to tzw. Bazowa Płaszczyzna Wielojęzykowa (Basic Multilingual Plane, BMP) z Uniwersalnego Wielobajtowego Zestawu Znaków ze standardu ISO 10646, pokrywająca się z zestawem znaków Unikodu (65536 znaków), plus zbiór efektorów_formatu oraz, tylko w komentarzach, zbiór innych_funkcji_kontrolnych. Rząd 00 BMP to Latin-1, zestaw znaków opisanych w standardzie ISO 8859-1 (pierwsze 256 znaków Unikodu).
Kategorie znaków:
litera_identyfikatora ::= duża_litera_identyfikatora | mała_litera_identyfikatora
duża_litera_identyfikatora
Dowolny znak z Rzędu 00 BMP którego nazwa zaczyna się od “Duża litera łacińska”.
mała_litera_identyfikatora
Dowolny znak z Rzędu 00 BMP którego nazwa zaczyna się od “Mała litera łacińska”.
cyfra Jeden ze znaków 0, 1, 2, 3, 4, 5, 6, 7, 8 lub 9.
znak_spacji Znak z BMP o nazwie „spacja”
znak_specjalny Dowolny znak BMP który nie jest zarezerwowany dla funkcji kontrolnej i nie jest literą identyfikatora, cyfrą ani spacją.
efektor_formatu Funkcje kontrolne z ISO 6429 zwane tabulacją poziomą (HT), tabulacją pionową (VT), powrotem karetki (CR), wysuwem linii (LF) i wysuwem strony (FF).
inna_funkcja_kontrolna
Dowolna funkcja kontrolna, inna niż efektor formatu, dozwolona w komentarzach (ten zestaw zależy od implementacji).
Elementy leksykalne, separatory i ograniczniki:
Tekst każdej kompilacji jest cięgiem oddzielnych elementów leksykalnych. Każdy element leksykalny jest ciągiem znaków. Rodzaje elementów leksykalnych:
a) ograniczniki pojedyncze & ' ( ) * + , - . / : ; < = > | oraz złożone => .. ** := /= >= <= << >> <>
identyfikatory
słowa zastrzeżone
d) literały liczbowe
e) literały znakowe
f) literały łańcuchowe
g) komentarze
Znaczenie programu zależy tylko od konkretnego ciągu elementów leksykalnych, po wyłączeniu komentarzy.
Tekst kompilacji jest podzielony na linie. Reprezentacja końca linii zależy od implementacji. Tym niemniej, dowolny ciąg złożony z jednego lub więcej efektorów formatu innych niż HT oznacza przynajmniej jeden koniec linii.
Separatory mogą być między dowolnymi elementami leksykalnymi (także przed pierwszym i po ostatnim); muszą być między takimi elementami leksykalnymi, że bez nich mogłyby być traktowane jako jeden element leksykalny (np. między dwoma identyfikatorami). Separatorami są:
a) spacja (poza komentarzem, literałem łańcuchowym i znakowym);
b) tabulacja pozioma (poza komentarzem);
koniec linii.
Identyfikatory są używane jako nazwy.
identyfikator ::=
litera_identyfikatora {[podkreślenie] litera_lub_cyfra}
litera_lub_cyfra ::= litera_identyfikatora | cyfra
Identyfikatory różniące się tylko wielkością liter są uważane za takie same. Znak podkreślenia jest znaczący.
Pewne identyfikatory mają znaczenie zdefiniowane standardowo (np. INTEGER lub TRUE), ale można je w programie zmienić, chociaż na ogół nie należy.
Przykłady identyfikatorów:
Numer X Get_Symbol Maria Bronek
Snobol_4 X1 Licznik_Stron Zapisz_Nast.Elem
10. Przykład podziału na elementy leksykalne:
wiek := 23; -- wiek Jasia
Jest tu 5 elementów leksykalnych: identyfikator wiek, złożony ogranicznik :=, literał liczbowy 23 i komentarz -- wiek Jasia oraz 3 separatory - spacje.
11. Zadanie:
Które z poniższych są legalnymi identyfikatorami i dlaczego:
a) Ada |
b) Kowalski&Co. |
c) semi-final |
d) UFO164G |
e) Uplyw_czasu |
f) 77E2 |
g) X_ |
h) stopa_podatkowa |
i) goto |
|
j) kupuj_w_Łodzi |
k) hop__hop |
|
|
|
12. Uwaga.
Max. długość identyfikatora = max. długość linii (element leksykalny musi się mieścić w linii) = [200 znaków] (wszystkie ważne).
13. Literały liczbowe: rzeczywiste - z kropką, typu uniwersalny_rzeczywisty; całkowite - bez kropki, typu uniwersalny_całkowity. Dzielą się na dziesiątkowe i z podstawą. Składnia:
literał_dziesiątkowy ::= całkowita [ .całkowita ] [ wykładnik ]
całkowita ::= cyfra { [ podkreślenie ] cyfra }
wykładnik ::= E [ + ] całkowita | E-całkowita.
Litera E może być duża lub mała; wykładnik liczby całkowitej nie może mieć znaku -. Znaki podkreślenia nie wpływają na wartość liczby.
14. Przykłady:
a) całkowite: 15 0 2E9 2_500_000
b) rzeczywiste: 15.0 0.0 0.321E-13 3.14159_26 1.2E+7
15. Literały z podstawą : od 2 do 16
literał_z_podstawą ::= podstawa # całkowita_z_podstawą
[.całkowita_z_podstawą]#[wykładnik]
Podstawa to całkowita od 2 do 16, całkowita z podstawą używa cyfr 0 do 9 i liter A do F (dużych lub małych), ale tylko do wartości podstawa-1. Wartości podstawy i wykładnika są zawsze dziesiątkowe.
16. Przykłady:
2#1111_1111# 16#FF# 016#0FF# - całkowite, wartość 225
16#E#E1 2#1110_0000# - całkowite, wartość 224
16#F.FF#E+2 2#1.1111_1111_1111#E11 - rzeczywiste, wartość 4095.0
17. Zadania
1) Które z poniższych literałów są literałami liczbowymi; dla legalnych stwierdź, czy są całkowite, czy rzeczywiste:
a) 36.8 b) .5 c) 32e2 d) 32e-2 e) 2#1011 f) 2.71828_18285 g) 12#ABC# h) e+6 i) 16#FfF# j) 1_0#1_0#E1_0 k) 27.4 e_2 l) 2#11#e-1
2) Podaj wartość następujących literałów:
a) 16#D#E1 b) 2#11#E11 c) 16#FF.F#E+1 d) 2#11111.111_1111#E7
3) Na ile sposobów możesz zapisać jako literały liczbowe liczby całkowite a) 27 b) 130 (nie uwzględniając podkreśleń, różnicy między E i e oraz opcjonalnego znaku + przy wykładniku)
18. Literały znakowe otrzymuje się przez umieszczenie między apostrofami jednego z 95 znaków graficznych (nr ASCII 32 do 126, tzn. wszystkie bez kontrolnych).
19. Przykłady : 'a' '*' ''' ' '
20. Literały łańcuchowe otrzymuje się przez umieszczenie między cudzysłowami ciągu (0 lub więcej) znaków graficznych. Cudzysłów w łańcuchu trzeba zapisać jako dwa cudzysłowy. Długość łańcucha - liczba reprezentowanych znaków.
Literał łańcuchowy (jako element leksykalny) musi się mieścić w jednej linii. Jeśli chcemy zapisać długi łańcuch, używamy operatora konkatenacji &. Jeśli chcemy umieścić w łańcuchu znaki kontrolne, używamy pakietu ASCII z pakietu STANDARD.
21. Przykłady:
"Oto powitanie:" "" " " "A" """" - literały o długościach 14, 0, 1, 1, 1.
"Takie znaki, jak , % czy} mozna umieszczac w literalach lancuchowych"
"Pierwsza czesc ciagu znakow"&
"kontynuowana w nastepnej linii"
"ciag, ktory zawiera" &ASCII.HT& "znak kontrolny"
22. Komentarze zaczynają się od dwóch myślników -- i rozciągają do końca linii. Mogą występować na każdej linii programu i nie mają wpływu na legalnośc programu (o ile zawierają tylko znaki graficzne i ew. tabulatory horyzontalne).
23. Przykłady:
-- komentarze sluza glownie oswieceniu czytelnika
begin -- glownego programu
-- dlugi komentarz mozna
-- umiescic w dwoch lub wiecej liniach
------------- pierwsze dwa myslniki rozpoczynaja komentarz
24. Pragmy służą przekazaniu informacji kompilatorowi; zaczynają się od zastrzeżonego słowa pragma, potem identyfikator, który jest nazwą pragmy i ewentualnie argumenty.
25. Przykłady:
pragma LIST (ON); pragma SUPRESS (RANGE_CHECK);
26. Pragmy dzielą się na zdefiniowane przez język (Annex L; LRM) i zależne od implementacji; te drugie nie mogą wpływać na legalność programu.
27. Zadania
1) Ile jest elementów leksykalnych w każdej z poniższych linijek:
a) X := X + 2; -- dodaj dwa do X
b) -- to jest komentarz bez sensu
c) ---------------------------------------------
d) - - - - - - - - - - - - - -
2) Opisz a) delay 2.0; b) delay2.0; (leksykalnie)
28. Podsumowanie:
a) Wielkość liter jest nieważna poza literałami znakowymi i łańcuchami (i ew. komentarzami)
b) Podkreślenia są ważne w identyfikatorach, ale nie w literałach liczbowych
c) Spacje nie są dozwolone w elementach leksykalnych, poza literałami znakowymi, łańcuchami i komentarzami. Znaki tabulacji (Tab) są dozwolone tylko w komentarzach.
d) Obecność kropki odróżnia literały rzeczywiste od całkowitych. Literały rzeczywiste muszą mieć przynajmniej jedną cyfrę na prawo i lewo od kropki dziesiętnej.
e) Liczba całkowita nie może mieć ujemnego wykładnika.
f) Literały liczbowe nie mają znaku.
Rozdział 3: Obiekty i typy
1. Obiekt jest to wielkość, która zawiera wartość pewnego typu. Obiekty dzielą się na zmienne i stałe. Deklaracje obiektów:
deklaracja_obiektu ::= lista_identyfikatorów : [ constant ]
wskazanie_podtypu [ := wyrażenie ]; | ...
2. Przykłady: I : Integer;
P : Integer := 38; -- inicjalizacja
Rozmiar : constant Float := 12.0;
Uwaga: Stałe muszą być inicjowane podczas deklaracji.
J, K : INTEGER := 3;
jest równoważne dwóm kolejnym deklaracjom
J : INTEGER := 3;
K : INTEGER := 3;
Uwaga: Wyrażenie inicjujące jest obliczane w czasie wykonania programu, w trakcie opracowywania deklaracji.
Nie musi być statyczne, np.
MN : INTEGER := M * N; -- lub
MN : constant INTEGER := M * N; -- stała nie może się zmienić w trakcie -- swego życia
2. Nazywane liczby są wprowadzane przez deklaracje liczb:
deklaracja_liczby : constant := wyrażenie_uniwersalne_statyczne;
Przykład: PI : constant := 3.14159_26536;
Wyrażenie musi dawać wartość typu uniwersalna_całkowita lub uniwersalna_rzeczywista, możliwą do obliczenia w czasie kompilacji - wyrażenie statyczne.
3. Zadania
a) Napisz deklarację zmiennej rzeczywistej R z wartością początkową jeden.
b) Napisz deklaracje stałych rzeczywistych (liczb) zero i jeden.
c) Co jest nie w porządku w poniższych deklaracjach:
var i : integer;
g : constant := 981;
q : constant integer;
mn : constant integr := m*m*n;
2PI : constant := 2.0 * PI;
Instrukcja przypisania - zastępuje bieżącą wartość zmiennej przez wynik obliczenia wyrażenia.
instrukcja_przypisania ::= nazwa_zmiennej := wyrażenie;
Nie wolno podstawiać za kilka zmiennych, np. p:=q:=7; jest nielegalne. Zmienna powinna być typu nielimitowanego.
5. Deklaracje. W języku definiuje się pewne rodzaje wielkości które są deklarowane, jawnie lub niejawnie, za pomocą deklaracji (np. obiekty, typy, podprogramy). Dla każdego rodzaju deklaracji reguły języka określają pewną część tekstu zwaną zasięgiem deklaracji. Większość deklaracji związuje z deklarowaną wielkością identyfikator. W ramach zasięgu, i tylko tam, znajdują się miejsca, gdzie można używać tego identyfikatora, aby odnieść się do odpowiedniej wielkości. W tych miejscach identyfikator ten jest nazwą tej wielkości. Mówimy, że nazwa ta oznacza tę wielkość.
Deklaracje są opracowywane, i to w kolejności występowania. Nie wolno używać żadnej wielkości przed jej zadeklarowaniem (jawnym lub nie). W trakcie opracowywania obliczane są wartości początkowe.
6. Instrukcja bloku
Przykład:
declare
I : INTEGER := 0; -- tu deklaracje (opracowywane)
begin
I := I + 1; -- tu instrukcje (wykonywane)
end;
Kolejność deklaracji jest ważna:
declare
I : INTEGER := 0;
K : INTEGER := I;
begin
jest prawidłowe, natomiast
declare
K : INTEGER := I;
I : INTEGER := 0;
begin
na ogół nie jest (a jeśli jest, to ma inne znaczenie).
Bloki można zagnieżdżać:
declare
i,j : integer;
begin
... -- tu i jest zewnętrzne
declare
i : integer;
begin
... -- tu i jest wewnętrzne
end;
... -- tu i jest znowu zewnętrzne
end;
7. Zasięg i widzialność - przykład
declare
i : integer := 0;
begin widzialność zewnętrznego i
...
declare zasięg zewnętrznego i
k : integer := i;
i : integer := 0;
begin zasięg widzialność
.. . wewnętrznego i wewnętrznego i
end;
... widzialność zewnętrznego i
end;
zasięg widzialność
zewnętrznego i zewnętrznego i
8. Zadanie
Ile jest błędów w poniższym tekście:
declare
i : integer := 7;
j,k : integer
begin
j:=i+k;
declare
p : integer = i;
i,j : integer;
begin
i:=p+q;
j:=p-q;
k:=i*j;
end;
put(k);
end;
Typy - typ charakteryzuje zbiór wartości i zbiór operacji stosowalnych do tych wartości, np. typ INTEGER (standardowy, tzn. z pakietu STANDARD) ma jako zbiór wartości zbiór liczb całkowitych, a jako zbiór operacji +, -, * itp.
Klasyfikacja typów. Można je podzielić na jawne (posiadające nazwy) i anonimowe. Pewne typy anonimowe mogą mieć nazwy na użytek LRM (są one podawane kursywą, nie można ich używać w programach). Wszystkie typy standardowe (podane dużymi literami) są zadeklarowane w pakiecie STANDARD. Typy standardowe podane w nawiasach mogą w danej implementacji nie wystąpić.
wszystkie typy
elementarny
skalarny
dyskretny
wyliczeniowy
znakowy
CHARACTER
WIDE_CHARACTER
logiczny
BOOLEAN
inny
inny wyliczeniowy
całkowity
całkowity ze znakiem
INTEGER
NATURAL (podtyp)
POSITIVE (podtyp)
(LONG_INTEGER)
(LONG_LONG_INTEGER)
(SHORT_INTEGER)
(SHORT_SHORT_INTEGER)
universal_integer (anonimowy)
root_integer (anonimowy)
inny
całkowity modularny
rzeczywisty
universal_real (anonimowy)
root_real (anonimowy)
zmiennoprzecinkowy
FLOAT
(LONG_FLOAT)
(LONG_LONG_FLOAT)
(SHORT_FLOAT)
inny
stałoprzecinkowy
universal_fixed (anonimowy)
stałoprzecinkowy zwykły
DURATION
inny
stałoprzecinkowy dziesiętny
dostępu
dostępu-do-obiektu
dostępu-do-podprogramu
złożony
tablicowy
łańcuchowy
STRING
WIDE_STRING
inny
inny tablicowy
rekordowy nieznaczony
znaczony
zadaniowy
chroniony
Typy numeryczne (liczbowe) to typy całkowite i rzeczywiste. Typy prywatne i limitowane nie mieszczą się w powyższej klasyfikacji.
Deklaracja typu - uproszczona składnia:
deklaracja typu ::= type nazwa_typu is definicja_typu;
definicja_typu ::= definicja_typu_wyliczeniowego | definicja_typu_całkowitego | definicja_typu_rzeczywistego | .
Przykłady
type kolor is (czerwony, zolty, zielony); -- wyliczeniowy
type byte is range 0 .. 255; -- całkowity
type real is digits 6; -- rzeczywisty zmiennoprzecinkowy
type cena is delta 0.01 range -1.0E6 .. +1.0E6; -- rzeczywisty -- stałoprzecinkowy
c : kolor := czerwony; -- deklaracja zmiennej c typu kolor zainicjowanej na -- czerwony
stop : constant kolor := czerwony; -- deklaracja stałej stop.
Podtypy. Podtyp jest to typ wraz z ograniczeniem. Zbiór wartości podtypu jest podzbiorem zbioru wartości jego typu. Zbiór operacji jest taki sam dla typu i jego podtypu. Nakładając ograniczenie na podtyp pewnego typu otrzymuje się nowy podtyp o tym samym typie. Ograniczenia nie muszą być statyczne. Jeśli ograniczenie jest puste (zerowe), to podtyp jest niezawężony, w przeciwnym przypadku jest to podtyp zawężony.
Deklarując typ deklarujemy jednocześnie jego pierwszy podtyp - nazywa się on tak samo, jak typ.
Pierwszy podtyp jest zawężony, jeśli w jego deklaracji występuje zawężenie (zakresu, indeksu lub wyróżnika). Dla każdego typu skalarnego istnieje jego niezawężony podtyp zwany bazowym podtypem tego typu. Można go otrzymać przy pomocy atrybutu base. Na przykład deklaracja:
type calkowity is range -1_000..1_000;
wprowadza typ o nazwie calkowity oraz jego pierwszy, zawężony podtyp o nazwie calkowity, jak również podtyp bazowy calkowity'base. Zbiorem wartości typu jest zbiór wszystkich liczb całkowitych, dla pierwszego podtypu - zbór liczb od -1000 do 1000, dla podtypu bazowego zakres wybrany dla danego typu przez implementację - tu może to być na przykład od -32768 do 32767.
Przykłady:
subtype numer_dnia is integer range 1 .. 31;
subtype dzien_lutego is numer_dnia range 1 .. 29;
d : numer_dnia;
Podstawienie d := 33; spowoduje constraint_error.
Uwaga Deklaracja podtypu nie wprowadza nowego typu. Np. jeśli
i : integer;
to podstawienie i:=d; oraz d:=i; są legalne, przy czym to drugie może spowodować constraint_error. Taki sam efekt można uzyskać za pomocą deklaracji
d:integer range 1..31;
jak za pomocą
d : numer_dnia;
Jeśli mamy deklaracje
type numer_dnia is range 1 .. 31; d : numer_dnia; i : integer;
to zarówno i:=d; jak i d:=i; są nielegalne, bo numer_dnia jest innym typem.
13. Ograniczenia i zakresy
ograniczenie ::= ograniczenie_zakresu | ograniczenie_zmiennoprzecinkowe | ograniczenie_stałoprzecinkowe | ...
ograniczenie_zakresu ::= range zakres
zakres ::= wyrażenie_proste .. wyrażenie_proste
Jeśli zakres jest postaci L .. R, to określa on podzbiór wartości pewnego typu skalarnego. L i R nazywa się górnym i dolnym kresem zakresu. Zakres nazywa się zerowy, jeśli R < L jest TRUE. Ograniczenie nałożone na podtyp L1 .. R1 musi być zgodne z ograniczeniem podtypu L .. R, tzn. L <= L1 <= R1 <= R lub L1 .. R1 jest zerowy:
subtype nr_dnia is numer_dnia range 1..10;
powoduje constraint_error, bo 0 .. 10 nie jest zgodne z 1 .. 31.
Podtyp może być nieograniczony:
subtype int is integer;
lub pusty:
subtype pusty is integer range 1..0;
14. Typy skalarne i atrybuty
Dla każdego typu skalarnego określone są atrybuty FIRST i LAST. Atrybut określa własność typu, podtypu, obiektu itp. Zapisuje się go przez napisanie apostrofu i nazwy atrybutu po nazwie odpowiedniego typu, podtypu itd.
Przykłady:
INTEGER'FIRST=-32786; INTEGER'LAST=32767;
numer_dnia'first=1; numer_dnia'last=31; -- pierwszy i ostatni element podtypu.
Dla podtypów określony jest atrybut BASE, który daje typ bazowy. Wolno go użyć tylko przed kolejnym atrybutem, np.
dzien_lutego'base'first=-32768; numer_dnia'base'last=32767;
Standardowe podtypy NATURAL i POSITIVE są określone jako
subtype NATURAL is INTEGER range 0..INTEGER'LAST;
subtype POSITIVE is INTEGER range 1..INTEGER'LAST;
15. Operacje na typach liczbowych
Według malejących priorytetów: dwuargumentowy ** i jednoargumentowy abs.
abs : całkowity → ten sam -- wartość bezwzględna
rzeczywisty: ten sam
**: całkowity INTEGER ten sam całkowity -- podnoszenie do potęgi, wykładnik -- musi być >= 0
rzeczywisty INTEGER jak lewy -- podnoszenie do potęgi, wykładnik -- dowolny zmiennoprzecinkowy
Mnożące:
*, / - operandy i wynik tego samego typu, całkowitego lub rzeczywistego zmiennoprze- cinkowego; mnożenie i dzielenie (całkowite dla operandów całkowitych)
mod, rem - operandy i wynik tego samego typu całkowitego; określone relacjami
-- reszta z dzielenia, znak dzielnej
, -- operacja modulo, znak dzielnika
Dodające unarne: +, - : dowolny liczbowy → ten sam
dodające binarne: +, -: operandy i wynik tego samego typu liczbowego
Relacyjne: =, /=, >, <, >=, <=: operandy tego samego typu, wynik BOOLEAN.
Przy tym samym priorytecie - od lewej do prawej. Czasem konieczne nawiasy, np. A**-B, A*-B, A**B**C są nielegalne - wynika to ze składni wyrażeń.
Nie wolno mieszać typów: 2+3.0 jest nielegalne!
16. Zadania:
Obliczyć wartości poniższych wyrażeń przy następujących deklaracjach:
i:integer:=7;
j:integer:=-5;
k:integer:=3;
a) i*j*k b) i/j*k c)i/j/k d) j+2 mod i e) j+2 rem i f) (k**k)**k g) k**(k**k)
h) -j mod 3 i) -j rem 3
Przepisz poniższe wyrażenia matematyczne używając składni Ady. Użyj odpowiednich identyfikatorów właściwego typu.
-- wyróżnik równania kwadratowego
-- objętość sfery
17. Typy wyliczeniowe. W definicji typu wyliczeniowego występują w okrągłych nawiasach, oddzielone przecinkami literały wyliczeniowe - jeden lub więcej. Każdy literał wyliczeniowy jest identyfikatorem lub literałem znakowym. Jeśli w definicji występuje choć jeden literał znakowy, to typ nazywa się znakowy. Taki jest np. standardowy typ CHARACTER, składający się ze 128 znaków ASCII. 95 z nich jest oznaczonych przez literały znakowe, innym - znakom kontrolnym - odpowiadają (anonimowe) identyfikatory. Inny typ wyliczeniowy standardowy to BOOLEAN:
type BOOLEAN is (FALSE, TRUE);
18. Porządek i atrybuty
Porządek dla typu wyliczeniowego jest zgodny z kolejnością literałów wyliczeniowych. Każdy z tych literałów ma określoną pozycję - pierwszy ma pozycję zero.
Poniższe atrybuty dotyczą typów bazowych. Stosują się również do typów całkowitych. Pozycją liczby całkowitej jest ta sama liczba.
POS Funkcja podająca pozycję parametru aktualnego
VAL Funkcja podająca element o danej pozycji (powoduje constraint_error jeśli nie ma takiego elementu)
SUCC Funkcja podająca następny element. Constraint_error dla ostatniego.
PRED Funkcja podająca element poprzedni. Constraint_error dla pierwszego.
Podtyp typu wyliczeniowego otrzymujemy ograniczając zakres tego typu.
19. Przeładowanie i kwalifikacja. Różne typy wyliczeniowe mogą używać tych samych literałów, np.
type kolor is (czerwony, zolty, zielony);
type barwa is (bialy, czerwony, zolty, zielony, niebieski, brazowy, czarny);
Mówimy, że takie literały są przeładowane. Jeśli ich używamy, z kontekstu musi być jasne, jakiego są typu. Jeden ze sposobów: kwalifikacja:
kolor'(zielony) -- zielony typu kolor
barwa'(zielony) -- zielony typu barwa
20. Zamiana typów. Podobna do kwalifikacji jest konwersja (zamiana) typów. Różnica polega na tym, że kwalifikacja tylko określa, do jakiego typu należy dana wartość, konwersja natomiast zamienia pewną wartość na wartość określonego typu, np.
float(5)=5.0; integer(4.45)=4; integer(4.6)=5
21. Przykłady
type dzien is (pon, wto, sro, czw, pia, sob, nie);
subtype dzien_powsz is dzien range pon..pia;
kolor'first = czerwony
dzien_powsz'last = pia
kolor'succ(zolty) = zielony
barwa'pred(czerwony)= bialy
dzien_powsz'succ(pia) = sob
kolor'pos(czerwony) = 0
kolor'pos(zielony) = 2
dzien'val(6) = nie
wto<czw; czarny>=zolty; true>false
type cyfra_rzymska is ('I', 'V', 'X', 'L', 'C', 'D', 'M');
type mieszany is ('A', 'B', '*', B, NIC, '?', '%');
cyfra_rzymska'('I') < 'C'
'I' > character'('C')
'B'<b, B<NIC, NIC<'%'
22. Zadania:
1. Oblicz
a) dzien'succ(dzien_powsz'last)
b) dzien_powsz'succ(dzien_powsz'last)
c) barwa'pos(zielony)
2. Napisz deklaracje typów wyliczeniowych dla
a) kolorów tęczy b) typowych owoców
3. Po zjedzeniu n cukierków dziecko powinno otrzymać kredkę jednej z barw typu barwa. Napisz odpowiednie wyrażenie tak, by kredki wszystkich kolorów były rozdawane.
4. Przypuśćmy, że pierwszego danego miesiąca przypada w dniu tygodnia d, gdzie d jest typu dzien. Napisz podstawienie, które zastąpi d przez dzień tygodnia przypadający na N-tego tego miesiąca.
23. Operatory logiczne. Działają na wartościach boole'owskich.
Unarny NOT o priorytecie ABS,; binarbe AND, OR, XOR o najniższym priorytecie
24. Zadania
1) Napisz deklaracje stałych T i F mających wartości odpowiednio TRUE i FALSE.
2) Przy T i F z poprzedniego zadania, oblicz
a) T and F and T
b) not T or T
c) (F=F)=(F=F)
3) Oblicz (A/=B)=(A xor B) dla wszystkich kombinacji wartości A i B
25. Składnia wyrażeń
wyrażenie ::= relacja {and relacja} | relacja {and then relacja} | relacja {or relacja} | relacja {or else relacja} | relacja {xor relacja}
relacja ::= wyrażenie_proste [operator_relacyjny wyrażenie_proste] | wyrażenie_proste [not] in zakres | wyrażenie_proste [not] in oznaczenie_typu
wyrażenie_proste ::= [unarny_operator_dodający] składnik {binarny_operator_dodający składnik}
składnik ::= czynnik {operator_mnożący czynnik}
czynnik ::= pierwsze [** pierwsze] | abs pierwsze | not pierwsze
pierwsze ::= literał_liczbowy | null | agregat | literał_łańcuchowy | nazwana_liczba | alokator | wywołanie_funkcji | zamiana_typu | wyrażenie_kwalifikowane | wyrażenie
oznaczenie_typu ::= nazwa_typu | nazwa_podtypu
26. Formy krótkiego obiegu i operacje przynależności
L and then R daje FALSE, jeżeli L daje FALSE i wtedy R nie jest obliczane; jeśli L jest TRUE, to całe wyrażenie ma wartość R
L or else R daje TRUE, jeżeli L daje TRUE i wtedy R nie jest obliczane; jeśli L jest FALSE, to całe wyrażenie ma wartość R.
Np. if K/=0 and then J/K>0 then ...
if delta > 0 or else (delta=0 and ...) then ...
Operacje in (not in) oznaczają przynależność (lub jej brak) do zakresu lub podtypu, np.
N not in 1..10 -- TRUE jeśli N<1 lub N>10
dzisiaj in pon..pia -- dzisiaj in dzien_powsz
27. Podsumowanie - klasy operatorów - priorytety
logiczne - and, or, xor (and then, or else)
relacyjne - =, /= , <, <=, >, >= (in, not in)
binarne dodające - +, -, &; unarne dodające + , -
mnożące - *, /, mod, rem; o najwyższym priorytecie **, abs, not
Uwaga. Operatory = i /= stosuje się do wszystkich typów, pozostałe relacyjne - do skalarnych.
28. Zadania:
1) Zapisz następujące wyrażenia w Adzie:
a)
(okres wahadła)
b)
(masa cząsteczki relatywistycznej)
c)
(formuła Stirlinga dla n! - całkowite n)
2) Przepisz 1(c) zastępując n przez rzeczywistą wartość x.
29. Podsumowanie:
1) Deklaracje i instrukcje kończą się średnikiem.
2) Inicjalizacje i podstawienia używają :=
3) Używaj deklaracji liczb zamiast deklaracji stałych tam, gdzie można.
4) Deklaracje są opracowywane kolejno.
5) Każda definicja typu wprowadza inny typ.
6) Podtyp nie jest nowym typem, ale rodzajem skróconego zapisu dla typu z ograniczeniem.
7) Brak arytmetyki z mieszaniem typów.
8) Odróżniaj rem i mod dla ujemnych operandów.
9) Podnoszenie do potęgi z ujemnym wykładnikiem stosuje się tylko do typów rzeczywistych.
10) Typ nie może być pusty, podtyp może.
11) Uważaj na priorytety operatorów unarnych.
12) POS, VAL, SUCC, PRED są takie same na podtypach, jak i itypach bazowych.
13) FIRST i LAST są różne dla podtypów.
14) Zamiana typów używa nawiasów, kwalifikacja dodatkowo apostrofu.
15) Kolejność obliczania operandów binarnych nie jest okreslona.
16) Odróżniaj and, or i and then, or else.
WYKŁAD NR 3
1. Abstrakcja - oddzielenie istotnych właściwości obiektu od nieistotnych detali.
Przykład - przyrządy kuchenne: dziadek do orzechów, grill, kran/zlew, kuchenka mikrofalowa. Urządzenie jest na wyższym poziomie abstrakcji, jeżeli znajomość szczegółów jego działania jest mniej potrzebna przy jego używaniu.
a) dziadek do orzechów - musimy dokładnie wiedzieć, jak działa, by móc go z powodzeniem użyć. Całkowity brak abstrakcji.
b) grill - byłoby wygodnie używać go tak, że wkładamy węgiel drzewny, steki, podpalamy zapałką i po pewnym czasie wyjmujemy gotowe steki, ale w praktyce trzeba nieźle rozumieć to, co się dzieje.
c) kran/zlew - odkręcamy kran - leci woda. Rzadko potrzebne są nasze ingerencje. Duży stopień abstrakcji.
d) kuchenka mikrofalowa - najwyższy stopień abstrakcji. Odpowiednie dane na wejściu (potrawa do podgrzania, ustawienie zegara) i otrzymujemy na wyjściu gorące danie.
2. Ogólność - stopień, do którego urządzenie może być zastosowane do wielu podobnych zadań, a nie do kilku konkretnych.
a) dziadek do orzechów - ogólność niewielka, nie należy używać do tłuczenia innych rzeczy.
b) kran/zlew - napełnianie garnków wodą, zmywanie statków, ochładzanie gorących czajników itp.
c) grill - ogrzewanie na wiele różnych sposobów, opiekanie, podwędzanie itp.
d) kuchenka mikrofalowa - gotowanie, podgrzewanie, odmrażanie itp.
3. Rodzaje abstrakcji:
a) Abstrakcja wyrażeń (np. FORTRAN) - piszemy X=A+B(I) zamiast ciągu instrukcji używających rejestrów procesora. Abstrakcja wyrażeń w Adzie została opisana na wykładzie nr. 2
b) Abstrakcja sterowania (ALGOL 60) - przebieg sterowania posiada jasną strukturę i poszczególne miejsca w programie nie muszą być nazywane lub numerowane (brak etykiet i skoków, np. w instrukcji if X = Y then P:=Q else A:=B).
c) Abstrakcja proceduralna - wydzielanie z programu akcji o wyższym stopniu abstrakcji, nazwanie ich i używanie tych nazw w celu zastosowania tych akcji. Np. w Pascalu główny program może mieć postać begin czytaj; przetwarzaj; pisz end. , gdzie czytaj, przetwarzaj i pisz są osobno zakodowanymi procedurami.
d) Abstrakcja danych - wydzielenie z programu struktur danych wraz ze stosującymi się do tych danych operacjami. Przykładem są jednostki w Turbo Pascalu, moduły w Moduli 2 i pakiety w Adzie.
e) Abstrakcja typów - wydzielenie typów strukturalnych, dla których stosowane operacje nie zależą, lub zależą w niewielkim stopniu, od typów elementów bazowych tych typów strukturalnych. Przykład: operacje na stosach, kolejkach, listach łączonych itp. nie zależą od tego, czy te stosy itp. składają się z liczb całkowitych, rzeczywistych, łańcuchów, rekordów itp. W Adzie - jednostki rodzajowe.
f) Abstrakcja zadań - wydzielanie akcji w programie, które mogą zachodzić współbieżnie i określenie sposobów komunikowania się i synchronizacji tych akcji. W Adzie - zadania.
4. Struktury sterujące w Adzie - instrukcje złożone
instrukcja_złożona ::= instrukcja_if | instrukcja_case | instrukcja_pętli | instrukcja_bloku | ...
instrukcja_prosta ::= instrukcja_null | instrukcja_podstawienia | instrukcja_wywołania_procedury | instrukcja_exit | instrukcja_return | instrukcja_goto | ...
5. Instrukcja warunkowa if
Przykład
if A=0.0 then
-- przypadek liniowy
else
if B**2-4.0*A*C>=0.0 then
-- pierwiastki rzeczywiste
else
-- pierwiastki zespolone
end if;
end if;
lub lepiej
if A=0.0 then
-- przypadek liniowy
elsif b**2-4.0*A*C>=0.0 then
--pierwiastki rzeczywiste
else
-- pierwiastki zespolone
end if;
Zamiast:
if J>0 then
if I/J>K then
akcja;
end if;
end if;
lepiej:
if J>0 and then I/J>K then
akcja;
end if;
6. Zadania
1) Mamy dane deklaracje:
type nazwa_mies is (sty, lut, mar, kwi, maj, cze, lip, sie, wrz, paz, lis, gru);
dzien: integer range 1..31;
miesiac: nazwa_mies;
rok: integer range 1901..2099;
Załóżmy, że zmienne dzien, miesiac, rok zawierają dzisiejszą datę. Napisz instrukcje, które zmieniają tę datę na jutrzejszą. Co się stanie, gdy dzisiaj jest 31 grudnia 2099?
2) X i Y są zmiennymi rzeczywistymi. Napisz instrukcje, które w razie potrzeby wymienią wartości X i Y tak, by większa była w X. Użyj bloku, by zadeklarować pomocniczą
zmienną T.
7. Instrukcja wyboru
instrukcja_case ::=
case wyrażenie is
alternatywa_instrukcji_case
{alternatywa_instrukcji_case}
end case;
alternatywa_instrukcji_case::=
when wybór { | wybór} => ciąg instrukcji
wybór ::= wyrażenie_proste | zakres_dyskretny | others | ...
Przykład
case dzisiaj is -- przy założeniu, że zadeklarowano
-- type dzien_tyg is (pon, wto, sro, czw, pia, sob, nie); dzisiaj:dzien_tyg;
when pon | wto | sro | czw => pracuj;
when pia => pracuj;
baw_sie;
when sob | nie => null;
end case;
Zamiast pon | wto | sro | czw lepiej napisać pon .. czw.
Zakres dyskretny to zakres (czyli L .. R) lyb nazwa typu lub podtypu, ewentualnie z ograniczeniem zakresu. Np. można poprzedni przykład zapisać tak:
case dzisiaj is
when dzien_powsz => pracuj;
if dzisiaj=pia then baw_sie, end if;
when others +> null;
end case;
Wybór others może być tylko na końcu i pojedynczy. Wybory muszą wyczerpywać wszystkie wartości statycznego podtypu lub (o ile takiego nie ma) typu bazowego wyrażenia wybierającego (np. typem dla dzisiaj jest dzien). Wszystkie wybory muszą być statyczne. Każda wartość wyrażenia wybierającego może się pojawić najwyżej raz. Wyborowi others może nie odpowiadać żadna wartość. Zbiór koniecznych wyborów można ograniczyć poprzez kwalifikowanie wyrażenia wybierającego statycznym podtypem, np.
case dzien_powsz'(dzisiaj) is
when pon..czw => pracuj;
when pia => pracuj; baw_sie;
end case;
Jeśli mamy deklaracje
i: integer range 1..10;
j: integer range 1..N; -- N nie jest statyczne
to case i is.. . wymaga wyborów dla liczb od 1 do 10, natomiast case j is... wymaga wyborów dla wszystkich liczb typu INTEGER. W przypadku instrukcji
case dzien'pos(dzisiaj) is... musi być użyty wybór others, bo wyrażenie wybierające jest typu uniwersalny całkowity.
Podsumowanie:
1) Każda wartość wyrażenia po case musi wystąpić dokładnie jeden raz.
2) Wszystkie wartości i zakresy po when muszą być statyczne
3) Jeśli występuje others, to na końcu i samodzielnie.
8. Instrukcja pętli
Najprostsza:
loop
ciąg_instrukcji
end loop;
np.
loop;
praca; jedzenie; spanie;
end loop;
Przykład:
Oblicz
I rozwiązanie:
declare
e: float:=1.0;
i: integer := 0;
wyraz:float:=1.0;
begin
loop
i:=i+1; wyraz:=wyraz/float(i);
e:=e+wyraz;
end loop;
II rozwiązanie (z zakończeniem pętli)
loop
if i=n then exit; end if;
i:=i+1;
wyraz:=wyraz/float(i);
e:=e+wyraz;
end loop;
Zamiast if warunek then exit; end if; piszemy exit when condition;
III wersja:
while i /= n loop
i := i + 1;
wyraz := wyraz / float(i);
e := e + wyraz;
end loop;
IV wersja:
for i in 1 .. n loop
wyraz := wyraz / float(i);
e := e + wyraz;
end loop;
Zmienna i w IV wersji jest niejawnie zadeklarowana w miejscu jej wystąpienia. Wewnątrz pętli jest stałą. Zakres (1..n) jest obliczany tylko raz. Typem zmiennej sterującej pętli może być każdy typ dyskretny, np.
for dzisiaj in pon .. nie loop
case dzisiaj is
...
end case;
end loop;
Inaczej:
for dzisiaj in dzien_tyg loop... end loop;
można też
for dzisiaj in dzien_tyg range pon .. pia loop ...
albo
for dzisiaj in dzien_powsz loop ...
Jeśli zmienna sterująca przebiega zakres od góry w dół, piszemy in reverse.
Pętle można nazywać:
szukaj: for i in 1..n loop
for j in 1 .. m loop
if speln_warunek then
wart_i := i;
wart_j := j;
exit szukaj;
end if;
end loop;
end loop szukaj;
Można też pisać:
exit szukaj when warunek;
9. Zadania:
1) Przepisz zadanie 1 z nr 6 używając instrukcji case, aby ustawić właściwą wartość w koniec_mies.
2) Ogrodnik kopie w zimie, sadzi na wiosnę, podlewa w lecie i zbiera na jesieni. Napisz instrukcję case, aby wywołać odpowiednie podprogramy: kopie, sadzi, podlewa, zbiera w zależności od miesiąca M. W razie potrzeby zadeklaruj podtypy.
3) Pewien nierozważny człowiek dostaje pensję 1-go każdego miesiąca. Przez pierwsze 10 dni miesiąca bawi się, następne 10 przetrzymuje, pozostałe głoduje. Wywołuj podprogramy zabawa, przetrzymanie, glod w zależności od dnia D. Załóż, że koniec_mies został ustawiony i D jest zadeklarowane jako d: integer range 1 .. koniec_mies.
4) Napisz instrukcje czytające i dodające serię liczb. Koniec serii jest oznaczony liczbą ujemną.
5) Napisz instrukcje określające potęgę 2 w rozkładzie liczby N. Oblicz wynik w zmiennej ilość, ale nie zmieniaj N.
6) Oblicz ()
7) Przepisz przykład z zagnieżdżonymi pętlami używając etykiety <<szukaj>> zamiast nazwanej pętli. Dlaczego nie jest to zbyt dobre rozwiązanie?
10. Podsumowanie:
1) Nawiasy instrukcji muszą sobie dokładnie odpowiadać.
2) Używaj elsif tam, gdzie należy.
3) Wybory w instrukcji case muszą być statyczne.
4) Wszystkie wybory w instrukcji case muszą byc uwzględnione.
5) Jeśli others występuje, to ostatnie i samodzielnie.
6) Wyrażenie po case może byc kwalifikowane, aby zredukować ilość możliwości.
7) Zmienna sfer pętli zachowuje się jak stała.
8) Nazywana pętla musi mieć nazwę na obu końcach.
9) Unikaj goto.
10) Używaj zalecanego stylu pisania.
WYKŁAD 4
1. Tablica - obiekt złożony, wszystkie składowe tego samego typu. Deklaracja zmiennej:
A : array (integer range 1..6) of float;
Nadanie wartości początkowej:
for i in 1..6 loop
A(i) := 0.0;
end loop;
2. Tablice wielowymiarowe, np.
aa : array (integer range 0..2, integer range 0..3) of float;
Inicjacja:
for i in 0..2 loop
for j in 0..3 loop
aa (i,j) := 0.0;
end loop;
end loop;
3. Zakresy dyskretne nie muszą byc statyczne, np.
get (n);
declare
b : array (integer range 1 .. n ) of float;
begin
for i in 1 .. n loop
get (b(i));
end loop;
-- przetwarzaj wektor b
end;
4. Inne zakresy dyskretne dla indeksów;
a: array (1..6) of float; -- reguła: indeksy są typu INTEGER
godz_pracy : array (dzien) of float;
Inicjacja:
for d in dzien_powsz loop
godz_pracy(d) := 8.0;
end loop;
godz_pracy (sob) := 0.0;
godz_pracy (nie) := 0.0;
5. Atrybuty: first, last, length, range
godz_pracy'first = pon
godz_pracy'last = nie
godz_pracy'length = 7
godz_pracy'range to pon .. nie
aa'first(1) = 0, aa'first(2) = 0, aa'last(1) = 2, aa'last(2) = 3, aa'length(1) = 3, aa'length (2) = 4, aa'range (1) to 0 .. 2,
aa'range(2) to 0 .. 3.
6. Inicjacja z użyciem atrybutów:
for i in a'range loop
a(i) := 0.0;
end loop;
for i in aa'range(1) loop
for j in aa'range(2) loop
aa(i,j) := 0.0;
end loop;
end loop;
Atrybuty w deklaracji:
j : integer range a'range; -- zamiast j : integer range 1 .. 6;
jest to użyteczne gdy j będzie indeksowało tablicę a.
7. Inicjacja w deklaracji:
a : array (1..6) of float := (0.0,0.0,0.0,0.0,0.0,0.0);
aa : array (0..2, 0..3) of float :=
( (0.0,0.0,0.0,0.0), (0.0,0.0,0.0,0.0), (0.0,0.0,0.0,0.0) );
Wyrażenia po prawej stronie to agregaty, muszą inicjować wszystkie składowe tablicy.
8. Tablice stałe:
dzien_rob : constant array (dzien) of boolean := (true, true, true, true, true, false, false);
jutro : constant array (dzien) od dzien := (wto, sro, czw, pia, sob, nie, pon); -- jutro(d) jest następnym dniem.
9. Zadania:
1) Zadeklaruj tablicę F liczb całkowitych z indeksem od 0 do N. Napisz instrukcje nadające składowym F wartości równe kolejnym liczbom Fibonacciego:
,
,
dla
.
2) Napisz instrukcje dla znalezienia wartości i, j minimalnej składowej tablicy
array(1..N, 1..M) of float;
3) Zadeklaruj tablicę dni_w_mies podającą liczbę dni w każdym miesiącu. Użyj jej, by rozwiązać pierwsze zadanie z poprzedniego wykładu (zob. też pierwsze zadanie z II serii z poprzedniego wykładu).
4) Zadeklaruj tablicę wczoraj podobną do jutro.
5) Zadeklaruj stałą tablicę lub tak, żeby lub(P,Q)=P or Q.
6) Zadeklaruj stałą macierz jednostkową UNIT rzędu 3 (1 na przekątnej, 0 poza).
10. Typy tablicowe
type wektor_6 is array (1..6) of float;
a,b:wektor_6;
Teraz można legalnie napisać
b:=a;
co oznacza b(1):=a(1), b(2):=a(2), ... , b(6):=a(6);
Jeśli napiszemy
c,d:array(1..6) of float;
to c i d są różnych typów (anonimowych), więc c:=d jest nielegalne. Typy tablicowe należy deklarować, gdy traktujemy tablice jako obiekty, a nie zestaw składowych. Anonimowe powinny być tablice takie jak jutro czy dzien_rob.
11. Typy nieograniczone
type wektor is array (integer range <>) of float;
subtype wektor_5 is wektor (1..5); -- obiekt jest zawsze ograniczony
v: wektor_5;
lub
v: wektor(1..5); -- 1..5 jest ogranicznikiem indeksu
Uwaga:
type wektor_6 is array(1..6) of float;
jest skrótem ciągu deklaracji
subtype indeks is integer range 1..6;
type anon is array (indeks range <>) of float;
subtype wektor_6 is anon(1..6);
Typ tablicowy jest zawsze nieograniczony, podtyp może być ograniczony, obiekty tablicowe muszą być ograniczone.
Podobnie
type macierz is array(integer range <>,integer range <>) of float;
subtype macierz_3 is macierz(1..3,1..3);
m:macierz_3;
lub
m:macierz(1..3,1..3);
Podtyp nieograniczony:
subtype tab is macierz; -- wszystkie wymiary są ograniczone lub -- wszystkie nieograniczone.
Ograniczenia indeksowe nie muszą być statyczne.
12. Inicjacja tablic stałych nieograniczonego podtypu.
type pow is array (dzien_powsz range <>) of dzien;
nast_dzien_pracy: constant pow := (wto, sro, czw, pia, pon);
Teraz nast_dzien_pracy'first=pon, nast_dzien_pracy'last=pia.
Uwaga: Jeśli zadeklarujemy
unit_2: constatnt macierz((1.0, 0.0),(0.0,1.0));
to nie można napisać unit_2(1,1)=unit_2(2,2)=1.0 itd., bo dolne ograniczenie indeksy unit_2 to -32768. Trzeba więc zadeklarować
type macierz is array(positive range <>,positive range <>) of float;
13. Przesuwanie tablic
Jeśli v: wektor(1..5); w:wektor(0..4);
to v i w są tego samego typu (wektor) i można podstawić
v:=w; -- tzn. v(1):=w(0); ..., v(5):=w(4);
Podobnie można sprawdzać, czy v=w. Oczywiście podstawienie i porównywanie jest niemożliwe, gdy liczba składowych jest różna.
14. Konwersja typów tablicowych.
Jeśli
type wektor is array (integer range <>) of float;
type szereg is array (integer range <>) of float;
v:wektor (1..5);r:szereg(0..4);
to r:=szereg(v) jest legalne.
15. Atrybuty first, last, length, range można stosować do ograniczonych podtypów tablicowych.
16. Zadania:
1) Zadeklaruj typ tablicowy bbb dla tablicy lub
2) Zadeklaruj dwuwymiarowy typ tablicowy odpowiedni dla tablicy działań na wartościach podtypu
subtype pier5 is integer range 0..4;
Zadeklaruj tablice dodawanie i mnożenie modulo 5. Użyj tych tablic dla obliczenia wyrażenia (a+b)*c używając arytmetyki modulo 5 i podstaw wynik pod d, gdzie a, b, c, d muszą być odpowiednio zadeklarowane.
17. Agregaty nazywane
(1=> 0.0, 2=>0.0, 3=>0.0, 4=>0.0, 5=>0.0)
unit_2: constant macierz:=(1=>(1=>1.0, 2=>0.0),
2=>(1=>0.0, 2=>1.0)); -- rozwiązuje trudność
Reguły są bardzo podobne do reguł dla wyborów w instrukcji case, np.
a: array(1..6) of float:=(1..6=>0.0);
dzien_rob: constant rray(dzien) of boolean:=(pon..pia=>true, sob|nie=>false);
Porządek w agregacie nazywanym jest dowolny, np.
(sob|nie=> false, pon..pia)=> true)
Agregaty nie nazywane nazywa się pozycyjnymi. W jednych i drugich może występować others (na końcu i samodzielnie). Notacji pozycyjnej i nazywanej nie można mieszać w ramach jednego agregatu.
18. Kontekst z możliwością przesuwania - podstawienia agregatów i inicjacje macierzy stałych. Są cztery możliwości:
a) agregaty nazywane bez others - nie ma problemów z granicami indeksów.
b) agragaty pozycyjne bez others - nie ma problemu: dolny indeks to S'first, gdzie S jest podtypem indeksu, górny można wyliczyć z liczby składowych (nawet gdy podtyp macierzy jest nieograniczony).
c) agregaty pozycyjne z others są dopuszczalne, gdy podtyp macierzy jest ograniczony, wtedy indeksy dolny i górny są wyznaczone przez podtyp S
d) agregaty nazywane z others są w takich kontekstach niedopuszczalne, chyba że others jest jedynym wyborem.
Przykład:
dzien_rob: constant array(dzien) of bolean:=
(pon..pia=>true,others=>flase); -- nielegalne
Trzeba napisać
dzien_rob: constant array(dzien) of boolean:=
(true, true, true,true, true, others=> false);
lub, używając kwalifikacji, po zadeklarowaniu
type plan_tyg is array(dzien) of boolean;
dzien_rob: constant plan_tyg'(pon..pia=> true, others=> false);
19. Pusty lub dynamiczny zbiór indeksów (lub pojedynczy indeks) są dopuszczalne tylko jako jedyny wybór:
a: array(1..n) of integer:=(1..n=>1/n);
Tutaj n może być ujemne lub zero i wtedy prawa strona w ogóle nie jest obliczana.
20. Mieszanie notacji przy wielu wymiarach
Wartość początkowa dla unit_2
(1=>(1.0, 0.0), 2=>(0.0, 1.0))
lub
((1=>1.0, 2=>0.0), (1=>0.0, 2=>1.0))
lub
(1=>(1=>1.0, 2=>0.0), 2=>(1=>0.0, 2=>1.0))
itd.
21. Nie wolno tego, co deklarujemy, używać w czasie deklaracji:
a: array(1..4) of integer:=(a'range=>0); -- nielegalne
a: array(1..n) of integer:=(others=>0); -- OK
Agregat pozycyjny musi mieś co najmniej dwie pozycje:
a: array(1..1) of integer:=(99); -- nielegalne
a: array(1..1) of integer:=(1=>99); -- legalne
a: array(N..N) of integer := (N=>99); -- legalne
22. Zadania
1) Przepisz deklarację macierzy dni_w_mies używając nazywanego agregatu dla nadania wartości początkowej
2) Zadeklaruj stałą MACIERZ, której obie granice indeksów są 1..N, gdzie N jest dynamiczne, a której składowe są wszystkie zero.
3) Zadeklaruj stałą MACIERZ jak wyżej, ale zrób z niej macierz jednostkową
4) Zadeklaruj stałą dwuwymiarową tablicę, która podaje liczbę atomów każdego rodzaju w cząsteczkach alkoholi (metanol CH3OH, etanol C2H5OH, propanol C3H7OH i butanol C4H9OH). Zadeklaruj odpowiednie typy wyliczeniowe dla atomów i alkoholi.
Wykład nr 5
1. Typ znakowy - typ wyliczeniowy, który wśród literałów posiada lietrały znakowe
type cyfra_rzym is ('I', 'V', 'X', 'L', 'C', 'D', 'M');
cyf:cyfra_rzym:='D';
cyfra_rzym'first='I'
cyfra_rzym'succ('X')='L'
cyfra_rzym'pos('M')=6
(cyf<'L')=FALSE
'X'<'L' jest dwuznaczne:
character'('X')<'L' FALSE -- type character is (nul, ..., '0', '1', ...., 'z', ..., del);
cyfra_rzym('X')<'L' TRUE
2. Typ łańcuchowy STRING jest określony jako
type STRING is array(positive range <>) of character;
s:string(1..7);
g:constant string:="kot"; -- wszystkie składowe agregatu pozycyjnego są znakami
('A','"', 'B')="A""B"
(1=>'A')="A"
(1..0=>'A')=""
"KOT"/="kot"
3. Łańcuchy dla typów znakowych innych niż STRING:
type liczba_rzym is array (positive range <>) of cyfra_rzym;
rok_1984: constant liczba_rzym:="MCMLXXXIV";
cztery: array(1..2) of cyfra_rzym:="IV";
4. Zadania:
1) Zadeklaruj stałą macierz rzym_na_nat, której można używać do zamiany cyfr rzymskich na ich naturalne odpowiedniki (np. 'C' na 100).
2) Mając dany obiekt R typu liczba_rzym napisz instrukcje obliczające odpowiednią całkowitą wartość V. Można przyjąć, że łańcuch R jest poprawnie zbudowany.
5. Tablica tablic. Przy definicji
type wektor_6 is array(1..6) of float;
można określić
type macierz_3_6 is array (1..3) of wektor_6;
ale nie można (tablica z tablic nieograniczonych)
type macierz_3_N is array(1..3) of wektor;
Można
type macierz_N_6 is array (integer range <>) of wektor_6;
Zadeklarujmy
tzt:macierz_3_6; lub tzt:macierz_n_6(1..3);
tdw:macierz(1..3,1..6);
Agregaty dla tzt i tdw są identyczne, dostęp do składowych różny
tzt(i)(j) tdw(i,j)
Tablice z tablic pozwalają na dostęp do poszczególnych rzędów, np.
tzt(2):=(1..6=>0.0); -- nie można tego zrobić z tdw
Można tworzyć wielowymiarowe tablice z tablic lub tablice z wielowymiarowych tablic itp.
6. Tablica łańcuchów - implementacja dwuwymiarowa.
type tabl_lanc is array (positive range <>, positive range <>)
of character;
zagroda: constant tabl_lanc:=("pies", "kura", "koza", "owca", "kuna", "wilk");
zoo: constant tabl_lanc := ("goryl", "wielblad", "delfin",
"slon", "zebra"); -- nielegalne
Trzeba napisać
zoo: constant tabl_lanc := ("goryl ", "wielblad", "delfin ", "slon ", "zebra ");
Nie można wybrać pojedynczego łańcucha! (zagroda(5) jest nielegalne).
7. Tablice łańcuchów jako tablice z tablic
type tabl_lanc_4 is array(positive range <>) of string (1..4);
zagroda: constant tabl_lanc_4:=)"pies", "kura", "koza", "owca", "kuna", "wilk");
Teraz można napisać put(zagroda(5)), które wypisze kuna, ale dalej nie można używać tablic "poszarpanych", takich jak zoo.
8. Warstwy
Przy deklaracji s:string(1..10); można napisać s(3..8). Jeśli
t: constatnt string:=s(3..8);
to t'first=3, t'last=8. Ograniczenia dolne i górne warstwy mogą być dynamiczne.
Warstwa może być zerowa. Jeśli
s(1..4):="BARA"; s(4..7):=s(1..4), to s(1..7)="BARBARA".
Warstwy istnieją tylko dla tablic jednowymiarowych. Przy drugiej implementacji stałej zagroda można napisać
drap:=tabl_lanc_4(1..2):=zagroda(5..6);
Gdyby zagroda była zmienną, a nie stałą, to można by napisać
zagroda(4)(1..2):="ba"; -- zmiana owcy w bacę
9. Zadania
1) Napisz pojedynczą instrukcję podstawienia, aby wymienić pierwszy i drugi rząd w tzt.
2) Zadeklaruj drugą ZAGRODĘ jako zmienną. Następnie zmień "kurę" na "kozę".
3) Załóżmy, że R jest liczbą rzymską. Napisz instrukcje sprawdzające, czy ostatnią cyfrą w przedstawieniu dziesiętnym jest 4 i zamiana jej na 6, jeśli taką jest.
10. Operacje na tablicach jednowymiarowych - operacje logiczne and, or, xor, not mogą być stosowane do tablic o składowych boole'owskich. Dla and, or, xor oba operandy muszą być tego samego typu (ale nie podtypu) i muszą mieć tyle samo składowych (wynik ma indeksy takie jak lewy operand), np.
type szereg_bit is array(positive range <>) of boolean;
a:szereg_bit(1..4);
b:szereg_bit(2..5);
t: constant boolean:=true;
f: constant boolean:=false;
a:=(t,t,f,f);
b:=(t,f,t,f);
a:=a and b; b:=not b; -- a=(T,F,F,F), b=(F,T,F,T)
11. Zbiory - pierwsze podejście
type bazowy is (c,z,n);
type kolor is array(bazowy) of boolean;
k:kolor;
bialy: constant kolor:=(F,F,F);
czerwony: constant kolor:=(T,F,F);
zolty: constant kolor:=(F,T,F);
niebieski: constant kolor:=(F,F,T);
zielony: constant kolor:=(F,T,T);
purpura: constant kolor:=(T,F,T);
pomarancz: constant kolor:=(T,T,F);
czarny: constant kolor:=(T,T,T);
i teraz czerwony or zolty=pomarancz, not czarny = bialy.
Tak więc kolory są podzbiorami zbioru {c,z,n}. or, and, xor to suma, iloczyn i różnica symetryczna zbiorów. Testowanie przynależności R do zbioru uzyskujemy sprawdzając k(R), gdzie R jest c, z lub n. Literały zbiorowe są oznaczane agregatami: (c|z=>T, others=>F).
12. Operatory relacyjne na tablicach jednowymiarowych = i /= są określone dla wszystkich typów;
<, <=, >, >= do jednowymiarowych tablic o dyskretnym typie składowych.
Uporządkowanie jest leksykograficzne:
"kuna"<"kura"
"kret"<"kretowisko"
"AZZ"<"B"
""<"A"
string'("CCL")<"CCXC" TRUE
liczba_rzym'("CCL")<"CCXC" FALSE
rok_1984<"MM" TRUE
(1,2,3)<(2,3)
(sty,sty)<(1=>lut)
13. Konkatenacja tablic jednowymiarowych
"kret"&"owisko" = "kretowisko"
"Ten lancuch nie konczy sie"&
"na jednej linijce"
Jeden z operandów może być typu składowego:
"bok"&'i'="boki", 'o'&"bok"="obok", 'o'&'o'="oo"
Przykład:
"Pierwsza linia"&ASCII.CR&ACSII.LF&"druga linia"
r: liczba_rzym(1..5);
s:string(1..5);
r:="CCL"&"IV"; s:="CCL"&"IV"; -- nie ma dwuznaczności
b: boolean:="CCL"<"IV"; -- jest dwuznaczność
Jeśli lewy operand konkatenacji jest typu składowego, to dolne ograniczenie indeksu jest takie, jak podtypu indeksowego; jeśli nie, to takie, jak lewego operanda.
Zadania:
6) Napisz 8 stałych bialy..czarny typu kolor w porządku rosnącym określonym przez operator < zastosowany do jednowymiarowych tablic.
7) Oblicz:
a) czerwony or zielony b) czarny xor czerwony c) not zielony
8) Pokaż, że not (czarny xor K)=K jest prawdą dla wszystkich wartości K
9) Dlaczego nie można napisać (sty, sty)<(lut)?
10) Ułóż w kolejności rosnącej następujące wartości typu string: "ABC", "123", "abc", "Abc", "abC", "aBc".
11) Mając dane
character; s:string(5..10);
podać dolne ograniczenie dla a)C&s b) s&c.
15. Rekordy - postać najprostsza.
type nazwa_mies is (sty, lut, mar, kwi, maj, cze. lip. sie, wrz, paz, lis, gru);
type data is
record
dzien: integer range 1..31;
mies: nazwa_mies;
rok:integer;
end record;
d:data;
d.dzien:=3;d.mies:=maj;d.rok:=1791;
d:dzien:=(3,maj,1791);
e:dzien;
e:=d; - -- lub
e:=(mies=>maj,dzien=>3,rok=>1791);
W agregatach nazywanych nie można używać zakresów, zaś | i others są dopuszczalne tylko gdy odnoszą się do składowych tego samego typu. Można mieszać notację pozycyjną i nazywaną, ale zaczynać się musi od pozycyjnej i od pewnego miejsca do końca zmienia się na nazywaną:
(3,maj,rok=>1791)
(3,rok=>1791,mies=>maj) ...
16. Wartości domyślne dla typów
type complex is
record
rl,im:real:=0.0;
end record;
Teraz
c1:complex; -- ma wartość (0.0,0.0)
c2:complex:=(1.0,0.0); -- agregat musi być kompletny
Można też używać agregatów (rl | im=> 1.0) lub (others=>1.0).
17. Typ rekordowy zerowy
type dziura is
record
null;
end record;
18. Składowe rekordów muszą być typów ograniczonych (tzn. nie mogą być nieograniczonymi tablicami) i nie anonimowych. Składowe nie mogą być stałymi, ale całe rekordy mogą:
i: constant complex:=(0.0,1.0);
19. Przykład:
type osoba is
record
data_ur:data;
nazw:string(1..20):=(1..20)=>' ');
end record;
jan:osoba;
jan.data_ur:=(19,sie,1937);
jan.nazw(1..3):="JAN";
i wtedy
jan=((19,sie,1937),"JAN ").
ludzie: array (1..N) of osoba;
ludzie(6).data_ur.dzien:=19;
ludzie(8).nazw(3):='H'; ...
20. Uwaga. Wyrażenia w deklaracjach składowych są obliczane podczas opracowywania deklaracji typu rekordowego, poza wartościami domyślnymi, które są obliczane wtedy, gdy deklarowany jest obiekt tego typu i to bez wartości początkowej. Dlatego deklaracja składowej postaci
nazw:string(1..n):=(1..n=>' ');
może prowadzić do constraint_error (lepiej użyć others=>).
21. Zadania.
1) Przepisz rozwiązanie zad. 3 z wykładu 4 używając zmiennej d typu data zamiast trzech indywidualnych zmiennych.
2) Zadeklaruj trzy zmienne C1, C2, C3 typu complex. Napisz instrukcje, które C3 przypiszą (a) sumę, (b) iloczyn liczb C1 i C2.
3) Napisz instrukcje znajdujące indeks pierwszej osoby tablicy ludzie urodzonej nie wcześniej niż 1 stycznia 1950 r.
22. Podsumowanie.
1) Typy tablicowe mogą być anonimowe, rekordowe nie.
2) Agregaty muszą być kompletne.
3) Odróżniaj tablice ograniczone od nieograniczonych (z <>).
4) Notacja pozycyjna i nazywana nie może być pomieszana w tablicach, może w rekordach.
5) Agregat z ohers musi mieć kontekst podający jego ograniczenie.
6) Wybór w agregacie tablicowym może być dynamiczny lub zerowy, jeśli jest to jeden wybór.
7) Atrybuty first, last, length, range stosują się do obiektów tablicowych i do ograniczonych (pod)typów tablic, ale nie do nieograniczonych typów tablicowych.
8) Aby przypisanie tablic było ważne, liczby składowych dla każdego wymiaru muszą się zgadzać, a nie ograniczenia.
9) Wielkość liter jest istotna w literałach znakowych i łańcuchach.
10) Agragat z jedną składową musi użyć notacji nazywanej (dla rablic i rekordów).
11) Składowa rekordu nie może być anonimową tablicą.
12) Domyślne wyrażenie składowej jest obliczane tylko wtedy, gdy deklarowany jest nieinicjowany obiekt.
Wykład 6
1. Funkcje.
Ciało funkcji:
function sqrt(x: float) return float is
r:float;
begin
-- obliczanie wartości sqrt(x) w zmiennej r
return r;
end sqrt;
Funkcja może nie mieć parametrów. Jeśli ma, to poszczególne specyfikacje parametrów są oddzielone średnikami. Na końcu musi być return i nazwa typu (podtypu). W specyfikacjach przy nazwach podtypów nie może być ograniczeń (przy typie wyniku też nie). W ciele musi wystąpić przynajmniej jedna instrukcja return.
2. Deklaracja funkcji
function sqrt(x:float) return float;
3. Parametry są lokalnymi stałymi, np. jeśli liczymy sqrt(t+0.5), to wynik jest taki, jak gdyby x było zadeklarowane tak:
x: constant float:=t+0.5;
4. Przykład - kilka instrukcji return:
function sign (x:integer) return integer is
begin
if x>0 then
return +1;
elsif x<0 then
return -1;
else
return 0;
end if;
end sign;
5. Przykład - funkcja rekursywna
function silnia (n: positive) return positive is
begin
if n=1 then
return 1;
else
return n*silnia(n-1);
end if;
end silnia;
6. Nieograniczone tablice jako parametry - ograniczenia od parametrów aktualnych.
type wektor is array(integer range <>) of float;
function suma (a: wektor) return float is
wynik:float:=0.0;
begin
for i in a'range loop
wynik:=wynik+a(i);
end loop;
return wynik;
end suma;
Można teraz napisać
w:wektor(1..4):=(1.0, 2.0, 3.0, 4.0);
s:float;
...
s:=suma(w); -- s=10.0
7. Więcej niż jeden parametr - przykład:
function il_skal(a,b:wektor) return float is
wynik:float:=0.0;
begin
if a'first/=b'first or a'last/=b'last then
raise constraint_error;
end if;
for i in a'range loop
wynik:=wynik+a(i)*b(i);
end loop;
return wynik;
end il_skal;
8. Wynik jest nieograniczoną tablicą
function odwr(x:wektor) return wektor is
r: wektor(x'range);
begin
for i in x'range loop
r(i):=x(x'first + x'last - i);
end loop;
return r;
end odwr;
9. Zadania: 6.1-6.8 -- ?????????????
10. Operatory - oznaczenie funkcji jest identyfikatorem lub symbolem operatora. Symbol operatora jest to jeden z następującychl iterałów łańcuchowych (opuszczone cudzysłowy): and, or, xor, =, <, <=, >, >=, +, -, &, abs, not, *, /, mod, rem, **. Definiowana funkcja nadaje operatorowi nowe znaczenie:
function "*" (a,b:wektor) return float is
wynik:float :=0.0;
begin
for i in a'range loop
wynik:=wynik+a(i)*b(i);
end loop;
return wynik;
end "*";
i teraz r:=v*w dla v,w:wektor(1..5);
11. Przeciążanie (przeładowywanie): dla operacji standardowych, np.
function "<"(left, right:integer) return boolean;
function "<" (left, right:boolean) return boolean; -- itp.
Uwaga: "/=" nie może byc przeciążone; "=" może, ale tylko w szczególnych przypadkach i wynik musi być typu boolean. Priorytety operatorów i ich binarność (unarność) pozostają bez zmian.
12. Zadania 6.9-6.13 -- ??????????????????
13. Procedury - wywoływane jak instrukcje. Ciała takie, jak ciała funkcji, z następującymi różnicami: zaczynają się od procedure; nazwa musi być identyfikatorem; nie zwracają wartości; parametry trzech rodzajów: in, out, in out (wszystkie funkcyjne in). Domyślnie: in.
in Parametr formalny jest stałą i pozwala tylko na czytanie wartości odpowiedniego parametru aktualnego.
in out Parametr formalny jest zmienną i pozwala na czytanie i aktualizację wartości odpowiedniego parametru aktualnego.
out Parametr formalny jest zmienną i pozwala na aktualizację wartości odpowiedniego parametru aktualnego.
14. Przykłady
procedure dodaj (a,b: in integer; c:out integer ) is
begin
c:=a+b;
end dodaj;
p,q:integer;
...
dodaj(2+p,37,q);
declare
a: constant integer:= 2+p; -- in
b: constant integer := 37; -- in
c: integer;
begin
c:=a+b; q:=c; -- ciało i out
end;
procedure inc (x: in out integer) is
begin
x:=x+1;
end;
i:integer;
...
inc(i);
declare
x: integer:=i;
begin
x:=x+1;
i:=x;
end;
15. Mechanizmy przekazywania parametrów
in: zawsze przez wartość;
in out: przez wartośc i wynik lub (dla typów złożonych) przez referencję;
out: przez wynik (przy czym granice tablicy są czytane) lub przez referencję.
Program, którego wynik zależy od mechanizmu jest błędny.
16. Ograniczenia na parametry. Dla typów skalarnych, jeśli tryb jest in lub in out, to dowolne ograniczenie na parametr formalny musi być spełnione przez aktualny na początku wywołania; jeśli tryb jest in out lub out, to dowolne ograniczenie na zmienną będącą parametrem aktualnym musi być spełnione przez parametr formalny przy powrocie z podprogramu. Jeśli parametr formalny jest ograniczonego typu tablicowego, to ograniczenia parametru formalnego i aktualnego muszą być takie same. Jeśli parametr formalny jest nieograniczonego typu tablicowego, to przyjmuje on ograniczenia od parametru aktualnego (to samo dotyczy wyników funkcji). W szczególności dozwolone są agregaty nazywane z others jako parametry aktualne (i wyniki funkcji).
17. Konwersja typu jako parametr
r:float;
...
inc(integer(r));
Jeśli r miało wartość 2.3, to będzie najpierw zamienione na 2, zwiększone do 3 i zamienione na 3.0
18. Uwaga na parametry typu out. Można je czytać tylko przy powrocie z podprogramu, np.
procedure kwadratowe (a,b,c: in float; pierw_1,pierw_2: out float; ok: out boolean) is
d: constant float:=b**2-4.0*a*c;
begin
if d<0.0 or a=0.0 then
ok:=false;
return;
end if;
pierw_1:=(-b+sqrt(d))/(2.0*a);
pierw_2:=(-b-sqrt(d))/(2.0*a);
ok:=true;
end kwadratowe;
Nie można napisać
ok:=d>=0.0 and a/=0.0;
if not ok then
return;
end if;
...
bo ok nie wolno czytać.
19. Parametry nazywane. Parametry aktualne mogą być podawane pozycyjnie lub nazywane. Obie notacje mogą być mieszane, pozycyjne pierwsze. Każdy parametr musi być podany oddzielnie i nie wolno używać others.
Przykłady:
kwadratowe(a=>l,b=>m,c=>n, pierw_1=>p, pierw_2=>q, ok=>status);
inc(x=>i); -- lub nawet inc(x=>x);
dodaj(c=>q, a=>2+p, b=>37);
Również dla funkcji:
f:=silnia(n=>4);
s:=sqrt(x=>t+0.5);
r:=il_skal(b=>w, a=>v);
Nie można używać notacji nazywanej przy infiksowych operatorach.
20. Parametry domyślne. W deklaracjach funkcji i procedur można stosować parametry domyślne - tylko dla trybu in. Przy wywołaniu, jeśli korzystaliśmy z parametru domyślnego, to następne mogą być tylko nazywane.
Przykład:
type alkohol is (gin, wodka);
type styl is (z_lodem, czyste);
type dodatki is (oliwka, brak);
procedure martini is (baza: alkohol:=gin; jak: styl:=z_lodem; plus: dodatki:=oliwka);
Wywołania:
martini(jak=>czysta);
martini(wodka,plus=>brak);
martini;
martini(gin, czyste);
Parametr domyślny jest obliczany tylko wtedy, gdy jest potrzebny podczas wywołania procedury lub funkcji. Nawet przy specyfikacji
p,q:integer :=e;
może się zdarzyć, że p i q będą miały różne domyślne wartości (np. gdy e jest funkcją taką jak random).
21. Zad. 6.14 - 6.15 --??????????
22. Przeciążanie - może być stosowane do dowolnych podprogramów. Jeśli tylko specyfikacje podprogramów dostatecznie się różnią, to przeciążają się one zamiast przesłaniać. {Przesłonięcie ma miejsce wtedy, gdy kolejność i typy parametrów i (ewentualnie) wyniku są takie same. Nazwy parametrów, ich rodzaj i wartości domyślne nie mają znaczenia. Podprogramy i literały wyliczeniowe mogą się nawzajem przeciążać. W sytuacjach dwuznacznych można używać kwalifikacji lub notacji nazywanej.
23. Notacja prefiksowa. Podobnie jak przy nazywanych blokach i pętlach można stosować notację z kropką:
procedure P is
i: integer:=0;
procedure Q is
k:integer:=i; -- zewnętrzne i
i: integer; -- wewnętrzne i
j: integer; -- tu można napisać j: integer := P.i;
begin
...
end Q;
end P;
Notacja kropkowa może też być stosowana do operatorów pisanych prefiksowo (podobnie jak notacja nazywana).
24. Hierarchia exit, return i goto:
Return kończy wykonanie podprogramu, może być wewnątrz bloku lub pętli, które wtedy też automatycznie kończy.
Exit kończy pętlę, ale nie może wystąpić wewnątrz podprogramu i zakończyć go. Może wystąpić w bloku.
Goto może wyprowadzić z pętli lub bloku, ale nie z podprogramu. Wewnątrz tego samego podprogramu identyfikatory bloków, pętli i etykiet muszą być wszystkie różne.
25. Podsumowanie:
1) Podtypy parametrów i wyniku muszą być dane oznaczeniami typu (bez ograniczeń).
2) Funkcja musi zwracać wynik i nie może dojść do końcowego end, procedura może.
3) Porządek obliczania parametrów jest nieokreślony.
4) Parametr domyślny jest obliczany tylko wtedy, gdy podprogram jest wywołany i odpowiedni parametr aktualny nie jest określony.
5) Parametr formalny i aktualny dla ograniczonego typu tablicowego muszą mieć takie samo ograniczenie.
6) Parametry skalarne są kopiowane. Mechanizm dla tablic i rekordów jest nieokreślony.
7) Specyfikacje parametrów formalnych są rozdzielane średnikami.
Wykład nr 7
1. Inżynieria oprogramowania - terminologia
Abstrakcja - rozważanie istotnych właściwości obiektu, z pominięciem nieistotnych szczegółów.
Ogólność - miara możliwości zastosowania urządzenia do wielu podobnych zadań zamiast kilku specyficznych.
Specyfikacja - dokładny opis istotnych właściwości obiektu. W programowaniu kompletny opis wymaganego zachowania jednostki programowej.
Implementacja - obiekt zbudowany tak, że spełnia specyfikację. W programowaniu - jednostka programu napisana tak, by zachowywała się w wyspecyfikowany sposob. Nie całe zachowanie jest specyfikowane, co daje implementującemu pewną dowolność wyboru zachownia, które jest zależne od implementacji.
2. Przykład - urządzenia nawilżające powietrze:
1) Gotujemy wodę, aby otrzymać parę wodną - źródło wilgoci.
2) Włączamy nawilżacz, który uderzając łopatkami w powierzchnię wody wyrzuca w powietrze drobniutkie kropelki wody.
3) Maszyna Rube Goldberga
a) pociąga się za sznurek
b) hałas z urządzenia B straszy owcę
c) owca skacze do przodu, przewracając stos książek
d) książki spadają na jedną szalkę wagi, powodując jej opuszczenie
e) małpa z drugiej strony wagi jest podrzucana do góry, co pozwala jej włączyć wentylator
f) małpa włącza wentylator, który ochładza sąsiednie pomieszczenie
g) człowiekowi w sąsiednim pomieszczeniu jest zimno, więc zapala ogień
h) ogień podgrzewa kociołek z zupą, produkując przyjemny zapach
i) para z kociołka wypełnia pomieszczenie, uruchamiając spryskiwacz
j) woda ze spryskiwaczy i para z zupy czynią otoczenie nieznośnym dla kanarka, który ucieka z klatki i rozbija szybę świetlika, uwalniając strumień wilgotnego powietrza.
3. Terminologia - cd.
Interfejs - sposób interakcji obiektu z otoczeniem zewnętrznym. W programowaniu - opis sposobu uruchomienia jednostki programowej z jej otoczenia zewnętrznego (np. sposób przekazywania parametrów) razem z listą zależności, jakie istnieją pomiędzy tą jednostką i otoczeniem (np. zmienne globalne używane przez te jednostkę).
Model czarnej skrzynki - widzenie obiektu w terminach jego interfejsu, z całkowitym ignorowaniem wszystkich szczegółów wewnętrznych, tak jakby całe wnętrze było całkowicie ukryte wewnątrz czarnej skrzynki. W programowaniu - widzenie elementu programu w terminach jego interfejsu z ignorowaniem jego wewnętrznych detali (np. zmiennych lokalnych i algorytmu).
sygnał liczba liczba liczba
włączający wilgoć całk. całk. całk.
Uwaga. W modelu czarnej skrzynki specyfikowana jest tylko składnia, np. czarna skrzynka dla funkcji NWD będzie wyglądać tak samo. Semantykę opisują warunki wstępne i końcowe.
4. Warunki wstępne (preconditions) podprogramu i zbiór warunków, które muszą być spełnione, gdy podprogram jest uruchamiany. Jeśli jeden lub więcej z warunków wstępnych nie jest spełniony w chwili uruchomienia podprogramu, może on nie wykonać swojego zadania.
Warunki końcowe (postconditions) podprogramu: zbiór warunków, które będą spełnione, gdy podprogram zakończy działanie, pod warunkiem, że warunki wstępne były spełnione, gdy program był uruchamiany.
function Max (x,y:integer) return integer;
-- pre
--post zwraca większą z liczb x, y.
function nwd (x,y: integer) return integer;
-- pre x,y>0
--post zwraca największy wspólny dzielnik x i y
function max (x,y:integer) return integer is
begin
if x>0 then
return x;
else
return y;
end if;
end max;
function nwd(x,y:integer) return integer is
t: integer;
u: integer:=x;
v: integer:=y;
begin
while u>0 loop
if u<v then
t:=u;
u:=v;
v:=t;
end if;
u:=u-v;
end loop;
return v;
end nwd;
5. Moduł - jednostka programu, na ogół oddzielnie kompilowana, grupująca pokrewne stałe, typy, zmienne i podprogramy tak, że dowolna inna jednostka programu ma dostęp do tych elementów modułu, które są oznaczone jako publiczne, ale tylko wyjściowy moduł ma dostęp do elementów oznaczonych jako prywatne.
Dekompozycja - rozkład na oddzielne części, z których każda jest prostsza niż całość
Przykład. Rozkład na podprogramy i moduły. Rozkład skomplikowanych struktur danych.
Hermetyzacja (ukrywanie informacji, kapsułkowanie): zanurzanie detali programu wewnątrz elementu w taki sposób, że jego stosowanie nie wymaga znajomości tych detali. Najlepiej, gdy szczegóły te są tak ukryte, że nie są w ogóle dostępne (są niewidoczne).
6. Cele inżynierii oprogramowania: budowa składowych oprogramowania dających się wielokrotnie używać (reusability). Powinny one być:
modyfikowalne - gdy wymagane jest ich inne zachowanie lub znaleziono błędy;
wydajne w użyciu zasobów, głównie czasu i pamięci;
niezawodne - powinny się zachowywać w sposób oczekiwany i zachowywać się w rozsądny sposób, gdy występują błędy.
Wykład 8
Ogólna struktura programu
1. Pakiety - implementacja stosu
max: constant := 100;
s: array(1..max) of integer;
top: integer range 0..max;
procedure push (x: integer) is
begin
top:=top+1;
s(top):=x;
end push;
function pop return integer is
begin
top:=top-1;
return s(top+1);
end pop;
Dostęp do pop i push daje automatycznie dostęp do s i top; może je czytać i dowolnie modyfikować.
package STOS is -- specyfikacja (pojedynczego) stosu
procedure push (x: integer);
function pop return integer;
end stos;
package body STOS is
max: constant := 100;
... -- jak wyżej
end pop;
begin
top:=0; -- można też wyżej napisać top:integer range 0..max:=0;
end stos; -- i wtedy begin i inicjacja mogą być opuszczone.
W specyfikacji pakietu - tylko deklaracje i specyfikacje, bez ciał.
2. Użycie pakietu - zadeklarowany w części deklaracyjnej
declare
package STOS is -- specyfikacja
... -- i
end STOS; -- ciało
begin
...
stos.push(m);
...
n:=stos.pop;
...
end;
3. Klauzula use - aby uniknąć notacji kropkowej, wszędzie tam, gdzie pakiet jest widzialny, np.
declare
use stos;
begin
...
push(m);
...
n:=pop;
...
end;
4. Kolejność deklaracji - duże po małych. Po ciałach mogą występować tylko specyfikacje (podprogramów, pakietów), inne ciała i klauzula use.
5. Pakiet bez podprogramów - przykład
package dziennik is
type dzien is (pon, wto, sro, czw, pia, sob, nie);
subtype dzien_rob is dzien range pon..pia;
jutro: constant array(dzien) of dzien:=(wto, sro, czw, pia, sob, nie, pon);
nast_dz_pr: constant array(dzien_rob) of dzien_rob:=
(wto, sro, czw, pia, pon);
end dziennik;
Ciała może nie być.
6. Opracowanie specyfikacji nie może obejmować obliczenia czegoś, co będzie implementowane dopiero w ciele, np.
function a return integer;
i: integer :=a ; -- jest nielegalne - powoduje program_error
7. Zadania 8.1, 8.2 ??????????????????
8. Jednostki biblioteczne - składowe oprogramowania tworzące biblioteki, składające się na ogół z modułów użytkowych. Są one oddzielnie kompilowane. Jednostka biblioteczna to specyfikacja podprogramu lub pakietu. Odpowiednie ciała tworzą jednostki podrzędne. Jeśli podprogram ma tylko ciało, to jest ono traktowane jak jednostka biblioteczna (i podrzędna). Każda skompilowana jednostka trafia do biblioteki programów. Każda używana jednostka biblioteczna musi byc podana w klauzuli with.
package stos is -- jednostka biblioteczna, oddzielnie kompilowana
...
end stos;
package body stos is -- jednostka podrzędna, też osobno kompilowana
...
end stos;
with stos; -- może być dużo nazw pakietów oddzielonych przecinkami lub dużo zdań with;
procedure main is
use stos; -- klauzula use może być umieszczona po klauzuli with.
m,n: integer; -- stosuje się też do ciała
begin
...
push (m);
...
n:=pop;
...
end main; -- procedura bez parametrów
Kolejność kompilacji
spec STOS
MAIN ciało STOS
Różne jednostki biblioteczne muszą mieć różne nazwy, nie mogą byc operatorami.
10. Podjednostki - inny rodzaj jednostek podrzędnych - ciało pakietu lub podprogramu może być "wyjęte"z otaczającej go jednostki bibliotecznej lub podrzędnej i samo oddzielnie skompilowane. Np.
package body stos is
max: constant :=100;
s: array(1..max) of integer;
top: integer range 0..max;
procedure push (x: integer) is separate; -- pieniek ciała
function pop return integer is separate; -- pieniek ciała
begin
top:=0;
end stos;
separate(stos)
procedure push (x: integer) is
begin
top:=top+1;
s(top):=x;
end push;
Uwaga. Po separate trzeba podac pełną ścieżkę podjednostek zaczynając od jednostki bibliotecznej (np. P.Q). Podjednostki nie mogą być operatorami. Podjednostki danej jednostki muszą mieć różne nazwy. Są kompilowane po swoich rodzicach. Mogą mieć dodatkowe klauzule with i use, np.
with X; use X;
separate(P.Q)
...
11. Dwie strategie: z góry w dół (top-down) i z dołu do góry (bottom-up)
Bottom-up: tworzymy pakiety użytkowe przed użyciem ich w głównym programie (budujemy biblioteki procedur i funkcji).
Top-down: Zaczynamy od głównego programu. Procedury i funkcje zastępujemy pieńkami.
12. Zakres działania i widzialność. Rejon deklaracyjny tworzą bloki i podprogramy; również specyfikacja i ciało pakietu tworzą razem jeden rejon. Jeśli deklaracja występuje w widzialnej części pakietu, to jej zakres działania rozciąga się do końca zakresu działania pakietu (np. jeśli ten pakiet jest zadeklarowany w innym pakiecie, to zakres rozciąga się do końca zakresu tamtego pakietu itp). Jeśli deklaracja występuje w ciele pakietu (lub w jego prywatnej części), to zakres rozciąga się do końca ciała pakietu. Jeśli coś jest w zakresie działania bloku lub podprogramu, to jest widzialne, o ile nie zostanie przesłonięte. Można to coś użyć przy pomocy notacji kropkowej. tak samo jest wewnątrz pakietu. Na zewnątrz pakietu, aby coś było widzialne, trzeba podać nazwę pakietu lub użyć klauzuli use. Identyfikator widzialny dzięki use nie może niczego przesłonić, ale może przeciążyć.
13. Przykład:
declare
type R is
record
i: integer;
end record;
type S is
record
i: integer;
end record;
ar:r;
as:s;
i:integer;
begin
...
i:=ar.i+as.i; -- legalne
...
end;
14. Przykład
package P1 is
package P2 is
...
end P2;
...
end P1;
Można napisać (na zewnątrz P1)
use P1; use P2;
lub
use P1,P1.P2;
ale nie
use P1,P2; -- P2 tu jeszcze nie widać
15. Przemianowanie
declare
procedure spush (x: integer) renames stos.push;
function spop return integer renames stos.pop;
begin
...
spush(m);
...
n:=spop;
...
end;
16. Użyteczność przemianowania:
a) uniknięcie dwuznaczności bez konieczności korzystania z notacji kropkowej
b) uniknięcie zbyt częstego używania klauzuli use, przy jednoczesnym ułatwieniu pisania (przemianowanie długiej nazwy z kropkami)
c) przemianownaie funkcji na operator i odwrotnie
d) umożliwienie używania przeciążonych funkcji, które są oddzielnie kompilowane
e) zmiana nazw parametrów, inne wartości domyślne itp.
f) tam, gdzie w Pascalu używa się instrukcji with
ad. a - zob. 15 ????????????
ad. b - v: float renames dane_samolotu.obecna_predkosc;
ad. c - function "*"(x,y: wektor) return float renames il_skal;
ad. d - napisz dwie różne procedury jako jednostki biblioteczne, dołącz je do swojego programu klauzulą with i przemianuj je za pomocą renames tak, by miały tę samą nazwę.
ad. f -
for i in ludzie'range loop
put(ludzie(i).data_ur.dzien); put(":");
put(nazwa_mies'pos(ludzie(i).data_ur.mies)+1);
put(":");
put(ludzie(i).data_ur.rok);
end loop;
for i in ludzie'range
declare
d:data renames ludzie(i).data_ur;
begin
put(d.dzien);put(":");
put(nazwa_mies'pos(d.mies)+1);
put(":");
put(d.rok);
end;
end loop;
17. Co można przemianować: obiekty (zmienne i stałe), składowe obiektów złożonych, warstwy tablic, wyjątki, podprogramy i pakiety.
package S renames STOS; -- Typy za pomocą: subtype S is T;
18. Przemianowanie podprogramu - liczba, typy bazowe i tryby parametrów (i wynik funkcji) muszą być takie same. Nowe ograniczenia na parametry lub wynik nowego podprogramu są ignorowane. Stosują się stare. Wartości domyślne mogą się różnić, tak jak i nazwy parametrów. Można też napisać:
function dziesiec return cyfra_rzym renames 'X';
19. Podsumowanie
1) Zmienne wewnątrz pakietu istnieją pomiędzy wywołaniami podprogramów z tego pakietu.
2) Jednostka biblioteczna musi być kompilowana po innych jednostkach bibliotecznych wymienionych w klauzuli with.
3) Podjednostka musi być kompilowana po swoim rodzicu.
4) Ciało musi być kompilowane po odpowiedniej specyfikacji.
5) Specyfikacja i ciało pakietu tworzą jeden rejon deklaracyjny.
6) Nie definiuj na nowo STANDARD.
7) Przemianowanie nie jest podstawieniem tekstu (makrem).
Wykład 9
Hermetyzacja
1. Pakiet COMPLEX
package liczby_zespolone is
type complex is
record
re, im:float;
end record;
i: constatn complex:=(0.0, 1.0);
function "+" (x: complex) return complex;
function "-" (x: complex) return complex;
function"+" (x,y: complex) return complex;
....
function "/" (x,y: complex) return complex;
end liczby_zesp;
Zamiast c:=c+i; użytkownik może napisać c.im:=c.im+1.0; Ponadto użytkownik musi użyć tej metody, aby skonstruować liczbę zespoloną.
2. Typy prywatne i stałe odłożone
package liczby_zesp is
type complex is private; -- typ prywatny
i: constant complex; -- stała odłożona
function "+"(x: complex) return complex;
...
function "/"(x,y: complex) return complex;
function konstr(r,i: float) return complex;
function rzecz(x: complex) return float;
function uroj (x: complex) return float;
private
type complex is
record
re, im: float;
end record;
i: constant complex := (0.0, 1.0);
end liczby_zesp;
Operacje dopuszczalne poza pakietem: podstawienie, "=" i "/="
Typ prywatny może być dowolny, może nawet być pisany w terminach innego typu prywatnego.
Można łatwo zmienić implementację, np. określić
type complex is array(1..2) of float;
package body liczby_zesp is
-- unarny +, -
function "+"(x,y: complex) return complex is
begin
return (x.re+y.re, x.im+y.im);
end "+"
-- podobnie -, *, /
function konstr (r,i:float) return complex is
begin
return (r,i);
end konstr;
-- podobnie rzecz, uroj
end liczby_zesp;
3. Użycie pakietu liczby_zesp
declare
use liczby_zesp;
c,d: complex;
r,s: float;
begin
c:=konstr(1.5, -6.0);
d:=c+i; -- + zespolony
r:=rzecz(d)+6.0; -- + rzeczywisty
end;
Nie można napisać
c:=2.0*c;
Zamiast tego
c:=konstr(2.0, 0.0)*c;
lub też przeładować (przeciążyć) * tak, by można było mieszać typy.
4. Zmiana implementacji
private
pi: constant :=3.14159_26536; -- tu dowolne deklaracje poza ciałami
type complex is
record
r: float;
teta: float range 0.0..2.0*pi;
end record;
i: constant complex:=(1.0, 0.5*pi);
end liczby_zesp;
5. Użycie typu prywatnego pomiędzy deklaracją i definicją: można deklarować stałe odłożone tego typu, użyć w deklaracjach innych typów (które też są wtedy prywatne) i podtypów oraz w specyfikacjach podprogramów. Nie wolno deklarować zmiennych tego typu. Np.
type tabl_zespo is array (integer range <>) of complex;
c: constant tabl_zesp;
Stałe odłożone nie mogą być typu z jawnie podanym ograniczeniem.
Uwaga. Nie można użyć poza pakietem czegoś, co nie daje gwarancji sukcesu, np. nie można do tablic zespolonych stosować standardowej funkcji "<", bo nie wiadomo, czy typ zespolony jest dyskretny. Warstwy natomiast mogą być stosowane.
6. Typy prywatne limitowane - nie wolno używać przypisania, "=" i "/=" poza pakietem. Można przeciążyć "=" w widzialnej części pakietu, musi dawać wynik typu boolean. "/=" jest wtedy automatycznym zaprzeczeniem "=". W deklaracji obiektu typu limitowanego nie może być nadania wartości początkowej (np. nie można poza tym pakietem definiować stałych). Nie można też inicjować składowych rekordu, które są typu limitowanego. Wolno deklarować podprogramy, których parametry są typu in lub in out, można też dostarczać wartości domyślnych dla parametrów typu in. Parametry typu out nie są dozwolone.
Użycie typu limitowanego daje pełną kontrolę nad jego użyciem.
7. Przykład - stosy.
package STOSY is
type STOS is limited private;
procedure push (s: in out stos; x:; i integer);
procedure pop (s: in out stos; x: out integer);
function "=" (s,t: stos) return boolean;
private
max: constant:=100;
type wektor_calk is array(integer range <>) of integer;
-- trzeba zadeklarować
-- bo s nie może być typu anonimowego
type stos is
record
s: wektor_calk(1..max);
top: integer range 0..max:=0;
end record;
end stosy;
package body stosy is
procedure push (s: in out stos; x: in integer) is
begin
s.top:=s.top+1;
s.s(s.top):=x;
end push;
procedure pop (s: in out stos; x: out integer) is
begin
x:=s.s(s.top);
s.top:=s.top-1;
end pop;
function "=" (s,t:stos) return boolean is
begin
if s.top/=t.top then
return false;
end if;
for i in 1..s.top loop
if s.s(i)/=t.s(i) then
return false;
end if;
end loop;
return true;
end "=";
end stosy;
Uwaga. Gdyby stos był tylko prywatny, to nie można by przedefiniować "=", porównywane byłyby całe rekordy (również składowe nie używane).
8. Użycie pakietu stosy
declare
use stosy;
st: stos;
pusty: stos; -- tu nie można zadeklarować stałej
...
begin
push(st, n);
...
pop(st,n);
...
if st=pusty then -- działa jeśli pusty nie był zmieniany
...
end if;
...
end;
Poza pakietem stosy można określić:
procedure wsk_stosu (s: in out stos; x: in out integer) is
begin
pop(s,x);
push(s,x);
end;
Można określić
type tabl_stosow is array (integer range <>) of stosy;
-- tablica stosów jest też typem limitowanym
function "=" (a,b: tabl_stosow) return boolean;
W ciele pakietu stosy można zdefiniować (zadeklarować w części widzialnej) procedurę podstaw:
procedure podstaw (s: in stos; t: out stos) is
begin
t:=s;
end;
Uwaga: Procedura podstaw jest potrzebna ze względu na brak możliwości użycia := dla typów limitowanych.
9. Podsumowanie
1) Aby standardowa równość miała sens, wartości powinny być w formie kanonicznej.
2) Nielimitowany typ prywatny może być implementowany w terminach innego nielimitowanego typu prywatnego.
3) Limitowany typ prywatny może być implementowany w terminach dowolnego innego typu prywatnego.
4) "/=" nie może być przedefiniowane - wynika z "=".
5) "=" może być przedefiniowane poza pakietem definiującym odpowiedni limitowany typ prywatny.
6) Reguły powyższe stosuje się tranzytywnie do typów złożonych.
Wykład 10
sytuacje wyjątkowe
1. Obsługa wyjątków. Standardowe: constraint_error, numeric_error (używać razem z constraint_error), program_error (błąd w programie niewykrywalny w trakcie kompilacji), storage_error (np. przepełnienie pamięci), tasking_error.
begin
-- ciąg instrukcji
exception
when constraint_error => -- obsługa wyjątku (exception handler)
-- zrób coś
end;
2. Przykład (zły, bo niedziele są zjawiskiem normalnym)
begin
jutro:=dzien'succ(dzisiaj);
exception
when constraint_error=>
jutro:=dzien'first;
end;
lepiej
if dzisiaj=dzien'last then
jutro:=dzien'first;
else
jutro:=dzien'succ(dzisiaj);
end if;
3. Kilka programów obsługi między exception i end
begin
-- ciąg instrukcji
exception
when numeric error | contraint_error =>
put("Wystapil blad zakresu lub numeryczny");
...
when storage_error =>
put("Skonczyla mi sie pamiec");
...
when others =>
put("Cos sie nie udalo");
end;
4. Występowanie obsługi wyjątków: pod koniec bloku, ciała podprogramu i ciała pakietu. Jeśli występuje w ciele funkcji, to powinna zawierać instrukcję return:
function jutro (dzisiaj: dzien) return dzien is
begin
return dzien'succ (dzisiaj);
exception
when constraint_error=>
return dzien'first;
end jutro;
Sterowanie nie może powrócić do jednostki, która spowodowała wyjątek. Goto nie może skakać do obsługi wyjątków ani pomiędzy programami obsługi. Może skakać z obsługi wyjątków na zewnątrz, ale nie do obsługiwanej jednostki.
Handler może zawierać bloki, wywołania podprogramów, instrukcję exit (jeśli blok był w pętli) itp.
Handler na końcu pakietu obsługuje tylko część inicjalizacyjną pakietu, a nie jego podprogramy, które muszą mieć oddzielne handlery.
5. Obsługa wyjątku. Jeśli jednostka nie obsługuje danego wyjątku, to jest on dynamicznie propagowany, tzn. jednostka jest zakończona (zastopowana) i wyjątek jest powodowany w miejscu, z którego jednostka została uruchomiona. W przypadku bloku będzie to jednostka zawierająca ten blok, w przypadku podprogramu - jednostka, która ten podprogram wywołała. Proces ten jest powtarzany, dopóki wyjątek nie zostanie obsłużony lub nie dojdziemy do najwyższego poziomu. Wtedy program główny jest porzucany i środowisko powinno podać odpowiedni komunikat. Jeśli instrukcje w handlerze same powodują wyjątek, to jednostka zostaje zakończona i wyjątek jest propagowany do jednostki wywołującej.
6. Deklarowanie i powodowanie wyjątków
declare
use stos;
begin
...
push(m);
...
n:=pop;
...
exception
when constraint_error=>
-- nieprawidłowa manipulacja stosem
end;
Przy tej metodzie nie ma pewności, czy wyjątek spowodował nadmiar lub niedomiar stosu (top=max lub top=0). Lepiej:
package Stos is
error: exception;
procedure push(x: integer);
function pop return integer;
end Stos;
package body Stos is
max: constant :=100;
s: attay(1..max) of integer;
top:integer range 0..max;
procedure push(x: integer) is
begin
if top=max then
raise error;
end if;
top:=top+1;
s(top):=x;
end push;
function pop return integer is
begin
if top=0 then
raise error;
end if;
top:=top-1;
return s(top+1);
end pop;
begin
top:=0;
end stos;
Użycie
declare
use stos;
begin
...
push(m);
...
n:=pop;
...
exception
when error=>
-- niewłaściwa manipulacja stosem
when others=>
-- coś innego nie poszło
end;
Można też spowodować wyjątek w handlerze. Jeżeli napiszemy tylko raise; to w jednostce wywołującej powodowany jest ten sam wyjątek. Jest to szczególnie użyteczne, gdy nie wiemy, jaki to wyjątek:
when others=>
put("Cos innego nie poszlo");
czyszczenie; -- popraw na miejscu to co się da
raise; -- propaguj dalej
end;
7. Zakres działania wyjątków
Mogą być przemianowane:
pomocy: exception renames bank.alarm;
Nie są obiektami i nie można nimi manipulować.
Wyjątek może być propagowany poza swój zakres działania, np:
declare
procedure P is
x: exception;
begin
raise x;
end P;
begin
P;
exception
when others=>
-- x jest tu obsłużone, mimo że nazwa x nie jest tu widoczna
end;
W rekursywnym podprogramie nie tworzy się przy każdym wywołaniu nowy wyjątek:
procedure f(n: integer) is
x: exception;
begin
if n=0 then
raise x;
else
f(n-1);
end if;
exception
when x=>
put("Mam cie");
raise;
when others=>
null;
end f;
Jeśli wywołany f(4), to komunikat jest wypisany 5 razy.
8. Wyjątki w deklaracjach - od razu propagowane wyżej
9. Podsumowanie
1) Nie używaj wyjątków bez potrzeby.
2) Używaj raczej wyjątków przez siebie zadeklarowanych, a nie standardowych.
3) Staraj się, by handlery właściwie zwracały zasoby.
4) Dopasuj ograniczenia na zmienne indeksowe do odpowiednich macierzy.
5) Pamiętaj o niezainicjowanych zmiennych.
6) Parametry in out i out mogą nie być właściwie uaktualnione, jeśli procedurę kończy obsługa wyjątków.
Wykład 11
typy parametryzowane
1. Przykład. Operacje na macierzach kwadratowych, ślad macierzy (trace)
type macierz is array (integer range <>, integer range <>)
of float;
function trace (m: macierz) return float is
suma: float:=0.0;
begin
if m'first(1)/=m'flirst(2) or m'last(1)/=m'last(2) then
raise nie_kwadrat;
end if;
for i in m'range loop
suma:=suma+m(i,i);
end loop;
return suma;
end trace;
2. Lepiej:
type kwadrat(rzad: positive) is -- rekord o 2 składowych
record
mac: macierz(1..rzad, 1..rzad);
end record;
3. Deklaracja zmiennej:
m: kwadrat(3); -- ograniczenie wyróżnika
lub
m: kwadrat(rzad=>3); -- notacja nazywana
Ograniczenie takie może być dynamiczne, ale nie można go zmieniać
Inicjalizacja:
m: kwadrat(3):=(3, (1..3=>(1..3=>0.0)));
Nie można
m: kwadrat(n):=(m.rzad, (m.mac'range(1)=>(m.mac'range(2)=>0.0)));
bo m nie może być po prawej stronie. Można:
declare m: kwadrat(n);
begin
m:=(m.rzad, (m.mac'range(1)=>(m.mac'range(2)=>0.0)));
4. Podtypy
subtype kwadrat_3 is kwadrat(3);
m: kwadrat_3;
Funkcja ślad:
function trace (m: kwadrat) return float is
suma: float:=0.0;
begin
for i in m.mac'range loop
suma:=suma+m.mac(i,i);
end loop;
return suma;
end trace;
Przekazywanie parametrów jak dla tablic: formalny przejmuje ograniczenie od aktualnego, chyba że oba są ograniczone i wtedy muszą się zgadzać.
5. Przykład
function transp (m: kwadrat) return kwadrat is
r: kwadrat (m: rzad);
begin
for i in 1..m.rzad loop
for j in 1..m.rzad loop
r.mac(i,j):=m.mac(j,i);
end loop;
end loop;
return R;
end transp;
6. Typ prywatny z wyróżnikiem
package stosy is
type stos(max: natural) is limited private;
procedure push (s: in out stos; x: in integer);
procedure pop (s: in out stos; x: out integer);
function "=" (s,t: stos) return boolean;
private
type wektor_calk is array (integer range <>) of integer;
type stos (max: natural) is
record
s: wektor_calk(1..max);
top: integer:=0;
end record;
end;
Użycie:
st: stos(100);
7. Stałe
c: constant stos; -- w części widzialnej pakietu
c: constant stos(3):=(3,(1,2,3),3); -- w części prywatnej ((3) po stos
-- może nie być)
Inaczej:
subtype stos_3 is stos(3); -- w części widzialnej
c: constant stos_3;
c: constant stos_3:=(3,(1,2,3), 3);
8. Wiele wyróżników
type prostokat (rzedy, kolumny: positive) is
record
mac: macierz(1..rzedy, 1..kolumny);
end record;
r: prostokat(2,3);
lub
r: prostokat(rzedy=>2, kolumny=>3);
9. Użycie w rekordzie jako górna lub dolna granica tablicy i do wprowadzenia wariantów - wyróżnik nie może być w wyrażeniu, np.
type tabl_sym (n: positive) is
record
a: wektor (-n..n); -- nielegalne
end record;
10. Wyróżniki domyślne - dla wszystkich lub dla żadnego wyrażenia
type wiel (n: indeks) is -- subtype indeks is integer range 0..max;
record
a: wektor_calk(0..n);
end record;
Niewygodne, bo wielomiany typu wiel ( jeśli ) mają stałą wielkość, a np. odejmowanie dwóch wielomianów z n = 3 może dać wielomian z indeksem n = 2 lub 1
Lepiej:
type wielomian (n: indeks:=0) is
record
a: wektor_calk(0..n);
end record;
...
p,q: wielomian;
P i Q mogą być zmieniane tylko przez przypisanie całego rekordu. Gdyby zadeklarować P: wielomian(5); to P byłoby już zawsze ograniczone przez 5. Wartości początkowe:
p: wielomian:=(3,(5,0,4,2)); --
Można teraz zadeklarować
function "-" (P,Q: wielomian) return wielomian;
Wynik przed zwróceniem powinien być znormalizowany (by ):
function normal (p: wielomian) return wielomian is
rozm: integer:=p.n;
begin
while rozm>0 and p.a(rozm)=0 loop
rozm:=rozm-1;
end loop;
return (rozm, p.a(0..rozm));
end normal;
11. Atrybut constrained stosuje się do zmiennych, których typem jest rekord z wyróżnikiem. Wynik jest false tylko wtedy, gdy wyróżnik ma wartość domyślną i przy deklaracji zmiennej nie było ograniczenia wyróżnika.
12. Łańcuchy zmiennej długości
subtype rozm_str is integer range 0..80;
type v_string (n: rozm_str:=0) is
record
s: string(1..n);
end record;
v: v_string:=(5, "Witam"); -- trzeba podać długość łańcucha
function "+" (s: string) return v_string is
begin
return (s'length, s);
end "+";
type tabl_v_str is array (positive range <>) of string;
zoo: constant tabl_v_str:=(+"goryl", +"wielblad", +"slon", +"krokodyl",...);
13. Rekordy z wariantami
type rodzaj is (kob, mezcz);
type osoba (plec: rodzaj) is
record
data_ur: data;
case plec is
when mezcz =>
z_broda: boolean;
when kob =>
dzieci: integer;
end case;
end record;
jan: osoba (mezcz); -- wyróżnik może być dynamiczny
barbara: osoba(kob);
subtype mezczyzna is osoba (plec=>mezcz);
subtype kobieta is osoba(plec => kob);
jan: mezczyzna;
barbara: kobieta;
jan:=(mezcz, (19, sie, 1937), false);
barbara:=(kob, (13, maj, 1943), 2);
ale nie
s: rodzaj:=kob;
barbara:=(s,(13, maj, 1943), 2); -- s musi być statyczne
type rodzaj is (mezcz, kob, neutr);
type mutant (plec: rodzaj := neutr) is
record
data_ur: data;
case plec is
when mezcz=>
z_broda: boolean;
when kob =>
dzieci: integer;
when neutr =>
null;
end case;
end record;
m: mutant;
...
m: mutant:=(neutr,(1, sty, 1994)); -- m'constrained=false
m: mutant(neutr):=(neutr, (1, sty, 1994)); -- m'constrained=true
Tylko jedna część z wariantami i na końcu!
Jeśli istnienie składowej zależy od wariantu, to nie wolno jej przemianować. Na przykład:
c: integer renames m.dzieci; -- nielegalne przy deklaracji m: mutant
c: integer renames barbara.dzieci; -- legalne, barbara nie może
-- zmienić płci
robert: mezcz renames barbara; -- legalne, ale robert jest kobietą
-- (w męskim przebraniu)
14. Inne użycie wyróżników
type wiel_wym (n,d: indeks:=0) is
record
licz: wielomian(n);
mian:wielomian(d);
end record;
r: wiel_wym(2,3); -- r constrained, licz i mian też
p: wiel_wym:=(2,3,licz=>(2,(-1,0,1)),
(3, (-1,0,0,1))); -- r nie jest constrained
Nie można napisać p.licz=>(1,(1,1)); , ale można
p:=(1,2,licz=>(1,(1,1)), mian=>(2,(1,1,1)));
Można napisać funkcję normalizującą, która usunie współczynniki z licz i mian.
15. Jeszcze inne użycie wyróżników - w wyrażeniach podających wartość początkową dla składowych rekordu. Nie wolno napisać:
type stos(max: natural) is
record
s: wekt_calk(1..max);
top: integer range 0..max:=0; -- nielegalne
end record;
Można w ogóle nie używać wyróżnika, np. możemy chcieć, aby pewne składowe rekordu były widzialne (wyróżniki), a inne nie (pozostałe dla typu prywatnego). Przykład ekstremalny:
type klucz (kod: natural :=0) is limited private;
i w części prywatnej:
type klucz (kod: natural:=0) is
record
null;
end record;
Użytkownik może odczytać wartość klucza, ale nie może jej zmienić.
Wykład 12
wskaźniki i obiekty
1. Typy dostępu - obiekty tych typów są wskaźnikami do innych obiektów, które są alokowane dynamicznie.
type cell; -- komórka
type link is access cell; -- łącze
type cell is
record
wart: integer;
nast: link;
end record;
l: link; -- l ma wartość domyślną null
2. Tworzenie obiektów, do których jest dostęp za pośrednictwem zmiennych dostępu:
l: new cell; -- lub
L
l: new cell'(37, null); -- możliwe też w czasie deklaracji
3. Tworzenie listy łączonej - dodanie elementu na początku
n: link ;
n:=new cell'(10, l) ;
l:=n;
n: link; n:=new cell'(10,l);
-- krócej l:=new cell'(10,l);
l:=n;
Kopiowanie obiektów wskazywanych:
l.wart:=n.wart; -- automatyczna dereferencja
l.nast:=n.nast;
lub
l.all:=n.all;
(tzn. l.wart=l.all.wart)
l=n jeśli wskazują na ten sam obiekt
l.all=n.all jeśli wskazywane obiekty mają tę samą wartość
4. Stałe dostępu
c: constant link:=new cell'(0, null); -- wartość początkowa konieczna
c.all:=l.all; możliwe, ale c:=l; nie
5. Operacje na listach
procedure dodaj_do_listy (lista: in out link; w: in integer) is
begin
lista:=new cell'(w,lista);;
end;
function suma(lista: link) return integer is
l: link:=lista; -- kopia listy, która jest tu stałą;
s: integer := 0; -- parametry dostępu są zawsze kopiowane
begin
while l/=null loop
s:=s+l.wart;
l:=l.nast;
end loop;
return s;
end suma;
6. Drzewa
type node; -- węzeł
type tree is access node;
type node is
record
wart: float;
lewe, prawe: tree;
end record;
7. Sortowanie tablicy za pomocą drzewa
procedure sort (a: in out wektor) is
i: integer;
podst: tree:= null; -- podstawa (korzeń) drzewa
-- null niepotrzebne
procedure wstaw (t: in out tree; w: float) is
begin
if t=null then
t:=new node'(w, null, null);
else
if w<t.wart then
wstaw(t.lewe, w);
else
wstaw (t.prawe, w);
end if;
end if;
end wstaw;
procedure wypisz(t: tree) is
begin
if t/= null then
wypisz(t.lewe);
a(i):=t.wart;
i:=i+1;
wypisz(t.prawe);
end if;
end wypisz;
begin -- ciało sort-u
for j in a'range loop
wstaw (podst, a(j));
end loop;
i:=a'first; -- indeksy są dowolnego typu dyskretnego
wypisz (podst);
end sort;
8. Dostęp do innych typów:
type ref_int is access integer;
r: ref_int:=new integer'(46);
...
r.all:=13;
9. Kolekcja - tworzą ją obiekty wskazywane, jej zakres to zakres odpowiedniego typu dostępu. Pamięć może być odzyskiwana automatycznie (odśmiecacz) lub przez programistę.
a: array(1..10) of link:=(1..10=> new cell); -- 10 wskazywanych obiektów
a,b: link:=new cell; -- 2 wskazywane obiekty
a: link:=new cell;
b: link:=a; -- 1 obiekt
10. Dostęp do podtypów
type ref_pos is access positive;
type ref_pos is access integer range 1..integert'last;
Można teraz podstawiać:
rn: ref_pos:=new positive'(10);
lub
rn: ref_pos:=new integer'(10);
11. Stos jako lista
package stosy is
type stos is limited orivate;
procedure push (s: in out stos; x: in integer);
procedure push (s: in out stos; x: out integer);
private
type cell;
type stos is access cell;
type cell is
record
wart: integer;
nast: stos;
end record;
package body stosy is
procedure push (s: in out stos; x: in integer) is
begin
s:=new cell'(x,s);
end;
procedure pop (s: in out stos; x: out integer) is
begin
x:=s.wart;
s:=s.nast;
end;
end stosy;
s: stos; -- od razu pusty (null), wielkość stosu ograniczona tylko wielkością pamięci
Uwaga. Następna funkcja nie działa (rekursywna definicja)
function "=" (s,t: stos) return boolean is
ss: stos:=s;
tt: stos:=t;
begin
while ss/=null and tt/=null loop -- tu niewłaściwa definicja
ss:=ss.nast;
tt:=tt.nast;
if ss.wart/=tt.wart then
return false;
end if;
end loop;
return ss=tt; -- true jeśli oba null; tu niewłaściwa definicja
end;
Możliwe rozwiązanie:
type cell;
type link is access cell;
type cell is
...
type stos is
record
lista: link;
end record;
Można wskazywać na typ prywatny:
type ref_stos is access stos;
12. Drzewo genealogiczne
type osoba;
type nazw_osoby is access osoba;
type osoba is
record
plec: rodzaj;
data_ur: data;
wspolmal: nazw_osoby;
ojciec: nazw_osoby;
I_dziecko: nazw_osoby;
nast_rodz: nazw_osoby;
end record;
Lepiej: rekord z wyróżnikiem
type osoba (plec: rodzaj);
type nazw_osoby is access osoba;
type osoba (plec: rodzaj) is
record
data_ur: data;
ojciec: nazw_osoby(mezcz); -- constrained
nast_rodz: nazw_osoby; -- not constrained
case plec is
when mezcz =>
zona: nazw_osoby(kob);
when kob =>
maz: nazw_osoby(mezcz);
I_dziecko: nazw_osoby;
end case;
end record;
joasia: nazw_osoby;
joasia:=new osoba'(kob, (29, sie, 1983), jerzy, others=>null);
lub
joasia:=new osoba(kob);-- wyróżnik musi być (brak wartości domyślnej dla wyróżnika)
Można
subtype kobieta is osoba (kob);
joasia:=new kobieta;
Wyróżnik wskazywanego obiektu nie może się zmienić, nawet jeśli ma on wartość domyślną. Można natomiast, nawet przy braku wartości domyślnej, deklarować nieograniczone obiekty typu wskaźnikowego nazw_osoby, które mogą wskazywać na obiekty z różnymi wyróżnikami. Można też tworzyć podtypy:
subtype nazw_mezcz is nazw_osoby(mezcz);
subtype nazw_kob is nazw_osoby (kob);
Małżeństwo
procedure malz (panna: nazw_kob; kawaler: nazw_mezcz) is
begin
if panna.maz/=null or kawaler.zona/=null then
raise bigamia;
end if;
panna.maz:=kawaler;
kawaler.zona:=panna;
end malz;
13. Funkcja zwracająca wskaźnik:
function wspolmal (p: nazw_osoby) return nazw_osoby is
begin
case p.plec is
when mezcz =>
return p.zona;
when kob =>
return p. maz;
end case;
end wspolmal;
Można napisać wspolmal(p).data_ur (w wyrażeniu) lub nawet wspolmal(p).data_ur:=nowa_data; (typu data) możliwe, bo funkcja zwraca wskaźnik; niemożliwe, gdyby zwracała wartość typu osoba. Nie wolno:
wspolmal(p):=q; -- ale wolno
wspolmal(p).all:=q.all;
14. Urodzenie dziecka
function nowe_dziecko(matka: nazw_kob; chl_lub_dz: rodzaj;
urodz: data) return nazw_osoby is
dziecko: nazw_osoby;
begin
if matka.maz=null then
raise pozamal;
end if;
dziecko:=new osoba(chl_lub_dz);
dziecko.data_ur :=urodz;
dziecko.ojciec:=matka.maz;
declare
ost:=nazwa_osoby:=matka.I_dziecko;
begin
if ost=null then
matka.I_dziecko:=dziecko;
else
while ost.nast_rodz/=null loop
ost:=ost.nast_rodz;
end loop;
ost.nast_rodz:=dziecko;
end if;
end;
return dziecko;
end nowe_dziecko;
Teraz
helena:=nazw_osoby:=noew_dziecko(barbara, kob, (28, wrz, 1969));
15. Dostęp do tablic - jak do rekordów z wyróżnikami
type a_string is access string;
function "+" (s: string) return a_string is
begin
return new string'(s);
end "+";
i
type tabl_a_string is array (positive range <>) of a_string;
zoo: constant tabl_a_string:=(+"goryl", +"koczkodan", +"zebra", +"lew", ...);
16. Typy pochodne
type T is new S;
T ma jako zbiór wartości kopię zbioru wartości S; jako zbiór operacji: te same atrybuty, :=, = i /= jak S oraz odziedziczone podprogramy, które stosują się do S. Jeśli przodek T (np. S) jest typem standardowym, to T dziedziczy standardowe podprogramy. Jeśli przodek jest zdefiniowany przez użytkownika, to T dziedziczy te standardy, które stosują się do S.
Jeśli S było zadeklarowane w widzialnej części pakietu, a T poza specyfikacją tego pakietu, to T dziedziczy te wszystkie podprogramy z pakietu, które stosują się do S (tzn. argumenty lub wynik są typu S). Na przykład:
package P is
type integer_a is new integer;
procedure inc(i: in out integer_a);
function "&" (i, j: integer_a) return integer_a;
end;
type integer_b is new integer_a;
-- tu są niejawnie zadeklarowane operacje standardowe (np. "+", "-", "=", "<" itd) oraz inc i &
Jeśli odziedziczony podprogram nam się nie podoba, to możemy go zmienić:
package a is
type integer_x is new integer;
function "abs" (x: integer_x) return integer_x;
end;
type integer_y is new integer_x; -- dziedziczy nową wersję "abs"
Nie wolno tworzyć typów pochodnych wewnątrz tej samej części widzialnej pakietu, ani pochodnych do typów prywatnych przed ich pełną definicją.
17. Konwersja typów pochodnych
type S is new T;
tx: t;
sx: s;
Można tx:=t(sx); lub sx:=s(tx); Nie można sx:=tx; lub sx:=tx; Wystarczy stosować najbardziej zewnętrzną konwersję.
18. Stos jako lista - ulepszony
type cell;
type link is access cell;
type cell is
record
wart: integer;
nast: link;
end record;
type stos is new link;
function "=" (s,t: stos) return boolean is
sl: link:=link(s);
tl: link:=link(t);
begin
-- ćwiczenie
end "=";
19. Użycie: Jeśli chcemy wyrazić typ prywatny jako już istniejący, musimy użyć typu pochodnego. Dla odróżnienia obiektów koncepcyjnie różnych:
type jablka is new integer;
type gruszki is new integer;
licz_jablek: jablka;
licz_gruszek: gruszki;
Można napisać
licz_jablek: licz_jablek+1;
licz_gruszek: licz_gruszek+1;
ale nie można
licz_jablek := licz_gruszek;
Można:
licz_jablek:=jablka(licz_gruszek);
20. Pochodne podtypów:
type forsa is new natural;
(równoważne type anon is new integer;
type forsa is anon range 0..anon'last; )
21. Typy całkowite
type int is range -1E6..+1E6; -- typ pochodny
(równoważne type anon is new integer
long_integer;
subtype int is anon range -1E6..+1E6; )
22. Typy zmiennoprzecinkowe i stałoprzecinkowe
type real is digits 7;
type temperatura is delta 0.1 range 34.0 .. 43.0;
23. Literały liczbowe - dopuszczalne operacje na typach uniwersalnych:
uniw_całk*uniw_rzecz
uniw_rzecz*uniw_całk
uniw_rzecz/uniw_całk
Liczby stałoprzecinkowe można dodawać i odejmować, mnożyć (z obu stron) przez liczby typu integer i dzielić przez liczby typu integer.
Liczby (dowolnych) stałoprzecinkowych typów można przez siebie mnożyć i dzielić, ale wynik musi być zamieniony na konkretny typ stałoprzecinkowy.
24. Podsumowanie
1) Jeśli wyróżnik nie ma wartości domyślnej, to wszystkie obiekty muszą być ograniczone.
2) Wyróżnik obiektu nieograniczonego może być zmieniony tylko przez przypisanie całego rekordu.
3) Wyróżniki mogą być tylko użyte jako granice tablic, do wybrania wariantu lub jako zagnieżdżone wyróżniki i w domyślnych wartościach początkowych dla składowych.
4) Wyróżnik w agregacie i zarządzający wariantami musi być statyczny.
5) Dowolny wariant musi być ostatni na liście składowych.
6) Niekompletna deklaracja może być użyta dla typów dostępu.
7) Zakres obiektu wskazywanego jest taki jak wskaźnika.
8) Jeśli obiekt wskazywany ma wyróżnik, to jest zawsze ograniczony.
9) Funkcje zwracające wskaźniki mogą być używane w nazwach.
10) Wskaźniki mają domyślną wartość początkową null.
11) Alokator w agregacie jest szacowany dla każdej wartości indeksu.
12) Alokator z kompletną wartością początkową używa apostrofu.
13) Używaj typów liczbowych pochodnych dla zwiększenia przenaszalności.
Wykład 13
struktury rodzajowe. Parametryzacja pakietów i podprogramów.
1. Przykład
procedure wym (x, y: in out real) is
t: real;
begin
t:=x; x:=y; y:=t;
end;
Rodzajowa
generic
type elem is private;
procedure wym (x, y: in out elem);
procedure wym (x, y: in out elem)is
t: elem;
begin
t:=x; x:=y; y:=t;
end;
Konkretyzacja:
procedure wym_rzecz is new wym (real);
lub
procedure wym_rzecz is new wym (elem => real);
Można użyć tej samej nazwy: wymien:
procedure wymien is new wym (integer);
procedure wymien is new wym (data); -- przeładowane
2. Pakiet rodzajowy
generic
max: positinve;
type elem is private;
package stos is
procedure push (x: elem);
function pop return elem;
end stos;
package body stos is
s; array (x: max) of elem;
top: integer range 0..max;
-- itd
end stos;
Użycie:
declare
package moj_stos is new stos (100, real);
use moj_stos;
begin
--
push(x);
--
y:=pop;
--
end;
3. Ososbna kompilacja - zarówno jednostki rodzajowe, jak i konkretyzacje mogą byc jednostkami bibliotecznymi. Np. osobno można skompilować (o ile stos jest jednostką biblioteczną):
with stos;
package stos_bool is new stos (200, boolean);
4. Wyjątki - jeśli zadeklarowane w pakiecie rodzajowym, to każda konkretyzacja da nowy wyjątek. Dlatego lepiej jest wyjątki deklarować w osobnych pakietach (zob. we-wy).
5. Parametry formalne jednostek rodzajowych - pomiędzy generic i package (lub procedure, function) - moga być obiekty, typy i podprogramy. Obiekty mogą być w trybie in (wtedy aktualne są kopiowane do formalnych) lub in out (wtedy formalny przemianowuje aktualny i np. ważne są tylko ograniczenia (constraints) aktualnego). Jeśli tryb jest in, to może być wartość domyślna, ale typ obiektu nie może być limitowany. Wewnątrz ciała parametry formalne są dynamiczne, tzn. nie mogą być np. w definicji typu całkowitego, zarządzać wariantami itp.
6. Zagnieżdżanie
generic
type rzecz is private;
procedure cab (a, b, c: in out rzecz);
procedure cab (a, b, c: in out rzecz) is
procedure wymien is new wym (elem => rzecz);
begin
wymien (a,b);
wymien(a,c);
end cab;
7. Typy jako paramtery formalne. Różne parametry formalne oznaczają różne klasy typów:
type T is private; -- T dowolny nielimitowany
type T (...) is private; -- T dowolny z wyróżnikiem (-ami), -- nielimitowany
type T is limited private; -- dowolny podstawienie i "=" nie są -- automatycznie dostępne
type T (...) is limited private;
type T is (<>); -- dowolny typ dyskretny
type T is range <>; -- dowolny typ całkowity
type T is digits <>; -- dowolny zmiennoprzecinkowy
type T is delta <>; -- dowolny stałoprzecinkowy
type T is array (...) of ...; -- tablica
type T is access ...; -- wskaźnik
8. Przykład
generic
type T is (<>);
function nast (x: t) return t;
function nast (x: t) return t is
begin
if x=t'last then
return t'first;
else
return t'succ(x);
end if;
end nast;
function jutro is new nast(dzien);
lub
function nast_dzien_rob is new nast (dzien_rob);
subtype cyfra is integer range 0..9;
function nast_cyfra is new nast (cyfra); -- nast_cyfra(9) = 0
9. Przykład
generic
type real is digits <>;
package zespolone_rodzajowe is
type complex is private;
-- jak poprzedmio
i: constant := (0.0, 1.0); -- 0.0 i 1.0 są uniwers. rzecz.
end;
type moje_rzecz is digits 6;
package moje_zesp is new zespolone_rodzajowe (moje_rzecz);
10. Parametry tablicowe
generic
type indeks is (<>);
type zmiennoprz is digits <>;
type wektor is array (indeks range <>) of zmiennoprz;
function suma (a: wek) return zmiennoprz;
function suma (a: wek) return zmiennoprz is
wynik: zmiennoprz:=0.0;
begin
for i in a'range loop
wynik:=wynik+a(i);
end loop;
return wynik;
end suma;
function suma_wekt is new suma(integer, real, wektor);
11. Zbiory
generic
type bazowy is (<>);
package zbior_z is
type zbior is private;
type lista is array (positinve range <>) of bazowy;
pusty, pelny: constant zbior;
function tworz_zbior (x: lista) return zbior;
function tworz_zbior (x: bazowy) return zbior;
function rozloz (x: zbior)return lista;
function "+" (x,y: zbior) return zbior;
function "*" (x,y: zbior) return zbior;
function "-" (x,y: zbior) return zbior; -- różnica symetryczna
function "<" (x:bazowy;y: zbior) return boolean; --
function "<=" (x,y: zbior) return boolean; --
function rozmiar (x: zbior) return natural;
private
type zbior is array (bazowy) of boolean;
pusty: constant zbior := (zbior'range => false);
pelny: constant zbior :=(zbior'range => true);
end;
12. Podprogramy jako parametry
generic
tye indeks is (<>);
type elem is (<>);
type kolekcja is array (indeks range <>) of elem;
procedure sort (c: in out kolekcja);
Ciało np. takie:
procedure sort (c: in out kolekcja ) is
min: indeks;
t: elem;
begin
for i in c'first .. indeks'pred(c'last) loop
min:=i;
for j in indeks'succ(i) .. c'last loop
if c(j) < c(min) then min:=j; end if;
end loop;
t:=c(i); c(i):=c(min); c(min):=t;
end loop;
end sort;
lepiej:
generic
type indeks is (<>);
type elem is private;
type kolekcja is array (indeks range <>) of elem;
with function "<" (x,y: elem) return boolean;
procedure sort (c: in out kolekcja);
procedure sort_wektor is new sort (integer, real, wektor, "<");
procedure odwr_sort_wektor is new sort (integer, real,
wektor, ">");
W specyfikacji parametrów rodzajowych lepiej napisać
with function "<" (x,y: elem) return boolean is <>:
Można też po is napisać nazwę, która poda domyślną funkcję; może to być atrybyt, inny parametr formalny lub podprogram, którego parametry nie zależą od innych typów formalnych.
Uwaga.
with function "<" (x,y:elem) return boolean is mniej;
nie może byc poprawne, bo specyfikacja mniej musi się zgadzać ze specyfikacją "<", ale typ elem nie jest znany przed konkretyzacją.
Użycie
type tabl_dat is array (positive range <>) of data;
function "<" (x,y: data) return boolean is
begin
if x.rok /= y.rok then
return x.rok < y.rok;
elsif x.mies /= y.mies then
return x.mies < y.mies;
else
return x.dzien < y.dzien;
end if;
end "<";
i teraz "<" nie trzeba podawać przy konkretyzacji.
Przykład:
generic
with function f(x: real) return real;
function calkuj (a,b: real) return real;
function g(t: real) return real is
begin
return exp(t)*sin(t);
end;
function calkuj_g is new calkuj(g);
calkuj_g(0.0, 2.0);
Jednostki rodzajowe
Przykład
generic
max: positive;
type elem is private;
package stos is
procedure push (x: elem);
function pop return elem;
end stos;
--------------------------------
package body stos is
s: array (1..max) of elem;
top: integer range 0..max;
-- itd
end stos;
---------------------------
Użycie
declare
package moj_stos is new stos (100, float);
use moj_stos;
begin
---
push(x);
---
y:=pop;
---
end;
---
package inny_stos is new stos (50, integer);
Uwaga. Podtyp określony - przy deklarowaniu zmiennych nie trzeba go zawężać
nieokreślony - trzeba zawężać (niezawężone tablice i rekordy z dyskryminantami).
Parametry rodzajowe:
1. obiektowe w trybie in lub in out
2. typu
3. podprogramy
4. pakiety
ad. 1 w trybie in - jak stałe
w trybie in out - zmienne przemianowujące parametr aktualny; zawężenia parametru formalnego są nieistotne
Parametry typu
Typ parametru formalnego |
Operacje dozwolone w ciele rodzajowym |
Typ parametru aktualnego |
type typ_dowolny is limited private; |
żadne |
dowolny |
type typ_nielimitowany is private; |
=, /=, := |
dyskretne, rzeczywiste, zawężone tablice, rekordy określone, prywatne |
type typ_ogólny (<>) is private; (lub limited private) |
=, /=, := (lub żadne) |
dyskretne, rzeczywiste, tablicowe, rekordy |
type typ_rekordowy (x:u; y:v; ...) is private; (lub limited private) |
=, /=, := (lub żadne) |
rekordowe z podtypowo zgodnymi wyróżnikami |
type typ_dyskretny is (<>); |
=, /=, :=, >, >=, <, <=, pred, succ, first, last |
dyskretne |
type typ_całkowity is range <>;
|
operacje całkowite |
całkowity |
type typ_zmiennoprz is digits <>; |
operacje rzeczywiste |
zmiennoprzecinkowe |
type typ_stałoprz is delta <>; |
operacje stałoprzecinkowe |
stałoprzecinkowe |
type typ_dziesiętny is delta <> digits <>; |
operacje dziesiętne |
typy dziesiętne |
type typ_modularny is mod <>; |
operacje modularne |
całkowite modularne |
type typ_dostępu is access typ_dowolny; |
=, /=, := |
jakiś typ wskazujący na typ dowolny |
type typ_tablicowy is array (indeks) of typ_dowolny; |
operacje tablicowe |
tablica zawężona o takim samym typie indeksu |
type typ_tablicowy is array (indeks range <>) of typ_dowolny; |
operacje tablicowe |
tablica niezawężona |
Przemianowanie
Przykład
declare
procedure s_push (x: integer) renames stack.push;
function s_pop return integer renames stack.pop;
begin
...
s_push(m);
...
n:=s_pop;
...
end;
Po co? Aby uniknąć dwuznaczności i notacji kropkowej (niepotrzebna klauzula use).
Przykład v: float renames dane_samolotu.biezaca_predkosc;
Po co? Aby utworzyć skrót
Przykład function "*" (x,y: wektor) return float renames il_skal;
lub
function il_skal(x,y: wektor) return float renames "*";
Aby używać jako operatora lub na odwrót.
Przykład package text_io renames ada.text_io;
Dla zgodności z Adą 83
Zgodność profilów podprogramów
Poziom |
Zgodność |
Używane przy |
typów |
typy bazowe |
ukrywanie |
trybów |
+ tryby |
przemianowanie jako specyfikacja podprogramy rodzajowe |
podtypów |
+ podtypy statyczne |
przemianowanie jako ciało dostęp do podprogramu |
pełna |
+ nazwy parametrów + wartości domyślne |
osobna specyfikacja i ciało powtarzane wyróżniki |
Przykład
function dziesiec return cyfra_rzymska renames 'X';
(funkcja może przemianować literał wyliczeniowy)
Przykład (por. z instrukcją with w Pascalu):
for i in ludzie'range loop
put (ludzie(i).dzien_urodz.dzien); put(":");
put (nazwa_mies'pos(ludzie(i).data_urodz.miesiac)+1);
put(":");
Put(ludzie(i).data_urodz,rok);
end loop;
lepiej:
for i in ludzie'range loop
declare
d: data renames ludzie(i).data_urodz;
begin
put (d.dzien); put(":");
put(nazwa_mies'pos(d.mies) + 1);
put(":");
put(d.rok);
end;
end loop;
Przy przemianowaniu nowe zawężenia są ignorowane; używa się zawężeń oryginału.
Przemianowanie można stosować do: obiektów (zmiennych i stałych), składowych obiektów złożonych (z warstwami tablic włącznie), wyjątków, podprogramów i pakietów.
Przykład: E: constant:=ada.numerics.e;
Typom zmieniamy nazwy za pomocą podtypów:
subtype tecza is kolor;
Typy prywatne
Pakiet liczby_zesp, record re, im:float; ...
i: constant zesp:=(0.0, 1.0);
function +, -, +, -, *, /
type zesp is record r: float; teta:float range 0.0..2*Pi; ...
inaczej:
function i return zesp; -- można zmienić i bez konieczności kompilacji specyfikacji
Można deklarować typy i stałe zależnie od typów prywatnych, ale nie zmienne
Pierwotne operacje typu: predefiniowane - podstawienie, predefiniowane =, atrybuty itd.
Dla typu pochodnego - dziedziczone po rodzicu (można przedefiniować)
Dla typu zadeklarowanego w specyfikacji pakietu - operacje z tego pakietu o typie argumentu lub wyniku.
use type liczby_zesp.zesp;
c:= a+b;
c:=liczby_zesp."+"(a,b);
Typy pochodne
type jablka is new integer;
type pomar is new integer;
zamiany typów: Jablka'(6)
Równość
package stacks is
type stack is private;
procedure push s: in out stack; x: in integer);
procedure pop (s: in out stack; x: : out integer);
function "=" (s,t: stack) return boolean;
private
max: constant :=100;
type wektor_calk is array (int range <>) of integer;
type stack is
record
s: wektor_calk (1..max);
top: integer range 0..max:=0;
end record;
end;
package body stosy is
...
function "=" (s,t: stos ) return boolean is
begin
if s.top/= t.top then
return false;
end if;
for i in 1..s.top loop
if s.s(i)/=t.s(i) then
return false;
end if;
end loop;
return true;
end "=";
type t is limited privare; -- nie ma podstawienia, =, /=
Gospodarowanie zasobami:
package menedzer is
type klucz is limited private;
procedure wez_klucz (k: in out klucz);
procedure zwroc_klucz (k: in out klucz);
function wazny (k: klucz) return boolean;
procedure akcja (k: in klucz; ...);
private
max: constant :=100; -- liczba kluczy
subtype kod_klucza is integer range 0..max;
type klucz is
record
kod: kod_klucza:=0;
end record;
end;
package body menedzer is
wolne: array (kod_klucza range 1..kod_klucza'last) of boolean
:= (others => true);
function wazny (k: klucz) return boolean is
begin
return k.kod/=0;
end wazny;
procedure wez_klucz (k: in out klucz) is
begin
if k.klucz=0 then
for i in wolne'range loop
if wolne(i) then
wolne(i):=false;
k.kod:=i;
return;
end if;
end loop;
-- wszystkie klucze w użyciu
end if;
end wez_klucz;
procedure zwroc_klucz (k: in out klucz) is
begin
if k.klucz/= 0 then
wolne(k.kod):=true;
k.kod:=0;
end if;
end zwroc_klucz;
procedure akcja(k: in out klucz; ...) is
begin
if wazne (k) then
...
end akcja;
end menedzer;
declare
use menedzer;
moj_klucz:klucz;
begin
...
wez_klucz(moj_klucz);
...
akcja(moj_klucz, ...);
...
zwroc_klucz(moj_klucz);
...
end;
generic
type object is limited private;
type name is access object;
procedure Ada.Unchecked_Deallocation (X: in out Name);
Przykład:
type wskaznik is access komorka;
procedure zwolnij is new Unchecked_Deallocation (komorka, wskaznik);
lista:wskaznik;
...
begin
zwolnij(lista);
pragma Controlled (wskaznik); -- no garbage collector
89
urządzenie nawilżające
funkcja MAX
czarna skrzynka
czarna skrzynka
null
null
37
L
10
N
null
37
L
N
null
null
37
L
10
N
to może być w ciele
zawsze oddzielnie