









Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Il documento spiega il concetto di puntatori in informatica, ovvero gli indirizzi di dati che permettono di accedere a strutture dati complesse in modo semplice e compatto. Vengono descritte le dichiarazioni dei puntatori, gli operatori specifici per i puntatori e come utilizzarli come argomenti di funzioni. Viene inoltre spiegato come i puntatori possono puntare ad elementi di un array e come sommare o sottrarre un intero a un puntatore per ottenere l'indirizzo di una componente di un array.
Tipologia: Appunti
1 / 15
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!










Ogni dato presente in memoria possiede un indirizzo: i puntatori sono indirizzi di dati, nonché dati essi
stessi.
Ogni variabile occupa una o più locazioni di memoria (byte); l’indirizzo del primo byte che viene usato per
rappresentare la variabile è anche l’indirizzo della variabile. Gli indirizzi delle locazioni di memoria possono
essere memorizzate in variabili speciali dette puntatori e, quando memorizziamo l’indirizzo di una variabile
i nel puntatore p, diciamo che p “punta a” i.
I puntatori permettono di accedere a strutture dati anche complesse in modo semplice e compatto;
consentono la condivisione di dati fra diverse parti dei programmi (passaggio di parametri per riferimento);
consentono di allocare dinamicamente la memoria e di definire strutture dinamiche; permettono di creare
connessioni fra dati: strutture a puntatori.
Dichirazione dei puntatori:
Quando una variabile puntatore viene dichiarata, il suo nome deve essere preceduto da un asterisco:
p è una variabile che può contenere l’indirizzo di un oggetto (variabile) di tipo int.
Usiamo il termine oggetto al posto di variabile in quanto p potrebbe puntare ad una zona di memoria che
non appartiene ad una variabile.
Variabili puntatore possono apparire insieme ad altre variabili nella dichiarazione:
Il C richiede che ogni puntatore punti solo ad oggetti del tipo specificato nella dichiarazione (detto il tipo
referenziato dal puntatore):
Il tipo referenziato può essere qualsiasi tipo.
Il C fornisce una coppia di operatori specifici per i puntatori:
(l’asterisco della dichiarazione NON è un operatore di ridirezione).
La dichiarazione di una variabile puntatore crea la variabile puntatore senza che essa punti ad un oggetto
Per utilizzare il puntatore p dobbiamo prima assegnargli un valore
Un modo per assegnare un indirizzo ad un puntatore sfrutta l’operatore indirizzo:
Assegnare a p l’indirizzo di i significa far puntare p ad i.
È possibile inizializzare un puntatore nel momento della dichiarazione:
La dichiarazione di i può essere combinata con quella di p:
Una volta che un puntatore punta ad un oggetto è possibile usare l’operatore ridirezione * per accedere a
ciò che è memorizzato nell’oggetto. Se p punta ad i, possiamo stampare il valore di i in questo modo:
Applicando & ad una variabile si ottiene l’indirizzo della variabile; applicando * ad un puntatore si ottiene il
valore della variabile:
Quando p punta ad i, *p è un sinonimo (alias) di i, cioè: *p ha lo stesso valore di i, quindi cambiando il
valore di *p si cambia il valore i.
Puntatori come argomenti:
In generale, una funzione non può modificare i suoi argomenti, perché gli argomenti vengono passati per
valore. Se però passiamo un puntatore alla variabile invece della variabile, allora sarà il puntatore ad essere
passato per valore, ma grazie al puntatore potremo modificare la variabile.
Puntatori come valore di ritorno:
Una funzione può restituire un puntatore:
Ecco una chiamata alla funzione max:
Dopo la chiamata, p punta o ad i oppure a j. La funzione max restituisce uno dei due puntatori che sono
stati forniti come argomenti.
Una funizone può restiture anche un puntatore ad altre variabili (una variabile globale o una variabile
statica). Attenzione a non restituire un puntatore ad una variabile locale:
La variabile i non esisterà quando f termina. Un puntatore può puntare ad elementi di un array: se a è un
array, &a[i] è un puntatore all’elemento i di a.
A volte è utile che una funzione restituisca il puntatore ad un elemento di un array. Ecco una funzione che
restituisce un puntatore all’elemento centrale dell’array a, assumendo che a abbia n elementi:
Se p è il puntatore all’elemento iniziale di un array A, e k è un intero, allora p + k è per definizione l’indirizzo
della componente k di A, cioè &A[k].
Quando si somma un intero k a un puntatore p, si ottiene l’indirizzo che avrebbe la componente di indice k
di un array allocato all’indirizzo p. E’ anche possibile sottrarre un intero da un puntatore, col significato
inverso.
Aritmetica dei puntatori:
Il C permette di eseguire addizioni e sottrazioni con i puntatori. Questo permette di gestire gli array usando
i puntatori al posto degli indici. L’interconnessione fra puntatori ed array in C è molto stretta: capire questa
interconnessione è di fondamentale importanza per la programmazione in C.
Abbiamo visto che un puntatore può puntare a elementi di un array:
possiamo accedere ad a[0] usando p; per esempio possiamo memorizzare il valore 5 in a[0] scrivendo
Se p punta ad un elemento di un array a, è possibile accedere agli altri elementi di a usando un’operazione
aritmetica su p. Si possono effettuare queste (e solo queste) operazioni aritmetiche sui puntatori:
posizioni dopo l’elemento attualmente puntato da p. Più precisamente, se p punta all’elemento
a[i], allora p + j punta all’elemento a[i+j]. Si assuma di aver fatto le seguenti dichiarazione:
equivalente all’espressione (a + i), quindi, &a[i][0] è equivalente a &((a[i] + 0)), che è equivalente a
&*a[i], e quest’ultima espressione è uguale a a[i], poiché gli operatori & * si cancellano.
Esistono due tipi di stringhe: le stringhe costanti (o letterali, per usare il gergo del C) e le stringhe variabili
(cioè variabili il cui valore è una stringa).
Le stringhe sono array di caratteri; un carattere speciale—il carattere nullo—segnala la fine della stringa.
La libreria standard del C fornisce molte funzioni per manipolare le stringhe.
Stringhe letterali:
Una stringa letterale è una sequenza di caratteri racchiusi fra doppi apici; le stringhe letterali possono
anche contenere sequenze di escape.
Tali sequenze vengono usate spesso nelle stringhe di formato per printf e scanf.
Quando il compilatore trova una stringa letterale di lunghezza n, alloca n + 1 byte di memoria per la stringa.
Tale memoria conterrà gli n caratteri della stringa più un carattere aggiuntivo—il carattere nullo—che
segnala la fine della stringa. Il carattere nullo ha codice ASCII 0 ed è rappresentato dalla sequenza di escape
Poiché una stringa è memorizzata in un array di caratteri, il compilatore la vede come char *. Sia printf che
scanf si aspettano valori di tipo char * per il primo argomento.
La seguente chiamata a printf passa l’indirizzo di "abc" (un puntatore alla locazione di memoria dove è
memorizzato il carattere a):
Possiamo usare una stringa letterale dovunque sia permesso usare un char *. È possibile usare un indice:
Il valore di ch sarà il carattere b.
Non è possibile modificare una stringa letterale: un programma che cerca di cambiare una stringa letterale
può andare in crash o avere comportamenti non definiti.
Una stringa letterale che contiene un solo carattere è diversa da una costante “carattere”
Stringhe variabili:
Un array monodimensionale di caratteri può essere usato per memorizzare una stringa: una stringa deve
essere terminata dal carattere nullo. Le difficoltà di questo approccio sono:
Se una stringa può essere lunga 80 caratteri, deve essere dichiarata come array di 81 elementi:
La locazione aggiuntiva serve per il carattere di fine stringa. Usare una macro che definisce la lunghezza
della stringa e poi aggiungere il +1 nella dichiarazione è una pratica comune.
Ricordarsi del carattere di fine stringa nella dichiarazione di una stringa variabile. Dimenticarsi di farlo può
creare problemi.
La dimensione dell’array non è la lunghezza della stringa e la lunghezza della stringa dipende dalla posizione
nell’array del carattere di fine stringa. Un array di lunghezza STR_LEN + 1 può contenere stringhe di
lunghezza fra 0 e STR_LEN.
Una stringa variabile può essere inizializzata al momento della dichiarazione; esempio:
Il compilatore allocherà automaticamente 8 byte per l’array ed inserirà il carattere di fine stringa. In questo
contesto,"June 14" non è una stringa letterale ma un’abbreviazione dell’inizializzatore per l’array. Se
l’inizializzatore ha meno caratteri della grandezza dell’array, il compilatore riempie il resto con caratteri
nulli.
La dichiarazione 𝑐ℎ𝑎𝑟 𝑑𝑎𝑡𝑒[] = "𝐽𝑢𝑛𝑒 14"; dichiara date come un array; la dichiarazione 𝑐ℎ𝑎𝑟 ∗ 𝑑𝑎𝑡𝑒 =
"𝐽𝑢𝑛𝑒 14"; dichiara date come un puntatore. Entrambe le variabili possono essere usate come stringhe,
poiché array e puntatori sono in stretta relazione.
Tuttavia ci sono differenze importanti fra le due versioni della variabile date: nella versione “array”, i
caratteri memorizzati nella variabile date possono essere modificati e “date” è il nome dell’array; nella
versione “puntatore”, date punta ad una stringa letterale che non può essere modificata e “date” è una
variabile che punta ad una stringa.
La dichiarazione 𝑐ℎ𝑎𝑟 ∗ 𝑝; non alloca memoria. Prima di poter usare p come una stringa, dobbiamo farla
puntare ad un array: una possibilità è quella di farla puntare ad una stringa variabile:
𝐿𝐸𝑁
Un’altra possibilità è quella di allocare dinamicamente la memoria necessaria.
Usare un puntatore non inizializzato può avere conseguenze disastrose: poiché la variabile p non è stata
inizializzata il comportamento del programma non è definito.
Leggere e scrivere stringhe:
Una stringa può essere stampata usando printf o puts.
Leggere una stringa è un po’ più complicato perché l’input potrebbe essere più lungo della lunghezza
dell’array dove la stringa verrà memorizzata; per leggere una stringa intera possiamo usare scanf o gets,
oppure possiamo leggere un carattere alla volta.
La specifica di conversione %s permette di usare una stringa con printf :
Questa istruzione confronta str1 e str2 come puntatori: poiché str1 e str2 puntano ad indirizzi diversi,
l’espressione str1 == str2 avrà valore 0.
La libreria C fornisce un ricco insieme di funzioni per operare sulle stringhe. I programmi che vogliono
utilizzare le funzioni della libreria devono includere il file string.h:
Negli esempi che seguono assumeremo che str1 e str2 siano array di caratteri usati come stringhe.
strcpy copia la stringa s2 nella stringa s1.
Per essere precisi dovremmo dire “strcpy copia la stringa puntata da s2 nell’array puntato da s1.”
Strcpy restituisce il valore di s1 (un puntatore alla stringa di destinazione).
Nella chiamata strcpy(str1, str2), strcpy non ha modo di controllare che ci sia abbastanza spazio per
copiare str2 nell’array puntato da str1: se non c’è abbastanza spazio il comportamento del
programma non è definite.
Ha un terzo argomento che specifica la grandezza dell’array di destinazione. Una chiamata strncpy
che copia str2 in str1:
strncpy non scriverà il carattere nullo alla fine di str1 se la lunghezza di str2 è maggiore della
lunghezza dell’array str1. Ecco come ovviare al problema:
L’istruzione dopo la chiamata alla funzione scrive in modo esplicito il carattere nullo nell’ultima
posizione dell’array str.
dove size_t è un nome typedef che rappresenta un intero senza segno. Strlen restituisce la
lunghezza della stringa s, senza contare il carattere nullo di fine stringa.
strcat aggiunge il contenuto della stringa s2 alla (fine della) stringa s1; restituisce un il valore di s1 (il
puntatore alla stringa risultato della concatenazione).
Il comportamento del programma non è definito se strcat(str1, str2) non ha abbastanza spazio in
str1 per inserire anche i caratteri di str2.
specifica il numero massimo di caratteri che può copiare.
Ecco una chiamata a strncat:
strncat terminerà str1 con un carattere nullo, che dobbiamo considerare quando calcoliamo il terzo
argomento.
strcmp confronta la stringa s1 con s2, e restituisce un valore :
Usando il giusto operatore (<, <=, >, >=, ==, !=), possiamo controllare tutte le relazioni fra str1 e
str2.
strcmp considera l’ordine lessicografico: s1 è minore di s2 se una delle seguenti condizioni è
soddisfatta: i primi i caratteri di s1 e s2 sono uguali, ma il carattere (i+1)-esimo di s1 è minore
del carattere (i+1)-esimo di s2.
Tutti i caratterei di s1 sono uguali a quelli di s2, ma s1 è più corta di s2.
La relazione di ‘<’ fra caratteri è determinta dal codice ASCII del carattere. Nella codifica ASCII
A–Z, a–z, e 0–9 hanno codici consecutivi
della lunghezza della stringa:
Copiare una stringa:
La copia di una stringa è un’operazione molto comune. Per vedere i modi tipici in cui si copia una stringa in
C, svilupperemo due versioni della funzione strcat, la prima versione di strcat (prossima slide) opera nel
seguente modo: trova il carattere nullo di fine stringa nella prima stringa s1 e fa puntare p ad esso e poi
copia i caratteri di s2 scrivendoli a partire dal punto in cui punta p.
promemoria al giorno;
Una complicazione: giustificare a destra il numero del giorno in un campo di 2 caratteri. Una soluzione:
usare scanf per leggere il giorno in una variabile di tipo int e poi usare sprintf per riconvertirla in una stringa
sprintf è simile a printf, ma al posto di stampare scrive il risultato in una stringa. La chiamata
𝑠𝑝𝑟𝑖𝑛𝑡𝑓(𝑑𝑎𝑦_𝑠𝑡𝑟, "%2𝑑", 𝑑𝑎𝑦); scrive il valore di day in day_str.
La seguente chiamata a scanf assicura che non ci siano più di 2 cifre nel giorno dato in input:
Array di stringhe:
Esistono vari modi per memorizzare un array di stringhe. Una possibilità è quella di usare un array bi-
dimensionale, e memorizzare una stringa in ogni riga
Il numero di righe nell’array può essere omesso, ma occorre specificare il numero di colonne.
L’array planets contiene vari byte non utilizzati:
Spesso quando dobbiamo gestire molte stringhe ci troveremo in questa situazione: alcune stringhe più
lunghe altre più corte.
Potremmo usare un array irregolare (ragged array) in cui le righe possono avere lunghezze diverse, oppure
possiamo “simulare” un tale array utilizzando un array di puntatori alle stringhe:
Ecco come verranno memorizzate le stringhe planets:
Per accedere ad una delle stringhe dobbiamo usare l’indice per selezionare il puntatore dell’array planets.
Dopo di che possiamo accedere ai caratteri della stringa come si fa con un normale array bidimensionale.
Ecco un ciclo che cerca le stringhe che iniziano per M: