Barninga Z
a- a+

Le strutture

ENTITA' COMPLESSE 

Le strutture 

Tra gli strumenti cui si è fatto cenno apparefondamentale la struttura (structure), mediante laquale si definisce un modello (template) che individuaun'aggregazione di tipi di dato fondamentali. 

Ecco come potremmo descrivere un pixel con l'aiuto diuna struttura: 

struct pixel {    int x;    int y;    int colour;};

Quello dell'esempio è una dichiarazione ditemplate di struttura: si apre con la parola chiavestruct seguita dal nome (tag) che intendiamodare al nostro modello; questo è a sua volta seguitoda una graffa aperta. Le righe che seguono, vere e propriedichiarazioni di variabili, individuano il contenuto dellastruttura e si concludono con una graffa chiusa seguita dalpunto e virgola. 

Ecco un altro esempio di dichiarazione di template, dalquale risulta chiaro che una struttura può comprenderedifferenti tipi di dato: 

struct ContoCorrente {    char   intestatario[50];    char   data_accensione[9];    int    cod_filiale;    double saldo;    double tasso_interesse;    double max_fido;    double tasso_scoperto;};

E' meglio focalizzare sin d'ora che la dichiarazionedi un template di struttura non comporta che il compilatoreriservi dello spazio di memoria per  allocare icampi[36] della struttura stessa. La dichiarazione ditemplate definisce semplicemente la "forma" dellastruttura, cioè il suo modello. 

Di solito le dichiarazioni di template di strutturacompaiono all'inizio del sorgente, anche perché itemplates devono essere stati dichiarati per poter essereutilizzati: solo dopo avere definito l'identifictore(tag) e il modello (template) della struttura, come negliesempi di poco fa, è possibile dichiarare edutilizzare oggetti di quel tipo, vere e proprie variabilistruct

 

#includestruct concorso {    int serie;    char organizzatore;    int partecipanti;};void main(void){    struct concorso c0, c1;    c0.serie = 2;    c0.organizzatore = 'F';    c0.partecipanti = 482;    c1.serie = 0;    c1.organizzatore = 'G';    c1.partecipanti = 33;    printf("Serie della concorso 0: %d" ,c0.serie);    printf("Organizzatore della concorso 1: %c" ,c1.organizzatore);}

Nel programma dell'esempio viene dichiarato un templatedi struttura, avente tag concorso. Il templateè poi utilizzato in main() per dichiarare duestrutture di tipo concorso: solo a questo punto sonocreati gli oggetti concorso e viene loro riservatamemoria. Gli elementi, o campi, delle due strutture sonoinizializzati con dati di tipo opportuno; infine alcuni diessi sono visualizzati con la solitaprintf()

Cerchiamo di evidenziare alcuni concetti fondamentali, ascanso di equivoci. La dichiarazione di template non presentanulla di nuovo: parola chiave struct, tag, graffaaperta, campi, graffa chiusa, punto e virgola. Unanovità è invece rappresentata dalladichiarazione delle strutture c0 e c1: comesi vede essa è fortemente analoga a quelle di comunivariabili, con la differenza che le variabili dichiarate nonappartengono al tipo int, float, o a unodegli altri tipi di dati sin qui trattati. Esse appartengonoad un tipo di dato nuovo, definito da noi: il tipo structconcorso

Finora si è indicato con "dichiarazione ditemplate" l'operazione che serve a definirel'aspetto della struttura, e con "dichiarazione distruttura" la creazione degli oggetti, cioè ladichiarazione delle variabili struttura. E' forse unaterminologia prolissa, ma era indispensabile per chiarezza.Ora che siamo tutti diventati esperti di strutture potremoessere un poco più concisi e indicare con il termine"struttura" , come comunemente avviene, tanto itemplate che le  variabili di tipo struct[37]. 

In effetti, la dichiarazione: 

struct concorso {    int serie;    char organizzatore;    int partecipanti;};

crea semplicemente un modello che può essere usatocome riferimento per ottenere variabili dotate di quelleparticolari caratteristiche. Ciascuna variabile conforme aquel modello contiene, nell'ordine prefissato, unint, un char e un secondo int. Aciascuna di queste variabili, come per quelle di qualsiasialtro tipo, il compilatore alloca un'area di memoria didimensioni sufficienti, alla quale associa il nome simbolicoche compare nella dichiarazione 

struct concorso c0;

cioè c0. In quest'ultima dichiarazione,l'identificatore concorso indica il modelloparticolare al quale si deve conformare la variabiledichiarata. Esso è, in pratica, un'abbreviazionedi 

