Le strutture





Con gli array è stato possibile gestire, mediante un nome comune, un insieme formato da un numero arbitrario di elementi tutti dello stesso tipo. Grazie alle strutture è possibile gestire in modo compatto un insieme formato da un numero arbitrario di elementi di tipo diverso. La scelta delle variabili con cui formare una struttura viene fatta direttamente dal programmatore; effettuata la scelta, la struttura va definita in modo tale che il suo prototipo sia noto al compilatore.




[1] La definizione di una struttura equivale alla dichiarazione di un nuovo tipo di variabile.


struct nuova {int numero;
                     char lettera;
                     double valore};


Tutti i tipi di dato incontrati fino ad ora (oltre a quelli che incontreremo in seguito) possono essere inseriti all'interno di una struttura.





L'analogia tra la dichiarazione di una struttura e quella di un nuovo tipo diventa ancora più marcata grazie allo specificatore typedef

              vecchio nome di tipo      nuovo nome di tipo

typedef        struct nuova                    mystruct;


L'uso di typedef aumenta la portabilità del codice e lo rende più chiaro.


mystruct stru1,stru2; // dichiaro due variabili di tipo struct nuova (ovvero mystruct)
























[2] L'operatore binario '.' permette di accedere agli elementi di una struttura

nome_struttura.nome_componente


struct nuova {int numero;
                     char lettera;
                     double valore};

typedef   struct nuova  mystruct;

mystruct stru1;

stru1.numero = 4;
stru1.lettera = 'e' ;
stru1.valore = 3.14159;

Il tipo relativo all'identificatore che si ottiene con questa regola è quello della variabile corrispondente all'identificatore che si trova più a destra.




Per accedere agli elementi di una struttura non si usa un indice, come nel caso dei vettori, ma un operatore di accesso. L'uso di tale operatore per gestire i valori dei componenti di una struttura permette di accedere rapidamente ad ogni singolo elemento senza sapere quale sia la posizione dell'elemento stesso all'interno dell'area di memoria riservata per la struttura.

La dimensione di tale area di memoria viene calcolata dall'operatore sizeof()












[3] Per il momento, l'unico operatore applicabile alle strutture (oltre all'operatore '.') è l'operatore di assegnazione.

mystruct stru1,stru2; // dichiaro due variabili di tipo struct nuova (ovvero mystruct)


L'operatore permette di effettuare una COPIA di stru1, ovvero di assegnare a ciascun membro della struttura stru2 il valore del corrispondente membro della struttura stru1. La copia viene effettuata membro a membro.


stru2 = stru1 ;


La copia viene effettuata copiando le informazioni contenute nell'area di memoria relativa alla variabile che si trova alla destra dell'operatore di assegnazione nell'area di memoria relativa alla struttura che si trova alla sinistra dell'operatore di assegnazione.

Questo significa che l'operatore di assegnazione si può usare solo tra strutture dello stesso tipo.


stru2 = stru1 ;

printf("%d %c %f",stru2.numero,stru2.lettera,stru2.valore);




struct nuova {int numero;
                     char lettera;
                     double *puntatore };
struct nuova1 {char lettera;
                      int numero;
                      double *puntatore };


struct nuova  a;
struct nuova1  b;

... inizializzo a;

b = a ;   // NON HA SENSO, non sono strutture dello stesso tipo


Il compilatore visualizza un errore, in quanto si sta applicando un'operatore a variabili non compatibili tra loro. Il problema non si risolve con un cast esplicito.


b = (struct nuova1) a ;   // NON FUNZIONA !

























Esercizio: implementare un codice che permetta di memorizzare e raggruppare le informazioni relative ad uno studente all'interno di una singola struttura.

Le informazioni possono essere suddivise in diverse categorie:

(1) Informazioni anagrafiche (Nome, Cognome, indirizzo, numero di telefono)
(2) Informazioni relative all'ambito universitario (numero di matricola, numero di crediti da raggiungere)
(3) Informazioni relative alla carriera universitaria (corso di laurea, voti sostenuti negli esami)


Cominciamo ad implementare un codice che, inizialmente, gestisca solo alcune informazioni.











// inizio codice : versione 1


