9 - Puntatori e Memoria dinamica

annuncio pubblicitario
Puntatori
INFORMATICA
• I puntatori sono variabili che contengono indirizzi di
memoria.
• Sono specificati tramite l’operatore unario ‘*’ specificato
accanto al nome della variabile.
• Sintassi:
<tipo> ‘*’ <identificatore>
• Per ogni tipo di variabile “puntata ”, esiste un “tipo ”
diverso di puntatore:
Puntatori e
memoria dinamica
- Esempi:
int x; int *px;
/* puntatore a un intero breve */
double y; double *py;
/* puntatore a un double */
© Piero Demichelis
Puntatori: esempio
Puntatori
• Gli operatori ‘*’ e ‘&’ sono uno l’inverso dell’altro
Indirizzi
int x=5, *px;
…
px = &x;
x
2
5
- L’applicazione consecutiva in qualunque ordine ad un puntatore
fornisce lo stesso risultato
• *&px e &*px sono la stessa cosa!
10016
• In termini di operatori:
- ‘*’= operatore di indirezione
• Opera su un indirizzo
• Ritorna il valore contenuto in quell’indirizzo
dopo quel frammento di programma
px contiene l’indirizzo di x (cioè 10016).
Pertanto x e *px sono la stessa
cosa!
- ‘&’ = operatore di indirizzo
px
10016
• Opera su una variabile
• Ritorna l’indirizzo di una variabile
28104
• Quindi:
*(&px): il contenuto della cella che contiene l’indirizzo di px
&(*px): l’indirizzo della variabile puntata da px
Memoria
© Piero Demichelis
3
© Piero Demichelis
4
Puntatori
Puntatori
• Esempio:
#include <stdio.h>
#include <stdlib.h>
• Nelle istruzioni di I/O per i puntatori è definito un
apposito descrittore di campo, %p, il quale converte in un
intero esadecimale la variabile a cui è riferito.
int main()
{
int a, *aptr;
a = 256;
aptr = &a;
• Questo descrittore può essere usato sia con la scanf che
con la printf, tuttavia la sua utilità in input è riservata a
casi particolarissimi di accesso diretto a specifiche
locazioni di memoria.
printf ("\nIl valore di a è %p", a);
/* valore di a in Hex. */
printf ("\nIl valore di *aptr è %p", *aptr);
/* valore di a in Hex*/
printf ("\n&*aptr = %p ----- *&aptr = %p", &*aptr, *&aptr);
/* indirizzo di a */
}
• In output invece può essere utilizzato per visualizzare
l’indirizzo di memoria delle variabili.
© Piero Demichelis
5
Operazioni sui puntatori
- Sottrazione tra due puntatori (NON la somma) dello
stesso tipo
• Sui puntatori sono definite solo alcune operazioni:
- Incremento/decremento
px--;
double *px, *py;
px – py
/* è un operazione lecita e produce un risultato
intero (quanti elementi ci sono tra i due indirizzi) */
/* sono operazioni lecite */
- Confronto tra puntatori dello stesso tipo
- Somma/sottrazione di un valore intero
int *px
px += 3;
6
© Piero Demichelis
Operazioni sui puntatori
int *px;
px++;
/* indirizzo di a */
/* indirizzo di a */
printf (“\nL'indirizzo di a è %p", &a);
printf ("\nIl valore di aptr è %p", aptr);
int *px, *py;
px -= 5;
px == py
/* sono operazioni lecite */
- Assegnazione di un puntatore ad un altro dello stesso tipo
int *px, *py;
px = py;
px != py
px > py
/* sono operazione lecite */
• Confronto tipico:
px == NULL
/* è un operazione lecita */
© Piero Demichelis
7
© Piero Demichelis
8
Operazioni sui puntatori: errori tipici
Aritmetica dei puntatori
• L’assegnazione di un valore a un puntatore è permessa,
ma è sconsigliata.
int *px;
px = 5;
• Le operazioni sui puntatori non avvengono secondo
l’aritmetica intera ma dipendono dal tipo a cui si fa
riferimento.
/* L’accesso a quella cella potrebbe essere vietato! */
• Incremento/decremento di un puntatore:
• Sottrarre o confrontare puntatori di tipo diverso.
- Il risultato è ottenuto moltiplicando il valore dell’incremento o
decremento per la dimensione (numero di byte) del tipo cui si fa
riferimento.
- Unica eccezione: uso di puntatori void* che sono considerati come
dei puntatori “jolly”.
- Esempio:
int *px;
/*
assumiamo che px sia uguale a 1000
*/
px += 3;
px non vale 1003, bensì:
1000 + 3*sizeof(int) = 1006
• Usare l’aritmetica dei puntatori su puntatori che non si
riferiscono ad un vettore.
- Gli operatori relazionali (<,>, etc.) hanno senso solo se riferiti ad
un “oggetto” comune.
© Piero Demichelis
9
Aritmetica dei puntatori
10
Aritmetica dei puntatori: Esempi
• Sottrazione tra puntatori:
long *p1, *p2;
int j;
char *p3;
- Il risultato corrisponde al numero di elementi del tipo cui si fa
riferimento compresi tra i due puntatori.
- Esempio:
p2 = p1 + 4;
j = p2 - p1;
j = p1 - p2;
p1 = p2 - 2;
p3 = p1 - 1;
j = p1 - p3;
int *px, *py, diff;
/* assumiamo che px sia uguale a 1012 e py uguale a 1000 */
diff = px – py;
© Piero Demichelis
diff non vale 12, bensì:
(1012-1000)/sizeof(int) = 6
/* legittimo: p2 = p1 + (4*sizeof(long)) */
/* a j viene assegnato 4 */
/* a j viene assegnato − 4 */
/* OK, i tipi dei puntatori sono compatibili */
/* NO i tipi dei puntatori sono diversi */
/* NO i tipi dei puntatori sono diversi */
• NOTA: l’aritmetica dei puntatori dipende dalla macchina
- Quanti byte occupa un puntatore?
- TurboC puntatore = 2 byte
© Piero Demichelis
11
© Piero Demichelis
12
Puntatori e vettori
Puntatori e vettori: analogie
• La relazione tra puntatori e vettori in C è molto stretta.
• L’analogia tra puntatori e vettori si basa sul fatto che,
dato un vettore a[ ]
• Il nome di un vettore rappresenta l’indirizzo del primo
elemento del vettore stesso!
a[i] e *(aptr+i) sono la stessa cosa!
- Esempio:
• int a[8], *aptr;
• L’assegnazione aptr = a; fa puntare aptr al primo elemento
del vettore a (cioè a[0]).
aptr
• Pertanto ci sono almeno due modi per accedere al
generico elemento i del vettore:
- Usando il nome del vettore (tramite indice)
- Usando un puntatore (tramite scostamento o offset)
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7]
.....
.....
• Interpretazione:
aptr = a
- aptr + i = indirizzo dell’elemento che si trova i posizioni dall’inizio
- *(aptr + i) = contenuto di questo elemento
a
© Piero Demichelis
13
Puntatori e vettori: analogie
© Piero Demichelis
14
Puntatori e vettori: analogie
for (i = 0; i < 5; i++) {
/* legge i valori da inserire nel vettore */
printf ("\nInserisci a[%d] = ", i);
scanf ("%d", &a[i]);
}
/*
visualizza il vettore
*/
for (i = 0; i < 5; i++)
/* in modo “normale” */
printf ("\nValore di a[%d] = %d", i, a[i]);
• Esempio: il programma che segue riempie un vettore di 5 elementi
leggendone i valori da tastiera e successivamente lo visualizza due
volte; la prima in modo tradizionale, la seconda utilizzando un
puntatore a_ptr.
#include <stdio.h>
#include <stdlib.h>
a_ptr = a;
for (i = 0; i < 5; i++)
/* utilizzando un puntatore (modo A) */
printf ("\nValore di a[%d] = %d", i, a_ptr[i]);
int main()
{
int a[5], *a_ptr;
int i;
/* utilizzando un puntatore (modo B) */
for (i = 0; i < 5; i++)
printf ("\nValore di a[%d] = %d", i, *(a_ptr+i));
}
© Piero Demichelis
15
© Piero Demichelis
16
Puntatori e struct
Puntatori e stringhe
• Il C fornisce uno speciale operatore ( l’operatore freccia:
->) per accedere ai membri di una struttura tramite un
puntatore alla struttura stessa:
• Stringa è un vettore di char terminato da ’\0‘.
• La relazione tra puntatori e stringhe è sostanzialmente
identica a quella con vettori generici.
• Differenza:
- Utile per passaggio di struct per indirizzo!
- char *p = “abcd”;
- char s[5] = “abcd”;
p
2200
2201
...
1000
1001
1002
1003
1004
‘a’
‘b’
‘c’
‘d’
‘\0’
s
6400
6401
6402
6403
6405
6400
• Esempio:
struct complex
{
double re;
double im;
} complesso, *pc;
‘a’
‘b’
‘c’
‘d’
‘\0’
© Piero Demichelis
17
Puntatori e struct
Puntatori e struct: Esempio
struct complex
{
double re;
double im;
};
• Come abbiamo visto a suo tempo si accede ai campi con
complesso.re, complesso.im
• Utilizzando il puntatore alla struttura, agli stessi campi si
accede con
typedef struct complex complesso;
pc->re, pc->im
/* Funzione add_cplx: somma due numeri complessi: a = b + c
naturalmente dopo aver assegnato l’indirizzo di
complesso a pc!
*/
void add_cplx (complesso *a, complesso *b, complesso *c)
{
a->re = b->re + c->re;
a->im = b->im + c->im;
}
pc = &complesso;
• NOTA: come già detto a proposito dei vettori, pc->re e
(*pc).re sono equivalenti.
© Piero Demichelis
18
© Piero Demichelis
19
© Piero Demichelis
20
Gestione della memoria
Gestione della memoria
• Fino ad ora abbiamo sempre dichiarato variabili di
dimensioni note, e la cui durata in vita è vincolata
alle regole di esecuzione di un programma scritto in
linguaggio C.
• Per usare, ad esempio, un vettore, è necessario
specificarne la dimensione in fase di definizione.
• La dichiarazione, per esempio,
int peso [100];
• Ad esempio una variabile dichiarata nel main vive per
tutta la durata del programma; una variabile dichiarata in
una funzione vive solo per la durata dell’esecuzione della
funzione, se la funzione viene richiamata un’altra volta la
stessa variabile di prima viene creata di nuovo e vivrà
solo per la durata di quell’esecuzione.
• alloca un vettore di cento interi, che, a seconda di dove
avviene la definizione, vive per la durata della funzione in
cui viene definito (variabile automatica) o per tutta la
durata del programma.
• Questo implica che si conosca in anticipo e con
precisione, (a compile-time, si dice, o in pratica quando si
scrive il programma) il numero dei dati da trattare.
• Le variabili così definite, siano esse variabili semplici
oppure vettori oppure ancora altre strutture dati, si
dicono variabili statiche.
© Piero Demichelis
•
21
© Piero Demichelis
Gestione della memoria
Gestione della memoria
E se invece non se ne conosce la dimensione?
• Esempio #2: si supponga di dover leggere e registrare in
memoria (ad esempio, in un vettore) dati da un file di
dimensioni ignote; si può ipotizzare (ma è un ipotesi come
un'altra!) che i dati non superino un numero molto grande, per
esempio un milione.
In pratica si ricorre alla sovra-allocazione : si
dimensiona la variabile (ovvero il vettore) in maniera
tale da contenere il più grande numero immaginabile di
dati in ingresso.
•
Esempio #1: acquisendo linee di testo da tastiera, si
può ipotizzare (ma è un ipotesi come un'altra!) che le
linee non superino mai i 100 caratteri.
•
E se una linea supera quel limite? Si può al più mandare
un messaggio d'errore all'utente ma gestire quell’errore
all’interno del programma è quasi sempre complicato.
© Piero Demichelis
22
• Questo caso è ancora più grave di quello prospettato
nell’esempio #1, in quanto questa situazione anomala non è
praticamente mai gestibile nel programma e anche l’eventuale
segnalazione all’utente del problema non conduce a nessuna
contromisura efficace.
• Certo, si possono scrivere messaggi di errore a schermo, ma di
fatto è un fallimento del programma di fronte a dati
perfettamente legittimi.
23
© Piero Demichelis
24
Gestione della memoria
Gestione della memoria
Cosa si può fare?
• Esempio: leggendo dei record da un file posso richiedere,
prima di ogni lettura, l’allocazione di uno spazio di
memoria grande a sufficienza per contenere i dati che
leggerò.
• Per risolvere il problema molti linguaggi ad alto livello
consentono di allocare memoria secondo il bisogno che
emerge al momento dell'esecuzione del programma.
…
while (!feof(fp))
{
• Questa opportunità è nota col nome di
alloca memoria per un record;
fscanf (fp, …);
…
allocazione dinamica della memoria
/* leggi il record */
}
• La dimensione della variabile viene decisa a run-time
(cioè, al momento dell'esecuzione del programma)
a seconda del numero di dati in ingresso.
© Piero Demichelis
• Ad ogni ciclo si incrementa lo spazio di memoria riservato
alla mia base dati!
25
Memoria dinamica
record 1
ptr2
record 2
• Resta ancora da definire quanto grande deve essere lo
spazio di memoria che deve essere richiesto.
• Dal momento che tutti gli oggetti di un certo tipo
occupano esattamente lo stesso spazio di memoria,
possiamo determinare con precisione lo spazio di
memoria (il numero di byte) necessario a ospitare i dati.
• Per conoscerne con precisione la quantità basta usare
l'operatore sizeof il quale restituisce il numero di byte che
occupa il tipo di dato, o la struttura dati, indicata quale
parametro tra le parentesi.
ecc.
© Piero Demichelis
26
Memoria dinamica
• Come funziona il meccanismo? Tramite puntatori! Ogni
volta che richiedo una nuova area di memoria mi viene
restituito un puntatore all’area di memoria che mi è stata
assegnata.
ptr1
© Piero Demichelis
27
© Piero Demichelis
28
Memoria dinamica
Memoria dinamica
• L'operatore sizeof può agire o su un tipo, ad esempio,
• Ecco alcuni esempi di applicazione di sizeof nell’ipotesi
che gli oggetti di tipo short occupino 2 byte e quelli di
tipo long 4 byte.
sizeof (double)
o su una variabile, un vettore, un’espressione, una struct,
ad esempio,
sizeof (char)
sizeof (long int)
short s; ...sizeof (s)
short s; ...sizeof (s + 24)
sizeof (vettore_dati)
in ogni caso restituisce un intero corrispondente al
numero di byte occupato dall’argomento. Per definizione,
sizeof applicato al tipo char restituisce 1.
(il risultato dell’addizione è di tipo int) !
long int vett[10]; ...sizeof (vett)
double num_real; ... sizeof (num_real)
double vett_r [20]; ... sizeof (vett_r)
• Non può essere applicato a vettori incompleti (senza
indicazione della dimensione), a funzioni, o al tipo void.
© Piero Demichelis
1
4
2
2
29
Memoria dinamica
40
8
160
© Piero Demichelis
30
Memoria dinamica
• Programma di esempio per determinare le dimensioni dei
principali tipi di dati C:
• Quando sizeof è applicato ad un' espressione,
l'espressione è analizzata al momento della compilazione
per determinare lo spazio occupato, ma non è valutata.
#include <stdio.h>
• Per esempio, l'esecuzione di:
int main()
{
printf ("\t Dimensione dei tipi:\n");
printf ("char \t short \t int \t long \t float \t double \n");
printf ("%3d \t %3d \t %3d \t %3d \t %3d \t %3d\n", sizeof (char),
sizeof (short), sizeof (int), sizeof (long), sizeof (float), sizeof (double) );
}
© Piero Demichelis
sizeof (j++)
non provocherà l’incremento di j, ma verrà valutata
solamente la dimensione in byte del risultato.
31
© Piero Demichelis
32
Memoria dinamica
Memoria dinamica
• Per ottenere l’allocazione di spazio in memoria sono
disponibili delle funzioni di libreria.
• Quando un programma richiede l’allocazione di un certo
spazio di memoria, il sistema verifica l’esistenza di uno
spazio di celle contigue disponibile pari a quello richiesto
e, se lo trova, lo occupa e ne restituisce l’indirizzo al
chiamante (puntatore alla memoria).
• Permettono ad un programma di richiedere anche
ripetutamente l'allocazione di una regione “fresca ” di
memoria e di deallocarla più tardi (liberarla) se tale
regione non serve più.
• Questo puntatore sarà di tipo void *, ma è garantito che
sia adeguatamente allineato per qualsiasi tipo di dati. E’
necessario che il chiamante faccia un cast per convertirlo
nell’opportuno tipo di puntatore (int, double, ecc.).
• Regioni esplicitamente deallocate sono riciclate dallo
storage manager del sistema operativo per soddisfare
ulteriori richieste del programma corrente o di un
qualunque altro programma che faccia richiesta di
allocazione di memoria.
© Piero Demichelis
• Le funzioni sono dichiarate nell'header file <stdlib.h>.
33
© Piero Demichelis
Funzione malloc
Funzione malloc
• Esempio:
• La creazione di una variabile dinamica avviene
prelevando dalla memoria un blocco di dimensione
opportuna tramite una funzione di allocazione (malloc).
…
int dimension = 50;
char *stringa;
…
stringa = (char *) malloc (dimension * sizeof (char));
if (stringa == NULL)
{
printf ( “\nErrore: memoria non disponibile”);
return ;
}
strcpy (stringa, “questa e’ una stringa”);
printf (“\n%s”, stringa);
…
• Prototipo della funzione:
void *malloc (unsigned int dimensione);
• La funzione restituisce un puntatore a void (puntatore a
memoria non strutturata) oppure il valore NULL se
l'operazione di allocazione non ha avuto successo (ad
esempio per mancanza di memoria disponibile).
• Per utilizzare correttamente l'area allocata occorre
effettuare un'operazione di cast del puntatore verso il tipo
di dato a cui l’area è destinata.
© Piero Demichelis
34
35
© Piero Demichelis
36
Memoria dinamica
Memoria dinamica
#include <stdio.h>
#include <stdlib.h>
• Una prima soluzione al problema della definizione della
lunghezza dei vettori ignota al momento della scrittura
del programma può pertanto trovare soluzione, purché
l’utente sia in grado di fornirla all’avvio del programma.
int main()
{
int *punt, i, num_el;
• Esempio: programma che richiede il numero di elementi
interi da leggere, alloca lo spazio in memoria, legge gli
elementi e infine li visualizza.
/* richiede il numero di elementi del vettore
printf ("\nNumero di elementi da leggere: ");
scanf ("%d", &num_el);
• Simile a: programma che richiede il numero di elementi
interi da leggere, li legge e li salva in un vettore e infine li
visualizza.
© Piero Demichelis
*/
/* richiede l’allocazione di memoria sufficiente a contenere i dati pari
a num_el * il numero di byte occupato da ogni elemento
*/
punt = (int *) malloc (num_el * sizeof (int));
37
© Piero Demichelis
Memoria dinamica
Memoria dinamica
if (punt == NULL)
{
printf ( “\nErrore: memoria non disponibile”);
return;
}
/* legge num_el elementi da tastiera e li salva nello spazio allocato */
for (i = 0; i < num_el; i++)
{
printf ("\nInserisci l'elemento di indice %d = ", i);
scanf ("%d", &punt[i]);
}
/* visualizza il vettore */
for (i = 0; i < num_el; i++)
printf ("\nElemento %d = %d", i, punt[i]);
/* Utilizzando la definizione di puntatore la lettura e visualizzazione dei
dati può essere effettuata anche nel modo seguente */
© Piero Demichelis
38
/* legge num_el elementi da tastiera e li salva nello spazio allocato */
for (i = 0; i < num_el; i++)
{
printf ("\nInserisci l'elemento di indice %d = ", i);
scanf ("%d", (punt + i));
}
/* visualizza il vettore */
for (i = 0; i < num_el; i++)
printf ("\nElemento %d = %d", i, *(punt + i));
}
39
© Piero Demichelis
40
Funzione free
Esempio
• Si realizzi un programma che legga da un file la classifica di un
campionato di calcio. Il numero di squadre è scritto nella prima riga
del file.
• Per liberare la memoria allocata dinamicamente, si utilizza
la funzione free.
• Successivamente il programma dovrà visualizzare la classifica sul
monitor.
• Il blocco di memoria deallocato viene restituito allo
storage manager del sistema operativo e potrà essere
riutilizzato successivamente dai programmi che faranno
richiesta di memoria.
• Esempio del file di input
8
Juventus 66
Milan 62
Inter 60
Roma 58
Bologna 56
Livorno 54
Reggina 52
Siena 50
• Prototipo:
void free (void *punt)
• il parametro della funzione è il puntatore all’area di
memoria allocata in precedenza tramite malloc.
© Piero Demichelis
41
42
© Piero Demichelis
Esempio
Esempio
main()
{
• Analisi:
- per memorizzare le squadre e il relativo punteggio useremo una
struct con due campi: nome della squadra (stringa) e punti (int).
typedef struct
{
char nome_sq[30];
int punti;
} classifica;
- Il programma dovrà richiedere il nome del file, aprirlo e leggere il
primo valore, ovvero il numero di squadre che seguono.
- In base a questo numero richiederà l’allocazione del numero di
byte necessario per memorizzare l’intero file ovvero tutte le
squadre e il loro punteggio.
classifica *vett_class;
char nomefile[30];
int num_squadre, i;
/* puntatore alla struttura
*/
- Il numero di byte da richiedere sarà uguale a:
num. squadre * sizeof (struttura con i due campi)
© Piero Demichelis
FILE *filedati;
43
© Piero Demichelis
44
Esempio
Esempio
/* richiede il nome del file contenente la classifica */
printf ("\nIntroduci il nome del file: ");
scanf ("%s", nomefile);
/* apre il file */
if ((filedati = fopen (nomefile,"r")) == NULL)
{
printf (“\nErrore apertura: %s", nomefile);
exit (0);
}
if (vett_class == NULL)
{
printf (“\nErrore allocazione memoria");
exit (1);
}
/* legge il file e lo memorizza nel vettore dinamico */
for (i = 0 ; i < num_squadre ; i++)
fscanf (filedati, "%s%d", &vett_class[i].nome_sq, &vett_class[i].punti);
fclose (filedati);
/* Visualizza sul monitor il vettore dinamico
*/
printf ("\n\nSquadra
Punti\n");
for (i = 0; i < num_squadre; i++)
printf(“\n%-12s %d", vett_class[i].nome_sq, vett_class[i].punti);
/* rilascia la memoria */
free (vett_class);
}
/* legge il primo dato, ovvero il numero di squadre che seguono */
fscanf (filedati, "%d", &num_squadre);
/* richiede l’allocazione della memoria necessaria per memorizzare il
resto del file */
vett_class = (classifica *) malloc (num_squadre * sizeof (classifica));
© Piero Demichelis
45
Memoria dinamica
46
Strutture dati complesse
• Mettendo insieme la versatilità delle struct e la possibilità
di richiedere quanta memoria si desidera (nell’ambito
della memoria disponibile) si riesce a costruire delle
strutture dati assai flessibili e complesse.
• Spesso succede però che la quantità di memoria
necessaria per memorizzare i dati non è nota neppure nel
momento in cui inizia l’esecuzione del programma.
• Tuttavia la quantità di memoria che un programma può
allocare dipende solo dalla memoria disponibile.
• Non essendoci particolari limiti alla definizione di un
campo di una struct possiamo pensare di definirlo di tipo
puntatore, in particolare puntatore a una struct dello
stesso tipo.
• Per risolvere definitivamente il problema bisogna pensare
di costruire strutture dati che “crescono” durante
l’esecuzione del programma e quindi capaci di adattarsi
alle necessità man mano che queste emergono.
© Piero Demichelis
© Piero Demichelis
• Se in quel campo inseriamo l’indirizzo di un’altra struttura
possiamo creare una struttura dati, nota come lista
concatenata, simile a un vettore e che può crescere al
bisogno fino al limite della memoria disponibile
47
© Piero Demichelis
48
Strutture dati complesse
Strutture dati complesse
• Supponiamo ad esempio di dover leggere da un file una sequenza di
numeri interi la cui dimensione non è nota al momento della scrittura
del programma.
• Ad ogni lettura dal file scriveremo il valore del dato nel
primo campo (quello int) utilizzando poi il campo
puntatore per inserire l’indirizzo della prossima area di
tipo struct che richiederemo con la malloc e destinata a
contenere il prossimo dato.
• Si potrebbe pensare a una lista concatenata utilizzando una struct
con due campi: un campo int per il valore letto dal file e un campo
puntatore per concatenarsi al valore successivo.
• Si ottiene una struttura di questo tipo:
• La struct sarà del tipo:
struct
{
int dato;
void *punt;
}
dato 1 punt
dato 2 punt
testa
© Piero Demichelis
dato n-1 punt
49
dato n NULL
50
© Piero Demichelis
Strutture dati complesse
Strutture dati complesse
• La lista concatenata è una struttura sequenziale: per
leggere l’ennesimo dato bisogna scorrere gli n-1 dati che
precedono.
• Ad esempio pensando a una struct con due campi
puntatori potremo costruire un albero di questo tipo:
p1
d1
• La lunghezza della lista è limitata solo dalla memoria a
disposizione.
d2
• Poiché non c’è limite al numero di campi di una struttura,
si può pensare a tutta una serie di strutture dati in cui
l’unico limite è la fantasia del programmatore.
d4
• Naturalmente esistono delle strutture dati “canoniche”
che vengono regolarmente utilizzate nei programmi e che
si chiamano stack, code, alberi, ecc.
© Piero Demichelis
dato 3 punt
p7
p8
p2
d3
p3 p4
d5
p9 p10
p5
p6
d6 p11 p12
d7 p13 p14
ecc.
51
© Piero Demichelis
52
Scarica