← Programmazione
C++ · File

Gestione File

File di testo, file binari e file di record in C++ — dalla scrittura con ofstream all'accesso diretto con seekg(), con gestione degli errori e visualizzatori interattivi.

🎓 4° anno ITIS/SIA 📂 Persistenza dei dati ⏰ ~50 min 🔧 2 visualizzatori

Perché i file?

Tutto ciò che si memorizza in variabili, array o struct esiste solo durante l'esecuzione del programma. Appena il programma termina, quei dati spariscono. I file risolvono il problema della persistenza: scrivere dati su disco significa conservarli tra un'esecuzione e la successiva, condividerli con altri programmi o stamparli.

📌 Due categorie di file in C++ File di testo — contenuto leggibile con qualsiasi editor, ogni dato è convertito in caratteri ASCII. Adatti per log, configurazioni, scambio di dati semplici.

File binari — contenuto scritto byte per byte, identico alla rappresentazione in memoria. Più compatti e veloci da leggere/scrivere, non leggibili direttamente. Adatti per struct, array numerici, dati strutturati.

Le tre classi stream

ClasseUsoInclude
ofstreamScrittura su file (output)<fstream>
ifstreamLettura da file (input)<fstream>
fstreamLettura e scrittura (entrambe)<fstream>

Modalità di apertura

FlagSignificatoDefault per
ios::inApre in letturaifstream
ios::outApre in scrittura (sovrascrive)ofstream
ios::appAggiunge in fondo senza sovrascrivere
ios::truncSvuota il file all'aperturaofstream
ios::binaryModalità binaria (nessuna conversione)

File di testo

In un file di testo ogni valore viene convertito nella sua rappresentazione a caratteri prima di essere scritto. L'operatore << funziona esattamente come con cout, e >> come con cin.

Scrittura e lettura — esempio base

C++ · file_testo_base.cpp
#include <iostream>
#include <fstream>
using namespace std;

int main() {
    // scrittura: ofstream crea il file se non esiste
    ofstream out("dati.txt");
    out << "Mario Rossi" << endl;
    out << 7.5 << endl;
    out.close();

    // lettura: ifstream apre il file esistente
    ifstream in("dati.txt");
    string  nome;
    float   media;
    getline(in, nome);   // legge una riga intera
    in >> media;         // legge il valore numerico
    in.close();

    cout << nome << " | " << media << endl;
    return 0;
}

Gestione degli errori e pattern completo

Aprire un file può fallire: file inesistente, permessi insufficienti, disco pieno. Il metodo is_open() verifica se l'apertura è riuscita — va sempre controllato prima di usare lo stream.

C++ · file_testo_completo.cpp
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    // ── SCRITTURA ──────────────────────────────────────
    ofstream out("voti.txt");
    if (!out.is_open()) {
        cerr << "Errore: impossibile creare il file." << endl;
        return 1;
    }
    out << "Anna Bianchi" << endl << 8 << endl;
    out << "Mario Rossi"  << endl << 7 << endl;
    out << "Luca Verdi"   << endl << 9 << endl;
    out.close();

    // ── LETTURA RIGA PER RIGA ──────────────────────────
    ifstream in("voti.txt");
    if (!in.is_open()) {
        cerr << "Errore: file non trovato." << endl;
        return 1;
    }
    string nome;
    int    voto;
    while (getline(in, nome)) {         // legge il nome
        if (!(in >> voto)) break;       // legge il voto
        in.ignore();                    // NECESSARIO: scarta il '\n' lasciato da operator>> dopo il numero, altrimenti il getline successivo leggerebbe una riga vuota
        cout << nome << ": " << voto << endl;
    }
    in.close();
    return 0;
}

Modalità append — aggiungere senza sovrascrivere

C++ · append
// ios::app posiziona il cursore in fondo prima di ogni scrittura
ofstream out("voti.txt", ios::app);
if (!out.is_open()) { cerr << "Errore apertura." << endl; return 1; }
out << "Sara Neri" << endl << 9 << endl;
out.close();
cout << "Dati aggiunti con successo." << endl;
// il file ora contiene i dati precedenti + Sara Neri

🔧 Simulatore File di Testo

Aggiungi righe al file simulato, scegli se sovrascrivere o aggiungere in fondo, poi simula la lettura con getline().

🔧 Simulatore File di Testo ofstream · ifstream · getline()
📄 dati.txt (su disco)
File vuoto
👁 Lettura con getline()
Premi Leggi per simulare ifstream
Scrivi del testo e premi Scrivi o Append.

File binari

In un file binario i dati vengono scritti byte per byte esattamente come sono in memoria, senza alcuna conversione. Un int occupa sempre 4 byte, un float sempre 4 byte. Risultato: file più compatti, lettura/scrittura più veloci, contenuto non leggibile con un editor.

⚠ Portabilità I file binari dipendono dall'architettura della macchina (endianness, dimensione dei tipi). Un file scritto su un sistema a 64 bit potrebbe non essere leggibile correttamente su un sistema a 32 bit con una struct diversamente allineata. Per scambio di dati tra sistemi diversi si preferisce il formato testo.

Scrittura binaria — esempio base

C++ · binario_base.cpp
#include <fstream>
using namespace std;

int main() {
    int dati[] = {10, 25, 7, 42};

    // scrittura binaria: ios::binary disabilita le conversioni
    ofstream out("numeri.bin", ios::binary);
    out.write(reinterpret_cast<const char*>(dati), sizeof(dati));
    out.close();

    // lettura binaria
    int letti[4];
    ifstream in("numeri.bin", ios::binary);
    in.read(reinterpret_cast<char*>(letti), sizeof(letti));
    in.close();
    return 0;
}

Con gestione degli errori

C++ · binario_completo.cpp
#include <iostream>
#include <fstream>
using namespace std;

int main() {
    int dati[] = {10, 25, 7, 42};
    const int N = sizeof(dati) / sizeof(dati[0]);

    // ── SCRITTURA ──────────────────────────────────────
    ofstream out("numeri.bin", ios::binary);
    if (!out.is_open()) {
        cerr << "Errore: impossibile aprire in scrittura." << endl;
        return 1;
    }
    out.write(reinterpret_cast<const char*>(dati), sizeof(dati));
    if (!out.good()) {
        cerr << "Errore durante la scrittura." << endl;
        return 1;
    }
    out.close();

    // ── LETTURA ────────────────────────────────────────
    int letti[N];
    ifstream in("numeri.bin", ios::binary);
    if (!in.is_open()) {
        cerr << "Errore: file non trovato." << endl;
        return 1;
    }
    in.read(reinterpret_cast<char*>(letti), sizeof(letti));
    if (in) {   // lettura riuscita: lo stream è ancora in stato valido
        for (int i = 0; i < N; i++)
            cout << letti[i] << " ";
        cout << endl;
    } else {
        cerr << "Errore durante la lettura del file binario." << endl;
        return 1;
    }
    in.close();
    return 0;
}
💡 reinterpret_cast<char*> — perché? write() e read() lavorano su sequenze di char (byte grezzi). reinterpret_cast dice al compilatore di trattare il puntatore al nostro dato come un puntatore a byte, senza modificarne il contenuto. È il modo corretto per accedere alla rappresentazione binaria di qualsiasi tipo.

File di record

Il caso d'uso più comune in ambito SIA: salvare su file un intero array di struct e rileggerlo in un'altra esecuzione. Ogni struct è un record a dimensione fissa, il che permette l'accesso diretto per posizione senza leggere tutto il file.

Scrittura e lettura sequenziale

C++ · record_base.cpp
#include <fstream>
using namespace std;

typedef struct { int matricola; char nome[30]; float media; } Studente;

int main() {
    Studente classe[3] = {
        {101, "Anna Bianchi", 8.5f},
        {102, "Mario Rossi",  7.2f},
        {103, "Luca Verdi",   9.1f}
    };

    // scrittura: ogni struct è scritta come blocco di byte
    ofstream out("studenti.dat", ios::binary);
    for (int i = 0; i < 3; i++)
        out.write(reinterpret_cast<const char*>(&classe[i]), sizeof(Studente));
    out.close();

    // lettura sequenziale
    Studente s;
    ifstream in("studenti.dat", ios::binary);
    while (in.read(reinterpret_cast<char*>(&s), sizeof(Studente)))
        cout << s.matricola << " " << s.nome << " " << s.media << endl;
    in.close();
    return 0;
}

Accesso diretto per posizione

Poiché ogni record ha dimensione fissa (sizeof(Studente) byte), l'offset del record di indice n è esattamente n × sizeof(Studente) byte dall'inizio del file. Con seekg() si salta direttamente a quella posizione senza leggere tutto il file — O(1) invece di O(n).

C++ · accesso_diretto.cpp
#include <iostream>
#include <fstream>
using namespace std;

typedef struct { int matricola; char nome[30]; float media; } Studente;

// legge il record in posizione n (indice base 0)
bool leggiRecord(ifstream& in, int n, Studente& s) {
    in.clear();                                          // resetta eventuali flag di errore (es. EOF da lettura precedente)
    in.seekg((long)n * sizeof(Studente), ios::beg);
    in.read(reinterpret_cast<char*>(&s), sizeof(Studente));
    return (bool)in;                                       // vero se la lettura è riuscita
}

// modifica il record in posizione n
bool modificaRecord(fstream& f, int n, const Studente& s) {
    f.clear();                                            // resetta eventuali flag di errore prima del seek
    f.seekp((long)n * sizeof(Studente), ios::beg);
    f.write(reinterpret_cast<const char*>(&s), sizeof(Studente));
    return f.good();  // good() è vero solo se lo stream non ha errori E non è EOF: più rigoroso di (bool)stream
}

int main() {
    // lettura diretta del secondo record (indice 1)
    Studente s;
    ifstream in("studenti.dat", ios::binary);
    if (!in.is_open()) { cerr << "File non trovato." << endl; return 1; }

    if (leggiRecord(in, 1, s))
        cout << s.nome << " | " << s.media << endl;  // Mario Rossi | 7.2
    in.close();

    // modifica del terzo record (indice 2) con fstream
    fstream f("studenti.dat", ios::binary | ios::in | ios::out);
    if (!f.is_open()) { cerr << "Errore apertura." << endl; return 1; }
    Studente nuovo = {103, "Luca Verdi", 9.8f};  // aggiorno la media
    modificaRecord(f, 2, nuovo);
    f.close();
    return 0;
}

🔧 Visualizzatore Record

Aggiungi record, poi usa l'accesso diretto per selezionarne uno per indice. Il visualizzatore mostra l'offset in byte calcolato con n × sizeof(Studente).

🔧 Visualizzatore Record Accesso diretto · seekg(n × sizeof(Studente), ios::beg)
Indice Matricola Nome Media Offset nel file
Nessun record — aggiungi il primo
Leggi record [ ]
Aggiungi record per iniziare.
📌 sizeof(Studente) — un valore dipendente dal compilatore Per la struct {int matricola; char nome[30]; float media;}: int = 4 byte, char[30] = 30 byte, float = 4 byte, più eventuali 2 byte di padding per allineare al confine di 4 byte. Risultato tipico: 40 byte. Il visualizzatore usa questo valore. Nella pratica, usare sempre sizeof(Studente) e mai un numero fisso.