edutecnica

Files

     

Per definizione, un file è un insieme di dati, organizzato in modo ordinato e memorizzato su memoria di massa la cui gestione è affidata al sistema operativo.

I dati che un computer elabora, non sempre hanno come input la tastiera e non sempre hanno come periferica di output il monitor o la stampante.

I dati possono essere registrati e conservati in memorie di massa, generando così una nuova sorgente di informazione disponibile per altri programmi.

Gli elaboratori di testi, i fogli di calcolo, i database etc..: ciascuno di questi programmi opera su insiemi di dati, denominati file, che nella maggior parte dei casi si trovano su dischi magnetici, ottici o sulle memorie elettroniche delle chiavette USB (pen drive).

I file generati da un programma possono, in questo modo, essere riutilizzati da altri programmi o dallo stesso programma in un momento successivo.

Il salvataggio dei dati sulle memorie di massa è organizzato in modo ordinato e secondo uno standard preciso: un insieme di dati così archiviato si definisce file ed è identificato mediante un nome. Sarebbe meglio dire che un file di dati è caratterizzato da un nome e da un cognome. Il carattere "." (punto) fa da separatore tra il nome e il cognome.

Il cognome viene propriamente chiamato estensione: per esempio txt, rtf, jpg, mp3, avi, per distinguere file che contengono la stessa tipologia di informazioni (testo, documenti, immagini, brani musicali, filmati...).

L'insieme e il numero di caratteri consentiti per il nome e l'estensione di un file dipendono dal sistema operativo utilizzato.

I file si possono suddividere in due categorie: file di testo e file binari.

I files di testo sono formati da tutti quei caratteri, solitamente codificati in ASCII, che costituiscono un testo e sono quindi direttamente comprensibili da un operatore umano, listabili e stampabili da un qualsiasi editor di testo (come il blocco note di Windows).

I files binari sono usati per memorizzare qualsiasi tipo di informazione e sono costituiti da una sequenza di byte il cui significato non è direttamente comprensibile: se stampati producono una sequenza di caratteri priva di senso per un un operatore umano.

Un programma eseguibile, un file mp3 o jpg sono tutti esempi di file binari. Rispetto ai file di testo, i files binari hanno diversi vantaggi:

Risparmio di spazio e aumento della velocità: il numero di byte richiesto per rappresentare un dato numerico (intero o reale) è solitamente inferiore al numero di caratteri da cui è composto e, di conseguenza diminuisce anche il tempo necessario per accedervi.

Formattazione implicita: ogni dato numerico dello stesso tipo impiega il medesimo numero di byte per essere memorizzato e, quindi, non è necessario utilizzare altri caratteri per delimitarne l'inizio e la fine.

I file binari hanno comunque lo svantaggio di essere meno portabili, perchè il formato di rappresentazione delle informazioni è strettamente legato al sistema operativo usato e al programma che li ha creati.

Accesso ai files

     

Con l'espressione "accesso a file" si indica, indifferentemente, un'operazione di lettura/scrittura di dati in un file. L'accesso a file richiede da parte del calcolatore,una serie di operazioni elettroniche e meccaniche piuttosto articolate. Ad esempio, per leggere o modificare le informazioni su un hard-disk è necessario:

1 avviare il disco rigido e mantenerlo ad una velocità di rotazione costante;

2 posizionare la testina di lettura/scrittura sulla traccia che contiene l'informazione cui si vuole accedere;

3 sincronizzare gli apparati meccanici in movimento (disco e testina) con quelli elettronici (scrittura e lettura in RAM) notoriamente più veloci al fine di consentire un corretto trasferimento dei dati in entrambe le direzioni;

4 leggere o scrivere dati (in forma binaria) mediante la testina di registrazione.

Fortunatamente il sistema operativo riesce a gestire in modo agevole tutte queste operazioni complesse, che altrimenti richiederebbero una conoscenza molto approfondita dell'hardware. In questo modo il problema di come acedere ai file si riconduce ad una semplice sequenza di operazioni logiche.

A apertura del file : si indica al sistema operativo su quale file operare ;

B lettura e/o scrittura di dati: si specificano quali sono le informazioni da leggere o da trasferire;

C chiusura del file: si concludono le operazioni sul file.

La sequenza delle operazioni da compiere si ricorda facilmente, poichè è simile alla normale procedura di consultazione/scrittura di un qualsiasi archivio cartaceo.

Stream

     

Nell'elaborazione di programmi è molto frequente utilizzare differenti dispositivi di I/O (Input/Output) quali tastiere, video, stampanti, scanner, dischi.. etc. con i quali scambiare informazioni. Le difficoltà, legate soprattutto alle differenti modalità di accesso (per un disco, per esempio, può essere random, mentre per una tastiera è sequenziale), sono state superate associando al dispositivo fisico un dispositivo logico detto stream (flusso), che può essere pensato come un "canale" tra la sorgente e la destinazione attraverso il quale fluiscono i dati. Per esempio, tra la tastiera e il programma i dati viaggiano attraverso uno stream di ingresso (cin), mentre tra il programma e lo schermo sono incanalati in uno stream di uscita (cout). Questo livello di astrazione consente di semplificare notevolmente le operazioni di input/output dei dati, poiché il programma interagisce solo con un oggetto, lo stream, e non con una molteplicità di periferiche differenti. Il programmatore dovrà conoscere solo come scambiare informazioni con gli stream (modalità che sono universali) e non con diverse periferiche, ognuna con un proprio set di istruzioni.

Nella Libreria Standard, oltre agli stream standard cin (di tipo istream) e cout (di tipo ostream), sono previsti altri tipi di stream, ciascuno specializzato per un determinato compito, ma tutti accomunati da analoghe modalità di accesso:

ifstream (input file stream) per acquisire dati da un file;
ofstream (output file stream) per scrivere dati su un file;
fstream (file stream) per generiche operazioni di I/O su file;
istrstream e ostrstream (rispettivamente, input e output string stream), che come sorgente e destinazione di dati prevedono stringhe di caratteri.

Apertura di un file

     

Per accedere alle informazioni contenute in un file su disco occorre prima di tutto connetterlo ad un apposito stream. Solo allora sarà possibile la comunicazione tra file e programma. L'associazione file-stream si chiama anche "apertura del file"; in C++ può essere realizzato impiegando le funzionalità della libreria standard.

Ad es. le seguenti righe di programma aprono un file in scrittura, ovvero creano un nuovo file su disco, atleti.txt e lo connettono allo stream di uscita atleti, di tipo ofstream (output file stream definito nell'header file .

ofstream atleti; // dichiara che atleti è uno stream
atleti.open("atleti.txt"); //connessione file-stream

Lo stesso risultato può essere ottenuto usando un'unica riga:

ofstream atleti("atleti.txt");

Se il file esiste già, viene eliminato e ricreato. L'apertura di un file è significativa solo se la connessione tra file e stream è possibile. Se, per qualsiasi motivo, il file non è accessibile non ha nemmeno operare sul suo contenuto. Il successo dell'operazione può essere verificato con un test sullo stato dello stream dopo l'apertura del file.

if(!atleti) cout<<"impossibile accedere al file"<<endl;

Lo stato false di atleti indica che la connessione del file allo stream non è riuscita; viceversa lo stato true ne segnala la regolarità.
Una procedura del tutto analoga è prevista per aprire un file in lettura.

ifstream datiAtleti("atleti.txt");

Dato che questa volta la direzione del flusso di dati va dal file al programma si è impiegato uno stream di ingresso, di tipo ifstream dichiarato come il precedente nell'header file <fstream>.

Anche in questo caso, se il file non esiste o si verifica qualsiasi altro errore, lo stream assume lo stato false, grazie al quale è possibile appurare il successo dell'operazione di aperture:

if(!datiAtleti ) cout<< "Non è possibile aprire il file" <<endl;

Se il file da aprire si trova in una cartella (directory) diversa da quella corrente, occorre specificarne il percorso (path) completo. Per esempio, per accedere al file atleti.txt che si trova nella cartella gare, la quale a sua volta è contenuta nella cartella campionato che fa parte della cartella principale root, nei sistemi Microsoft Windows solitamente si scrive:

\campionato\gare\atleti.txt

mentre in Linux si scrive:

/campionato/gare/atleti.txt

Quest'ultima sintassi è ammessa anche in Windows ed è consigliabile rispetto alla precedente perchè in C++ il backslash \ è anche il segno che identifica una sequenza di escape e potrebbe dare luogo a interpretazioni non corrette o segnalazioni di errore da parte del compilatore.

Modalità di apertura di un file

     

Nella modalità di apertura predefinita di uno stream di tipo ofstream, viene creato un nuovo file di testo.
Se lo stesso programma è impegnato ad aggiungere nuovi dati, si ottiene il risultato di di eliminare quelli precedenti. Per evitare questo problema occorre modificare il comportamento di default di ofstream mediante l'enumeratore predefinito ios::app .

ofstream maga("atleti.txt",ios::app);

questo enumeratore fa si che il file specificato venga aperto in scrittura per aggiungere nuovi dati (append).
Se il file non esiste viene creato.

Nella tabella seguente sono indicati gli enumeratori predefiniti comuni a tutti gli stream.

Enumeratore Descrizione
ios::in Il file è aperto per operazioni di input (lettura) . Il file indicato deve esistere: default per tutti gli stream.
ios::out Il file è aperto per operazioni di output (scrittura). Se il file esiste già, il contenuto precedente viene cancellato. Default per tutti gli stream di output
ios::app Il file è aperto in modalità append: i nuovi dati sono aggiunti alla sua fine, qualunque sia la posizione determinata dall’ultimo accesso. Se il file non esiste viene creato.
ios::ate All’apertura, come nel caso precedente, i nuovi dati sono scritti alla fine del file, ma i successivi nella posizione che dipende dall’ultimo accesso
ios::trunc Se il file esiste il contenuto è cancellato, questa modalità è implicita se viene specificato ios::out senza ios::app, ios::ate e ios::in.
ios:binary Il file è aperto in modo binario. Se non è esplicitamente indicato, il defaut è la modalità testo.

Questi enumeratori possono essere usati da soli o combinati insieme mediante l'operatore bitwise OR (|) e hanno significato solo se applicati appropriatamente:

ofstream elenco("atleti.txt",ios::out|ios::trunc);//defalt per ofstream

Per aprire un file binario occorre specificare esplicitamente l'enumeratore predefinito ios::binary.
Per esempio, per aprire in scrittura e come binario il file atleti.txt si puo scrivere:

ofstream elenco("atleti.txt",ios::app|ios::binary);

Una volta aperto un file, è possibile leggere e su di esso con la stessa semplicità con la quale i dati sono acquisiti dalla tastiera (mediante lo stream cin) o inviati al monitor (con cout).

Scrittura su un file

     

Il modo più semplice per scrivere dei dati su un file è quello di inviarli allo stream associato mediante l'operatore di inserzione <<, come per lo stream cout:

atleti<<cognome<<'\t'<<altezza<<'\t'<<peso<< endl;

Con un'unica operazione, questa riga di programma scrive i dati relativi al cognome, all'altezza e al peso del partecipante sullo stream atleti. Nel programma in esame, prima che vengano scritti sul fi1e, l'operatore di inserzione << converte i dati nei caratteri corrispondenti, secondo 1e specifiche di formato (width, fill, precision ecc.). In questi casi parla di output formattato.

Nell'esempio ogni dato è distinto dal successivo da una tabulazione,'\t'; se lo stream atleti è associato a un file di testo, questo 'spazio bianco' sarà internamente convertito in un'appropriata sequenza di spazi.
Il manipolatore endl invia allo stream un ritorno a capo ('\n'), ma contemporaneamente ne svuota anche il buffer (con l'operazione di flush) causando la scrittura immediata de1 suo contenuto nel file.

Il seguente programma scrive i dati in un file atleti.txt.

#include<iostream>
#include<fstream>
#include<string>
using namespace std;

main(){
 string cognome;
  int n,altezza, peso;
  cout<<"quanti atleti:";cin>>n;
  cin.ignore();
  ofstream atleti("atleti.txt");
  if(atleti)
   for(int i=0;i<n;i++){
   cout<<"\nCognome: "; getline(cin,cognome);
   cout<<"Altezza: "; cin>>altezza;
   cout<<"Peso: "; cin>>peso;
   atleti<<cognome<<"\t"<<altezza<<"\t"<<peso<<endl;
   cin.ignore();
   }//fine for
  else cout<<"Non e' possibile creare il file\n\n";
}//fine main

A questo punto è possibile impiegare le informazioni memorizzate nel file iscritti.txt come input di un altro programma. Per farlo occorre associarlo a uno stream di ingresso e leggerne il contenuto.

Lettura di un file

     

Se l'operatore di estrazione >> è applicato a uno stream di ingresso, si può accedere ai dati formattati di un file:

datiAtleti>>cognome>>altezza>>peso;

Questa riga di programma 'estrae' dal file associato allo stream datiAtleti i caratteri che costituiscono ogni dato e li converte nel tipo delle variabili corrispondenti. Gli 'spazi bianchi' che separano un dato dall'altro sono ignorati. Se l'acquisizione si è svolta senza errori, lo stream assume il valore true, altrimenti, se si verificano problemi nella lettura o nella conversione dei dati, o se si tenta di leggere oltre la fine del file, lo stato stream diventa false.

Attenzione, pero: quando si verifica un errore, i caratteri restanti rimangono nello stream e potrebbero causare letture inappropriate nei successivi accessi. Per 'riparare' lo stream è necessario in primo luogo azzerarne lo stato di errore mediante il metodo clear() e successivamente, con il metodo ignore(), 'saltare' i caratteri restanti o almeno tutti quelli fino al successivo ritorno a capo (New-Line). Il seguente segmento di programma esamina lo stato dello stream datiAtleti dopo l'acquisizione di una riga di dati:

if(datiAtleti >> cognome >> altezza >> peso){
   cout<< cognome <<'\t'<< altezza <<'\t'<< peso <<endl;
}else{
   datiAtleti.clear();
   datiAtleti .ignore(10000, '\n');
}

Una volta raggiunta la fine del file, non ha ovviamente più senso eseguire altre letture dallo stream.
Questa condizione si può esaminare mediante il metodo eof() (End Of File), che assume il valore true dopo che è stato letto l'ultimo carattere dallo stream.

Il programma seguente riassume i concetti esposti fino qui. Esso apre in lettura iI file atleti.txt e ne legge i dati fino a quando non ne viene raggiunta la fine.

#include<iostream>
#include<fstream>
#include<string>
using namespace std;
main(){
  string cognome;
  int altezza, peso;

  ifstream datiAtleti("atleti.txt");
  if(datiAtleti)
    do{
    if(datiAtleti>> cognome >> altezza >> peso){
         cout<< cognome <<'\t'<< altezza <<'\t'<< peso <<endl;
    }else{
         datiAtleti.clear();
        
datiAtleti.ignore(10000, '\n');
  }
  }while(!datiAtleti.eof());
  else cout<<"Non e' possibile aprire il file"<<endl;
}//fine main

Per ottenere letture coerenti è importante che la modalità di apertura di un file (testo o binario) coincida con quella impiegata per scriverlo.

getline()

     

L'operatore di estrazione è ideale per leggere da unfile di testo dati separati da spazi bianchi, ma in determinate circostanze può essere più conveniente impiegare altri metodi, per esempio getline().

Mediante l'operatore di estrazione >> non è possibile acquisire stringhe che contengono 'spazi bianchi' perché l'operatore li considera dei caratteri separatori e pertanto li ignora.

In alcuni casi, questo comportamento predefinito puo rappresentare un problema ed è consigliabile impiegare altri metodi di lettura, come getline(), che consente di leggere con un'unica operazione l'intera riga del file, 'spazi bianchi' compresi.

Nel segmento di programma che segue, getline() legge una riga dallo stream associato al file di testo atleti.txt e la memorizza ne1la stringa riga:

char riga[80];
while( datiAtleti.getline(riga, 80) )
   cout << riga << endl;

Gli argomenti di getline() indicano, rispettivamente, l'indirizzo di memoria dove cominciare a scrivere i caratteri letti (quello del primo elemento del vettore riga) e il loro numero massimo (80). In realtà getline() non leggerà più di 79 caratteri, perché 1'ultimo è riservato al carattere terminatore della stringa. In questo modo il numero di caratteri acquisire non supera la dimensione del vettore destinato a contenerla. Se nello stream compare un NEW-LINE (presente normalmente alla fine di una riga), l'acquisizione si conclude prima di raggiungere il numero specificato di caratteri. Anche in questo caso la stringa sarà automaticamente terminata con il carattere null ('\0').

Quando non è più necessario accedere a un file, si deve procedere alla sua chiusura in modo da liberare risorse e rendere nuovamente disponibile lo stream associato; infatti, molti sistemi operativi limitano il numero massimo di fi1e che possono essere contemporaneamente aperti; pertanto è buona norma chiudere i file di cui non si ha più bisogno.

Chiusura di un file

     


La chiusura di un file elimina l'associazione tra stream e file su disco e restituisce al sistema operativo la memoria utilizzata dal buffer dello stream. Se il file era aperto in scrittura, i dati ancora presenti nel buffer dello stream associato sono immediatamente registrati sul file, assicurando così che nessuna informazione vada perduta. I file vengono chiusi automaticamente alla fine del programma o quando lo stream termina il proprio periodo di vita. Se occorre farlo prima si può impiegare il metodo close():

atleti.close();

Una volta chiuso, lo stream può essere riaperto con il metodo open() per associarlo a un altro file, anche con modalità di apertura differenti (compatibilmente con il tipo di stream dichiarato).

Gli stream ifstream e ofstream sono specializzati, rispettivamente, per I'input e l'output di dati su file, ma la Libreria Standard dispone anche di un tipo di stream più generico: fstream.

Stream di I/O fstream

     

Il tipo fstream è uno stream specializzato per l'accesso a file, ma con modalità che non sono predefinite, come nel caso di ifstream (input) e ofstream (output). Le modalità di accesso si possono invece personalizzare combinando opportunamente gli enumeratori predefiniti. Per esempio, volendo impiegare il medesimo stream per leggere e scrivere dati sullo stesso file e nello stesso programma, si può scrivere la seguente riga di codice:

fstream fileAtleti("atleti.txt", ios::in | ios::out);

Non è previsto un default per la modalità di accesso, pertanto occorre sempre specificarla al momento dell'apertura.

Una volta chiuso, uno stream di tipo fstream può essere riaperto con modalità di accesso totalmente differenti rispetto al precedente uso.

Diversamente da ifstream e ofstream, per verificare il successo dell'apertura di questo tipo di stream occorre impiegare esplicitamente il metodo is_open(). Il valore true segnala un'apertura regolare, false un errore:

if(!fileAtleti.is_open())
   cout << "Errore di apertura del file!" << endl;

Il tipo fstream è definito nell'header file , analogamente agli altri stream che consentono di accedere ai file su disco. E' costituito da due sottostream, uno per le operazioni di input, l'altro per quelle di output. Pertanto. dopo la precedente apertura del file atleti, sullo stream fileAtleti sarà possibile compiere operazioni sia di lettura sia di scrittura:

fileAtleti<<"Rossi"<<190<<80<<endl;//scrive sullo stream
fileAtleti>>cognome>>altezza>>peso;//legge dallo stream

Nella seguente tabella sono elencate alcune possibili combinazioni degli enumeratori predefiniti che possono essere impiegate con fstream e che ne permettono una grande flessibilità d'uso.

Enumeratore Descrizione
ios::in Il file (che deve esistere) è aperto in lettura.
ios::out &equiv; ios::out|ios::trunc Il file è aperto in scrittura. Se il file esiste già, il contenuto precedente viene cancellato.
ios::app &equiv; ios::out|ios::app Il file è aperto in scrittura. Se il file esiste già, il contenuto precedente viene preservato e i nuovi dati sono aggiunti in fondo.
ios::in|ios::out Apre un file esistente per operazioni di lettura e scrittura.
ios::in|ios::out|ios::trunc Crea un nuovo file per operazioni di lettura e scrittura.
ios::in|ios::out|ios::app Apre un file esistente per operazioni di lettura e scrittura. I nuovi dati saranno aggiunti in fondo al file.

Ognuno di questi enumeratori, se combinato con ios::binary, permette l'apertura di un file binario, altrimenti di un file di testo.

Gli operatori di inserzione << ed estrazione >> non prevedono tipi di dati diversi da quelli fondamentali. Non sono consentiti nemmeno dati aggregati, anche se costituiti da elementi o da campi di tipo fondamentale. In questi casi è preferibile impiegare modalità alternative, come quella di registrare direttamente le informazioni presenti in memoria senza formattarle (input e output non formattati).

Input e output non formattati

     

Gli stream di scrittura e lettura di file su disco prevedono due metodi che consentono di inviare o leggere sequenze di byte qualsiasi (blocchi) su un file: write() e read(). Entrambi i metodi non formattano le informazioni, come invece avviene con gli operatori di inserzione << ed estrazione >>. Per scrivere dati come sequenza di byte occorre impiegare il metodo write(). Con write() bisogna specificare il blocco di byte che deve essere registrato; il blocco è identificato dal suo indirizzo iniziale e dalla dimensione. Si supponga, per esempio, che i dati da scrivere siano contenuti in una struttura di tipo Atleta:

struct Atleta {
    char cognome[20];
    unsigned short altezza, peso;
} partecipante;
...
// Apertura del file binario iscritti
ofstream iscritti( "iscritti.bin", ios::app | ios::binary);
...
// Scrittura di dati non formattati
iscritti.write((char *)&partecipante, sizeof(partecipante));

Il primo argomento di write() (necessariamente un puntatore a char) è l'indirizzo della variabile partecipante, il secondo la sua dimensione in byte. Il metodo read(), invece, legge un blocco di byte da un file e memorizza i dati a partire dall'indirizzo specificato:

...
// Lettura di dati non formattati
iscritti.read((char *)&partecipante, sizeof(partecipante));
...

Ovviamente il formato dei dati sul file e quello della struttura partecipante devono coincidere, altrimenti si potrebbero ottenere dei risultati inaspettati. Oltre a write() e read(), esistono altri due metodi che non formattano le informazioni e che consentono, rispettivamente, di scrivere e leggere un singolo carattere: put() e get(). Per esempio, la seguente istruzione:

iscritti.put('A');

invia allo stream iscritti il carattere 'A'. Analogamente, il segmento di programma:

char c;
datiAtleti.get(c);

memorizza nella variabile c qualsiasi carattere contenuto nel file, anche uno "spazio bianco".

E' importante osservare che l'affermazione fatta all'inizio :"i file si possono suddividere in due categorie: file di testo e file binari" è vera e riportata quasi fedelmente su tutti i contesti, ma perde significato quando si usa l'operatore di inserzione << per scrivere i dati sul file. In questo caso, come già affermato, la sola differenza tra un file di testo e un file binario è che nel file binario gli "spazi bianchi" non sono convertiti. Con il presente paragrafo si intuisce invece che la differenza tra dati formattati e non formattati è decisamente più netta. In conclusione, per scrivere dei dati come sequenza di byte occorre impiegare un file binario e registrare i dati con il metodo write(), che non li formatta.

Per evitare risultati erronei o incongruenze tra scritture e letture, i metodi di input/output non formattati vanno applicati ai file binari perché, diversamente dai file di testo, non effettuano alcuna conversione dei byte scritti o letti.

A volte è richiesto che l'accesso alle informazioni contenute in un file non avvenga in ordine dalla prima all'ultima, ma direttamente (in modo random). In questi casi, il dato desiderato deve essere reso disponibile con un'unica operazione di scrittura o lettura.

Accessi sequenziali e random

     

Esistono due modalità di accesso a un file:
Sequenziale: per accedere a un dato è necessario procedere in sequenza scorrendo quelli precedenti (come per i brani di un'audiocassetta);
Random (casuale): è possibile accedere "immediatamente" al dato desiderato (come su una traccia di un disco o in una locazione di memoria RAM).

Tutti gli stream possiedono un indicatore di posizione interno che punta sempre al primo byte del prossimo dato da leggere o da scrivere. Dopo ogni accesso, il puntatore è automaticamente incrementato della dimensione del dato letto o scritto in modo da riposizionarsi correttamente nello stream.

La Figura seguente rappresenta graficamente l'evoluzione dell'indicatore di posizione interno durante la lettura di tre float (4 byte):

Pertanto, l'accesso a un file è sequenziale per default: il dato successivo viene letto solo dopo tutti quelli che lo precedono. Esistono, tuttavia, delle applicazioni in cui occorre accedere alle informazioni desiderate in modo più rapido: in questi casi si parla di accesso random, ottenuto spostando adeguatamente l'indicatore di posizione interno dello stream.

Per modificare la collocazione dell'indicatore di posizione interno di uno stream, e ottenere così un accesso di tipo random, si impiegano due metodi analoghi, ma distinti: per gli stream di ingresso e per quelli di uscita.

I metodi seekg() e seekp()

     

Per entrambi i metodi seekg() e seekp() lo spostamento dell'indicatore di posizione interno può essere assoluto o relativo. Nel primo caso occorre specificare a quale byte (partendo da O) deve puntare l'indicatore di posizione:

datiAtleti.seekg(96); // spostamento assoluto

Gli spostamenti relativi, invece, sono intesi rispetto all'inizio, alla fine o alla posizione corrente del puntatore. A questo scopo sono previsti tre enumeratori predefiniti:
ios::beg : lo spostamento è relativo all'inizio dello stream;
ios::end : lo spostamento è relativo alla fine dello stream;
ios::cur : lo spostamento è relativo alla posizione corrente dell'indicatore di posizione interno.
Per esempio, la seguente istruzione:

datiAtleti.seekg(-24, ios::end); // spostamento relativo

sposta l'indicatore di posizione interno al 24-esimo byte prima della fine dello stream. Mentre seekg() è utilizzato per gli stream di ingresso, il metodo seekp() è il suo equivalente per gli stream di uscita. Dopo lo spostamento dell'indicatore di posizione interno, la scrittura dei dati inizierà dal primo byte puntato.

#include <iostream>
#include <fstream>
using namespace std;
main() {
fstream file;
char c;

// Crea un nuovo file in modalità r/w
file.open("seek.txt", ios::in | ios::out | ios::trunc);

if(file.is_open()) { // se l'apertura è stata regolare

// Scrive nel file seek.txt una stringa di cifre numeriche
file<<"1234567890";

// Si posiziona all'inizio del file per leggere il primo char (1)
file.seekg(0);
file >> c;
cout << c;

// Legge il penultimo char (9)
file.seekg(-2, ios::end);
file >> c;
cout << c;

// Legge il quinto char (5)
file.seekg(4, ios::beg);
file >> c; // dopo la lettura attuale punta al char '6'
cout << c;

// Legge 2 char dopo (8)
file.seekg(2, ios::cur);
file >> c;
cout << c << endl;

// Si posiziona char (0) del file
file.seekp(-1, ios::end);

// e lo sovrascrive con la seguente stringa
file << "ABCDEF";

file.close();
}
else
cout << "Errore di apertura del file!" << endl;
}

I metodi seekg() e seekp() sono utili soprattutto per gestire informazioni memorizzate sotto forma di record. Un record può essere pensato come una struttura i cui campi contengono i dati relativi a un oggetto. Un insieme di record costituisce un file. Per muoversi velocemente da un record all'altro del file, si sposta l'indicatore di posizione interno dello stream. Lo spostamento avviene aggiungendogli (o sottraendogli) un numero di byte pari alla dimensione di un record moltiplicata per il numero del record cui si desidera accedere, partendo da zero. In questo esempio:

cout << "Numero del record da leggere";
cin >> record;
iscritti.seekg(sizeof(Atleta) * (record - 1), ios::beg);

le prime due righe del programma richiedono all'utente il numero del record che deve essere letto. Successivamente, il metodo seekg() sposta l'indicatore di posizione interno dello stream di ingresso iscritti di un numero di byte specificato dal primo argomento. Lo spostamento è relativo all'inizio dello stream, come specificato dall'enumeratore predefinito ios::beg (begin), passato come secondo argomento. Dopo lo spostamento, l'indicatore di posizione interno dello stream punterà al primo byte del record desiderato.

Insieme ai metodi seekg() e seekp(), che consentono di spostare l'indicatore di posizione di uno stream, esistono anche dei metodi complementari che permettono di conoscerne la posizione corrente: tellg() per gli stream di ingresso e tellp() per quelli di uscita.

I metodi tellg() e tellp()

     

I metodi tellg() e tellp() ritornano il valore corrente dell'indicatore di posizione interno dello stream, rispettivamente per gli stream di ingresso e per quelli di uscita. Per esempio, se dati è uno stream di ingresso, le seguenti istruzioni:

dati.seekg(0, ios::end); // si posiziona alla fine del file
cout << Dimensione del file: "<<dati.tellg() << endl; // legge la posizione

consentono di conoscere la dimensione (in byte) del file associato allo stream dati. La prima istruzione colloca l'indicatore di posizione interno dopo l'ultimo carattere del file, mentre la seconda visualizza il valore della posizione attuale, ovvero il numero di byte che costituiscono il file.