Podstawy Programowania
Wykład piąty:
Typy wyliczeniowe, okrojone i zbiory.
1.Typy definiowane przez programistę
Zestaw standardowych typów zmiennych, którym dysponuje język Pascal jest
niezbędny do tworzenia oprogramowania. W niektórych sytuacjach jednakże
może on okazać się niewystarczający lub nieporęczny. Z tego powodu uwzględ-
niono w tym języku możliwość definiowania nowych typów danych. Na poprzed-
nim wykładzie zostały opisane procedur i funkcje, które pozwalają tworzyć
nowe instrukcje języka za pomocą już istniejących1. Podobnie, programista
może konstruować własne typy danych, w oparciu o już istniejące, korzystając
z odpowiednich konstrukcji języka. Stosowanie nowych typów danych, które
tak jak podprogramy posiadają swoje nazwy, zwiększa również czytelność
programów.
2.Typy wyliczeniowe
Załóżmy, że chcemy w pisanym przez nas programie posługiwać się nazwami
dni tygodnia, tak jakby były one elementami typu porządkowego. Możemy to
oczywiście osiągnąć ograniczając się do użycia poznanych wcześniej typów
danych, ale prościej będzie zadeklarować odpowiedni typ wyliczeniowy. Zanim
przejdziemy do opisu rozwiązania takiego problemu przedstawmy trochę teorii
dotyczącej typów wyliczeniowych. Są one typami porządkowymi i zazwyczaj
używa się ich do definiowania zbiorów, które zawierają niewielką liczbę
elementów. Każdy z tych elementów otrzymuje swój numer porządkowy, przy
czym pierwszy element ma numer 0, a ostatni o jeden mniejszy od liczby
elementów w zbiorze. Elementy typu wyliczeniowego (wartości zmiennych)
można ze sobą porównywać, wyznaczać ich następniki i poprzedniki,
odczytywać ich numery porządkowe, ale nie można przeprowadzać na nich
operacji arytmetycznych. Nazwy elementów typu wyliczeniowego, tworzy się tak
samo jak inne identyfikatory. Procedury write i writeln nie potrafią wypisać
wartość zmiennej typu wyliczeniowego na ekran, podobnie read i readln nie
mogą być stosowane dla zmiennych tych typów. Typ wyliczeniowy definiujemy
w części programu lub podprogramu rozpoczynającej się słowem kluczowym
type, według następującego wzoru:
nazwa_typu = (nazwa_elementu_1, nazwa_elementu_2, nazwa_elementu_3);
Wróćmy do naszego zagadnienia i przedstawmy jego rozwiązanie2:
1 Mamy więc ponownie do czynienia z budowaniem abstrakcji, tym razem jednak na poziomie
typów danych, a nie kodu, jak było w przypadku podprogramów.
2 Program inspirowany przykładami z książki A. Marciniaka Borland Pascal 7.0 .
2
program typ_wyliczeniowy;
uses
crt;
type
dni_tygodnia=(poniedzialek,wtorek,sroda,czwartek,piatek,sobota,niedziela);
var
dzien:dni_tygodnia;
function day(dzien:dni_tygodnia):string;
begin
case dzien of
poniedzialek : day:='poniedziałek';
wtorek : day:='wtorek';
sroda : day:='środa';
czwartek : day:='czwartek';
piatek : day:='piątek';
sobota : day:='sobota';
niedziela : day:='niedziela';
end;
end;
begin
clrscr;
for dzien:=poniedzialek to niedziela do
if dzien<=piatek then writeln('Jest ',day(dzien),' - pracujemy.')
else writeln('Jest ',day(dzien),' - odpoczywamy.');
writeln('Poniedziałek ma numer:',ord(poniedzialek):2,', a niedziela',ord(niedziela):2,'.');
writeln('Środę poprzedza ',day(pred(sroda)),', a jej następnikiem jest ',day(succ(sroda)),'.');
readln;
end.
Sekcja type programu zawiera definicję typu o nazwie dni_tygodnia. W tej
części programu można, w razie potrzeby umieścić definicje większej liczby
typów porządkowych. Elementy typu dni_tygodnia są nazwami dni tygodnia
(bez polskich liter). Element poniedzialek , otrzymuje numer porządkowy
0, ponieważ jest pierwszy, element niedziela , ponieważ jest ostatni otrzymuje
3
numer 6. Elementów jest razem siedem. W części var programu deklarujemy
zmienną dzien typu dni_tygodnia. Kompilator nie pozwoli na przypisanie tej
zmiennej wartości, która nie jest elementem tego typu wyliczeniowego3. Funk-
cja day pozwala zamienić identyfikator elementu typu wyliczeniowego na ła-
ńcuch znaków będący nazwą odpowiedniego dnia tygodnia. Jest to konieczne,
jeśli chcemy wypisać nazwę dnia na ekran. Poprzez parametr o nazwie dzien4
przekazujemy tej funkcji wartość typu wyliczeniowego, którą chcemy skonwer-
tować, a ona zwraca nam odpowiadający jej łańcuch znaków. Do konwersji
w ciele funkcji używana jest instrukcja case, gdyż typ typy wyliczeniowe są
jednocześnie typami porządkowymi. W bloku głównym programu wykonujemy
na zmiennej globalnej dzien kilka przykładowych operacji. Zmienna ta jest
używana jako licznik pętli for (spełnia wymogi, jest zmienną typu
porządkowego). Można również budować wyrażenia relacyjne zawierające taką
zmienną, jak zostało to zrobione w instrukcji if. Funkcja predefiniowana ord
zwraca numer porządkowy, jaki został przyporządkowany elementowi
należącemu do typu wyliczeniowego. Z kolei funkcje pred i succ zwracają
odpowiednio poprzednik i następnik elementu, który zostanie przekazany im
przez parametr. Należy pamiętać o tym, że nie wolno zastosować funkcji pred
do pierwszego elementu typu wyliczeniowego, a funkcji succ do ostatniego
elementu typu wyliczeniowego. Jeśli spróbujemy coś takiego zrobić, kompilator
zgłosi nam błąd czasu kompilacji. Zmienne typu wyliczeniowego można
deklarować w inny niż podano wcześniej sposób, definiując typ wyliczeniowy
w miejscu deklaracji zmiennej, w sekcji var, np.:
dzien:(poniedzialek, wtorek, sroda, czwartek, piatek, sobota, niedziela);
Tego rodzaju typ wyliczeniowy możemy nazwać typem anonimowym (nie po-
dajemy jego nazwy). Należy pamiętać, że powyższa definicja typu ma poważną
wadę. Nie jest możliwe zadeklarowanie w ten sposób parametrów formalnych
podprogramu.
3.Typy okrojone
Typów okrojonych używamy wtedy, gdy chcemy zawęzić zakres przyjmowanych
przez zmienną wartości do podzbioru wartości określonego typu porządkowego.
Tak jak pozostałe typy zmiennych definiujemy je w sekcji type programu lub
podprogramu. Wzorzec typu okrojonego jest następujący:
nazwa_typu_okrojonego = wartość_1..wartość_2;
3 Typy wyliczeniowe są interpretowane wyłącznie przez kompilator.
4 Możemy nazwać go również inaczej.
4
gdzie wartość_1 i wartość_2 są wartościami określonego typu porządkowego
(nazywanego tu typem bazowym) lub wyrażeniami zbudowanymi ze stałych.
Wartości tych wyrażeń muszą należeć do typu bazowego. Pierwsza wartość jest
nazywana ograniczeniem dolnym, a druga górnym. Typ okrojony jest poprawnie
zdefiniowany, jeśli zachodzi relacja wartość_1<=wartość_2. Jako przykład
przedstawmy definicję typu okrojonego pozwalającego przechowywać
tylko liczby naturalne od 0 do 5:
przedzial = 0..5;
Zamiast liczb możemy również przechowywać ściśle określone litery:
litery = 'a'..'m';
Typem bazowym dla naszego typu okrojonego może być również zdefiniowany
przez nas typ wyliczeniowy5, np.:
dni_robocze = poniedzialek..piatek;
W definicji typu możemy posłużyć się stałymi, które zostały wcześniej określo-
ne. Tych stałych możemy też użyć w wyrażeniach określających zakres górny
i dolny typu okrojonego6. Wyrażenia, w których oprócz operatorów występują
jedynie stałe będziemy nazywać wyrażeniami stałymi, ponieważ kompilator
może określić ich wartość na etapie kompilacji programu. Przykład:
const
a = 10;
b = 20;
type
przedzial_1 = a..b;
przedzial_2 = 2*(a-b)..2*(a+b);
Wyrażenia stałe nie mogą rozpoczynać się od nawiasu okrągłego, czyli nie mogą
być zapisane np. tak: (a-b)*2. Poniżej znajduje się kod zródłowy prostego
programu, w którym zdefiniowano typ okrojony. Program ten ujawnia również
istotny szczegół, o którym należy pamiętać posługując się w języku Pascal
typami okrojonymi:
5 Przykład pochodzi z książki A.Marciniaka Borland Pascal 7.0 (został trochę zmodyfikowany).
6 Przykład pochodzi z książki A.Marciniaka Borland Pascal 7.0 (został trochę zmodyfikowany).
5
program typy_okrojone;
uses
crt;
type
przedzial = 0..5;
var
liczba:przedzial;
begin
clrscr;
liczba:=5;
{liczba:=10;}
writeln('Wartość zmiennej liczba:',liczba:3,'.');
liczba:=liczba*2;
writeln('Wartość zmiennej liczba:',liczba:3,'.');
{$R+}
{liczba:=liczba*2;
writeln('Wartość zmiennej liczba:',liczba:3,'.');}
readln;
end.
Z kodu zródłowego programu wynika, że kompilator nie pozwoli przypisać
zmiennej liczba wartości, która znajduje się poza zakresem jej typu, zgłaszając
błąd. Jednakże podczas wykonania programu nie jest już sprawdzany zakres
wartości przypisywanych do zmiennej, dlatego po wykonaniu pierwszej instruk-
cji liczba:=2*liczba; ta zmienna będzie miała wartość 10. Wynika to ze sposobu
w jaki typy okrojone są reprezentowane w pamięci komputera. Ponieważ nie da
się przydzielić tyle miejsca w pamięci komputera, żeby mieściły się w nim tylko
wartości od 0 do 5, więc kompilator przydziela na zmienną typu okrojonego
1 bajt (lub jego wielokrotność, jeśli istnieje taka konieczność). Możemy jednak
wymusić sprawdzenie zakresu wartości przypisywanej zmiennej uaktywniając
dyrektywę sprawdzania zakresu typu zmiennej (ang. range check). Należy
w tym celu umieścić w kodzie zródłowym programu zapis7: {$R+}. Jeśli
usuniemy komentarze znajdujące się za rozkazem włączającym dyrektywę
7 Jeśli chcemy, aby ta dyrektywa obejmowała wszystkie wiersze kodu, to możemy włączyć ją
przed nagłówkiem programu (przed słowem kluczowym program), lub możemy wyświetlić
wszystkie przełączniki dyrektyw naciskając dwukrotnie klawisz O, jednocześnie trzymając wci-
śnięty klawisz Ctrl, a następnie odszukać przełącznik dyrektywy R i zmienić jego stan z - na
+ . Uwaga ta dotyczy wyłącznie środowiska Turbo Pascal.
6
sprawdzania zakresu, to druga instrukcja liczba:=liczba*2; spowoduje awaryjne
zakończenie wykonania programu, co niekoniecznie musi być pożądanym
efektem8. Podsumowując, typy okrojone zwiększają czytelność kodu zródłowego
programu, ale nie należy na nich polegać jeśli chcemy zagwarantować
określony zakres wartości zmiennej. Należy raczej posłużyć się odpowiednią
instrukcją warunkową. Typy okrojone przy włączonej dyrektywie sprawdzania
zakresu zmiennej mogą być przydatne w fazie testów programu. Podobnie jak
w przypadku typów wyliczeniowych, możemy tworzyć anonimowe typy
okrojone, definiując je bezpośrednio, w miejscu deklaracji zmiennej, np.:
liczba:0..5;
Podobnie jak w poprzednim przypadku uniemożliwia nam to deklarowanie
parametrów formalnych o takim typie.
4.Zbiory
Typy zbiorowe, stanowią reprezentację zbiorów matematycznych w języku Pas-
cal. Ponieważ zmienne tych typów, mogą jednocześnie przechowywać kilka war-
tości (danych), to typy zbiorowe są pierwszymi przedstawicielami struktur
danych, z jakimi się zapoznamy. Zbiór (jako typ) jest zbiorem wszystkich pod-
zbiorów określonego typu porządkowego, zwanego dalej typem bazowym. Do
tych podzbiorów zaliczamy również zbiór pusty. Liczba elementów typu bazo-
wego nie może przekraczać 256, a zakres wartości musi się mieścić między
0 i 255. Z typów prostych, typami bazowymi mogą być: char, byte i boolean.
Typem bazowym może być również zdefiniowany przez nas typ wyliczeniowy,
lub typ okrojony, o ile spełniają wymaganie co do liczby elementów i zakresu
ich wartości. Typ zbiorowy definiujemy w sekcji type programu lub pod-
programu według następującego wzorca:
nazwa_typu_zbiorowego = set of nazwa_typu_porządkowego;
Przykłady:
zbior = set of char;
zbior_2 = set of 0..5;
zbior_3 = set of przedzial;
8 Kompilator po napotkaniu rozkazu włączającego dyrektywę badania zakresu zmiennej, niejaw-
nie umieszcza w kodzie wynikowym programu odpowiednie rozkazy maszynowe, które dokonują
sprawdzenia przypisywanej wartości. Rozkazy te przerywają wykonanie programu, wypisując na
ekran komunikat o błędzie, jeśli przypisywana wartość nie należy do określonego przedziału.
Niestety, język Pascal nie jest wyposażony w odpowiednie konstrukcje, pozwalające na obsługę
sytuacji wyjątkowych, w jakie jest wyposażona znakomita większość języków obiektowych.
7
Elementy umieszczamy w zbiorze reprezentowanym przez zmienną typu
zbiorowego za pomocą instrukcji przypisania. Po jej prawej stronie powinien się
znalezć ujęty w nawiasy kwadratowe element, który chcemy do zbioru zapisać.
Jeśli tych elementów ma być więcej, to też umieszczamy je w nawiasach kwa-
dratowych, ale rozdzielamy przecinkami. Zmiennej typu zbiorowego możemy
przypisać również zbiór pusty, umieszczając po prawej stronie instrukcji przy-
pisania puste nawiasy kwadratowe. Istnieje specjalny operator relacyjny (ma
taki sam priorytet, jak pozostałe operatory tego typu) pozwalający sprawdzić,
czy dany element znajduje się w zbiorze reprezentowanym przez zmienną typu
zbiorowego. Tym operatorem jest in . Operatory arytmetyczne dla zmiennych
typów zbiorowych zachowują się jak operatory teoriomnogościowe, tzn. +
zwraca zbiór zawierający wszystkie elementy należące do obu zbiorów, które są
jego argumentami, operator - zwraca zbiór elementów, które należą do zbioru
będącego pierwszym argumentem operatora, ale nie należące do zbioru będące-
go drugim argumentem. Operator * zwraca zbiór zawierający elementy będące
wspólnymi dla obu zbiorów. Operator / nie jest dla zbiorów zdefiniowany i nie
można go z nimi użyć. Również operatory relacyjne działają trochę inaczej jeśli
używane są ze zbiorami. Operator = zwraca wartość true, jeśli oba porówny-
wane zbiory mają taką samą liczbę elementów i dodatkowo w obu zbiorach są
te same elementy, <> jeśli zbiory zawierają różne elementy i/lub różnią się ich
liczbą. Operator <= zwraca wartość true, jeśli każdy element zbioru będącego
jego pierwszym argumentem jest także elementem zbioru będącego jego drugim
argumentem. W drugą stronę ta relacja nie musi być prawdziwa. Operator >=
ma odwrotne działanie. Pozostałe operatory relacyjne nie są dla zbiorów
zdefiniowane. Wartości zmiennej typu zbiorowego, podobnie jak zmiennej typu
wyliczeniowego nie możemy wprost wypisać na ekran za pomocą write i writeln.
W języku Pascal istnieją dwie specjalne procedury, służące do obsługi zbiorów.
Pierwszą z nich jest include, która dołącza do zbioru przekazanego jej przez
pierwszy parametr wywołania element przekazany jej przez drugi parametr.
Procedura exclude działa odwrotnie, usuwa element ze zbioru. Ponieważ zbiory
są stosunkowo dużymi zmiennymi, to powinniśmy przekazywać je do procedur
i funkcji przez zmienną lub stałą, nigdy przez wartość. Możemy również
definiować anonimowe typy zbiorowe, w miejscu deklaracji zmiennej:
cyfry : set of byte;
To rozwiązanie jest obarczone tą samą wadą, co w opisanych wcześniej typach.
Istnieje także możliwość zadeklarowania zmiennej zainicjalizowanej typu
zbiorowego. Deklarację takiej zmiennej należy umieścić w sekcji const
programu lub podprogramu (nie zaś w sekcji var). Ogólna postać takiej dekla-
racji jest następująca:
8
nazwa_zbioru : typ_zbioru = [element1, element2, element3];
Oto krótki program pokazujący w jaki sposób możemy posługiwać się zbiorami:
{$A+,B-,D+,E+,F-,G-,I+,L-,N+,O-,P-,Q-,R-,S+,T-,V+,X+,Y+}
{$M 16384,0,655360}
program zbiory;
uses crt;
type
zbior = set of char;
var
a,b,c:zbior;
function elementy(const x:zbior):string;
var
i:char;
wynik:string;
begin
wynik:='[';
for i:=#0 to #255 do
if i in x then wynik:=wynik+''''+i+''''+', ';
wynik:=wynik+']';
elementy:=wynik;
end;
begin
clrscr;
a:=['a','e','i','o','u'];
writeln('Zbiór a: ',elementy(a));
b:=['a','c','e','i','z'];
writeln('Zbiór b: ',elementy(b));
c:=a+b;
writeln('Suma zbiorów a i b: ',elementy(c));
c:=a-b;
writeln('Różnica zbiorów a i b: ',elementy(c));
9
c:=a*b;
writeln('Iloczyn zbiorów a i b: ',elementy(c));
{Porównujemy dwa zbiory.}
if c = ['a','e','i'] then writeln('Oba zbiory są takie same.');
include(c,'b');
if 'b' in c then writeln('Litera b występuje w zbiorze c.')
else writeln('Litera b nie występuje w zbiorze c.');
exclude(c,'b');
if 'b' in c then writeln('Litera b występuje w zbiorze c.')
else writeln('Litera b nie występuje w zbiorze c.');
readln;
end.
W programie definiujemy typ zbiorowy, którego elementami są znaki,
a następnie deklarujemy trzy zmienne tego typu. Funkcja elementy sprawdza,
które ze znaków należą do zbioru i umieszcza je (wraz z apostrofami, spacją
i przecinkiem) w łańcuchu znaków9. Taki łańcuch będzie pózniej wyświetlany
na ekranie, co pozwala poznać zawartość zbioru. W programie głównym zmien-
nym a i b przypisujemy pewne zbiory wartości, wypisujemy ich zawartość na
ekran, a następnie do zmiennej c przypisujemy kolejno wyniki sumy, różnicy
i iloczynu zmiennych a i b, za każdym razem wypisując je na ekran.
W programie zademonstrowano również użycie procedur include i exclude,
najpierw umieszczając w zbiorze znak 'b', a następnie go usuwając. Do
stwierdzenia, czy 'b' znajduje się w zbiorze, czy też nie posłużył operator in.
Z kolei operator = pozwolił określić, czy porównywane ze sobą zbiory są takie
same.
Uwagi: W środowisku Free Pascal dyrektywy kompilatora są inne, więc przed
kompilacją tego programu, należy usunąć dwa pierwsze wiersze. Ponadto kom-
pilator Free Pascal posiada nie dodatkowy operator dla zbiorów. Jest to różnica
symetryczna oznaczana w języku przez ><. Jest to operator dwuargumentowy,
który zwraca zbiór zawierający te elementy, które nie są wspólne dla zbiorów
będących jego argumentami, np. ['a', 'b', 'c', 'd'] ><['d','e','f'] zwróci zbiór
['a','b','c','e','f'].
9 Za pomocą operatora + można ze sobą łączyć łańcuchy. Aby w łańcuchu umieścić znak
apostrofu, należy użyć czterech apostrofów następujących po sobie, tj. ''''
10
Wyszukiwarka
Podobne podstrony:
PP1 wykladPP1 wyklad 8PP1 wyklad 3PP1 wyklad 1PP1 wyklad 2PP1 wykladPP1 wyklad 4PP1 wyklad 5PP1 wyklad 9PP1 wykladPP1 wyklad 4PP1 wykladSieci komputerowe wyklady dr FurtakWykład 05 Opadanie i fluidyzacjaWYKŁAD 1 Wprowadzenie do biotechnologii farmaceutycznejwięcej podobnych podstron