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