strutture

l'uso delle strutture non è obbligatorio, ma in certe situazioni è uno strumento comodo perché permette di aggregare e gestire in modo unitario dati diversi e/o semplifica la progettazione dell'algoritmo.

I linguaggi di programmazione mettono a disposizione del programmatore dei tipi di dato primitivi (int, float, double e char nel linguaggio C) che il programmatore può usare per dichiarare variabili e costanti.

Le variabili utilizzate finora nei programmi possono contenere un solo valore o più valori dello stesso tipo (infatti l’array è un dato strutturato che contiene più elementi dello stesso tipo identificati da un indice di posizione).

Il programmatore anche può definire nuovi tipi di dato, detti tipi riferimento (detti anche derivati o record o registrazione o struttura) per gestire i dati di un particolare problema; può creare una propria struttura dati(detta struct nel linguaggio C) costituita da elementi anche eterogenei tra loro; questa struttura è composta da attributi o caratteristiche che descrivono il dato (detti anche campi) e che possono essere di vario tipo.

Dopo aver definito una struttura è possibile dichiarare variabili del nuovo tipo descritto con la struttura.

Infatti in alcuni casi è comodo aggregare i diversi dati (anche non omogenei tra loro) in un nuovo tipo di dato, che costituirà un tipo riferimento definito dal programmatore.

Esempio

Dati due punti nel piano cartesiano, calcolare il punto medio

Per risolvere il problema possiamo dichiarare delle variabili float per le ascisse e le ordinate dei tre punti.

float x1,y1,x2,y2,xm,ym; scanf("%f",&x1); scanf("%f",&y1); scanf("%f",&x2); scanf("%f",&y2); xm=(x1+x2)/2; ym=(y1+y2)/2; printf("xm=%0.2f\nym=%0.2f",xm,ym);

Possiamo immaginare un Punto come un’entità, un concetto astratto che ha significato per il nostro problema; un punto è formato da una ascissa e da una ordinata, che ne descrivono le caratteristiche e formano la sua struttura:

Punto è il nuovo tipo di dato; x e y sono i campi, o caratteristiche, del dato.

Dopo aver creato il nuovo tipo di dato “Punto” possiamo scrivere programmi in cui:

  • dichiarare variabili di tipo Punto: Punto a,b;

  • dichiarare array i cui elementi sono di tipo Punto: Punto v[100];

  • passare parametri di tipo Punto alle f unzioni: void funzione(Punto p);

  • far restituire dati di tipo Punto alle funzioni: Punto funzione();

In tutti i casi la variabile di tipo Punto conterrà due valori distinti, uno nel campo x e uno nel campo y

Quindi un nuovo tipo di dato permette di aggregare diversi valori e consente di trattarli anche in modo unitario.

Per accedere ad un singolo campo si utilizza la notazione con punto (dot notation):

a.x indica il contenuto del campo x della variabile a

b.y indica il contenuto del campo y della variabile b

Le struct in C

Per dichiarare un nuovo tipo di dato in linguaggio C si usa la keyword “struct”; quindi per l’esempio precedente:

struct Punto { float x; float y; };

Oppure, essendo x e y dello stesso tipo:

struct Punto { float x, y; };

La struct può contenere qualsiasi tipo di dato :

struct Persona { char nome[20]; int peso; char sesso; };

struct Triangolo { // a,b,c sono i vertici del triangolo e sono di un tipo definito in precedenza. struct Punto a,b,c; };

Una struct è una dichiarazione che deve essere visibile a tutto il programma, quindi viene di solito definita nella sezione delle dichiarazioni globali (dopo #include… e prima dei prototipi delle eventuali funzioni che la usano).

Il codice relativo al problema precedente, usando le funzioni, diventa:

//dichiarazione del tipo di datostruct Punto { float x,y; }; //prototipi delle funzioni:struct Punto inputPunto(void); struct Punto puntoMedio(struct Punto p1,struct Punto p2); main() { struct Punto a,b,m; //variabili del main printf("primo punto\n"); a=inputPunto(); printf("secondo punto\n"); b=inputPunto(); m=puntoMedio(a,b); printf("punto medio (%0.2f;%0.2f)\n",m.x,m.y); getchar(); } //funzionistruct Punto inputPunto(void) { struct Punto p; //variabile locale printf("ascissa: "); scanf("%f",&p.x); printf("ordinata: "); scanf("%f",&p.y); return p; } struct Punto puntoMedio(struct Punto p1,struct Punto p2) { struct Punto p;//variabile locale p.x=(p1.x+p2.x)/2; p.y=(p1.y+p2.y)/2; return p; }

Ogni volta che vogliamo un dato del nuovo tipo dobbiamo dichiarare struct tipoDato nomeDato; è un po' scomodo!

La parola chiave typedef consente di creare un alias di un qualsiasi tipo, primitivo o derivato; non definisce un nuovo tipo , ma crea un sinonimo che corrisponde a uno dei tipi già definiti.

Possiamo creare un alias di struct Punto:

// definisce un nuovo tipo "struct p"struct p{

float x,y;

}; // rinomina il tipo di dato “struct p” come “Punto”typedef struct p Punto;

D'ora in poi nelle dichiarazioni possiamo usare indifferentemente la scrittura “struct p” o ”Punto”:

struct p a; // dichiara la variabile a di tipo p Punto b; // dichiara la variabile b di tipo p

Le variabili a e b sono dello stesso tipo!

In modo ancora più comodo possiamo usare una sola istruzione, definendo una struct “anonima” e rinominandola immediatamente.

typedef struct{ float x,y; } Punto;

Quindi i prototipi delle funzioni diventano:

Punto inputPunto(void); Punto puntoMedio(Punto p1, Punto p2);

e le variabili vengono dichiarate semplicemente con:

Punto a,b;

Array di struct

Quando in un programma è necessario utilizzare molte variabili dello stesso tipo, è comodo aggregarle in un array.

Gli array possono essere di qualunque tipo di dato, quindi anche di un tipo definito dal programmatore.

Avendo, ad esempio, dichiarato un nuovo tipo di dato per descrivere un alunno con nome (stringa) e un voto:

typedef struct{ char nome[50]; int voto; } Alunno;

per gestire un array di massimo 100 elementi di tipo Alunno si scrive:

Alunno v[100];

Nel programma l'array viene gestito come abbiamo già visto: può essere un parametro in ingresso alle funzioni (utilizzando un puntatore), può essere memorizzato su file, ecc.

L’istruzione per accedere ad un campo di un elemento in posizione i-esima è v[i].campo, quindi v[i].nome consente di accedere al campo “nome” e v[i].voto permette di accedere al campo “voto”.

Naturalmente l'alternativa poteva essere quella di gestire due array paralleli: un array di “stringhe” per i nomi e un array di int per i voti. Ma è molto più comodo usare un array di struct, come si evidenzia in questo esempio:

void ordina(int n, Alunno *v) { int i,j; Alunno aux; for(i=0; i<n-1; i++) { for(j=i+1; j<n; j++) { if(strcmp(v[i].nome,v[j].nome)>0) { //crescente aux=v[i]; v[i]=v[j]; v[j]=aux; } } } }

Infatti è possibile dichiarare una variabile “ausiliaria” per scambiare un dato di tipo Alunno (che contiene insieme il nome e il voto) e usare solo tre istruzioni per lo scambio (la struct consente di trattare in modo unitario tutti i dati riferiti allo stesso oggetto).

Se avessimo usato gli array paralleli, avremmo dovuto usare una variabile per lo scambio del nome e una per lo scambio del voto, con sei istruzioni per lo scambio!