Typ Wyliczeniowy
Witam, dzisiaj przedstawię Wam typ wyliczeniowy. Chcielibyśmy na przykład mieć jakąś zmienną, która przechowywałaby nam dni tygodnia-przypisywalibyśmy jej wartości od 1 do 7 włącznie. Jednakże nie jest to za dobre rozwiązanie, ponieważ istnieje możliwość przypisanie tej naszej zmiennej wartości mniejszej/większej niż 1-7, a poza tym przypisywanie jedynek, dwójek, trójek itd. jako nazwy miesięcy nie byłoby czytelne. Co zrobić w takim wypadku? Można zdefiniować stałe. Spójrzcie na poniższy przykład:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const Poniedzialek = 0 ; Wtorek = 1 ; Sroda = 2 ; Czwartek = 3 ; Piatek = 4 ; Sobota = 5 ; Niedziela = 6 ; |
Po takiej definicji można się odwoływać do poszczególnych dni tygodnia:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var Dni_tygodnia : Integer ; begin Dni_tygodnia := Niedziela ; if Dni_tygodnia = Niedziela then ShowMessage('Odczep się! Ja mam dzisiaj wolne! ;]' ); end ; |
Jednakże, w ten sposób możemy przypisywać wartości każdej zmiennej typu Integer, poza tym, gdy np. będziemy do jakiejś naszej procedury przesyłali wartości w ten sposób, to nadal istnieje możliwość wyjścia poza zakres ‘tygodnia’. Jest jednak inne wyjście, dużo bardziej wygodne. Popatrzcie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// przykład rozpatrzymy na miesiącach type // deklaracja naszego nowego typu wyliczeniowego TMiesiace = (Styczen , Luty , Marzec , Kwiecien , Maj , Czerwiec , Lipiec , Sierpien , Wrzesien, Pazdziernik, Listopad, Grudzien ) ; TForm1 = class(TForm) private public end ; procedure Jakas_tam_procedura ; var Aktualny_miesiac : Miesiace ; begin Aktualny_miesiac := Luty ; ... { !Błąd! Nie możemy przypisać do naszej zmiennej czegoś, co nie znajduje się w deklaracji typu wyliczeniowego! W tym przypadku nie istnieje element Wtorek } Aktualny_miesiac := Wtorek ; end ; |
Jak widzicie to wszystko jest bardzo proste. Musimy sobie najpierw utworzyć dowolną zmienną (typu TMiesiace) i do niej przypisywać wartości. Zawsze należy pamiętać, że jeżeli będziemy chcieli przypisać naszej zmiennej element, który nie istnieje, kompilator powiadomi nas o błędzie. Chciałbym zaznaczyć, że można deklarować zarówno nowe typy wyliczeniowe jak i zmienne wyliczeniowe. Jaka jest różnica, chyba się domyślacie. Popatrzcie:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// jako nowy typ type TDni = (Poniedzialek, Wtorek) ; // jako zmienna var Dni : (Poniedzialek, Wtorek) ; |
Teraz, aby używać typu wyliczeniowego zadeklarowanego w sekcji type, musimy sobie utworzyć nową zmienną tego typu, ponieważ nie możemy operować bezpośrednio na naszym typie, tak samo, jak na typie podstawowym np. Integer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure Jakas_tam_procka ; var // tworzymy sobie nową zmienną typu Dni Dni_tygodnia : TDni ; begin Dni_tygodnia := Wtorek ; TDni := Wtorek ; // Błąd! Integer := 5 ; // Błąd! end ; |
Natomiast używanie naszego typu wyliczeniowego z sekcji var to nic innego jak używanie zwykłej zmiennej. Nie możemy tworzyć nowych zmiennych o typie Dni, ponieważ taki typ nie istnieje – istnieje tylko zmienna o nazwie Dni, która ma określoną ilość możliwych do jej przypisania elementów :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var Dni : (Poniedzialek, Wtorek) ; Tydzien : Dni ; // Błąd! begin Dni := Poniedzialek ; ... end ; |
Jak widać, różnica pomiędzy typami wyliczeniowymi deklarowanymi w sekcji type i bloku var jest intuicyjna. W type deklarujemy wtedy, kiedy wiemy, że w programie będzie nam potrzebny nowy typ, to znaczy będziemy deklarować kilka/kilkanaście/kilkadziesiąt itd. zmiennych tego naszego nowego typu. W var deklarujemy, kiedy potrzebna nam jest tylko jedna zmienna z listą wyliczeniową.
Trzeba Wam teraz jeszcze wiedzieć, że elementy typów wyliczeniowych są numerowane(jeżeli my sami się o to nie zatroszczymy) od zera, więc Styczen = 0, a Listopad = 10. Jeżeli jednak zapiszemy deklarację w ten sposób:
1 2 3 4 5 |
type Miesiace = (Styczen = 10 , Luty , Marzec = 15 , Kwiecien , Maj , Czerwiec , Lipiec , Sierpien , Wrzesien, Pazdziernik = 100 , Listopad, Grudzien = Pazdziernik + Listopad) ; |
to wartości poszczególnych elementów będą wynosić: Styczen = 10, Luty = 11 , Marzec = 15 , Kwiecien = 16 , … , Wrzesien = 21 , Pazdziernik = 100, Listopad = 101 i Grudzień 201. Dzieje się tak, dlatego, że jeżeli przypiszemy elementowi wcześniejszemu wartość X, to następny element będzie o 1 większy od X.
Aby sprawdzić, jaką wartość ma jeden ze składników typu wyliczeniowego lub jaka jest aktualna wartość jakiegoś obiektu naszego typu możemy się posłużyć funkcją ORD:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
type TDni = (Pon , Wto , Sro , Czw , Piat , Sob , Niedz) ; var Dni : TDni ; begin Dni := Czw ; { sprawdzamy wartość składnika Czw, a potem konwertujemy wynik na łańcuch tekstowy } ShowMessage(IntToStr(Ord(Sob))) ; // wynik = 5 ShowMessage(IntToStr(Ord(Dni))) ; // wynik = 3 end ; |
Lub wykonać rzutowanie:
1 2 3 |
ShowMessage(IntToStr(Integer(Sob))) ; ShowMessage(IntToStr(Integer(Dni))) ; |
Musicie zapamiętać jedną, ważną rzecz: nie wolno przypisywać elementu zmiennej (np. Miesiace) w ten sposób:
1 |
Miesiace := 10 ; |
próba takiego porównania też będzie błędna :
1 |
if Miesiace = 1 then |
Wolno jedynie przypisywać i porównywać wartości w ten sposób:
1 2 3 4 5 |
Miesiace := Czerwiec ; if Miesiac = Czerwiec then |
Można też wykonać rzutowanie:
1 |
Miesiac := TMiesiace(4) ; |
Składniki typu wyliczeniowego można też używać w pętlach:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
type TDni = (Pon , Wto , Sro , Czw , Piat , Sob , Niedz) ; var i : TDni ; begin for i := Pon to Niedzi do Memo1.Lines.Add(IntToStr(Ord(i))) ; end ; |
W Memo1 ujrzymy kolejne indeksy.
Przydać się mogą także funkcje: Pred, zwracająca wartość poprzedniego elementu oraz Succ – zwracająca wartość następnego elementu oraz procedury: Inc zwiększająca i Dec zmniejszająca o podaną wartość(domyślnie o 1):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var Dni : TDni ; begin Dni := Wto ; Dni := Pred(Wto) ; // wynik = Sro Dni := Succ(Wto) ; // wynik = Czw Inc(Dni) ; // wynik = Czw Dec(Dni , 3) ; // wynik = Pon // można też tak: Dni := Pred(Dni) end ; |
Jeszcze dwie uwagi. Jeżeli mamy taki typ wyliczeniowy:
1 |
TDni = (Pon , Wto , Sro , Czw , Piat , Sob , Niedz) ; |
to tak, jakbyśmy mieli tablice [Pon..Niedz] czyli [0..6]. Każdemu z elementów tej tablicy przyporządkowany jest identyfikator: 0-Pon, 1-Wto…6-Niedz. Jeżeli jednak, tak zapiszemy powyższą deklarację:
1 |
TDni = (Pon , Wto = 5 , Sro , Czw = 20 , Piat , Sob , Niedz) ; |
To będziemy mieli tablicę [0..23]. Tylko niektórym jej elementom przyporządkowany jest identyfikator, np. 0-Pon, 5-Wto itd. A co z pozostałymi? Czy nie można ich używać? Można, są to nienazwane elementy, przez które można się odwoływać jedynie za pomocą funkcji Pred, Succ, Inc i Dec, które omówiłem powyżej, lub przez rzutowanie:
1 2 3 4 5 |
Dni : TDni ; Dni := TDni(4) ; |
Poza tym, jeżeli w ten sposób napiszemy deklarację:
1 |
TDni = (Pon , Wto = 1 , Sro = 1 , Czw = 1, Piat = 1 , Sob = 1 , Niedz = 1) ; |
to jaka będzie ilość elementów możliwych do przypisania(o różnych wartościach)? Tylko dwa-0 i 1, tablica będzię dwuelementowa: [0..1], z tym, żę drugiemu elementowi zostanie przyporządkowane aż 6 identyfikatorów.
Typy wyliczeniowe przydają się także podczas wysyłania argumentów do procedury lub funkcji, np.:
1 2 3 4 5 6 |
type TAkcja = (Uruchom , Zatrzymaj , Przewin , Wylacz) ; procedure JakasProcedura(Akcja : TAkcja) ; |
Mamy pewność, że do procedury(lub funkcji) nie zostanie wysłane nic innego, niż znajduje się w zakresie typu wyliczeniowego. W przeciwnym razie kompilator powiadomi nas o błędzie.
W przypadku funkcji możemy też zwracać jej wynik, który będzie mógł przyjąć wartość tylko jednego z elementów typu wyliczeniowego. To tyle na temat typów wyliczeniowych.
Typ okrojony
Typ okrojony pozwala wyeliminować dane spoza zakresu. Na przykład, zamiast używać zmiennej Integer do przechowywania numerów miesięcy, można użyć zmiennej o nazwie np.: Miesiace, która będzie okrojonym zakresem liczb całkowitych:
1 2 3 |
type TMiesiace = 1..12 ; |
Teraz taka instrukcja zostanie uznana za błąd przez kompilator:
1 2 3 4 |
Miesiace : TMiesiace ; Miesiace := 14 ; |
Dodam, że typ, jaki używany jest do reprezentowania numerów przydzielonych miesiącom nazywamy typem bazowym(Integer) dla typu okrojonego. Jeżeli chcielibyśmy, aby typem bazowym był np. typ Char, to moglibyśmy zadeklarować zmienną przechowującą
np. wielkie litery:
1 2 3 |
type TWielkieLitery = ‘A’..’Z’ ; |
Oczywiście można też deklarować typy okrojone na podstawie typów wyliczeniowych:
1 2 3 4 5 6 |
type TDni_tygodnia = (Poniedzialek , Wtorek , Sroda , Czwartek , Piatek , Sobota , Niedziela) ; TDo_szkoly = Poniedzialek..Piatek ; |
Jak widzicie typ okrojony TDo_szkoly zadeklarowano jako podzbiór typu wyliczeniowego TDni_tygodnia. Typów okrojonych można używać jak typów wyliczeniowych (też jako argumenty funkcji itp.).
Koniec. Mam nadzieję, że wszystko jest zrozumiale wyjaśnione. W razie pytań zapraszam na forum, w razie sugestii/zauważonych błędów-piszcie na iskar_pk@wp.pl