Programmeringsteknik med C och Matlab Kapitel 6: Filhantering Henrik Björklund Umeå universitet 13 oktober 2009 Björklund (UmU) Programmeringsteknik 13 oktober 2009 1 / 22
Textfiler Filer är sekvenser med data, vanligtvis lagrade på hårddisk e.d. Vi skiljer på två typer av filer: textfiler och binära filer. Textfiler består av sekvenser av tecken. Tecken kan vara bokstäver, skiljetecken och siffror, men också sådant som <space>, <newline> och <tab>. Björklund (UmU) Programmeringsteknik 13 oktober 2009 2 / 22
<eof> : end of file Slutet på en fil markeras med ett speciellt slut-på-filen-tecken:. C använder sig av ett särskilt värde, EOF för att representera. Många av input-funktionerna från stdio.h returnerar EOF när de läser, vilket vi kan använda oss av för att styra programflödet: while( scanf("%d", &mynumber)!= EOF ){ printf("%d\n", mynumber); } Björklund (UmU) Programmeringsteknik 13 oktober 2009 3 / 22
Ett program som läser från och skriver till filer Antag att vi har ett antal kodfiler och att vi vill skriva ut dem på papper med radnumrering. Det finns visserligen ett antal olika sätt att åstadkomma detta, men vi bestämmer oss för att göra det med hjälp av ett litet C-program. Programmet ska heta rownumbers och kunna anropas med två filnamn. Tex >> rownumbers infil.c utfil.txt Programmet ska läsa från infil.c och skapa en radnumrerad kopia i utfil.txt Vi antar att ingen av filerna programmet ska hantera har mer än 1000 rader eller någon rad med mer än 300 tecken. Björklund (UmU) Programmeringsteknik 13 oktober 2009 4 / 22
rownumbers (1) #include <stdio.h> #define BUFSIZE 300 int main(int argc, char ** argv){ char line[bufsize]; /* Buffer a line at the time from input */ FILE * infilep; /* Pointer to input file */ FILE * outfilep; /* Pointer to output file */ int rownumber = 1;... Björklund (UmU) Programmeringsteknik 13 oktober 2009 5 / 22
Filpekare För att hålla reda på filer ett program arbetar med använder sig C av strukturer av datatypen FILE. Dessa innehåller diverse information om filerna, och hur programmet kan komma åt dem. Vi behöver inte bry oss så mycket om exakt hur datatypen FILE ser ut, eftersom vi kommer att ta hjälp av funktioner från stdio.h för att hantera filer. Vad vi behöver hålla reda på, däremot, är att dessa funktioner, för att kunna veta vilken fil de ska arbeta med, tar emot och returnerar filpekare. Dessa filpekare har typen FILE *. Exempel på filpekare är stdin, stdout, stderr. Björklund (UmU) Programmeringsteknik 13 oktober 2009 6 / 22
rownumbers (2)... /* check that there are two input parameters (in- and outfile) * if(argc <= 2){ fprintf(stderr, "Usage: rownumbers <input file name> "); fprintf(stderr, "<output file name>\n"); return 0; }... Björklund (UmU) Programmeringsteknik 13 oktober 2009 7 / 22
rownumbers (3)... /* Try to open the input file */ infilep = fopen(argv[1], "r"); if(infilep == NULL){ fprintf(stderr, "Couldn t open input file %s\n", argv[1]); return 0; } /* Try to open the output file */ outfilep = fopen(argv[2], "w"); if(outfilep == NULL){ fprintf(stderr, "Couldn t open output file %s\n", argv[2]); return 0; }... Björklund (UmU) Programmeringsteknik 13 oktober 2009 8 / 22
fopen (1) Funktionen fopen hjälper till att öppna filer och skapa filpekare, så att vi kan läsa från och skriva till filer. Den tar två argument: 1. Det första argumentet är filnamnet, representerat som en sträng. 2. Det andra argumentet är också en sträng, och anger vad det är vi vill göra med filen. Det finns ett antal olika värden för det andra argumentet som fopen förstår: "r" : read. Vi vill läsa från filen. "w" : write. Vi vill skriva till filen. "a" : append. Vi vill skriva till filen, men efter det som redan finns i den. Det finns även andra alternativ, som vi dock inte behöver just nu. Björklund (UmU) Programmeringsteknik 13 oktober 2009 9 / 22
fopen (2) fopen returnerar en filpekare, som alltså har typen FILE *. Det finns emellertid en hel massa saker som kan gå fel när vi vill öppna en fil: En fil vi vill läsa från finns inte. Vi vill skriva till en fil men har inte de nödvändiga rättigheterna. Etc. Om något går fel returnerar fopen en pekare med värdet NULL, en så kallad null-pekare. NULL är en minnesadress som inte finns, och värdet används för att markera att pekaren faktiskt inte pekar på någonting. Björklund (UmU) Programmeringsteknik 13 oktober 2009 10 / 22
fopen (3) infilep = fopen(argv[1], "r"); if(infilep == NULL){ fprintf(stderr, "Couldn t open input file %s\n", argv[1]); return 0; } Att kontrollera att pekaren vi får från fopen inte är en null-pekare är 1. synnerligen viktigt och dessutom 2. alldeles väldigt obligatoriskt (för den som vill bli godkänd på OU3). Om vi försöker läsa data med hjälp av en filpekare med värdet NULL kommer programmet att krascha. Felet som uppstår faller under den inom C-programmeringen blott alltför vanliga kategorin pekarfel. Björklund (UmU) Programmeringsteknik 13 oktober 2009 11 / 22
Pekarfel Björklund (UmU) Programmeringsteknik 13 oktober 2009 12 / 22
rownumbers (4)... /* Read a line at a time from the input file. * Write the line, preceded by a row number, to the output file. */ while(fgets(line, BUFSIZE,infilep)!= NULL) { fprintf(outfilep, "%-3d ", rownumber); fprintf(outfilep, "%s", line); rownumber++; }... Björklund (UmU) Programmeringsteknik 13 oktober 2009 13 / 22
fgets fgets har vi redan sett. Den anropas till exempel med där fgets(line, maxchar, filep) 1. line är en char-array, 2. maxchar är en int, och 3. filep är en filpekare. fgets returnerar normalt en char-pekare (addressen till den första positionen i line). Om fgets läser, dvs om det inte finns något mer att läsa från filpekaren filep, returnerar den i stället en pekare med värdet NULL. while(fgets(line, BUFSIZE,infilep)!= NULL) { fprintf(outfilep, "%-3d ", rownumber); fprintf(outfilep, "%s", line); rownumber++; } Björklund (UmU) Programmeringsteknik 13 oktober 2009 14 / 22
rownumbers (5)... fclose(infilep); fclose(outfilep); } return 0; Björklund (UmU) Programmeringsteknik 13 oktober 2009 15 / 22
fclose Funktionen fclose tar en filpekare (FILE *) som inparameter och stänger motsvarande fil. fclose tar bort strukturen som skapats för att hålla rätt på tillgångsinformationen samt utför ett par andra renhållningsuppgifter. Att stänga alla filer programmet öppnat är också synnerligen obligatoriskt. Björklund (UmU) Programmeringsteknik 13 oktober 2009 16 / 22
fgetc En annan användbar funktion från stdio.h är fgetc. Den kan användas för att läsa ett tecken i taget från en fil. fgetc tar en filpekare (FILE *) som enda inparameter, och returnerar nästa tecken i motsvarande fil. När fgetc läser returnerar den EOF. while(c!= EOF){ c = fgetc(infilep); /* Do something useful with c */ } Björklund (UmU) Programmeringsteknik 13 oktober 2009 17 / 22
Binära filer Binära filer I motsats till en textfil förväntas en binär fil inte nödvändigtvis bestå av en sekvens av tecken, utan kan bestå av data på godtyckligt format. Även om vi i princip kan lagra vilken data som helst som en sekvens av tecken innebär det i praktiken ofta onödigt arbete (både för programmeraren och datorn). Vi har till exempel ofta sett att vi måste ägna en hel del möda åt att omvandla strängar till heltal, flyttal, etc. Om ett program vill lagra data som ska läsas av ett annat C-program kan den göra det i en binär fil utan att bry sig om att konvertera till strängar. Programmet lagrar helt enkelt datorns interna representation av data. Ett annat program kan sedan läsa den binära filen, förutsatt att det känner till vad det är som lagrats. Björklund (UmU) Programmeringsteknik 13 oktober 2009 18 / 22
Binära filer Att öppna binära filer Precis som för textfiler använder vi fopen för att öppna binära filer för läsning eller skrivning. Den enda skillnaden är att vi lägger till ett b till den andra inparametern: binaryp = fopen("myfile.bin", "rb"); binaryp = fopen("myfile.bin", "wb"); binaryp = fopen("myfile.bin", "ab"); Björklund (UmU) Programmeringsteknik 13 oktober 2009 19 / 22
Binära filer fwrite För att skriva till en binär fil kan vi använda oss av funktionen fwrite: size_t fwrite (const void *array, size_t size, size_t count, FILE *stream); array är en pekare till en plats i minnet, där fwrite ska börja läsa. size är storleken på den datatyp som ska skrivas till filen, angedd i bytes. count är antalet element vi vill skriva till filen. stream är filpekaren. fwrite kommer att skriva de första count blocken av storlek size från minnet, med start vid array, till filen kopplad till stream. Björklund (UmU) Programmeringsteknik 13 oktober 2009 20 / 22
Binära filer sizeof Hur vet vi då hur många bytes en viss datatyp upptar? Det får vi reda på med hjälp av functionen sizeof: long sizeint = sizeof(int); long sizevector3dim = sizeof(vector3dim); Björklund (UmU) Programmeringsteknik 13 oktober 2009 21 / 22
Binära filer fread För att läsa från en binär fil kan vi använda oss av funktionen fwrite: size_t fread (const void *array, size_t size, size_t count, FILE *stream); array är en pekare till en plats i minnet, där fwrite ska börja skriva. size är storleken på den datatyp som ska läsas från filen, angedd i bytes. count är antalet element vi vill läsa från filen. stream är filpekaren. fread kommer att skriva de första count blocken av storlek size från filen kopplad till stream och skriva till minnet, med start vid array. Björklund (UmU) Programmeringsteknik 13 oktober 2009 22 / 22