Lezen van structs uit en schrijven van structs in binaire files.

© Harry Broeders.

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

Informatie over structs kun je hier vinden: Het gebruik van structs.

Opslaan van een struct.

Een struct variabele kan eenvoudig worden opgeslagen (en weer worden teruggelezen uit) een binairefile:

void writeStudent(FILE* fp, Student s) {
fwrite(&s,
sizeof s, 1, fp); } void readStudent(FILE* fp, Student *ps) {
fread(ps,
sizeof *ps, 1, fp);
}

Meer informatie over het gebruik van binairefiles kun hier vinden.

Voorbeeld.

Bij het gebruik van binarefiles kun je met fseek heel snel een bepaalde struct vinden. Als je bijvoorbeeld de volgende struct gebruikt:

typedef struct {
    int sleutel;
    char naam[80];
} Persoon;

Dan kun je in een file waarin een aantal van deze structs zijn opgeslagen heel snel de persoon met een bepaalde sleutel vinden en uitlezen. Dit gaat als volgt:

    FILE* fp;
    Persoon p;
    fp = fopen("persoon.geg", "rb+");
    fseek(fp, sleutel * sizeof p, SEEK_SET);
    fread(&p, sizeof p, 1, fp);

Dit werkt echter alleen maar goed als de structs netjes op volgorde in de file zijn opgeslagen. Dus als eerste de struct met sleutel 0, daarna de struct met sleutel 1, daarna de struct met sleutel 2 enz. Bij het aanmaken van personen moet het programma dus zelf de sleutels toekennen.

Er ontstaat nu een probleem als je een persoon uit de file wilt verwijderen. Alle volgende structs moeten dan een plaatsje opschuiven en ook hun sleutels moeten worden aangepast. Als er nog andere bestanden zijn waarin naar personen wordt verwezen door middel van sleutels dan moeten ook in deze bestanden de sleutels worden aangepast! Het op deze manier verwijderen van een persoon uit de file is heel ingewikkeld en duurt bij grote files erg lang. Je kunt dit ook veel eenvoudiger oplossen door een struct niet echt te verwijderen maar alleen maar als leeg te markeren. In de struct moet je dan een extra veld toevoegen:

typedef struct {
    int sleutel;
    int leeg;
    char naam[80];
} Persoon;

Het veld leeg geeft aan of de struct "leeg" is. Bij het aanmaken van personen worden de sleutels netjes op volgorde toegekend en krijgt het veld leeg steeds de waarde 0. Als nu een persoon verwijderd moet worden dan krijgt het veld leeg van deze struct de waarde 1. Alle volgende structs blijven dus gewoon op dezelfde plaats staan en de sleutels wijzigen niet.

Bij het uitlezen van de file moet nu natuurlijk wel gecontroleerd worden of een struct niet leeg is. Bij het toevoegen van een nieuwe persoon moet je nu eerst kijken of er geen lege struct in de file staat. Deze lege struct met de bijbehorende sleutel kun je dan hergebruiken. Als er geen lege struct gevonden is dan moet de nieuwe persoon aan het einde van de file met de volgende sleutel worden toegevoegd.

In het onderstaande voorbeeldprogramma demobinfile.c kun je zien hoe je op deze manier personen kunt:

#include <stdio.h>

typedef struct {
    int sleutel;
    int leeg;
    char naam[80];
} Persoon;

