Klasy zagnieżdżone i wewnętrzne
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 38 z 104.
Klasa zagnieżdżona to klasa zdefiniowana wewnątrz innej klasy. Ta technika pomaga organizować kod poprzez grupowanie powiązanych typów i może zwiększyć hermetyzację przez ukrywanie szczegółów implementacji.
class LinkedList {
public:
class Node { // Klasa zagnieżdżona
int data;
Node* next;
public:
Node(int d) : data(d), next(nullptr) {}
friend class LinkedList;
};
private:
Node* head;
public:
LinkedList() : head(nullptr) {}
};Zagnieżdżona klasa Node jest ściśle powiązana z LinkedList — ma sens tylko w tym kontekście. Zagnieżdżając ją, sygnalizujemy tę relację i unikamy zaśmiecania globalnej przestrzeni nazw ogólną nazwą, taką jak „Node”.
Klasy zagnieżdżone podlegają tym samym zasadom dostępu, których się nauczyłeś. Klasa zagnieżdżona zadeklarowana w sekcji prywatnej jest dostępna tylko w obrębie klasy zewnętrznej.
Gdy zostanie zadeklarowany jako publiczny, kod zewnętrzny może go używać za pomocą operatora zakresu:
LinkedList::Node node(42); // Tylko jeśli Node jest publicznaWażny szczegół dotyczący kierunku dostępu: klasa zagnieżdżona jest składową klasy zewnętrznej i ma takie same prawa dostępu jak każda inna składowa, więc może uzyskiwać dostęp do prywatnych składowych klasy zewnętrznej bez deklaracji friend. Nie działa to jednak automatycznie w odwrotną stronę: klasa zewnętrzna nie ma dostępu do prywatnych składowych klasy zagnieżdżonej.
Dlatego Node deklaruje friend class LinkedList; w powyższym przykładzie: deklaracja friend przyznaje otaczającej klasie LinkedList dostęp do prywatnych składowych Node, co nie dzieje się automatycznie w tym jednym kierunku.
Wyzwanie
ŁatwyZbudujmy prosty system zarządzania zadaniami, w którym zadania są zorganizowane w ramach projektu. To idealny przykład zastosowania klas zagnieżdżonych: Task ma sens tylko w kontekście Project, dlatego zagnieździmy ją w tej klasie, aby uporządkować kod i uniknąć zaśmiecania globalnej przestrzeni nazw.
Utworzysz dwa pliki, aby uporządkować kod:
Project.h: Zdefiniuj klasęProject, która zawiera zagnieżdżoną klasęTask.Zagnieżdżona klasa
Taskpowinna być zadeklarowana w sekcji public klasyProject, aby można było uzyskać do niej dostęp z zewnątrz. Powinna przechowywać nazwę zadania (string) i stan jego ukończenia (bool). Dodaj konstruktor, który przyjmuje nazwę i domyślnie ustawia zadanie jako nieukończone. Dodaj metodycomplete(), która oznacza zadanie jako ukończone,isComplete(), która sprawdza jego stan, orazgetName(), która pobiera nazwę.Sama klasa
Projectpowinna przechowywać nazwę projektu (string) i kolekcję wskaźnikówTask. Dodaj konstruktor przyjmujący nazwę projektu, metodęaddTask(const std::string& taskName), która tworzy nowe zadanie i dodaje je do projektu, metodęcompleteTask(const std::string& taskName), która wyszukuje zadanie po nazwie i oznacza je jako ukończone, oraz metodędisplayStatus(), która wyświetla nazwę projektu, a następnie stan każdego zadania.Ponieważ
Taskjest zagnieżdżona wProject, klasaProjectmoże bezpośrednio obsługiwać zadania. Nie zapomnij zwolnić pamięci przydzielonej dynamicznie na zadania w destruktorze.main.cpp: Połącz wszystkie elementy, odczytując z wejścia nazwę projektu i nazwy dwóch zadań (łącznie trzy wiersze). UtwórzProjecto podanej nazwie, dodaj do niego oba zadania, a następnie wyświetl początkowy stan. Oznacz pierwsze zadanie jako ukończone i ponownie wyświetl stan, aby pokazać zmianę.Format wyjściowy metody
displayStatus()powinien wyglądać następująco:Project: <project_name> - <task_name>: [incomplete] - <task_name>: [complete]Użyj
[complete]lub[incomplete]w zależności od stanu każdego zadania.
To wyzwanie pokazuje, jak klasy zagnieżdżone pomagają porządkować powiązane typy: klasa Task jest ściśle powiązana z Project i sama w sobie nie miałaby sensu. Zagnieżdżając ją, jasno sygnalizujemy tę relację w strukturze kodu.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "Project.h"
using namespace std;
int main() {
// Odczytaj dane wejściowe
string projectName;
string task1Name;
string task2Name;
getline(cin, projectName);
getline(cin, task1Name);
getline(cin, task2Name);
// TODO: Utwórz obiekt Project o podanej nazwie
// TODO: Dodaj oba zadania do projektu
// TODO: Wyświetl początkowy stan
// TODO: Oznacz pierwsze zadanie jako ukończone
// TODO: Wyświetl stan ponownie, aby pokazać zmianę
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