Klasy znajomych
A Klasa przyjaciół to klasa, która ma dostęp do prywatnych i chronionych członków innej klasy.
Oto przykład:
#include <iostream>
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{ }
// Make the Display class a friend of Storage
friend class Display;
};
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
// Because Display is a friend of Storage, Display members can access the private members of Storage
void displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
void setDisplayIntFirst(bool b)
{
m_displayIntFirst = b;
}
};
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
display.setDisplayIntFirst(true);
display.displayStorage(storage);
return 0;
}Ponieważ Display klasa jest przyjacielem Storage, Display członkowie mogą uzyskać dostęp do prywatnych członków dowolnego Storage obiektu, do którego mają dostęp.
Ten program daje następujący wynik:
6.7 5 5 6.7
Kilka dodatkowych uwag na temat klas znajomych.
Po pierwsze, mimo że Display jest przyjacielem Storage, Display nie ma dostępu do *this wskaźnik Storage obiektów (ponieważ *this jest w rzeczywistości parametrem funkcji).
Po drugie, przyjaźń nie jest wzajemna. To, że Display jest przyjacielem Storage nie oznacza, Storage jest także przyjacielem Display. Jeśli chcesz, aby dwie klasy były dla siebie przyjaciółmi, obie muszą zadeklarować drugą jako przyjaciela.
Nota autora
Przepraszam, jeśli ta klasa jest dla Ciebie zbyt bliska!
Przyjaźń klasowa również nie jest przechodnia. Jeśli klasa A jest przyjacielem B, a B jest przyjacielem C, nie oznacza to, że A jest przyjacielem C.
Dla zaawansowanych czytelników
Przyjaźń też nie jest dziedziczona. Jeśli klasa A czyni B przyjacielem, klasy wywodzące się z B nie są przyjaciółmi A.
Deklaracja klasy zaprzyjaźnionej działa jak deklaracja forward dla zaprzyjaźnionej klasy. Oznacza to, że nie musimy przekazywać dalej deklaracji, że klasa jest zaprzyjaźniona przed dodaniem jej do grona znajomych. W powyższym przykładzie friend class Display działa zarówno jako deklaracja forward Display i deklaracja znajomego.
Funkcje członka znajomego
Zamiast czynić przyjaciela całą klasą, możesz uczynić jednego członka członkiem funkcją przyjaciela. Odbywa się to podobnie do zaprzyjaźniania funkcji niebędącej członkiem, z tą różnicą, że zamiast niej używana jest nazwa funkcji członkowskiej.
Jednak w rzeczywistości może to być trochę trudniejsze, niż oczekiwano. Przekształćmy poprzedni przykład, aby utworzyć Display::displayStorage przyjacielską funkcję członkowską. Możesz spróbować czegoś takiego:
#include <iostream>
class Display; // forward declaration for class Display
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{
}
// Make the Display::displayStorage member function a friend of the Storage class
friend void Display::displayStorage(const Storage& storage); // error: Storage hasn't seen the full definition of class Display
};
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
void displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
return 0;
}Okazuje się jednak, że to nie zadziała. Aby pojedynczy element członkowski stał się przyjacielem, kompilator musi zapoznać się z pełną definicją klasy zaprzyjaźnionej funkcji członkowskiej (a nie tylko deklaracją forward). Ponieważ klasa Storage nie widziała jeszcze pełnej definicji klasy Display , kompilator popełni błąd w momencie, gdy spróbujemy uczynić funkcję składową zaprzyjaźnioną.
Na szczęście można to łatwo rozwiązać, przenosząc definicję klasy Display przed definicją klasy Storage (w tym samym pliku lub przenosząc definicję of Display do pliku nagłówkowego i #włączając go przed zdefiniowaniem Storage ).
#include <iostream>
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
void displayStorage(const Storage& storage) // compile error: compiler doesn't know what a Storage is
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{
}
// Make the Display::displayStorage member function a friend of the Storage class
friend void Display::displayStorage(const Storage& storage); // okay now
};
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
return 0;
}Jednak mamy teraz inny problem. Ponieważ funkcja składowa Display::displayStorage() używa Storage jako parametr referencyjny i właśnie przenieśliśmy definicję Storage poniżej definicji Display, kompilator będzie narzekał, że nie wie, czym jest Storage . Nie możemy tego naprawić, zmieniając kolejność definicji, ponieważ wtedy cofniemy poprzednią poprawkę.
Na szczęście można to również naprawić w kilku prostych krokach. Po pierwsze, możemy dodać class Storage jako deklarację forward, tak aby kompilator zgodził się z odwołaniem do Storage zanim zobaczy pełną definicję klasy.
Po drugie, możemy przenieść definicję Display::displayStorage() poza klasę, po pełnej definicji Storage .
Oto jak to wygląda np.:
#include <iostream>
class Storage; // forward declaration for class Storage
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst)
: m_displayIntFirst { displayIntFirst }
{
}
void displayStorage(const Storage& storage); // forward declaration for Storage needed for reference here
};
class Storage // full definition of Storage class
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue)
: m_nValue { nValue }, m_dValue { dValue }
{
}
// Make the Display::displayStorage member function a friend of the Storage class
// Requires seeing the full definition of class Display (as displayStorage is a member)
friend void Display::displayStorage(const Storage& storage);
};
// Now we can define Display::displayStorage
// Requires seeing the full definition of class Storage (as we access Storage members)
void Display::displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
int main()
{
Storage storage { 5, 6.7 };
Display display { false };
display.displayStorage(storage);
return 0;
}Teraz wszystko skompiluje się poprawnie: deklaracja forward class Storage wystarczy, aby spełnić deklarację Display::displayStorage() wewnątrz klasy Display . Pełna definicja Display spełnia deklarację Display::displayStorage() jako przyjaciela Storage. A pełna definicja klasy Storage wystarczy, aby spełnić definicję funkcji składowej Display::displayStorage().
Jeśli jest to nieco zagmatwane, zobacz komentarze w programie powyżej. Najważniejsze jest to, że deklaracja przekazania klasy spełnia odniesienia do klasy. Jednakże uzyskanie dostępu do elementów klasy wymaga, aby kompilator zapoznał się z pełną definicją klasy.
Jeśli wydaje się to uciążliwe – rzeczywiście tak jest. Na szczęście ten taniec jest konieczny tylko dlatego, że staramy się zrobić wszystko w jednym pliku. Lepszym rozwiązaniem jest umieszczenie definicji każdej klasy w oddzielnym pliku nagłówkowym, a definicje funkcji składowych w odpowiednich plikach .cpp. W ten sposób wszystkie definicje klas będą dostępne w plikach .cpp i nie będzie konieczne przestawianie klas ani funkcji!
Czas quizu
Pytanie nr 1
W geometrii punkt to pozycja w przestrzeni. Możemy zdefiniować punkt w przestrzeni 3D jako zbiór współrzędnych x, y i z. Na przykład Point { 2.0, 1.0, 0.0 } byłby punktem w przestrzeni współrzędnych x=2,0, y=1,0 i z=0,0.
W fizyce wektor to wielkość, która ma wielkość (długość) i kierunek (ale nie ma położenia). Możemy zdefiniować wektor w przestrzeni 3D jako wartość x, y i z reprezentującą kierunek wektora wzdłuż osi x, y i z (można z nich wyprowadzić długość). Na przykład Vector { 2.0, 0.0, 0.0 } byłby wektorem reprezentującym kierunek wzdłuż dodatniej osi x (tylko) o długości 2,0.
A Vector można zastosować do a Point w celu przesunięcia Point do nowej pozycji. Odbywa się to poprzez dodanie kierunku wektora do położenia punktu, aby uzyskać nową pozycję. Na przykład Point { 2.0, 1.0, 0.0 } + Vector { 2.0, 0.0, 0.0 } dałoby Point { 4.0, 1.0, 0.0 }.
Takie punkty i wektory są często używane w grafice komputerowej (z punktami reprezentującymi wierzchołki kształtu, a wektory reprezentują ruch kształtu).
Biorąc pod uwagę następujący program:
#include <iostream>
class Vector3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Vector3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{
}
void print() const
{
std::cout << "Vector(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
};
class Point3d
{
private:
double m_x{};
double m_y{};
double m_z{};
public:
Point3d(double x, double y, double z)
: m_x{x}, m_y{y}, m_z{z}
{ }
void print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
void moveByVector(const Vector3d& v)
{
// implement this function as a friend of class Vector3d
}
};
int main()
{
Point3d p { 1.0, 2.0, 3.0 };
Vector3d v { 2.0, 2.0, -3.0 };
p.print();
p.moveByVector(v);
p.print();
return 0;
}> Krok #1
Utwórz Point3d przyjazną klasę Vector3d i zaimplementuj funkcję Point3d::moveByVector().
> Krok #2
Zamiast tworzyć klasę Point3d przyjacielem klasy Vector3d, uczyń funkcję składową Point3d::moveByVector przyjacielem klasy Vector3d.
> Krok #3
Zaimplementuj rozwiązanie z poprzedniego kroku, używając 5 oddzielnych plików: Point3d.h, Point3d.cpp, Vector3d.h, Vector3d.cpp i main.cpp.
Dziękujemy czytelnikowi Shivie za sugestię i rozwiązanie.

