Programowanie komputerów Programowanie obiektowe. Typy zmiennych proste i złożone Typy zmiennych "wbudowane", tj. identyfikowane przez słowa kluczowe, są określane jako proste: int short long float double char Tablice oraz typy zmiennych definiowane są określane jako typy złożone: int t[100]; Złożone typy zmiennych Tablica Istnieje kilka rodzajów typów złożonych: tablice unie (union) struktury (struct) klasy (class) Poza tablicami, każdy wymaga zdefiniowania, do czego służą słowa kluczowe union, struct i class Tablica nie stanowi definicji nowego typu zmiennych składa się z wielu zmiennych tego samego typu Struktura Klasa Struktura stanowi definicję nowego typu zmiennych składa się ze zmiennych różnych typów Struktura (podobnie jak unia) wywodzi się z języka C. Wjęzyku C++ struktury i klasy są niemal tożsame. Klasa stanowi definicję nowego typu zmiennych składa się ze zmiennych oraz funkcji; elementami składowymi klasy są pola oraz metody (w tym operatory), dodatkowo w Borland C++ Builder - właściwości oraz domknięcia
Elementy składowe klasy Elementy składowe klasy Elementy składowe klasy: pola zmienne wchodzące w skład klasy metody funkcje wchodzące w skład klasy Elementy składowe klasy: konstruktory i destruktory specjalne metody, wykonywane automatycznie podczas tworzenia/niszczenia obiektów definicje operatorów (takich jak +, -, *, /, >, <, ==,!=, && itd.) dla których argumentem jest obiekt danej klasy Elementy składowe klasy Pola Rozszerzenie klas firmy Borland : właściwość (ang. property) logiczne połączenie trzech elementów: pola, metody odczytującej wartość tego pola oraz metody ustawiającej wartość tego pola domknięcie (ang. closure) specjalny rodzaj adresu, wykorzystywany przy obsłudze zdarzeń Pola (zmienne wchodzące w skład klasy): pole może być dowolnego typu prostego (jak char, int, float,...) lub złożonego (tablica, struktura) bądź wskaźnikiem do takiej zmiennej pole może być obiektem innej klasy, tablicą takich obiektów lub wskaźnikiem do takiego obiektu Metody Przykład Metody (funkcje wchodzące w skład klasy): mają dostęp do wszystkich elementów klasy proste metody (nie używające instrukcji warunkowych i pętli) mogą być definiowane w obrębie definicji klasy złożone metody są deklarowane w obrębie definicji klasy, zaś definiowane poza definicją klasy; definicja takiej metody musi zawierać wskazanie klasy, do której należy mogą być przeciążane Przykład klasy : class ZEGAR { double czas; void ustaw (double nc); void pokazuj (void); Klasa "ZEGAR" posiada pole "czas" typu double oraz metody "ustaw" i "pokazuj".
jest instancją klasy, tzn. obiekt jest zmienną, dla której typem jest klasa wskazana w deklaracji obiektu: "Z" jest obiektem klasy "ZEGAR", analogicznie jak w deklaracji int a; "a" jest zmienną typu "int". jest wyposażony we wszystkie pola i metody, które zostały zawarte w definicji klasy. class ZEGAR { double czas; void ustaw (double nc); void pokazuj (void); "Z" jest obiektem klasy "ZEGAR", zatem posiada pole "czas" i metody "ustaw" oraz "pokazuj". Odwołanie się do tych pól i metod umożliwia operator dostępu "." (kropka): Nazwa_obiektu. Nazwa_pola Nazwa_obiektu. Nazwa_metody Z pól korzysta się podobnie, jak ze zmiennych (a z metod podobnie, jak z funkcji), z tą różnicą, że odwołując się do pola (metody), należy zawsze najpierw wskazać obiekt i użyć operatora dostępu. Sposób korzystania z pól: Nazwa_obiektu.Nazwa_pola ZEGAR X, Y, Z; Z.czas = 12.0; cout << Z.czas; X.czas = Z.czas + 4; Y.czas = 9.25 + srqt (X.czas Z.czas); Sposób korzystania z metod: Nazwa_obiektu.Nazwa_metody (argumenty) Z.ustaw (8.25); Z.pokazuj (); Sposób korzystania z metod: Nazwa_obiektu.Nazwa_metody (argumenty) Z.ustaw (8.25); Z.pokazuj (); Uwaga! Wyrażenie Z.pokazuj jest poprawne składniowo, chociaż nie oznacza wywołania metody (reprezentuje adres metody).
Wskaźnik jest zmienną przeznaczoną do przechowywania adresu innej zmiennej (lub obiektu). W C++ każda zdefiniowana klasa ma typ wskaźnikowy: ZEGAR *W; "Z" jest obiektem klasy "ZEGAR", zaś "W" jest wskaźnikiem obiektów klasy "ZEGAR", analogicznie jak w deklaracji int *w; "w" jest wskaźnikiem zmiennej typu "int". Każda klasa jest automatycznie wyposażana w operatory związane ze wskaźnikami: & referencji (pobrania adresu) * dereferencji (wyłuskania) new (utworzenie zmiennej) delete (zniszczenie zmiennej) Jeżeli zdefiniowana została klasa "ZEGAR", to można zapisać ZERAG Z, *W; W = &Z; // referencja ZEGAR *N; N = new ZEGAR; //... delete N; // utworzenie obiektu // zniszczenie obiektu Odwołanie się do tych pól i metod obiektu poprzez wskaźnik umożliwia operator dostępu "->" (minus-większe): Wskaźnik_obiektu -> Nazwa_pola Wskaźnik_obiektu -> Nazwa_metody Sposób korzystania z pól: Wskaźnik_obiektu->Nazwa_pola ZEGAR *W; W = new ZEGAR; Sposób korzystania z metod: Wskaźnik_obiektu->Nazwa_metody (argumenty) ZEGAR *W; W = new ZEGAR; W->ustaw (8.25); W->czas = 12.0; cout << W->czas; Z.czas = W->czas
Programowanie obiektowe Sposób korzystania z metod: Wskaźnik_obiektu->Nazwa_metody (argumenty) ZEGAR *W; W = new ZEGAR; W->ustaw (8.25); Uwaga! Wyrażenie W->pokazuj jest poprawne składniowo, chociaż nie oznacza wywołania metody (reprezentuje adres metody). Zaawansowane mechanizmy programowania obiektowego: hermetyzacja (enkapsulacja) dziedziczenie polimorfizm (enkapsulacja) polega na wydzieleniu w definicji klasy dwóch sekcji: - public (interfejs klasy) oraz - private (implementacja klasy) umożliwia oddzielenie implementacji klasy od interfejsu klasy, zapewnia wygodniejsze i bezpieczniejsze korzystanie z klas utworzonych przez innych programistów Przykład klasy : class ZEGAR { private: double czas; public: void ustaw (double nc); double podaj (void); Część prywatna (np. w klasie "ZEGAR" pole "czas"): jest dostępna "z wewnątrz obiektu" (np. w klasie ZEGAR dla metod "ustaw" i "podaj"), jest niedostępna "z zewnątrz obiektu": Z.ustaw (12.0); cout << Z.podaj(); Z.czas = 12.0; // BŁĄD! pole prywatne Powszechna praktyka polega na definiowaniu prywatnego pola oraz dwóch publicznych metod służących do odczytu i zapisu tego pola: class Sample { private: int FCoś; public: void SetCoś (int Coś); int GetCoś (void); F (field) pole, Set ustaw, Get - weź
Co daje hermetyzacja: metoda "Set" może sprawdzić, czy wartość jest poprawna: ZEGAR X; X.Time = 25.25; // błąd przy próbie wyśw. ZEGAR Y; Y.SetTime(25.25); // nie ma błędu Co daje hermetyzacja: metoda "Set" może dokonać zmian wartości innych pól obiektu, natychmiast zapisać zmiany do pliku, spowodować zmiany na ekranie itp.: ZEGAR X; X = 12.0; // zmiana tylko w RAM ZEGAR Y; Y.SetTime(12.0); // zmiana na ekranie Właściwość (ang. property) jest rozszerzeniem języka C++, wprowadzonym przez firmę Borland. Właściwość jest rodzajem definicji, która łączy w jedną całość trzy elementy: pole (FCoś), metodę do odczytania pola (GetCoś) oraz metodę do ustawienie pola (SetCoś) Właściwość jest zawsze elementem publicznym, natomiast pole i obie metody prywatne. Przykład właściwości: class Clock { private: double FTime; void SetTime (double newtime); double GetTime (void); public: property double Time = {read=gettime, write=settime} Odwołanie do właściwości jest możliwe poprzez operator dostępu, "." lub "->" Nazwa_obiektu. Nazwa_właściwości Wskaźnik_obiektu -> Nazwa_właściwości Z właściwości korzysta się (prawie) identycznie, jak z pól, CLOCK C; C.Time = 12.0; CLOCK *W; W = &C; cout << W->Time; Z zapisu powyżej nie wynika, czy "Time" jest polem, czy właściwością.
Należy jednak pamiętać, że niejawnie wywoływane są odpowiednie metody: CLOCK C; C.Time = 12.0; // C.SetTime (12.0); CLOCK *W; W = &C; cout << W->Time; // cout << W->GetTime(); Należy jednak pamiętać, że niejawnie wywoływane są odpowiednie metody: CLOCK C; C.Time = 12.0; // C.SetTime (12.0); CLOCK *W; W = &C; cout << W->Time; // cout << W->GetTime(); Skutek prostota zapisu charakterystyczna dla pola, funkcjonalność charakterystyczna dla metody Ważne! Dziedziczenie Sposób korzystania z pól, metod i właściwości (składnia + przykład), gdy odwołujemy się do obiektu bezpośrednio lub przez wskaźnik; Rozpoznawanie na podstawie zapisu, x = A->B; C.D (y, z); podstawowych elementów OOP: A,B,C,D = klasa, obiekt, wskaźnik obiektu, pole, metoda, właściwość...??? Dziedziczenie polega na wskazaniu klasy bazowej w definicji klasy potomnej; klasa potomna dziedziczy wszystkie elementy składowe klasy bazowej umożliwia tworzenie nowych klas jako rozwinięcie klas już istniejących (zamiast "od zera") ułatwia tworzenie rozbudowanych bibliotek klas (ponowne wykorzystanie kodu!) Bez dziedziczenia Dziedziczenie OSOBA DZIENNY ZAOCZNY DYDAKTYK TECHNIK Album Warunek Album Czesne Pensja Przedmiot Pensja Sala STUDENT Album PRACOWNIK Pensja DZIENNY ZAOCZNY DYDAKTYK TECHNIK Warunek Czesne Przedmiot Sala
Dziedziczenie Dziedziczenie Mając klasę, class OSOBA { char *Imie, *; można stworzyć klasę potomną tej klasy: class STUDENT : public OSOBA { int Album; Klasa potomna dziedziczy wszystkie elementy klasy bazowej nie trzeba ich definiować na nowo. Mając klasę potomną, można korzystać ze wszystkich jej elementów składowych w jednakowy sposób, tj. nie różnicując elementów własnych oraz elementów odziedziczonych bezpośrednio i pośrednio, DYDAKTYK X; // odziedziczone z... cout << X.Imie; //... z klasy OSOBA cout << X.; //... też z klasy OSOBA cout << X.Pensja; //... z klasy PRACOWNIK cout << X.Przedmiot; // własne polega na definiowaniu metod wirtualnych o identycznych nazwach i argumentach w klasie bazowej i w klasach potomnych ułatwia zarządzanie obiektami w złożonych programach, korzystających z rozbudowanych bibliotek klas STUDENT Album OSOBA PRACOWNIK Pensja DZIENNY ZAOCZNY DYDAKTYK TECHNIK Warunek Czesne Przedmiot Sala Wszystkie klasy mają metodę wirtualną, class OSOBA { char *Imie, *; virtual (void); class STUDENT : public OSOBA { int Album; virtual (void); Każda z metod wirtualnych wykonuje działania właściwe dla swojej klasy. Wskaźnik klasy bazowej (tu: klasy OSOBA) OSOBA *Osoba; Osoba = new DYDAKTYK; może zawierać adres obiektu tej klasy lub dowolnej klasy potomnej
Wywołanie metody wirtualnej: OSOBA *Osoba; Osoba = new DYDAKTYK; Osoba->(); // DYDAKTYK->() spowoduje wykonanie tej spośród zdefiniowanych metod wirtualnych o danej nazwie, która jest najbliższa obiektowi faktycznie utworzonemu, a nie metody zdefiniowanej w klasie typowi wskaźnika Wywołanie metody wirtualnej w tej sytuacji: OSOBA *Osoba; if (...) Osoba = new DYDAKTYK; else Osoba = new TECHNIK; Osoba->(); //??? spowoduje wykonanie właściwej wersji metody (DYDAKTYK-> lub TECHNIK->) pomimo tego, że w czasie kompilacji nie wiadomo, która z nich powinna być użyta Przykład (Borland C++ Builder): Formularz może wyświetlić poprawnie komponenty, nie dbając o ich rodzaj, for (c=0; c<count; c++) Components[c]->Show(); dzięki temu że wszystkie komponenty mają wirtualne metodę Show.