struct Rettangolo { double lunghezza; double altezza; }; |
La sintassi per la creazione di una struttura è evidente: dopo la
parola chiave struct basta inserire il nome della struttura e,
tra parentesi graffe ({ }), l'elenco dei campi che
costituiscono la struttura; si noti che la parentesi che chiude la
struttura è seguita da un punto e virgola. Nell'esempio che abbiamo
appena presentato abbiamo dunque creato un nuovo tipo, chiamato
Rettangolo
, che possiamo utilizzare esattamente come se fosse un
tipo primitivo; è possibile infatti dichiarare puntatori, array,
riferimenti a Rettangolo
. Per convenzione stilistica, i nomi
delle strutture sono costituiti da lettere interamente minuscole, tranne
la prima.
Dichiarata una variabile di un nuovo tipo derivato tramite
struct, è possibile accedere ai suoi campi tramite un nuovo
operatore: il punto (`.'
), come nel seguente esempio:
// ex7_4_1 #include <iostream.h> struct Rettangolo { double larghezza; double altezza; }; void main() { Rettangolo r; cout << "larghezza? "; cin >> r.larghezza; cout << "altezza? "; cin >> r.altezza; double area = r.larghezza * r.altezza; cout << "area = " << area; }
esempio di output:
larghezza? 7
altezza? 4
area = 28
Come è ovvio, avremmo potuto utilizzare due variabili reali, piuttosto che
una struttura Rettangolo
, ottenendo il medesimo risultato; tuttavia
l'astrazione ottenuta è maggiore utilizzando questo composto, in quanto
possiamo creare dei veri e propri ``rettangoli'', identificati da una altezza
e una larghezza, riducendo enormemente lo sforzo programmativo e la
possibilità di commettere errori. Vediamo il seguente
// ex7_4_2 #include <iostream.h> struct Rettangolo { double larghezza; double altezza; }; // funzione che immette nRettangoli nel vettore v void immetti (Rettangolo* v, int nRettangoli) { for (int i = 0; i < nRettangoli; i++) { cout << "rettangolo " << i+1 << "? "; cin >> v[i].larghezza >> v[i].altezza; } } // funzione che torna il rettangolo avente area minore // tra quelli contenuti nel vettore v Rettangolo minore (Rettangolo* v, int nRettangoli) { Rettangolo min = v[0]; for (int i = 1; i < nRettangoli; i++) { double areaCorrente = v[i].larghezza * v[i].altezza; double areaMin = min.larghezza * min.altezza; if (areaCorrente < areaMin) min = v[i]; } return min; } void main() { int nRettangoli; cout << "quanti rettangoli? "; cin >> nRettangoli; // creiamo un array dinamico di rettangoli Rettangolo* v = new Rettangolo[nRettangoli]; immetti (v, nRettangoli); Rettangolo m = minore (v, nRettangoli); cout << "il rettangolo avente area minore e`: " << m.larghezza << " x " << m.altezza << "\n"; }
esempio di output:
quanti rettangoli? 3
rettangolo 1? 3 5
rettangolo 2? 4 10
rettangolo 3? 2 7
il rettangolo avente area minore e`: 2 x 7
Come si vede nel seguente esempio, l'utilizzo di variabili di tipo
Rettangolo
è esattamente analogo a quello di variabili dei tipi
primitivi; è possibile inoltre costruire strutture contenenti altre
strutture, nel qual caso l'accesso ai campi della struttura più interna si
effettua tramite la concatenazione di operatori punto:
// ex7_4_3 #include <iostream.h> struct Rettangolo { double larghezza; double altezza; }; struct Rombo { double diagonale1; double diagonale2; }; struct Triangolo { double base; double altezza; }; struct Composizione { Rettangolo rettangolo; Rombo rombo; Triangolo triangolo; }; void main() { Composizione c; cout << "Rettangolo\n"; cout << "larghezza? "; cin >> c.rettangolo.larghezza; cout << "altezza? "; cin >> c.rettangolo.altezza; cout << "Rombo\n"; cout << "diagonale 1? "; cin >> c.rombo.diagonale1; cout << "diagonale 2? "; cin >> c.rombo.diagonale2; cout << "Triangolo\n"; cout << "base? "; cin >> c.triangolo.base; cout << "altezza? "; cin >> c.triangolo.altezza; cout << "le superfici sono, rispettivamente:\n" << c.rettangolo.larghezza * c.rettangolo.altezza << ", " << c.rombo.diagonale1 * c.rombo.diagonale2 / 2 << ", " << c.triangolo.base * c.triangolo.altezza / 2 << "\n"; }
esempio di output:
Rettangolo
larghezza? 5.3
altezza? 3.2
Rombo
diagonale 1? 7
diagonale 2? 6.4
Triangolo
base? 10
altezza? 4.5
le superfici sono, rispettivamente:
16.96, 22.4, 22.5
Vediamo ora come sia possibile utilizzare una struttura per contenere dati eterogenei, come i dati personali:
// ex7_4_4 #include <iostream.h> #include <string.h> enum Stabilimento { ROMA, MILANO, TORINO }; enum Qualifica { OPERAIO, TECNICO, CAPO_REPARTO, CAPO_SEZIONE, DIRETTORE, SOCIO }; struct Impiegato { char nome[20]; char cognome[30]; Qualifica qualifica; Stabilimento stabilimento; int salario; }; void stampaDati (const Impiegato& i) { char q[20], s[10]; switch (i.qualifica) { case OPERAIO: strcpy (q, "operario"); break; case TECNICO: strcpy (q, "tecnico"); break; case CAPO_REPARTO: strcpy (q, "capo reparto"); break; case CAPO_SEZIONE: strcpy (q, "capo sezione"); break; case DIRETTORE: strcpy (q, "direttore"); break; case SOCIO: strcpy (q, "socio"); break; } switch (i.stabilimento) { case ROMA: strcpy (s, "Roma"); break; case MILANO: strcpy (s, "Milano"); break; case TORINO: strcpy (s, "Torino"); break; } cout << "\n" << i.nome << " " << i.cognome << "\n" << q << " presso lo stabilimento di " << s; if (i.qualifica != SOCIO) cout << "\nsalario di lire " << i.salario; } void main() { int q, s; Impiegato i; cout << "Dati di un impiegato\n"; cout << "nome? "; cin >> i.nome; cout << "cognome? "; cin >> i.cognome; cout << "Qualifica\n" "\t(0) operaio\n" "\t(1) tecnico\n" "\t(2) capo reparto\n" "\t(3) capo sezione\n" "\t(4) direttore\n" "\t(5) socio\n? "; cin >> q; switch (q) { case 0: i.qualifica = OPERAIO; break; case 1: i.qualifica = TECNICO; break; case 2: i.qualifica = CAPO_REPARTO; break; case 3: i.qualifica = CAPO_SEZIONE; break; case 4: i.qualifica = DIRETTORE; break; case 5: i.qualifica = SOCIO; break; } cout << "Stabilimento\n" "\t(0) Roma\n" "\t(1) Milano\n" "\t(2) Torino\n? "; cin >> s; switch (s) { case 0: i.stabilimento = ROMA; break; case 1: i.stabilimento = MILANO; break; case 2: i.stabilimento = TORINO; break; } // se si tratta di un socio il salario NON e` definito if (i.qualifica != SOCIO) { cout << "salario? "; cin >> i.salario; } stampaDati (i); }
esempio di output:
Dati di un impiegato
nome? Paolo
cognome? Rossi
Qualifica
(0) operaio
(1) tecnico
(2) capo reparto
(3) capo sezione
(4) direttore
(5) socio
? 2
Stabilimento
(0) Roma
(1) Milano
(2) Torino
? 0
salario? 3500000
Paolo Rossi
capo reparto presso lo stabilimento di Roma
salario di lire 3500000
Notiamo che il passaggio della struttura Impiegato
avviene per
riferimento costante; quando si passa un argomento ad una funzione, se tale
argomento è un oggetto più complesso di un semplice tipo primitivo,
conviene sempre passare l'argomento per riferimento: non costante se l'oggetto
va modificato, costante in caso contrario (come nel nostro esempio). Il
passaggio per valore, infatti, avviene copiando membro a membro i campi
dalla struttura locale alla funzione chiamante in quella della struttura
chiamata: tale operazione potrebbe essere del tutto inefficiente nel caso
l'oggetto occupi un gran quantitativo di memoria.
Una struttura in definitiva permette di avere a disposizione dei dati astratti, il cui utilizzo è immediato ed intuitivo; per programmi semplici (come gli esempi da noi presentati, per intenderci) una raffinatezza del genere non è necessaria, in quanto è facile tenere a mente la struttura dell'intero programma senza perdere di vista il modello reale. Al contrario, per un programma ``vero'' diventa assolutamente necessario costruire dei tipi ad hoc; comunque le strutture non sono che il primo passo verso la programmazione ad oggetti, che prenderà il volo solo con la presentazione delle classi.
Rettangolo
e raddoppi le
sue dimensioni; il passaggio deve avvenire per riferimento? Perché?
Rombo
e torni il suo
perimetro; il passaggio deve avvenire per riferimento? Perché?