informatica 2 – modulo c · gestione dei files in c •un file è una sequenza di bytes che...
TRANSCRIPT
Programmazione C Massimo Callisto De Donato
www.cs.unicam.it/massimo.callisto
LEZIONE 9 – FILE
Università degli studi di Camerino Scuola di scienze e tecnologia - Sezione Informatica
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 Significato
fopen() Apre un file
fclose() Chiude un file
putc()/fputc() Scrive un carattere su file
getc()/fgetc() Legge un carattere da file
fgets()/fputs() Come sopra su stringhe
fread()/fwrite() Lettura/scrittura di byte da/su file
fprintf()/fscanf() Equivalente della printf/scanf su file
fseek() Posizionamento su un certo byte del file
feof() Raggiungimento fine file
ferror() Restituisce vero se si verifica un errore
rewind() Riporta l’indicatore di posizione all’inizio del file
fremove() Cancella il file
fflush() 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à Descrizione
r Read File di testo in sola lettura [se non esiste torna NULL]
w Write File di testo in sola scrittura [ il contenuto viene cancellato,se il file
non esiste viene creato]
r+ Read & Write File di testo in lettura e scrittura [se non esiste torna NULL]
w+ Write & Read File di testo in scrittura e lettura [il contenuto viene cancellato, se il file non esiste viene creato]
a Append File di testo in scrittura aggiungendo i dati alla fine del file [ae il file non esiste viene creato].
a+ Append & Read Come append ma aggiunge anche la lettura.
b Binary 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) {… fclose(fp); exit(-1);}
if(!strlen(str)){ break; }
if(fputs(str, fp) == EOF) {… fclose(fp); exit(-1);}
if(fputs("\n", fp) == EOF) {…fclose(fp); exit(-1);}
}
fclose(fp);
} 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"
int main()
{
num();
return 0;
}
// file.c
#include <stdio.h>
/*static*/ int ggl = 0;
int num(){
printf("Ciao mondo");
ggl++;
return 0;
}
// bridge.h
int num();
1
2
3
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