De manier waarop de records in een file staan, bepaalt de file-organisatie. Vier gebruikelijke soorten file-organisatie zijn:

Vergelijkbare documenten
17 Operaties op bits Bitoperatoren en bitexpressies

Operaties op bestanden

4 Invoer en uitvoer. 4.1 Toegang tot de standaardbibliotheek

9 Meer over datatypen

Lineaire data structuren. Doorlopen van een lijst

10 Meer over functies

Week 2 : Hoofdstukken 2 en 6; extra stof: inleiding pointers

4EE11 Project Programmeren voor W. College 2, , Blok D Tom Verhoeff, Software Engineering & Technology, TU/e

EE1400: Programmeren in C BSc. EE, 1e jaar, , 3e college

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni, 2010

Tentamen Programmeren in C (EE1400)

Tentamen Programmeren in C (EE1400)

PROS1E1 Gestructureerd programmeren in C Dd/Kf/Bd

Variabelen en statements in ActionScript

EE1400: Programmeren in C BSc. EE, 1e jaar, , 4e college

Waarden persistent (blijvend) opslaan gaat in bestanden (files). Lege tekst: eof

Objective-C Basis. 23 april 2005, Eindhoven Patrick Machielse

EE1400: Programmeren in C BSc. EE, 1e jaar, , 2e college

HOOFDSTUK 3. Imperatief programmeren. 3.1 Stapsgewijs programmeren. 3.2 If Then Else. Module 4 Programmeren

Instructies en blokken

Pascal uitgediept Data structuren

Een typisch programma in C en C++ bestaat uit een aantal onderdelen:

In deze aflevering van deze serie zal ik proberen een groot gebrek van Turbo Pascal weg te nemen, namelijk het gemis aan Random Access Files.

Datastructuren: stapels, rijen en binaire bomen

continue in een for, while of do lus herhaalt de lus vroegtijdig. De volgende herhaling wordt onmiddellijk begonnen.

Week 3 : Hoofdstukken 7 en 8; extra stof: invoer met scanf() extra aanvulling op het boek: inlezen met scanf() in C

Als een PSD selecties bevat, deelt de lijn van het programma zich op met de verschillende antwoorden op het vraagstuk.

Technology, Innovation & Society Delft

Programmeren in Java les 3

PROGRAMMEREN IN C CURSUS VOOR STARTERS. J.W. Welleman. header files. source files. *.h. *.c. compiler. object files. library builder. *.

Oefententamen 2. Tijd: 2 uur. Maximaal aantal punten: 30. Naam: Studentnummer:

Lab Webdesign: Javascript 3 maart 2008

Functioneel programmeren

Recursie: definitie. De som van de kwadraten van de getallen tussen m en n kan als volgt gedefinieerd worden:

Een korte samenvatting van enkele FORTRAN opdrachten

Deel 1: Arduino kennismaking. Wat is een microcontroller, structuur van een programma, syntax,

Tentamen Programmeren in C (EE1400)

Dynamisch geheugen beheer

Leren Programmeren met Visual Basic 6.0 Les 3+4. Hoofdstuk 4 : De Selectie

Uitwerking Aanvullend tentamen Imperatief programmeren Woensdag 24 december 2014, uur

Programmeren in C++ Efficiënte zoekfunctie in een boek

Een eenvoudig algoritme om permutaties te genereren

Programmeren met Arduino-software

HOGESCHOOL VAN AMSTERDAM Informatica Opleiding. CPP 1 van 10

Programmeermethoden. Recursie. week 11: november kosterswa/pm/

Week 5 : Hoofdstuk 11+ extra stof: meer over functies. Hoofdstuk 11:

De MySQL C API. Variabelen in C Functies in C Pointers in C

Programmeren /13 Test Tentamen

PROS1E1 Handleiding ( ) Kf/Dd/Bd

1 Rekenen in eindige precisie

Syntax- (compile), runtime- en logische fouten Binaire operatoren

Zelftest Inleiding Programmeren

Controle structuren. Keuze. Herhaling. Het if statement. even1.c : testen of getal even of oneven is. statement1 statement2

Technology, Innovation & Society Delft

int main() { int m; m = power(2,3) /* berekent 2^3 en geeft de resultaat naar m terug */ }

QR-code op aanvoerbrief 2.xx.0: Specificaties

RADBOUD UNIVERSITEIT NIJMEGEN AFDELING STERRENKUNDE JAN VAN ROESTEL. Programmeren /14 Computer Practicum

Korte uitleg: File descriptors en redirection in de shell (en pipes)

Uitwerking tentamen Analyse van Algoritmen, 29 januari

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni, 2010

Programmeermethoden NA

Instructies en blokken

BEGINNER JAVA Inhoudsopgave

1.1 Programmeren en voorbereiding

Opdrachten numerieke methoden, week 1

12 Meer over pointers

OPDRACHT Opdracht 2.1 Beschrijf in eigen woorden wat het bovenstaande PSD doet.

Modelleren en Programmeren

start -> id (k (f c s) (g s c)) -> k (f c s) (g s c) -> f c s -> s c

UNIVERSITEIT ANTWERPEN FACULTEIT WETENSCHAPPEN DEPARTEMENT WISKUNDE-INFORMATICA OBERON CODE CONVENTIONS

