InfoC adventi naptár
fflush(stdin)
Írjunk egy programot, amelyik kér egy egész számot; utána pedig beolvas egy sornyi szöveget.
#include <stdio.h> int main() { int i; char s[200]; printf("Szam: "); scanf("%d", &i); printf("Szoveg: "); fgets(s, 200, stdin); printf("A szamod: %d, a szoveged: [%s]\n", i, s); return 0; }
Érdemes kipróbálni: a program nem olvassa be a szöveget. Helyette
a fgets()
híváson látszólag átugrik, de igazából egy üres sztringet rak s
-be.
Szam: 5 Szoveg: A szamod: 5, a szoveged: [ ]
1. feladvány: miért?
2. feladvány: csináljunk valamit, hogy ez ne így legyen.
Tartalom
1 Mi történik?
Tegyük fel, hogy beírjuk: 5 <enter> hello <enter>. Ekkor a program a szabványos bemenetén a következő karaktereket kapja:
5 | ↵ | h | e | l | l | o | ↵ |
Logikus is: a scanf %d
beolvassa az 5
-ös számot, beírja i
-be.
A következő karakternél megáll, mert az az enter. Nem számjegy, ezért nem tartozhat egy
egész számhoz. Így kerül az 5 a változóba, és tér vissza a scanf()
függvény 1-gyel.
Ez az enter ott fog maradni a bemeneten. A következő fgets()
hívás enterig
olvassa a sort; az entert már nem teszi a sztringbe. Mivel az első karakter, amit ez meglát, pont
az enter, a sztring üres lesz, az üres sort delimitáló entert pedig a fgets()
beteszi a sztringbe. A bemeneten egyébként a hello megmarad, ezt mondjuk egy következő fgets()
hívás beolvasná. Vagy ha utána megint egy scanf %d
jönne, az meg hibakóddal térne
vissza.
Erre szokták azt mondani, hogy fflush(stdin)
-t kell írni. Nem, nem kell
azt írni. A C szabvány egy olvasásra megnyitott fájlnál az fflush()
-tól nem
vár el semmit; vagyis a szabvány szerint az fflush(stdin)
utasítás nem
csinál semmit. Windowson, az MSDN szerint az fflush()
input streamre
„kiüríti a bemeneti puffert”, ami elég érdekesen hangzik, ugyanis nem egyértelmű, honnan kellene
tudni, hogy meddig tart a bemeneti puffer. Ez a hülyeség amúgy annyira elterjedt, hogy könyvekben
is megjelent; és annyira, hogy más operációs rendszereken és függvénykönyvtárakon, pl. az újabb
Linuxokon is elkezdték leutánozni az fflush()
ilyen jellegű funkcionalitását –
tisztán kompatibilitási okokból, hogy a rosszul megírt programok működjenek. Azért csak
jegyezzük meg, még akkor is, ha a fenti hülyeség sok helyen nyomtatásban is megjelent már:
az fflush(stdin)
a szabvány szerint nem csinál semmit.
Hogy lehet akkor javítani a programot? Legegyszerűbben úgy, hogy a scanf %d
hívás után beteszünk
egy üres getchar()
-t. Ott kell lennie egy enternek, azt az entert beolvassuk, eldobjuk.
A fgets()
pedig majd már a h
betűt látja elsőnek. Ezt a getchar()
hívást én közvetlenül a scanf %d
után tenném. Akkor az a scanf()
+getchar()
kombó beolvas egy számot, és nem hagy maga után semmit a bemeneten. Nem a következő beolvasás
leprogramozásánál kell emlékeznünk, hogy az előzőnél még maradt valamit a bemeneten.
A javított rész:
printf("Szam: "); scanf("%d", &i); getchar(); printf("Szoveg: "); fgets(s, 200, stdin);
Szam: 5 Szoveg: hello A szamod: 5, a szoveged: [hello]
2 Hogyan tovább?
Most már akkor fejezzük be, amit elkezdtünk. Mi történik akkor, ha a felhasználó azt írja be, hogy 5 szóköz enter hello enter? A bemeneten ez lesz:
5 | ␣ | ↵ | h | e | l | l | o | ↵ |
Vagyis a getchar()
hívásunk a szóközt fogja beolvasni, és az előző probléma újból
előáll.
Pontosan mit is kellene csinálnunk? A bemeneten van egy szám, utána lehetnek egyéb dolgok, amik minket nem érdekelnek, az enterig; végül pedig egy enter (ami úgyszint nem érdekel bennünket). Nagyon egyszerű, olvassuk be ezeket is, és dobjuk el. Egyik megoldás lehet erre, hogy egészen addig olvasunk, amíg entert nem kapunk. Enternek biztosan lennie kell előbb-utóbb.
while (getchar()!='\n') ; /* üres */
A scanf()
-et is használhatjuk erre. A scanf %[]
hasonló a %s
-hez;
egy szót olvas be egy sztringbe, csak itt a szögletes zárójelek között megadhatjuk azokat a karaktereket,
amelyek a szóban szerepelhetnek. Meg lehet adni tartományt is, pl. sscanf("hello123", "%[a-z]", s)
az s
sztringbe „hello”-t ír. Meg lehet adni azt is, hogy egy adott karakter ne szerepeljen
a beolvasott szóban (vagyis annál megálljon a feldolgozás). Ezt a ^
kalap karakterrel tehetjük
meg. Emiatt a következő scanf()
hívás egy egész sort beolvas a sztringbe (az entert
a bemeneten hagyja):
char s[200]; scanf("%[^\n]", s);
Most, amit beolvastunk, azt el szeretnénk dobni. Utána az entert is be szeretnénk olvasni, és azt is el szeretnénk dobni, ezért a bűvös formátumsztring a szám beolvasása után:
int i; scanf("%d", &i); scanf("%*[^\n]%*c"); // nem is kap változót
Ez jó kell legyen, hiszen azért áll meg a %[]
, mert entert talált, a következő, a %c
által beolvasott karakter az maga az enter lesz.
3 Ugyanez karakterre
A scanf %c
-nek van egy érdekes tulajdonsága. Az összes többi konverzió (pl. %d
,
%s
stb.) a beolvasott whitespace karaktereket eldobja, csak amikor nem whitespace
karaktert talál, akkor kezdi meg a konverziót.
Emiatt mindegy, ha egy számra várunk, hogy a felhasználó "5"
-öt, vagy "␣␣5"
-öt
ír be. A scanf %c
viszont nem teszi ezt. Direkt, hogy egy whitespace karaktert is be lehessen vele
olvasni.
Írjunk egy programot, amelyik megkérdezi, hogy „igen(i) vagy nem(n)”, és utána kiírja, melyiket választottuk!
Ha a scanf %c
-nek azt írjuk, hogy "␣␣␣i"
, akkor a szóközt fogja a c
-be
tenni, és nem működik rendesen. A scanf()
-et a formátumsztringjében egy szóköz karakterrel
kérhetjük arra, hogy a szokásos whitespace karakterek eldobását elvégezze. Szóval a scanf()
hívás,
amelyik beolvas egy karaktert, de nem zavarja, ha szóköz van előtte; illetve a többi karaktert eldobja, és
az entert sem hagyja ott a bemeneten:
#include <stdio.h> int main() { char c; printf("igen(i) vagy nem(n)? "); scanf(" %c%*[^\n]%*c", &c); // omg switch (c) { case 'i': printf("igen\n"); break; case 'n': printf("nem\n"); break; default: printf("???\n"); break; } return 0; }
Nem szokás amúgy ilyeneket írni… De láthatóan elég sok mindent össze lehet rakni a scanf()
-fel.