Capitolo 10 (Deitel) Strutture, unioni ed enumerazioni Sommario 10.1 - Introduzione 10.2 - Definire le strutture 10.3 - Dichiarare e inizializzare le strutture 10.4 - Accedere ai membri delle strutture 10.5 - Usare le strutture con le funzioni 10.6 - Ridenominazione di tipi con typedef 10.7 - Caso di studio: un efficiente mescolatore/distributore di carte 10.8 - Le Unioni 10.9 - Le costanti di Enumerazione 2000 Prentice Hall, Inc. All rights reserved. 10.1 - Introduzione • I vettori – Permettono di memorizzare una serie di dati omogenei in tipo – Si può accedere ad ogni singolo dato specificandone l’indice/posizione – Ma come memorizzare una serie di dati non necessariamente omogenei? • Ad esempio, come memorizzare i dati di una persona (nome, indirizzo, età, ..)? • Si deve davvero dichiarare una variabile per ogni tipo di dato della persona e duplicarla N volte per il numero di persone da memorizzare? • Le strutture – Sono raggruppamenti di variabili correlate (aggregati) sotto un unico nome • • • • Tale nome indica il “tipo di struttura”, che è definito dal programmatore Possono contenere variabili eterogenee in tipo (in genere tra quelli base del C) Numero/tipo di variabili che definiscono una struttura è dato dal programmatore Sono come vettori senza vincolo di avere elementi tutti dello stesso tipo e i cui elementi non sono identificati da un indice, ma da un nome – Sono usate comunemente per definire i record da salvare nei file – Se combinate con i puntatori, permettono di creare liste concatenate ed altre strutture dati dinamiche complesse (come pile, code e alberi) 2000 Prentice Hall, Inc. All rights reserved. 10.2 - Definire le strutture (1/2) • Sintassi struct nome_struttura{ tipo_base1 nome_membro1; tipo_base2 nome_membro2; ... }; – La parola chiave per definire una struttura dati è struct, seguita dal nome della struttura, che sarà poi il tipo delle variabili/istanze di tale struttura – La definizione della struttura descrive il modello dati che essa implementa, costituito da un insieme di variabili dette membri della struttura – Esempio: struct carta{ char *figura; char *segno; }; • struct introduce la definizione della struttura carta • carta è il nome della struttura che verrà poi usato per dichiarare variabili aventi questo tipo di struttura • carta contiene due membri di tipo char * (figura e segno) 2000 Prentice Hall, Inc. All rights reserved. 10.2 - Definire le strutture (2/2) • Regole e vincoli inerenti le strutture – La definizione dichiara un tipo di dato ma non alloca spazio in memoria – Una struttura non può avere come membri variabili che hanno come tipo la struttura stessa, ma può tuttavia contenere puntatori a tali variabili • Ovvero puntatori a variabili che hanno come tipo la struttura stessa – Si possono annidare strutture differenti (come membri di altre) struct nome_struttura1{ tipo_base1 nome_membro1; struct nome_struttura2 nome_membro2; //OK struct nome_struttura1 nome_membro3; //Errore!!! struct nome_struttura1 * nome_membro4; //OK }; • Operazioni valide/permesse – – – – Assegnare variabili struct ad altre variabili aventi lo stesso tipo di struct Rilevare l’indirizzo di memoria (&) di una variabile/istanza di struttura Accedere ai valori dei membri/campi di un’istanza di struttura Determinare la dimensione in memoria della struttura (sizeof) 2000 Prentice Hall, Inc. All rights reserved. 10.3 - Dichiarare e inizializzare le strutture • Dichiarazione (alloca spazio in memoria) – Una variabile struct si dichiara come tutte le altre struct carta unaCarta, mazzo[ 52 ], *cPtr; – Si può anche unire dichiarazione di variabile e definizione della struct struct carta { //Dichiara una variabile di tipo struct carta, char *figura; //un vettore di elementi di tipo struct carta char *segno; //e un puntatore a variabili di tipo struct carta } unaCarta, mazzo[ 52 ], *cPtr; • Inizializzazione – Puè essere fatta all-in-one tramite una lista di inizializzatori struct carta treCuori = { "Tre", “Cuori" }; – Oppure tramite assegnamento di variabili struct struct carta unaCarta = { "Tre", “Cuori" }; struct carta treCuori = unaCarta; – Oppure separando dichiarazione e inizializzazioni di ogni membro struct carta treCuori; treCuori.figura = “Tre”; treCuori.segno = “Cuori”; 2000 Prentice Hall, Inc. All rights reserved. 10.4 - Accedere ai membri delle strutture • Accesso diretto – Per accedere al valore dei membri delle strutture (istanze) in modo diretto tramite la variabile/istanza stessa, si usa l’operatore punto (.) – L’operatore punto va applicato alla variabile struct e deve essere seguito dal nome del membro di cui si vuole reperire il valore struct carta miaCarta; printf( "%s", miaCarta.segno ); • Accesso via puntatore – Per accedere al valore dei membri tramite un puntatore all’istanza di struttura dati, si usa l’operatore freccia (->) – L’operatore freccia va applicato al puntatore alla variabile struct e deve essere seguito dal nome del membro di cui si vuole reperire il valore struct carta *miaCartaPtr = &miaCarta; printf( "%s", miaCartaPtr->segno ); – I due tipi di accesso sono equivalenti • Ovvero miaCartaPtr->segno è alias di (*miaCartaPtr).segno 2000 Prentice Hall, Inc. All rights reserved. 10.5 - Usare le strutture con le funzioni • Passare le strutture come parametri alle funzioni – Si può passare una struttura interamente oppure solo i singoli membri – In entrambi i casi, il passaggio di default è per valore – Nel primo caso, nel corpo della funzione si accede ai membri usando (.) • Passare le strutture per riferimento – Si deve passare l’indirizzo della variabile struct (&) – La funzione riceve per parametro un puntatore alla variabile struct – Nel corpo della funzione si accede ai membri usando (->) • Con le strutture si può passare un intero vettore per valore – – – – – Si crea una struttura dati che ha come unico membro un vettore Si dichiara una variabile struct che ha per tipo la struttura appena definita Si passa direttamente la variabile struct per intero alla funzione In questo modo anche il vettore è automaticamente passato per valore Viceversa, si può passare una struct per riferimento creando e passando un vettore di struct con un solo elemento, la variabile struct da passare 2000 Prentice Hall, Inc. All rights reserved. 10.6 - Ridenominazione di tipi con typedef • typedef – Crea sinonimi (alias) per tipi di dato strutturato già definiti in precedenza – In genere si usa typedef per abbreviare i nomi dei certi tipi di struttura – typedef non crea un nuovo tipo di dato, ma solo un alias – Funziona anche con i tipi di dato classici del C (int, float, ..) • Sintassi typedef nome_struttura nome_alias_tipo – Esempio: typedef struct carta Carta; typedef Carta * CartaPtr; • Definisce un nuovo nome di tipo CartaPtr come un sinonimo per il tipo Carta * (che è il tipo “puntatore a struttura dati Carta”) • Da ora per creare puntatori a variabili struct di tipo Carta, si può usare in modo equivalente struct carta * nomePointer oppure CartaPtr nomePointer 2000 Prentice Hall, Inc. All rights reserved. 10.7 - Caso di studio: un efficiente mescolatore/distributore di carte (1/4) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /* Fig. 10.3: fig10_03.c Il programma per mescolare e distribuire carte usando strutture */ #include <stdio.h> #include <stdlib.h> #include <time.h> struct carta{ const char *figura; const char *segno; }; typedef struct carta Carta; void creaMazzo( Carta * const, const char *[], const char *[] ); void mischiaMazzo( Carta * const ); void daiCarte( const Carta * const ); int main(){ Carta mazzo[ 52 ]; const char *figura[] = { "Asso", "Due", "Tre", “Quattro", “Cinque", "Sei", "Sette", “Otto", "Nove", “Dieci", "Jack", “Regina", “Re"}; const char *segno[] = { “Cuori", “Quadri", “Fiori", “Picche"}; 2000 Prentice Hall, Inc. All rights reserved. 1. Definisce la struttura “carta” composta da due stringhe (come array di caratteri) 2. Rinomina il tipo “carta” in “Carta” 3. Prototipi di funzioni 4. Dichiara il vettore mazzo, i cui elementi sono istanze del tipo strutturato “Carta” 5. Inizializza i vettori di stringhe figura e segno 10.7 - Caso di studio: un efficiente mescolatore/distributore di carte (2/4) 23 6. Chiama in sequenza 24 srand( time( NULL ) ); le funzioni per creare 25 creaMazzo( mazzo, figura, segno ); e mischiare il mazzo e 26 mischiaMazzo( mazzo ); per distriuire le carte: 27 daiCarte( mazzo ); mazzo, figura e segno 28 return 0; sono vettori e passano 29 } quindi per riferimento 30 31 void creaMazzo( Carta * const wMazzo, const char * wFigura[], 7. Inizializza il vettore 32 const char * wSegno[] ){ Mette tutte le 52 carte nel mazzo mazzo assegnando 33 int i; in modo ordinato. Le 12 figurefigura/segno ei ai membri 34 di ogni suo elemento 4 segni vengono determinati da 35 for ( i = 0; i <= 51; i++ ) { 36 wMazzo[ i ].figura = wFigura[ i % 13 ];divisione/resto progressivi per 13. 8. I parametri formali di 37 wMazzo[ i ].segno = wSegno[ i / 13 ]; Si va dell’asso fino al re per ogni mazzo, figura e segno segno, finite le carte di un segno 38 } sono vettori e possono 39 } si ripete per gli altri segni rimasti. quindi esser dichiarati 40 come vettori o come puntatori al tipo di dato dei vettori stessi 2000 Prentice Hall, Inc. All rights reserved. 10.7 - Caso di studio: un efficiente mescolatore/distributore di carte (3/4) 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void mischiaMazzo( Carta * const wMazzo ){ int i, j; Carta temp; 9. Mischia il mazzo che inizialmente era stato creato ordinato 10. Per ogni posizione all’interno del mazzo genera un numero casuale tra 0 e 51 Seleziona un numero casuale tra 0 e 51. che indica un’altra Scambia l’elemento i con quell’elemento. posizione nel mazzo e scambia la carta in posizione corrente } con quella nell’altra Fa il ciclo utilizzando gli array e posizione casuale void daiCarte( const Carta * const wMazzo ){ visualizza i 2 mazzi distribuiti int i; 11. Distribuisce le carte del mazzo in modo for ( i = 0; i <= 51; i++ ) analogo all’esempio printf( "%5s of %-8s%c", wMazzo[ i ].figura, già visto, ma figura e wMazzo[ i ].segno, ( i + 1 ) % 2 ? '\t' : '\n' ); segno sono presi dai } membri degli elementi del vettore mazzo for ( i = 0; i <= 51; i++ ){ j = rand() % 52; temp = wMazzo[ i ]; wMazzo[ i ] = wMazzo[ j ]; wMazzo[ j ] = temp; } 2000 Prentice Hall, Inc. All rights reserved. 10.7 - Caso di studio: un efficiente mescolatore/distributore di carte (4/4) Otto Otto Sette Asso Due Sette Fante Re Tre Tre Dieci Dieci Sei Sei Nove Fante Re Nove Sei Regina Asso Re Re Regina Quattro Quattro di di di di di di di di di di di di di di di di di di di di di di di di di di Quadri Fiori Cuori Fiori Picche Picche Fiori Cuori Cuori Fiori Cuori Fiori Fiori Cuori Quadri Picche Quadri Picche Picche Quadri Picche Fiori Picche Cuori Picche Fiori Asso Cinque Due Dieci Sei Due Dieci Fante Tre Nove Due Sette Regina Tre Asso Cinque Sette Quattro Otto Cinque Nove Cinque Quattro Otto Fante Regina 2000 Prentice Hall, Inc. All rights reserved. di di di di di di di di di di di di di di di di di di di di di di di di di di Cuori Picche Quadri Quadri Quadri Fiori Picche Quadri Quadri Fiori Cuori Quadri Picche Picche Quadri Fiori Fiori Cuori Picche Quadri Cuori Cuori Quadri Cuori Cuori Fiori Visualizzazione del Programma 10.8 - Le Unioni (1/2) • union – Sono locazioni di memoria che contengono diversi tipi di dato nel tempo • Tutti i membri di una union condividono lo stesso spazio di memoria – Quando viene dichiarata una union, di fatto viene allocato uno spazio di memoria pari all'elemento più grande della union stessa – A run-time, la memoria contiene il valore di un solo membro alla volta • Si può accedere/usare solo l’ultimo membro assegnato (a cui si è dato un valore), tutti quelli precedenti si perdono perché lo spazio è condiviso – Le operazioni sulle union sono simili a quelle delle struct • Assegnamenti tra union di pari tipo di struttura, recupero dei loro indirizzi (&), accesso ai membri direttamente con (..) o tramite puntatore (->) • Dichiarare una union – Si dichiara nello stesso modo delle struct union Numero{ int x; float y; }; union Numero valore; 2000 Prentice Hall, Inc. All rights reserved. 10.8 - Le Unioni (2/2) 1 /* Fig. 10.5: fig10_05.c - Un esempio di unione */ 3 #include <stdio.h> 4 5 union numero{ int x; double y; }; 6 7 int main(){ 8 union numero valore; 9 10 11 12 13 14 15 16 17 18 19 } valore.x = 100; printf( "%s\n%s%d\n%s%f\n\n", “Inserisci un valore nel membro” “intero e visualizza entrambi i membri.”, "int: ", valore.x, "double: ", valore.y ); valore.y = 100.0; printf( "%s\n%s%d\n%s%f\n", “Inserisci un valore nel membro” “decimale e visualizza entrambi i membri.", "int: ", valore.x, "double: ", valore.y ); return 0; 1. Definizione della union 2. Assegnamento del primo membro della union (un intero) 3. Visualizzazione del membro intero 4. Assegnamento del secondo membro (un double) che di fatto sostiuisce il primo Inserisci un valore nel membro intero e visualizza entrambi i membri. int: 100 double: -92559592117433136000000000000000000000000000000000000000000000.00000 Inserisci un valore nel membro decimale e visualizza entrambi i membri. int: 0 double: 100.000000 2000 Prentice Hall, Inc. All rights reserved. Visualizzazione del Programma 10.9 - Le costanti di Enumerazione (1/2) • Enumerazione (enum) – E’ un tipo di dato che può assumere solo valori all’interno di un insieme di costanti intere rappresentate da identificatori – Sono come delle costanti simboliche i cui valori vengono impostati automaticamente • I loro valori partono per default da 0 e sono incrementati di 1 • I loro valori possono anche essere assegnati esplicitamente con l’operatore = • E’ necessario usare nomi di costanti unici, mai ripeterli nell’enumerazione – Sintassi (esempio): enum Mesi { GEN = 1, FEB, MAR, APR, MAG, GIU, LUG, AGO, SET, OTT, NOV, DIC} mese; • Crea un nuovo tipo enum Mesi dove gli identificatori sono impostati a interi da 1 a 12, dove l’enumerazione inizia da 1 e incrementa di 1 ogni volta • Dichiara una variabile “mese” di tipo enum, che potrà valere GEN, FEB, … – Il vantaggio è dato dall’uso del nome di costante al posto del valore che intero che essa rappresenta (frequente l’uso nei cicli) – Alle variabili di tipo enum si possono assegnare solamente valori pari agli identificatori dell’enumerazione (mai i rispettivi interi corrispondenti) • Quando invece vengono lette, viene restituito il rispettivo valore intero 2000 Prentice Hall, Inc. All rights reserved. 10.9 - Le costanti di Enumerazione (2/2) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3 4 5 6 7 8 9 10 11 12 /* Fig. 10.18: fig10_18.c - Usare il tipo enumerazione */ #include <stdio.h> enum Mesi{ GEN = 1,FEB,MAR,APR,MAG,GIU,LUG,AGO,SET,OTT,NOV,DIC }; int main(){ enum Mesi mese; const char *nomeMese[] = { "", “Gennaio", “Febbraio", "Marzo", "Aprile", "Maggio", “Giugno", "Agosto", "Settembre", "Ottobre", “Luglio", "Novembre", "Dicembre" }; for (mese=GEN; mese<=DIC; mese++) printf( "%2d%11s\n", mese, nomeMese[mese] ); return 0; 1. Definizione della enumerazione 2. Inizializzazione delle variabili 3. Ciclo di stampa della enumerazione } Gennaio Febbraio Marzo Aprile Maggio Giugno Luglio Agosto Settembre Ottobre Novembre Dicembre 2000 Prentice Hall, Inc. All rights reserved. Visualizzazione del programma