Lezen uit en schrijven in tekstfiles.

© Harry Broeders.

Deze pagina is bestemd voor studenten van de Haagse Hogeschool - Academie voor Technology, Innovation & Society Delft.

De programmeertaal C onderscheidt 2 soorten files: tekstfiles en binaire files. Deze webpagina behandeld aleen tekstfiles. Meer informatie over binaire files kun je hier vinden.

Tekstfiles.

Je kunt de volgende bewerkingen op tekstfiles uitvoeren:

Tekstfile openen.

Om een tekstfile te kunnen lezen of beschrijven moet de file eerst geopend worden. Je kunt een file openen met de functie fopen. Deze functie is in de include file stdio.h als volgt gedeclareerd:

FILE *fopen(const char *filename, const char *mode);

Het returntype is een FILE*. Dit wordt een filepointer genoemd. Deze filepointer kun je, als de file eenmaal geopend is, gebruiken om uit de file te lezen of om in de file te schrijven. Deze pointer wijst niet rechtstreeks naar de file. Dat kan namelijk niet want de file bevindt zich niet in het werkgeheugen (RAM) van de PC maar in het achtergrond geheugen (bijvoorbeeld op de harddisk). De filepointer wijst naar een zogenaamde file control structure waarin informatie wordt bijgehouden waarmee het operating systeem de echte file kan benaderen. Een filepointer zou dus eigenlijk file control structure pointer genoemd moeten worden.

Via de parameter filename kun je de naam van de file die je wilt openen meegeven. Als je alleen een naam opgeeft wordt de file in het "huidige" directory opgezocht. Je kunt ook de relatieve of absolute padnaam opgeven. Let er daarbij op dat de backslash in C strings gebruikt wordt als escape karakter. Één backslash moet dus worden ingetyped als twee backslashes.
Bijvoorbeeld "C:\\Mijn documenten\\testbestand.txt".

Via de parameter mode kun je opgeven hoe je de file wilt openen:
"r"    Open om te lezen (read). Als de file niet bestaat is dat een fout.
"w"    Open om te schrijven (write). Als de file niet bestaat dan wordt deze aangemaakt. Als de file al bestaat wordt deze file overschreven.
"a"    Open om te schrijven aan het einde van de file (append). Als de file niet bestaat dan wordt deze aangemaakt.
"r+"   Open om te lezen en te schrijven. Als de file niet bestaat is dat een fout.
"w+"   Open om te lezen en te schrijven. Als de file niet bestaat dan wordt deze aangemaakt. Als de file al bestaat wordt deze file overschreven.
"a+"   Open om te lezen en te schrijven aan het einde van de file. Als de file niet bestaat dan wordt deze aangemaakt.

Als alles goed gaat geeft fopen een geldige FILE* terug. Als er een fout optreedt (bijvoorbeeld omdat de file die je wilt lezen niet bestaat of omdat je een file wilt schrijven naar een CD-ROM) geeft fopen de waarde NULL terug. NULL is gedefineerd in stdio.h.

Tekstfile sluiten.

Nadat de filepointer gebruikt is om uit de file te lezen of om in de file te schrijven moet de file weer worden gesloten. Het is belangrijk om een file zo snel mogelijk te sluiten omdat andere applicaties de file niet kunnen openen zolang de file al geopend is. Als een programma eindigt zal het operating systeem alle open files zelf sluiten. Het is echter netter als het programma dit zelf doet met de functie fclose. Deze functie is in de include file stdio.h als volgt gedeclareerd:

int fclose(FILE *stream);

Deze functie geeft de integer waarde EOF terug als er een fout optreed. Als parameter moet een FILE* worden meegegeven die je hebt teruggekregen van de functie fopen.

Tekstfile lezen.

Er zijn verschillende functies beschikbaar om uit een tekstfile te lezen.

getc.

De eenvoudigste functie is getc. Deze functie leest 1 karakter uit de file en is in de include file stdio.h als volgt gedeclareerd:

int getc(FILE *stream);

Als parameter moet een FILE* worden meegegeven die je hebt teruggekregen van de functie fopen.

Deze functie geeft geen char terug maar een int! Als het lukt om een karakter uit de file te lezen geeft getc (de ASCII waarde van) het ingelezen karakter terug. De functie geeft de speciale integer waarde EOF terug als er iets fout gaat. EOF is gedefinieerd in stdio.h. Doordat de functie een int teruggeeft kan elke char waarde worden teruggegeven plus de speciale errorcode EOF.

Let goed op:

Het programma showfile.c leest de file testfile.txt  karakter voor karakter in en schrijft deze karakters naar het beeldscherm.

#include <stdio.h>

int main(void) {
    int c;
    FILE *fp;
    fp = fopen("testfile.txt", "r");
    if (fp == NULL) {
        printf("Kan file testfile.txt niet openen!");
    }
    else {
        c = getc(fp);
        while (c != EOF) {
            putchar(c);
            c = getc(fp);
        }
        fclose(fp);
    }
    getchar();
    return 0;
}