struct stud1{int prefisso_tel;
                   int numero_tel;
                   int numero_matricola;
                   int numero_crediti; };

typedef   struct stud1   studente1;


int main()
{studente1 mario;    // supponiamo sia di Ferrara e che faccia informatica
  int numero_tel;       // non si commette errore dichiarando una variabile con questo nome

  mario.prefisso_tel = 532;
  
 // versione 1
  printf("Inserisci il tuo numero di telefono :");
  scanf("%d",& numero_tel);

  mario.numero_tel = numero_tel;
















 // versione 2
  printf("Inserisci il tuo numero di telefono :");

  scanf("%d",& ( mario.numero_tel) );

  printf("Il numero inserito e' : %d", mario.numero_tel );
  


  printf("Inserisci il tuo numero di matricola :");

  scanf("%d",& ( mario.numero_matricola) );

  mario.numero_crediti = 180 ;

 return 0; }

// fine codice
























/* inizio codice : versione 2

Per implementare una struttura che permetta di gestire anche il nome ed il cognome dello studente, oltre che il nome del paese in cui ha la residenza, è necessario inserire nella struttura delle stringhe.

*/



struct stud2{char nome[40];
                   char cognome[40];
                   char paese[40];
                  
int prefisso_tel;
                   int numero_tel;
                   int numero_matricola;
                   int numero_crediti; };

typedef   struct stud2   studente2;


int main()
{studente2 mario;    // supponiamo sia di Ferrara e che faccia informatica

 char temp[40];       

 int lunghezza = 0, i = 0;

// la gestione delle variabili di tipo int è identica a quella utilizzata nel caso precedente
  mario.prefisso_tel = 532;
  printf("Inserisci il tuo numero di telefono :");
  scanf("%d",& ( mario.numero_tel) );
  printf("Inserisci il tuo numero di matricola :");
  scanf("%d",& ( mario.numero_matricola) );
  mario.numero_crediti = 180 ;


// versione 1
  printf("Inserisci il tuo nome : (max 39 car.) ");
  scanf("%s",temp);

  
/* gestiamo l'elemento mario.nome come una stringa, ovvero copiamo i caratteri inseriti dall'utente all'interno di tale variabile usando le funzioni della libreria standard

mario.nome è un identificatore composto; esso si riferisce ad una variabile che ha il tipo dell'elemento che si trova a destra all'interno del nome composto -> puntatore a char        */

  strcpy(mario.nome , temp);

  printf("il nome dello studente e' : %s", mario.nome);





















/* gestiamo l'elemento mario.cognome come un array di caratteri, ovvero inseriamo manualmente, carattere dopo carattere, compreso il terminatore, i caratteri inseriti dall'utente all'interno della stringa mario.cognome */


  printf("Inserisci il tuo cognome : (max 39 car.) ");
  scanf("%s",temp);

  lunghezza = strlen (temp);

  for(i = 0; i <= lunghezza ; i++)
    mario.cognome[i] = temp[i];
















// versione 2

  printf("Inserisci il tuo nome : (max 39 car.) ");
  scanf("%s",mario.nome);

  printf("Inserisci il tuo cognome : (max 39 car.) ");
  scanf("%s",mario.cognome);

 return 0; }


// fine codice








Analizziamo l'espressione : mario.cognome[i]

Essa è composta da tre parti:

il nome di una variabile di tipo studente1
il nome di un elemento di una struttura
un indice


L'elemento della struttura viene "prelevato" dalla struttura stessa mediante l'operatore '.'
L'elemento dell'array di char viene "prelevato" dall'array mediante l'indice compreso tra []

Gli operatori '.' e [] hanno la stessa priorità ma sono associativi da sinistra a destra e quindi:

PRIMA viene prelevato l'elemento della struttura

mario.cognome

In questo modo viene ottenuto un identificatore che rappresenta una variabile di tipo puntatore a char


POI vengono applicate le [] e l'elemento dell'array corrispondente all'indice i viene prelevato


Ovvero : ( mario.cognome )[i]

Questo sarebbe un errore : mario.( cognome [i] )
(in quanto cognome esiste solo come elemento di mario)












