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:

  1. scanf Karakter0'ı okudu
  2. scanf Karakter1'i atladı
  3. scanf Karakter2'yi okudu
  4. 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.

  1. Karakter0: ekrana yazdırıldı
  2. Tampon boş olduğundan scanf bir tuş girdisi beklemeye geçti
  3. 'a' karakteri girildi ve \n (enter) tuşuna basıldı
  4. Bu aşamada tamponda 'a' ve \n karakterleri var
  5. scanf %c biçimlendiricisi ile tamponu okuduğundan yalnızca 'a' karakterini tüketti ve karakter0 değişkenine sakladı, \n karakteri ise tüketilmediği için tamponda kaldı
  6. Karakter1: ekrana yazdırıldı
  7. İ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üketip karakter1 değişkenine sakladı ve tampon yine boşaldı ve işte bu aşamada biz scanf işlevinin düzgün çalışmadığını düşündük
  8. Karakter2: ekrana yazdırıldı
  9. Üçüncü scanf çalıştırılıdı fakat tampon boş olduğu için beklemeye geçti
  10. 'b' karakteri girildi ve \n tuşuna basıldı
  11. Üçüncü scanf 'b' karaketini tüketti ve yine \n karakteri tamponda kaldı
  12. Dördüncü scanf çalıştırıldı ve tamponda kalan \n karakterini tüketip karakter3 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.

comments powered by Disqus

Çeviriler: