Struktury

52
Struktury Struktury

description

Struktury. Wskaźnik do struktury:. Jeśli do obiektu strukturalnego odwołujemy się za pomocą wskaźnika , to dostęp do poszczególnych pól możemy uzyskać przez złożenie * i . lub operator -> ( znak  -  oraz znak  >  ):. ( * Wskaźnik_Obiektu ) . Nazwa_Składnika. - PowerPoint PPT Presentation

Transcript of Struktury

Page 1: Struktury

StrukturyStruktury

Page 2: Struktury

Wskaźnik do struktury:

Jeśli do obiektu strukturalnego odwołujemy się za pomocą wskaźnika, to dostęp do poszczególnych pól możemy uzyskać przez złożenie * i . lub operator -> ( znak - oraz znak > ):

Wskaźnik_Obiektu -> Nazwa_Składnika

(* Wskaźnik_Obiektu ). Nazwa_Składnika

* Wskaźnik_Obiektu . Nazwa_Składnika

Page 3: Struktury

Każda definicja struktury wprowadza nowy, unikatowy typ, np.:

struct S1 { int i; };struct S2 { int j; };

Typy S1 i S2 są różnymi typami, zatem w deklaracjach:

S1 x, y;S2 z;

zmienne x oraz y są tego samego typu S1, natomiast x oraz z są różnych typów.

Page 4: Struktury

Wobec tego przypisania:

x = y;y = x;

są poprawne, podczas gdy:

x = z;z = y;

są błędne.

Dopuszczalne są natomiast przypisania składowych o tych samych typach, np.:

x.i = z.j;

Page 5: Struktury

Przeładowanie funkcji

Page 6: Struktury

W jezyku C

w danym zakresie ważności może być tylko jedna funkcja o danej nazwie.

Kompilator języka C++ daje nam większą swobodę.

Przykład:

void pisz(float);

void pisz(char, int, char);

pisz(‘C’, 123, ‘F’);

Czy masz wątpliwość o wywołanie jakiej funkcji chodzi?

Page 7: Struktury

Przeładowanie funkcji polega na tym, że w danym zakresie ważności jest więcej niż jedna funkcja o takiej samej nazwie.

To, która z nich zostaje w danym przypadku uaktywniona zależy od typu argumentów podanych podczas wywołania.

Funkcje takie mają tę samą nazwę, ale muszą się różnić liczbą lub typem argumentów.

Przeładowujemy funkcję wówczas, gdy wykonuje ona analogiczną akcję na różnych zestawach obiektów.

Page 8: Struktury

void pisz (int x);

void pisz (int y); // powtórna deklaracja

void pisz (int , char);

void pisz (float, char, int);

Powtórna deklaracja nie jest błędem.

W przypadku deklaracji kompilator nie zareaguje.

Zaprotestuje dopiero przy definicjach tych funkcji

(patrz PROG112.CPP)

Page 9: Struktury

int rysuj (int);

float rysuj (int);

Przy przeładowaniu ważna jest tylko odmienność argumentów.

int rysuj (float, int);

int rysuj (int, float);

BŁĄD !

POPRAWNIE !

Page 10: Struktury

void pisz (float);

void pisz (int);

void pisz (int, int);

pisz (123, (int)45.67); void pisz (int, int);

void pisz (int, int);

void pisz (int, unsigned int);poprawnie !

Page 11: Struktury

Przeładowanie przy argumentach domniemanych

void pisz (float);

void pisz (char *);

void pisz (int, float = 0);

pisz (5.67); // pisz (float);

pisz (”2000 r.”); // pisz (char *);

pisz (123); // pisz (int, float = 0);

pisz (123, 5.67); // pisz (int, float);

void pisz (int);

Page 12: Struktury

W rzeczywistości funkcje przeładowane mają różne nazwy.

Kompilator zmienia nazwy wszystkich funkcji programu.

Kompilator uzupełnia nazwę funkcji dodając typ argumentów:

void rys(void );

void rys (void , float);

void rys (float, void );

void rys (void, float, float);

rys_Fv

rys_Fvf

rys _Ffv

rys _Fvff

Zmiana nazwy funkcji dotyczy zarówno definicji i deklaracji funkcji, jak też i wywołań.

Informacja o typie zwracanym nie jest doczepiana do nazwy.

Page 13: Struktury

Rozwinięcie tej koncepcji stanowią szablony w niektórych jezykach nazywane typami ogólnymi (generic)

Page 14: Struktury

KlasyKlasy

Page 15: Struktury

Definicja klasy:

class identyfikator_typu { ciało klasy };

gdzie ciało klasy zawiera deklaracje składników klasy.

class Okrag { public: int x, y, r; … };

Page 16: Struktury

Deklaracja obiektu:

Okrag Zielony;Okrag * Wskaz;Okrag & Moj = Zielony;

Odwołanie się do składników obiektu:

obiekt . składnik

wskaźnik -> składnik

referencja . składnik

Page 17: Struktury

Zielony . r = 100;Wskaz = & Zielony;Wskaz -> r = 100;Moj . r = 100;

Przykład:

Składnikami klasy mogą być też funkcje.

class Okrag{ public: int x, y, r; void Inicjalizuj ( int x1, int y1, int r1); void Rysuj ( int x1, int y1, int r1);};

Page 18: Struktury

W ogólnym przypadku deklaracje funkcji w klasie mogą być pomieszane z deklaracjami danych.

Składnik jest znany w całej definicji klasy, niezależnie od miejsca zdefiniowania składnika wewnątrz klasy.

Nazwy deklarowane w klasie mają zakres ważności równy obszarowi całej klasy.

W ciele klasy, jak w kapsule, zamknięte są dane oraz funkcje operujące na tych danych.

Takie zamknięcie nazywamy enkapsulacją ( od ang. encapsulation).

Funkcje składowe klasy mają zakres klasy.

Uwagi:

Page 19: Struktury

Ukrywanie informacji:

class Moj_Typ{ private:

int Liczba; // prywatne dane składowefloat Temperatura;char Komunikat [80];

int Czy_Gotowe( ); // prywatna funkcja składowa

public:float Predkosc; // publiczna dana składowaint Pomiar( ); // publiczna funkcja składowa

}

Page 20: Struktury

Ukrywanie informacji:

Są 3 etykiety, za pomocą których można określać dostęp do składników klasy:

private:protected:public:

Page 21: Struktury

Ukrywanie informacji: Składnik private: jest dostępny tylko dla składowych

danej klasy. Jeżeli zależy nam na ukryciu informacji, to wówczas składnik powinien być deklarowany jako prywatny.

Składnik protected: jest dostępny tak, jak składnik private, ale dodatkowo jest dostępny dla klas wywodzących się z danej klasy.

Składnik public: jest dostępny bez ograniczeń. Zwykle składnikami takimi są wybrane funkcje składowe. Za ich pomocą dokonujemy z zewnątrz operacji na danych prywatnych.

Etykiety można umieszczać w dowolnej kolejności, mogą też się powtarzać.

Zakłada się, że dopóki w definicji klasy nie wystąpi żadna z tych etykiet, składniki przez domniemanie mają dostęp private.

Page 22: Struktury

Klasa a obiekt :class Osoba{ char Nazwisko[40]; int Wiek; public:

void Zapamietaj (char *, int);void Pisz ( );

};

Osoba Student1, Student2, Asystent;

W pamięci utworzone zostały 3 różne komplety danych składowych (Nazwisko i Wiek).

Natomiast funkcje składowe są zapamiętane w pamięci tylko jednokrotnie.

Page 23: Struktury

Funkcje składowe :

Funkcje składowe mają pełny dostęp do wszystkich składników swojej klasy, to znaczy: do danych (mogą z nich korzystać) i do innych funkcji (mogą je wywoływać).

