[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]


155. C: tipi di dati derivati

Fino a questo punto sono stati incontrati solo i tipi di dati primitivi, oltre agli array di questi (incluse le stringhe). Nel linguaggio C, come in altri, è possibile definire dei tipi di dati aggiuntivi, derivati dai tipi primitivi.

155.1 Strutture e unioni

Si è già visto in che modo siano organizzati gli array: come una serie di elementi uguali, tutti adiacenti nel modello di rappresentazione della memoria (ideale o reale che sia). In modo simile si possono definire strutture di dati più complesse in cui gli elementi adiacenti siano di tipo differente. In pratica, una struttura è una sorta di mappa di accesso a un'area di memoria.

La variabile contenente una struttura si comporta in modo analogo alle variabili di tipo primitivo, per cui, la variabile che è stata creata a partire da una struttura, rappresenta tutta la zona di memoria occupata dalla struttura stessa, e non solo il riferimento al suo inizio. Questa distinzione è importante, per non fare confusione con il comportamento relativo agli array, che in realtà sono solo dei puntatori.

155.1.1 Dichiarazione e accesso

La dichiarazione di una struttura si articola in due fasi: la dichiarazione del tipo e la dichiarazione delle variabili che utilizzano quella struttura. Dal momento che il tipo di struttura è una cosa diversa dalle variabili che l'utilizzeranno, è opportuno stabilire una convenzione nel modo di attribuirne il nome. Per esempio, si potrebbero utilizzare nomi con iniziale maiuscola.

struct Data { int iGiorno; int iMese; int iAnno; };

L'esempio mostra la dichiarazione della struttura `Data' composta da tre interi dedicati a contenere rispettivamente: il giorno, il mese e l'anno. In questo caso, trattandosi di tre elementi dello stesso tipo si sarebbe potuto utilizzare un array, ma come si vedrà in seguito, una struttura può essere conveniente anche in queste situazioni.

È importante osservare che le parentesi graffe sono parte dell'istruzione di dichiarazione della struttura, e non rappresentano un blocco di istruzioni. Per questo motivo appare il punto e virgola finale, cosa che potrebbe sembrare strana, specialmente quando la struttura si articola su più righe come nell'esempio seguente:

struct Data {
    int iGiorno;
    int iMese;
    int iAnno;
};		/* il punto e virgole finale è necessario */

La dichiarazione delle variabili che utilizzano la struttura può avvenire contestualmente con la dichiarazione della struttura, oppure in un momento successivo. L'esempio seguente mostra la dichiarazione del tipo `Data', seguito da un elenco di variabili che utilizzano quel tipo: `DataInizio' e `DataFine'.

struct Data {
    int iGiorno;
    int iMese;
    int iAnno;
} DataInizio, DataFine;

Tuttavia, il modo più elegante per dichiarare delle variabili a partire da una struttura è quello seguente:

struct Data DataInizio, DataFine;

