Università degli studi di Camerino Scuola di scienze e tecnologia - Sezione Informatica Programmazione C Massimo Callisto De Donato massimo.callisto@unicam.it www.cs.unicam.it/massimo.callisto LEZIONE 9 FILE A.A. 2011/12
Gestione dei files in C Un file è una sequenza di bytes che supportano le operazioni di: creazione, cancellazione, posizionamento, lettura, scrittura. Un file può rappresentare qualsiasi cosa (file di testo, schermo, una stampante, un disco) chiamato canale. Le operazioni saranno tradotte a seconda del canale su cui operiamo. Esempio: scrivere qualcosa su un file che rappresenta lo schermo produce una stampa a video. La stessa operazione su una stampante provoca una stampa.
Gestione dei files in C Le classiche operazioni sono: Apertura Possibilità di creazione Operazioni sul file Lettura e scrittura. Posizionamento [solo su file allacciati a canali che lo supportano] Chiusura Serve a scrivere eventuali dati rimasti in attesa da inviare al canale.
Gestione dei files in C da stdio.h Funzione fopen() fclose() putc()/fputc() getc()/fgetc() fgets()/fputs() fread()/fwrite() fprintf()/fscanf() fseek() feof() ferror() rewind() fremove() fflush() Significato Apre un file Chiude un file Scrive un carattere su file Legge un carattere da file Come sopra su stringhe Lettura/scrittura di byte da/su file Equivalente della printf/scanf su file Posizionamento su un certo byte del file Raggiungimento fine file Restituisce vero se si verifica un errore Riporta l indicatore di posizione all inizio del file Cancella il file Scarica il contenuto del buffer in memoria
File: aggetti utili Per capire la posizione si usano: NULL: definisce il puntatore nullo EOF: identifica la fine del file Costanti di posizionamento(con fseek()) SEEK_SET inizio file SEEK_CUR posizione corrente SEEK_END: fine del file
File: apertura Apertura: #include <stdio.h> FILE *fopen(const char * filename, const char *mode); Il prototipo dice: Una stringa filename in ingresso Una stringa mode che identifica come aprire il file Ritorna: un puntatore a struttura di tipo FILE NULL se l apertura non va a buon fine.
Esempio #include <stdio.h> // STanDard Input/Output FILE *fp; if((fp = fopen( test.txt, r )) == NULL){ printf( Errore di apertura di test.txt!\n ); exit(-1); // Possiamo operare sul file puntato da fp
File: la struttura FILE La struttura FILE è contenuta nella libreria stdio.h Mantiene le informazioni per la corretta gestione del file [nome, modalità di apertura, posizione corrente, ] Le funzioni che operano sul file useranno queste informazioni a seconda dei casi. Es.: fwrite() controllerà nella struttura se abbiamo aperto il file in modalità corretta.
File: modalità di apertura Modalità r w r+ w+ a a+ b Read Write Read & Write Write & Read Append Append & Read Binary Descrizione File di testo in sola lettura [se non esiste torna NULL] File di testo in sola scrittura [ il contenuto viene cancellato,se il file non esiste viene creato] File di testo in lettura e scrittura [se non esiste torna NULL] File di testo in scrittura e lettura [il contenuto viene cancellato, se il file non esiste viene creato] File di testo in scrittura aggiungendo i dati alla fine del file [ae il file non esiste viene creato]. Come append ma aggiunge anche la lettura. Se una b viene aggiunta alle modalità precedenti si indica che il file è binario. [rb, wb, ab, e r+b, w+b, a+b]
File: modalità di apertura In modalità testo ciò che effettivamente contiene il file può non rispecchiare ciò che leggiamo/scriviamo. Esempio: new line può corrispondere al carattere \n oppure alla coppia di caratteri CarriageReturn + LineFeed ( \r\n ). In modalità binario leggiamo ciò che si trova sul file [i due caratteri CR e LF]
Esempio #include <stdio.h> #include <stdlib.h> #define MAX_LEN 100 int main(int argc, char *argv[]) { FILE *fp; char mfile[max_len], mod[4]; while(1){ printf("file da aprire e modalita'[r r+ w w+ a a+...]:"); scanf("%s%s", mfile, mod); if((fp = fopen(mfile, mod)) == NULL){ printf("errore di apertura di %s!\n", mfile); exit(-1); printf("file %s aperto in modalità %s!\n", mfile, mod); // do nothing fclose(fp);
Esercizio Modificare il programma precedente per aprire un solo file nella modalità scelta. Il file su cui operare deve essere indicato all avvio del programma. Schema di utilizzo: programma.exe nome_file modalità
File: chiusura La chiusura del file è sempre un operazione importante: (in generale) l O.S. gestisce scritture bufferizzate. La chiusura forza a scrivere i dati ancora bufferizzati. int fclose(file* fp); Restituisce zero se va a buon fine; EOF se c'è stato qualche errore; Quando può tornare EOF? Es.: il file puntava ad un disco e prima della chiusura qualcuno lo ha estratto dal computer.
File: lettura/scrittura Dopo l apertura di un file possiamo: Leggere/scrivere caratteri: getc(), putc() Leggere/scrivere stringhe: fputs(), fgets() Leggere e scrivere con fscanf(), fprintf() In modalità binario leggiamo/salviamo tipi di dati arbitrari. Es.: Possiamo creare una rubrica tramite le struct. Il salvataggio consiste nello scrivere le variabili di tipo struct su un file.
File: lettura di caratteri getc() [fgetc()] per la lettura da file di un carattere. int getc(file* fp); Dopo la lettura fp punta al prossimo carattere La funzione ritorna: Il carattere letto EOF per la fine del file EOF in caso di errore. Per distinguere tra errore o fine del file usiamo ferror() o feof()
Esempio #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { FILE *fp; char ch; if((fp = fopen("input.txt", "r")) == NULL){ printf("errore di apertura di input.txt!\n"); exit(-1); printf("file input.txt aperto il lettura!\n"); while( (ch = getc(fp))!= EOF ) { printf( "letto %c [%d]\n", ch, ch); //sleep(10); // se entro 20s elimino input.txt provoco un errore(?) if(!feof(fp)){ printf("c'e' stato un errore!"); printf("%d", ferror(fp)); fclose(fp); return 0;
File: scrittura di caratteri putc() [fputc()] per la scrittura su file di un carattere int puntc(int ch, FILE* fp); Accoda alla posizione di fp il carattere ch. Ritorna: Il ch carattere appena scritto EOF in caso di errore
Esempio #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { FILE *fp; char ch, *ofile = "output.txt"; if((fp = fopen(ofile, w")) == NULL){ printf("errore di apertura di %s!\n, ofile); exit(-1); printf("file %s aperto il scrittura!\n Termina con 0 \n, ofile); while( (ch = getchat())!= 0 ) { if(putc(ch, fp) == EOF){printf( Errore in scrittura!\n ); exit(-1); // Valeva anche: if(fputc(ch, fp)!= ch){ fclose(fp); return 0;
Esercizio Scrivere il programma copia_files.c che prende in input: il file da cui leggere il file su cui scrivere: copia_files.exe in.txt out.txt N.b: getc() e putc() non effettuano una copia esatta da in.txt a out.txt, perché?
Esercizio Proviamo ad implementare il programma copia_files in modalità binaria. Osservazione 1: dobbiamo aggiungere b alla modalità di apertura di un file. Osservazione 2: in modalità binario un byte potrebbe rappresentare EOF e quindi potremmo leggere erroneamente la fine di un file. Per controllare la terminazione usiamo al suo posto feof(file *).
File: lettura di stringhe con fgets() Leggiamo il file a righe con fgets() char *fgets(char *buffer, int n, FILE *fp); Inserisce in buffer i caratteri letti: Fino alla terminazione della riga (con \n compreso); Oppure più n-1 caratteri se la riga è più lunga di n. A buffer si aggiunge automaticamente \0. Ritorna NULL in caso di errore. FILE *fp = fopen( ); char line[line_max]; // vedi LINE_MAX di limits.h... while (fgets(line, LINE_MAX, fp)!= NULL){...
File: scritture di stringhe con fputs() Scriviamo il contenuto di buffer nel file int fputs(const char *buffer, FILE *fp); Non verranno aggiunti \n in maniera automatica. Ritorna EOF se c è stato errore, un intero positivo altrimenti. FILE *fp = fopen( ); char line[line_max]; scanf( %s, line); if((fputs(line, fp) == EOF){ /* gestione errore */
Esempio int main(int argc, char *argv[]) { FILE *fp; char str[line_max]; int i = 0; if(argc!= 2){ printf("errore! Usare: %s file_scrittura\n", argv[0]); exit(-1); if((fp = fopen(argv[1], "w")) == NULL){ /*errore */ exit(-1); printf("inserire il testo. Una riga vuota per terminare:\n"); while(1){ if(gets(str) == NULL) if(!strlen(str)){ break; if(fputs(str, fp) == EOF) if(fputs("\n", fp) == EOF) fclose(fp); { fclose(fp); exit(-1); { fclose(fp); exit(-1); { fclose(fp); exit(-1); Il carattere \n non era stato inserito in str. Vedi gets() su Open Group
Esercizio Creiamo un programma conta_caratteri che: Usi le funzioni fgets() e fputs(); Prende in input un file di testo input.txt; Per ogni riga calcola il numero dei caratteri presenti su una riga; Riscrive la riga su un altro file output.txt aggiungendo il numero dei caratteri trovato. Esempio: input.txt output.txt
File: fprintf e fscanf Date le funzioni printf e scanf, esistono le rispettive versioni che operano su file in maniera esattamente uguale: int i = 1034; FILE* fp = fopen(...); fprintf(fp, "%d\n", i); // scrive su fp il valore 1034 fscanf(fp, "%d", &i); // legge da fp Otteniamo l effetto delle funzioni note usando i puntatori a FILE definiti in stdio.h: standard input stdin (la tastiera) standard output stdout (lo schermo) : printf(...) fprintf(stdout,...) scanf(...) fscanf(stdin,...) getchar(...) fgetc(stdin) putchar(c) fputc(c, stdin)
Esempio FILE *fp; char str[line_max]; int i = 0; if(argc!= 2){ fprintf(stdout, "Errore! Usare... "); exit(-1); if((fp = fopen(argv[1], "w")) == NULL){ /*errore */ exit(-1); printf("inserire il testo. Una riga vuota per terminare:\n"); while(1){ if(fgets(str, LINE_MAX, stdin) == NULL) { fclose(fp); exit(-1); if(strlen(str) < 2){ break; if(fputs(str, fp) == EOF ) { fclose(fp); exit(-1); //if(fputs("\n", fp) == EOF) { fclose(fp); exit(-1); /* N.b.: fgets legge anche \n e lo memorizza in str; e se c è un solo carattere deve essere \n */
File: operare in binario In modalità binario accediamo all effettiva rappresentazione binaria del file. #define BUFF_MAX 512 char buffer[buff_max]; int n; FILE* fp = fopen(...); n = fread(buffer, sizeof(char), BUFF_MAX, fp); Dati in ingresso: Leggiamo sizeof(char) alla volta per BUFF_MAX volte; Il contenuto va inserito in buffer La funzione scrive in n: BUFF_MAX se c erano abbastanza dati < BUFF_MAX se il file è terminato prima
File: operare in binario In modalità binario accediamo all effettiva rappresentazione binaria del file. #define BUFF_MAX 512 char buffer[buff_max]; int n; FILE* fp = fopen(...); n = fwrite(buffer, sizeof(char), BUFF_MAX, fp); Dati in ingresso: Scriviamo sizeof(char) alla volta per BUFF_MAX volte; Il contenuto viene letto da buffer La funzione scrive in n il numero degli elementi scritti: BUFF_MAX se è andato tutto bene < BUFF_MAX se c è stato un errore
Esempio /* Copia binaria tra due files. Gestione degli errori è omessa */ #define BUFF_MAX 512 FILE *fp_in, *fp_out; char buffer[buff_max]; /* copiamo 1 byte (1 char)alla volta. */ int n = 0; fp_in = fopen( input.txt, rb ); fp_out = fopen( output.txt, wb ); while(!feof(fp_in)){ n = fread(buffer, sizeof(char), BUFF_MAX, fp_in); if(fwrite(buffer, sizeof(char), n, fp_out)!= n){ /* errore */ exit(-1); fclose(fp_in); fclose(fp_out); /* Nb:se al posto di char mettiamo int o altro potremmo avere dei troncamenti! */
Esercizio 1. Scriviamo un programma verify_1.c che: Prende in input due files; Verifica se i due files hanno lo stesso contenuto. Opera in modalità testo; Usi le funzioni fread, fwrite. 2. Scriverne un programma verify_2.c che opera come verify_1.c ma effettua il controllo in modalità binaria. 3. Scriverne verify.c che prende in input un ulteriore parametro: text per effettuare una verifica modalità testo; bin per effettuare una verifica in modalità binaria.
File: salvataggio tipi di dati derivati Scriviamo/leggiamo su un file strutture dati shop che contiene l id di un negozio ed relativo nome. #define MAX_LEN 100 #define MAX_ID 10 typedef struct { char id[10]; char nome[max_len]; shop; Operiamo in binario con append. Eseguendo più volte il programma la lista crescerà mostrando i negozi salvati in precedenza.
Esempio // nel main shop buffer[n]; FILE *fp = fopen( shop.save, ab ); // append binario /* Assumiamo di riempire buffer[n] di N shops e salviamo il file*/ savefile(fp, buffer, N); void save_file(file * file, const shop* buffer, const int size){ int n; n = fwrite(buffer, sizeof(shop), size, file); if(n!= size){ printf("errore di scrittura!\n"); exit(-1);
Esempio // nel main FILE *fp = fopen( shop.save, r ); // read binario /* Riapriamo il file e richiamiamo una funzione che lo legge e ne stampa il contenuto */ read_file (fp); void read_file(file * file){ shop *ptr = (shop *) malloc(sizeof(shop)); if(ptr == NULL) { /* errore */ exit(-1); while(fread(ptr, sizeof(shop), 1, file)){ if(ferror(file)){ /* errore */ exit(-1); printf("[id: %s, NOME: %s]\n", ptr->id, ptr->nome); free(ptr); ptr = NULL;
File: spostamenti fseek() ci permette di muoverci a piacimento a partire da un punto di riferimento. int fseek(file *fp, long offset, int pos); offset (anche negativo) è la quantità in bytes dello spostamento rispetto al riferimento pos pos può essere inizio (SEEK_SET), posizione corrente (SEEK_CUR), fine file (SEEK_END) Ritorna: 0 in caso di successo -1 se c è stato un errore E.s.: usiamo ftell() per conoscere la dimensione di un file in bytes.
Esempio /* long ftell(file *fp); ritorna il n di bytes nella posizione attuale rispetto all inizio del file */ shop buffer[n]; long n = 0; FILE *fp = fopen( shop.save, r ); // read dall inizio n = fseek(fp, 0, SEEK_END); if(!n) { /* errore */ exit(-1); printf( Dimensione file: %l bytes, ftell(fp)); /* ci riportiamo all inizio del file */ n = fseek(fp, 0, SEEK_SET); if(!n) { /* errore */ exit(-1);
File Passiamo all esempio SaveStruct.c con i concetti visti prima. Il file è diventato un po lungo e comincia ad essere difficile da maneggiare Impariamo ad usare un file.h per lavorare meglio
Header files Un file.h contiene solo: I prototipi delle funzioni Strutture dati derivate, alias, etc. Costanti Eventuali variabili globali Dovrà esistere il corrispettivo file.c che definisce i prototipi. Nel nostro file con il main includiamo il nostro file con: #include file.h
Esempio // main.c #include <stdio.h> #include "bridge.h" 2 // bridge.h int num(); 3 int main() { num(); 1 return 0; // file.c #include <stdio.h> /*static*/ int ggl = 0; int num(){ printf("ciao mondo"); ggl++; return 0;
Esempio #include <stdio.h> #include "my_math.h" int main(){ int a, b, c; fprintf(stdout, Tre numeri prego:"); fscanf(stdin, "%d %d %d", &a, &b, &c); printf( La media e %f\n", average(a,b,c)); return 0; /* my_math.h */ #define ZERO 0 float average(int,int,int); float sum(int, int); /* my_math.c */ #include "my_math.h float average(int x, int y, int z){ int somma; return (somma = sum(sum(x, y), z)) == ZERO? ZERO : (somma / 3); float sum(int x, int y){ return x + y;
Esercizio (ultimo ) Creare un programma Rubrica che mantenga le informazioni sui contatti (e.s.: nome, cognome). Ciascun contatto possiede un ID unico nella rubrica. Il programma deve offrire un menu interattivo per: Apertura da file di una rubrica Salvataggio su file della rubrica attualmente in memoria Stampa lista contatti in memoria Aggiunta di un nuovo contatto Cancellazione di un contatto dalla rubrica Ricerca contatto Analisi del contenuto di un file di una rubrica Duplicazione del file di una rubrica
The end Grazie per l attenzione