Riepilogo
Finora ogni collezione del corso conteneva dati di un solo tipo: voti, stature, celle di una tabella. Le entità di un problema, invece, hanno vari attributi di tipi diversi: uno studente ha un nome e un voto; un libro, un titolo, un autore e un anno. Questa lezione presenta il record, il tipo composto che il programmatore stesso definisce con struct per raggruppare quegli attributi sotto un solo nome. Comincia dalla ragion d’essere del record, la fragilità dei vettori paralleli; prosegue con la definizione di uno struct, i suoi campi con valore iniziale e la creazione di record con le graffe e con inizializzatori designati; poi vengono i record come parametri e come risultati di funzioni e i vettori di record, che si scorrono con binding strutturati e si ordinano e si esplorano per un campo, con la questione dell’ordinamento stabile; e si chiude con l’arte di modellare un problema con tipi che ne riflettano il dominio.
Obiettivi di apprendimento
- Definire uno
structcon campi e valori iniziali, e creare record con l’inizializzazione con le graffe, anche con inizializzatori designati. - Scrivere funzioni che ricevano e restituiscano record, scegliendo la modalità di passaggio.
- Ordinare un vettore di record e cercarvi per un campo con
std::ranges::sort,std::ranges::stable_sortestd::ranges::findcon una proiezione, e distinguere un ordinamento stabile da uno che non lo è. - Modellare con record i dati di un problema e giustificare quali campi e tipi si scelgono.
Raggruppare dati
Vettori paralleli
Si supponga che un programma debba conservare, per ogni studente di un gruppo, il nome, il voto e l’età. Con quanto si è visto fino alla lezione Tabelle, la soluzione a portata di mano sono tre vettori, uno per attributo, con la convenzione che l’indice designi la stessa persona in tutti e tre: names[i], grades[i] e ages[i]. Si chiamano vettori paralleli. Una tabella non serve, perché le sue celle devono essere tutte dello stesso tipo, e un nome non è un numero.
#include <algorithm>#include <cstddef>#include <print>#include <string>#include <vector>int main(){ const std::vector<std::string> names{"Ana", "Luis", "Eva"}; std::vector<int> grades{6, 4, 7}; const std::vector<int> ages{19, 22, 20}; std::ranges::sort(grades); for (std::size_t i{0}; i < names.size(); ++i) { std::println("{}: voto {}, {} anni", names[i], grades[i], ages[i]); }}Ana: voto 4, 19 anniLuis: voto 6, 22 anniEva: voto 7, 20 anniIl programma voleva ordinare gli studenti per voto, e ha ordinato solo i voti. Compila senza avvisi, e né il sanitizer degli errori di memoria, AddressSanitizer, né quello del comportamento indefinito, UndefinedBehaviorSanitizer, hanno nulla da segnalare; eppure il suo output è falso: Ana aveva un 6 e figura con un 4. La convenzione che univa i tre vettori non è scritta in nessun luogo che il compilatore possa leggere, sicché nulla impedisce di romperla. Ne segue la fragilità dei vettori paralleli: ogni operazione che sposti, aggiunga o tolga un elemento deve essere ripetuta, identica e nello stesso ordine, in tutti; e basta dimenticarne uno per corrompere i dati in silenzio.
Tre vettori paralleli a confronto con un vettore di schede, ciascuna con i tre dati di una persona.
Dimostrazione
La colonna 0 descrive una persona:
names[0],grades[0]eages[0]sono il nome, il voto e l’età di Ana. Il legame è soltanto l’indice.std::ranges::sort(grades)riordina una sola riga. Le altre due non se ne accorgono: la colonna 0 riunisce ora il nome di Ana, il voto di Luis e l’età di Ana, che non sono di nessuno.L’alternativa: un solo vettore, i cui elementi sono schede. Ogni scheda contiene i tre dati di una persona, e il legame fra essi non è più una convenzione sugli indici, ma la scheda stessa.
Ordinare per voto sposta schede intere: quella di Luis passa al primo posto con il nome e l’età che vi sono scritti. Nessun dato può separarsi dagli altri, perché l’operazione non li vede separatamente.
Se è così, ciò che il problema chiede è un tipo i cui valori siano schede: un valore che contenga un nome, un voto e un’età, e che si copi, si passi a una funzione, si conservi in un vettore e si ordini come un tutto. C++ non lo fornisce già pronto, perché le schede di ogni problema sono diverse; ma permette al programmatore di definirlo. Quel tipo è il record.
Caricamento del contenuto…