Le strutture

30.1 Le strutture: generalita'

Il C prevede un particolare costrutto che permette di raggruppare piu' variabili anche di tipi diversi, all'interno di un'unica entita': la struttura. Tale costrutto e' simile ai record del Pascal o del Cobol. Un esempio puo' essere una struttura 'anagrafica' che contiene tutte le informazioni anagrafiche appunto, di un cliente. La sintassi e' la seguente:

struct  nome-opzionale { var1; var2; ...} var-opzionale1, var-opzionale2, ...;

             esempio:

struct punto
	{
		int x;
		int y;
	}

nell'esempio qui sopra e' stata dichiarata una struttura di nome 'punto' che contiene le coordinate x e y. La parola chiave struct e' obbligatoria ed e' seguita da un nome opzionale detto identificatore che identifica il tipo di struttura. Le variabili specificate all'interno della struttura vengono definite membri. Una dichiarazione struct definisce un tipo, ma non alloca spazio in memoria. E' possibile tuttavia allocare definire una struttura ed allocare un oggetto in memoria del tipo appena definito:

struct punto
	{
		int x;
		int y;
	} miopunto;

dove 'miopunto' e' una variabile di tipo 'punto' e punto e' una struttura. E' possibile istanziare una variabile di tipo punto anche dopo aver dichiarato la struttura:

struct punto
	{
		int x;
		int y;
	};

struct punto miopunto;   /* concettualmente simile ad una dichiarazione come: int mia_variabile;  */

Occorre fare attenzione al punto e virgola (;) che termina la dichiarazione di struttura. La prima espressione dichiara un tipo di struttura, mentre la seconda espressione definisce una variabile che ha le caratteristiche della struttura dichiarata prima. E' possibile inizializzare una struttura mediante un assegnamento simile a quello utilizzato nei vettori:

struct punto miopunto = {100, 200};

dove all'interno delle parentesi graffe e' presente una lista di inizializzatori. Per far riferimento ad un membro di una struttura si utilizza il simbolo '.' attraverso un costrutto di questo tipo:

nome-struttura.membro

        esempio:

miopunto.x = 100;
miopunto.y = 200;

Le strutture possono essere nidificate, percio', data la struttura punto dichiarata prima, e' possibile ad esempio dichiarare una seconda struttura:

struct rettangolo
	{
		struct punto p1;
		struct punto p2;
	};

Quando si dichiara una struttura, si definisce una tipologia di variabile composta (cioe' derivata dai tipi primitivi). La struttura rettangolo ad esempio, definisce la tipologia di variabili 'rettangolo', percio' e' possibile dichiarare delle variabili di questo tipo appena appena creato:

struct rettangolo mio_rect;

         e quindi:

     Y

     ^
     |
     |
     |                   p2
     |     ------------- 
     |    |             |
     |    |             |
     |     ------------- 
     |   p1
----- ----------------------------> X
     |
     |

mio_rect.p1.x, mio_rect.p1.y, mio_rect.p2.x e mio_rect.p2.y, corrispondono ai 4 punti del rettangolo mostrato sopra.

30.2 Funzioni e strutture

Una struttura e' una variabile composta e derivata dai tipi primitivi del C, ma puo' essere gestita come qualsiasi variabile di tipo primitivo. Percio' una struttura puo' essere assegnata, copiata, passata ad una funzione, indirizzata mediante l'operatore & e cosi' via. E' anche possibile allocare vettori di strutture e puntatori a strutture. E' possibile passare ad una funzione un'intera struttura, membri della struttura od un puntatore ad essa:

struct punto creapunto (int x, int y)
{
	struct punto temp;
	temp.x = x;
	temp.y = y;
	return temp;
}

	oppure:

struct punto sommapunti (struct punto p1, struct punto p2)
{
	p1.x = p1.x   p2.x;
	p1.y = p1.y   p2.y;
	return p1;
}

la funzione creapunto riceve in input due int e ritorna in output una struttura di tipo punto (temp). Le variabili di tipo int passate in input hanno lo stesso nome dei membri della struttura (x e y): cio' non crea confusioni in quanto sono due variabili distinte. Infatti x e' una variabile di tipo int mentre temp.x e' il membro 'x' della stuttura temp di tipo punto (ovviamente anche questo membro a sua volta e' una variabile di tipo int). La funzione sommapunti invece, riceve in input le strutture p1 e p2, effettua dei calcoli utilizzandone i membri ed infine ritorna in output la struttura p1. E' possibile passare ad una funzione un puntatore ad una struttura. Cio' puo' essere fatto dichiarando un puntatore al quale viene assegnato l'indirizzo di una struttura:

