Wiki

Clone wiki

CPL01 / scanf_char

Het inlezen van een letter werkt niet goed, wat is er aan de hand?

Als je in C programma een getal inleest met scanf en daarna een letter probeert in te lezen dan werkt dit niet zoals je misschien zou verwachten.

Hieronder is een programma gegeven waarin de gebruiker gevraagd wordt om een getal in te voeren. Daarna wordt het ingevoerde getal op het scherm afgedrukt en wordt aan de gebruiker gevraagd of dit correct is door te vragen om het karakter J of N in te typen.

Bijvoorbeeld probleem.c:

#!C
#include <stdio.h>

int main(void)
{
    printf("Geef een getal: ");
    int getal;
    char antwoord;
    do
    {
        scanf("%d", &getal);
        printf("Het ingevoerde getal is: %d\n", getal);
        printf("Is dit correct [J/N]: ");
        scanf("%c", &antwoord);
        if (antwoord == 'N')
        {
            printf("Geef dan nu het goede getal: ");
        }
    }
    while (antwoord != 'J');
    printf("Het definitief ingevoerde getal is %d\n", getal);
    return 0;
}

Als je dit programma uitvoert en als invoer eerst 43<Enter> en daarna N<Enter> intypt dan verschijnt de volgende uitvoer:

#!none
Geef een getal: 43
Het ingevoerde getal is: 43
Is dit correct [J/N]: N
Het ingevoerde getal is: 43
Is dit correct [J/N]: Geef dan nu het goede getal: 

Dit is anders dan dat je misschien zou verwachten omdat het ingevoerde getal twee keer wordt geprint en de vraag of de invoer correct is twee keer wordt gesteld.

Wat is er aan de hand?

Na het intypen van het getal 43 moet je ook nog op de <Enter>-toets drukken omdat de invoer pas verwerkt wordt door scanf nadat je op <Enter> hebt gedrukt. Het idee hierachter is dat je de invoer nog kunt corrigeren door op <Backspace> te drukken of door gebruik te maken van de pijltjestoetsen voordat je op <Enter> drukt. Deze <Enter> is echter ook een karakter en als je na het inlezen van het getal een karakter inleest, dan lees je dus als eerste het karakter <Enter> in. Het karakter <Enter> is ongelijk aan het karakter N dus de code die bij de if hoort wordt niet uitgevoerd. Het karakter <Enter> is ongelijk aan het karakter J dus de do-while-loop wordt nogmaals uitgevoerd. De eerste scanf-instructie wil een integer inlezen maar 'ziet' het karakter N. De scanf-instructie wordt beëindigd zonder dat de waarde van getal wordt aangepast en zonder dat er iets wordt ingelezen. Vervolgens worden beide printf-instructies uitgevoerd. Daarna wordt de tweede scanf-instructie uitgevoerd en wordt het karakter N ingelezen in de variabele antwoord.

Oplossing 1

Deze fout kun je vrij eenvoudig afvangen door bij het inlezen van het karakter met scanf aan te geven dat er eerst een <Enter> moet worden ingelezen voordat het karakter wordt ingelezen. Het karakter <Enter> wordt in C gecodeerd als \n.

oplossing1.c:

#!C
#include <stdio.h>

int main(void)
{
    printf("Geef een getal: ");
    int getal;
    char antwoord;
    do
    {
        scanf("%d", &getal);
        printf("Het ingevoerde getal is: %d\n", getal);
        printf("Is dit correct [J/N]: ");
        scanf("\n%c", &antwoord);
        if (antwoord == 'N')
        {
            printf("Geef dan nu het goede getal: ");
        }
    }
    while (antwoord != 'J');
    printf("Het definitief ingevoerde getal is %d\n", getal);
    return 0;
}

Als je dit programma uitvoert en als invoer twee maal achter elkaar een getal invoert, dan gaat het toch weer niet zoals gewenst:

#!none
Geef een getal: 43
Het ingevoerde getal is: 43
Is dit correct [J/N]: N
Geef dan nu het goede getal: 42 42
Het ingevoerde getal is: 42
Is dit correct [J/N]: Het ingevoerde getal is: 2
Is dit correct [J/N]: N
Geef dan nu het goede getal: 42
Het ingevoerde getal is: 42
Is dit correct [J/N]: J
Het definitief ingevoerde getal is 42

Oplossing 2

Bij oplossing 1 treedt een probleem op als de gebruiker niet gewoon 'netjes' één getal invoert als daarom wordt gevraagd. Dit kunnen we eenvoudig oplossen door net zolang karakters in te lezen totdat een geldig karakter (in dit geval een J of een N wordt ingelezen.

oplossing2.c:

#!C
#include <stdio.h>

int main(void)
{
    printf("Geef een getal: ");
    int getal;
    char antwoord;
    do
    {
        scanf("%d", &getal);
        printf("Het ingevoerde getal is: %d\n", getal);
        printf("Is dit correct [J/N]: ");
        do
        {
            scanf("%c", &antwoord);
        }
        while (antwoord != 'N' && antwoord != 'J');
        if (antwoord == 'N')
        {
            printf("Geef dan nu het goede getal: ");
        }
    }
    while (antwoord != 'J');
    printf("Het definitief ingevoerde getal is %d\n", getal);
    return 0;
}
Als je dit programma uitvoert en als invoer twee maal achter elkaar een getal invoert, dan gaat het wel zoals gewenst:

#!none
Geef een getal: 43
Het ingevoerde getal is: 43
Is dit correct [J/N]: N
Geef dan nu het goede getal: 47 49
Het ingevoerde getal is: 47
Is dit correct [J/N]: N
Geef dan nu het goede getal: 42
Het ingevoerde getal is: 42
Is dit correct [J/N]: J
Het definitief ingevoerde getal is 42

Oplossing 3

Er is nog een alternatieve oplossing waarbij gebruik gemaakt wordt van de instructie fflush(stdin). Deze instructie kan op Windows machines gebruikt worden om alle invoer (tot dat moment) weg te gooien. Let op: de aanroep fflush(stdio) werkt alleen op het Windows operating systeem en niet op andere operating systems zoals bijvoorbeeld Linux.

oplossing3.c:

#!C
#include <stdio.h>

int main(void)
{
    printf("Geef een getal: ");
    int getal;
    char antwoord;
    do
    {
        scanf("%d", &getal);
        printf("Het ingevoerde getal is: %d\n", getal);
        printf("Is dit correct [J/N]: ");
        fflush(stdin);
        scanf("%c", &antwoord);
        if (antwoord == 'N')
        {
            printf("Geef dan nu het goede getal: ");
        }
    }
    while (antwoord != 'J');
    printf("Het definitief ingevoerde getal is %d\n", getal);
    return 0;
}

Als je dit programma uitvoert en als invoer twee maal achter elkaar een getal invoert, dan gaat het wel weer zoals gewenst:

#!none
Geef een getal: 43
Het ingevoerde getal is: 43
Is dit correct [J/N]: N
Geef dan nu het goede getal: 47 49
Het ingevoerde getal is: 47
Is dit correct [J/N]: N
Geef dan nu het goede getal: 42
Het ingevoerde getal is: 42
Is dit correct [J/N]: J
Het definitief ingevoerde getal is 42

Het bij oplossing 3 gegeven programma werkt alleen correct op het Windows operating systeem. Volgens de C standaard mag de functie fflush alleen gebruikt worden met output streams. Op http://en.cppreference.com/w/c/io/fflush staat:

For input streams (and for update streams on which the last operation was input), the behavior is undefined.

stdin is een input steam dus de aanroep ffush(stdin) is undefined.

Het bij oplossing 2 gegeven programma maakt alleen gebruik van scanf en werkt correct op alle operating systems.

Zeer uitgebreide informatie over de functie scanf kun je vinden op: http://www.opengroup.org/onlinepubs/009695399/functions/scanf.html.

Updated