Als in de file testfile.txt de volgende regel staat: "In het Nederlands is een ij geen ÿ en ook geen y." Dan wordt deze regel als volgt afgedrukt op het scherm:

De ÿ wordt niet afgedrukt omdat dit karakter in het, in het console window gebruikte, lettertype niet bestaat.

Als in het bovenstaande programma de regel:

    int c;

vervangen wordt door:

    char c;

dan geeft dit programma de volgende uitvoer:

Je ziet dat het programma nu stopt voordat het eind van de file bereikt is! Deze fout wordt veroorzaakt doordat het karakter ÿ wordt aangezien voor EOF.

fscanf.

In plaats van karakter voor karakter in te lezen kun je ook zogenaamde geformateerde invoer doen. Dit houdt in dat de ingelezen karakters worden omgezet naar het gewenste type. Je bent al bekend met geformateerde invoer vanaf het toetsenbord met de functie scanf. Geformateerde invoer vanuit een file gaat op vergelijkbare wijze met de functie fscanf. Deze functie is in de include file stdio.h als volgt gedeclareerd:

int fscanf(FILE *stream, const char *format, ...);

Als eerste parameter moet een FILE* worden meegegeven die je hebt teruggekregen van de functie fopen. De overige parameters zijn hetzelfde als bij de al bekende functie scanf.

De functie geeft de integer waarde EOF terug als er iets fout gaat bij het inlezen. Anders geeft de functie het aantal variabelen terug dat succesvol is geconverteerd en ingelezen. Zie onderstaande voorbeeldprogramma scan.c :

#include <stdio.h>

int main(void) {
    int i;
    int ret;
    FILE *fp;
    fp = fopen("getallenfile.txt", "r");
    if (fp == NULL) {
        printf("Kan file getallenfile.txt niet openen!");
    }
    else {
        do {
            ret = fscanf(fp, "%d", &i);
            switch (ret) {
                case 0:
                    printf("Dat was geen getal!\n");
                    printf("Maar het karakter %c.\n", getc(fp));
                    break;
                case 1:
                    printf("Dat was het getal %d.\n", i);
                    break;
                case EOF:
                    printf("Dat was het einde.\n");
                    break;
                default:
                    printf("Dat kan niet!\n");
                    break;
            }
        }
        while (ret != EOF);
        fclose(fp);
    }
    getchar();
    return 0;
}

Als in de file getallenfile.txt  de volgende regel staat: "1234 a56789" Dan wordt het volgende afgedrukt op het scherm:

Met behulp van scanf en fscanf kun je ook woorden inlezen vanaf het toetsenbord of vanuit een file. Deze woorden worden dan opgeslagen in een char array. Hierbij moet je goed oppassen voor buffer overrun. Dit probleem treedt op als de invoer meer karakters bevat als de char array kan bevatten. Voorbeeld:

#include <stdio.h>
#include <string.h>

int main(void) {
    char password[] = "Geheim", buffer[10];
    printf("Geef password: ");
    scanf("%s", buffer);
    if (strncmp(password, buffer, 7) == 0) {
        printf("Toegang tot geheime informatie.");
    }
    else {
        printf("Onjuist password!");
    }
    fflush(stdin);
    getchar();
    return 0;
}

De functie strncmp hebben we nog niet behandeld. Deze functie uit string.h vergelijkt een aantal karakters van twee als parameters meegegeven strings. Het aantal karakters dat vergeleken moet worden wordt als derde parameter meegegeven. Deze functie geeft 0 terug als de strings gelijk zijn (waarbij alleen gekeken wordt naar het aantal meegegeven karakters).

Je zou denken dat je alleen toegang tot de geheime informatie krijgt als je het password kent1. Als dit programma met Microsoft Visual C++ wordt gecompileerd blijkt dat de variabele password 20 bytes na de variabele buffer in het geheugen wordt geplaatst. Als we meer dan 10 karakters invoeren worden deze karakters allemaal in het geheugen weggeschreven (buffer overrun). Op deze manier kan ook de variabele password overschreven worden.

Buffer overrun kun je eenvoudig voorkomen door in de functies scanf en fscanf altijd een maximaal in te lezen karakters bij het format %s te gebruiken. In het bovenstaande programma moet de regel:

    scanf("%s", buffer);

worden vervangen door:

    scanf("%9s", buffer);

De functie zal nu hooguit 9 karakters inlezen (en de tiende plaats in de buffer vullen met het nul karakter).

1 Ik ben er wel vanuit gegaan dat de gebruiker alleen uitvoerrechten heeft voor het programma en geen leesrechten! Anders zou de gebruiker namelijk eenvoudig het geheime password kunnen opsporen door de bufferoverrun.exe file in notepad te laden. Als de gebruiker ook leesrechten heeft op de executable moeten we het password "encrypted" in de exe file opslaan.

fgets en andere functies.

