Tipi fondamentali e tipi derivati

Prima di introdurre le strutture, parliamo della differenza fra tipi fondamentali e tipi derivati in C.


I tipi derivati si chiamano così perché i domini che rappresentano sono definiti a partire da quelli dei tipi base, esempio:
Inoltre possiamo continuare a comporre tipi derivati per ottenere tipi derivati sempre più complessi, esempio:

In generale, se i tipi fondamentali rappresentano i valori elementari immediatamente gestibili dal calcolatore, i tipi derivati possono essere usati per modellare domini complessi a piacere e vicini alle strutture logiche necessarie a modellare direttamente molti aspetti e problematiche del mondo che ci circonda.

Partendo dai tipi fondamentali, abbiamo percorso molti passi di astrazione verso tipi più complessi (array, funzioni, puntatori). La durata del corso ci lascia solo lo spazio per imparare a conoscere ed usare anche le strutture, un tipo derivato fondamentale per coloro che volessero proseguire lo studio del C e la programmazione di strutture dati di uso comune come liste concatenate ed alberi.

Non vedremo invece i tipi enumerativi e le unioni, il cui uso è piuttosto marginale se confrontato con quello delle strutture.


Le strutture

Nelle lezioni precedenti abbiamo visto che gli array permettono di aggregare elementi omogenei (dello stesso tipo) delegando ad un indice numerico l'accesso ad ogni elemento della collezione.

Spesso accade di dover integrare elementi di tipi diversi (di cui alcuni possono essere fondamentali ed altri derivati), oppure di creare delle corrispondenze non ovvie tra indice ed interpretazione dell'elemento.

Ad esempio, se volessimo rappresentare un punto sul piano cartesiano avremmo bisogno di due valori in virgola mobile che ne rappresentino l'ascissa e l'ordinata. A questo scopo potremmo usare un vettore con due posizioni:

  double punto[2];

dove punto[0] rappresenta l'ascissa e punto[1] l'ordinata. Ma se volessimo rappresentare due punti usando una matrice

  double punti[2][2];

otterremmo una notazione ambigua di difficile interpretazione.

Le strutture permettono di aggregare dati eterogenei usando nomi invece che indici per accedere ai vari elementi.

Nell'esempio, potremmo definire il tipo struttura Punto come:

  struct Punto {
double ascissa;
double ordinata;
};

e poi dichiarare le due variabili come elementi di un vettore di punti:

  struct Punto punti[2];

Notare le seguenti convenzioni sintattiche:

Per evitare di dover premettere la parola chiave struct al nome della struttura, possiamo usare la primitiva typedef per definire un nome più compatto:
  typedef struct {
double ascissa;
double ordinata;
} Punto;

Adesso Punto può essere impiegato senza premettere struct.

Per concludere questo esempio, ecco un programma completo che definisce il tipo struttura Punto, crea due punti e ne calcola la distanza sfruttando una funzione opportuna.


/* programma che, dati due punti sul piano cartesiano,
ne calcola la distanza */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct {
double ascissa;
double ordinata;
} Punto;

double distanza(Punto, Punto);

int main(int argc, char *argv[]) {

Punto punti[2];

punti[0].ascissa = 5;
punti[0].ordinata = 7;

punti[1].ascissa = 2;
punti[1].ordinata = 4;

printf("%f\n",distanza(punti[0],punti[1]));

return EXIT_SUCCESS;
}

double distanza(Punto p1, Punto p2) {

double delta1 = p1.ascissa - p2.ascissa;
double delta2 = p1.ordinata - p2.ordinata;

return sqrt((delta1*delta1)+(delta2*delta2));

}


È possibile anche:



La gestione della memoria

Le strutture possono essere combinate con l'uso dei puntatori, per gestire efficientemente una grossa quantita' di dati eterogenei. In teoria, potremmo usare gli array (es. array di strutture) per gestire una grossa mole di dati eterogenei, ma gli array non sono efficienti dal punto di vista di gestione della memoria. Infatti, quando dichiaro un array viene riservata una parte di memoria destinata a contenere i dati dell'array, e questa memoria rimane occupata dall'array durante l'intero svolgimento del programma.

L'utilizzo delle strutture in combinazione con i puntatori permette invece di gestire la memoria in maniera dinamica durante l'esecuzione di un programma. In altre parole, posso allocare memoria per un nuovo dato solo quando ce ne e' bisogno, e liberare la memoria occupata da un dato quando questo non e' piu' necessario.

La libreria
stdlib.h mi mette a disposizione delle funzioni per l'allocazione e la disallocazione della memoria. In particolare:
  malloc che, dato un intero unsigned che rappresenta la dimensione in byte dell'oggetto da allocare, restituisce un puntatore allo spazio
allocato per l'oggetto, oppure
NULL se non c'e' spazio disponibile.

calloc che alloca lo spazio per un array di oggetti, ognuno della dimensione opportunamento speficato, restituendo un puntatore allo
spazio allocato oppure
NULL se non c'e' spazio disponibile.

realloc serve per riallocare lo spazio associato ad un oggetto puntato dal puntatore passato come argomento. La nuova dimensione
dell'oggetto puo' essere
maggiore o minore a quella attuale.

free che rende libero lo spazio corrispondente all'oggetto puntato dal puntatore passato come argomento.

Vediamo un semplice esempio di allocazione dinamica della memoria, ottenuto utilizzando le strutture in combinazione con i puntatori.





/* programma che memorizza un punto del piano
cartesiano in una struttura*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct {
int ascissa;
int ordinata;
struct Punto *next;
} Punto;


int main() {

Punto *NuovoPunto;
int asc,ord;

printf("Inserisci l'ascissa\n");
scanf("%d",&asc);
printf("Inserisci l'ordinata\n");
scanf("%d",&ord);
NuovoPunto=malloc(sizeof(Punto));
NuovoPunto->ascissa=asc;
NuovoPunto->ordinata=ord;
NuovoPunto->next=NULL;
printf("Ecco il punto che hai inserito\n");
printf("(%d,%d)\n",NuovoPunto->ascissa,NuovoPunto->ordinata);

printf("Adesso lo cancelliamo\n");
free(NuovoPunto);

printf("Memoria liberata!\n");

return EXIT_SUCCESS;
}






Esercitazione non guidata


Risolvere il seguente esercizio e spedire il solo file sorgente (suffisso .c) all'indirizzo paolo.santi@iit.cnr.it come attachment di un messaggio di posta elettronica con subject: Esercitazione Lezione 9, indicando il vostro nome, cognome e gruppo di appartenenza.


Esercizio:

Estendere il semplice programma sopra descritto in modo che venga previsto l'inserimento di un nuovo punto (opzione 1), la cancellazione dell'ultimo punto inserito (opzione 2), la stampa della lista corrente dei punti (opzione 3), oppure l'uscita dal programma (opzione 4). Per realizzare l'inserimento, la cancellazione e la stampa dei punti si consiglia di definire apposite funzioni ausiliarie.


Torna alla HomePage del corso