Quando una variabile è stata definita come organizzata secondo una certa struttura, si accede ai suoi componenti attraverso l'indicazione del nome della variabile stessa, seguita dall'operatore punto (`.') e dal nome dell'elemento particolare.

DataInizio.iGiorno = 1;
DataInizio.iMese = 1;
DataInizio.iAnno = 1980;
...
DataFine.iGiorno = DataInizio.iGiorno;
DataFine.iMese = DataInizio.iMese +1;
DataFine.iAnno = DataInizio.iAnno;

155.1.2 Strutture anonime

Una struttura può essere dichiarata in modo anonimo, definendo immediatamente tutte le variabili che fanno uso di quella struttura. La differenza sta nel fatto che la struttura non viene nominata nel momento della dichiarazione, e dopo la definizione dei suoi elementi devono essere elencate tutte le variabili in questione. Evidentemente, non c'è la possibilità di riutilizzare questa struttura per altre variabili definite in un altro punto.

struct {
    int iGiorno;
    int iMese;
    int iAnno;
} DataInizio, DataFine;

155.1.3 Assegnamento, inizializzazione, campo d'azione e puntatori

Nella sezione precedente si è visto come accedere ai vari componenti della struttura, attraverso una notazione che utilizza l'operatore punto. Volendo è possibile assegnare a una variabile di questo tipo l'intero contenuto di un'altra che appartiene alla stessa struttura.

DataInizio.iGiorno = 1;
DataInizio.iMese = 1;
DataInizio.iAnno = 1999;
...
DataFine = DataInizio;
DataFine.iMese++;

L'esempio mostra l'assegnamento alla variabile `DataFine' di tutta la variabile `DataInizio'. Questo è ammissibile solo perché si tratta di variabili dello stesso tipo, cioè di strutture di tipo `Data' (come deriva dagli esempi precedenti). Se invece si trattasse di variabili costruite a partire da strutture differenti, anche se realizzate nello stesso modo, ciò non sarebbe ammissibile.

Nel momento della dichiarazione di una struttura, è possibile anche inizializzarla utilizzando una forma simile a quella disponibile per gli array.

struct Data DataInizio = { 1, 1, 1999 };

Dal momento che le strutture sono tipi di dati nuovi, per poterne fare uso occorre che la dichiarazione relativa sia accessibile a tutte le parti del programma che hanno bisogno di accedervi. Probabilmente, il luogo più adatto è al di fuori delle funzioni, eventualmente anche in un file di intestazione realizzato appositamente.

Ciò dovrebbe bastare a comprendere che le variabili che contengono una struttura vengono passate regolarmente attraverso le funzioni, purché la dichiarazione del tipo corrispondente sia precedente ed esterno alla descrizione delle funzioni stesse.

...
struct Data { int iGiorno; int iMese; int iAnno; };
...
void elabora( struct Data DataAttuale ) {
    ...
}

Così come nel caso dei tipi primitivi, anche con le strutture si possono creare dei puntatori, e la loro dichiarazione avviene in modo intuitivo, come nell'esempio seguente:

struct Data *pDataFutura;
...
pDataFutura = &DataFine;
...

Quando si utilizza un puntatore a una struttura, diventa un po' più difficile fare riferimento ai vari componenti della struttura stessa, perché l'operatore punto, quello che unisce il nome della struttura a quello dell'elemento, ha priorità rispetto all'asterisco che utilizza per dereferenziare il puntatore.

*pDataFutura.iGiorno = 15; /* non è valido */

L'esempio appena mostrato, non è ciò che sembra, perché l'asterisco posto davanti viene valutato dopo l'elemento `pDataFutura.iGiorno', che non esiste. Per risolvere il problema si possono usare le parentesi, come nell'esempio seguente:

(*pDataFutura).iGiorno = 15; /* corretto */

In alternativa, in sostituzione del punto si può usare l'operatore `->', fatto espressamente per i puntatori a una struttura.

pDataFutura->iGiorno = 15; /* corretto */

155.1.4 Unioni

L'unione permette di definire un tipo di dati accessibile in modi diversi, gestendolo come se si trattasse contemporaneamente di tipi differenti. La dichiarazione è simile a quella della struttura; quello che bisogna tenere a mente è che si fa riferimento alla stessa area di memoria.

union Livello {
    char cLivello;
    int iLivello;
};

Si immagini, per esempio, di voler utilizzare indifferentemente una serie di lettere alfabetiche, oppure una serie di numeri, per definire un livello di qualcosa («A» equivalente a uno, «B» equivalente a due, ecc.). Le variabili generate a partire da questa unione, possono essere gestite in questi due modi, come se fossero una struttura, ma condividendo la stessa area di memoria.

union Livello LivelloCarburante;

L'esempio mostra in che modo si possa dichiarare una variabile di tipo `Livello', riferita all'omonima unione. Il bello delle unioni sta però nella possibilità di combinarle con le strutture.

struct Livello {
    char cTipo;
    union {
	char cLivello; /* usato se cTipo == 'c' */
	int iLivello;  /* usato se cTipo == 'n' */
    };
};

L'esempio non ha un grande significato pratico, ma serve a chiarire le possibiltà. La variabile `cTipo' serve ad annotare il tipo di informazione contenuta nell'unione, se di tipo carattere o numerico. L'unione viene dichiarata in modo anonimo come appartenente alla struttura.

155.1.5 Campi

All'interno di una struttura è possibile definire l'accesso a ogni singolo bit di un determinato tipo di dati, oppure a gruppetti di bit. In pratica viene dato un nome a ogni bit o gruppetto.

struct Luci {
    unsigned char
	bA	:1,
	bB	:1,
	bC	:1,
	bD	:1,
	bE	:1,
	bF	:1,
	bG	:1,
	bH	:1,
};

L'esempio mostra l'abbinamento di otto nomi ai bit di un tipo `char'. Il primo, `bA', rappresenta il bit più a destra, ovvero quello meno significativo. Se il tipo `char' occupasse una dimensione maggiore di 8 bit, la parte eccedente verrebbe semplicemente sprecata.

struct Luci LuciSalotto;
...
LuciSalotto.bC = 1;

L'esempio mostra la dichiarazione della variabile `LuciSalotto' come appartenente alla struttura mostrata sopra, e poi l'assegnamento del terzo bit a uno, probabilmente per «accendere» la lampada associata.

Volendo indicare un gruppo di bit maggiore, basta aumentare il numero indicato a fianco dei nomi dei campi, come nell'esempio seguente:

struct Prova {
    unsigned char
	bA	:1,
	bB	:1,
	bC	:1,
	stato	:4;
};

Nell'esempio appena mostrato, si usano i primi tre bit in maniera singola (per qualche scopo), e altri quattro per contenere un'informazione più consistente. Ciò che resta (probabilmente solo un bit) viene semplicemente ignorato.

155.1.6 typedef

L'istruzione `typedef' permette di definire un nuovo di tipo di dati, in modo che la sua dichiarazione sia più agevole. Lo scopo di tutto ciò sta nell'informare il compilatore; `typedef' non ha altri effetti. La sintassi del suo utilizzo è molto semplice.

typedef <tipo> <nuovo-tipo>

Si osservi l'esempio seguente:

struct Data {
    int iGiorno;
    int iMese;
    int iAnno;
};
typedef struct Data;
Data DataInizio, DataFine;

Attraverso `typedef', è stato definito il tipo `Data', facilitando così la dichiarazione delle variabili `DataInizio' e `DataFine'.

---------------------------

Appunti Linux 2000.04.12 --- Copyright © 1997-2000 Daniele Giacomini --  daniele @ pluto.linux.it


[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]