Funkcja zaprzyjaźniona danej klasy może być też funkcją składową zu-pełnie innej klasy. Taka funkcja ma dostęp do prywatnych danych swojej klasy i do danych klasy, z którą się przyjaźni. Kolejny przykład ilustruje to zagadnienie. Mamy dwie klasy - klasę prostokat i klasę punkt. Klasa prostokat definiuje prostokąt przy pomocy współrzędnych dwóch punktów – lewego dolnego rogu prostokąta i prawego górnego rogu prostokąta. Kla-sa punkt opisuje punkt na podstawie jego współrzędnych kartezjańskich.
Mając dany punkt i dany prostokąt należy określić czy punkt znajduje się wewnątrz prostokąta czy też leży poza nim.
W programie deklarujemy dwie klasy – punkt i prostokat z konstruk-torami. Funkcja miejsce() jest funkcją składową klasy prostakat i jest za-przyjaźniona z klasą punkt. W programie testującym wywołujemy funkcję miejsce(), aby ustalić położenie punktu względem prostokąta.
Listing 6.9. Dostęp do składowych prywatnych; funkcje zaprzyjaźnione
1#include <i o s t r e a m >
#include <c o n i o >
3 using namespace s t d ;
c l a s s punkt ; // d e k l a r a c j a z a p o w i a d a j a c a
5
c l a s s p r o s t o k a t
7 { i n t xp , yp , xk , yk ; public:
9 p r o s t o k a t ( i n t xpo , i n t ypo , i n t xko , i n t yko ) ; void m i e j s c e ( punkt &p ) ;
11 } ;
c l a s s punkt
13 { i n t x1 , y1 ; public:
15 punkt (i n t ax , i n t ay ) ;
friend void p r o s t o k a t : : m i e j s c e ( punkt &p ) ;
17 } ;
6.3. Funkcja składowa zaprzyjaźniona z inną klasą 135
Po uruchomieniu tego programu mamy następujący wydruk:
punkt l e z y w p o l u
Jak widzimy deklaracja klasy prostokat ma postać:
c l a s s p r o s t o k a t
Funkcja miejsce() jest zwykłą funkcją składową klasy prostokat. W dekla-racji funkcji składowej miejsce() argumentem jest obiekt klasy punkt, dla-tego konieczna jest deklaracja zapowiadająca klasę punkt. Deklaracja klasy punkt ma postać:
c l a s s punkt { i n t x1 , y1 ;
public:
punkt (i n t ax , i n t ay ) ;
136 6. Funkcje zaprzyjaźnione
friend void p r o s t o k a t : : m i e j s c e ( punkt &p ) ; } ;
Deklaracja funkcji zaprzyjaźnionej ma postać:
friend void p r o s t o k a t : : m i e j s c e ( punkt &p ) ;
W deklaracji zaprzyjaźnionej funkcji miejsce() musimy podać nazwę klasy, w której ta funkcja jest funkcją składową, w naszym przypadku jest to klasa prostokat.
Bardzo często argumentuje się, że nie należy nadużywać funkcji zaprzy-jaźnionych, ponieważ istotą programowania obiektowego jest ukrywanie in-formacji. Funkcja zaprzyjaźniona nie jest składową klasy a mimo tego ma dostęp do danych prywatnych. W wielu jednak przypadkach zastosowanie funkcji zaprzyjaźnionych znacznie usprawnia algorytm. Klasycznym przy-kładem jest program do wykonania mnożenie wektora przez macierz. Załóż-my, że są zdefiniowane dwie klasy: wektor i macierz. Każda z nich ukrywa swoje dane i dostarcza odpowiedni zbiór operacji do działania na obiektach swojego typu. Należy zdefiniować funkcję mnożącą macierz przez wektor.
Ustalmy konkretne warunki. Niech wektor ma cztery elementy indeksowane 0,1,2,3. Wektor zapamiętywany będzie w postaci tablicy jednowymiarowej.
Macierz ma rozmiar 4x4 i będzie zapamiętywana w postaci tablicy dwuwy-miarowej. Funkcja obliczająca iloczyn musi korzystać z danych pochodzą-cych z dwóch klas, jest więc oczywiste, że musi być z nimi zaprzyjaźniona. W tym przypadku konieczne jest także użycie deklaracji referencyjnej(zwaną także deklaracją zapowiadającą, albo referencją zapowiadającą), w przypad-ku deklaracji klasy wekt musi wystąpić deklaracja klasy macierz i podobnie w deklaracji klasy macierz musi wystąpić deklaracja klasy wekt.
Jest to konieczne, gdyż w klasie wekt w deklaracji iloczyn() istnieje odwo-łanie do niezadeklarowanej jeszcze klasy macierz. Deklaracje poszczególnych klas i ich definicje zapisujemy w oddzielnych plikach. Musimy na początku opracować klasę wekt do obsługi wektorów. Deklaracja klasy wekt może mieć postać:
Listing 6.10. Iloczyn macierzy przez wektor; funkcje zaprzyjaźnione
1 // p l i k w e k t o r 1 . h , d e k l a r a c j a k l a s y wekt
3#i f n d e f _WEKTOR1_H
#define _WEKTOR1_H
5
c l a s s m a c i e r z ; // d e k l a r a c j a z a p o w i a d a j a c a
7
c l a s s wekt
9 {
6.3. Funkcja składowa zaprzyjaźniona z inną klasą 137
Podobnie w deklaracji klasy macierz musimy zastosować deklaracje zapo-wiadającą klasy wekt. Deklaracja klasy macierz do obsługi macierzy może mieć postać:
Listing 6.11. Iloczyn macierzy przez wektor; funkcje zaprzyjaźnione
// p l i k m a c i e r z . h , d e k l a r a c j a k l a s y m a c i e r z
W klasie wekt mamy deklarację funkcji składowej pokaz(), która służy do wyświetlania składowych wektora. Definicja funkcji pokaz() może mieć po-stać:
Listing 6.12. Iloczyn macierzy przez wektor; funkcje zaprzyjaźnione
1 // p l i k p o k a z . cpp , d e f i n i c j a s k l a d o w e j p o k a z ( )
#include <i o s t r e a m . h>
3#include " wektor1 . h"
5 void wekt : : pokaz ( ) { i n t i ;
7 f o r ( i =0; i < 4 ; i ++) c o u t << v [ i ] << " ␣ "; c o u t << " \n";
9 }
138 6. Funkcje zaprzyjaźnione W klasie macierz jest zadeklarowany konstruktor macierz(). Jego definicja może mieć postać:
Listing 6.13. Iloczyn macierzy przez wektor; funkcje zaprzyjaźnione
1 // p l i k konmac . cpp , d e f i n i c j a k o n s t r u k t o r a k l a s y m a c i e r z
Należy opracować funkcje zaprzyjaźnioną iloczyn(). Definicja tej funkcji (wykonuje ona mnożenie macierzy przez wektor) może mieć postać:
Listing 6.14. Iloczyn macierzy przez wektor; funkcje zaprzyjaźnione
1 // p l i k i l o c z y n . cpp
// d e f i n i c j a f u n k c j i i l o c z y n
3#include " wektor1 . h"
#include " m a c i e r z . h"
Możemy zacząć testować mnożenie macierzy przez wektor. Funkcje ilo-czyn() możemy wykorzystać do realizacji przekształceń w przestrzeni 3D. W grafice komputerowej wykorzystuje się tzw. współrzędne jednorodne. W tych współrzędnych punkt (x,y,z) reprezentowany jest jako punkt w przestrzeni 4-wymiarowej (x,y,z,1). Poszczególne przekształcenia takie jak przesuniecie, skalowanie czy obroty reprezentowane są macierzami 4x4. Aby otrzymać położenie nowego punktu P’ należy punkt początkowy P pomnożyć przez macierz przekształcenia M:
P′= M • P (6.1)
6.3. Funkcja składowa zaprzyjaźniona z inną klasą 139 Przesunięcie w przestrzeni 3D ma postać:
T (dx, dy, dz) =
Operacja skalowania przedstawiana jest następująco:
S(sx, sy, sz) =
Mamy trzy macierze reprezentujące obroty wokół osi x,y i z. Obrót wokół osi z przedstawia się następująco:
Rz(θ) =
Obrót wokół osi x ma postać:
Rx(θ) =
Obrót wokół osi y ma postać:
Ry(θ) =
Te transformacje można łatwo zweryfikować: wynikiem obrotu o 90o jed-nostkowego wektora osi xh 1 0 0 1 iT powinien być jednostkowy wektor h 0 1 0 1 iT osi y.
Ogólnie mamy do czynienia z mnożeniem macierzy przez wektor:
W programie testującym wyliczymy współrzędne punktu po przekształ-ceniach.
140 6. Funkcje zaprzyjaźnione Listing 6.15. iloczyn macierzy przez wektor; funkcje zaprzyjaźnione
#include <i o s t r e a m . h>
2#include <c o n i o . h>
#include " wektor1 . h"
4#include " m a c i e r z . h"
Po uruchomieniu programu testującego otrzymamy następujący wynik:
punkt poczatkowy : 1 0 0 1 po t r a n s l a c j i : 3 3 1 1 po s k a l o w a n i u : 2 0 0 1 po o b r o c i e o PI /2 : 0 1 0 1