520 likes | 723 Views
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.
E N D
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
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.
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;
Czy masz wątpliwość o wywołanie jakiej funkcji chodzi? 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’);
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.
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 (patrzPROG112.CPP)
int rysuj (int); float rysuj (int); BŁĄD ! Przy przeładowaniu ważna jest tylko odmienność argumentów. int rysuj (float, int); int rysuj (int, float); POPRAWNIE !
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, unsignedint); poprawnie !
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);
void rys(void ); void rys (void , float); void rys (float, void ); void rys (void, float, float); rys_Fv rys_Fvf rys _Ffv rys _Fvff • 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: • Zmiana nazwy funkcji dotyczy zarówno definicji i deklaracji funkcji, jak też i wywołań. • Informacja o typie zwracanym nie jest doczepiana do nazwy.
Rozwinięcie tej koncepcji stanowią szablony w niektórych jezykach nazywane typami ogólnymi (generic)
Definicja klasy: • classidentyfikator_typu • { • ciało klasy • }; gdzie ciało klasy zawiera deklaracje składników klasy. • class Okrag • { • public: • int x, y, r; • … • };
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
Przykład: • Zielony . r = 100; • Wskaz = & Zielony; • Wskaz -> r = 100; • Moj . r = 100; 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); };
Uwagi: • 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.
Ukrywanie informacji: class Moj_Typ { private: int Liczba; // prywatne dane składowe float Temperatura; char Komunikat [80]; int Czy_Gotowe( ); // prywatna funkcja składowa public: float Predkosc; // publiczna dana składowa int Pomiar( ); // publiczna funkcja składowa }
Ukrywanie informacji: Są 3 etykiety, za pomocą których można określać dostęp do składników klasy: • private: • protected: • public:
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 dodatkowojest 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.
Klasa a obiekt : class Osoba { char Nazwisko[40]; int Wiek; public: void Zapamietaj (char *, int); void Pisz ( ); }; OsobaStudent1, 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.
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);
Funkcje składowe : Możemy także wywołać funkcję składową dla danego obiektu, pokazując na niego wskaźnikiem lub za pomocą referencji np.: • Osoba * Wsk; • Wsk = &Asystent; • Osoba &Belfer = Asystent; • Wsk ->Zapamietaj (Kowalski, 21); • Belfer.Zapamietaj (Nowak, 30);
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; } };
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 prywatne int 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; }
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.
Funkcja typu inline: • inlineint Zaokr ( float Liczba); • { • return (Liczba + 0.5); • } • 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
Funkcja składowa zdefiniowana poza definicją klasy może być typu inline, ale trzeba to zaznaczyć pisząc słowo inline, np.: • inlinevoid Osoba :: Pisz ( ) • { • //ciało funkcji • }
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ładowejFun.
Składnik publicznyL2 oprócz tego, że może być dostępny w funkcji składowejFun, 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;
Zasłanianie nazw (PROG57.CPP) Przesyłanie do funkcji argumentów będących obiektami • Przez domniemanie zakłada się, że obiekt jest przesyłany do funkcjiprzez 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ę.
void Prezentacja (Osoba &Ktos) • { • cout <<Mam zaszczyt przedstawić Państwu,\n • <<Oto we własnej osobie: ; • cout << Ktos . Pisz_Dane (); • }
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; }// konstruktor • void Schowaj ( int L ) { Liczba = L; } • int Zwracaj ( ) {return Liczba; } • } ; (PROG59.CPP)
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.
Konstruktor • 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 • Klasa może posiadać więcej niż jeden konstruktor; • Jest to możliwe dzięki mechanizmowi przeładowaniafunkcji;
Konstruktor • 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 ?
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. Destruktorwywoływany jest wtedy, gdy obiekt danej klasy ma być zlikwidowany.
Destruktor • 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 ~. (p. PROG156.CPP, PROG157a.CPP)
Destruktor • 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;
Pola • Polaklasy 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 • 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.
Funkcje • 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);
Wskaźnik this • 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: Class Punkt { private: Punkt *this; int x, y; public: Punkt (int _x, int _y); ~Punkt() .... };
Wskaźnikthis • 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!
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.
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_Poziomaprzedefiniować (przesłonić) funkcję Pokaz().
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.
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.