Perché raggruppare i dati
Finora ogni variabile se ne stava per conto suo: un int qui, una string là. Ma i programmi reali lavorano con cose fatte di più pezzi: un punto ha una x e una y, uno studente ha un nome, un'età e una media. Passare tutto questo come variabili sparse e separate porta facilmente a errori: niente le tiene insieme, e una funzione che le usa tutte e tre deve ricevere tre parametri.
Una struct risolve il problema. Definisce un nuovo tipo che riunisce variabili correlate, i suoi membri, in un'unica unità. Una volta definita, tratti l'intero gruppo come un singolo valore che puoi memorizzare, copiare e passare alle funzioni.
Accedi a ogni membro con l'operatore punto (s.name, s.age). Il punto e virgola dopo la } di chiusura della definizione di una struct è obbligatorio: dimenticarlo è uno degli errori di compilazione più comuni tra chi inizia con il C++.
Inizializzare una struct
Assegnare ogni campo a mano funziona, ma è prolisso ed è facile dimenticare un membro. L'opzione più pulita è l'inizializzazione aggregata: elenchi i valori tra graffe, nello stesso ordine in cui sono stati dichiarati i membri.
Attenzione al caso predefinito vuoto: Point p; (senza graffe) lascia x e y con valori spazzatura, perché i membri di tipo predefinito non vengono azzerati in automatico. Point p{}; li inizializza per valore a 0. Preferisci le graffe. Puoi anche fissare dei valori predefiniti direttamente nella definizione, così perfino Point p; parte pulito:
Struct come parametri di funzione
Una struct è un singolo valore, quindi una funzione può ricevere un solo parametro invece di tre. Ricorda però che passare una struct per valore copia tutti i suoi membri. Per qualsiasi cosa più grande di un paio di int, passala per riferimento const per evitare la copia: la stessa regola che hai visto nella pagina sui riferimenti.
Restituire un'intera struct da una funzione è il modo idiomatico per restituire più valori in una volta sola, molto più pulito che destreggiarsi tra diversi riferimenti di output.
Aggiungere funzioni membro e costruttori
Una struct non si limita ai dati. Può contenere funzioni membro che lavorano sui suoi membri e un costruttore che inizializza l'oggetto nel momento in cui viene creato. È qui che una struct comincia ad assomigliare a un piccolo oggetto con un comportamento.
Dentro una funzione membro accedi ai membri per nome (width, height): si riferiscono alla copia di questo oggetto. Marcare area() const dice al compilatore che la funzione legge soltanto l'oggetto, il che ti permette di chiamarla anche su valori const Rectangle. I costruttori sono un argomento a sé, compresa quella sintassi con lista di inizializzazione : width(w): la pagina sui costruttori li tratta in profondità.
Struct e class: la vera differenza
Forse hai sentito dire che "le struct sono per i dati, le classi per gli oggetti". È una convenzione, non una regola del linguaggio. In C++ una struct e una class sono quasi la stessa cosa: l'unica differenza integrata è il livello di accesso predefinito.
struct S {
int x; // public di default
};
class C {
int x; // private di default
};
I membri di una struct sono public se non li marchi diversamente; quelli di una class partono private. Tutto qui: entrambe possono avere costruttori, funzioni membro, ereditarietà e tutto il resto. Puoi perfino aggiungere specificatori di accesso espliciti a una struct per nascondere dei membri:
In pratica: usa struct per raggruppamenti trasparenti di dati in cui ogni campo è pensato per essere usato liberamente (un Point, un Color, un record di configurazione), e passa a class quando vuoi proteggere lo stato interno dietro un'interfaccia pubblica. Il compilatore le tratta allo stesso modo; la parola chiave esprime solo la tua intenzione.
Array e vector di struct
Dato che una struct è un tipo come un altro, puoi metterne tante in un array o in un vector e scorrerle come qualsiasi altro valore.
Nota le graffe annidate: ogni {"Keyboard", 49.99} inizializza per aggregazione un Product, e le graffe esterne costruiscono il vector. Usare const Product& nel ciclo basato su intervallo evita di copiare ogni struct a ogni iterazione: togli la & e duplicheresti ogni elemento senza motivo.
Prossimo: enum
Le struct ti permettono di costruire un tipo a partire da più valori raggruppati. Il capitolo successivo va nella direzione opposta: un enum definisce un tipo che può contenere esattamente un valore scelto da un piccolo insieme con nome, perfetto per cose come Color::Red, Direction::North o lo stato di una macchina a stati. Vedrai come enum class ti dà costanti leggibili e sicure rispetto ai tipi, che si abbinano in modo naturale alle struct e alle classi che stai costruendo.
Domande frequenti
Cos'è una struct in C++?
Una struct è un tipo definito dall'utente che raggruppa più variabili correlate (chiamate membri) sotto un unico nome. Invece di gestire separatamente le variabili string name, int age e double gpa, le riunisci in un unico tipo Student e passi in giro quell'oggetto. Una struct può contenere anche funzioni membro e costruttori: nel C++ moderno è una classe a tutti gli effetti i cui membri sono public di default.
Qual è la differenza tra struct e class in C++?
Tecnicamente solo il livello di accesso predefinito: i membri di una struct sono public se non indichi altro, mentre quelli di una class sono private di default. Lo stesso vale per l'ereditarietà. Tutto il resto (costruttori, funzioni membro, metodi, ereditarietà) funziona allo stesso modo. Per convenzione, i programmatori usano struct per semplici raggruppamenti di dati e class per tipi con invarianti e dettagli interni nascosti.
Come si inizializza una struct in C++?
Il modo più semplice è l'inizializzazione aggregata con le graffe, nell'ordine dei membri: Point p{3, 4};. Puoi anche assegnare ogni campo a mano con l'operatore punto (p.x = 3;), dare ai membri valori predefiniti nella definizione (int x = 0;) oppure scrivere un costruttore perché la struct si inizializzi da sola. L'inizializzazione con le graffe è preferibile perché non lascia campi non inizializzati in silenzio.