23 aprile 2024
Le strutture sono molto utili per definire… strutture dati.
Per esempio si possono definire array dinamici, liste concatenate, alberi, pile, code…
Gli array in C sono molto limitati, infatti dobbiamo gestire la loro lunghezza a mano…
Si può creare una struttura che contenga un array e la sua lunghezza:
Adesso si può passare la struttura come argomento di una funzione:
void stampa_array(IntArray a) {
for (int i = 0; i < a.length; i++) {
printf("%d ", a.array[i]);
}
printf("\n");
}
Si nota subito che non abbiamo più bisogno di passare la lunghezza dell’array come argomento.
L’implementazione che abbiamo visto ha due grossi problemi:
IntArray
funziona solo con array di interi, per fare array di float, char, ecc. bisogna creare ogni volta una nuova strutturaLe strutture dati in C hanno dimensione fissa. Infatti gli array vengono inizializzati con un valore costante.
Esistono però dei comandi che permettono di allocare dinamicamente zone di memoria, permettendo di fare strutture che crescono o si rimpiccioliscono durante l’esecuzione del programma.
Le operazioni per gestire la memoria dinamica si trovano nella libreria stdlib.h
e sono:
malloc
(memory allocation): alloca una zona di memoria di una certa dimensionecalloc
(clear allocation): alloca una zona di memoria di una certa dimensione e la inizializza a 0realloc
(reallocate): cambia la dimensione di una zona di memoria già allocatamalloc
, calloc
e realloc
restituiscono un puntatore a void, che è un puntatore generico.
Questo puntatore non fa riferimento a un certo tipo di dato, ma solo a un indirizzo di memoria.
malloc
void* malloc(size_t size);
Supponendo di voler creare un array di interi di dimensione variabile, c’è bisogno di creare un puntatore ad interi e poi assegnargli l’indirizzo della nuova zona di memoria:
La funzione malloc
accetta come argomento il numero di byte da allocare. L’esempio crea 10 cellette di memoria, ciascuna grande quanto un intero (4 byte).
calloc
void* calloc(size_t num, size_t size);
L’effetto di questo codice è lo stesso del precedente (con la malloc), ma con tutti i membri dell’array inizializzati a 0.
realloc
void* realloc(void* ptr, size_t size);
Questa funzione cambia la dimensione di uno spazio allocato dinamicamente.
L’argomento ptr
deve essere un puntatore ottenuto da una funzione ..alloc, altrimenti porta a comportamento non definito.
I dati già presenti nella zona di memoria vengono mantenuti.
Quando si usa la memoria dinamica, è importante controllare che l’allocazione sia andata a buon fine.
Se malloc
, calloc
o realloc
non riescono a trovare spazio in memoria, restituiscono NULL
.
NULL
è una costante che rappresenta un puntatore all’indirizzo 0x0
. NULL
è definito in stdlib.h
.
malloc
, calloc
, realloc
free
void free(void* ptr);
ptr
dopo l’esecuzione di free
continua a puntare alla stessa zona di memoria, ma il contenuto di quella zona non è più garantito, si chiama dangling pointerptr
il valore NULL
(oppure una nuova memoria)Un array dinamico è un array la cui dimensione può cambiare durante l’esecuzione del programma.
Per implementare un array dinamico si può usare una struttura che contiene un puntatore all’array e la sua lunghezza:
ArrayList
Il nuovo tipo ArrayList
ha tre membri:
array
è un puntatore ad interi, che punta all’array dinamicocapacity
è la dimensione massima dell’arraylength
è il numero di elementi attualmente presenti nell’arrayQuando length raggiunge capacity, bisogna riallocare la memoria per l’array, aumentando la sua dimensione.
L’implementazione classica della lista concatenata in C è:
Dato il file con il main e i prototipi delle funzioni, implementare:
Gli operatori bitwise permettono di effettuare calcoli al livello dei bit delle variabili.
Gli operatori bitwise in C sono:
&
AND|
OR^
XOR~
NOT<<
Shift a sinistra>>
Shift a destraQuesti operatori applicano le regole dell’algebra booleana, pertanto sarà importante conoscere le tabelle di verità delle operazioni AND, OR e XOR.
A | B | A & B | A | B | A ^ B |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 1 |
0 | 1 | 0 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 | 0 |
1 | 1 | 1 | 1 | 0 | 1 |
Per effettuare le operazioni bitwise conviene sempre cercare di rappresentare i numeri in binario.
C permette di dichiarare numeri in binario utilizzando il prefisso 0b
:
L’operatore OR viene spesso usato per unire due insiemi di bit. Ad esempio, se si ha una variabile switches
che rappresenta lo stato di accensione di 8 interruttori, si può accendere un interruttore utilizzando l’operatore OR:
Ora il valore di switches
sarà 0b00001100
. Il che rappresenta il fatto che gli interruttori 3 e 4 sono accesi.
Per spegnere tutti gli interruttori si può utilizzare l’operatore AND:
Scrivere un programma che stampi a monitor il valore del bit meno significativo di un numero intero.
Scrivere una funzione che dato un numero intero \(n\), restituisca true se \(n\) è pari, false altrimenti. Usare l’operatore AND per verificare se un numero è pari.
L’operatore XOR è molto utile per invertire lo stato di un bit.
65: 0b01000001
73: 0b01001001
08: 0b00001000
65 ^ 73 = 8
8 ^ 73 = 65
‘A’ ^ ‘I’ = ’
Scrivere un programma di semplice crittografia in grado di cifrare e decifrare una stringa utilizzando l’operatore XOR.
Gli operatori di shift permettono di spostare i bit di una variabile a sinistra o a destra.
Scrivere un programma che moltiplichi un numero intero per 2 utilizzando l’operatore di shift.
Scrivere un programma che stampi a monitor la codifica in binario di un unsigned char.
Ad esempio, se il valore di c
è 5, il programma dovrà stampare 00000101
.
Immaginando che una serie di 8 bit rappresenti lo stato di accensione di 8 led, scrivere un programma che:
Scarica il main dell’esercizio
Dati e Algoritmi