C scanf() ile Karakter Okuma Sorunu
Eğer bu makaleyi buldunuz ve okuyorsanız yüksek ihtimalle siz de aynı sorundan
müzdaripsiniz. C ile bir alıştırma veya uygulama yapıyorsunuz, klavyeden birkaç
kez karakter veya string okumanız gerekiyor, aa, bir bakıyorsunuz ki arada bazı
girişleri okuyamamışsınız, değişkenlerinizde yalnızca istenmeyen bir \n
karakteri var. Ne olacak şimdi?
Gelin örnek bir kodla sorunu irdeleyelim:
1#include <stdio.h>
2
3int main() {
4 char karakter0;
5 char karakter1;
6 char karakter2;
7 char karakter3;
8
9 printf("Karakter0: ");
10 scanf("%c", &karakter0);
11
12 printf("Karakter1: ");
13 scanf("%c", &karakter1);
14
15 printf("Karakter2: ");
16 scanf("%c", &karakter2);
17
18 printf("Karakter3: ");
19 scanf("%c", &karakter3);
20
21 putchar('\n');
22
23 printf("Girilen karakterler:\n");
24 printf("Karakter0: %c\n", karakter0);
25 printf("Karakter1: %c\n", karakter1);
26 printf("Karakter2: %c\n", karakter2);
27 printf("Karakter3: %c\n", karakter3);
28
29 return 0;
30}
Kodun çıktısı tam olarak şöyle:
1Karakter0: a
2Karakter1: Karakter2: b
3Karakter3:
4Girilen karakterler:
5Karakter0: a
6Karakter1:
7
8Karakter2: b
9Karakter3:
10
11 .
Çıktıya baktığımızda ilk girdinin okunduğunu fakat ikinci girdinin sanki
okunmayıp atlanmış gibi bir havası olduğunu gözlüyoruz. Aynı durum aynı biçimde
üçüncü ve dördüncü okumalar için de geçerli. Özellikle girilen karakterler
yazıdırıldığında Karakter1:
ve Karakter3:
'ten sonraki boş satırlara dikkat
edin. Bu boşlukların olmasının bir nedeni var. Çıktıda görünenin özeti şöyle
yorumlanabilir:
- scanf Karakter0'ı okudu
- scanf Karakter1'i atladı
- scanf Karakter2'yi okudu
- scanf Karakter3'ü atladı
Öyleyse scanf
işlevinde bir bug var, hatalı çalışıyor. Peki gerçekte
böyle mi? Hayır, böyle değil. scanf
işlevinde hiçbir sorun yok, tıkır tıkır
çalışıyor. Bu arada söylemek gerekir ki bu sorun %d ve %f gibi sayısal
biçimlendiriciler kullanıldığında oluşmuyor, yalnızca %c ve %s
biçimlendiricileri kullanılırken oluşuyor (bildiğim kadarıyla). Neden böyle
olduğunu anlatmadan önce scanf
işlevinin klavye (veya başka bir girdi aygıtı)
girdilerini biçimlendirmeye göre nasıl okuduğuna kısaca bakalım.
Klavyeden bir tuş girişi yapıldığında, bu tuş olaylarını tüketen bir uygulama
veya işlev çalışana dek bu tuşlar geçici bir tampon bellek bölgesinde tutulur.
scanf
işlevi bir tuş olayı tüketici işlevdir. Bu işlevlere örnek olarak
getchar, getch, gets vb. işlevler verilebilir. Kod örneğimiz üzerinden scanf
ile okuma yaparken bu tamponda neler olduğuna adım adım bakalım.
Karakter0:
ekrana yazdırıldı- Tampon boş olduğundan
scanf
bir tuş girdisi beklemeye geçti - 'a' karakteri girildi ve
\n
(enter) tuşuna basıldı - Bu aşamada tamponda 'a' ve
\n
karakterleri var scanf
%c biçimlendiricisi ile tamponu okuduğundan yalnızca 'a' karakterini tüketti vekarakter0
değişkenine sakladı,\n
karakteri ise tüketilmediği için tamponda kaldıKarakter1:
ekrana yazdırıldı- İkinci
scanf
çalışınca onun da biçimlendiricisi %c olduğundan ve\n
girdisi de sonuçta bir ASC2 karakter olduğundan tampondaki bu karakteri tüketipkarakter1
değişkenine sakladı ve tampon yine boşaldı ve işte bu aşamada bizscanf
işlevinin düzgün çalışmadığını düşündük Karakter2:
ekrana yazdırıldı- Üçüncü
scanf
çalıştırılıdı fakat tampon boş olduğu için beklemeye geçti - 'b' karakteri girildi ve
\n
tuşuna basıldı - Üçüncü
scanf
'b' karaketini tüketti ve yine\n
karakteri tamponda kaldı - Dördüncü
scanf
çalıştırıldı ve tamponda kalan\n
karakterini tüketipkarakter3
değişkenine sakladı
Aslında bu durum yalnızca scanf
işlevine has değil, aynı durum girdi
tamponundan karakter veya string okuyan getchar
, gets
vb. işlevler için de
geçerlidir. Peki karakter okurken bu sorun oluşuyor da sayısal biçimli olarak
okurken neden olmuyor? Çünkü %d
gibi sayısal biçimlendiriciler \n
ve \s
gibi boşluk karakterlerini otomatikmen tüketiyorlar. Dolayısıyla scanf
veya
benzerleriyle ne kadar arka arkaya okuma yapılırsa yapılsın karakter
biçimlendiricilerde oluşan sorun oluşmuyor.
Güzel, artık bu garip davranışın nedenini anladık. Ancak anlamak yeterli değil,
bunun üstesinden gelmek için ne yapmalıyız? Aslında bu sorunun yanıtı
yukarıdaki açıklamalarda zaten var. Neydi sorunumuz? \n
karakterinin tamponda
kalması ve bir sonraki tüketici tarafından tüketilmesi. Öyleyse ya bu karakteri
sonraki tüketiciyi çalıştırmadan önce manuel olarak tüketeceğiz, ya da bir
şekilde bir biçimlendirme numarasıyla boşluk karakterlerinin görmezden
gelinmesini sağlayacağız.
Çözüm
Çözüm kodumuzu yazıp çalıştırıp çıktısına bakalım. Sonrasında çözümü yöntemlerini irdeleyeceğiz.
1#include <stdio.h>
2
3int main() {
4 char karakter0;
5 char karakter1;
6 char karakter2;
7 char karakter3;
8
9 printf("Karakter0: ");
10 scanf("%c", &karakter0);
11
12 while ((getchar()) != '\n'); // Çözüm 1
13 printf("Karakter1: ");
14 scanf("%c", &karakter1);
15
16 printf("Karakter2: ");
17 scanf(" %c", &karakter2); // Çözüm 2
18
19 printf("Karakter3: ");
20 scanf(" %c", &karakter3);
21
22 putchar('\n');
23
24 printf("Girilen karakterler:\n");
25 printf("Karakter0: %c\n", karakter0);
26 printf("Karakter1: %c\n", karakter1);
27 printf("Karakter2: %c\n", karakter2);
28 printf("Karakter3: %c\n", karakter3);
29
30 return 0;
31}
Çözüm uygulanan kodun çıktısı:
1Karakter0: a
2Karakter1: b
3Karakter2: c
4Karakter3: d
5
6Girilen karakterler:
7Karakter0: a
8Karakter1: b
9Karakter2: c
10Karakter3: d
Çıktı kodu incelenirse istediğimizi elde ettik. Dört karakteri de hiç atlama
olmadan değişkenlerine atadık. Çözüm kodunda iki ayrı çözüm yöntemi görüyorsunuz.
Aslında ikisi de aynı işi yapıyor; istenmeyen, artık \n
karakterlerini
tüketiyorlar.
1while ((getchar()) != '\n'); // Çözüm 1
İlk çözümde, \n
karakterlerinin hepsi tüketilene kadar program döngüde kalıyor.
1scanf(" %c", &karakter2); // Çözüm 2
İkinci çözümde ise tüketme bizzat scanf biçimlendirme parametresinin içinde
yapılıyor. Dikkat ettiyseniz %c
notasyonundan önce bir boşluk var. Bu boşluk
tamponda kalan bir önceki boşluk karakterini tüketip asıl almak istediğimiz
karakteri, karakter2 içerisine alıp onu da tüketmiş olacaktır.
Ancak unutmayın ki istediğimiz karakteri yazıp ardından enter tuşuna
bastığımızda yine tamponda bir \n
karakteri tüketilmeyi bekliyor olacaktır.
Dolayısıyla sonraki stdin tamponundan okuma işlevlerini kullanırken bu durumu
işlemek yine biz programcıların sorumluluğundadır. Sizin de bu konuda
bildiğiniz bir çözüm veya öneriniz varsa yorum bölümünden paylaşabilirsiniz.
Başka bir konuda görüşmek dileğiyle, herkese mutlu kodlamalar.