Do składnika swojej klasy funkcje odwołują się przez podanie jego nazwy .

Nazwa_Obiektu . Nazwa_Funkcji_Składowej ( argumenty );

Student1 . Zapamietaj (Kowalski, 21);

Page 24: Struktury

Osoba * Wsk;Wsk = &Asystent; Osoba &Belfer = Asystent;

Wsk -> Zapamietaj (Kowalski, 21);Belfer. Zapamietaj (Nowak, 30);

Możemy także wywołać funkcję składową dla danego obiektu, pokazując na niego wskaźnikiem lub za pomocą referencji np.:

Funkcje składowe :

Page 25: Struktury

Definiowanie funkcji składowych :

Pierwszy sposób: wewnątrz definicji klasy.

class Osoba{ char Nazwisko [80]; // składniki prywatne

int Wiek;public: // składniki publiczne

void Zapamietaj (char * Napis, int Lata ){ strcpy (Nazwisko, Napis); Wiek = Lata;}void Pisz ( ){ cout << Nazwisko << , lat: << Wiek

<< endl;}

};

Page 26: Struktury

Drugi sposób: w definicji klasy umieszcza się tylko same deklaracje funkcji składowych, natomiast definicje są napisane poza ciałem klasy.

class Osoba{

char Nazwisko [80]; // składniki prywatneint Wiek;public: // składniki publiczne

void Zapamietaj (char * Napis, int Lata );void Pisz ( );

};void Osoba::Zapamietaj (char * Napis, int Lata ){ strcpy (Nazwisko, Napis); Wiek = Lata;}void Osoba::Pisz ( ){ cout << Nazwisko << , lat: << Wiek << endl;}

Page 27: Struktury

Ponieważ funkcje znajdują się teraz poza definicją klasy, dlatego ich nazwa została uzupełniona nazwą klasy, do której mają należeć. Służy do tego operator zakresu ::.

Funkcja zdefiniowana poza klasą ma dokładnie taki sam zakres, jakby była zdefiniowana wewnątrz klasy.

Jednakże sposób definiowania funkcji wewnątrz, czy na zewnątrz definicji klasy stanowi różnicę dla kompilatora.

Jeśli bowiem funkcję składową zdefiniowaliśmy wewnątrz definicji klasy, to kompilator uznaje, że chcemy, aby ta funkcja była typu inline.

Page 28: Struktury

inline int Zaokr ( float Liczba);{ return (Liczba + 0.5);}

Funkcja typu inline:

Jeśli ciało funkcji składowej ma nie więcej niż dwie linijki, to funkcję tę definiujemy wewnątrz klasy. Jest ona automatycznie inline.

Jeśli funkcja składowa jest dłuższa niż dwie linijki, to definiujemy ją poza definicją klasy. Nie jest ona automatycznie inline

Page 29: Struktury

Funkcja składowa zdefiniowana poza definicją klasy może być typu inline, ale trzeba to zaznaczyć pisząc słowo inline, np.:

inline void Osoba :: Pisz ( ){

// ciało funkcji}

Page 30: Struktury

Odwołanie się do publicznych danych składowych:

class Liczby{ int L1: public :

float L2;void Fun ( );

};

• Są tu dwie dane składowe. • Składnik L1 jest prywatny (przez domniemanie). • Jako prywatny może być dostępny tylko z zakresu

klasy - czyli wewnątrz funkcji składowej Fun.

Page 31: Struktury

• Składnik publiczny L2 oprócz tego, że może być dostępny w funkcji składowej Fun, dostępny jest także z zewnątrz klasy.

• Pracując jednak na nim z zewnątrz musimy podać, o który konkretnie obiekt chodzi, np.:

Liczby Temperatura, Cisnienie;

Temperatura . L2 = 18.6;Ciscienie . L2 = 1003;cout << Temperatura wynosi : << Temperatura . L2 << stopni C\n;cout << Ciśnienie wynosi: << Cisnienie . L2 << hPa\n;

Page 32: Struktury

Zasłanianie nazw

Przesyłanie do funkcji argumentów będących obiektami

(PROG57.CPP)

Przez domniemanie zakłada się, że obiekt jest przesyłany do funkcji przez wartość.

Konsekwencja: jeśli obiekt jest duży, to proces kopiowania może trwać długo.

Lepszym rozwiązaniem w takim przypadku jest przesyłanie przez referencję.

Page 33: Struktury

void Prezentacja (Osoba &Ktos){ cout << Mam zaszczyt przedstawić Państwu,\n

<< Oto we własnej osobie: ;cout << Ktos . Pisz_Dane ();

}

Page 34: Struktury

Konstruktor : Definicję obiektu i nadanie mu wartości można

załatwić w jednej instrukcji. W tym celu należy posłużyć się specjalną funkcją

składową zwaną konstruktorem. Charakteryzuje się ona tym, że nazywa się tak

samo jak klasa.

class Numer{ int Liczba; public:

Numer ( int L ) { Liczba = L; } // konstruktorvoid Schowaj ( int L ) { Liczba = L; }int Zwracaj ( ) { return Liczba; }

} ;

(PROG59.CPP)

Page 35: Struktury

Konstruktor

• Klasy języka C++ wyposażone są w specjalną funkcję zwaną konstruktorem;

• Konstruktor jest specjalną funkcją składową, wywoływaną zawsze w chwili tworzenia obiektu danej klasy;

• Zadaniem konstruktora jest inicjalizacja danych składowych (pól) obiektu danej klasy, przydzielenie pamięci dla jego elementów oraz wykonanie innych czynności niezbędnych do prawidłowego utworzenia obiektu;

• Konstruktor nie jest obowiązkowym elementem definicji klasy.

Page 36: Struktury

• Jeśli tworząc klasę nie zdefiniujesz jawnie jej konstruktora, kompilator automatycznie wygeneruje tzw. konstruktor domyślny;

• Rozwiązanie takie, choć dość wygodne, sprawdza się tylko dla bardzo prostych klas;

• W praktyce każda definicja nietrywialnej klasy będzie zawierała konstruktor;

• Nazwa konstruktora musi być taka sama, jak nazwa zawierającej go klasy;

• Konstruktor nie może zwracać żadnej wartości.

Konstruktor

Page 37: Struktury

• Klasa może posiadać więcej niż jeden konstruktor;

• Jest to możliwe dzięki mechanizmowi przeładowania funkcji;

Konstruktor

Page 38: Struktury

• Przypomnienie - niezainicjalizowane zmienne będą zawierały przypadkowe wartości;

• Reguła ta odnosi się również do klas;• Dobra praktyka wymaga inicjalizowania

wszystkich pól klasy;• Przypomnienie - konstruktora nie można

wywołać jawnie;• Wywołanie konstruktora następuje w chwili

tworzenia obiektu danej klasy ;• W chwili powoływania obiektu wybierasz

również wersję konstruktora, jeżeli dana klasa definiuje ich więcej.

• Ciekawostki: niepubliczny konstruktor ?

Konstruktor

Page 39: Struktury

Destruktor :

Przeciwieństwem konstruktora jest destruktor. Destruktor to funkcja składowa klasy. Destruktor nazywa się tak samo, jak klasa z tym,

że przed nazwą ma znak ~ (tylda). Podobnie jak konstruktor - nie ma on określenia

typu zwracanego.

Destruktor wywoływany jest wtedy, gdy obiekt danej klasy ma być zlikwidowany.

Page 40: Struktury

• Destruktor jest specjalną funkcją wywoływaną w chwili likwidacji obiektu danej klasy;

• Destruktor jest funkcjonalnym przeciwieństwem konstruktora;

• Do jego zadań należy najczęściej zwalnianie zasobów wykorzystywanych przez obiekt i inne czynności natury porządkowej;

• Destruktor nie jest obowiązkowym elementem klasy;

• Destruktor możesz zdefiniować tylko raz ;

• Destruktor jest funkcją bezparametrową i nie zwracającą żadnej wartości;

• Nazwa składa się z nazwy klasy poprzedzonej znakiem ~.

Destruktor

(p. PROG156.CPP, PROG157a.CPP)

Page 41: Struktury

• Destruktor jest wywoływany w chwili usuwania obiektu danej klasy;

• Sama likwidacja obiektu może nastąpić poprzez– usunięcie go ze stosu, jeśli jest to obiekt lokalny, a

operująca na nim funkcja właśnie zakończyła działanie;

– lub w wyniku wywołania operatora delete, jeśli obiekt został utworzony dynamicznie.

• W obu przypadkach wywołanie destruktora jest ostatnią czynnością obiektu przed jego unicestwieniem;

Destruktor

Page 42: Struktury

• Pola klasy to nic więcej, jak tylko jej lokalne zmienne;• Pola klasy funkcjonują tak samo, jak pola struktury i

różnią się od ostatnich wyłącznie domyślną kategorią dostępu;

• Wewnątrz klasy wszystkie pola są swobodnie dostępne dla wszystkich funkcji składowych;

• Natomiast ich widoczność na zewnątrz klasy jest uwarunkowana kwalifikatorami dostępu;

• Pola w sekcjach private i protected są na zewnątrz niedostępne;

• Pola public mogą być czytane i zapisywane spoza klasy bez ograniczeń.

Pola

Page 43: Struktury

• Rozwiązaniem problemu dostępu do pól prywatnych są specjalne funkcje klasy ustawiające i pobierające wartości tych pól;

• Funkcje te, zwane funkcjami udostępniającymi, deklarowane są oczywiście w sekcji publicznej;

• Fundamentaliści: ” wszystkie pola klas powinny być prywatne, a dostęp do nich ma byś realizowany wyłącznie za pomocą funkcji udostępniających”;

• Radykałowie: ” wręcz przeciwnie”;

• Wytyczenie granicy jest kwestią doświadczenia i zdrowego rozsądku;

• Jeśli nie wiesz, co robić z danym polem, umieść go w sekcji prywatnej.

Pola

Page 44: Struktury

• Zestaw publicznych funkcji składowych powinien być ograniczony do niezbędnego minimum zapewniającego skuteczną komunikację z obiektami i kontrolowanie ich działania;

• Jeśli dana funkcja składowa nie musi być widoczna na zewnątrz, powinna być zadeklarowana jako prywatna;

• Jeśli dana funkcja składowa nie musi być widoczna na zewnątrz, ale powinna być dostępna dla klas pochodnych, powinna być zadeklarowana jako chroniona (protected);

• Jeśli zależy Ci na szybkim wykonaniu funkcji, a jednocześnie jest ona niewielka, możesz zadeklarować ją jako funkcję wstawianą (inline);

Funkcje

Page 45: Struktury

• Deklaracja każdej klasy zawiera ukryte pole wskaźnikowe o nazwie this;

• Po utworzeniu obiektu, wskaźnik this zawiera adres obiektu w pamięci;

• Oto klasa Punkt widziana oczami komputera:

Wskaźnik this

Class Punkt{ private: Punkt *this; int x, y; public: Punkt (int _x, int _y); ~Punkt() ....};

Page 46: Struktury

• Czemu służy wskaźnik this ?;• Każdy obiekt danej klasy posiada własną kopię

zestawu pól;• Natomiast funkcje składowe są przechowywane

w jednym egzemplarzu;• Wskaźnik this pozwala na zidentyfikowanie

właściciela danych, do których odwołuje się funkcja składowa;

• Jeśli chcesz uniknąć kłopotów, nigdy nie zmieniaj wartości wskaźnika this!

Wskaźnik this

Page 47: Struktury

Dziedziczenie

• Dziedziczeniem nazywamy proces tworzenia nowych klas na podstawie klas już istniejących;

• Klasa wykorzystywana jako podstawa w procesie dziedziczenia jest klasą bazową;

• Klasy dziedziczące po klasie bazowej są to klasy pochodne;

• Klasa pochodna dziedziczy wszystkie możliwości funkcjonalne klasy bazowej, poszerzone o nowe pola i funkcje;

• Niemożliwe jest usunięcie jakichkolwiek elementów klasy bazowej.

Page 48: Struktury

Dziedziczenie

• Samo dziedziczenie symbolizowane jest przez znajdujący się w pierwszym wierszu deklaracji klasy dwukropek, po którym występuje nazwa klasy bazowej ;

• Słowo kluczowe virtual deklaruje poprzedzoną nim funkcję jako wirtualną;

• Jako przykład funkcji wirtualnych rozpatrz funkcje o nazwie Pokaz() w klasach Punkt i Linia_Pozioma;

• Ponieważ procedura narysowania na ekranie linii różni się od procedury narysowania punktu, należy w klasie Linia_Pozioma przedefiniować (przesłonić) funkcję Pokaz().

Page 49: Struktury

Przesłanianie

• Przesłanianiem nazywamy przedefiniowywanie funkcji klasy bazowej w klasach pochodnych ;

• Przesłanianie stosuje się w celu całkowitej zmiany działania funkcji klasy bazowej lub, znacznie częściej, jej uzupełnienia i rozszerzenia o dodatkowe operacje;

• Aby rozszerzyć pierwotną definicję nie musisz jej przepisywać. W nowej definicji funkcji wystarczy najpierw wywołać funkcję klasy bazowej, a następnie dopisać kod realizujący rozszerzenia;

• Przesłaniając funkcje klasy bazowej musisz zapewnić identyczność nagłówków funkcji;

• Istotne jest również aby funkcja była dostępna dla klas pochodnych.

Page 50: Struktury

Przesłanianie

• Odwołując się do funkcji klasy bazowej musisz poprzedzić ją nazwą klasy i operatorem zakresu;

• Użycie operatora zakresu jest konieczne tylko wtedy, gdy wywoływana funkcja jest zdefiniowana zarówno w klasie bazowej, jak i pochodnej ;

• Jeśli funkcja jest zdefiniowana tylko w sekcji publicznej lub chronionej klasy bazowej, a nie wchodzi w skład definicji klasy pochodnej, możesz ją wywołać bez użycia operatora zakresu.

Page 51: Struktury

Funkcje wirtualne

• Funkcją wirtualną nazywamy funkcję wywoływaną zawsze w obrębie posiadającej ją klasy;

• Poprzedzenie deklaracji słowem kluczowym virtual powoduje, że wszystkie odwołania do funkcji będą zawsze wykonywane w obrębie klasy, która ją zdefiniowała;

• Jeśli podejrzewasz, że funkcja będzie przedefiniowywana w klasach pochodnych, warto deklarować ją jako wirtualną;

• Jeśli przedefiniowywana funkcja jest wywoływana przez inne funkcje klasy bazowej, prawie na pewno powinieneś zadeklarować ją jako wirtualną.

Page 52: Struktury

Inicjalizacja obiektów• Kolejnym problemem związanym z dziedziczeniem jest

inicjalizacja obiektów;

• W chwili utworzenia obiektu klasy pochodnej należy zainicjalizować też pola klasy bazowej, a w przypadku dziedziczenia „piętrowego” - również wszystkich klas pośrednich;

• Najskuteczniejszym na to sposobem jest po prostu wywołanie konstruktora klasy bazowej w konstruktorze klasy pochodnej;

• Ponieważ nie można wywołać tego konstruktora bezpośrednio, należy wykorzystać w tym celu listę inicjalizującą;

• Prawidłowa inicjalizacja klasy bazowej w konstruktorze klasy pochodnej jest sprawą bardzo istotną.