• Nie Znaleziono Wyników

Wstęp do Programowania Obiektowego

N/A
N/A
Protected

Academic year: 2021

Share "Wstęp do Programowania Obiektowego"

Copied!
45
0
0

Pełen tekst

(1)

Wstęp do Programowania Obiektowego

Wykład 12

(2)

ZAKRES WIDOCZNOŚCI

(3)

Zakres widoczności zmiennej to zbiór tych instrukcji programu, w

których zmienna ta jest widoczna, tzn.

można się do niej odwołać.

zmienna jest lokalna (w jednostce programu lub w bloku), jeśli jest

zadeklarowana w tej jednostce.

zmienna jest nielokalna , jeśli jest

widoczna, ale zadeklarowana gdzie

(4)

Zakres statyczny

Zakres statyczny opiera się na kodzie programu (w sensie

tekstowym). Jest powszechnie stosowany.

 Istnieją dwie kategorie języków z zakresem statycznym:

◦ dopuszczające zagnieżdżanie

podprogramów (np. Ada, Pascal)

◦ nie dopuszczające takiego zagnieżdżania

(np. C), za wyjątkiem definicji klas

(5)

Odwoływanie się do zmiennych w językach z zakresem statycznym

Napotkawszy odwołanie do zmiennej, kompilator musi odnaleźć jej deklarację i określić atrybuty.

Deklaracji szuka się najpierw w bieżącej jednostce programu.

Jeśli tu jej nie ma, trzeba szukać w jednostce

„okalającej”, zwanej poprzednikiem statycznym.

Jeśli i tu jej nie ma, trzeba szukać w poprzedniku poprzednika itd. (czyli w przodkach statycznych), być może docierając aż do zakresu globalnego.

Zmienne mogą się przesłaniać. Jeśli w

bliższym przodku statycznym jest zadeklarowana

(6)

Dostęp do przesłoniętych zmiennych

Język może oferować mechanizmy pozwalające na dostęp do

przesłoniętych zmiennych.

 W języku C++ jest to operator ::

 W Adzie podobną rolę spełnia kropka,

np. nazwa_jednostki.nazwa_zmiennej.

(7)

Problemy z zakresem statycznym

Zbyt swobodny dostęp do zmiennych lub podprogramów.

Wszystkie zmienne w głównym programie (tzn. w zakresie globalnym) są widoczne dla wszystkich procedur

(podprogramów).

Nie ma sposobu, by narzucić szczegółowe ograniczenia w dostępie do

podprogramów.

To prowadzi do nadużywania globalnych

(8)

Przykład zakresu statycznego

P0 {

P1 { P3 { }

P4 { }

}

P2 { P5 { }

}

}

(9)

Zakres dynamiczny

 Zakres dynamiczny opiera się na

kolejności wywołań podprogramów, a nie na ich rozmieszczeniu

„przestrzennym”.

(10)

Odwoływanie się do zmiennych w

językach z zakresem dynamicznym

Napotkawszy odwołanie do zmiennej, kompilator musi odnaleźć jej deklarację i określić jej atrybuty.

Deklaracji szuka się najpierw w bieżącym

podprogramie (podobnie jak dla zakresu statycznego).

Jeśli tu jej nie ma, trzeba szukać w podprogramie, który wywołał bieżący podprogram, zwanym

poprzednikiem dynamicznym.

Jeśli i tu jej nie ma, trzeba szukać w poprzedniku poprzednika itd. (czyli w przodkach dynamicznych), być może docierając aż do zakresu globalnego, czyli do programu głównego.

Zmienne mogą się przesłaniać. Jeśli w bliższym

przodku dynamicznym jest zadeklarowana zmienna o takiej samej nazwie, jak w dalszym przodku, to

przesłania ona tę dalszą.

(11)

Przykład zakresu dynamicznego

