- Tipi
fondamentali e tipi derivati
Prima di introdurre le strutture,
parliamo della differenza fra tipi
fondamentali e tipi
derivati in C.
- Tipi fondamentali (anche detti tipi base o elementari o aritmetici), esempio:
- char
- int
- float
- double
- enum
- Tipi derivati, esempio:
- void
- array
- funzioni
- puntatori
- strutture
- unioni
I tipi derivati si chiamano così perché i domini che
rappresentano sono definiti a partire da quelli dei tipi base, esempio:
- array di caratteri (stringhe)
- funzioni da numeri in virgola
mobile a interi
- puntatori a interi
Inoltre
possiamo continuare a comporre tipi derivati per ottenere tipi
derivati sempre più complessi, esempio:
- array di stringhe
- funzioni da puntatori di interi a
interi
- puntatori a puntatori di numeri in
virgola mobile
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:
- Quando riferiamo un
tipo struttura dobbiamo sempre
usare la parola chiave struct (anche
quando dichiariamo un array)
- La definizione del tipo struttura deve essere chiusa
da un punto e virgola dopo la parantesi graffa
- I nomi elencati all'interno della struttura vengono
detti membri oppure campi
- Per accedere ai valori dei campi di una
struttura si usa la dot
notation punti[0].ascissa
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:
- Definire funzioni che restituiscono valori di un
qualsiasi tipo struttura definito dall'utente
- Effettuare
assegnamenti tra variabili dello stesso tipo struttura
senza riferirne i singoli membri, esempio:
punti[0] = punti[1];
copia
i valori dei campi ascissa e ordinata
di punti[1]
nei corrispondenti campi
di punti[0],
ovvero è
equivalente a:
punti[0].ascissa = punti[1].ascissa;
punti[0].ordinata = punti[1].ordinata;
- Definire
puntatori a variabili di tipo struttura, esempio:
Punto *p = &punti[0];
In questo caso per accedere i campi possiamo
usare le notazioni
alternative riportate sotto:
(*p).ascissa = 10; // sconsigliata
p->ordinata = 20; // più frequente
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