{    int serie;    char organizzatore;    int partecipanti;};

e come tale può venire usato nel programma. In altreparole, è possibile riferirsi all'interadichiarazione di struttura semplicemente usandone iltag. 

Una variabile di tipo struct può esseredichiarata contestualmente al template: 

struct concorso {    int serie;    char organizzatore;    int partecipanti;} c0, c1;

Il template può essere normalmente utilizzato perdichiarare altre  strutture nel programma[38]. 

Tornando a quel che avviene nella main()dell'esempio, ai campi delle strutture dichiarate sonostati assegnati valori con una notazione del tipo 

nome_della_variabile_struttura.nome_del_campo = valore;

ed in effetti l'operatore punto (".") èlo strumento offerto dal C per accedere ai singoli campidelle variabili struct, tanto per assegnarvi unvalore, quanto per leggerlo (e lo si vede dalleprintf() che seguono). 

Abbiamo parlato delle strutture viste negli esempiprecedenti come di variabili di tipo structconcorso. In effetti definire un template di strutturasignifica arricchire il linguaggio di un nuovo tipo di dato,non intrinseco, ma al quale è possibile applicare lamaggior parte dei concetti e degli strumenti disponibili conriferimento ai tipi di dato intrinseci. 

Le strutture possono quindi essere gestite mediante array epuntatori, proprio come comuni variabili C. La dichiarazionedi un array di strutture si prsenta come segue: 

struct concorso c[3];

Si nota immediatamente la forte somiglianza con ladichiarazione di un array di tipo intrinseco: il valore traparentesi quadre specifica il numero di elementi,cioè, in questo caso, di strutture che formanol'array. Ogni elemento è, appunto, una strutturaconforme al template concorso; l'array ha nomec. Per accedere ai singoli elementi dell'arrayè necessario, come prevedibile, specificare il nomedell'array seguito dall'indice, tra quadre,dell'elemento da referenziare. La differenza rispetto adun array "comune" , ad esempio di tipoint, sta nel fatto che accedere ad una struttura nonsignifica ancora accedere ai dati che essa contiene: perfarlo occorre usare l'operatore punto, come mostrato pocosopra. Un esempio chiarirà le idee: 

#includestruct concorso {    int serie;    char organizzatore;    int partecipanti;};void main(void){    register i;    struct concorso c[3];    c[0].serie = 2;    c[0].organizzatore = 'F';    c[0].partecipanti = 482;    c[1].serie = 0;    c[1].organizzatore = 'G';    c[1].partecipanti = 33;    c[2].serie = 3;    c[2].organizzatore = 'E';    c[2].partecipanti = 107;    for(i = 0; i < 3; i++)  printf("%d    %c    %d" ,c[i].serie,c[i].organizzatore,c[i].partecipanti);}

 

Con riferimento ad un array di strutture, la sintassi usataper referenziare i campi di ciascuna struttura elementodell'array è simile a quella utilizzata per arraydi tipi intrinseci. Ci si riferisce, ad esempio, al camposerie dell'elemento di posto 0dell'array con la notazione c[0].serie; èbanale osservare che c[0] accede all'elementodell'array, mentre .serie accede al campo volutodi quell'elemento. 

Si può pensare all'esempio presentato sopraimmaginando di avere tre fogli di carta, ciascuno contenenteun elemento dell'array c. In ciascun foglio sonopresenti tre righe di informazioni che rappresentano,rispettivamente, i 3 campi della struttura. Se i 3 foglivengono mantenuti impilati in ordine numerico crescente, siottiene una rappresentazione "concreta"dell'array, in quanto è possibile conoscere sia ilcontenuto dei tre campi di ogni elemento, sia la relazionetra i vari elementi dell'array stesso. 

I più attenti hanno  sicuramente[39] notato che,mentre le operazioni di assegnamento, lettura, etc. con tipidi dato intrinseci vengono effettuate direttamente sullavariabile dichiarata, nel caso delle strutture esse sonoeffettuate sui campi, e non sulla struttura comeentità direttamente accessibile. In realtà leregole del C non vietano di accedere direttamente ad unastruttura intesa come un'unica entità, ma sitratta di una  pratica poco seguita[40]. E' infattiassai più comodo ed efficiente utilizzare ipuntatori. 

Anche nel caso dei puntatori le analogie tra strutture etipi intrinseci sono forti. La dichiarazione di un puntatorea struttura, infatti, è: 

struct concorso *cPtr;

dove cPtr è il puntatore, che puòcontenere l'indirizzo di una struttura di templateconcorso. L'espressione *cPtrrestituisce una struct concorso, esattamente come inuna dichiarazione quale 

