Vai al contenuto

I puntatori

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: Sì

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)! 🚀

Esercitazioni Pratiche C++

Metti in pratica ciò che hai imparato con queste esercitazioni complete

Tutte le Esercitazioni

📊 Variabili e Tipi di Dato

➕ Operatori Matematici e Logici in C++

🔀 Strutture di controllo condizionali

🔄 I cicli in C++

🗃️ C++ gli array

⚙️ Le funzioni in C++

📈 Ordinamento dei Dati

🏗️ Le struct in C++

🎯 I puntatori

🧩 La Programmazione Orientata agli Oggetti (OOP)

📁 La gestione dei file in C++

💪 Esercizi C++ Raccolta Completa

12 esercitazioni complete per padroneggiare il C++