Java Les 3 Theorie Herhaal structuren

Structures. Voorbeeld: een punt in een vlakte heft een x en een y coördinaat struct point{ int x; int y; }

Voorbeeldtentamen Inleiding programmeren (IN1608WI), Oktober 2003, , Technische Universiteit Delft, Faculteit EWI, Afdeling 2.

Proeftentamen in1211 Computersystemen I (NB de onderstreepte opgaven zijn geschikt voor de tussentoets)

Tentamen Computersystemen

De standaard programmeertaal

Niet-numerieke data-types

Datatypes Een datatype is de sort van van een waarde van een variabele, veel gebruikte datatypes zijn: String, int, Bool, char en double.

Jörg R. Hörandel Afdeling Sterrenkunde

{ auteur, toelichting }

Programmeermethoden NA. Week 5: Functies (vervolg)

Programmeermethoden. Functies vervolg. Walter Kosters. week 5: 1 5 oktober kosterswa/pm/

In de tweede regel plaatsen we in het gereserveerde stukje geheugen een getal.

Constanten. Variabelen. Expressies. Variabelen. Constanten. Voorbeeld : varid.py. een symbolische naam voor een object.

VBA voor Doe het Zelvers Deel 7

Modelleren en Programmeren

Programmeermethoden NA. Week 5: Functies (vervolg)

Numerieke benadering van vierkantwortels

6.2 VBA Syntax. Inleiding Visual Basic

Hutscodering. De techniek: illustratie. een tabel met 7 plaatsen, genummerd van 0 tot en met 6.

Talstelsels en getalnotaties (oplmodel)

VOORBLAD SCHRIFTELIJKE TOETSEN

Online c++ leren programmeren:

Tentamen Imperatief en Object-georiënteerd programmeren in Java voor CKI

AFO 139 Automatische export

6,1. Samenvatting door een scholier 1809 woorden 28 oktober keer beoordeeld. Informatica

Vakgroep CW KAHO Sint-Lieven

Inleiding Programmeren 2

Uitwerking Tentamen Modelleren en Programmeren - versie 1 Woensdag 1 februari 2017, uur

Tweede college algoritmiek. 12 februari Grafen en bomen

Transcriptie:

14 Files De programma's die we hebben bestudeerd, produceren allemaal uitvoer en lezen in de meeste gevallen invoer. Dat gebeurt via de standaardfiles standaardinvoer en standaarduitvoer. Deze eenvoudige voorbeelden zijn niet representatief voor het grootste deel van de toepassingen van computers. Bij de meeste toepassingen worden grote hoeveelheden permanente data opgeslagen. Om de data tussen verschillende uitvoeringen van programma's te bewaren, worden ze in files in een secundair geheugen opgeslagen, bijvoorbeeld op een magneetschijf. In dit hoofdstuk kijken we nader naar de standaardfiles en andere files. Een file is een verzameling data, geproduceerd door een programma of invoer voor een programma. In het algemeen is een file een collectie records. Het record is de basiseenheid voor het verwerken van files. In het algemeen leest een programma telkens één record uit of naar een file. Afhankelijk van de toepassing kan een record bestaan uit één karakter, een regel tekst, of een andere eenheid. Records bestaan meestal uit een aantal data-elementen, de zogenaamde velden. Een record dat een regel tekst is, kan als velden de woorden van de tekst hebben. De velden kunnen zelf weer te verdelen zijn in ondervelden, bijvoorbeeld de afzonderlijke letters van een woord. De manier waarop de records in een file staan, bepaalt de file-organisatie. Vier gebruikelijke soorten file-organisatie zijn: serieel/sequentieel random geïndexeerd geïnverteerd In dit hoofdstuk zullen we toepassingen bekijken waarin de eerste twee methoden worden gebruikt. Seriële files zijn zo georganiseerd, dat elk record in de file, behalve het laatste, een unieke opvolger heeft; elk record, behalve het eerste, heeft ook een unieke voorganger. In figuur 14-1 ziet u een fragment van een file waarin gegevens over studenten staan; de velden van elk record zijn het nummer van de student, de naam van de student, het vak en de beoordeling. De file is sequentieel georganiseerd in stijgende volgorde van het studentnummer. Bij toevoeging of verwijdering van records, moet de volgorde gehandhaafd blijven. Een nieuw record voor een student met nummer 45 moet onmiddellijk voor student Jansen worden tussengevoegd.

