I puntatori sono una delle caratteristiche fondamentali del linguaggio C e C++. Un puntatore è una variabile che memorizza l’indirizzo di memoria di un’altra variabile. Capire i puntatori significa capire come i dati vengono gestiti in memoria.
1️⃣ Cos’è un puntatore
Ogni variabile in C++ è salvata in una certa posizione di memoria, identificata da un indirizzo. Un puntatore è una variabile che contiene proprio questo indirizzo.
int x = 10; // variabile normale
int* p; // dichiarazione di un puntatore a int
p = &x; // p contiene l indirizzo di x
✅ In questo esempio:
xè una variabile intera con valore 10.&xè l indirizzo di memoria dix.pè un puntatore che contiene&x.
🔍 Perché gli indirizzi iniziano con 0x?
Quando stampiamo il contenuto di un puntatore, il suo valore appare quasi sempre in una forma simile a:
0x61ff0c
0x7ffd32a910
0x0045F230
Il prefisso 0x indica che il numero è scritto in esadecimale (base 16).
Gli indirizzi di memoria non vengono mostrati in base 10 perché sarebbero troppo lunghi e difficili da leggere.
L’esadecimale è una rappresentazione più compatta del binario:
ogni cifra esadecimale rappresenta esattamente 4 bit.
Per questo un indirizzo come 0x61ff0c è molto più leggibile rispetto alla sua forma binaria.
0x61ff0c corrisponde a:
0110 0001 1111 1111 0000 1100 (in binario)
Il prefisso 0x serve quindi solo a dire:
Quando un puntatore contiene un valore come 0x61ff0c, significa che sta memorizzando l’indirizzo di memoria
della variabile a cui punta, espresso in esadecimale.
2️⃣ Operatori & e *
Due operatori sono fondamentali per usare i puntatori:
- & (“address of”) → restituisce l indirizzo di una variabile.
- * (“dereference”) → accede al valore contenuto nell indirizzo puntato.
int x = 10;
int* p = &x; // p punta a x
cout << "Valore di x: " << x << endl;
cout << "Indirizzo di x: " << &x << endl;
cout << "Contenuto di p (indirizzo di x): " << p << endl;
cout << "Valore puntato da p: " << *p << endl;
✅ Output tipico:
Valore di x: 10
Indirizzo di x: 0x61ff0c
Contenuto di p: 0x61ff0c
Valore puntato da p: 10
3️⃣ Puntatori e modifica indiretta
int x = 10;
int* p = &x;
*p = 25; // modifica x attraverso il puntatore
cout << "Nuovo valore di x: " << x;
✅ Output:
Nuovo valore di x: 25
4️⃣ Rappresentazione grafica
| Variabile | Contenuto | Indirizzo |
|---|---|---|
| x | 25 | 0x61ff0c |
| p | 0x61ff0c | 0x61ff08 |
5️⃣ Dichiarazione di più puntatori
int *p1, *p2; // due puntatori a int
float *pf; // puntatore a float
char *pc; // puntatore a char
⚠️ Attenzione: l’asterisco va scritto vicino al tipo (int*), non solo alla variabile, per chiarezza.
Questo è importante perché in una dichiarazione multipla l’asterisco si applica alla singola variabile.
Ad esempio, la dichiarazione int* p1, p2; crea un puntatore (p1) e una variabile intera normale (p2).
Scrivere int *p1, *p2; rende invece chiaro che entrambi sono puntatori.
LABORATORIO
Gestione dei crediti di un giocatore
In questo laboratorio simulerai il sistema dei crediti di un videogioco. Scoprirai come due funzioni diverse possono modificare lo stesso valore grazie all'utilizzo dei puntatori, proprio come avviene nella gestione della vita, energia o denaro nei giochi reali.
- Ambiente di sviluppo C++
- Console per visualizzare output e indirizzi
1 Creiamo la variabile principale
Crea una variabile che rappresenta i crediti del giocatore. Sarà il valore che verrà modificato dalle diverse funzioni del programma.
int crediti = 500;
cout << "Crediti iniziali: " << crediti << endl;
2 Creiamo un puntatore ai crediti
Ora crea un puntatore che memorizzi l'indirizzo di memoria della variabile crediti. Questo puntatore verrà passato alle funzioni che dovranno modificare il suo valore.
int* pCrediti = &crediti;
cout << "Indirizzo di crediti: " << &crediti << endl;
cout << "Contenuto del puntatore: " << pCrediti << endl;
cout << "Valore puntato da pCrediti: " << *pCrediti << endl;
3 Aggiungere un bonus ai crediti
Scrivi una funzione che aggiunge un bonus di 100 crediti. La funzione deve ricevere un puntatore, così da modificare il valore originale e non una copia.
void aggiungiBonus(int* p) {
*p += 100;
}
// Nel main:
aggiungiBonus(pCrediti);
cout << "Dopo il bonus: " << crediti << endl;
4 Applicare una penalità
Ora crea una funzione che sottrae 50 crediti dal valore originale. Anche in questo caso usa un puntatore.
void penalita(int* p) {
*p -= 50;
}
// Nel main:
penalita(pCrediti);
cout << "Dopo la penalità: " << crediti << endl;
5 Domande per la riflessione
- Perché le funzioni riescono a modificare il valore originale?
- Che cosa rappresenta il puntatore pCrediti?
- Perché modificando *p cambia anche la variabile crediti?
- Cosa accadrebbe se le funzioni ricevessero un int normale invece di un puntatore?
6 Variabile globale vs variabile locale con puntatore
In questa sezione vediamo una differenza fondamentale: una variabile globale può essere letta e modificata da qualunque funzione del programma, mentre una variabile locale può essere modificata solo dalle funzioni che ricevono il suo puntatore.
Esempio 1: variabile globale
In questo caso la variabile crediti è visibile ovunque: tutte le funzioni possono modificarla senza permesso.
int crediti = 500; // variabile globale
void A() { crediti += 10; }
void B() { crediti -= 5; }
void C() { cout << crediti; }
Questo è comodo ma pericoloso: qualunque parte del programma può cambiare il valore e diventa difficile capire chi modifica cosa.
Esempio 2: variabile locale + puntatore
Qui la variabile crediti rimane dentro il main.
Le altre funzioni possono modificarla solo se ricevono il puntatore.
void A(int* p) { *p += 10; }
void B(int* p) { *p -= 5; }
int main() {
int crediti = 500; // variabile locale
A(&crediti); // la funzione A può modificarla
B(&crediti); // anche B può modificarla
// Nessun'altra funzione può accedervi
}
In questo modo la variabile non è più visibile a tutti: è il programmatore che decide quali funzioni possono accedervi tramite il puntatore.