Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Programowanie obiektowe PO PO - LAB 3 Wojciech Pieprzyca.

Podobne prezentacje


Prezentacja na temat: "Programowanie obiektowe PO PO - LAB 3 Wojciech Pieprzyca."— Zapis prezentacji:

1 Programowanie obiektowe PO PO - LAB 3 Wojciech Pieprzyca

2 Funkcja zaprzyjaźniona z klasą to funkcja, która ma dostęp do wszystkich składników klasy pomimo tego, że sama nie jest metodą tej klasy. class punkt { friend void stan(punkt &p); private: int x,y; }; void stan(punkt &p) { cout << Wsp. X: << p.x << endl; cout << Wsp. Y: << p.y << endl; } Funkcja stan ma dostęp do prywatnych składowych x i y klasy punkt. Funkcje zaprzyjaźnione (I) PO

3 Funkcji zaprzyjaźnionych używa się zwykle wtedy, gdy utworzenie jej jako metody klasy jest utrudnione lub też gdy chcemy mieć dostęp do składowych kilku klas jednocześnie. Oprócz funkcji zaprzyjaźnionych globalnie, klasa może także deklarować przyjaźń z inną klasą lub z konkretną metodą klasy. Przykładem może być funkcja mnożąca dwie macierze, która musi mieć jednocześnie dostęp do dwóch obiektów klasy macierz. class macierz { friend macierz mnoz(Macierz a, Macierz b) private: int el[5][5]; }; macierz mnoz(Macierz a, Macierz b) { …… } Funkcje zaprzyjaźnione (II) PO

4 Przykład 2: class prostokat; //zapowiedz pozniejszej definicji klasy prostokat class kwadrat { private: int a; public: kwadrat(int _a); friend int pole(kwadrat k, prostokat p); }; class prostokat { private: int a,b; public: prostokat(int _a, int _b); friend int pole(kwadrat k, prostokat p); }; Funkcje zaprzyjaźnione (III) PO

5 //konstruktor dla klasy kwadrat kwadrat::kwadrat(int _a) { a = _a; } //konstruktor dla klasy prostokat prostokat::prostokat(int _a, int _b) { a = _a; b = _b; } Funkcje zaprzyjaźnione (IV) PO

6 //fukcja pole ma dostęp do składowych zarówno klasy kwadrat //jak i klasy prostokat int pole(kwadrat k, prostokat p) { int pk=0, pp=0; pk = k.a*k.a; pp = p.a*p.b; cout << Pole kwadratu: << pk << endl; cout << Pole prostokata: << pp << endl; } int main() { kwadrat k(3); prostokat p(3,4); pole(k,p); } Funkcje zaprzyjaźnione (V) PO

7 Funkcja wirtualna pozwala w klasach pochodnych zastępować implementację metod dostarczonych przez klasę bazową. W tym celu najczęściej wykorzystuje się zmienną wskaźnikową, która może wskazywać na obiekty klasy bazowej i obiekty klas pochodnych. W przypadku istnienia kilku metod zastępczych w różnych klasach pochodnych, to która metoda zostanie uruchomiona jest uzależnione od typu obiektu na jaki aktualnie wskazuje zmienna wskaźnikowa. W podanym przykładzie pojawią się 4 definicje metody pole, której zadaniem będzie obliczanie pola odpowiedniej figury. Klasa figura jest tutaj klasą bazową dla 3 klas pochodnych: prostokat, trojkat, romb. Funkcje wirtualne (I) PO figura prostokattrojkatromb

8 //klasa bazowa – figura class figura { public: float a,b; figura(int _a, int _b); virtual void pole() { cout << Nie zdefiniowana funkcja obliczajaca pole; } }; //klasa pochodna – prostokat class prostokat : public figura { public: prostokat(int a, int b):figura(a,b) {} virtual void pole() { cout << Pole prostokata: << a*b << endl; } }; Funkcje wirtualne (II) PO

9 //klasa pochodna - trojkat class trojkat : public figura { public: trojkat(int a, int b):figura(a,b) {} virtual void pole() { cout << Pole trojkat: << 0.5*a*b << endl; } }; //klasa pochodna – romb class romb : public figura { public: romb(int a, int b):figura(a,b) {} }; //konstruktor klasy figura figura::figura(int _a, int _b) { a = _a; b = _b; } Funkcje wirtualne (III) PO