Figuur 14-1 NUMMER NAAM VAK PUNT 12 Bruin c 6 23 Smit C++ 5 34 Zwart Pascal 5 56 Jansen c 9 67 Arends Pascal 7 Een random fileorganisatie is alleen toepasbaar op files die bewaard worden in geheugen met direct access. Deze methode wordt gebruikt als de records in willekeurige volgorde worden verwerkt en niet in de volgorde waarin ze staan opgeslagen. Een random file is een verzameling records die elk worden aangegeven door een uniek relatief recordnummer. Als de file N records bevat, hebben die elk een uniek integer nummer 1, 2, 3,, N. Als een record in een random file wordt opgeslagen, wordt zijn plaats bepaald door een adresseringsalgoritme, die de sleutelwaarde van het record vertaalt naar een integer in het bereik van 1 tot en met N. Bij het ophalen van hetzelfde record uit de file wordt dezelfde sleutelwaarde opgegeven, waarbij dezelfde adresseringsalgoritme wordt toegepast, die hetzelfde relatieve recordnummer berekent. In figuur 14-2 ziet u de records uit het vorige voorbeeld, maar nu opgeslagen in een random file. De file bevat hoogstens 37 records (het gekozen maximale aantal studenten) en de adresseringsalgoritme is: studentnummer mod 37 + 1 waarin mod de restoperator is. Voor het verwerken van files zijn in een hogere programmeertaal diverse operaties beschikbaar die op de file en de records van de file worden uitgevoerd. Met de creatie-operator wordt een nieuwe file met nul records tot stand gebracht. Voordat het verwerken van een file kan beginnen, moet de file eerst worden geopend. Als de verwerking afgelopen is, moet de file weer worden gesloten. Het openen van een file kan volgens verschillende modes gebeuren, bijvoorbeeld invoermode (de file wordt gelezen, maar blijft onveranderd), uitvoermode (er worden nieuwe records in de file geschreven) en invoer/uitvoermode (er worden records gelezen en geschreven).

Figuur 14-2 RELATIEF RECORD- NUMMER NUMMER NAAM VAK PUNT 1 2 13 12 Bruin c 6 20 56 Jansen C 9 24 23 Smit C++ 5 31 67 Arends Pascal 7 35 34 Zwart Pascal 5 36 37 Op records van een file zijn voornamelijk twee operaties van toepassing. De lees-operatie haalt één record uit de file op. In een sequentieel georganiseerde file leest deze operatie het volgende record uit de rij. Bij een random file bepaalt de opgegeven sleutelwaarde, en daarmee het relatieve recordnummer, welk record er wordt gelezen. De schrijf-operatie plaatst een nieuwe recordwaarde in een file. Bij een sequentiële file wordt het record geschreven als de voorganger van het vorige in de file geschreven record. Bij een random file bepaalt de sleutelwaarde waar een record wordt geschreven. Als er herhaaldelijk uit een sequentiële file wordt gelezen, wordt uiteindelijk het einde van de file bereikt. Om die situatie aan te geven is er een operator end of file nodig. De standaardbibliotheek van C bevat alle functies die nodig zijn om deze fileoperaties te implementeren. De functie fopen opent en fc1ose sluit een file. Varianten van de functies voor invoer uit de standaardinvoer en uitvoer naar de standaarduitvoer werken op files. Functies die invoer doen, leveren ook een indicator end of file. De positie waar de volgende invoer/uitvoeroperatie op een file wordt uitgevoerd, wordt bepaald met de functie fseek. In de rest van dit hoofdstuk houden we ons bezig met toepassingen die deze file functies gebruiken.

14.1 Toegang tot files Voordat er uit een file kan worden gelezen of erin kan worden geschreven, moet de file worden geopend. Dat wordt gedaan met de standaardbibliotheekfunctie fopen. De functie fopen heeft twee argumenten. Het eerste argument is de naam van de te openen file, opgegeven als een string die afgesloten is met een nulkarakter. Het tweede argument geeft aan hoe de file moet worden geopend en wordt ook als karakterstring opgegeven. Het tweede argument wordt de openmode genoemd. De volgende modes zijn mogelijk: r" - open een bestaande file om eruit te lezen; w " - creëer een nieuwe file of maak een bestaande leeg om erin te schrijven; a" - creëer een nieuwe file om in te schrijven of maak een bestaande klaar om eraan toe te voegen; r+" - open een bestaande file om te veranderen (lezen en schrijven) en begin bij het begin van de file; w+" - creëer een nieuwe file, of maak een bestaande leeg, om veranderingen aan te brengen (lezen en schrijven); a+" - creëer een nieuwe file om veranderingen aan te brengen of maak een bestaande file klaar om eraan toe te voegen (lezen en schrijven). Bij het openen van een file kan het gebeuren dat er een foutindicator wordt aangezet. Dat kan verschillende redenen hebben. Als een file bijvoorbeeld in een leesmode (11 r " of 11 r+ 11) wordt geopend, moet de betreffende file al in het filegeheugen aanwezig zijn. Als dat niet het geval is, levert f open een foutwaarde af. Als een niet-bestaande file wordt geopend voor schrijven ("w " of "w+") of toevoegen ( a" of "a+ "), wordt de file automatisch gecreëerd. Als de file wel al bestaat, gaat de oorspronkelijke inhoud bij het openen verloren. In sommige systemen wordt onderscheid gemaakt tussen tekstfiles en binaire files. Voor een binaire file moet de letter b achter de modestring worden geplaatst. Als fopen de opgegeven file in de voorgeschreven mode heeft geopend, levert de functie een interne naam af, die dan bij latere invoer/uitvoeroperaties op die file wordt gebruikt. Deze interne naam noemen we een filepointer. Om informatie over een file, of algemener een stroom, te bewaren, wordt het datatype FILE gebruikt. De precieze details van het datatype FILE hoeft ons niet te interesseren. Alle benodigde informatie staat in de standaardheader <stdio.h>. De declaratie voor een filepointer is: FILE *fp;

