Puntatori e Memoria Dinamica in C++

Gestisci la memoria in modo efficiente ed evita memory leak

I puntatori e la gestione dinamica della memoria sono concetti avanzati ma fondamentali in C++ per scrivere programmi efficienti e flessibili.

1. Puntatori e Riferimenti

📌 Cosa sono i puntatori?
Variabili che contengono indirizzi di memoria di altre variabili. Permettono di manipolare direttamente la memoria.

1.1 Dichiarazione e Operatori

*
Dereferenziazione

Accede al valore puntato

*ptr

&
Indirizzo

Restituisce l’indirizzo di una variabile

&variabile


Freccia

Accede a membri tramite puntatore

ptr->membro

#include <iostream>
using namespace std;
int main() {
int variabile = 10;
int *ptr = &variabile; // ptr contiene l’indirizzo di variabile
cout << “Valore di variabile: “ << variabile << endl;
cout << “Indirizzo di variabile: “ << &variabile << endl;
cout << “Valore memorizzato in ptr: “ << ptr << endl;
cout << “Valore puntato da ptr: “ << *ptr << endl;
// Modifica tramite puntatore
*ptr = 20;
cout << “Nuovo valore di variabile: “ << variabile << endl;
return 0;
}

1.2 Differenza tra Puntatori e Riferimenti

P
Puntatori

Riassegnabili:

Nullabile: Sì (nullptr)

Sintassi: *ptr, &variabile

int *ptr = &x;

R
Riferimenti

Riassegnabili: No

Nullabile: No

Sintassi: int &ref = x;

int &ref = x;

#include <iostream>
using namespace std;
int main() {
int variabile = 20;
int &riferimento = variabile; // riferimento a variabile
cout << “Valore di variabile: “ << variabile << endl;
cout << “Valore tramite riferimento: “ << riferimento << endl;
riferimento = 30; // modifica anche la variabile originale
cout << “Nuovo valore di variabile: “ << variabile << endl;
// I riferimenti non possono essere riassegnati
int altraVariabile = 40;
// riferimento = altraVariabile; // Questo modifica il valore, non il riferimento
return 0;
}

2. Allocazione Dinamica della Memoria

📌 Perché usare l’allocazione dinamica?
Quando non conosci a priori la quantità di memoria necessaria durante la compilazione.

2.1 Operatori new e delete

new – Allocazione

Singolo elemento: new tipo

Array: new tipo[dimensione]

int *ptr = new int;

delete – Deallocazione

Singolo elemento: delete ptr

Array: delete[] array

delete ptr;

2.2 Esempio: Allocazione Singolo Elemento

#include <iostream>
using namespace std;
int main() {
// Allocazione dinamica di un singolo intero
int *ptr = new int;
// Assegnazione del valore
*ptr = 42;
cout << “Valore allocato dinamicamente: “ << *ptr << endl;
cout << “Indirizzo di memoria: “ << ptr << endl;
// DEVE essere deallocato!
delete ptr;
ptr = nullptr; // Buona pratica
return 0;
}

2.3 Esempio: Array Dinamico

#include <iostream>
using namespace std;
int main() {
int dimensione;
cout << “Inserisci la dimensione dell’array: “;
cin >> dimensione;
// Allocazione dinamica dell’array
int *array = new int[dimensione];
// Inizializzazione
for (int i = 0; i < dimensione; i++) {
array[i] = i * 2;
}
cout << “Contenuto dell’array:” << endl;
for (int i = 0; i < dimensione; i++) {
cout << “Elemento “ << i << “: “ << array[i] << endl;
}
// DEVE essere deallocato con delete[]
delete[] array;
array = nullptr;
return 0;
}

⚠️ ATTENZIONE: Usare delete per array o delete[] per singoli elementi causa comportamenti indefiniti!

3. Best Practices

Regole fondamentali:

✅ Inizializza sempre

Assicurati che ogni puntatore sia inizializzato (preferibilmente a nullptr)

✅ Dealloca sempre

Usa delete o delete[] per liberare la memoria non più necessaria

✅ Evita doppia delete

Deallocare la stessa memoria più volte causa comportamenti indefiniti

✅ Usa riferimenti quando possibile

Più semplici e meno soggetti a errori rispetto ai puntatori

✅ Smart Pointer (C++11+)

Preferisci std::unique_ptr e std::shared_ptr per gestione automatica

3.1 Esempio: Gestione Sicura

#include <iostream>
using namespace std;
int main() {
int *ptr = nullptr; // Inizializzazione sicura
// Allocazione con controllo
ptr = new int;
if (ptr == nullptr) {
cout << “Errore di allocazione!” << endl;
return -1;
}
*ptr = 100;
cout << “Valore: “ << *ptr << endl;
// Deallocazione sicura
if (ptr != nullptr) {
delete ptr;
ptr = nullptr; // Importante!
}
return 0;
}

🎯 Esercizi Pratici

Metti in pratica la gestione dinamica della memoria

Esercizio 1: Gestione Array Dinamico

Obiettivo: Creare e gestire un array dinamico con input utente.

