![]() ![]() ![]() | Input/Output og Filer |
Vi vender nu blikket mod filer i C. Vi ser på åbning og lukning af filer i C, strukturen FILE som repræsenterer filer i C, tegnvis skrivning og læsning af filer, standard input, output og error, opening modes, og endelig et lille repertoire af funktioner fra standard biblioteket til håndtering af både sekventielle og random access filer i C.
44.1. Sekventielle filer i C
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
Filer i C håndteres via biblioteket stdio.h Sekventielle filer i C kan ses som abstraktioner over filerne i operativsystemet Sådanne abstraktioner omtales ofte som streams |
|
44.2. Filbuffering
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
En filbuffer er et lagerområde mellem anvendelsesprogrammet og det fysiske lager, hvor filen er gemt. Buffer ideen er illustreret i figur 44.1.
Hvis man fra en harddisk beder om ganske få bytes af data i en sekventiel læsning af en fil vil dette mest sandsynligt gå meget langsomt, med mindre man indfører en buffer. Årsagen er at programmet gentagne gange skal vente på at disken kommer forbi læsehovedet. Med buffering læses en forholdsvis stor portion af disken ind i bufferen når lejlighed gives, og efterfølgende tilgang til disse data fra programmet kræver derfor ikke fysisk læsning på selve disken.
Når vi har indført buffering skal vi være opmærksomme på få disken fuldt opdateret inden programmet lukkes ned. Dette sker når vi lukker filen, eller når vi eksplicit beder om det ved at kalde f.eks. C funktionen fflush fra stdio.h.
Figur 44.1 En illustration af filbuffere mellem den ydre enhed og programmet |
44.3. Strukturen FILE
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
En fil i et C program er i praksis en pointer til en FILE structure. Structures blev behandlet i kapitel 39. Vi vil ikke her beskrive de enkelte felter i FILE strukturen. Vi gør en dyd ud af ikke at gøre os afhængige af denne viden. I den forstand opfatter vi filer som objekter i en abstrakt datatype, jf. kapitel 42.
Strukturen FILE beskriver egenskaberne af en fil, herunder det afsatte bufferområde |
Helt overordnet er følgende blandt egenskaberne af FILE:
|
Når talen er om filens øjeblikkelige tilstand er den nuværende filposition af stor betydning. Det er vigtigt at forstå, at i takt med at vi læser en sekventiel fil vil filpositionen gradvis forøges.
Programmøren udnytter normalt ikke kendskab til detaljerne i FILE FILE og de tilknyttede operationer er et eksempel på en abstrakt datatype |
44.4. Simpel skrivning og læsning af en fil
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
Vi vender os nu mod små, illustrative eksempler. Vi ser først på tegnvis skrivning og læsning. Eksemplerne i program 44.1, program 44.2 og senere program 44.4 arbejder alle på en fast fil, som vi kalder "first-file".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h> int main(void) { FILE *output_file_pointer; char *str = "0123456789abcdefghijklmnopqrstuvw"; int i; output_file_pointer = fopen("first-file", "w"); while (*str != '\0'){ fputc(*str, output_file_pointer); str++; } fclose(output_file_pointer); return 0; } | |||
|
Vi diskuterer her program 44.1. Overordnet skriver programmet - tegn for tegn - strengen str på filen first-file. På det blå sted erklærer vi variablen ouput_file_pointer som en pointer til FILE. På det første røde sted åbner vi filen i skrive mode, "w" for write. While-løkken gennemløber tegnene i str, og ved brug af standard io funktionen fputc skrives hvert tegn i str til output filen. Vi erindrer *str dereferencer pointeren - dvs. at vi tilgår hvert enkelt tegn i strengen gennem pointeren. (Dereferencing er beskrevet i afsnit 22.4). str++ tæller pointeren op pr. pointer aritmetik, jf. afsnit 24.5. Afsluttende, på det sidste røde sted, lukker vi filen.
Når vi kører program 44.1 får vi lavet first-file. I program 44.2 læser vi denne fil og putter tegnene tilbage i en tekststreng.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h> #define MAX_STR_LEN 100 int main(void) { FILE *input_file_pointer; char str[MAX_STR_LEN]; int ch; int i = 0; input_file_pointer = fopen("first-file", "r"); while ((ch = fgetc(input_file_pointer)) != EOF){ str[i] = ch; i++; } str[i] = '\0'; printf("Read from file: %s\n", str); fclose(input_file_pointer); return 0; } | |||
|
Vi forklarer nu program 44.2. Ligesom i program 44.1 erklærer vi en FILE* variabel, og vi åbner filen, dog her i læse mode. I en while-løkke læser vi tegnene i filen, indtil vi møder EOF. Det boolske udtryk i while-løkken er et typisk C mønster som kalder fgetc, assigner til variablen ch, og tester om det læste tegn er forskelligt fra EOF. Bemærk at ch er erklæret som en int, ikke en char. Årsagen er at EOF ikke er en gyldig char værdi, men derimod typisk -1. Et kald af fgetc læser netop ét tegn. De læste tegn lægges i arrayet str. Vi afslutter med lukning af filen.
Det er altid en god ide at placere filåbning og fillukning i samme funktion, idet det sikrer (langt hen ad vejen i det mindste) at en åben fil altid bliver lukket. Åbning og lukning optræder således altid i par.
I program 44.3 viser vi et program fra lærebogen, som laver dobbelt linieafstand i en tekstfil.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <stdio.h> #include <stdlib.h> void double_space(FILE *ifp, FILE *ofp); void prn_info(char *pgm_name); int main(int argc, char **argv) { FILE *ifp, *ofp; if (argc != 3) { prn_info(argv[0]); exit(1); } ifp = fopen(argv[1], "r"); /* open for reading */ ofp = fopen(argv[2], "w"); /* open for writing */ double_space(ifp, ofp); fclose(ifp); fclose(ofp); return 0; } void double_space(FILE *ifp, FILE *ofp) { int c; while ((c = fgetc(ifp)) != EOF) { fputc(c, ofp); if (c == '\n') fputc('\n', ofp); /* found newline - duplicate it */ } } void prn_info(char *pgm_name) { printf("\n%s%s%s\n\n%s%s\n\n", "Usage: ", pgm_name, " infile outfile", "The contents of infile will be double-spaced ", "and written to outfile."); } | |||
|
Programmet accepterer to programparametre, jf. afsnit 31.3. Et typisk kald af programmet er således
dbl_space infile outfileforudsat at det oversatte program placeres i filen dbl_space. Det kan gøres med en -o option til gcc compileren. Filåbning og lukning er vist med blåt. Med brunt vises kaldet af den centrale funktion double_space, der laver arbejdet. I denne funktion læses et tegn med fgetc, og det skrives med fputc. Med rødt ser vi en test for at variablen c er newline tegnet. Når dette tegn mødes udskrives det endnu engang. Dermed opnås effekten af dobbelt linieafstand.
Når en fil er læst til ende vil fgetc og andre tilsvarende funktioner returnere EOF værdien |
44.5. Standard input, output og error
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
I dette afsnit skærper vi vores forstand af begreberne standard input og standard output.
De tre filer stdin, stdout, og stderr åbnes automatisk ved program start Hver af stdin, stdout, og stderr er pointere til FILE |
|
File redirection er illustreret i program 16.11.
Der findes en række behændige filfunktioner, som implicit arbejder på stdin og stdout i stedet for at få overført en pointer til en FILE structure |
Bemærkning i rammen ovenfor er f.eks. rettet mod funktionerne printf, scanf, putchar, og getchar.
44.6. Fil opening modes
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
Vi skal have helt styr på et antal forskellige opening modes af filer i C.
Foruden læse- og skrivemodes tilbydes yderligere et antal andre opening modes |
r | Åbne eksisterende fil for input |
w | Skabe ny fil for output |
a | Skabe ny fil eller tilføje til eksisterende fil for output |
r+ | Åbne eksisterende fil for både læsning og skrivning. Start ved begyndelse af fil. |
w+ | Skabe en ny fil for både læsning og skrivning |
a+ | Skabe en ny fil eller tilføje til eksisterende fil for læsning og skrivning |
Tabel 44.1 En tabel der beskriver betydningen af de forskellige opening modes af filer. |
Opening modes "r" og "w" er enkle at forstå. Bemærk at med opening mode "w" skabes en ny fil, hvis filen ikke findes i forvejen. Hvis filen findes i forvejen slettes den! Opening mode "a" er bekvem for tilføjelse til en eksisterende fil. Som vi vil se i program 44.4 sker tilføjelsen i bagenden af filen. Heraf naturligvis navnet 'append mode'.
Opening mode "r+" er ligesom "r", blot med mulighed for skrivning side om side med læsningen. Dette virker i Unix, men ikke i Windows og Dos. Der er nærmere regler for kald af (eksempelvis) fflush mellem læsninger og skrivninger. Vi beskriver ikke disse nærmere her. Dette illustreres i program 44.5.
Opening mode "w+" er ligesom "w" med mulighed for læsning side om side med skrivning.
I program 44.4 viser vi tilføjning af "Another string" til indholdet af "first-file". Bemærk brug af "a" opening mode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h> int main(void) { FILE *output_file_pointer; char *str = "Another string"; int i; output_file_pointer = fopen("first-file", "a"); while (*str != '\0'){ fputc(*str, output_file_pointer); str++; } fclose(output_file_pointer); return 0; } | |||
|
Programmet i program 44.5 benytter opening mode "r+". For hver læsning skrives et 'X' tegn. Det betyder at hvert andet tegn i filen bliver 'X' tegnet. Så hvis first-file indledningsvis indeholder strengen "abcdefg", og vi dernæst kører programmet, bliver filindholdet af first-file ændret til at være "aXcXeXgX". Variablen str ender med at have værdien "aceg", som udskrives på standard output.
Som allerede omtalt virker dette program kun på Unix, idet der er begrænsninger for skrivning i en fil i "r" modes i Windows og Dos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <stdio.h> #define MAX_STR_LEN 100 int main(void) { FILE *input_file_pointer; char str[MAX_STR_LEN], ch; int i = 0; input_file_pointer = fopen("first-file", "r+"); while ((ch = fgetc(input_file_pointer)) != EOF){ str[i] = ch; fflush(input_file_pointer); fputc('X', input_file_pointer); fflush(input_file_pointer); i++; } str[i] = '\0'; printf("Read from file: %s\n", str); fclose(input_file_pointer); return 0; } | |||
|
C muliggør åbning af binære filer ved at tilføje et b til opening mode I C på Unix er der ikke forskel på binære filer og tekstfiler |
44.7. Funktioner på sekventielle filer
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
Vi giver i dette afsnit en oversigt over de mest basale tegn-orienterede funktioner, som virker på sekventielle filer i C.
|
Læsere af C by Dissection kan slå disse og en del af andre funktioner op i appendix A.
44.8. Funktioner på random access filer
Indhold Op Forrige Næste Slide Aggregerede slides Stikord Programindeks Opgaveindeks
Ligesom for sekventielle filer i afsnit 44.7 viser i i dette afsnit de væsenligste funktioner på random access filer i C.
Begrebsligt kan man tilgå en random access fil på samme måde som et array |
Følgende funktioner er centrale for random access filer:
|
Vi illustrerer random access filer ved et af lærebogens eksempler, nemlig læsning af en fil baglæns. Altså læsning stik modsat af sekventiel læsning.
I program 44.6 prompter vi brugeren for filnavnet. Efter filåbning placerer vi os ved sidste tegn i filen. Dette sker med det første røde fseek. Vi går straks ét tegn tilbage med det andet røde fseek. I while-løkken læser vi tegn indtil vi når første position i filen. For hvert læst tegn går vi to trin tilbage. Husk at læsning med getc bringer os ét tegn frem. Kaldet af getc(ifp) er ækvivalent med fgetc(ifp). Det læste tegn udskrives på standard output med putchar. Det første tegn skal behandles specielt af det tredje røde fseek kald. Årsagen er naturligvis, at vi ikke skal rykke to pladser tilbage på dette tidspunkt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <stdio.h> #define MAXSTRING 100 int main(void){ char filename[MAXSTRING]; int c; FILE *ifp; fprintf(stderr, "\nInput a filename: "); scanf("%s", filename); ifp = fopen(filename, "r"); fseek(ifp, 0, SEEK_END); fseek(ifp, -1, SEEK_CUR); while (ftell(ifp) > 0) { c = getc(ifp); putchar(c); fseek(ifp, -2, SEEK_CUR); } /* The only character that has not been printed is the very first character in the file. */ fseek(ifp, 0, SEEK_SET); c = getc(ifp); putchar(c); return 0; } | |||
|