# Puntatori ## Indirizzi di memoria delle variabili Ogni variabile memorizzata nella memoria di un computer occupa una specifica posizione chiamata **indirizzo di memoria**. Questo indirizzo è rappresentato come un **numero intero** che identifica univocamente la locazione nella RAM in cui è memorizzata la variabile. ### Cosa significa indirizzo di memoria - Quando dichiariamo una variabile in C++, ad esempio: ```cpp int numero = 42; ``` il compilatore riserva una zona di memoria per la variabile `numero`. Questa zona è identificata da un indirizzo univoco, che possiamo ottenere utilizzando l'**operatore di indirizzo `&`**: ```cpp cout << "Indirizzo di numero: " << &numero << endl; ``` Il risultato sarà un valore esadecimale che rappresenta l'indirizzo. ### Indirizzo e dimensione delle variabili La dimensione di memoria occupata da una variabile dipende dal suo **tipo di dato**. Ad esempio: - Un `int` occupa generalmente 4 byte. - Un `char` occupa 1 byte. - Un `double` occupa 8 byte. L'indirizzo rappresenta il **primo byte** della memoria occupata dalla variabile. --- ## Cosa sono i puntatori Un **puntatore** è una variabile speciale che **contiene l'indirizzo di memoria** di un'altra variabile. In altre parole, mentre le normali variabili memorizzano valori, i puntatori memorizzano indirizzi *(che sono anche loro a tutti gli effetti dei valori)*. ### Tipo e indirizzo dei puntatori ![Stack Variabile Puntatore ++>](/resources/programmazione-c++/immagini/stack-variabile-puntatore.png) Anche un puntatore è una variabile, quindi: - Un puntatore ha un **indirizzo proprio**, che può essere ottenuto con `&`. - Un puntatore ha un **tipo di dato**, che dipende dal tipo della variabile a cui punta: ```cpp int numero = 42; int *ptr = &numero; // 'ptr' è un puntatore a int ``` Nel codice sopra: - `ptr` memorizza l'indirizzo di `numero`. - Il tipo `int *` specifica che `ptr` punta a una variabile di tipo `int`. ### Dimensione dei puntatori La dimensione di un puntatore dipende dall'architettura del sistema: - Su sistemi a 32 bit, un puntatore occupa 4 byte. - Su sistemi a 64 bit, un puntatore occupa 8 byte. Indipendentemente dal tipo di variabile a cui punta, la dimensione del puntatore è sempre la stessa, perché rappresenta un indirizzo nella memoria e non il valore contnuto a quell'indirizzo. --- ### Operazioni principali sui puntatori #### Dichiarazione dei puntatori Essendo una variabile, la dichiarazione di un puntatore segue le regole generali delle altre variabili, con l'aggiunta del simbolo `*` accanto al tipo: ```cpp int *ptr; // Dichiarazione di un puntatore a int ``` Il tipo della variabile include il simbolo `*` perché il puntatore "punta" a una variabile di un certo tipo. Ad esempio: - `int *` indica un puntatore a un intero (int). - `double *` indica un puntatore a un numero in virgola mobile (double). È importante ricordare che un puntatore, essendo una variabile, ha un suo **indirizzo di memoria** e una sua **dimensione**, che dipende dall'architettura del sistema (es. 4 byte su sistemi a 32 bit, 8 byte su sistemi a 64 bit). #### Assegnamento Quando assegniamo un valore a un puntatore, gli stiamo dicendo **quale indirizzo deve contenere**, ovvero dove "puntare". Per farlo, utilizziamo l'**operatore di indirizzo `&`**, che restituisce l'indirizzo di una variabile. ```cpp int numero = 42; // Dichiarazione di una variabile int *ptr = &numero; // Assegniamo a 'ptr' l'indirizzo di 'numero' ``` Qui, il puntatore `ptr` ora contiene l'indirizzo di memoria di `numero`. Questo significa che `ptr` sa dove trovare `numero` nella RAM. Possiamo verificare l'indirizzo: ```cpp cout << "Indirizzo di numero: " << &numero << endl; cout << "Valore di ptr: " << ptr << endl; // Stampa lo stesso indirizzo ``` <ex> ##### Metafora dell'assegnamento Immagina di avere una **casa** (la variabile `numero`) e una **mappa** (il puntatore `ptr`): - La casa ha un indirizzo specifico. - Scrivendo l'indirizzo sulla mappa (`ptr = &numero`), possiamo usarla per trovare la casa. </ex> --- #### Dereferenziazione La dereferenziazione è l'operazione che consente di accedere al **valore memorizzato** all'indirizzo contenuto nel puntatore. Per farlo, si utilizza l'**operatore `*`** davanti al puntatore. ```cpp int numero = 42; int *ptr = &numero; cout << "Valore puntato da ptr: " << *ptr << endl; // Stampa 42 ``` In questo esempio: - `ptr` contiene l'indirizzo di `numero`. - Usando `*ptr`, seguiamo l'indirizzo per accedere al valore di `numero`. La dereferenziazione consente non solo di leggere, ma anche di **modificare** il valore della variabile puntata: ```cpp *ptr = 10; // Modifica il valore di 'numero' tramite il puntatore cout << "Nuovo valore di numero: " << numero << endl; // Stampa 10 ``` <ex> ##### Metafora della dereferenziazione Ritorniamo alla metafora della casa e della mappa: - Se hai una mappa (`ptr`) che punta all'indirizzo della casa (`numero`), puoi usarla per arrivare alla casa. - Una volta arrivato, puoi vedere cosa c'è dentro (leggere il valore con `*ptr`) o sostituire l'arredamento (modificare il valore con `*ptr = ...`). </ex> <im> Una volta che dereferenziaziamo un puntatore, esso può funzionare **esattamente** come la variabile originale alla quale indica. Questo significa che possiamo leggere e modificare il valore della variabile originale tramite il puntatore dereferenziato esattamente come se stessimo lavorando nella variabile originale. </im> --- ### Esempio completo Esempio di Assegnamento e Dereferenziazione ```cpp #include <iostream> using namespace std; int main() { int numero = 5; // Variabile normale int *ptr = &numero; // Puntatore a 'numero' cout << "Indirizzo di numero: " << &numero << endl; cout << "Valore di numero: " << numero << endl; // Usare il puntatore per accedere al valore di 'numero' cout << "Valore puntato da ptr: " << *ptr << endl; // Modificare 'numero' tramite il puntatore *ptr = 20; // Cambia il valore di 'numero' cout << "Nuovo valore di numero: " << numero << endl; return 0; } ``` **Risultato atteso**: ``` Indirizzo di numero: 0x7ffee4a2c4a8 (esempio, varia tra esecuzioni) Valore di numero: 5 Valore puntato da ptr: 5 Nuovo valore di numero: 20 ``` --- ### Errori comuni nell'uso dei puntatori 1. **Puntatori non inizializzati**: Se un puntatore non è inizializzato (contiene "schifo"), punta a un indirizzo casuale: ```cpp int *ptr; // Non inizializzato *ptr = 10; // Errore: accesso a memoria non valida ``` 2. **Dereferenziare puntatori nulli**: Un puntatore nullo (`nullptr`) non può essere dereferenziato: ```cpp int *ptr = nullptr; cout << *ptr; // Errore: crash dell'applicazione ``` **Best practice**: - Inizializzare i puntatori a `nullptr` se non si sa subito cosa devono puntare. - Controllare sempre se un puntatore è nullo prima di dereferenziarlo: ```cpp if (ptr != nullptr) { cout << *ptr << endl; } ``` <ac> **Creare un albero genealogico in c++** Supponiamo di voler rappresentare un albero genealogico in un programma. Di ogni membro vogliamo memorizzare: - Nome - Cognome - Data di nascita - Genitori (padre e madre) Un'idea naturale sarebbe usare una struct per aggregare questi dati. ```cpp #include <iostream> using namespace std; struct Persona { string nome; string cognome; string data_nascita; }; int main() { Persona persona = {"Mario", "Rossi", "01/01/1980"}; cout << "Nome: " << persona.nome << endl; cout << "Cognome: " << persona.cognome << endl; cout << "Data di nascita: " << persona.data_nascita << endl; return 0; } ``` Qui, la struct `Persona` funziona bene per aggregare nome, cognome e data di nascita. Tuttavia, se vogliamo aggiungere i genitori (anche loro di tipo `Persona`), non possiamo scrivere: ```cpp struct Persona { string nome; string cognome; string data_nascita; Persona padre; // Errore! Definizione ricorsiva della struct Persona Persona madre; // Errore! Definizione ricorsiva }; ``` Non possiamo scrivere così perché lo scopo della dichiarazione di una struct `Persona` è quello di dire al compilatore quanto è "grande" un valore di questo tipo. Ma se ogni `Persona` contiene al suo interno altre 2 `Persona` la sua dimensione sarà infinita. Per risolvere il problema, usiamo dei **puntatori** per i genitori; invece di includere direttamente le struct padre e madre, memorizziamo i loro indirizzi (sappiamo che l'indirizzo di una variabile ha una dimensione definita e fissa). Ecco come si modifica la definizione della struct: ```cpp struct Persona { string nome; string cognome; string data_nascita; Persona *padre; // Puntatore alla struct del padre Persona *madre; // Puntatore alla struct della madre }; ``` Ora, possiamo rappresentare i genitori puntando ad altre istanze di Persona. **Ecco l'esempio completo:** ```cpp #include <iostream> using namespace std; struct Persona { string nome; string cognome; string data_nascita; Persona *padre; // Puntatore al padre Persona *madre; // Puntatore alla madre }; int main() { // Creazione dei genitori Persona padre = {"Luigi", "Rossi", "15/03/1955", nullptr, nullptr}; Persona madre = {"Anna", "Bianchi", "20/07/1960", nullptr, nullptr}; // Creazione del figlio Persona figlio = {"Mario", "Rossi", "01/01/1980", &padre, &madre}; // Stampa dei dati del figlio cout << "Figlio: " << figlio.nome << " " << figlio.cognome << endl; cout << "Nato il: " << figlio.data_nascita << endl; // Accesso ai genitori tramite i puntatori if (figlio.padre) { cout << "Padre: " << figlio.padre->nome << " " << figlio.padre->cognome << endl; } if (figlio.madre) { cout << "Madre: " << figlio.madre->nome << " " << figlio.madre->cognome << endl; } return 0; } ``` **Cosa succede nel programma:** - Creiamo il padre (`padre`) e la madre (`madre`) come variabili normali. - Creiamo il figlio (`figlio`) e usiamo i puntatori `&padre` e `&madre` per collegarlo ai genitori. - Per accedere ai dati dei genitori tramite i puntatori, usiamo l'operatore `->` (es. `figlio.padre->nome`). <im> Per accedere ad un attributo di una struct alla quale accediamo da puntatore (come in questo caso per accedere al `padre` o alla `madre` di una `Persona`) si utilizza l'operatore `->` al posto dell'operatore `.`. Quindi al posto di scrivere il seguente codice: ```cpp *(figlio.padre).nome // Mi posiziono dal padre (utilizzando il puntatore) e accedo all'attributo nome ``` si utilizza il seguente codice: ```cpp figlio.padre->nome // Mi posiziono dal padre e accedo all'attributo nome tutto in una operazione ``` </im> </ac>