Funkcje i klasy zaprzyjaźnione Funkcje zaprzyjaźnione Obiekty klas mogą być argumentami zwykłych funkcji i metod innych klas enkapsulacja i hermetyzacja danych funkcje nie będące składnikiem klasy mają dostęp tylko do publicznych składników klasy niekiedy to jest niekorzystne (przykład: dom, kwiatki) o klasa: dom składowe prywatne: kwiatki o gdy urlop, to możliwe wyjścia: zmienić status kwiatków albo poprosić przyjaciela w C++ jest możliwość, by funkcja nie będąca składnikiem klasy miała do niej pełny dostęp warunek dostępu deklaracja przyjaźni w ciele klasy: Przykład: friend typ_wyniku nazwa_funkcji(lista_argumentów); class samochod int nr; static int ile_aut; int cena; samochod(int ll) nr=ll; ile_aut++; friend void dane(samochod &); int samochod::ile_aut=1000; void dane(samochod &a) cout << Auto nr: <<a.nr <<endl; cout << Calkowita ilosc aut: <<a.ile_aut <<endl; samochod a1(1001); dane(a1);
uwagi na temat deklaracji przyjaźni: o deklaracja musi być uczyniona w ciele klasy (to klasa określa kto jest jej przyjacielem, a nie funkcja - z kim się przyjaźni) o funkcja przyjaciel ma pełny dostęp do składników klasy (nie jest możliwy dostęp selektywny) o funkcja zaprzyjaźniona nie jest składnikiem klasy nie ma wskaźnika this obiektu odnosi się do składnika obiektu poprzez obiekt.składnik lub wskaźnik->składnik jaki jest sens deklarowania relacji przyjaźni z zewnętrzną funkcją czy nie lepiej uczynić z niej metody klasy? często tak, ale funkcje zewnętrzne mogą mieć pewne przewagi: o funkcja może być przyjacielem więcej niż jednej klasy o funkcja zaprzyjaźniona może wykonywać na swoich argumentach konwersje o dzięki mechanizmowi deklaracji przyjaźni możemy pozwolić na pełny dostęp do obiektu klasy funkcjom napisanym w innych językach Przykład: funkcja zaprzyjaźniona z dwoma klasami #include <stdio.h> #include <string.h> class kwadrat; class punkt int x, y; char nazwa[20]; punkt(int a, int b, char *opis); void przesun(int n, int m) x+=n; y+=m; friend int sedzia(punkt & p, kwadrat & k); class kwadrat int x, y, bok; char nazwa[20]; kwadrat(int a, int b, int bb, char *opis); friend int sedzia(punkt & p, kwadrat & k); // deklaracja zapowiadająca // konstruktor - deklaracja // konstruktor - deklaracja
punkt::punkt(int a, int b, char* opis) x=a; y=b; strcpy(nazwa,opis); // konstruktor - definicja kwadrat::kwadrat(int a, int b, int bb, char* opis) // konstruktor - definicja x=a; y=b; bok=bb; strcpy(nazwa,opis); int sedzia(punkt & p, kwadrat & k) printf("polozenie: (%d,%d)\n",p.x,p.y); if ( (p.x>=k.x)&&(p.x<=(k.x+k.bok)) && (p.y>=k.y)&&(p.y<=(k.y+k.bok))) printf("%s lezy na tle %s\n",p.nazwa,k.nazwa); return 1; else printf("aut! %s jest na zewnatrz %s\n",p.nazwa,k.nazwa); return 0; main() punkt pi(20,20,"pilka"); kwadrat bo(10,10,40,"boiska"); sedzia(pi,bo); printf("kopiemy pilke dopoki jest w boisku!\n"); while (sedzia(pi,bo)) pi.przesun(20,20); Uwaga: w czasie deklaracji klasy punkt musimy podać typy argumentów funkcji zaprzyjaźnionej sedzia w tym typ kwadrat, który nie jest jeszcze zdefiniowany. Gdy zmienimy kolejność klas, to sytuacja się nie zmieni. Wyjście deklaracja zapowiadająca (zwiastująca): class kwadrat;
Wydruk programu polozenie: (20,20) pilka lezy na tle boiska kopiemy pilke dopóki jest w boisku! polozenie (20,20) pilka lezy na tle boiska polozenie (40,40) pilka lezy na tle boiska polozenie (60,60) Aut! pilka jest na zewnatrz boiska na ogół w ciele klasy tylko deklaracja funkcji zaprzyjaźnionej, definicja jest na zewnątrz ciała może zdarzyć, że definicja definicja klasy zaprzyjaźnionej też jest w ciele klasy; wtedy to ta funkcja: o jest typu inline o nie staje się pomimo to metodą klasy, tylko zostaje przyjacielem o pozostaje w zasięgu leksykalnym klasy (może wykorzystywać z dokonanych w klasie instrukcji typedef oraz określonych tam typów wyliczeniowych enum) gdy nazwa funkcji zaprzyjaźnionej jest przeładowana, to przyjacielem klasy jest tylko ta wersja funkcji, która ma listę argumentów odpowiadającą liście w deklaracji przyjaźni dokonanej w klasie class K // friend int fun(int, K &); //.. int fun(char a, int b); int fun(k a, int b); int fun(int a, K &b); // to jest funkcja zaprzyjaźniona
funkcja zaprzyjaźniona z daną klasą może być metodą innej klasy ma ona wtedy dostęp do wszystkich składowych obu klas jeżeli funkcja zaprzyjaźniona z klasą A jest metodą klasy B, to wymaga się, by w momencie dokonywania deklaracji przyjaźni w A, klasa B musi być już znana to wymusza określony porządek definicji klas: class A; class B //.. int fun(a &); //.. class A //.. friend int B::fun(A &); //.. Przykład: modyfikujemy poprzedni przykład, by funkcja sędzia była metodą klasy kwadrat, zaprzyjaźnioną z klasą punkt #include <stdio.h> #include <string.h> class punkt; class kwadrat int x, y, bok; char nazwa[20]; kwadrat(int a, int b, int bb, char *opis); int sedzia(punkt & p); class punkt int x, y; char nazwa[20]; punkt(int a, int b, char *opis); void przesun(int n, int m) x+=n; y+=m; friend int kwadrat::sedzia(punkt & p); // deklaracja zapowiadająca // konstruktor - deklaracja // konstruktor - deklaracja
punkt::punkt(int a, int b, char* opis) x=a; y=b; strcpy(nazwa,opis); kwadrat::kwadrat(int a, int b, int bb, char* opis) x=a; y=b; bok=bb; strcpy(nazwa,opis); int kwadrat::sedzia(punkt & p) printf("polozenie: (%d,%d)\n",p.x,p.y); if ( (p.x>=x)&&(p.x<=(x+bok)) && (p.y>=y)&&(p.y<=(y+bok))) printf("%s lezy na tle %s\n",p.nazwa,nazwa); return 1; else printf("aut! %s jest na zewnatrz %s\n",p.nazwa,nazwa); return 0; int main() punkt pi(20,20,"pilka"); kwadrat bo(10,10,40,"boiska"); bo.sedzia(pi); printf("kopiemy pilke dopoki jest w boisku!\n"); while (bo.sedzia(pi)) pi.przesun(20,20); system("pause"); return 0; ogólnie gdy chcemy, by funkcja miała pełny dostęp do składowych dwóch klas, to możemy to zrealizować jako: o funkcję przyjaciela obu klas o metoda jednej a funkcja zaprzyjaźniona drugiej klasy
Klasy zaprzyjaźnione jeżeli klasa A deklaruje przyjaźń ze wszystkimi funkcjami składowymi klasy B, to możemy to zapisać jako deklarację przyjaźni z klasą B: class A friend class B; // ciało klasy A jeżeli klasa A deklaruje przyjaźń w stosunku do klasy B, to nie wynika z tego nic na temat stosunku klasy B do klasy A (przyjaźń nie jest symetryczna) jeżeli klasa A deklaruje przyjaźń w stosunku do klasy B, zaś B w stosunku do klasy C, to z tego nie wynika nic na temat stosunku A do C (przyjaźń nie jest przechodnia) istnieje możliwość, aby klasy przyjaźniły się wzajemnie nie da się zrobić tak, by klasa A przyjaźniła się z kilkoma wybranymi metodami klasy B, zaś B z kilkoma wybranymi metodami klasy A (powód wymóg, by w momencie, w którym dokonujemy deklaracji przyjaźni do metody danej klasy była ona już zdefiniowana) jedyne możliwe rozwiązanie wzajemna deklaracja przyjaźni obu klas jako całości class B; class A friend class B; //.. reszta ciała klasy A class B friend class A; //.. reszta ciała klasy B // deklaracja zapowiadająca zakładamy, że zakres ważności funkcji zaprzyjaźnionej z klasą jest taki sam, jak zakres ważności klasy. Na ogół zakres ważności klasy globalny. Gdyby zakres funkcji zaprzyjaźnionej ograniczony do pliku (klauzula static) błąd kompilacji.
Funkcje zaprzyjaźnione podsumowanie 1. Pojedyńcza klasa, funkcja nie bedąca jej funkcją składową class K private: friend void fz(int xa, K & k); wywołanie: K k; fz(x,k); w ciele funkcji fz: k.x=k.x+x;
2. Dwie klasy, funkcja nie będąca funkcją składową żadnej z nich ma mieć dostęp do wszystkich pól po class K2; class K1 private: friend void fz(k1 & k1, K2 & k2); class K2 private: friend void fz(k1 & k1, K2 & k2); wywołanie: K1 o1; K2 o2; fz(o1, o2); w ciele funkcji fz: o1.x=o1.x+o2.x; 3. Dwie klasy, funkcja fs1z2 ma być funkcją składową pierwszej klasy a zaprzyjaźnioną drugiej class K2; class K2; class K1
private: void fs1z2(k2 & k2); class K2 private: friend void K1::fsz2(K2 & k2); wywołanie: K1 o1; K2 o2; o1.fs1z2(o2); w ciele funkcji fs1z2: x=x+o2.x;