Funkcje i klasy zaprzyjaźnione
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 32 z 104.
Czasami funkcja lub klasa spoza Twojej klasy potrzebuje dostępu do jej prywatnych składowych. Zamiast upubliczniać te składowe, C++ udostępnia słowo kluczowe friend, które pozwala przyznać wybranym elementom dostęp, zachowując jednocześnie hermetyzację.
Funkcja zaprzyjaźniona jest deklarowana wewnątrz klasy, ale nie jest jej składową. Może uzyskiwać dostęp do prywatnych i chronionych składowych klasy:
class Box {
double width;
public:
Box(double w) : width(w) {}
// Zadeklaruj printWidth jako funkcję zaprzyjaźnioną
friend void printWidth(const Box& b);
};
// Definicja funkcji zaprzyjaźnionej – NIE jest metodą klasy
void printWidth(const Box& b) {
std::cout << b.width; // Może uzyskać dostęp do prywatnego pola
}Zauważ, że printWidth jest zdefiniowana poza klasą, bez operatora rozstrzygania zakresu. To zwykła funkcja, która ma po prostu specjalne uprawnienia dostępu.
Możesz też uznać całą klasę za zaprzyjaźnioną, przyznając wszystkim jej funkcjom składowym dostęp do składowych prywatnych:
class Engine {
int horsepower;
friend class Mechanic; // Mechanic ma dostęp do prywatnych składowych
public:
Engine(int hp) : horsepower(hp) {}
};
class Mechanic {
public:
void diagnose(const Engine& e) {
std::cout << e.horsepower; // Dozwolone — Mechanic jest klasą zaprzyjaźnioną
}
};Przyjaźń nie jest wzajemna — jeśli Engine deklaruje Mechanic jako przyjaciela, Mechanic może uzyskać dostęp do prywatnych składowych Engine, ale nie odwrotnie. Używaj deklaracji przyjaźni oszczędnie, ponieważ powodują silne sprzężenie między klasami. Są najbardziej przydatne przy przeciążaniu operatorów i w przypadku ściśle powiązanych funkcji pomocniczych.
Wyzwanie
ŁatwyZbudujmy bezpieczny system przesyłania wiadomości, w którym klasa MessageReader potrzebuje specjalnego dostępu, aby odczytywać zaszyfrowane wiadomości przechowywane w klasie SecureMessage. To idealny przypadek do użycia funkcji zaprzyjaźnionych i klas zaprzyjaźnionych.
Utworzysz dwa pliki, aby uporządkować kod:
SecureMessage.h: Zdefiniuj klasęSecureMessage, która przechowuje prywatne dane wiadomości i przyznaje wybranym zaufanym elementom dostęp do nich. Twoja klasa powinna mieć:- Pola prywatne:
content(string) isecretKey(int) - Konstruktor przyjmujący treść i klucz tajny
- Zadeklaruj
MessageReaderjako klasę zaprzyjaźnioną, aby mogła uzyskiwać dostęp do pól prywatnych - Zadeklaruj zaprzyjaźnioną funkcję
decryptMessage(const SecureMessage& msg, int key), która zwraca treść tylko wtedy, gdy podany klucz pasuje do klucza tajnego; w przeciwnym razie zwraca"Access Denied"
Następnie zdefiniuj klasę
MessageReaderw tym samym pliku nagłówkowym:- Metodę
readContent(const SecureMessage& msg), która bezpośrednio uzyskuje dostęp do prywatnego polacontentwiadomości i je zwraca - Metodę
verifyKey(const SecureMessage& msg, int key), która sprawdza, czy podany klucz pasuje do polasecretKeywiadomości, i zwraca"Valid"lub"Invalid"
Na koniec zdefiniuj zaprzyjaźnioną funkcję
decryptMessagepo definicjach klas.- Pola prywatne:
main.cpp: Pokaż działanie zarówno funkcji zaprzyjaźnionych, jak i klas zaprzyjaźnionych. Wczytaj treść wiadomości, klucz tajny i klucz testowy ze standardowego wejścia (każdy w osobnym wierszu), a następnie:- Utwórz obiekt
SecureMessagez treścią i kluczem tajnym - Utwórz obiekt klasy
MessageReader - Wypisz
"Reader access: <content>", używając metodyreadContent()czytnika - Wypisz
"Key verification: <result>", używając metodyverifyKey()z kluczem testowym - Wypisz
"Decrypt with test key: <result>", używając zaprzyjaźnionej funkcji z kluczem testowym - Wypisz
"Decrypt with correct key: <result>", używając zaprzyjaźnionej funkcji z oryginalnym kluczem tajnym
- Utwórz obiekt
Pamiętaj, że funkcje zaprzyjaźnione definiuje się poza klasą, bez operatora rozstrzygania zakresu: są to zwykłe funkcje ze specjalnymi uprawnieniami dostępu. Klasa MessageReader, jako klasa zaprzyjaźniona, może bezpośrednio uzyskiwać dostęp do prywatnych pól klasy SecureMessage we wszystkich swoich metodach.
Przekonwertuj klucze wczytane jako ciągi znaków na liczby całkowite za pomocą std::stoi(). Dołącz plik nagłówkowy w pliku main.cpp, używając #include "SecureMessage.h".
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "SecureMessage.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
string messageContent;
string secretKeyStr;
string testKeyStr;
getline(cin, messageContent);
getline(cin, secretKeyStr);
getline(cin, testKeyStr);
int secretKey = stoi(secretKeyStr);
int testKey = stoi(testKeyStr);
// TODO: Utwórz SecureMessage z treścią i tajnym kluczem
// TODO: Utwórz obiekt MessageReader
// TODO: Wypisz "Reader access: <content>", używając readContent()
// TODO: Wypisz "Key verification: <result>", używając verifyKey() z kluczem testowym
// TODO: Wypisz "Decrypt with test key: <result>", używając decryptMessage z kluczem testowym
// TODO: Wypisz "Decrypt with correct key: <result>", używając decryptMessage z tajnym kluczem
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online