int *iPtr;

*iPtr restituisce un int. Attenzione,però: per accedere ai campi di una strutturareferenziata mediante un puntatore non si deve usarel'operatore punto, bensì l'operatore"freccia" , formato dai caratteri "meno"("­") e "maggiore"(">") in sequenza, con una sintassi deltipo: 

nome_del_puntatore_alla_variabile_di_tipo_struttura->nome_del_campo = valore;

Vediamo un esempio. 

 

struct concorso *cPtr;    ....    cPtr->serie = 2;    ....    printf("Serie: %d" ,cPtr->serie);

I puntatori a struttura godono di tutte le proprietàdei puntatori a tipi intrinseci, tra le quali particolarmenteinteressante appare l'aritmetica dei puntatori.Incrementare un puntatore a struttura significa sommareimplicitamente al suo valore tante unità quante neoccorrono per "scavalcare" tutta la strutturareferenziata e puntare quindi alla successiva. In generale,sommare un intero ad un puntatore a struttura equivalesommare quell'intero moltiplicato per la  dimensionedella struttura[41]. E' appena il caso di sottolineareche la dimensione di un puntatore a struttura e la dimensionedella struttura puntata sono due concetti differenti, comegià si è detto per le variabili di tipointrinseco. Un puntatore a struttura occupa sempre 2 o 4byte, a seconda che sia near, oppure far ohuge, indipendentemente dalla dimensione dellastruttura a cui punta. Con la dichiarazione di un puntatore astruttura, dunque, il compilatore non alloca memoria per lastruttura stessa. 

Rivediamo il programma d'esempio di poco fa,modificandolo per utilizzare un puntatore a struttura: 

 

#includestruct concorso {    int serie;    char organizzatore;    int partecipanti;};void main(void){    struct concorso c[3], *cPtr;    c[0].serie = 2;    c[0].organizzatore = 'F';    c[0].partecipanti = 482;    c[1].serie = 0;    c[1].organizzatore = 'G';    c[1].partecipanti = 33;    c[2].serie = 3;    c[2].organizzatore = 'E';    c[2].partecipanti = 107;    for(cPtr = c; cPtr < c+3; ++cPtr)  printf("%d   %c   %d" ,cPtr->serie,cPtr->organizzatore,cPtr->partecipanti);}

Come si può notare, la modifica consisteessenzialmente nell'avere dichiarato un puntatore astruct concorso, cPtr, e nell'averloutilizzato in luogo della notazione c[i] peraccedere agli elementi dell'array. Le dichiarazionidell'array e del puntatore sono state raggruppate inun'unica istruzione, ma sarebbe stato possibilesepararle: il codice 

struct concorso c[3];    struct concorso *cPtr;

avrebbe avuto esattamente lo stesso significato, sebbene informa meno compatta e, forse, più leggibile. 

Nel ciclo for dell'esempio, il puntatorecPtr è inizializzato a c epoiché il nome di un array è puntatoreall'array stesso, cPtr punta al primo elementodi c, cioè c[0]. Durante la primaiterazione sono visualizzati i valori dei 3 campi dic[0]; all'iterazione successiva cPtrviene incrementato per puntare al successivo elemento dic, cioè c[1], e quindil'espressione 

cPtr->

è ora equivalente a 

c[1].

All'iterazione successiva, l'espressione 

cPtr->

diviene equivalente a 

c[2].

dal momento che cPtr è stato incrementatoancora una volta. 

A proposito di puntatori, è forse il caso dievidenziare che una struttura può contare tra i suoicampi puntatori a qualsiasi tipo di dato. Sono perciòammessi anche puntatori a struttura, persino puntatori astruttura identificata dal medesimo tag. In altre parole,è perfettamente lecito scrivere: 

struct TextLine {    char *line;    int  cCount;    struct TextLine *prevTL;    struct TextLine *nextTL;};

Quella dell'esempio è una struttura (o meglio, untemplate di struttura) che potrebbe essere utilizzata per unarudimentale gestione delle righe di un testo, ad esempio inun programma di word processing. Essa contiene duepuntatori a struttura dello stesso template: nell'ipotesiche ogni riga di testo sia gestita attraverso una strutturaTextLine, prevTL è valorizzato conl'indirizzo della struct TextLine relativa allariga precedente nel testo, mentre nextTL punta allastruct TextLine della  riga successiva[42].E' proprio mediante un utilizzo analogo a questo deipuntatori che vengono implementati oggetti quali le liste.Uno dei vantaggi immediatamente visibili che derivanodall'uso descritto dei due puntatori prevTL enextTL consiste nella possibilità diimplementare algoritmi di ordinamento delle righe di testoche agiscano solo sui puntatori: è sufficientemodificare il modo in cui le righe di testo sono legatel'una all'altra da un punto di vista logico, senzanecessità alcuna di modificarne l'ordine fisico inmemoria. 

