Per l’implementazione delle liste in linguaggio C, possiamo utilizzare due importanti costrutti:
Le strutture del C sono simili ai record del Pascal:
Per denotare una struttura si usa la parola chiave struct seguita dal nome identificativo della struttura, che è opzionale.
Nell’esempio in figura si definisce una struttura “libro” e si crea un’istanza di essa chiamata “biblio”.
Consideriamo il seguente esempio di uso congiunto di strutture e puntatori (vedi figura):
Abbiamo dunque creato una struttura di tipo PIPPO e di nome “elemento”, ed un puntatore ad una struttura di tipo PIPPO.
Per accedere ai membri interni della struttura “elemento” abbiamo usato l’operatore -> sul puntatore alla struttura.
A differenza di altri linguaggi, all’occorrenza il C permette di assegnare la giusta quantità di memoria alle variabili del programma.
Le funzioni utilizzate per gestire dinamicamente la memoria delle variabili sono principalmente:
Infine, un comando particolarmente utile è sizeof, che restituisce la dimensione del tipo di dato da allocare.
Queste funzioni sono incluse nella libreria malloc.h,
La sintassi della funzione realloc() ha due argomenti, il primo riguarda l’indirizzo di memoria, il secondo specifica la nuova dimensione del blocco;
Esempio di frammento di codice (vedi figura)
Produzione di “garbage”:
Riferimenti “dangling”(fluttuanti):
Una lista è una collezione di elementi omogenei
A differenza dell’array, la dimensione di una lista non è nota a priori e può variare nel tempo. Inoltre un elemento nella lista occupa una posizione qualsiasi, che tra l’altro può cambiare dinamicamente durante l’utilizzo della lista stessa.
Ogni elemento nella lista ha uno o più campi contenenti informazioni, e, necessariamente, deve contenere un puntatore per mezzo del quale è legato all’elemento successivo
Esempio (vedi figura)
Una lista puntata (semplice) ha una gestione sequenziale, in cui è sempre possibile individuare la testa e la coda della lista.
Esempio (vedi figura)
Le operazioni che agiscono su un a lista rappresentano gli operatori elementari che agiscono sulle variabili di tipo lista
Corrispondono a dei sottoprogrammi (funzioni)
Alcune operazioni modificano la lista
Operazioni tipiche:
Data una lista L, per verificare se essa è vuota è sufficiente controllare se essa punta a NULL.
La seguente verifica se L è vuota
La funzione crea_lista() crea due puntatori ad elemento, uno di nome p (punta al primo elemento) e l’altro di nome punt (permette di scorrere la lista);
Assumiamo che si voglia creare una lista di 3 elementi (5,20,12); Alla prima iterazione abbiamo la seguente situazione:
Supponiamo adesso di aver inserito i primi due elementi e stiamo per inserire il terzo. La lista avrà la seguente forma:
A questo punto inserendo il valore 12 per prima cosa viene creato un altro oggetto della lista, identificato con punt -> next,
poi “punt”, il puntatore ausiliario, viene fatto puntare, non più al secondo elemento, bensì al terzo, all’atto pratico “punt” diventa il puntatore dell’oggetto da lui puntato (cioè, punt = punt -> next;).
Quindi viene inserito il campo informazione dell’elemento tramite l’input da tastiera dell’utente; in questo caso viene inserito il valore 12;
Alla fine, punt punta al valore NULL che identifica la fine della lista.
La seguente funzione iterativa permette di stampare tutti gli elementi interi presenti in una lista, nell’ordine in cui sono memorizzati
La ricorsione risulta particolarmente utile sulle liste collegate. Questo è dovuto al fatto che le liste si possono definire in modo ricorsivo:
Una lista è la lista vuota, oppure un elemento seguito da un’altra lista.
In altre parole, una variabile di tipo lista L può valere NULL (che rappresenta la lista vuota), oppure può essere un puntatore a una struttura che contiene un dato più un altro puntatore. Possiamo quindi dire che la struttura è composta da un elemento e da un puntatore, che rappresenta un’altra lista.
La lista si ottiene guardando la struttura puntata e poi seguendo i puntatori fino a NULL.
Sia L una lista definita da
struct lista {int val; struct lista *next;} L;
Una funzione ricorsiva su L avrà come argomento L, e al suo interno una chiamata ricorsiva a cui si passa L_next.
Queste funzioni normalmente operano su L_val (il primo elemento della lista), e poi agiscono sul resto della lista solo attraverso la chiamata ricorsiva.
Se p rappresenta la lista vuota, non si stampa niente; si esce semplicemente dalla funzione senza fare nulla.
Al passo i-esimo, si stampa la testa della lista e si richiama la funzione visualizza_lista sulla lista meno la testa.
Sia L una lista definita da
struct lista {int val; struct lista *next;} L;
Scrivere in linguaggio C una funzione ricorsiva che preso in input L, raddoppi tutti gli elementi dispari della lista
1. Introduzione al Corso - Il Linguaggio C (I parte)
2. Linguaggio C – Seconda Parte
3. Ordinamento, Ricorsione e Code di Priorità
4. Esercitazione su Ricorsione e Code di Priorità
5. Stack e Code
6. Esercitazione di Laboratorio su Stack e Code
7. Implementazioni di Liste puntate
8. Esercitazione di laboratorio su Liste Puntate Semplici
9. Implementazioni di Liste Doppiamente Puntate e Circolari
10. Esercitazione di laboratorio su Liste Doppiamente puntate
12. Esercitazione di laboratorio su Alberi Binari di Ricerca
13. Alberi Binari di Ricerca. Cancellazione di un nodo
14. Esercizio di Laboratorio. Gioco su alberi
15. Grafi: Implementazione ed operazioni di base
16. Esercitazione di laboratorio: Implementazione operazioni di bas...
17. Grafi: Inserimento e Cancellazione di un nodo. Visite in ampiez...
18. Esercitazione di laboratorio: Problema del venditore Prima part...
19. Componenti fortemente connesse e alberi minimi di copertura
20. Esercitazione di laboratorio: Problema del venditore Seconda pa...