struct stud3{char nome[40];    // dati personali
                   char cognome[40];
                   char paese[40];
                   int prefisso_tel;
                   int numero_tel;

                   char corso_di_laurea[100];  // informazioni per la segreteria
                   int numero_matricola;
                   int numero_crediti; };



















Visto che dentro una struttura possiamo inserire un qualsiasi tipo di dato, una struttura può contenere un'altra struttura; l'importante è che NON SIANO DELLO STESSO TIPO ! (perché ?)



















Raggruppiamo le informazioni di carattere personale in una struttura e quelle relative all'ambito universitario in una seconda struttura


struct personale{char nome[40];    // dati personali
                          char cognome[40];
                          char paese[40];
                          int prefisso_tel;
                          int numero_tel; } ;



struct segreteria{char corso_di_laurea[100];  // informazioni per la segreteria
                           char e_mail[40];
                           int numero_matricola;
                           int numero_crediti; };


typedef   struct personale   persona;

typedef   struct segreteria univ;

struct stud4{persona p1;
                   univ u1; }



typedef struct stud4  studente4;




Come accedo al numero telefonico dello studente ? Tale informazione si trova nella variabile numero_tel che è membro della struttura p1 (struttura di tipo persona); p1, a sua volta, è membro di una struttura di tipo studente4


studente4 mario;

mario . p1 . numero_tel = 1234567 ;

/*
L'identificatore composto fa riferimento ad una variabile avente lo stesso tipo dell'identificatore che, all'interno del nome composto, si trova a destra -> int       
*/

















Come accedo al stringa contenente il nome del corso di laurea dello studente ? Tale informazione si trova nella stringa corso_di_laurea che è membro della struttura u1 (struttura di tipo univ); u1, a sua volta, è membro di una struttura di tipo studente4


studente4 mario;

mario . u1 . corso_di_laurea


/*
L'identificatore composto fa riferimento ad una variabile avente lo stesso tipo dell'identificatore che, all'interno del nome composto, si trova a destra -> char *             
*/







// inizio codice

#include <stdio.h>
#include <string.h>



struct personale{char nome[40];    // dati personali
                          char cognome[40];
                          char paese[40];
                          int prefisso_tel;
                          int numero_tel; } ;



struct segreteria{char corso_di_laurea[100];  // informazioni per la segreteria
                           char e_mail[40];
                           int numero_matricola;
                           int numero_crediti; };


typedef   struct personale   persona;

typedef   struct segreteria univ;

struct stud4{persona p1;
                   univ u1; }

typedef struct stud4  studente4;



int main()
{int lunghezza = 0, i = 0;
  studente4 mario;
 printf("\n la memoria occupata dal nuovo tipo e' pari a %d byte \n",sizeof(studente4));
 
 // supponiamo che la persona sia di Ferrara

 mario.p1.prefisso_tel = 532 ;

 printf("\n inserisci il numero di telefono ");
 scanf("%d", & ( mario.p1.numero_tel ));

 printf("\n inserisci l'indirizzo di posta elettronica (max 39 char) ");
 scanf("%s", mario.u1.e_mail );


/* il tipo corrispondente all'identificatore è sempre quello del nome del membro della struttura che sta in fondo a destra */


 printf("\n inserisci il tuo cognome (max 39 char) ");
 scanf("%s", mario.p1.cognome);


 printf("\n inserisci il numero di matricola");
 scanf("%d", mario.u1.numero_matricola );


// se lo studente è di Ferrara
 strcpy( mario.p1.paese , "Ferrara" );



// visualizziamo ora, carattere per carattere, il cognome dello studente
 lunghezza = strlen(mario.p1.cognome);

// versione 1

 for(i = 0 ; i < lunghezza ; i++)
   {printf("%c", mario.p1.cognome[i] );}















// versione 2 : l'identificatore mario.p1.cognome fa riferimento ad un puntatore a char

 for(i = 0 ; i < lunghezza ; i++)
   {printf("%c", * (mario.p1.cognome + i ) );}

















// versione 3 : senza libreria standard

 for(i = 0 ; mario.p1.cognome [i]; i++)
   {printf("%c", * (mario.p1.cognome + i ) );}



 return 0; }


// fine codice