E' ovvio che, come al solito, un puntatore non riservalo spazio per l'oggetto a cui punta. Nell'ipotesi dipuntatori near, l'espressione sizeof(structTextLine) restituisce 8. La memoria necessariaa contenere la riga di testo e le strutture TextLinestesse deve essere allocata esplicitamente. 

Nel caso degli array, al contrario, la memoria èallocata staticamente dal compilatore (anche qui nulla dinuovo): riscriviamo il template in modo da gestire la riga ditesto come un array di caratteri, avente dimensione massimaprestabilita (in questo caso 80): 

struct TextLine {    char line[80];    int  cCount;    struct TextLine *prevTL;    struct TextLine *nextTL;};

questa volta l'espressione sizeof(struct TextLine)restituisce 86. 

Va anche precisato che una struttura può contenereun'altra struttura (e non solo il puntatore ad essa),purché identificata da un diverso tag: 

struct TextParms {    int textLen;    int indent;    int justifyType;};struct TextLine {    char line[80];    struct TextParms lineParms;    struct TextLine *prevTL;    struct TextLine *nextTL;};

In casi come questo le dichiarazioni delle due strutturepossono perfino essere nidificate: 

 

struct TextLine {    char line[80];    struct TextParms {  int textLen;  int indent;  int justifyType;    } lineParms;    struct TextLine *prevTL;    struct TextLine *nextTL;};

Da quanto appena detto appare evidente che una struttura nonpuò mai contenere una struttura avente il propriostesso tag identificativo: per il compilatore sarebbeimpossibile risolvere completamente la definizione dellastruttura, in quanto essa risulterebbe definita in funzionedi se stessa. In altre parole è illecita unadichiarazione come: 

 

struct ST {    int number;    // OK    float *fPtr;   // OK    struct ST inner; // NO! il tag e' il medesimo e questo non e' un puntatore};

 

Anche agli elementi di strutture nidificate si accede tramiteil punto (".") o la freccia("­>"): con riferimento aitemplates appena riportati, è possibile, ad esempio,dichiarare un array di strutture TextLine

struct TextLine tl[100]; // dichiara un array di strutture TextLine

Alla riga di testo gestita dal primo elemento dell'arraysi accede, come già sappiamo, con l'espressionetl[0].line. Per visualizzare la riga successiva(gestita dall'elemento di tl il cui indirizzoè contenuto in nextTL) vale laseguente: 

printf("Prossima riga: %s " ,tl[0].nextTL->line);

Infatti tl[0].nextTL accede al campo nextTLdi tl[0]: l'operatore utilizzato è ilpunto, proprio perchè tl[0] è unastruttura e non un puntatore a struttura. Ma nextTLè, al contrario, un puntatore, perciò perreferenziare l'elemento line della struttura chesi trova all'indirizzo che esso contiene ènecessario usare la "freccia". Supponiamo ora divoler conoscere l'indentazione (rientro rispetto almargine) della riga appena visualizzata: è ormai notoche ai campi della struttura "puntata" danextTL si accede con l'operatore­>; se il campo referenziato è, a suavolta, una struttura (lineParms), i campi di questasono "raggiungibili" mediante il punto. 

printf("Indentazione della prossima riga: %d " ,tl[0].nextTL->lineParms.indent);

Insomma, la regola generale (che richiede di utilizzare ilpunto se l'elemento fa parte di una strutturareferenziata direttamente e la freccia se l'elementoè raggiungibile attraverso il puntatore allastruttura) rimane valida e si applica pedestremente ad ognilivello di nidificazione. 

Vale infine la pena di chiarire che le strutture, purcostituendo un potente strumento per la rappresentazioneinformatica di entità complesse (quali record diarchivi, etc.), sono ottimi "aiutanti" anche quandosi desideri semplificare il codice ed incrementarnel'efficienza; se, ad esempio, occorre passare moltiparametri ad una funzione, e questa è richiamata moltevolte (si pensi al caso di un ciclo con molte iterazioni),può essere conveniente definire una struttura cheraggruppi tutti quei parametri, così da poter passarealla funzione un parametro soltanto: il puntatore allastruttura stessa.



Ti potrebbe interessare anche

commenta la notizia