10 int main() { //zmienna wskaźnikowa, która może wskazywać zarówno na obiekty //klasy bazowej - figura, jak i obiekty klas pochodnych - prostokat, trojkat, romb figura *wsk = 0; prostokat p(4,2); trojkat t(3,4); romb r(2,3); //na początku wskaźnik pokazuje na obiekt typu prostokąt, //wywołana zostanie metoda pole z klasy prostokąt wsk = &p; wsk->pole(); //teraz wskaźnik pokazuje na obiekt typu trójkąt //wywołana zostanie metoda pole z klasy trójkąt wsk = &t; wsk->pole(); //zmiana - wskaźnik pokazuje na obiekt typu romb //wywołana zostanie metoda pole z klasy romb wsk = &r; wsk->pole(); } Funkcje wirtualne (IV) PO

11 Dla wbudowanych typów danych takich jak integer, double, float, itp. określone są operatory +,-,*,:, które wykonują podstawowe operacje arytmetyczne na liczbach tych typów. Okazuje się, że również dla typów (klas) definiowanych przez użytkownika możliwe jest określenie tych i innych operatorów. Wymaga to oczywiście dokładnego określenia tego jakie operacje mają być zrealizowane w ramach wywołania operatora. Mechanizm ten nazywany jest przeładowywaniem operatorów i wymaga zdefiniowania funkcji operatorowych. Typowa funkcja operatorowa ma postać: typ_zwracanego_wyniku operator rodzaj (lista_argumentów) np. zespolona operator+(zespolona a, zespolona b) Jak łatwo można się domyślić operator ten będzie realizował dodawanie dwóch liczb zespolonych. Operatory przeładowane (I) PO

12 Przykład: Mamy daną klasę liczb zespolonych (podobnie jak na zajęciach 1) class zespolona { private: int r,u; public: zespolona(int r, int u); zespolona(); void pokaz(); int pobierzR(); int pobierzU(); }; zespolona::zespolona(int _r, int _u) { r = _r; u = _u; } zespolona::zespolona()//konstruktor bezargumentowy { } Operatory przeładowane (II) PO

13 void zespolona::pokaz() { cout << r << + << u << i << endl; } int zespolona::pobierzR() { return r; } int zespolona::pobierzU() { return u; } Operatory przeładowane (III) PO

14 Dodatkowo określimy funkcję operatorową odpowiedzialną za dodawanie dwóch liczb zespolonych. Zauważmy, ze nie jest to funkcja składowa, ale zwykła funkcja globalna. zespolona operator+(zespolona a, zespolona b) { int r,u; r = a.pobierzR()+b.pobierzR(); u = a. pobierzU()+b.pobierzU(); zespolona wynik(r,u); return wynik; } Operatory przeładowane (IV) PO

15 Pozostaje nam określić operacje wykonywane w funkcji main: void main() { //pierwsza liczba zespolona zespolona a(10,5); //druga liczba zespolona zespolona b(2,3); zespolona suma;//tutaj uzyty zostanie konstruktor bezargumentowy //suma liczb zespolonych obliczana za pomocą operatora + //tutaj zadziala nasz operator zgodnie ze zdefiniowana funkcja operatorowa suma = a+b; //wyswietlenie wartosci liczb i sumy a.pokaz(); b.pokaz(); suma.pokaz(); } Operatory przeładowane (V) PO

16 Kilka zasad dotyczących operatorów: Przeładowywać można tylko i wyłącznie operatory wbudowane tzn. takie które już istnieją w języku C++, nie można wymyślać swoich operatorów, Lista operatorów, które można przeładować jest mimo to dosyć ogromna: + - * / % ^ & | ~ ! = += -= *= /= %= ^= &= |= > >>= <<= == != = && || ++ --, ->* -> new delete () [] Nie można zmieniać priorytetów operatorów tzn. że np. operator * zawsze będzie miał większy priorytet niż +, Nie można też zmienić liczby argumentów, na których działa operator, czyli operator * będzie nadal dwuargumentowy, a operator ++ jednoargumentowy. Przynajmniej jeden z argumentów musi być typu zdefiniowanego przez użytkownika tzn. że nie można po raz drugi zdefiniować jak zachowują się operatory dla typów wbudowanych. Operatory przeładowane (VI) PO

17 W poprzednim przykładzie funkcja operatorowa została zdefiniowana w zakresie globalnym i nie była składową żadnej klasy ani też z nią nie posiadała żadnych innych relacji. Teraz zobaczymy, że funkcja ta może być także metodą składową klasy, wymaga to jednak wprowadzenia pewnych zmian. A zatem jak wygląda funkcja operatorowa będącą składową klasy ? Oto rozwiązanie: zespolona zespolona::operator+(zespolona b) { int rr,uu; rr = r+b.r; uu = u+b.u; zespolona wynik(rr,uu); return wynik; } Różnice na pierwszy rzut oka są mało widoczne, ale po bliższym przyjrzeniu się definicji funkcji widać, że… Funkcje operatorowe jako składowe (I) PO

18 … widać, że funkcja przyjmuje tylko jeden argument, liczbę zespoloną oznaczoną jako b. Powstaje pytanie skąd w takim razie bierzemy wartości dla liczby zespolonej a. Otóż, trzeba zdać sobie sprawę, że każda funkcja (metoda) składowa jest uruchamiana na rzecz jakiegoś obiektu (w tym przypadku np. obiektu typu zespolona reprezentującego liczbę zespoloną a). Nie potrzebujemy już zatem drugiego argumentu (obiektu) funkcji, gdyż dostęp do składowych tego obiektu mamy automatycznie, gdyż ta metoda wywoływana jest właśnie przez ten obiekt. Nie musimy już zatem wywoływać a.pobierzU(), ale samo pobierzU(), podobnie jest z metodą pobierzR(). Ogólna zasada jest taka: Funkcja operatorowa zdefiniowana jako metoda składowa klasy przyjmuje zawsze o jeden argument mniej niż ta sama funkcja operatorowa zdefiniowana jako funkcja globalna. PS. Aby to zadziałało konieczne jest dopisanie nagłówka funkcji operatorowej do definicji klasy, w końcu to będzie nowa metoda klasy!, proszę o tym nie zapomnieć. Funkcje operatorowe jako składowe (II) PO

19 Nie odpowiedzieliśmy sobie jeszcze na pytanie czy zajdą jakieś zmiany w funkcji main ? To zadziwiające, ale nie będzie żadnych zmian. Wywołanie funkcji operatorowej na rzecz obiektu a wykona się niejako za kurtyną, bez naszej wiedzy. Nadal sumowanie liczb zespolonych odbędzie się za pomocą instrukcji: suma = a+b; Wszystko stanie się jasne, gdy uświadomimy sobie, że faktyczne wywołanie operacji a+b ma postać: a.operator+(b) czyli, że to obiekt a wywołuje funkcję operatorową i przekazuje jej jako argument obiekt b. W uproszczeniu możemy napisać a+b, ale realizacja jest taka sama jak powyżej. Funkcje operatorowe jako składowe (III) PO

20 W zasadzie wszystko jest już jasne. Możemy tworzyć funkcje operatorowe w dwojaki sposób: 1)Jako funkcje globalne, 2)Jako składowe klas. Znamy już różnicę i konieczność innego sposobu definiowania funkcji w obu przypadkach. Ale jest jeszcze coś… Zauważmy, że w przypadku budowy funkcji operatorowej jako globalnej, dostęp do składowych klas jest bardzo ograniczony. Zgodnie z zasadą enkapsulacji, możemy odwołać się tylko do składowych publicznych lub też napisać odpowiednie metody akcesorowe i odwoływać się poprzez ich użycie. Pisanie metod akcesorowych dla wszystkich składowych może być jednak uciążliwe. Dlatego czasami lepiej po prostu przyznać funkcjom operatorowym odpowiednie uprawnienia dostępowe do składowych prywatnych klasy. Możemy to zrobić, gdyż znamy już mechanizm zaprzyjaźniania się klasy z jakąś funkcją. W naszym przypadku klasa zespolona zadeklaruje przyjaźń z funkcję operatorową. Funkcje operatorowe a przyjaźń (I) PO

21 Zacznijmy zatem od początku i deklaracji klasy. Pozbędziemy się z niej metod akcesorowych i zadeklarujmy przyjaźń z funkcją operatorową. class zespolona { friend zespolona operator+(zespolona a, zespolona b); private: int r,u; public: zespolona(int r, int u); zespolona(); void pokaz(); }; zespolona::zespolona(int _r, int _u) { r = _r; u = _u; } zespolona::zespolona()//konstruktor bezargumentowy { } Funkcje operatorowe a przyjaźń (II) PO

22 void zespolona::pokaz() { cout << r << + << u << i << endl; } zespolona operator+(zespolona a, zespolona b) { int r,u; //juz nie musimy korzystac z metod akcesorowych r = a.r+b.r; u = a.u+b.u; zespolona wynik(r,u); return wynik; } Funkcja main pozostaje bez zmian. Funkcje operatorowe a przyjaźń (III) PO


Pobierz ppt "Programowanie obiektowe PO PO - LAB 3 Wojciech Pieprzyca."

Podobne prezentacje


Reklamy Google