Scanf és hibakezelés
Csendes Dávid · 2021.08.24.
Hogyan jelzi a scanf a hibát beolvasáskor? Hogyan tudjuk a hibák típusait megkülönböztetni?
Amikor inputról olvasunk, ennek a sikerességét néha ellenőriznünk kell. Azonban gyakran félreértések övezik ezt a területet, és
a scanf
visszatérési értékét. Itt tisztázunk pár dolgot a következő kódon keresztül:
#include <stdio.h>
int main(void) {
int a = -23, b = -23, c = -23, d = -23;
int s = scanf("%d %d %d %d", &a, &b, &c, &d);
printf("scanf eredmenye: %d\n", s);
printf("a: %d , b: %d , c: %d , d: %d\n", a, b, c, d);
char betu = '$';
if (scanf(" %c", &betu) == EOF)
printf("EOF erkezett!");
else
printf("betu: %c\n", betu);
return 0;
}
Először is: a scanf
nem adja vissza, milyen értéket olvasott be. Azt a megadott változókba tárolta el. Ez
tulajdonképpen a legfontosabb, amit tudni kell, mert nagyon abszurd kódot tud eredményezni az ilyen feltételvizsgálat.
Mégis mi a visszatérési értéke a scanf függvénynek? A fent lévő kódot különböző inputokkal kipróbálva elég hamar rá lehet jönni: hány darab értéket olvasott be sikeresen.
A helyzetet egy dolog bonyolítja: ha valahol a beolvasás sikertelen lesz (pl. a lenti példában a c változó esetén),
akkor az adott scanf
befejezi a feldolgozást. Ezáltal ahol a beolvasás sikertelen lett, az az input még
ott marad feldolgozásra, és egy későbbi scanf
fel is tudja dolgozni. Erre egy jó példa a következő bemenet,
ha begépeljük:
100 200 a 300
Mi is történik ekkor? A 100-at és a 200-at sikeresen beolvassa az a és a b változóba. Ekkor azonban egy karakterbe ütközik, amit
nem tud feldolgozni – a scanf
emiatt ezen a ponton visszatér. Két értéket tudott beolvasni sikeresen, ezért 2 lesz a
visszatérési értéke. Mi a helyzet akkor az ott maradt 'a' betűvel? Az utána lévő scanf
fel fogja dolgozni, és a
betu
nevű változóba az 'a' fog kerülni (és annak a visszatérési értéke 1 lesz). A 300 ott maradt az inputon, nem
került már feldolgozásra, a c
és a d
változóban pedig az eredeti érték maradt bent.
scanf eredmenye: 2 a: 100 , b: 200 , c: -23 , d: -23 betu: a
Mi történik fájl vége jel (end-of-file, EOF) kiadásakor? Adjunk ki egy ilyet a program legelején! (Windowson Ctrl+Z + Enter, Linuxon Ctrl-D.) A scanf eredménye egy negatív szám lesz. Adjunk ki még egyet, amikor a karaktert olvasná be! Kiírja, hogy EOF érkezett. Azaz az EOF értéke úgy van definiálva, hogy valamilyen negatív érték legyen, ami nyilván nem lehet azonos a beolvasott elemek számával. Az EOF konstans értelme az, hogy ezzel a kódban kifejezően jelezhetjük, mit vizsgálunk: end-of-file jelzést.
Mihez lehet kezdeni ezzel a sok háttértudással? Írjunk egy olyan függvényt, ami garantáltan beolvas egy olyan számot, aminek az értéke a megadott intervallumon belül van! Vagyis addig nem hagyja a felhasználót tovább lépni a program futásában.
Először is nyilván könnyű elérni azt, hogy addig olvassunk, míg a beolvasott szám nem megfelelő tartománybeli:
// int min és max, amik a legkisebb és legnagyobb elfogadható értéket tárolják
int olvasott;
scanf("%d", &olvasott);
while (olvasott > max || olvasott < min) {
printf("A megadott szam nem volt megfelelo tartomanybeli!");
scanf("%d", &olvasott);
}
Ez megfelelően működik... mindaddig, míg csak számot adunk meg. Ha akár csak egy betűt is írunk, akkor végtelenszer elvégzi a
kiírást. A fent leírtakból már tudjuk miért: a helytelen inputot, a karaktert a scanf
ott hagyja az inputon, ami miatt
a következő scanf
ismét elhasal, majd ott hagyja az inputot... végtelen ciklusba kerültünk.
Hogyan kerülhetjük ezt el? A cél az lenne, hogy ha helytelen volt az input, akkor az eldobásra kerüljön. Erre egy jó módszer lehet a következő:
int sikerult, olvasott;
sikerult = scanf("%d", &olvasott);
while (sikerult != 1) {
printf("Amit megadtal, nem volt szam!");
scanf("%*[^\n]"); // enterig mindent eldob
sikerult = scanf("%d", &olvasott);
}
Ezt a kettőt már csak ötvözni kell, ki kell szervezni egy függvénybe, és kész is a hiper-szuper számbeolvasó függvényünk. Mivel a kétféle hibára másképp szeretnénk reagálni, ezért érdemesebb átszervezni kicsit a kódunkat ehhez.
int szam_beolvas(int min, int max) {
// bár végtelen ciklusnak tűnik így, van benne return, ami megszakítja
while (true) {
int olvasott;
int sikerult = scanf("%d", &olvasott);
if (sikerult != 1) {
printf("Amit megadtal, nem szam volt!\n");
scanf("%*[^\n]");
} else if (olvasott > max || olvasott < min) {
printf("A megadott szam nem volt megfelelo! (min: %d, max: %d)\n", min, max);
} else {
return olvasott;
}
}
}
Már csak a fájl vége jelet kellene lekezelni...