#include <iostream>
using namespace std;
int main() {
int dimensione;
cout << “Inserisci la dimensione dell’array: “;
cin >> dimensione;
// Allocazione dinamica
int *array = new int[dimensione];
// Inserimento elementi
cout << “Inserisci “ << dimensione << ” numeri interi:” << endl;
for (int i = 0; i < dimensione; i++) {
cout << “Elemento “ << i << “: “;
cin >> array[i];
}
// Calcolo somma e media
int somma = 0;
for (int i = 0; i < dimensione; i++) {
somma += array[i];
}
double media = static_cast<double>(somma) / dimensione;
cout << “Somma: “ << somma << endl;
cout << “Media: “ << media << endl;
// Trova massimo e minimo
int max = array[0], min = array[0];
for (int i = 1; i < dimensione; i++) {
if (array[i] > max) max = array[i];
if (array[i] < min) min = array[i];
}
cout << “Valore massimo: “ << max << endl;
cout << “Valore minimo: “ << min << endl;
// Deallocazione
delete[] array;
array = nullptr;
return 0;
}

Esercizio 2: Scambio con Puntatori

Obiettivo: Scambiare valori usando puntatori.

#include <iostream>
using namespace std;
// Versione con puntatori
void scambiaPuntatori(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// Versione con riferimenti (alternativa)
void scambiaRiferimenti(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
cout << “Prima dello scambio: x = “ << x << “, y = “ << y << endl;
// Scambio con puntatori
scambiaPuntatori(&x, &y);
cout << “Dopo scambio con puntatori: x = “ << x << “, y = “ << y << endl;
// Scambio con riferimenti
scambiaRiferimenti(x, y);
cout << “Dopo scambio con riferimenti: x = “ << x << “, y = “ << y << endl;
return 0;
}

5. Esercizi Avanzati

Esercizio 3: Matrice Dinamica

Obiettivo: Creare e gestire una matrice 2D dinamica.

#include <iostream>
using namespace std;
int main() {
int righe, colonne;
cout << “Inserisci numero di righe: “;
cin >> righe;
cout << “Inserisci numero di colonne: “;
cin >> colonne;
// Allocazione matrice 2D
int **matrice = new int*[righe];
for (int i = 0; i < righe; i++) {
matrice[i] = new int[colonne];
}
// Inizializzazione
int contatore = 1;
for (int i = 0; i < righe; i++) {
for (int j = 0; j < colonne; j++) {
matrice[i][j] = contatore++;
}
}
// Stampa matrice
cout << “\\nMatrice:” << endl;
for (int i = 0; i < righe; i++) {
for (int j = 0; j < colonne; j++) {
cout << matrice[i][j] << “\\t”;
}
cout << endl;
}
// Calcolo somma righe
cout << “\\nSomma per riga:” << endl;
for (int i = 0; i < righe; i++) {
int sommaRiga = 0;
for (int j = 0; j < colonne; j++) {
sommaRiga += matrice[i][j];
}
cout << “Riga “ << i << “: “ << sommaRiga << endl;
}
// Deallocazione (IMPORTANTE: in ordine inverso)
for (int i = 0; i < righe; i++) {
delete[] matrice[i];
}
delete[] matrice;
matrice = nullptr;
return 0;
}

Esercizio 4: Gestione Stringhe Dinamiche

Obiettivo: Manipolare stringhe con allocazione dinamica.

#include <iostream>
#include <cstring>
using namespace std;
char* copiaStringa(const char* originale) {
if (originale == nullptr) return nullptr;
int lunghezza = strlen(originale);
char* copia = new char[lunghezza + 1]; // +1 per il terminatore
strcpy(copia, originale);
return copia;
}
char* concatenaStringhe(const char* str1, const char* str2) {
if (str1 == nullptr || str2 == nullptr) return nullptr;
int len1 = strlen(str1);
int len2 = strlen(str2);
char* risultato = new char[len1 + len2 + 1];
strcpy(risultato, str1);
strcat(risultato, str2);
return risultato;
}
int main() {
const char* originale = “Ciao Mondo”;
// Copia dinamica
char* copia = copiaStringa(originale);
cout << “Stringa originale: “ << originale << endl;
cout << “Copia dinamica: “ << copia << endl;
// Concatenazione dinamica
char* concatenata = concatenaStringhe(“Hello “, “World!”);
cout << “Stringa concatenata: “ << concatenata << endl;
// DEALLOCARE tutto!
delete[] copia;
delete[] concatenata;
return 0;
}

Conclusioni

Punti chiave da ricordare:

✅ Puntatori vs Riferimenti

Puntatori: flessibili ma complessi. Riferimenti: semplici ma vincolati

✅ Allocazione Dinamica

new/delete per memoria flessibile, fondamentale per array di dimensione sconosciuta

✅ Gestione Sicura

Sempre inizializzare, sempre deallocare, usare nullptr, evitare doppie delete

✅ Best Practices

Preferire riferimenti quando possibile, considerare smart pointer in C++ moderno

I puntatori e la gestione dinamica della memoria offrono un controllo granulare sulle risorse del computer. Con una comprensione solida di questi concetti, sarai in grado di scrivere codice più efficiente, modulare e sicuro. Ricorda: con grandi poteri (puntatori) derivano grandi responsabilità (gestione memoria)! 🚀

Torna in alto