Apertura in lettura e scrittura

Fino ad ora abbiamo visto che è possibile aprire il file in lettura, oppure in scrittura. È in effetti possibile aprire i file in modo tale che sia possibile sia leggere che scrivere sul file.

Per aprire il file in modo che si possa sia leggere che scrivere, si passa come stringa di modalità alla funzione fopen la stringa "r+". Per leggere si usa come al solito la funzione fread mentre per scrivere si usa la funzione fwritef.

Leggere e scrivere richiede però di introdurre il concetto di posizione corrente all'interno del file. Si consideri quindi il seguente programma dispari.c. Cosa succede se si prova ad eseguirlo su un file binario, visualizzando il contenuto del file prima e dopo? (per visualizzare il contenuto di un file binario, usare leggi.c).

/*
  Legge il primo numero, lo raddoppia, e lo
copia sul secondo. Fa lo stesso sul terzo e 
sul quarto, ecc.
*/

#include<stdlib.h>
#include<stdio.h>

int main() {
  FILE *fd;
  int x, y;
  int res;


			/* apre il file */
  fd=fopen("test.dat", "r+");
  if( fd==NULL ) {
    perror("Errore in apertura del file");
    exit(1);
  }


			/* ciclo di lettura */
  while(1) {

			/* legge un intero */
    res=fread(&x, sizeof(int), 1, fd);
    if( res!=1 )
      break;


			/* mette il doppio in y e lo scrive */
    y=2*x;
    fwrite(&y, sizeof(int), 1, fd);
  }


			/* chiude il file */
  fclose(fd);

  return 0;
}

A prima vista, può sembrare che questo programma raddoppia tutti i numeri del file. Quello che succede, invece, è che tutti i numeri in posizione pari (il secondo, il quarto, ecc) sono stati sostituiti dal doppio del numero che li precede (per esempio, nella seconda posizione ora c'è il doppio del primo numero).

Per analizzare il motivo di questo comportamento, occorre introdurre un concetto nuovo, che è quello di posizione all'interno del file. Come si è notato parlando della differenza fra file binari e testuali, un file è in effetti semplicemente una sequenza di byte. Quello che li rende differenti da un vettore di caratteri è il fatto che il dispositivo in cui è memorizzato è il disco invece che la memoria primaria. Per il resto, hanno tutte le caratteristiche degli array.

Quelle che i file hanno in più rispetto agli array è il fatto di avere una posizione corrente di lettura definita in modo automatico. Questa posizione corrente è semplicemente un intero che indica la posizione nella sequenza in cui si va a leggere e scrivere. In particolare, quando si apre il file in lettura, la posizione corrente è l'inizio della sequenza. Ogni volta che si legge qualcosa, la posizione avanza.

Quello che segue è una descrizione dettagliata delle operazioni di lettura e scrittura da file, e valgono anche nei casi in cui il file viene aperto in sola lettura o sola scrittura. Possiamo rappresentare un file come una sequenza di byte. All'apertura la posizione corrente indica il primo byte del file. Possiamo rappresentare questa cosa graficamente come segue:

L'effetto della operazione di lettura fread(&x, sizeof(int), 1, fd) è quello di leggere i primo quattro byte del file e metterli in una variabile intera (il numero di byte letti dipende ovviamente dal tipo della variabile). Questa operazione di lettura ha anche l'effetto di spostare la posizione corrente all'interno del file in avanti, al primo byte che segue quelli letti:

La lettura del secondo elemento ha ancora come effetto quello di spostare la posizione corrente in avanti.

Per essere precisi, possiamo dire che l'operazione di lettura si comporta come segue:

  1. legge il dato che si trova a partire dalla posizione corrente
  2. sposta la posizione corrente in avanti, al primo byte che segue quelli letti

Questo descrive cosa fa esattamente l'operazione di lettura. Il motivo per cui due successive operazioni di lettura leggono i dati in sequenza è che la prima, dopo aver letto, sposta la posizione in avanti, per cui la seconda operazione legge il secondo dato e non legge di nuovo il primo.

La stessa cosa succede quando si scrive: dopo aver scritto qualcosa, la posizione corrente avanza ancora. In altre parole, se si apre un file in scrittura, la posizione corrente è all'inizio del file. Quando si scrive, si scrive a partire dalla posizione corrente, e la posizione viene spostata nello stesso modo della lettura. Questo è il motivo per cui, quando si fanno più operazioni di scrittura su file, le cose vengono scritte in sequenza: in realtà, la funzione fwrite scrive sempre a partire dalla posizione corrente, solo che avanza questa posizione dopo aver scritto. Questo fa sí che la successiva operazione di scrittura scriva di seguito invece di sovrascrivere il dato scritto prima.

Vediamo ora cosa succede quando si esegue il programma di sopra. Supponiamo che nel file siano scritti, in forma binaria, i tre numeri 12, 9 e 28. Quando si apre, la posizione corrente è all'inizio del file:

Si legge il primo intero a partire dalla posizione corrente, e si sposta in avanti la posizione. Ora x vale quanto letto da file, cioè 12.

Si assegna a y il doppio del valore di x, cioè 24. Questo numero viene scritto in forma binaria nel file. La scrittura avviene a partire dalla posizione corrente, e sposta in avanti la posizione corrente. In questo momento, la posizione corrente è sopra il quinto byte del file, ed è a partire da questa posizione che si va a scrivere. La posizione avanza ancora di quattro, dato che si sta scrivendo un intero.

Ora si legge un intero, a partire dalla posizione corrente, per cui si legge 48. Si sposta la posizione corrente, che ora è sulla fine del file.

Il numero che si va a scrivere sul file è il doppio di quello letto, per cui si scrive 96. Questo numero viene scritto sotto la posizione corrente del file, e quindi si aggiungono quattro byte in coda al file. Lo stato del file alla fine è il seguente:

A questo punto si cerca di leggere di nuovo, ma l'operazione fallisce perchè il file è finito.

L'effetto che ne risulta è che il programma legge un numero, poi lo raddoppia e poi lo scrive. Però la scrittura avviene sulla posizione corrente dopo la lettura, per cui si scrive il doppio sulla posizione successiva. Quindi, per esempio, viene letto il primo numero e il doppio viene scritto sul secondo.

Riassumendo, i file si possono vedere come array, in cui la posizione corrente avanza a ogni lettura e scrittura. Nelle prossime pagine vediamo come si può modificare la posizione corrente nel file, con opportune istruzioni.

Osservazione: il concetto di posizione del file, e il modo in cui la si può modificare, vale per tutti i file, anche quelli di testo (del resto, la distinzione fra file di testo e binari dipende solo dal modo in cui viene interpretato il contenuto). Soltanto che per i file di testo modificare la posizione corrente è di minore utilità a causa della dimensione variabile dei dati numerici quando sono scritti come stringhe.