Rekordy z wyróżnikami
Rekordy z wariantami to takie rekordy, których zawartość, to znaczy ilość i rodzaj składowych, różni się w zależności od wartości jednego z pól - wyróżnika. Wyobraźmy sobie na przykład, że chcemy utworzyć bazę danych przechowującą imiona, nazwiska i daty urodzenia osób, a w przypadku, gdy dana osoba posiada prawo jazdy - również jego kategorie. Można to zrobić tworząc dwa odrębne typy rekordowe (osoby z prawem jazdy i bez niego), ale wówczas podczas pisania programów wykorzystujących takie typy napotykamy na wiele istotnych ograniczeń. Nie można umieścić wszystkich osób w jednej tablicy, zapisać do jednego pliku itp... Problem ten może rozwiązać zadeklarowanie typu rekordowego w następujący sposób:
type kategoria is (A, B, C, D, E, T);
type tablica_kategorii is array (kategoria) of boolean;
type osoba (kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
Typ rekordowy osoba składa się z wyróżnika (ang. discriminant), traktowanego jako normalne pole rekordu, oraz dwóch części - stałej (ang. invariant part), tzn. pól imię, nazwisko i rok_urodzenia, które występują we wszystkich rekordach tego typu, oraz części wariantowa (ang. variant part), mającej postać znanej nam instrukcji wyboru - case. W zależności od wartości wyróżnika - pola kierowca - rekord zawiera pole o nazwie prawo_jazdy lub nie zawiera nic więcej. W definicji typu rekordowego część wariantowa znajdować się musi po części stałej. Jej tworzeniem rządzą zasady podobne, jak dla instrukcji case - musimy wyczerpać wszystkie możliwe wartości wyróżnika, w przypadku gdy któraś z tych wartości nie powoduje dodania do rekordu nowych składowych używamy słowa kluczowego null. Jeżeli dla kilku wartości dodajemy takie same pola, możemy napisać
when wartość1 | wartość2 | ... => pole : typ_pola;
when wartość1 .. wartość2 => pole : typ_pola;
a także
when others => pole : typ_pola;
czy
when others => null;
"When others" musi być ostatnią pozycją w instrukcji wyboru.
Nie można deklarować pól o takich samych nazwach w różnych miejscach instrukcji wyboru. Poniższa deklaracja
type dni_tyg is (pon, wt, sw, czw, pt, sob, nie);
type jakis_typ (k:dni_tyg) is record
pole1,pole2:integer;
case k is
when sob|nie => pole3 : float;
when pon => pole3 : float;
pole4 : integer;
when others => null;
end case;
end record;
jest nieprawidłowa (pole o nazwie pole3 występuje w dwóch miejscach części wariantowej).
Zmienne należące do zdefiniowanego wcześciej typu osoba deklarujemy określając wartość wyróżnika:
sasiadka : osoba (false); -- notacja pozycyjna
sasiad : osoba (kierowca => true); -- notacja nazywana
Dany rekord (czyli dana zmienna czy stała) zostaje w ten sposób niejako "ograniczona" (ang. constrained) - wyróżnik zachowuje się tutaj jak stała; nie można już zmienic jego wartości, a zatem zmienna sasiadka pozostanie zawsze rekordem złożonym z czterech pól, a sasiad - z pięciu. Powyższy sposób deklaracji przypomina nieco deklarowanie zmiennych należących do anonimowego typu tablicowego. Podobnie jak tam zmienna, dla której określamy wartość wyróżnika, zostaje zaliczona automatycznie do jednego z podtypów typu osoba (tutaj ma on dwa podtypy , dla wartości wyróżnika kierowca odpowiednio true lub false). Oczywiście - znów podobnie jak w przypadku tablic - możemy określić te podtypy w sposób bezpośredni, pisząc:
subtype osoba_bez_prawa_jazdy is osoba(kierowca=>false);
subtype osoba_z_prawem_jazdy is osoba(kierowca=>true);
sasiadka: osoba_bez_prawa_jazdy;
sasiad: osoba_z_prawem_jazdy;
Nadawanie wartości poszczególnym składowym rekordu przebiega w standardowy sposob:
sasiadka.imie:="Kazimiera "
sasiadka.nazwisko:="Kowalska ";
lub przy użyciu agregatu rekordu z notacją pozycyjną czy nazywaną (jak dla rekordów bez wariantów).
sasiadka:=(false, "Kazimiera ", "Kowalska ", 1955);
sasiad:=(kierowca=>true, imie=>"Jan ", nazwisko=>"Kowalski ",
rok_urodzenia=>1954,prawo_jazdy=>(true,true,others=>false));
Zauważmy, że mimo określenia wartości wyróżnika w momencie deklaracji występuje on jeszcze raz w agregacie rekordu (oczywiście jego wartość w agregacie i deklaracji musi być taka sama, w przeciwnym razie wystąpi Constraint_Error). Agregat rekordu może być również użyty podczas deklaracji zmiennej, powodując "określenie" rekordu:
sasiadka : osoba := (false, "Kazimiera ", "Kowalska ", 1955);
W ten sposób również otrzymujemy rekord o stałej wartości wyróżnika.
Wyróżnik rekordu traktowany jest jak zwykłe pole (tutaj - o stałej wartości) i możemy się do niego odwoływać:
if sasiad.kierowca=true then ...
Próby odwołania się do nieistniejących pól rekordu (np. sasiadka.prawo_jazdy) powodują wystąpienie błędu Constraint_Error.
Gdyby powyższy sposób korzystania z rekordów z wyróżnikami był jedynym możliwym, byłoby to bardzo niewygodne. Sąsiadka skazana byłaby na życie bez prawa jazdy - a co miałby zrobić posiadacz bazy danych, gdyby udało jej się uzyskać ten dokument?... Możliwe jest więc deklarowanie rekordów, których wyróżniki mogą zmieniać swoją wartość. Są to rekordy tzw. "nie określone" (ang. unconstrained) - deklarowane bez ustalania wartości wyróżnika. Jest to możliwe dzięki nadaniu wyróżnikowi wartości domyślnej podczas definiowania typu rekordowego (wiadomo wówczas, ile pamięci zarezerwować):
type osoba (kierowca:boolean:=false) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
Jeśli zadeklarujemy teraz zmienną pisząc
teściowa : osoba;
otrzymamy rekord "nie określony", dla którego została przyjęta domyślna wartość pola kierowca - false. Zawartość tego pola możemy jednak wielokrotnie zmieniać, używając agregatu rekordu (użycie tylko pojedynczego pola - wyróżnika - jest nieprawidłowe):
-- prawidłowy sposób zmiany:
tesciowa:=(kierowca=>true, imie=>"Kleopatra ",
nazwisko=> "Kowalska ", rok_urodzenia=> 1930,
prawo_jazdy=>(C|D|T=>true, others=>false));
-- nieprawidłowo:
tesciowa.kierowca :=true;
tesciowa.imie:="Kleopatra ";
Oczywiście można takiemu rekordowi nadac wartość początkową już w chwili deklaracji
ciotka_sasiada : osoba := (kierowca=>true, imie=>"Eleonora ",
nazwisko=>"Kowalska ", rok_urodzenia=>1950,
prawo_jazdy=>(B=>true,others=>false));
a mimo to pozostaje on rekordem "nie określonym", z możliwością zmiany wartości wyróżnika:
ciotka_sasiada := (kierowca=>false, imie=>ciotka_sasiada.imie,
nazwisko=>ciotka_sasiada.nazwisko,
rok_urodzenia=>ciotka_sasiada.rok_urodzenia);
Możliwe jest też nadawanie wartości (i ewentualna zmiana typu wyrożnika) poprzez podstawianie całych rekordów:
narzeczony_Jasi : osoba := (true, "Jan ", "Iksinski ",
(A|B=>true,others=>false));
narzeczony_Kasi : osoba; -- domyślnie - nie ma prawa jazdy
-- ale że wszystko się zmienia...
narzeczony_Kasi := narzeczony_Jasi;
-- nastąpiła nie tylko zmiana narzeczonego,
-- ale i wyróżnika rekordu...
Posiadanie przez wyróżnik wartości domyślnej nie oznacza, że wszystkie deklarowane zmienne tego typu będą "nie określone". Zawsze możliwe jest ustalenie obowiązującej dla danej zmiennej wartości wyróżnika i "ograniczenie" rekordu, podobnie jak w przypadku typów rekordowych nie mających wartości domyślnej wyróżnika:
niemowlak : osoba (kierowca => false);
Reguły te mogą wydawać się trudne do zapamiętania. Istnieje na szczęście atrybut 'Constrained (sposób użycia: zmienna'Constrained) przyjmujący wartość true, gdy dany rekord jest "określony" (constrained - nie można zmienić wartości jego wyróżnika), a false - w przeciwnym przypadku.
if not ktos'constrained then
ktos:=(kierowca=>...,...);
end if;
-- zmiana wartości wyróżnika tylko wtedy, gdy jest to możliwe,
-- mimo ew. ostrzeżeń w czasie kompilacji
-- nie będzie błędu wykonania
Rekordy należące do tego samego typu (niezależnie od tego, czy są ograniczone, czy nie) możemy porównywać ze sobą przy pomocy operatorów = i /= (nawet jeśli mają różne wartości wyróżnika).
Możliwe jest definiowanie typów rekordowych posiadających więcej niż jeden wyróżnik. Rekord może posiadać jednak tylko jedną część wariantową, umieszczoną na końcu struktury rekordu. Kolejne konstrukcje case mogą jedynie być w niej zagnieżdżone. Przy ich tworzeniu obowiązują dotychczasowe zasady:
type kategoria is (A, B, C, D, E, T);
type tablica_kategorii is array (kategoria) of boolean;
type p is (K, M);
-- nieprawidłowa definicja
-- dwie części wariantowe
type osoba1 (plec:p; kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case plec is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
when M =>null;
end case;
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
-- nieprawidłowa definicja
-- powtarzające się nazwy pól
type osoba2 (plec:p; kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case plec is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
when M =>
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end case;
end record;
-- definicja prawidlowa
type osoba2 (plec:p; kierowca:boolean) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case plec is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
when M =>
case kierowca is
when true => pj: tablica_kategorii;
when false => null;
end case;
end case;
end record;
Jak widać w sytuacji, gdy wyróżniki są od siebie niezależne, trudno jest w elegancki sposób zdefiniować typ rekordowy - musimy powtarzać pole, a to wymaga użycia w każdym przypadku instrukcji case innej nazwy. Może pomóc tutaj deklaracja dwustopniowa (jak poniżej, zobacz "Inne zastosowania wyróżników").
type os (pl: p) is record
imie, nazwisko: string (1..10):=(others=>' ');
rok_urodzenia: positive;
case pl is
when K => nazwisko_panienskie: string(1..10):=(others=>' ');
when M =>null;
end case;
end record;
type osoba1 (plec:p; kierowca:boolean) is record
dane : os(plec);
case kierowca is
when true => prawo_jazdy: tablica_kategorii;
when false => null;
end case;
end record;
Gdy wartości wyróżnika nie są od siebie zależne, definicja wygląda dużo ładniej:
type rodzaj_psa is (obronny, pasterski, mysliwski, pokojowy);
type pies (rasowy:boolean; medalista:boolean) is record
imie:string(1..10);
wlasciciel:string(1..20);
case rasowy is
when true => rodzaj: rodzaj_psa;
rasa: string(1..10);
case medalista is
when true => ilosc_medali:positive;
when false => null;
end case;
when false=> null;
end case;
end record;
Agregaty rekordów należących do typu pies mogą mieć jedną z czterech postaci:
pies1 : pies := (rasowy=>false, medalista=>false,
imie=>"Azor ", wlasciciel=>"Jan Kowalski ");
pies2 : pies := (rasowy=>false, medalista=>true,
imie=>"Burek ", wlasciciel=>"Jan Kowalski ");
pies3 : pies := (rasowy=>true, medalista=>false,
imie=>"Cezar ", wlasciciel=>"Jan Kowalski ",
rodzaj=>mysliwski, rasa=>"foksterier");
pies4 : pies:= (rasowy=>true, medalista=>true,
imie=>"Funia ", wlasciciel=>"Jan Kowalski ",
rodzaj=>pokojowy, rasa=>"pekinczyk ",
ilosc_medali=>10);
Podobnie jak przedtem, także i w przypadku rekordów o kilku wyróżnikach możemy deklarować rekordy "określone" i "nie określone", jeżeli wyróżnikowi została nadana wartość domyślna. Jeśli wartość domyślną posiada jeden z wyróżników, muszą ją mieć również pozostałe.
Rekordy ze składowymi o zmiennym rozmiarze
Innym sposobem wykorzystania wyróżników rekordu jest określanie za ich pomocą rozmiaru składowych, na przykład długości stringa czy rozmiaru tablicy, jak w poniższym przykładzie:
type dane (dl_nazwy:positive) is record
zaklad_pracy:string(1..dl_nazwy);
rok_zalozenia: positive;
end record;
type osoba is record
imie, nazwisko:string(1..10);
rok_ur:positive;
end record;
type tabela_pracownikow is array (positive range <>) of osoba;
type firma (il_pracownikow:positive) is record
nazwa_firmy : string(1..20);
zatrudnieni : tabela_pracownikow (1..il_pracownikow);
end record;
W obu przypadkach jedno z pól rekordu należy do typu tablicowego o nie określonym rozmiarze (string jest, jak pamiętamy, zdefiniowany jako taka tablica o elementach typu character). Jeżeli wyróżnik nie ma nadanej wartości domyślnej, musimy podać ją w chwili dekaracji zmiennej.
zaklad1 : dane(dl_nazwy=>50);
zaklad2 : dane(20);
zaklad3 : dane := (dl_nazwy=>13, zaklad_pracy=>"WKS Mostostal",
rok_zalozenia=> 1974);
Jak pamiętamy, zmienna należąca do typu tablicowego o nie określonym rozmiarze musi być elementem jakiegoś jego podtypu, określonego przez rozmiar. Zatem zmienne zadeklarowane powyżej są "określone" i nie mogą zmieniać wartości wyróżnika, a więc i rozmiaru swoich składowych. Jeśli natomiast wyróżnik ma wartość domyślną:
type dane (dl_nazwy:positive:=20) is record
zaklad_pracy:string(1..dl_nazwy);
rok_zalozenia: positive;
end record;
to zmienne deklarujemy bądź podobnie jak poprzednio
zaklad1 : dane(dl_nazwy=>50);
zaklad2 : dane(20);
- wówczas są one rekordami "określonymi", a długość nazwy określiliśmy na zawsze (podobnie jak w przypadku rekordów z wariantami), bądź też
zaklad3 : dane; -- domyślna wartość wyróżnika - 20
zaklad4 : dane:= (dl_nazwy=>13, zaklad_pracy=>"WKS Mostostal",
rok_zalozenia=> 1974);
otrzymując rekord "nie określony":
zaklad3 := zaklad4; -- zmiana długości z 10 na 13
Oczywiście wyróżników może być więcej niż jeden. Mogą odnosić się one do tego samego lub do różnych pól rekordu:
type tabela_pracownikow is array (positive range <>) of osoba;
type firma (dl_nazwy,il_pracownikow:positive) is record
nazwa_firmy : string(1..dl_nazwy);
zatrudnieni : tabela_pracownikow (1..il_pracownikow);
end record;
f1 : firma(dl_nazwy=>3, il_pracownikow=>10);
f2 : firma(12,5);
Zdefiniowaliśmy tu dwa typy - tablicowy o nie określonym rozmiarze (tabela_pracownikow) i rekordowy. Niestety nie można utworzyć tylko jednego typu, pisząc
type firma2 (dl_nazwy,il_pracownikow:positive) is record
nazwa_firmy : string(1..dl_nazwy);
zatrudnieni : array (1..il_pracownikow) of osoba;
end record;
ponieważ żadne z pól rekordu nie może być zadeklarowane jako tablica anonimowa.
Wyróżniki rekordu nie mogą występować w deklaracji typu jako elementy jakiegoś wyrażenia. Napisanie na przykład
type firma (dl_nazwy,il_pracownikow:positive) is record
...
zatrudnieni : tabela_pracownikow1 (1..il_pracownikow+1);
end record;
jest niedozwolone.
Inne zastosowania wyróżników.
Poznaliśmy dotychczas następujące zastosowania wyróżników rekordów:
- do tworzenia rekordów z wariantami (wyróżnik określa wówczas zawartość rekordu),
- do tworzenia rekordów, których pola mają rozmiar zależny od wartości wyróżnika.
Istnieją jeszcze trzy inne zastosowania :
- tworzenie rekordów, w których pewne pola zachowują się jak stałe,
- inicjowanie wartości pewnych pól rekordu,
- "określanie" rekordu z wyróżnikiem zagnieżdżonego w danym rekordzie jako jedno z jego pól.
Oto przykłady:
Inicjowanie wartości pola rekordu:
type ograniczenie (predkosc:natural) is record
max_predkosc : natural := predkosc;
end record;
obszar_zabudowany : ograniczenie(60);
...
put (obszar_zabudowany.max_predkosc); -- wypisze 60
obszar_zabudowany.max_predkosc:=50;
put (obszar_zabudowany.max_predkosc); -- wypisze 50
Jak widać, wartość wyróżnika można tutaj zmieniać (nawet jeśli zadeklarowaliśmy rekord jako "określony").
Tworzenie w rekordzie pól zachowujących się jak stałe:
type plec is (M, K);
-- plec i rok urodzenia nie moga sie zmienic, -- waga i wzrost - tak
type czlowiek (p: plec;rok_urodzenia:positive) is record
waga, wzrost: positive;
end record;
Iksinski:czlowiek(p=>M, rok_urodzenia=>1966);
... -- niedozwolone
Iksinski:=(p=>M, rok_urodzenia=>1965, wzrost=>187, waga=>99);
Wyróżnik używany do "określenia" rekordu będącego polem definiowanego rekordu:
type tabela_pracownikow is array (positive range <>) of osoba;
type firma (il_pracownikow:positive) is record
nazwa_firmy : string(1..20);
zatrudnieni : tabela_pracownikow (1..il_pracownikow);
end record;
type pracodawca (ilosc_zatrudnionych:natural) is record
imie,nazwisko : string(1..10);
jego_firma : firma(ilosc_zatrudnionych);
end record;
********************************************************
Algorytmy 1
Pierwsze programy 2
Trochę matematyki 8
Funkcje matematyczne 14
Typy liczbowe i ich zakresy 18
Instrukcja warunkowa 21
Operatory logiczne. Kolejność działań. 24
Typ boolean 26
Typy znakowe 27
Typ wyliczeniowy 30
Podtypy 32
Definiowanie nowych typów liczbowych 36
Atrybuty typów 38
Instrukcja wyboru 44
Instrukcje pętli 48
Typ łańcuchowy 58
Tablice 65
Tablice jednowymiarowe 65
Tablice wielowymiarowe 74
Tablice anonimowe (anonimowy typ tablicowy) 78
Tablice dynamiczne 79
Typy tablicowe bez okeślenia rozmiaru 81
Operacje na tablicach 85
Rekordy 87
Rekordy "zwykłe" (bez wyróżników) 87
Struktury danych "wielokrotnie złożone" 93
Rekordy z wyróżnikami 100
Rekordy z wariantami 100
Rekordy ze składowymi o zmiennym rozmiarze 107
Inne zastosowania wyróżników. 109
|