next up previous contents index
Next: I files d'intestazione Up: Approfondimenti. I files d'intestazione Previous: Gestione data e ora   Indice   Indice analitico

Gestione minimale dei files

La gestione dei files è un argomento troppo avanzato per gli scopi di questo corso, specialmente volendo affrontare la questione dal punto di vista del C++, il quale fornisce degli strumenti molto sofisticati per la gestione dei files. Noi ci accontenteremo per ora di effettuare qualche operazione fondamentale a basso livello stile-C; alla fine di questo testo vedremo poi un accenno degli streams in C++. Le funzioni e i tipi utilizzati in questa sottosezione sono contenuti nella libreria stdio.h, la quale sta per STandarD Input/Output.

Il primo tipo di cui abbiamo bisogno è il tipo FILE, che è anomalo rispetto ai tipi che abbiamo fino ad ora incontrato; il motivo risiede nel fatto che la gestione dei files dipende pesantemente dal sistema operativo utilizzato, per cui si è preferito utilizzare dei meccanismi interni di memorizzazione delle variabili di tipo FILE, piuttosto che creare una struttura o qualcosa del genere. Comunque non dobbiamo preoccuparci: basta trattare le variabili di tipo FILE diversamente dalle altre, cioè non bisogna provare ad allocarle manualemente ma semplicemente far sì che siano le funzioni di libreria a svolgere questo compito per contro nostro. Le prime funzioni che incontriamo sono le seguenti:

