Strutture (singole) Abbiamo già visto che le strutture forniscono un mezzo per memorizzare sotto lo stesso nome più valori differenti in variabili di tipi potenzialmente differenti. Ciò dà luogo a un programma più modulare e più facile da modificare, dato che esso tratta i dati in modo particolarmente compatto. Le strutture sono utili di solito ogni qualvolta si debbano raggruppare insieme un gran numero di dati: ad es., per raccogliere i record di un data base o per memorizzare informazioni sui contatti di una rubrica telefonica. Nell’esempio dei contatti, si può usare una struttura che conserva tutte le informazioni su un singolo contatto, quali nome, indirizzo, numero di telefono, ecc. Vediamo le istruzioni C necessarie per creare, riempire e usare le strutture. L’uso di una struttura richiede gli stessi due passi necessari per usare qualsiasi variabile C: dapprima si dichiara la struttura, quindi si assegnano valori specifici ai suoi elementi individuali. Dichiarazione. La dichiarazione di una struttura richiede l’elencazione dei tipi dei dati, dei nomi dei dati e della disposizione dei dati individuali. Ad es., la definizione struct { int giorno; int mese; int anno; } nascita; dà la forma di una struttura di nome nascita e riserva la memoria per i dati individuali elencati in essa. La struttura nascita consiste in tre dati, che sono detti i suoi membri. Assegnazione. L’assegnazione dei valori effettivi a una struttura - o popolamento della struttura - avviene tenendo presente che ogni suo membro ha un nome costituito da: • nome della struttura • un punto, detto operatore di membro • nome del dato individuale. Così, nascita.giorno si riferisce al primo membro della struttura nascita, nascita.mese si riferisce al secondo membro, e nascita.anno al terzo. Il programma seguente illustra come si assegnano i valori ai membri individuali della struttura nascita. #include <stdio.h> main() { struct { int giorno; int mese; int anno; } nascita; nascita.giorno = 31; nascita.mese = 05; nascita.anno = 1946; printf(“Sono nato il giorno %d/%d/%d”, nascita.giorno, nascita.mese, nascita.anno); } Esso produce l’uscita Sono nato il giorno 31/5/1946 Naturalmente nella definizione di una struttura gli spazi non sono essenziali, per cui la precedente struttura nascita si poteva definire anche come: struct {int giorno; int mese; int anno;} nascita; Inoltre, come in tutte le istruzioni di definizione del C, nella stessa istruzione si può definire più di una variabile. Ad es., l’istruzione struct {int giorno; int mese; int anno;} nascita, attuale; crea due strutture con la stessa forma. I membri della prima hanno come riferimenti i nomi individuali nascita.giorno nascita.mese nascita.anno mentre quelli della seconda hanno come riferimenti i nomi attuale.giorno attuale.mese attuale.anno Nomi tag. Un’utile modifica nella definizione delle strutture consiste nell’elencare il formato della struttura senza farla seguire da nomi di variabili. In tale caso, però, la lista dei membri della struttura deve essere preceduta da un nome tag. Ad es., nella istruzione di dichiarazione struct Data { int giorno; int mese; int anno; }; il termine Data è un nome tag. Di solito i nomi tag vengono scritti con l’iniziale maiuscola. Si dice che la dichiarazione della struttura Data fornisce un modello per la struttura, senza riservare effettivamente alcuna locazione di memoria. Pertanto essa non è un’istruzione di definizione. Il modello presenta il formato della struttura Data descrivendo come i singoli dati sono organizzati in essa. La memoria effettiva per i membri della struttura viene riservata solo quando sono assegnati nomi specifici di variabili. Ad es., l’istruzione di definizione struct Data nascita, attuale; riserva memoria per due strutture chiamate rispettivamente nascita e attuale, ciascuna delle quali ha la forma dichiarata in precedenza per la struttura Data. In effetti, la dichiarazione di Data crea un tipo struttura chiamato Data. Le variabili nascita e attuale sono quindi definite appartenenti a questo tipo struttura. Perciò la stessa uscita del programma precedente si ottiene con il seguente: #include <stdio.h> struct Data { int giorno; int mese; int anno; }; void main(void) { struct Data nascita; nascita.giorno = 31; nascita.mese = 05; nascita.anno = 1946; printf("Sono nato il giorno %d/%d/%d", nascita.giorno, nacita.mese, nascita.anno); } Inizializzazione delle strutture. L’inizializzazione di una struttura segue le stesse regole di quella di un vettore, e si può ottenere facendo seguire la definizione da una lista di valori di inizializzazione. Ad es., l’istruzione di definizione struct Data nascita = {31, 05, 1946}; può sostituire le prime quattro istruzioni interne alla funzione main() del programma precedente. Osserviamo che, come nel caso dei vettori: i valori di inizializzazione vanno racchiusi tra parentesi graffe e separati con virgole Ovviamente i membri individuali di una struttura non sono limitati al tipo dati intero, come nel caso della struttura Data, ma possono essere di qualsiasi tipo dati valido, compresi i vettori e le strutture stesse. Ad es., consideriamo i dati per il calcolo di uno stipendio consistenti nelle seguenti voci: Nome Numero identificazione Stipendio base Stipendio per straordinari Una dichiarazione valida per queste voci è; struct Rec_stip { char nome[20]; int num_ident; float stip_base; float stip_stra; }; nella quale abbiamo usato come primo membro un vettore di caratteri. Una volta dichiarato il modello per Rec_stip, si può definire e inizializzare una struttura specifica che usi tale modello. Ad es., la definizione struct Rec_stip impiegato={“F. Russo”,1234,20.85,29.40}; crea una struttura di nome impiegato che usa il modello Rec_stip. I membri individuali di impiegato sono inizializzati con i rispettivi dati elencati tra parentesi nella istruzione di definizione. Osservazione. Una struttura singola è un metodo conveniente per combinare e memorizzare voci collegate sotto un nome comune. Sebbene essa sia utile per identificare in modo esplicito le relazioni tra i suoi membri, essi potrebbero essere definiti anche come variabili separate. Il vantaggio di usare una struttura si comprende quando si usa lo stesso modello più volte in una lista. Vedremo come creare liste con lo stesso modello di struttura. Osservazione. Per accedere a un elemento di un vettore membro di una struttura occorre fornire il nome della struttura, seguito da un punto e dalla designazione dell’elemento. Ad es., impiegato.nome[4] è un riferimento al quinto carattere del vettore nome nella struttura impiegato. L’inclusione di una struttura in un struttura segue le stesse regole dell’inclusione in una struttura di qualsiasi tipo dati. Ad es., supponiamo che una struttura consista in un nome e una data di nascita, e che la struttura per quest’ultima sia quella già dichiarata in precedenza: struct Data { int giorno; int mese; int anno; }; Una definizione conveniente di una struttura che comprenda un nome e una struttura per una data è la seguente: struct { char nome[20]; struct Data nascita; } persona; Osserviamo che: • nel dichiarare la struttura per la data il termine Data è un nome tag di struttura; • un nome tag si trova sempre prima delle parentesi nella istruzione di dichiarazione e identifica un modello di struttura; • nel definire la struttura persona, persona è il nome di una struttura specifica, e non un nome tag di struttura. Lo stesso succede con la variabile di nome nascita: questo è il nome di una struttura specifica che ha la forma di Data. Per accedere a uno specifico membro della struttura nascita contenuta nella struttura persona si dovranno usare evidentemente tre nomi, separati da due punti. Ad es., persona.nascita.mese è un riferimento alla variabile mese nella struttura nascita contenuta nella struttura persona. Osservazione. Si tenga presente che due strutture non possono essere confrontate usando gli operatori == e !=. Vettori di strutture. La vera potenza delle strutture si comprende quando si usa la stessa struttura per costruire una lista di dati. Ad es., supponiamo che si debbano elaborare i dati mostrati in tabella. Ovviamente si potrebbero memorizzare tutti i numeri impiegati in un vettore di interi, i nomi in un vettore di puntatori e gli stipendi in un vettore di numeri in virgola mobile o in doppia precisione. Organizzando i dati in tale maniera, ogni colonna della tabella verrebbe considerata come una lista separata, memorizzata nel suo proprio vettore. In tale caso, la corrispondenza tra le voci relative a ciascun impiegato verrebbe mantenuta memorizzando i dati di un impiegato nella stessa posizione in ciascun vettore. Però la separazione della lista completa in tre vettori individuali è poco opportuna, dato che tutte le voci relative a un sigolo impiegato costituiscono una naturale organizzazione dei dati in record, come mostra la tabella seguente. Usando una struttura, l’integrità dell’organizzazione dei dati in record si può mantenere e riflettersi nel programma. Secondo questo approccio, la lista della tabella precedente può essere elaborata come un singolo vettore di 6 strutture. Dichiarare un vettore di strutture è analogo a dichiarare un vettore di qualsiasi altro tipo di variabili. Ad es., se il modello Rec_stip è dichiarato come struct Rec_stip {int numiden; char nome[20]; float stip;}; allora si può definire un vettore di 6 di tali strutture come: struct Rec_stip impiegato[6]; Questa istruzione di definizione costruisce un vettore di sei elementi, ciascuno dei quali è una struttura del tipo Rec_stip. Osserviamo che la creazione di un vettore di 6 strutture ha la stessa forma della creazione di qualsiasi altro vettore. Ad es., la creazione di un vettore di 6 interi di nome impiegato richiede la dichiarazione int impiegato[6]; In questa dichiarazione il tipo dati è intero, mentre nella precedente dichiarazione di impiegato il tipo dati è una struttura che usa il modello Rec_stip. Una volta che un vettore di strutture sia stato dichiarato, si fa riferimento a una particolare voce dando la posizione della struttura desiderata nel vettore, seguita da un punto e dall’appropriato membro della struttura. Ad es., la variabile impiegato[0].stip è un riferimento al membro stip della prima struttura impegato nel vettore impiegato. L’inserimento di strutture quali elementi di un vettore permette di elaborare una lista di record usando le tecniche standard di programmazione per i vettori. Il programa seguente visualizza i primi 4 record di impiegato illustrati nella tabella precedente. #include <stdio.h> struct Rec_stip { int numiden; char nome[20]; float stip; }; void main(void) { int i; struct Rec_stip impiegato[4] = { { 3247, "Arduini, B.", 6.72 }, { 3362, "Baldovini, P.", 7.54}, { 3414, "Donna, S.", 5.56}, { 3598, "Ersili, T.", 5.43 }, }; for (i = 0; i < 4; i++) printf("%d %-20s %4.2f\n",impiegato[i].numiden, impiegato[i].nome, impiegato[i].stip); } Esso produce la seguente uscita: Nel programma precedente osserviamo l’inizializzazione del vettore di strutture: sebbene i valori di inizializzazione di ogni struttura siano stati racchiusi tra parentesi interne, esse non sono strettamente necessarie, dato che tutti i membri sono stati inizializzati. La sequenza di controllo di conversione %-20s, inserita nella chiamata alla funzione printf(), fa sì che ciascun nome sia visualizzato giustificato a sinistra in un campo di 20 spazi. Liste concatenate di strutture. Abbiamo già visto come il tipo dati lista concatenata permetta di inserire e/o cancellare in modo semplice una nuova struttura (o record) all’interno di un vettore di strutture, quale ad es.: Aloisi, Sandro 0432 174973 Dolan, Edih 02 385602 Lisi, Giovanni 0556 390048 Melloni, Paolo 02 35581224 Zermann Harold 091 1275294 Abbiamo anche visto come, per realizzare una lista concatenata, sia sufficiente aggiungere a ogni struttura di un vettore di strutture un ulteriore membro, il cui valore è l’indirizzo della successiva struttura in ordine logico della lista. Quindi, riferendoci alle prime tre strutture del vettore precedente, avremmo: In tal modo, ad e., i dati effettivi per la struttura Lisi potranno essere memorizzati fisicamente in qualsiasi punto della memoria, dato che il membro aggiuntivo inserito alla fine della struttura Dolan mantiene comunque l’ordine alfabetico corretto. Questo membro aggiuntivo fornisce l’indirizzo di partenza della locazione dove è memorizzato il record Lisi e, come ci si può aspettare è un puntatore. Per vedere l’utilità del puntatore inserito nel record Dolan, aggiungiamo nella precedente lista una struttura contenente il nome Ialongo, Marco e il suo numero di telefono. Affinché il numero di telefono di Ialongo sia visualizzato nel corretto ordine alfabetico (ossia dopo quello di Dolan), è sufficiente: • modificare l’indirizzo nel record Dolan in modo che punti al record Ialongo, e • impostare l’indirizzo nel record Ialongo in modo che punti al record Lisi. Ciò è illustrato in figura. Osserviamo che in ciascuna struttura il puntatore punta semplicemente alla locazione della struttura che segue nell’ordine, anche se essa non è posizionata fisicamente nell’ordine corretto. La rimozione di una struttura dalla lista ordinata è il processo inverso dell’aggiunta di una struttura. La struttura attuale viene rimossa dal punto di vista logico dalla lista semplicemente cambiando l’indirizzo nella struttura che precede quella cancellata in modo che punti alla struttura immediatamente seguente. Puntatori speciali. Ogni struttura di una lista concatenata ha il medesimo formato; tuttavia è chiaro che l’ultimo record non può avere un valore valido di puntatore che punti a un altro record, dato che tale record non esiste. Il C fornisce uno speciale valore di puntatore, detto NULL, che funge da sentinella o flag per indicare quando l’ultimo record è stato elaborato. Il valore di puntatore NULL, come la sua controparte fine di stringa, ha valore numerico zero. Oltre a un valore sentinella di fine lista, è necessario anche un puntatore speciale per memorizzare l’indirizzo della prima struttura della lista. La figura illustra l’insieme completo di puntatori e strutture per una lista che consiste in tre nomi. Il fatto che in una struttura sia stato incluso un puntatore non deve sorprendere in quanto, come abbiamo visto, una struttura può contenere qualsiasi tipo dati. Ad es., la dichiarazione di struttura struct Test { int num_iden; double *punt_stip; }; dichiara un modello di struttura consistente in due membri: • il primo è una variabile intera di nome num_iden • il secondo è un puntatore, di nome punt_stip, che punta a un numero in doppia precisione. Il programma seguente illustra come il membro puntatore di una struttura sia usato come qualsiasi altra variabile puntatore. #include <stdio.h> struct Test { int num_iden; double *punt_stip; }; void main(void) { struct Test imp; double stip = 456.20; imp.num_iden = 12345; imp.punt_stip = &stip; printf("L’impiegato numero %d è stato pagato €%6.2f\n", imp.num_iden, *imp.punt_stip); } Esso produce la seguente visualizzazione: L’impiegato numero 12345 è stato pagato €456.20 La figura seguente illustra la relazione tra i membri della struttura imp e la variabile di nome stip. Il valore assegnato a imp.num_iden è il numero 12345 e il valore assegnato a stip è 456.20. L’indirizzo della variabile stip è assegnato al membro della struttura imp.punt_stip. Dato che questo membro è stato definito come un puntatore a un numero in doppia precisione, è corretto porre in esso l’indirizzo della variabile in doppia precisione stip. Infine, dato che l’operatore di membro . ha una precedenza più alta dell’operatore di indirezione *, l’esprssione usata nella chiamata printf() del programma precedente è corretta. L’espressione *imp.punt_stip è equivalente all’espressione *(imp.punt_stip) e si leggono entrambe come: la variabile il cui indirizzo è contenuto nel membro imp.punt_stip. Sebbene il proramma precedente usi il puntatore in modo piuttosto banale, esso illustra il concetto di inserire un puntatore in una struttura. Questo concetto può essere facilmente esteso per creare una lista concatenata di strutture in grado di memorizzare i nomi e i numeri di telefono elencati in precedenza: Aloisi, Sandro 0432 174973 Dolan, Edith 02 385602 Lisi, Giovanni 0556 390048 Mastrocinque, Paolo 02 35581224 Santabarbara Harold 091 1275294 Un modello per tale struttura è creato dalla seguente dichiarazione: struct Tipo_tel { char nome[30]; char num_tel[15]; struct Tipo_tel *prossindir; } Il modello Tipo_tel consiste in tre membri: un vettore di 30 caratteri, in grado di memorizzare nomi con un massimo di 29 lettere e un marcatore di fine stringa; un vettore di 15 caratteri, in grado di memorizzare numeri di telefono con i loro prefissi; un puntatore in grado di memorizzare l’indirizzo di una struttura di modello Tipo_tel. Il programma seguente illustra l’uso del modello Tipo_tel definendo specificamente tre strutture che hanno questa forma. Le tre strutture vengono chiamate t1, t2, t3, e il nome e il numero di telefono di ciascuna di esse sono inizializzati quando le strutture sono definite, usando i dati elencati nella tabella precedente. #include <stdio.h> struct Tipo_tel { char nome[30]; char num_tel[15]; struct Tipo_tel *prossindir; }; void main(void) { struct Tipo_tel t1 = {"Aloisi, Sandro", "0432 174973"}; struct Tipo_tel t2 = {"Dolan, Edith", "02 385602"}; struct Tipo_tel t3 = {"Lisi, Giovanni", "0556 390048"}; struct Tipo_tel *primo; /*crea un puntatore a una struttura*/ primo = &t1; t1.prossindir = &t2; t2.prossindir = &t3; t3.prossindir = NULL; printf("\n%s \n%s \n%s\n", primo->nome, t1.prossindir->nome, t2.prossindir->nome); } Il programma produce la seguente uscita: Aloisi, Sandro Dolan, Edith Lisi, Giovanni e dimostra l’uso dei puntatori per accedere ai successivi membri di una struttura. Il programma utilizza l’operatore puntatore di struttura ->, che agisce in modo simile a * quando è usato con i puntatori, e dice di prendere ciò che si trova a quell’indirizzo di memoria (e non di prendere quell’indirizzo di memoria). Come mostra la figura seguente, ogni struttura contiene l’indirizzo della struttura successiva nella lista. L’inizializzazione dei nomi e dei numeri di telefono per ciascuna struttura definita nel programma è semplice. Sebbene ciascuna struttura consista in tre membri, solo i primi due sono inizializzati; dato che entrambi sono vettori di caratteri, possono essere inizializzati con delle stringhe. Il terzo membro di ciascuna struttura è un puntatore. Per creare una lista concatenata, al puntatore in ciascuna struttura si deve assegnare l’indirizzo della successiva struttura della lista. Le quattro istruzioni di assegnazione eseguono le corrette assegnazioni: ad es. l’espressione • primo = &t1 memorizza l’indirizzo della prima struttura della lista nella variabile puntatore di nome primo, mentre •t1.prossindir = &t2 memorizza l’indirizzo della struttura t2 nel membro puntatore della struttura t1. Per terminare la lista, si memorizza nel membro puntatore della struttura t3 il valore del puntatore NULL, che è zero. Una volta assegnati i valori a ogni membro di struttura, e memorizzati negli opportuni puntatori i corretti indirizzi, questi ultimi sono usati per accedere a ciascun membro nome della struttura. Ad es., l’espressione t1.prossindir -> nome è un riferimento al (e si legge) membro nome della struttura il cui indirizzo si trova nel membro prossindir della struttura t1. Dato che l’operatore di membro . ha la stessa precedenza dell’operatore puntatore di struttura ->, la precedente espressione è valutata come (t1.prossindir) -> nome Dato che t1.prossindir contiene l’indirizzo della struttura t2, si accede al nome corretto. Ovviamente l’espressione t1.prossindir -> nome può essere sostituta dall’espressione equivalente (*t1.prossindir).nome, che usa il pù familiare operatore di indirezione * e che si può leggere: il membro nome della variabile il cui indirizzo si trova in t1.prossindir. Esercizio. Modificare il programma precedente in modo che stampi anche i numeri di telefono. Gli indirizzi in una lista concatenata di strutture si possono usare per compiere un ciclo entro l’intera lista. Quando si accede a una struttura, si può esaminarla per selezionare un valore specifico, oppure usarla per stampare una lista completa. Ad es., la funzione mostra() del programma seguente contiene un ciclo while che usa gli indirizzi contenuti nel membro puntatore di ogni struttura per compiere un ciclo attraverso la lista e visualizzare in successione i dati contenuti in ogni struttura. #include <stdio.h> struct Tipo_tel { char nome[30]; char num_tel[15]; struct Tipo_tel *prossindir; }; void main(void) { struct Tipo_tel t1 = {"Aloisi, Sandro","0432 174973"}; struct Tipo_tel t2 = {"Dolan, Edith","02 385602"}; struct Tipo_tel t3 = {"Lisi, Giovanni","0556 390048"}; struct Tipo_tel *primo; void mostra(struct Tipo_tel *); /*prototipo di funzione*/ primo = &t1; t1.prossindir=&t2; t2.prossindir=&t3; t3.prossindir=NULL; mostra(primo); } /*chiamata di funzione*/ void mostra(struct Tipo_tel *contenuto) /*intestazione*/ { while (contenuto != NULL) { printf("%-30s %-20s\n",contenuto->nome,contenuto->num_tel); contenuto=contenuto->prossindir; } return; } Ecco l’uscita prodotta: Aloisi, Sandro Dolan, Edith Lisi, Giovanni 0432 174973 02 385602 0556 390048 Osservazioni. Il programma precedente illustra l’importante concetto dell’uso degli indirizzi contenuti in una struttura per accedere ai membri della struttura che la segue nella lista. Quando si chiama la funzione mostra(), le viene passato il valore memorizzato nella variabile primo; dato che primo è una variabile puntatore, il valore effettivamente passato è un indirizzo (quello della struttura t1). mostra() memorizza il valore passatole nell’argomento contenuto. Per una corretta memorizzazione dell’indirizzo passato, contenuto è dichiarato come un puntatore a una struttura di tipo Tipo_tel. mostra() esegue un ciclo while attraverso le strutture concatenate, a partire da quella il cui indirizzo è in contenuto. La condizione controllata nell’istruzione while confronta il valore in contenuto (che è un indirizzo) con il valore NULL. Per ogni indirizzo valido: • sono visualizzati i membri nome e numero di telefono della struttura indirizzata, quindi • l’indirizzo che si trova in contenuto viene aggiornato con quello che si trova nel membro puntatore della struttura corrente, quindi • si controlla di nuovo l’indirizzo in contenuto, e il processo continua fino a quando l’indirizzo non sia uguale al valore NULL. mostra() non “sa” nulla circa i nomi delle strutture dichiarate in main(), e neppure quante strutture esistano, ma si limita a compiere dei cicli attraverso la lista concatenata, struttura per struttura, fino a che incontra l’indirizzo NULL di fine lista. Dato che il valore di NULL è zero, la condizione controllata può essere sostituita dall’espressione equivalente !contenuto. Uno svantaggio del programma precedente è che in main() sono definite per nome esattamente tre strutture, per le quali viene riservata memoria in fase di compilazione. Se fosse necessaria una quarta struttura, essa andrebbe dichiarata e il programma ricompilato. Vedremo più avanti come fare allocare e rilasciare dinamicamente al computer la memoria per le strutture in fase di esecuzione, via via che serva memoria. La memoria per una nuova struttura verrà creata solo quando si debba aggiungere una nuova struttura alla lisa, e mentre il programma è in esecuzione. Analogamente, quando una struttura non è più necessaria e può essere cancellata dalla lista, la memoria per un record cancellato sarà rilasciata e restituita al computer. Unioni Ricordiamo che una unione è un tipo dati che riserva la stessa area in memoria per due o più variabili, che possono appartenere a tipi dati differenti. Una variabile dichiarata come unione può essere usata per contenere una variabile di tipo carattere, intera, in doppia precisione o di qualsiasi altro tipo dati valido. Ognuno di questi tipi, ma solamente uno alla volta, può essere assegnato effettivamente alla unione. Dichiarazione. La dichiarazione di una unione ha la stessa forma di quella di una struttura, con la parola riservata union al posto di struct. Ad es. la dichiarazione union { char chiave; int numero; double prezzo; } valore; crea una variabile unione di nome valore che contiene un singolo membro che può essere: • o una variabile intera di nome numero, • o una variabile carattere di nome chiave, • o una variabile in doppia precisione di nome prezzo. In effetti, la dichiarazione di una unione riserva locazioni di memoria sufficienti per accogliere il tipo dati del suo membro più grande. A questo stesso insieme di locazioni si farà quindi riferimento con differenti nomi di variabili, a seconda del tipo dati del valore che attualmente vi si trova. Ogni valore memorizzato sovrascrive il precedente, usando tutti i byte necessari della memoria riservata. Per accedere alla variabile memorizzata in una unione si indica il nome dell’unione, seguito dall’operatore “.” e dal nome della variabile corrispondente al tipo dati del valore memorizzato. Ad es., se nell’unione valore è memorizzato un carattere, il nome di variabile per accedervi è: valore.chiave. Analogamente, se nell’unione è memorizzato un intero, vi si accede con il nome valore.numero mentre si accede a un valore in doppia precisione con il nome valore.prezzo Nel riferirsi al membro di una unione, si deve quindi usare il nome corretto del membro per il tipo dati che attualmente si trova nell’unione. In genere, per tenere traccia del tipo dati attualmente memorizzato in una unione si usa una seconda variabile. Ad es., per selezionare l’appropriato membro di valore da visualizzare si può usare il codice seguente, nel quale il valore nella variabile tipo_u determina il tipo dati attualmente memorizzato nella unione valore. switch(tipo_u) { case ‘c’: printf(“%c”, valore.chiave); Qui il valorebreak; nella variabile tipo_u determina il tipo dati attualmente case ‘i’: printf(“%d”, valore.numero); memorizzato nell’unione val. break; case ‘d’: printf(“%f”, valore.prezzo); break; default : printf(“Tipo non valido in tipo_u : %c”,tipo_u); } Nomi tag. Come nelle strutture, si può associare a una unione un nome tag per creare dei modelli. Ad es., la dichiarazione union data_ora { long int giorni; double ora; }; fornisce un modello per una unione senza riservare effettivamente alcuna locazione di memoria. Si può quindi usare il modello per definire un qualsiasi numero di variabili del tipo unione data_ora: ad es., la definizione union data_ora primo, secondo, *pt; crea una variabile unione di nome primo, una variabile unione di nome secondo e un puntatore che può memorizzare l’indirizzo di qualsiasi unione con la forma di data_ora. Una volta dichiarato un puntatore a una unione, per accedere ai membri di essa si può usare la stessa notazione usata per accedere ai membri di una struttura. Ad es., se è stata fatta l’assegnazione pt = &primo; allora pt -> data è un riferimento al membro data della unione primo. Le unioni possono essere membri di strutture e vettori, mentre strutture, vettori e puntatori possono essere membri di unioni. In ogni caso la notazione usata per accedere a un membro deve essere coerente con la strutturazione impiegata. Ad es., nella struttura definita da struct { char tipo_u; union { char *testo; float velocità; } tasso_u; } flag; il riferimento alla variabile velocità è: flag.tasso_u.velocità mentre il riferimento al primo carattere della stringa il cui indirizzo è memorizzato nel puntatore testo è *flag.tasso_u.testo Operazioni. Con le unioni si possono compiere le seguenti operazioni: • assegnare una unione a un’altra dello stesso tipo; • rilevare l’indirizzo (&) di una variabile di tipo unione; • accedere ai membri di una unione usando gli operatori membro di struttura e puntatore di struttura. Come le strutture, le unioni non possono essere confrontate usando gli operatori == e !=. Il programma che segue usa la variabile valore, di tipo union numero, per visualizzare il valore memorizzato nell’unione, sia come int, sia come double. #include <stdio.h> union numero { int x; double y; }; int main() { union numero valore; valore.x = 100; printf("%s \n %s %d \n %s %f \n\n", "Scrive un valore nel membro intero e stampa i due membri", "int: ", valore.x, “double:", valore.y); valore.y = 100.0; printf("%s \n %s %d \n %s %f \n", "Scrive un valore nel membro double e stampa i due membri", "int: ", valore.x, “double:", valore.y); return 0; } Ecco l’uscita prodotta (che dipende dalla implementazione) : Scrive un valore nel membro intero e stampa i due membri int: 100 double: 0.000000 Scrive un valore nel membro double e stampa i due membri int: 0 double: 100.000000