Er zijn nog meer functies gedeclareerd in stdio.h om informatie uit tekstfiles te kunnen lezen, bijvoorbeeld fgets. Deze functies worden hier niet verder behandeld maar kunnen in de cppreference worden opgezocht.

Tekstfile schrijven.

Er zijn verschillende functies beschikbaar om in een tekstfile te schrijven.

putc.

De eenvoudigste functie is putc. Deze functie schrijft 1 karakter in de file en is in de include file stdio.h als volgt gedeclareerd:

int putc(int c, FILE *stream);

Als eerste parameter moet het karakter worden meegegeven dat in de file moet worden weggeschreven. Als tweede parameter moet een FILE* worden meegegeven die je hebt teruggekregen van de functie fopen.

Als het lukt om een karakter in de file te schrijven geeft putc het weggeschreven karakter terug. De functie geeft de speciale integer waarde EOF terug als er iets fout gaat.

Het programma toupper.c kopieert de file infile.txt  naar de file outfile.txt  waarbij alle kleine letters worden omgezet naar hoofdletters.

#include <stdio.h>
#include <ctype.h>

int main(void) {
    int c;
    FILE *infile, *outfile;
    infile = fopen("infile.txt", "r");
    if (infile == NULL) {
        printf("Kan file infile.txt niet openen!");
    }
    else {
        outfile = fopen("outfile.txt", "w");
        if (outfile == NULL) {
            printf("Kan file outfile.txt niet openen!");
        }
        else {
            c = getc(infile);
            while (c != EOF) {
                c = putc(toupper(c), outfile);
                if (c == EOF) {
                    printf("Fout tijdens schrijven in outfile.txt!");
                }
                else {
                    c = getc(infile);
                }
            }
            fclose(outfile);
        }
        fclose(infile);
    }
    getchar();
    return 0;
}

fprintf.

In plaats van karakter voor karakter weg te schrijven kun je ook zogenaamde geformateerde uitvoer doen. Dit houdt in dat variabelen van verschillende typen in een opgegeven formaat worden omgezet naar karakters die naar de file worden geschreven. Je bent al bekend met geformateerde uitvoer naar het beeldscherm met de functie printf. Geformateerde uitvoer naar een file gaat op vergelijkbare wijze met de functie fprintf. Deze functie is in de include file stdio.h als volgt gedeclareerd:

int fprintf(FILE *stream, const char *format, ...);

Als eerste parameter moet een FILE* worden meegegeven die je hebt teruggekregen van de functie fopen. De overige parameters zijn hetzelfde als bij de al bekende functie printf.

De functie geeft de integer waarde EOF terug als er iets fout gaat bij het wegschrijven. Anders geeft de functie het aantal karakters terug dat succesvol is weggeschreven.

In het volgende voorbeeldprogramma printf_123.c wordt aan de integer variabele i de waarde 123 toegekend en daarna wordt deze variabele weggeschreven in een tekstfile. (Dit is natuurlijk volkomen zinloos maar maakt wel duidelijk hoe het werkt.)

#include <stdio.h>

int main(void) {
    int i = 123;
    FILE *fp;
    int ret;
    fp = fopen("123.txt", "w");
    if (fp == NULL) {
        printf("Kan file 123.txt niet openen!");
    }
    else {
        ret = fprintf(fp, "%d", i);
        if (ret == EOF) {
            printf("Er is iets fout gegaan bij schrijven in file 123.txt!");
        }
        else {
            printf("Er zijn %d karakters geschreven in file 123.txt!", ret);
        }
        fclose(fp);
    }
    getchar();
    return 0;
}

De uitvoer van dit programma is als volgt:

In het directory waar de executable file is uitgevoerd is nu de file 123.txt aangemaakt. In de filebrowser kunnen de "eigenschappen" van deze file bekeken worden. De file blijkt 3 bytes groot te zijn. Deze file kan met notepad worden bekeken:

Met een zogenaamde hexeditor kunnen we de inhoud van de file zowel in hexadecimale codering als in ASCII codering bekijken. Een eenvoudige hexeditor kun je downloaden vanaf: http://www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm.

In het deel met de grijze achtergrond zien we de hex codes van de bytes uit de file. In het deel met de witte achtergrond zien we de bijbehorende ASCII karakters. Het is nu duidelijk te zien dat de file bestaat uit 3 bytes. Er is geen end-of-file karakter! De drie bytes bevatten de ASCII codes voor karakter 1, karakter 2 en karakter 3.

fputs en andere functies.

Er zijn nog meer functies gedeclareerd in stdio.h om informatie naar tekstfiles te kunnen schrijven, bijvoorbeeld fputs. Deze functies worden hier niet verder behandeld maar kunnen in de cppreference worden opgezocht.

Diverse andere functies voor tekstfiles.

Er zijn nog diverse andere functies beschikbaar in stdio.h die voor tekstfiles gebruikt kunnen worden: feof, ferror, ffush, freopen, remove, rename, rewind, tmpfile enz. Deze functies worden hier niet verder behandeld maar kunnen via de voorgaande links in de cppreference worden opgezocht.