P0() {

int x;

P1() { int x;

P2();

}

P2() {

Put(x);

}

P1();

(12)

Cechy zakresu dynamicznego

Zalety:

w wielu sytuacjach wygoda programisty

prosta implementacja.

Wady:

Gorsza efektywność, gdyż rozstrzyganie zakresu musi być robione dynamicznie.

Nie da się statycznie sprawdzić zgodności typów dla zmiennych nielokalnych.

Słaba czytelność odwołań.

Podprogramy są wykonywane w środowisku

wcześniej wywołanych podprogramów, które

jeszcze nie zakończyły działania.

(13)

Środowisko odwołań

Zbiór wszystkich nazw widocznych w danym punkcie programu nazywamy środowiskiem odwołań tego punktu.

W języku z zakresami statycznymi środowisko odwołań tworzą wszystkie zmienne zadeklarowane lokalnie wraz ze zmiennymi z przodków statycznych, z wyjątkiem zmiennych przesłoniętych.

Mówimy, że podprogram jest aktywny, jeśli jego wykonanie się rozpoczęło i jeszcze nie zakończyło.

W języku z zakresami dynamicznymi środowisko odwołań tworzą wszystkie zmienne zadeklarowane lokalnie wraz ze zmiennymi z aktywnych

podprogramów, z wyjątkiem zmiennych przesłoniętych.

(14)

Stałe nazwane

Stała nazwana to zmienna, która jest wiązana z wartością tylko raz — w

chwili wiązania z pamięcią.

 Przykład: Można by używać stałej o nazwie pi zamiast 3.1415926...

Stałe poprawiają czytelność i ułatwiają modyfikowanie

programu.

(15)

TYPY

(16)

Definicja typu

Typ to pewien ustalony zbiór

wartości oraz związany z nim zbiór operacji , które można wykonywać na wartościach z tego typu.

 Dozwolone operacje to wszystkie operatory (w szerokim rozumieniu, czyli również podprogramy,

podstawienia itp.), których dziedziną

jest ów typ lub typ z nim zgodny,

(17)

Dozwolone operacje w szerszym ujęciu to m.in.

 podstawienie pod zmienną,

 przekazanie jako parametr,

 sprawdzanie równości (czy x = y?),

 sprawdzanie relacji porządku (czy x <

y?),

 wybór elementu składowego tablicy

(18)

Typ pierwotny to taki, którego w

danym języku nie da się zdefiniować za pomocą innych typów.

 Większość języków posiada pewien zestaw typów pierwotnych, np. char, int, float.

 Z typów pierwotnych można tworzyć

typy złożone, np. rekordy, tablice.

(19)

Cele istnienia typów

Na poziomie maszynowym wszelkie dane

zapisane są jako układy bitów, niezależnie od tego, co reprezentują. Typy są sposobem na nadanie znaczenia tym „anonimowym”

układom bitów.

Dzięki temu zyskujemy możliwość

wykrywania wielu powszechnych błędów (sprawdzanie zgodności typów).

Optymalizacja kodu przez kompilator, który np. zna zakresy liczb i może wybrać bardziej efektywną reprezentację.

Typy abstrakcyjne są sposobem na

(20)

Typ abstrakcyjny to konstrukcja języka programowania, w której definiujemy typ (w dotychczasowym rozumieniu) oraz

operacje na nim w taki sposób, że inne byty w programie nie mogą manipulować danymi inaczej niż za pomocą

zdefiniowanych przez nas operacji.

 Istotą rzeczy jest tu oddzielenie części

„prywatnej” typu (czyli szczegółów reprezentacji danych i implementacji poszczególnych operacji) od części

„publicznej”

(21)

Pierwotne typy danych (tradycyjnie)

Typy całkowite

Typy zmiennopozycyjne

Typ znakowy

Typ logiczny

(22)

Pierwotne typy danych

Na ogół typy odzwierciedlające cechy sprzętu.

Podstawowy pierwotny typ całkowity (int, integer)

odpowiada zazwyczaj takiemu zakresowi liczb, jaki mieści się w jednym słowie maszyny (obecnie zwykle 32 lub 64 bity).

Miewa warianty różniące się rozmiarem (byte, short, long) i dopuszczaniem znaku, tzn. liczb ujemnych

(signed/unsigned).

Pierwotne typy zmiennopozycyjne (float, double) to

obecnie prawie zawsze typy obsługiwane sprzętowo, zgodne ze standardem IEEE 754.

Pierwotne typy znakowe (char, character) tradycyjnie wykorzystywały kodowanie ASCII; obecnie coraz częściej używany jest Unicode.

Pierwotny typ logiczny (boolean) jest kodowany za pomocą pojedynczych bitów (co jest oszczędne pamięciowo, ale

wolniejsze w dostępie) lub całych bajtów.

(23)

Typ napisowy

 Typ napisowy może być typem

pierwotnym, jak np. w Javie (klasa String).

 W wielu językach, np. w C/C++, napisy są jednak szczególnym

rodzajem tablic (a więc nie są typem

pierwotnym).

(24)

Obsługa napisów o zmiennej długości

Napisy statyczne, czyli po

zadeklarowaniu nie można zmienić długości napisu, np. obiekty z klasy String w Javie.

Napisy dynamiczne o długości

ograniczonej statycznie. Deklarujemy napis z górnym ograniczeniem na

długość, np. tablica znakowa w C.

Napisy w pełni dynamiczne, czyli długość może zmieniać się bez

ograniczeń, np. w Perlu.

(25)

Tablica

Tablica to zestaw elementów takiego samego typu, gdzie dostęp do

poszczególnych elementów jest poprzez indeksowanie.

 Wymaga to dynamicznego wyliczania

adresu elementu

(26)

Dostęp do elementów tablicy

Tłumacząc instrukcje zawierające odwołania do

elementów tablicy, kompilator generuje kod wyliczający adres elementów.

Dla tablic jednowymiarowych (indeksowanych od 0 jak w C/C++ i pochodnych) adres elementu T[i] to:

(adres elementu T[0]) + (i – indeks pierwszego elementu)*(rozmiar elementu).

Tablice wielowymiarowe przechowywane są tak, jakby to były tablice tablic jednowymiarowych, w dwóch

możliwych wariantach: wierszami lub kolumnami.

Przy ułożeniu tablicy dwuwymiarowej wierszami daje to adres elementu T[i, j] równy

(adres elementu T[0, 0]) + (i*n + j)*(rozmiar elementu)

(27)

Tablica asocjacyjna

Tablica asocjacyjna to

nieuporządkowany zestaw elementów identyfikowanych za pomocą kluczy.

Klucze mogą być bardzo różne (zwykle są to dowolne napisy). Są użyteczne

tam, gdzie potrzebny jest swobodny (nieuporządkowany) dostęp do

elementów.

(28)

Przykład tablic asocjacyjnych (Perl)

%wzrost = (”Jacek” => 177, ”Joanna” => 166, ”Jerzy” => 199);

$wzrost {”Józefina”} = 188;

delete $wzrost{”Jerzy”};

if (exists $wzrost{”Joanna”}) ...

Dostęp do elementu za pomocą klucza.

Dwie możliwe implementacje:

użycie funkcji haszującej do odwzorowania kluczy na adresy.

binarne drzewo poszukiwań (np. czerwono-czarne)

(29)

Rekord

Rekord to zestaw elementów

dowolnych typów. Elementy rekordu zwane są polami. Większość języków stosuje zapis „z kropką” na

oznaczenie dostępu do pól rekordu

(niekiedy „strzałka” czyli „->”)

(30)

Cechy typu rekordowego

 Pola rekordu są zwykle stałej

wielkości, więc da się statycznie przewidzieć rozmiar rekordu

 Równość rekordów można rozumieć jako równość wszystkich pól rekordu

 W językach obiektowych zwykle nie ma rekordów, ponieważ ich

uogólnieniem są klasy

(31)

Unia

Unia to zestaw elementów dowolnych typów, z których w dowolnym momencie przechowywany jest tylko jeden.

 Celem takiej implementacji jest oszczędność pamięci kosztem

bezpieczeństwa (trudno jest sprawdzać zgodność typów, gdy nie jest oczywiste jaki typ jest aktualnie przechowywany).

 Unie są współcześnie mało przydatne,

ponieważ nie ma kłopotów z

(32)

Przykład definicji oraz użycia unii

union unia {

float u_zm1;

short int u_zm2;

};

unia uu;

uu.u_zm2 = 456;

uu.u_zm1 = 123.45; // „nadpisanie” wartości w unii

(33)

Typ wskaźnikowy

Typ wskaźnikowy obejmuje wartości, które mogą wskazywać inne wartości w pamięci,

Dodatkowa wartość to wartość „pusta”, oznaczana jako null lub nil.

Technicznie wskaźnik to po prostu adres komórki pamięci.

Różnica między wskaźnikiem a adresem to

dodatkowa informacja o typie wskazywanych

obiektów, (którą kompilator wykorzystuje do

sprawdzania zgodności typu).

(34)

Zalety wskaźników

 możliwość dynamicznego zarządzania pamięcią

 elastyczność, jaką daje adresowanie pośrednie.

 wskaźniki są jedynym sposobem korzystania ze zmiennych

alokowanych dynamicznie na stercie.

(35)

Operacje na wskaźnikach

Przypisanie wartości

Dereferencja wskaźnika, czyli dostęp do elementu wskazywanego przez wskaźnik.

Arytmetyka wskaźników, czyli możliwość swobodnego przesuwania wskaźnika, daje programiście duże

możliwości, ale jest niebezpieczna: łatwo sięgnąć do

„nieswojej” pamięci (np. w C i C++).

Bez możliwości przesuwania wskaźnika mechanizm staje się bezpieczniejszy (Java, C#).

Do zarządzania pamięcią potrzebny jest mechanizm alokacji, np. new, malloc.

Adresowanie pośrednie wymaga operatora pobrania w języku C).

(36)

Przykłady użycia wskaźników (C/C++)

int a;

int* wsk;

wsk= &a;

*wsk = 25;

int* wsk2;

wsk2 = new int;

*wsk2 = 15;

(37)

Problemy ze wskaźnikami

Wiszący wskaźnik to wskaźnik odnoszący się do zmiennej, która została już zdealokowana. Ten

problem znika, gdy w języku nie ma jawnej dealokacji (np. Java, C#).

Zgubiona zmienna to zmienna na stercie, do której nie mamy żadnego wskaźnika.

Aliasowanie może prowadzić do

(38)

Typy referencyjne

Referencje to typy wskaźnikowe o ograniczonych (dla bezpieczeństwa) możliwościach.

 W C++ typ referencyjny obejmuje stałe wskaźniki z niejawną dereferencją, na których nie są dozwolone operacje

arytmetyczne.

 W Javie nie ma wskaźników tylko same referencje. Referencje odnoszą się do obiektów. Można je kopiować przez

przypisanie; arytmetyka nie jest

dozwolona.

(39)

Niejawna dealokacja

Niejawna dealokacja to zautomatyzowane

zarządzanie pamięcią zmiennych dynamicznych (programista nie musi o to dbać)

Pierwsza metoda — liczniki odwołań. Dla każdego przydzielonego bloku pamięci utrzymujemy licznik odwołań do tego bloku. Licznik aktualizujemy przy kopiowaniu wskaźników, wyjściu wskaźnika poza zakres widoczności itp. Blok, do którego nie ma odwołań, jest dealokowany.

Druga metoda — zbieranie śmieci. Gdy brakuje miejsca na stercie, rozpoczyna się zbieranie śmieci.

Przeglądamy stertę i wszystkie wskaźniki, zaznaczając te bloki, do których nie ma odwołań. Bloki te są

następnie dealokowane.

(40)

ABSTRAKCYJNE TYPY

DANYCH

(41)

Dwie podstawowe abstrakcje w językach programowania to:

Abstrakcja procesu: Abstrakcjami procesów są podprogramy. Pozwalają wskazać (przez ich wywołanie), że pewna czynność ma być wykonana, bez wskazywania jak ma być

wykonana. Szczegóły znajdują się w treści podprogramu, której wywołujący nie musi znać.

Abstrakcja danych : Zamknięta całość

obejmująca reprezentację pewnego typu

danych wraz z podprogramami,

(42)

Przykłady

Wbudowane w języki programowania typy pierwotne można uznać za abstrakcyjne: nie mamy dostępu do reprezentacji wewnętrznej, więc możemy posługiwać się jedynie tym, co dostarcza język.

Zdefiniowany w Pascalu typ rekordowy i kilka procedur na nim działających nie stanowi

abstrakcyjnego typu danych. Ktokolwiek zadeklaruje rekord tego typu, będzie mógł działać bezpośrednio na tym rekordzie z pominięciem „oficjalnych” procedur.

Sztandarowy przykład to klasa np. z Javy lub C++.

Dane schowane w części prywatnej klasy nie są dostępne na zewnątrz, stąd nie da się wykonywać żadnych operacji bezpośrednio.

(43)

Co to jest obiekt?

Obiekt to instancja abstrakcyjnego typu danych, czyli pojedyncza

zmienna (instancja) tego typu

zaalokowany na stercie (najczęściej), na stosie lub statycznie (najrzadziej).

 Sam abstrakcyjny typ danych w

większości języków zwany jest klasą.

(44)

Klasy w C++ (1/2)

Język oferuje dwie konstrukcje: class i struct, różniące się domyślnymi regułami dostępu.

Klasy języka C++ są typami.

Jednostka programu, która zadeklarowała instancję klasy (obiekt), ma dostęp do publicznych bytów tej klasy, ale tylko poprzez tę instancję.

Każda instancja klasy ma własny zestaw danych, natomiast funkcje (metody) nie są powielane lecz przechowywane wspólnie dla całej klasy.

Obiekty mogą być statyczne oraz dynamiczne,

alokowane na stosie (dostęp przez „zwykłe” zmienne) lub na stercie (dostęp przez wskaźniki).

Obiekty mogą zawierać zmienne dynamiczne alokowane na stercie, czyli poza obiektem.

Alokacja i dealokacja na stercie są jawne; służą do tego

(45)

Klasy w C++ (2/2)

Funkcje z klasy mogą być kompilowane jako inline. Kod

funkcji jest wówczas kopiowany do wywołującego, eliminując czasochłonną obsługę wywołania. Mechanizm ten jest

przydatny dla funkcji niewielkich objętościowo.

Elementy klasy mogą być deklarowane jako prywatne (dostęp tylko wewnątrz obiektu), publiczne (dostęp bez ograniczeń) lub chronione (dostęp dla potomków). Osiąga się to przez umieszczenie deklaracji w częściach, odpowiednio, private, public i protected.

Deklaracja friend pozwala dać „obcym” klasom dostęp do elementów prywatnych.

Definicja klasy może zawierać konstruktor, który będzie niejawnie wywoływany przy tworzeniu obiektu z klasy.

Konstruktor ma taką samą nazwę jak klasa. Konstruktory mogą mieć parametry i mogą być przeciążane.

Cytaty

Powiązane dokumenty

 Standardowo wywoływany jest konstruktor bezparametrowy (lub domyślny) klasy nadrzędnej..  Aby do konstrukcji podobiektu klasy bazowej

 Przeszukiwany jest stos wywołań funkcji w poszukiwaniu takiej, która zawiera obsługę wyjątku danego typu (czyli odpowiednią instrukcję catch).. ◦ Jeżeli

• Parametrami szablonów mogą być również szablony klas, jako tak zwane szablony parametrów szablonów.. Stack&lt;int, std::vector&gt;

 OutputIterator set_union (InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result);.  OutputIterator

Model dziedziny (uzupełniony): klasy, atrybuty klas oraz

 Symbole pojawiające się wyłącznie po prawej stronie to symbole terminalne.  Generalnie symbole terminalne to symbole z alfabetu definiowanego języka,

Utworzenie w instancji klasy TRachunek instancji klasy TKolekcja zawierającej instancje klasy TZakup; każda instancja klasy TZakup zawiera instancję klasy TProdukt oraz

Utworzenie w instancji klasy TRachunek instancji klasy TKolekcja zawierającej instancje klas TZakup; kaŜda instancja klasy TZakup zawiera instancję klasy TProdukt oraz