Classi annidate e interne
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 38 di 104.
Una classe annidata è una classe definita all'interno di un'altra classe. Questa tecnica aiuta a organizzare il codice raggruppando insieme tipi correlati e può migliorare l'incapsulamento nascondendo i dettagli di implementazione.
class LinkedList {
public:
class Node { // Classe annidata
int data;
Node* next;
public:
Node(int d) : data(d), next(nullptr) {}
friend class LinkedList;
};
private:
Node* head;
public:
LinkedList() : head(nullptr) {}
};La classe annidata Node è strettamente legata a LinkedList: ha senso solo in quel contesto. Annidandola, segnaliamo questa relazione ed evitiamo di inquinare lo spazio dei nomi globale con un nome generico come "Node".
Le classi annidate seguono le stesse regole di accesso che hai imparato. Una classe annidata dichiarata nella sezione privata è accessibile solo all'interno della classe esterna.
Quando viene dichiarato pubblico, il codice esterno può usarlo con l'operatore di ambito:
LinkedList::Node node(42); // Solo se Node è publicUn dettaglio importante riguardo alla direzione: una classe annidata è un membro della classe contenitore e ha gli stessi diritti di accesso di qualsiasi altro membro, quindi può accedere ai membri privati della classe contenitore senza una dichiarazione friend. Il contrario, però, non è automatico: la classe contenitore non ha accesso ai membri privati della classe annidata.
Ecco perché Node dichiara friend class LinkedList; nell'esempio qui sopra: la dichiarazione friend concede alla LinkedList che la contiene l'accesso ai membri privati di Node, l'unica direzione che non è automatica.
Sfida
FacileCostruiamo un semplice sistema di gestione delle attività in cui le attività sono organizzate all'interno di un progetto. È uno scenario perfetto per le classi annidate: una Task ha senso solo nel contesto di un Project, quindi la annideremo al suo interno per mantenere organizzato il codice ed evitare di inquinare lo spazio dei nomi globale.
Creerai due file per organizzare il codice:
Project.h: definisci una classeProjectche contiene una classeTaskannidata.La classe
Taskannidata deve essere dichiarata nella sezione public diProject, così potrà essere utilizzata dall'esterno. Deve memorizzare il nome di un'attività (stringa) e il suo stato di completamento (bool). Aggiungi un costruttore che accetti il nome e imposti l'attività come incompleta per impostazione predefinita. Includi i metodicomplete()per contrassegnarla come completata,isComplete()per verificarne lo stato egetName()per recuperare il nome.La classe
Projectstessa deve memorizzare il nome di un progetto (stringa) e una raccolta di puntatori aTask. Fornisci un costruttore che accetti il nome del progetto, un metodoaddTask(const std::string& taskName)che crei una nuova attività e la aggiunga al progetto, un metodocompleteTask(const std::string& taskName)che trovi e completi un'attività in base al nome e un metododisplayStatus()che mostri il nome del progetto seguito dallo stato di ciascuna attività.Poiché
Taskè annidata all'interno diProject, la classeProjectpuò lavorare direttamente con le attività. Non dimenticare di liberare le attività allocate dinamicamente nel distruttore.main.cpp: metti insieme tutti gli elementi leggendo da input il nome di un progetto e i nomi di due attività (tre righe in totale). Crea unProjectcon il nome fornito, aggiungici entrambe le attività e poi mostra lo stato iniziale. Completa la prima attività e mostra di nuovo lo stato per evidenziare il cambiamento.Il formato di output di
displayStatus()deve essere:Project: <project_name> - <task_name>: [incomplete] - <task_name>: [complete]Usa
[complete]o[incomplete]in base allo stato di ciascuna attività.
Questa sfida mostra come le classi annidate aiutino a organizzare tipi correlati: la classe Task è strettamente legata a Project e non avrebbe senso da sola. Annidandola, rendiamo chiara questa relazione nella struttura del codice.
Provalo tu
#include <iostream>
#include <string>
#include "Project.h"
using namespace std;
int main() {
// Leggi l'input
string projectName;
string task1Name;
string task2Name;
getline(cin, projectName);
getline(cin, task1Name);
getline(cin, task2Name);
// TODO: Crea un Project con il nome fornito
// TODO: Aggiungi entrambe le attività al progetto
// TODO: Mostra lo stato iniziale
// TODO: Completa la prima attività
// TODO: Mostra di nuovo lo stato per evidenziare il cambiamento
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online