Tracce
/
C++
C++
/
Programma
/
Puntatori
Pu

Puntatori in C++

1 esercizio

Informazioni su Puntatori

Come molti altri linguaggi, il C++ ha i puntatori. Sai già che riferimenti e puntatori sono simili, ma pensali come un livello più vicino al funzionamento interno del computer. I puntatori sono variabili che contengono indirizzi di oggetti. Si usano per interagire direttamente con gli oggetti, consentendo l'allocazione dinamica della memoria e una manipolazione efficiente dei dati in C++.

Se è la prima volta che incontri i puntatori, possono sembrare un po' misteriosi, ma una volta che ci prendi la mano sono piuttosto semplici.

Sono una parte cruciale del C++, quindi prenditi il tempo necessario per capirli davvero. La versione essenziale presentata in questo concetto è anche chiamata puntatore stupido o puntatore grezzo. Nel C++ moderno esistono anche i puntatori intelligenti; il tipo di base non è affatto intelligente e devi gestire tutto il lavoro manualmente.

Prima di addentrarci nei dettagli, vale la pena capire a cosa servono i puntatori. I puntatori sono un modo per condividere l'indirizzo di un oggetto con altre parti del programma, il che è utile per due motivi principali:

  1. Come i riferimenti, i puntatori evitano le copie e aiutano a ridurre il consumo di risorse del programma.
  2. A differenza dei riferimenti, i puntatori possono essere riassegnati ad altri oggetti.
  3. I puntatori possono anche puntare a un valore nullo, per indicare che al momento non puntano a nessun oggetto.

Sintassi generale

La dichiarazione di un puntatore in C++ prevede di specificare il tipo di dato a cui il puntatore punta, seguito da un asterisco (*) e dal nome del puntatore. Quando si dichiarano i puntatori, questi non vengono inizializzati automaticamente. Senza un'assegnazione esplicita, un puntatore contiene in genere un valore indeterminato, spesso indicato come «indirizzo spazzatura». Anche se alcuni compilatori potrebbero inizializzare i puntatori a nullptr, questo comportamento non è garantito su tutti i compilatori, quindi è essenziale non farci affidamento. La buona pratica è inizializzare esplicitamente i puntatori grezzi e verificarne lo stato non nullo prima di usarli, per evitare potenziali problemi.

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

Per assegnare l'indirizzo di una variabile a un puntatore, si usa l'operatore di indirizzo (&). La dereferenziazione di un puntatore avviene tramite l'operatore di indirezione (*).

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

Attenzione: la dereferenziazione va fatta esplicitamente, mentre i riferimenti funzionano semplicemente come un alias.

Aritmetica dei puntatori

L'aritmetica dei puntatori permette di eseguire operazioni aritmetiche sui puntatori, cosa particolarmente utile quando si lavora con gli array. Aggiungere un numero intero a un puntatore lo fa puntare a un elemento diverso.

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

L'aritmetica dei puntatori in C++ può facilmente portare a un comportamento indefinito se non viene gestita con attenzione. Il comportamento indefinito può manifestarsi in risultati inattesi del programma, arresti anomali o persino vulnerabilità di sicurezza. Un famoso esempio delle conseguenze del comportamento indefinito si è verificato nel 1996 con l'esplosione del razzo Ariane 5, dove un'eccezione software causata dalla conversione di un numero in virgola mobile a 64 bit in un intero con segno a 16 bit portò a un guasto catastrofico.

Accesso alle variabili membro

In C++, l'operatore -> serve ad accedere ai membri di un oggetto tramite un puntatore a quell'oggetto. È una scorciatoia che semplifica l'accesso ai membri degli oggetti puntati dai puntatori. Per esempio, se ptr è un puntatore a un oggetto con una variabile membro x, invece di usare (*ptr).x puoi usare direttamente ptr->x. Questo operatore migliora la leggibilità del codice e riduce la verbosità quando si lavora con puntatori a oggetti.

Ecco un breve esempio, con una struct Superhero che ha una variabile membro superpower. La funzione main crea un puntatore dianaPrince a un oggetto Superhero (che rappresenta Wonder Woman). L'operatore -> serve ad accedere alla variabile membro superpower, mostrando il famoso «Lasso of Truth» di Wonder Woman.

struct Superhero {
    std::string superpower;
};

Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";

// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

// Memory cleanup:
delete dianaPrince;

Puntatori e riferimenti a confronto

Sia i puntatori che i riferimenti consentono un accesso indiretto agli oggetti, ma differiscono per capacità e considerazioni sulla sicurezza. I puntatori offrono la flessibilità di cambiare l'oggetto a cui puntano e possono essere impostati a null. Tuttavia, questa flessibilità introduce dei rischi, come dereferenziare puntatori nulli o creare puntatori penzolanti. I riferimenti, invece, non possono essere nulli e sono legati a oggetti validi al momento della creazione, evitando questi rischi. Data la loro natura più sicura, i riferimenti dovrebbero essere preferiti ai puntatori, a meno che non siano necessarie le funzionalità aggiuntive offerte dai puntatori.

Advanced

Allocazione dinamica della memoria e puntatori nulli

In C++, new serve ad allocare dinamicamente memoria per gli oggetti sullo heap durante l'esecuzione. Restituisce un puntatore all'indirizzo di memoria allocato, permettendo agli oggetti di avere una durata che non è legata a uno scope specifico. Tuttavia, è importante abbinare sempre new a delete per deallocare correttamente la memoria. L'allocazione dinamica della memoria va usata con cautela per evitare problemi comuni come le perdite di memoria e l'accesso a memoria deallocata.

I puntatori possono anche essere impostati a un valore speciale chiamato puntatore nullo, che indica che non puntano a un indirizzo valido. I puntatori nulli sono comunemente usati per inizializzare i puntatori prima di assegnarli a valori validi.

// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;

Un puntatore dovrebbe contenere l'indirizzo di un oggetto valido oppure essere impostato a nullptr. Così devi solo verificare se un puntatore è nullo e puoi presupporre che ogni puntatore non nullo sia valido. Quando un oggetto viene distrutto, tutti i puntatori a quell'oggetto restano penzolanti (non vengono impostati automaticamente a nullptr). È tua responsabilità individuare questi casi e assicurarti che quei puntatori vengano poi impostati a nullptr.

Nel codice più vecchio potresti incontrare due alternative a nullptr. In primo luogo, il letterale 0 è interpretato specificamente come valore nullo per i puntatori, anche se è l'unico caso in cui un letterale intero può essere assegnato a un puntatore. In secondo luogo, la preprocessor macro NULL, ereditata dal C e definita nell'header <cstddef>, è un'altra rappresentazione di un puntatore nullo, anche se il suo uso è meno comune nel codice C++ moderno.

Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Puntatori

La pratica è bloccata

Sblocca un altro esercizio per esercitarti su Puntatori