Hier wordt gespecificeerd dat de variabele f p een pointer naar het afgeleide type FILE is. De naam FILE is een typenaam (ingevoerd door middel van een typedef -statement in <stdio.h> zoals elke andere. De prototypedeclaratie voor de functie fopen in <stdio.h> is: FILE *fopen(char *filename, char *mode) Een aanroep van de functie fopen om een file genaamd data te lezen, is: fp = fopen ("data", "r") ; Als bij het openen van de file geen fout optreedt, levert fopen een filepointer af die aan de pointervariabele fp wordt toegekend. Verdere invoeroperaties die op de file worden uitgevoerd, worden geformuleerd in termen van f p. Als er wel een fout optreedt, levert fopen de nulpointer NULL af. Een fout bij het openen van een file wordt meestal opgevangen met een constructie van deze vorm: if ( (fp = fopen ("data", "r") ) == NULL) printf ("Kan datafile niet openen \n") else Als het verwerken van een file afgelopen is, moet de file worden gesloten. Bij het sluiten van de file of stroom worden eventuele interne buffers geleegd. Dit wordt gedaan met de functie fclose: int fclose(file *fp) De functie fclose heeft één argument fp, dat de filepointer is die fopen voor de te sluiten file heeft afgeleverd. Als er een fout optreedt, levert fclose de waarde EOF af, anders de waarde nul. De waarde EOF wordt gebruikt om het eind van een file aan te geven. Het volgende programma opent een file en sluit deze onmiddellijk weer; de naam van de file wordt als argument op de commandoregel opgegeven. Er wordt niet van uitgegaan dat de file al in het filegeheugen voorkomt. Daarom wordt de file in leesmode geopend. Er wordt gecontroleerd of het openen en het sluiten slagen. Programma 14-1 /* Programma om de functies 'fopen' en fclose te demonstreren. De file waarvan de naam als argument op de commandoregel wordt opgegeven, wordt eerst geopend en dan meteen gesloten. De afgeleverde code wordt gecontroleerd. */

#include <stdio.h> #define SCHRIJVEN w main(int argc, char *argv[]) { FILE *fp; if(argc!= 2) printf( Gebruik: %s filenaam \n, argv[0]); else if ((fp = fopen(argv[1],schrijven)) == NULL) printf("%s: kan %s niet openen \n,argv[0l, argv[1]); else { printf( openen van %s is geslaagd \n", argv[1]); if(fclose(fp) == EOF) printf ( %s: kan %s niet sluiten \n,argv[ 0],argv[1]); else printf( sluiten van %s is geslaagd \n", argv[1]); 14.2 De functies fgetc, fputc en feof De functie fgetc leest één karakter uit een file. Het gedrag van de functie is gelijk aan dat van de functie getchar die we al hebben gezien. De prototypedeclaratie voor de functie fgetc is: int fgetc(file *fp) waarin fp een filepointer is die door de functie fopen is afgeleverd. Het uitvoeren van de statement c = fgetc(fp); heeft als effect dat er één karakter wordt gelezen uit de stroom die fp aangeeft. Verdere karakters worden uit dezelfde stroom gelezen met volgende aanroepen van fgetc. De functie fgetc levert de waarde EOF af als het einde van de file is bereikt Dit niet-bestaande karakter kan bijvoorbeeld in een besturende conditie van een lus worden gebruikt om de karakters in een file een voor een te lezen en te verwerken: while ((c = fgetc(fp))!= EOF)... Het is wel van belang dat de variabele c die wordt gebruikt voor het door fgetc gelezen karakter, van type int is. Anders werkt de lus niet goed. Neem bijvoorbeeld eens aan dat we werken met een machine met integers van 16 bits en dat EOF de waarde - 1 is. De representatie voor EOF is dan:

-1 (decimaal) = 1111111111111111 (binair) Als fgetc de waarde EOF aflevert en deze aan de variabele c wordt toegekend die ten onrechte is gedeclareerd als een karaktervariabele van bijvoorbeeld 8 bits, dan worden aan c de acht minst significante bits van het resultaat van fgetc (dus van EOF) toegekend, namelijk: c = 11111111 (binair) De test herkent de waarde EOF dan niet, omdat 11111111!= 11111111 11111111 de logische waarde TRUE oplevert. De lus gaat dan eeuwig door. Om expliciet aan te geven dat een variabele als karaktervariabele moet fungeren, maar als integer wordt gerepresenteerd om EOF goed te detecteren, raden we u aan de typenaam Karakter of Character in te voeren. De naam suggereert het type van de variabele, maar verbergt de representatie als int: typedef int Character; Character c; while ( (c = fgetc (fp))!= EOF)... De functie fgetc levert EOF af als het einde van de file wordt gedetecteerd of als er een fout is opgetreden. Om te bepalen of het einde van de file inderdaad is bereikt, moet de functie feof worden gebruikt. Het prototype voor feof is: int feof(file *fp) Als het einde van de opgegeven file is bereikt, levert feof een waarde ongelijk aan nul af (voor de logische waarde TRUE), en anders nul (voor FALSE). We kunnen dan met een lus karakter voor karakter door een file gaan tot het einde van de file is bereikt of tot er een fout is opgetreden: while ( (c = fgetc (fp)!= EOF) /* verwerk karakter c */ Vervolgens kunnen we vaststellen of het om een fout of om het echte einde van de file gaat: if(! feof(f)) printf("leesfout\n"); else /* afwerking */

De functie getchar, die gebruikt wordt om één karakter uit de standaardinvoer te lezen, levert dezelfde code EOF af. Dus alle tot nu toe geschreven programma's die die karakter voor karakter lazen uit de standaardinvoer en naar een expliciet afsluitend karakter zochten, hadden we ook zo kunnen schrijven dat ze EOF detecteerden. Programma 6-1 echode bijvoorbeeld karakters van de standaardinvoer naar de standaarduitvoer. Het programma gebruikte de punt als afsluiten. Als we het programma herschrijven in termen van EOF en het puntkarakter niet meer gebruiken, zodat dit ook in de invoerdata kan voorkomen, krijgen we programma 14-2. Programma 14-2 /* Kopieer een stroom karakters uit de standaardinvoer naar de standaarduitvoer. Het kopiëren eindigt als EOF in de invoer wordt gelezen. */ #include <stdio.h> typedef int Character; main() { Character c; while ((getchar())!= EOF) putchar(c); Om dit programma te laten eindigen, moeten we op het toetsenbord het karakter invoeren dat met EOF overeenkomt. Welk karakter dit is, hangt van het systeem af. Op de meeste UNIXsystemen wordt EOF gerepresenteerd door Ctrl-D. In een MS-DOS-omgeving is het Ctrl-Z. De functie fputc schrijft één karakter naar een file. Het gedrag van de functie is verder gelijk aan dat van de functie putchar, die we al uitgebreid hebben gebruikt. De prototypedeclaratie voor de functie f put c is: int fputc(int c, FILE *fp) Als fputc erin slaagt het karakter naar de opgegeven stroom te schrijven, wordt het karakter in de vorm van een waarde van type int als resultaat afgeleverd. Als er een fout optreedt, levert fputc de waarde EOF af. Nu we de nodige voorbereidingen hebben gemaakt, kunnen we een programma kopieer schrijven om de inhoud van de ene file naar de andere file te kopiëren. De twee filenamen worden als argumenten op de commandoregel opgegeven, met de naam van de bronfile voorop. U ziet dat het grootste deel van het programma uit controle op fouten bestaat. Hierop komen we binnenkort terug.

Programma 14-3 /* Kopieer de inhoud van een bronfile naar een doelfile. De twee filenamen worden als argumenten op de commandoregel opgegeven. De werking van de primitieve functies wordt volledig gecontroleerd. */ #include <stdio.h> typedef int Character; #define LEZEN r #define SCHRIJVEN w main(int argc, char *argv[]) { FILE *fpbron, *fpdoel; Character c; if(argc!= 3) printf( Gebruik: %s file1 file2\n", argv[0]); else if ((fpbron = fopen(argv[1], LEZEN)) == NULL) printf ( %s: kan %s niet openen \n", argv[0 ], argv[1]); else if ((fpdoel = fopen(argv[2], SCHRIJVEN)) == NULL) { printf ("%s: kan %s niet openen \n, argv[0], argv[2]); if (fclose(fpbron) == EOF) printf("%s: %s is niet correct gesloten\n, argv[0], argv[1]); else { while ((c = fgetc(fpbron))!= EOF) if(fputc(c, fpdoel) == EOF) { printf("%s: fout bij schrijven naar %s\n" argv[0l, argv[2]); break; if (! feof(fpbron)) printf("%s: fout bij lezen van %s\n, argv[0], argv[1]); if(fclose(fpbron) == EOF) printf("%s: %s niet correct gesloten\n", argv[0], argv[1]); if(fclose(fpdoel) == EOF) printf("%s: %s niet correct gesloten\n", argv[0l, argv[2]);

14.3 De functies fscanf en fprintf De functies fscanf en fprintf voeren ongeveer hetzelfde als de functies scanf en printf oeen file uit. Deze nieuwe functies hebben beide een extra argument, dat de filepointer naar de gewenste file is. De prototypedeclaraties zijn: int fscanf (FILE *fp, char *format,...) en int fprintf (FILE *fp, char *format,...) Het uitvoeren van fscanf kan vroegtijdig eindigen. Dit kan te wijten zijn aan het lezen van EOF in de gespecificeerde stroom of aan een verschil tussen de besturingsstring (format) en een invoerkarakter. Als de invoer het einde van de file detecteert voordat er een verschil is geconstateerd, levert fscanf de waarde EOF af. Anders levert fscanf het aantal geslaagde toekenningen af. Hier volgt een codefragment dat twee integer waarden uit een file leest en de noodzakelijke controle uitvoert om te zien of het correcte aantal data wordt gelezen en het eind van de file nog niet is bereikt. status = fscanf(fpin, "%d %d", &eerste, &tweede); if (status!= 2) { if (status == EOF) printf( Onverwacht einde van file \n"); else printf( Fout bij lezen uit file \n"); else {. /* verwerk gelezen data */ De door fprintf afgeleverde waarde is negatief als er een fout is opgetreden; anders wordt het aantal geschreven karakters afgeleverd. We kunnen dus als volgt de twee uit een file gelezen waarden naar een file schrijven: status = fscanf (fpin, "%d %d, &eerste, &tweede); if (status! = 2) ( if (status == EOF) printf ("Onverwacht einde van file \n ); else printf ("Fout bij lezen uit file \n ); else if (fprintf (fpuit, "%d %d \n", eerste, tweede) < 0) printf ("Fout bij schrijven naar file \n") ; De foutcodes die fscanf en fprintf afleveren, worden ook door de functies scanf en printf afgeleverd. Dezelfde controle als we zo juist hebben gezien, kan ook op de standaardinvoer en de standaarduitvoer worden toegepast.

14.4 De functies fgets en fputs Om hele regels data uit en naar files te lezen kunnen de functies fgets en fputs worden gebruikt. De functie fgets heeft drie argumenten: een string s, een teller n en een stroom fp die geopend moet zijn om uit te lezen: char *fgets(char *s, int n, FILE *fp); Er wordt aangenomen dat het argument s wijst naar het begin van een karakterarray. De karakters worden uit de opgegeven stroom gelezen en in opeenvolgende locaties in de array geplaatst. Het lezen gaat door tot het einde van de file is bereikt, tot er een newline wordt aangetroffen of zolang het aantal ingevoerde karakters niet groter is dan n. Als de invoer afgelopen is, wordt er een afsluitend nulkarakter achter de opgeslagen karakters geplaatst. Als het lezen geslaagd is, wordt er een pointer naar de array s afgeleverd. Als tijdens het lezen een fout optreedt of als het einde van de file wordt bereikt, wordt de nulpointer NULL afgeleverd. Fouten kunnen dus op de gebruikelijke manier worden opgevangen: if (fgets(regel, GROOTTE, fpin) == NULL)... De prototypedeclaratie voor de functie fputs is: int fputs(char *s, FILE *fp); De functie fputs schrijft een door het nulkarakter afgesloten string s naar de string waarnaar fp wijst, die geopend moet zijn om erin te schrijven. Als tijdens het schrijven een fout optreedt levert fputs de waarde EOF af. We hebben in de meeste programma's in dit boek #include gebruikt. In het volgende voorbeeld ziet u hoe we deze nuttige faciliteit kunnen implementeren. De schets voor onze versie van include is: WHILE haal één regel uit de bronfile; niet einde van file DO IF de regel begint met #include THEN neem deze nieuwe file op ELSE schrijf deze regel naar de doelfile ENDIF ENDWHILE Als de ten gevolge van #include opgenomen file zelf weer een #include bevat, leidt dit natuurlijk tot een recursieve oplossing. Een dergelijke nesting is nuttig en wordt in C geïmplementeerd door middel van een recursieve implementatie van de functie.

Het programma wordt aangeroepen met de commandoregel: include bronfilenaam doelfilenaam Zoals in het POT-ontwerp beschreven, worden de regels tekst van de bronfile naar de doelfile gekopieerd. Als een regel uit de bronfile de vorm #include filenaam heeft, is de include-operatie van toepassing op deze nieuwe bronfile, en wordt de uitvoer weer naar dezelfde doelfile gezonden. Als de nieuwe bronfile verwerkt is, gaat het kopiëren door bij de oorspronkelijke bronfile of eindigt het programma als het de eerste bronfile is. Programma 14-4 /* Kopieer de inhoud van een bronfile naar een doelfile. De namen van de twee files worden als argumenten op de commandoregel gegeven. Als in de bronfile een tekstregel van de vorm: #include filenaam voorkomt, wordt deze vervangen door de inhoud van de genoemde file. De include-statements mogen genest voorkomen tot een diepte gelijk aan het maximale aantal files dat in het systeem tegelijk open kan zijn. */ #include <stdio.h> #include <string.h> #define LEZEN r #define SCHRIJVEN w #define REGELLENGTE 256 #define INCLUDE "#include" void include_file(file *, FILE *); main(int argc, char *argv[]) { FILE *fpbron, fp*doel; if(argc!= 3) printf( Gebruik: %s filel file2\nl" argv[0]); else if((fpbron = fopen(argv[1], LEZEN)) == NULL) printf ( %s: kan %s niet openen \n" argv[0], argv[1]); else if((fpdoel = fopen(argv[2], SCHRIJVEN)) == NULL) { printf ("%s: kan %s niet openen\n" argv[0l, argv[2]); fclose(fpbron); else {

include_file(fpbron, fpdoel); fclose(fpbron); fclose(fpdoel); /* Kopieer de bronfile regel voor regel naar de doelfile. Expandeer regels die met #include beginnen. Nesting van #include is toegestaan. */ void include_file(file *fpin, FILE *fpuit) { char regel[regellengte],woord1[regellengte],woord2[regellengte]; FILE *fp; while (fgets(regel( REGELLENGTE, fpin)!= NULL) /* eof invoer? */ if(sscanf(regel, "%s %s", woord1, woord2)!= 2) /* 2 woorden? */ fputs(regel, fpuit); /* kopieer gewoon */ else if (strcmp(woord1, INCLUDE)!= 0) /* #include-regel? */ fputs(regel, fpuit); /* nee, kopieer */ else if ((fp = fopen(woord2, LEZE)) == NULL) printf( Kan %s niet openen genegeerd \n, woord2); else { include_file(fp, fpuit); /* geneste include */ fclose(fp); 14.5 De files' stdin, stdout en stderr Als een C-programma wordt uitgevoerd, worden drie 'files' automatisch geopend door het systeem, dat er filepointers voor aflevert. Dit zijn de files standaardinvoer, standaarduitvoer en standaardfout. De pointers hebben de namen stdin stdout en stderr. Deze pointers zijn gedefinieerd in de standaardi/0-header <stdio.h>. Het zijn constanten, geen variabelen. We kunnen er dus geen waarde aan toekennen. Normaal horen deze files bij de terminal van de gebruiker. Alle standaard-i/0functies die invoer doen en geen filepointer als argument hebben (getchar, gets en scanf) halen hun invoer uit stdin De aanroep fscanf (stdin, "%d", &getal); leest de volgende integer waarde uit de standaardinvoer en is equivalent met de aanroep

scanf("%d, &getal); Alle standaard-i/o-functies die uitvoer doen en geen filepointer als argument hebben (putchar, puts en printf) leveren hun uitvoer in stdout af. De aanroep fprintf (stdout, "Mijn eerste programma \n ) is equivalent met printf("mijn eerste programma \n ); De 'functies' die één karakter uit de standaardinvoer halen of naar de standaarduitvoer brengen, worden meestal als macro's geïmplementeerd. De macro's putc en getc zijn equivalent met de echte functies fputc en fgetc. Ze zijn als macro's geschreven, maar kunnen worden beschouwd als functies met de volgende declaraties: int putc(int c, FILE *fp) en int getc(file *fp) De 'functies' getchar en putchar kunnen als volgt in termen van getc, putc, stdin en stdout worden gedefinieerd: #define getchar() getc(stdin) #define putchar(c) putc((c), stdout) Tenslotte is er nog de filepointer stderr. Het is de bedoeling dat eventuele foutmeldingen die door een programma worden geproduceerd, naar deze 'standaardfoutstroom' worden geschreven. In een interactieve omgeving wordt deze stroom gekoppeld aan de terminal van de gebruiker. De reden voor het bestaan van stderr is dat foutrneldingen naar een ander apparaat kunnen worden gestuurd dan waarheen de normale uitvoer wordt geschreven. Dat is vooral wenselijk als de uitvoer van een programma wordt geleid naar een file of via een pipe wordt doorgestuurd. In de programma's in dit hoofdstuk moeten foutrneldingen daarom eigenlijk worden afgeleverd door middel van statements als: if ((fp = fopen(argv[1]), LEZEN)) == NULL) fprintf(stderr, "%s: kan %s niet openen\n", argv[0l, argv[1]); waarin fprintf en stderr worden gebruikt in plaats van printf en stdout.

14.6 De functie exit en de afhandeling van fouten Een programma eindigt automatisch als de laatste statement in de functie main is uitgevoerd. Soms kan het wenselijk zijn een programma geforceerd te beëindigen als er een fout is gedetecteerd. Om een programma expliciet af te breken, kunnen we de functie exit aanroepen. De functieaanroep exit(n); heeft tot gevolg dat het actuele programma wordt beëindigd, dat de open files worden geleegd en gesloten en dat de waarde van de integer n wordt doorgegeven aan het proces in het systeem dat voor het activeren van dit programma verantwoordelijk was (bijvoorbeeld het besturingssysteem). Onder UNIX is het de gewoonte dat de integer waarde 0 aangeeft dat de uitvoering van een programma geslaagd is; een waarde ongelijk aan nul geeft aan dat een programma geëindigd is omdat er een fout is gedetecteerd. De symbolische waarden EXIT- FAILURE en EXIT-SUCCESS kunnen als argument voor de functie worden gebruikt. Deze symbolische waarden zijn in <stdlib.h> gedefinieerd. In alle programma's in dit hoofdstuk hebben we veel aan controle op fouten gedaan, waardoor de logica van het programma enigszins op de achtergrond raakte. We kunnen de code wat overzichtelijker maken door de foutendiagnostiek te delegeren aan een functie genaamd meldfout. Als deze functie wordt aangeroepen, wordt er een foutmelding afgeleverd, worden alle open files gesloten en wordt het programma beëindigd. 14.7 Directe toegang De standaardbibliotheek bevat functies voor een aantal verschillende filestructuren. De eenvoudigste fileorganisatie is de seriële file - dit is het type file waarmee we tot nu toe hebben gewerkt. Een seriële file wordt opgebouwd door aan het eind van de file nieuwe records te schrijven. Als de file daarna wordt gelezen, worden de componenten in dezelfde volgorde ingevoerd als waarin ze er eerst in zijn geschreven. De standaard-i/o-bibliodieek kan ook werken met een andere filestructuur, waarin directe toegang tot een gespecificeerde component van de file mogelijk is. Een file met directe toegang kan worden beschouwd als een aantal datablokken of recordgebieden. Elk recordgebied heeft een vaste omvang, uitgedrukt in bytes. Om een record te lezen (of te schrijven) moeten we eerst de leeskop (of schrijfkop) op de eerste byte van het recordgebied positioneren. Dan wordt het vereiste aantal bytes overgebracht naar (of uit) het geheugen. Met de functie fseek is directe toegang in een file mogelijk. De header voor de functies fseek is:

int fseek(file *fp, long offset, int origin) Het eerste argument moet een filepointer zijn naar een file die geopend is voor invoer, uitvoer of beide. Het derde argument moet de waarde SEEK_SET, SEEK_CUR of SEEK_END hebben (gedefinieerd in <stdio.h>). Als het SEEK_SET is, wordt de positie in de file gelijk gemaakt aan de waarde van het tweede argument, gerekend als aantal bytes. De eerste byte van de file heeft het nummer 0. De waarde SEEK_SET van het argument origin wordt gebruikt als we de lees/schrijfkop op een absolute positie in de file willen zetten. Zo zet de aanroep fseek(fp, 0L, SEEK_SET) de kop aan het begin van de file. Als het derde argument SEEK_CUR is, wordt de positie in de file gelijk gemaakt aan de actuele positie plus de van een teken voorziene offset. Een positieve offset verplaatst de positie in de richting tegengesteld aan die van het begin van de file, terwijl een negatieve offset een verplaatsing in de richting van het begin van de file veroorzaakt. De waarde SEEK_CUR voor origin wordt gebruikt voor het relatief positioneren binnen de file. Als het derde argument SEEK_END is, wordt de positie gelijk gemaakt aan het eind van de file plus de (van een teken voorziene) offset. Deze mogelijkheid wordt vaak gebruikt om een file uit te breiden. Om naar het eind van een file te gaan alvorens bijvoorbeeld te schrijven, gebruiken we de aanroep fseek(fp, 0L, SEEK_END) Als de operatie slaagt, levert fseek de waarde 0 af, anders een waarde ongelijk aan nul. Gewoonlijk wordt het resultaat getest met een constructie van de vorm: if(fseek(fp, offset, origin)!= 0) fprintf(stderr, "Fout bij fseek \n ); else Als de gewenste positie in de file met fseek is opgezocht, begint het lezen of schrijven daarna op die positie. De invoer en uitvoer kan worden gedaan met de functies voor file-i/0, zoals fscanf en fprint f of met twee nieuwe functies fread en fwrite. De functie fread leest een blok binaire data naar een gespecificeerde geheugenbuffer. De header van de functie is: size_t fread(void *buffer, size_t size, size_t count, FILE *fp);

Het eerste argument is een pointer naar de buffer waarheen de invoer moet worden gelezen. Het tweede argument is de grootte van de elementen in de buffer (size_t is de naam voor een unsigned integer type gedefinieerd in <stddef.h»; als het eerste argument van een type T is, wordt het tweede argument in het algemeen berekend met de expressie sizeof(t). Het totale aantal uit de file gelezen bytes is size* count het vierde argument is een pointer naar een file die geopend is voor invoer. Om bijvoorbeeld twintig karakters in een array in te voeren, gebruiken we de aanroep: char tabel[20]; fread(tabel, sizeof(char), 20, fp); Het volgende voorbeeld leest twintig integers in een array: int stapel [20]; fread(stapel, sizeof(int, 20, fp); Het aantal werkelijk gelezen elementen wordt door de functie fread als resultaat afgeleverd. In het algemeen wordt deze waarde getest om te zien of de operatie geslaagd is. Als de afgeleverde waarde kleiner is dan het aantal in count, moeten de functies foef en ferror worden gebruikt om de status te achterhalen. De functie fread wordt vaak zo gebruikt: if ((aantal_gelezen = fread(stapel, sizeof(int), 20, fp))!= 20 ) { if (feof (fp) ) fprintf(stderr, "Einde file gevonden \n"); else fprintf(stderr, "Leesfout\n"); De tegenhanger van fread - voor het schrijven van een blok binaire data uit een geheugenbuffer naar een file - is fwrite. De header is: size_t fwrite (void *buffer, size_t size,size_t count, FILE *fp) De functie fwrite levert het aantal feitelijk gelezen elementen af. Als er een fout optreedt, is de afgeleverde waarde kleiner dan count.

14.8 Samenvatting Een file is een verzameling records van een of ander type. De organisatie van een file bepaalt hoe de records van een file worden verwerkt; twee organisatievormen zijn serieel en random toegang. Een seriële file wordt opgebouwd door nieuwe records aan het eind van de file te schrijven. Als de file later wordt gelezen, worden de element in dezelfde volgorde ingevoerd als waarin ze erin zijn geschreven. Bij een file met directe toegang kan naar/ uit elke positie in de file worden geschreven of gelezen. Voordat er in een file kan worden gelezen of geschreven, moet de file worden geopend. Dat kan gebeuren met de bibliotheekfunctie fopen, die een interne filenaam (filepointer) aflevert, die dan in volgende I/0-operaties wordt gebruikt. Als het verwerken van een file afgelopen is, moet de file gesloten worden met de functie fc1ose. De bibliotheekfunctie exit maakt geopende files leeg en sluit ze alvorens een programma te beëindigen. De functies fgetc, fputc, fscanf, enzovoort zorgen voor seriële verwerking van een file. Directe toegang tot een file is mogelijk met de functies fseek en rewind. In elk C-programma bestaan drie files met de filepointers stdin, stdout en stderr. De aanroep fgetc (stdin) is bijvoorbeeld equivalent met getchar(). Een aantal voorbeelden voor het gebruik van de ANSI C filefuncties kan men vinden in de file use_file.cpp.