Linguaggio C++

Introduzione

Introduzione ai puntatori in C++: cosa sono, come si dichiarano e come funzionano gli operatori & e *.

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
100%

✅ In questo esempio:

  • x è una variabile intera con valore 10.
  • &x è l indirizzo di memoria di x.
  • 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.

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

VariabileContenutoIndirizzo
x250x61ff0c
p0x61ff0c0x61ff08

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.


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.