FILE* fopen (const char* nome_file, const char* modalita)
int fclose (FILE* nome_file)
le quali servono, rispettivamente, ad aprire ed a chiudere un file. La funzione fclose è molto semplice: essa chiude un file precedentemente aperto e torna 0 se l'operazione è riuscita; in caso fclose fallisca nel chiudere un file essa torna il valore EOF, definito in stdio.h, il quale sta per End Of File (trad. ``fine del file''). Se la funzione fclose non viene chiamata mai all'interno del programma, alla fine di esso vengono automaticamente chiusi tutti i files aperti; tale operazione viene effettuata anche nel caso il programma venga chiuso tramite la nota funzione exit (contenuta nella stdlib.h), ma non nel caso si verifichino errori di esecuzione o si chiami la funzione abort (contenuta anch'essa nella stdlib.h), la quale termina immediatamente il programma in esecuzione.

La funzione fopen apre un file; cosa vuol dire ``aprire un file''? Significa che al file chiamato nome_file 8.7 viene associato uno stream (trad. ``flusso'') sul quale è possibile operare in modalità di scrittura o di lettura con le funzioni che presto vedremo. La modalità di apertura di un file è determinata dall'argomento modalita:

 r sola lettura (reading-only)
 w sola scrittura (writing-only)
 a concatenazione (append)
 r+ lettura/scrittura
 w+ scrittura/lettura

La modalità r consente esclusivamente di leggere un file; se l'operazione di apertura del file fallisce, ad esempio perché il file non esiste, viene tornato un puntatore nullo. La modalità w consente esclusivamente di scrivere su nome_file; se esso non esiste viene creato, altrimenti il file preesistente viene distrutto. Anche aprendo un file in modalità a si può solamente scrivere su di esso, tuttavia se il file esiste, la scrittura si effettua alla fine di esso: lo stream viene cioè concatenato al file preesistente. Le modalità r+ e w+ consentono entrambe di leggere e scrivere contemporaneamente su di un file; la differenza tra le due è che nel caso nome_file esista prima dell'apertura, con la prima modalità il file resta invariato mentre con la seconda esso viene distrutto.

Vediamo un esempio che determina l'esistenza di un file; nel caso esso non esista viene creato un file vuoto.


// ex8_6_1
#include <iostream.h>
#include <stdio.h>

void main() {
  char f[50];
  cout << "nome file? "; cin >> f;
  FILE* file;
  if ( (file = fopen (f, "r")) != 0 )
    cout << "il file " << f << " esiste\n";
  else
    file = fopen (f, "w");
  fclose (file);
}

esempio di output:
nome file? prova

esempio di output:
nome file? prova
il file prova esiste

Supponiamo che nella cartella contenente ex8_6_1.cpp non ci sia nessun file chiamato ``prova''; se eseguiamo due volte il programma di esempio precedente, la prima volta il file non esiste, dunque viene creato, poi viene segnalata la sua esistenza.

Aprire e chiudere i files può essere interessante, ma abbastanza inutile; vediamo invece come leggere e scrivere su files aperti:

int putc (int c, FILE* stream)
int fputs (const char* s, FILE* stream)
int getc (FILE* stream)

Le prime due funzioni, putc e fputs, servono semplicemente a scrivere su stream; la prima di esse scrive un carattere (c), la seconda una stringa (s) omettendo il carattere di fine stringa '\0'. Entrambe tornano il valore costante EOF in caso di fallimento, un valore positivo in caso di successo. Vediamo un semplice esempio:


// ex8_6_2
#include <iostream.h>
#include <stdio.h>
#include <string.h>

void main() {
  FILE* stream = fopen ("prova", "w");
  // NOTA: se `prova' esiste viene cancellato
  cout << "scrivi 'esci' per terminare\n";
  char buffer[100];
  for ( ; ; ) {  // infinite loop
    cout << "? "; cin >> buffer;
    if ( strcasecmp(buffer, "esci") == 0 )
      break;
    int codiceRitorno = fputs (buffer, stream);
    // aggiunge il carattere di fine riga
    putc ('\n', stream);
    if (codiceRitorno == EOF) {
      cout << "ERRORE DI SCRITTURA!\n";
      exit(1);
    }
  }
  fclose (stream);
}

esempio di output:
scrivi 'esci' per terminare
? love
? is
? sentimental
? measles
? esci

Se si prova ad aprire il file `prova' si vedrà che esso contiene esattamente:
love
is
sentimental
measles

La funzione più semplice per la lettura di uno stream è getc; essa non fa altro che leggere un carattere da stream e restituirlo. Vediamo un esempio che ci permette di copiare un file in un altro:


// ex8_6_3
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>

void main() {
  char fileSorgente[50];
  char fileDestinazione[50];
  cout << "sorgente? "; cin >> fileSorgente;
  cout << "destinazione? "; cin >> fileDestinazione;
  FILE* streamSorg = fopen(fileSorgente, "r");
  // controlla che il file sorgente esista
  if (streamSorg == 0) {
    cout << "errore in fase di apertura "
      "di " << fileSorgente << "\n";
    exit (1);
  }
  FILE* streamDest = fopen(fileDestinazione, "w");
  char c;
  while ((c = getc (streamSorg)) != EOF )
    putc (c, streamDest);
  fclose (streamSorg);
  fclose (streamDest);
}

esempio di output:
sorgente? file_che_non_esiste
destinazione? file_di_destinazione
errore in fase di apertura di file_che_non_esiste

Se il file fornito come sorgente esiste, otterremo una copia esatta di esso nel file di destinazione; possiamo costruire un nostro comando di copia di un file utilizzando gli argomenti da riga di comando:


// ex8_6_4
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  if (argc < 3) {
    cout << "la sintassi e`:"
      "\n\tcopia file_sorgente file_destinazione\n";
    exit (1);
  }
  FILE* sorg = fopen (argv[1], "r");
  FILE* dest = fopen (argv[2], "w");
  // controlla che il file sorgente esista
  if (sorg == 0) {
    cout << "il file " << sorg << " non esiste!\n";
    exit (1);
  }
  // copia `sorg' in `dest'
  char c;
  int n = 0; // conta i bytes copiati
  while ((c = getc (sorg)) != EOF ) {
    putc (c, dest);
    n++;
  }
  fclose (sorg);
  fclose (dest);
  cout << "copia di " << n << " effettuata\n";
  return 0;
}

Tale semplice programma copia byte a byte il file sorgente nel file destinazione, restituendo al termine dell'operazione il numero di bytes copiati.

ex-2
si scriva un programma che crei un file e salvi in esso cento numeri casuali da 1 a 100;
ex-3
si scriva un programma che apra un file in lettura e lo stampi a video saltando tutti i bytes pari;
ex-4
si scriva un programma che apra un file e lo concateni ad un altro esistente;
ex-5
si scriva un programma che salvi in un file data e ora attuali;


next up previous contents index
Next: I files d'intestazione Up: Approfondimenti. I files d'intestazione Previous: Gestione data e ora   Indice   Indice analitico
Claudio Cicconetti
2000-09-06