scanf, printf'in aynasıdır: aynı başlık, benzer görünen biçim metinleri, zıt yön. Standart girdiden metin okur, biçime göre dönüştürür ve sonuçları verdiğiniz göstericiler üzerinden saklar.
Aynı zamanda C'de diğer her fonksiyondan daha fazla kafası karışmış yeni başlayan üreten fonksiyondur, çünkü davranışlarından üçü şaşırtıcıdır ve biri bir güvenlik açığıdır. Bu sayfa dördünü de ele alıyor.
Temel Biçim ve Neden &
Bunu çalıştırmak için bir sayı yazın ve Enter'a basın. &age bir süs değildir. C her argümanı değerle geçirir, dolayısıyla scanf("%d", age) yazsaydınız fonksiyon age'in mevcut (çöp) değerinin bir kopyasını alırdı ve değişkenin kendisine ulaşmanın yolu olmazdı. &age, değişkenin adresini geçirir ve scanf sonra onun üzerinden yazar. Tüm resim için bkz. göstericiler.
&'i atlamak tek en yaygın scanf hatasıdır. -Wall ile derleyici bunu yakalar (format '%d' expects argument of type 'int *'); uyarılar açık değilken derlenir ve rastgele bir adreste belleği bozar.
Birkaç Değer Okumak
Tek bir çağrı birkaç değer okuyabilir. Girdideki boşluk - boşluklar, sekmeler, satır sonları - onları ayırır ve sayısal belirteçler ne kadar olursa olsun onları atlar.
"%d %d %d" içindeki gerçek boşluklar aslında gereksizdir - %d zaten baştaki boşlukları atlar - ama biçimi okunaklı kılarlar. Gereksiz olmayan şey, aşağıda ele alınan dönüş değerini denetlemektir.
Biçimdeki boşluk olmayan karakterler girdiyle tam olarak eşleşmelidir; yapılandırılmış girdiyi böyle okursunuz:
Biçimdeki :, girdide bir : talep eder. 9:30 yazın, ayrıştırılır; 9 30 yazın, 9'u okuduktan sonra başarısız olur.
Dönüş Değerini Her Zaman Denetleyin
scanf, başarıyla atanan öğe sayısını döndürür - karakter sayısını değil ve bir başarı bayrağını değil. Bir şey dönüştürülmeden önce girdi biterse EOF döndürür.
İstemde merhaba yazın. scanf, 0 döndürür, n dokunulmamış kalır - hâlâ başladığı çöpü tutar - ve sorunlu metin hâlâ girdi akışında durur. O son kısım, tek bir kötü girdiyi sonsuz bir döngüye dönüştüren şeydir:
/* HATA: sayısal olmayan girdide sonsuza dek döner */
while (scanf("%d", &n) != 1) {
printf("Tekrar deneyin: ");
}
scanf, dönüştüremediği metni asla tüketmez, dolayısıyla bir sonraki çağrı aynı karakterlerde sonsuza dek başarısız olur. Kurtulmak için satırın geri kalanını kendiniz atmalısınız:
EOF denetimi önemlidir: girdi akışı bittiğinde (kullanıcı Ctrl+D'ye basar ya da yöneltilen bir dosya tükenir), scanf bundan sonra hep EOF döndürür, dolayısıyla yalnızca != 1 sınayan bir döngü sonsuza dek yeniden dener. "Hatalı girdi" ile "girdi bitti" durumlarını, olduğu gibi iki ayrı durum olarak ele alın. Bu yerine oturunca, discard_line yardımcısı insan girdisinde scanf kullanan her programda elinizin altında tutmaya değer.
%s Tampon Taşması
%s, karakterleri bir char dizisine okur. & olmadığına dikkat edin - bir dizi adı zaten ilk elemanına bir göstericiye dönüşür, dolayısıyla &name yanlış tip olurdu.
Sorun, düz %s'nin dizinizin ne kadar büyük olduğu hakkında hiçbir fikri olmamasıdır:
char name[10];
scanf("%s", name); /* TEHLİKELİ: 40 karakter yazılır, 41 bayt yazar */
Hiçbir şey yazmayı dizinin sonunda durdurmaz. Bellekte sıradaki ne varsa ona doğru devam eder - diğer değişkenler, dönüş adresi - ki bu klasik tampon taşmasıdır ve tarihsel olarak C'de en çok sömürülen hata sınıfıdır. Çözüm, % ile s arasında, sonlandırıcı '\0' için yer bırakacak şekilde dizi boyutundan bir küçük bir genişliktir:
Genişlik, biçim metnine sabit olarak yazılmalıdır; bu da dizi boyutuyla senkron tutmayı can sıkıcı kılar - üretimdeki C'nin çoğunun bunun yerine fgets kullanmasının bir sebebi daha.
İkinci %s sürprizi: ilk boşlukta durur. Ada Lovelace yazın, name Ada tutar; Lovelace bir sonraki okuma için akışta bekler. %s bir kelime okur, bir satır değil.
Artık-Satır-Sonu Hatası
Herkesin çarptığı "scanf çalışmıyor" budur. Sayısal belirteçler baştaki boşlukları atlar; %c atlamaz.
O boşluk olmadan olan şu. 30 yazıp Enter'a basarsınız. %d, 3 ve 0'ı tüketir ve satır sonunda durur, tamponda '\n' bırakarak. Sonraki scanf("%c", &initial) o satır sonunu karakter olarak okur ve anında döner - istem belirir ve aynı nefeste atlanır.
scanf biçim metnindeki bir boşluk "buradaki her miktarda boşluğu atla" demektir, dolayısıyla " %c" başıboş satır sonunun üzerinden geçer ve gerçek bir karakter bekler. Boşluk yalnızca %c ve %[ öncesinde gereklidir; diğer her belirteç boşlukları kendiliğinden atlar.
Float ve Char Okumak
Belirteçler çoğunlukla printf ile ortaktır, önemli bir farkla: scanf, bir gösterici üzerinden yazdığı için hedefin kesin boyutunu bilmek zorundadır.
1.5 2.25 7 x deneyin.
%f bir float, %lf bir double okur. printf'te ikisi de aynı şekilde yazdırılır, çünkü float'lar çağrıdan önce double'a yükseltilir - ama scanf'te yükseltme yoktur ve %f'yi bir double * ile kullanmak sekiz baytlık bir değişkene dört bayt yazar ve onu bozuk bırakır. Buradaki %c, başta boşluk istemez, çünkü ondan önceki %d zaten bir satır sonunda değil bir boşlukta durdu. (Tam tablo biçim belirteçleri sayfasında.)
fgets: Sağlam Alternatif
Bir insanın yazdığı her şey için tüm satırı okuyun ve sonradan ayrıştırın. fgets, tampon boyutunu argüman olarak alır, dolayısıyla fiziksel olarak taşamaz ve satır sığdığında satır sonunu da tüketir, dolayısıyla bir sonraki okumayı karıştıracak hiçbir şey geride kalmaz. (Tampondan uzun bir satır, kuyruğunu okunmamış bırakır - aldığınızda '\n' olmaması bunu böyle saptarsınız.)
O kalıpla ilgili üç nokta:
sizeof linegerçek boyutu geçirir, dolayısıyla dizi boyutunu değiştirmek -%19ssabitinin aksine - başka düzenleme gerektirmez.- Satır sığdıysa
fgetssatır sonunu korur;strcspnkırpmasının orada olmasının sebebi budur. Tampon için çok uzun bir satırı da böyle saptarsınız: aldığınızda'\n'yoktur. - Sonradan ayrıştırın. Tam sayılar için
strtol(ayrıştırmanın nerede durduğunu da söyleyebilir), kayan nokta içinstrtodya da zaten güvenle tuttuğunuz bir metne karşı bir scanf biçimi kullanmak içinsscanf(line, "%d %d", &a, &b).
sscanf kendi başına anılmayı hak eder: girdi akışı yerine bir metinden okuyan scanf'tir ve fgets ile kusursuz birleşir. scanf'in elverişli ayrıştırmasını, akış sorunlarının hiçbiri olmadan elde edersiniz.
Sık Yapılan Hatalar
- Dizi olmayan bir argümanda
&'i unutmak.-Wallile derleyin. - Genişliksiz
%s. Uzun bir girdiyi bekleyen bir tampon taşması. - Dönüş değerini yok saymak. Sonra
scanf'in hiç atamadığı bir değişkeni okumak. - Başarısız bir
scanf'te girdi atmadan döngü kurmak. Sonsuz döngü. - Bir sayı okuduktan sonra baştaki boşluksuz
%c. Atlanan istem hatası. - Bir
doubleiçin%fkullanmak. Sessiz bozulma;scanf%lfister. %s'nin bir satır okumasını beklemek. Boşlukla ayrılmış bir kelime okur.
Sıkça Sorulan Sorular
C'de scanf neden & ister?
Çünkü C argümanları değerle geçirir. scanf, değişkeninizi değiştirmek zorundadır, dolayısıyla değerinin kopyasına değil değişkenin adresine ihtiyacı vardır: scanf("%d", &age). Tek istisna, adı zaten bir adres olan karakter dizisidir - scanf("%19s", name) & almaz.
scanf neden char ya da metin girdimi atlıyor?
Önceki bir scanf("%d", ...), Enter tuşunun satır sonunu girdi tamponunda bıraktı. %d ve %f baştaki boşlukları atlar, %c atlamaz, dolayısıyla beklemek yerine o satır sonunu okur. Çözüm, biçimde bir boşluktur - scanf(" %c", &ch) - bu, scanf'e önce boşlukları atlamasını söyler.
C'de scanf ile bir metin güvenle nasıl okunur?
%s'ye tamponunuzdan bir küçük bir en fazla genişlik verin: char name[20]; scanf("%19s", name);. Genişlik olmadan %s, kullanıcının yazdığı kadar karakter yazar ve memnuniyetle dizinin sonunu geçer. Ayrıca %s'nin ilk boşlukta durduğunu unutmayın, dolayısıyla içinde boşluk olan bir tam adı okuyamaz.
C'de scanf mi fgets mi kullanmalıyım?
Bir insanın yazdığı her şey için fgets kullanın. Bütün bir satırı boyutlandırılmış bir tampona okur, taşamaz ve satır sığdığında satır sonunu da tüketir, dolayısıyla klasik karışık girdi hataları yok olur. Satırı sonradan sscanf ya da strtol ile ayrıştırın. scanf, küçük alıştırmalar ve düzgün biçimli makine girdisi için uygundur.