Bu cevapta metin satırlarını okuduğunuzu ve yorumladığınızı varsayacağım . Belki bir şeyler yazıp RETURN'a vuran kullanıcıya soruyorsunuzdur. Ya da belki bir tür veri dosyasından yapılandırılmış metin satırlarını okuyorsunuzdur.
Metin satırlarını okuduğunuzdan, kodunuzu bir metin satırı okuyan bir kütüphane işlevi etrafında düzenlemek mantıklıdır. Standart işlevi, fgets()
başkaları olmasına rağmen (dahil getline
). Ve sonra bir sonraki adım o metin satırını bir şekilde yorumlamaktır.
fgets
Bir metin satırını okumak için aramak için temel tarif :
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
Bu sadece bir metin satırında okur ve tekrar basar. Yazıldığı gibi, birkaç dakika içinde ulaşacağımız birkaç sınırlama var. Ayrıca çok büyük bir özelliği var: İkinci argüman olarak geçtiğimiz 512 sayısı , okumak istediğimiz fgets
dizinin boyutu
. Bu gerçeği - ne kadar okumaya izin verildiğini söyleyebileceğimiz - diziyi çok fazla okuyarak taşmayacağından emin olabileceğimiz anlamına gelir .line
fgets
fgets
fgets
Şimdi bir metin satırını nasıl okuyacağımızı biliyoruz, ama ya gerçekten bir tam sayı, kayan nokta numarası veya tek bir karakter ya da tek bir kelime okumak istersek? (Ne Yani, eğer
scanf
biz geliştirmeye çalışıyoruz çağrı gibi bir biçim belirteci kullanarak olmuştu %d
, %f
, %c
veya %s
?)
Bir metin satırını - dize - bunlardan herhangi biri gibi yeniden yorumlamak kolaydır. Bir dizgiyi tamsayıya dönüştürmek için bunu yapmanın en basit (kusurlu olsa da) yolu çağırmaktır atoi()
. Kayan noktalı sayıya dönüştürmek için vardır atof()
. (Ve bir dakika içinde göreceğimiz gibi daha iyi yollar da var.) İşte çok basit bir örnek:
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
Tek bir karakteri (belki yazmak için kullanıcı isterse y
ya
n
evet olarak / yanıt yok), kelimenin tam anlamıyla sadece bu gibi hattın ilk karakteri, yakalayabilir:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(Bu, elbette, kullanıcının çok karakterli bir yanıt yazma olasılığını yok sayar; yazılan fazladan karakterleri sessizce yok sayar.)
Eğer kullanıcı isterse Nihayet, kesinlikle bir dize yazmak için değil giriş hattını tedavi etmek istiyorsa, boşluk içeren
hello world!
dize "hello"
ve başka bir şey ( scanf
biçimin %s
yapacağı şey) tarafından takip edildiğinden , bu durumda, biraz lif aldım, çizgiyi bu şekilde yeniden yorumlamak o kadar kolay değil, sonuçta cevap sorunun bir kısmı biraz beklemek zorunda kalacak.
Ama önce atladığım üç şeye geri dönmek istiyorum.
(1) Biz çağırıyoruz
fgets(line, 512, stdin);
diziye okumak line
ve 512 dizinin boyutu olduğu için taşmaması line
gerektiğini fgets
bilir. Ancak 512'nin doğru sayı olduğundan emin olmak için (özellikle, birinin boyutu değiştirmek için programı değiştirip düzeltmediğini kontrol etmek için), line
beyan edilen yere tekrar okumalısınız . Bu bir sıkıntı, bu yüzden boyutları senkronize tutmanın iki daha iyi yolu var. (A) boyut için bir ad oluşturmak üzere önişlemciyi kullanabilirsiniz:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
Veya, (b) C'nin sizeof
operatörünü kullanın :
fgets(line, sizeof(line), stdin);
(2) İkinci sorun, hatayı kontrol etmememiz. Girişi okurken, her zaman hata olasılığını kontrol etmelisiniz. Herhangi bir nedenle fgets
sorduğunuz metin satırını okuyamıyorsa, boş bir işaretçi döndürerek bunu gösterir. Yani böyle şeyler yapmalıydık
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
Son olarak, metin satırı okumak için, o sorun var
fgets
karakterleri okur ve bulduğu kadar diziye doldurur \n
hattını sonlandırır karakteri ve onu doldurur \n
sizin de diziye karakter . Önceki örneğimizi biraz değiştirirseniz bunu görebilirsiniz:
printf("you typed: \"%s\"\n", line);
Bunu çalıştırır ve bana sorulduğunda "Steve" yazarsam,
you typed: "Steve
"
Yani "
ikinci satır dize okumak ve dışarı aslında geri baskılı çünkü üzerinde "Steve\n"
.
Bazen bu ekstra yeni satırın önemi yoktur (aradığımız gibi
atoi
veya atof
her ikisi de sayıdan sonra sayısal olmayan herhangi bir girişi yok saydığından), ancak bazen çok önemlidir. Çoğu zaman bu satırsonu kaldırmak isteyeceğiz. Bunu yapmanın birkaç yolu var, ki bir dakika içinde ulaşacağım. (Bunu çok şey söylediğimi biliyorum. Ama tüm bu şeylere geri döneceğim, söz veriyorum.)
Ben dedin ki": Bu noktada, düşünme olabilir scanf
hiçbir iyiydi ve bu başka bir şekilde çok daha iyi olurdu Ama. fgets
Sıkıntı gibi görünmeye başlıyor Çağrı. scanf
Oldu o kadar kolay bunu kullanmaya devam edemez!? "
Tabii, isterseniz kullanmaya devam edebilirsiniz scanf
. (Ve gerçekten
basit şeyler için, bazı açılardan daha basittir.) Ama, lütfen, 17 tuhaflığı ve foiblesinden biri nedeniyle başarısız olduğunda bana ağlama ya da girişiniz nedeniyle sonsuz bir döngüye girme. beklemiyorduk ya da daha karmaşık bir şey yapmak için nasıl kullanacağınızı anlayamadığınızda. Ve fgets
gerçek rahatsızlıklarına bir göz atalım :
Her zaman dizi boyutunu belirtmeniz gerekir. Tabii ki, bu hiç bir sıkıntı değil - bu bir özellik, çünkü tampon taşması Gerçekten Kötü Bir Şey.
Dönüş değerini kontrol etmelisiniz. Aslında, bu bir yıkama, çünkü doğru kullanmak scanf
için, dönüş değerini de kontrol etmelisiniz.
Arkalý \n
soymalýsýn. Bu, itiraf ediyorum, gerçek bir sıkıntı. Keşke size bu küçük sorun yoktu işaret edebilir Standart bir işlevi olsaydı. (Lütfen kimse gelmez gets
.) Ama scanf's
17 farklı rahatsızlıkla kıyaslandığında , bunu fgets
herhangi bir günün sıkıntısından alacağım .
Peki nasıl mı o yeni satır şerit? Üç yol:
(a) Açık yol:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(b) Zor ve kompakt bir yol:
strtok(line, "\n");
Ne yazık ki bu her zaman işe yaramıyor.
(c) Başka bir kompakt ve hafif belirsiz yol:
line[strcspn(line, "\n")] = '\0';
Ve şimdi bu yoldan çekildiğine göre, atladığım başka bir şeye geri dönebiliriz: atoi()
ve atof()
. Bunlarla ilgili sorun, size başarı veya başarısızlığın başarılı olduğuna dair herhangi bir yararlı gösterge vermemeleri: sondaki sayısal olmayan girişi sessizce yok sayarlar ve hiç sayısal girdi yoksa sessizce 0 döndürürler. Başka avantajları da olan tercih edilen alternatifler strtol
ve şeklindedir strtod
.
strtol
Ayrıca size (diğer şeyler arasında) etkisini elde edebilirsiniz, yani 10 dışında bir üs kullanmasına izin verir %o
veya %x
birliktescanf
. Ancak bu işlevlerin doğru bir şekilde nasıl kullanılacağını göstermek kendi başına bir hikaye ve zaten oldukça parçalanmış bir anlatıya dönüşen şeyden çok fazla dikkat dağıtıcı olurdu, bu yüzden şimdi onlar hakkında daha fazla bir şey söylemeyeceğim.
Ana anlatıların geri kalanı, tek bir sayı veya karakterden daha karmaşık olan ayrıştırmaya çalıştığınız girdilerle ilgilidir. İki sayı veya boşlukla ayrılmış birden çok sözcük veya belirli çerçeveleme noktalama işaretleri içeren bir satırı okumak isterseniz ne olur? Bu, işlerin ilginçleştiği ve kullanarak bir şeyler yapmaya çalışıyorsanız, muhtemelen işlerin karmaşıklaştığı scanf
ve fgets
tüm bu seçeneklerin tüm hikayesi olmasına rağmen, bir metin satırını temiz bir şekilde okuduğunuzdan çok daha fazla seçenek olduğu yerdir. Muhtemelen bir kitabı doldurabilir, bu yüzden burada sadece yüzeyi çizebiliriz.
En sevdiğim teknik, satırı boşlukla ayrılmış "kelimelere" ayırmak, sonra her "kelimeyle" bir şeyler yapmaktır. Bunu yapmak için temel bir Standart işlev
strtok
(aynı zamanda sorunları da vardır ve ayrıca ayrı bir tartışmayı derecelendirir). Benim kendi tercihim, bu ders notlarında tarif ettiğim bir işlev olan her parçalanmış "kelime" ye işaretçiler dizisi oluşturmak için özel bir işlevdir
. Her halükarda, "kelimeler" elde ettikten sonra, her birini, belki de
daha önce incelediğimiz aynı atoi
/ atof
/ strtol
/ strtod
işlevleriyle daha fazla işleyebilirsiniz .
Paradoksal olarak, buradan nasıl uzaklaşacağımızı anlamak için oldukça fazla zaman ve çaba harcıyor scanf
olsak da, az önce okuduğumuz metin satırıyla başa çıkmanın bir başka güzel yolu da
fgets
onu iletmektir sscanf
. Bu şekilde, scanf
çoğu dezavantaj olmadan avantajların çoğunu elde edersiniz .
Giriş sözdiziminiz özellikle karmaşıksa, ayrıştırmak için bir "regexp" kitaplığı kullanmak uygun olabilir.
Son olarak, size uygun olan özel çözümleme çözümlerini kullanabilirsiniz. char *
Beklediğiniz karakterleri kontrol eden bir işaretçi ile bir kerede bir karakter üzerinde hareket edebilirsiniz
. Yoksa sizin gibi işlevleri kullanarak belirli karakterler için arama yapabilirsiniz strchr
veya strrchr
, ya strspn
ya strcspn
ya strpbrk
. Veya daha önce atladığımız strtol
veya
strtod
işlevlerini kullanarak rakam karakter gruplarını ayrıştırabilir / dönüştürebilir ve atlayabilirsiniz .
Söylenebilecek çok daha fazla şey var, ama umarım bu giriş sizi başlatır.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2
Sondaki sayısal olmayan metni o kadar kötü algılamadığını unutmayın .