struct punto puntoqualsiasi, *p_punto;  /* dichiara un puntatore alla struttura 'punto'  */

p_punto = &puntoqualsiasi;   /* assegna al puntatore l'indirizzo della struttura puntoqualsiasi  */

printf ("le coordinate di puntoqualsiasi sono: (%d, %d)\n", (*p_punto).x, (*p_punto).y);

la funzione printf() riceve in input i membri della struttura punto, pero', nel passaggio di questi parametri, non si utilizza la struttura punto ma bensi' il puntatore ad essa. Infatti dopo l'assegnamento p_punto = &puntoqualsiasi, p_punto punta alla struttura puntoqualsiasi, percio' *p_punto e' la struttura puntata. Quindi e' possibile scrivere indifferentemente puntoqualsiasi.x oppure (*p_punto).x, in quanto quest'ultima espressione rappresenta il membro x della struttura puntoqualsiasi. Nell'espressione (*p_punto).x le parentesi sono necessarie in quanto l'operatore di struttura '.' ha una precedenza superiore all'operatore di deriferimento '*': senza di esse l'espressione *p_punto.x equivale all'espressione *(p_punto.x) che non ha alcun significato in quanto (p_punto.x) non e' un puntatore ma bensi' un membro della struttura punto (piu' esattamente una variabile di tipo int). Esiste una notazione alternativa per rappresentare i puntatori alle strutture:

puntatore->membro

	che equivale a:

(*puntatore).membro

	quindi l'esempio precedente poteva anche essere scritto cosi':

printf ("le coordinate di puntoqualsiasi sono: (%d, %d)\n", p_punto->.x, p_punto->.y);

l'operatore '->' ha una precedenza superiore a quasi tutti gli altri operatori: piu' esattamente ha la stessa precedenza degli operatori '()', '[]' e '.' ed e' associativo da sinistra a destra (come gli altri 3 operatori). Data la precedenza cosi' alta, l'espressione:

  p_punto->x;

incrementa il membro x e non il puntatore alla struttura. Infatti tale espressione equivale a (p_punto->x). Per incrementare il puntatore alla struttura e non il suo membro occorre percio' scrivere ( p_punto)->x.

30.3 Vettori di strutture

Il C consente di utilizzare vettori composti da variabili di qualsiasi tipo, comprese le strutture. E possibile quindi dichiarare una struttura ed un vettore di strutture del tipo appena dichiarato:


struct miastruttura
{
	char var1;
	int  var2;
};

struct miastruttura struttvett[10];

	oppure:

struct miastruttura
{
	char var1;
	int  var2;
} struttvett[10];

struttvett e' un vettore di 10 strutture di tipo miastruttura. Ogni elemento di tale vettore e' una struttura. Le due forme appena viste sono equivalenti: nella prima viene dichiarata una struttura miastruttura e successivamente viene dichiarato un vettore di 10 strutture; nella seconda vengono dichiarati sia la struttura che il vettore contemporaneamente. E' possibile inizializzare il vettore di strutture in questo modo:

struct miastrutt
{
	int intero;
	char carattere;
} vettore[] = {
			0 , 'a',
			1 , 'b',
			2 , 'c',
			....
			....
			9 , 'i'
		  };

come al solito l'aritmetica dei puntatori e' utilizzabile anche in questo contesto, pertanto, l'incremento od il decremento di un puntatore ad una struttura e' possibile. Incrementando il puntatore ad una struttura, viene tenuto conto delle dimensioni della struttura percio' il puntatore viene incrementato di una quantita' tale da puntare alla struttura successiva. Attenzione a non cascare nell'errore di ritenere tale quantita' pari alla somma delle dimensioni dei membri della struttura. Infatti a causa dell'allineamento degli oggetti in memoria, qualsiasi oggetto potrebbe occupare una quantita' di caratteri minima a prescindere dalla sua dimensione. Ad esempio, se un int, all'interno di una particolare macchina ha una dimensione di 4 byte, in realta' tale oggetto potrebbe occupare 8 byte. Percio' una struttura composta da un char e da un int, che in teoria ha una dimensione di 5 byte (1 4) in realta' in memoria potrebbe occupare 8 byte. Per scoprire le dimensioni di un oggetto esiste l'operatore sizeof. L'operatore sizeof ritorna la dimensione di un oggetto in memoria all'interno della macchina che si sta utilizzando.

30.4 Strutture di bit

Puo' essere conveniente o necessario usare campi di bit, ad esempio per utilizzare una serie di switch senza occupare troppo spazio in memoria. Cosi' lo switch TROVATO puo' essere un bit impostato ad 1 se e' stata trovata una stringa 0 se non e' stata trovata, mentre lo switch FINEFILE puo' valere 1 o 0 se e' stata raggiunta o meno la fine di un file. Nella programmazione in generale, per convenzione 0 corrisponde a falso e 1 a vero, percio' e' possibile disporre di piu' switch delle dimensioni di un bit per gestire varie condizioni. Un metodo classico per impostare i campi di bit e' gia' stato visto precedentemente, e comporta l'uso delle maschere di bit. Ad esempio:

#define  KEYWORD  01
#define  EXTERNAL 02
#define  STATIC   04

miei_switch |= EXTERNAL | STATIC;

nell'esempio appena visto, miei_switches comprende una serie switch da un bit ciascuno: mediante l'operatore OR ('|') i bit corrispondenti agli switch EXTERNAL e STATIC vengono impostati ad 1 (secondo e terzo bit di miei_switch). E' possibile pero' utilizzare le strutture per manipolare i bit direttamente, senza cioe' l'uso di operatori bitwise:

struct
{
	unsigned int is_keyword: 1;
	unsigned int is_extern:  1;
	unsigned int is_static:  1;
} miei_switch;

gli oggetti definiti in questa struttura compongono il bit-field, cioe' campo di bit. La struttura cosi' definita rappresenta quindi una sequenza di bit adiacenti all'interno di una singola unita' di memoria. Viene cosi' definita la variabile miei_switch che contiene 3 campi da un bit ciascuno, in quanto il valore che segue i due punti definisce l'ampiezza del bit-field (in questo caso il valore e' 1, quindi il campo rappresenta un bit). Per accedere ai singoli campi della struttura (cioe' in questo caso ai singoli bit) si usa una sintassi analoga a quella usata per membri delle strutture:

miei_switch.is_extern = miei_switch.is_static = 1;

l'istruzione appena vista imposta ad 1 i bit corrispondenti ai campi is_extern e is_static (secondo e terzo bit di miei_switch).

30.5 Le union

un costrutto dalla sintassi simile alle strutture ma profondamente diverso come significato e' la union. Una union e' una variabile che rappresenta in momenti diversi oggetti di tipo e dimensioni diverse. Un costrutto simile e' il record variante del Pascal o alla clausola redefines del Cobol:

union mia_union
{
	int    intero;
	float  mio_float;
	char * stringa;
} variabilona;

nell'esempio precedente variabilona e' una variabile sufficientemente grande da contenere la variabile di dimensioni maggiori tra le tre dichiarate. Per accedere ai membri di una union si utilizza una sintassi analoga ai membri delle strutture:

nome_union.nome_membro

    oppure:

puntatore_union->nome_membro

Inizio della guida  I puntatori  Indice  Input ed Output

Copyright (c) 2002-2003 Maurizio Silvestri