void voegtoe(void) {
    FILE* fp;
    Persoon p;
    int gevonden = 0;
    fp = fopen("persoon.geg", "rb+");
    if (fp == NULL) {
        char antw;
        do {
            printf("Kan persoon.geg niet vinden. Nieuw bestand openen? (J/N): ");
            fflush(stdin);
        } while (scanf("%c", &antw) != 1 || antw != 'J' && antw != 'j' && antw != 'N' && antw != 'n');
        if (antw == 'J' || antw == 'j') {
            fp = fopen("persoon.geg", "wb+");
            if (fp == NULL) {
                printf("Error: Kan persoon.geg niet aanmaken.\n");
            }
        }
    }
    if (fp != NULL) {
        p.sleutel = -1;
        while (gevonden == 0 && fread(&p, sizeof p, 1, fp) == 1) {
            if (p.leeg == 1) {
                gevonden = 1;
            }
        }
        if (gevonden == 1) {
            fseek(fp, -1 * sizeof p, SEEK_CUR);
        }
        else {
            ++p.sleutel;
        }
        p.leeg = 0;
        do {
            printf("Geef naam voor persoon %d:\n", p.sleutel);
            fflush(stdin);
        } while (scanf("%79[ a-zA-Z]", p.naam) != 1);
        fwrite(&p, sizeof p, 1, fp);
        fclose(fp);
    }
}

void verwijder(void) {
    FILE* fp;
    fp = fopen("persoon.geg", "rb+");
    if (fp == NULL) {
        printf("Error: de file persoon.geg kan niet geopend worden.\n");
    }
    else {
        int sleutel;
        Persoon p;
        do {
            printf("Geef nummer: ");
            fflush(stdin);
        }
        while (scanf("%d", &sleutel) != 1);
        fseek(fp, sleutel * sizeof p, SEEK_SET);
        if (fread(&p, sizeof p, 1, fp) != 1) {
            printf("Error: de persoon kan niet gevonden worden.\n");
        }
        else {
            if (p.leeg == 1) {
                printf("Error: de persoon kan niet gevonden worden.\n");
            }
            else {
                p.leeg = 1;
                fseek(fp, -1 * sizeof p, SEEK_CUR);
                if (fwrite(&p, sizeof p, 1, fp) != 1) {
                    printf("Error: de persoon kan niet verwijderd worden.\n");
                }
            }
        }
        fclose(fp);
    }
}

void zoek(void) {
    FILE* fp;
    fp = fopen("persoon.geg", "rb");
    if (fp == NULL) {
        printf("Error: de file persoon.geg kan niet geopend worden.\n");
    }
    else {
        int sleutel;
        Persoon p;
        do {
            printf("Geef nummer: ");
            fflush(stdin);
        }
        while (scanf("%d", &sleutel) != 1);
        fseek(fp, sleutel * sizeof p, SEEK_SET);
        if (fread(&p, sizeof p, 1, fp) != 1) {
            printf("Error: de persoon kan niet gevonden worden.\n");
        }
        else {
            if (p.leeg == 1) {
                printf("Error: de persoon kan niet gevonden worden.\n");
            }
            else {
                printf("%6d %s\n", p.sleutel, p.naam);
            }
        }
        fclose(fp);
    }
}

void drukaf(void) {
    FILE* fp;
    fp = fopen("persoon.geg", "rb");
    if (fp == NULL) {
        printf("Error: de file persoon.geg kan niet geopend worden.\n");
    }
    else {
        Persoon p;
        while (fread(&p, sizeof p, 1, fp) == 1) {
            if (p.leeg == 0) {
                printf("%4d %s\n", p.sleutel, p.naam);
            }
        }
        fclose(fp);
    }
}

void main(void) {
    int antw;
    do {
        printf("0. Stoppen.\n"
               "1. Afdrukken.\n"
               "2. Toevoegen.\n"
               "3. Verwijderen.\n"
               "4. Zoek op nummer.\n"
               "\n"
               "Geef keuze: ");
        fflush(stdin);
        if (scanf("%d", &antw) != 1) {
            printf("Error: invoer is geen getal.\n");
        }
        else {
            switch (antw) {
                case 0:
                    break;
                case 1:
                    drukaf(); break;
                case 2:
                    voegtoe(); break;
                case 3:
                    verwijder(); break;
                case 4:
                    zoek(); break;
                default:
                    printf("Error: verkeerde invoer.\n");
            }
        }
    }
    while (antw != 0);
}