Scanf kullanılarak boşlukların girilmesine nasıl izin verirsiniz?


129

Aşağıdaki kodu kullanarak:

char *name = malloc(sizeof(char) + 256); 

printf("What is your name? ");
scanf("%s", name);

printf("Hello %s. Nice to meet you.\n", name);

Bir kullanıcı adını girebilir, ancak gibi bir boşlukla bir ad girdiğinde Lucas Aardvark, scanf()sadece sonrasındaki her şeyi keser Lucas. scanf()Boşluklara nasıl izin veririm


9
Daha deyimsel olanın 'malloc (sizeof (char) * 256 + 1)' veya 'malloc (256 + 1)' veya daha iyisi ('ad'ın kesinlikle yerel olarak kullanılacağı varsayılarak)' karakter adı [256 + 1 ]. '+1', tahsise dahil edilmesi gereken boş sonlandırıcı için bir mnömonik olarak hareket edebilir.
Barry Kelly

@Barry - sizeof(char) + 256Bir yazım hatası olduğundan şüpheleniyorum .
Chris Lutz

Yanıtlar:


186

Girdinin her zaman belirli bir biçimde olacağından (ve belki o zaman bile) emin olmadığınız sürece, kişiler (ve özellikle yeni başlayanlar), scanf("%s")veya gets()arabellek taşması korumasına sahip olmayan diğer işlevleri asla kullanmamalıdır .

Daha hatırla scanf"biçimlendirilmiş tarama" için standları ve değerli az şey var daha az kullanıcı tarafından girilen verilerin daha biçimlendirilmiş. Giriş veri formatının tam kontrolüne sahipseniz, ancak genellikle kullanıcı girişi için uygun değilse idealdir.

Kullanım fgets()( vardır taşma koruması tampon) bir dizeye girişinizi almak ve sscanf()bunu değerlendirmek. Ayrıştırmadan sadece kullanıcının girdiği şeyi istediğiniz için sscanf(), bu durumda gerçekten ihtiyacınız yok :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Maximum name size + 1. */

#define MAX_NAME_SZ 256

int main(int argC, char *argV[]) {
    /* Allocate memory and check if okay. */

    char *name = malloc(MAX_NAME_SZ);
    if (name == NULL) {
        printf("No memory\n");
        return 1;
    }

    /* Ask user for name. */

    printf("What is your name? ");

    /* Get the name, with size limit. */

    fgets(name, MAX_NAME_SZ, stdin);

    /* Remove trailing newline, if there. */

    if ((strlen(name) > 0) && (name[strlen (name) - 1] == '\n'))
        name[strlen (name) - 1] = '\0';

    /* Say hello. */

    printf("Hello %s. Nice to meet you.\n", name);

    /* Free memory and exit. */

    free (name);
    return 0;
}

1
Bilmiyordum fgets(). Aslında o zaman kullanmak daha kolay görünüyor scanf(). +1
Kredns

7
Kullanıcıdan sadece bir satır almak istiyorsanız, daha kolay. Ayrıca arabellek taşmalarını önleyebileceğiniz için daha güvenlidir. Scanf ailesi, bir dizeyi farklı şeylere dönüştürmek için gerçekten yararlıdır (örneğin, dört karakter ve "% c% c% c% c% d" ile bir int gibi), ancak o zaman bile, fgets ve sscanf kullanmalısınız, değil scanf, arabellek taşması olasılığını önlemek için.
paxdiablo

4
Scanf biçiminde maksimum arabellek boyutunu koyabilirsiniz, yalnızca biçimi çalışma zamanında oluşturmadan çalışma zamanı hesaplanmış olanı koyamazsınız (printf için * eşdeğeri yoktur, * başka bir davranışla scanf için geçerli bir değiştiricidir: atamayı bastırma ).
AProgrammer

Not Ayrıca scanfsayısal dönüşüm taşıyor if (tanımsız davranışa sahip N1570 7.21.6.2p10 , son cümle, ifade C89 beri değişmedi) anlamına hiçbiri ait scanffonksiyonları güvenle güvenilmeyen girdi sayısal dönüşüm için kullanılabilir.
zwol

@JonathanKomar ve gelecekte bunu okuyan başka biri: Profesörünüz size scanfbir ödevde kullanmanız gerektiğini söylediyse, bunu yapmaları yanlıştı ve onlara benim söylediğimi söyleyebilirsiniz ve benimle bu konuda tartışmak isterlerse , e-posta adresim profilimden kolayca bulunabilir.
zwol

124

Deneyin

char str[11];
scanf("%10[0-9a-zA-Z ]", str);

Umarım yardımcı olur.


10
(1) Açıktır ki boşlukları kabul etmek için karakter sınıfına bir boşluk koymanız gerekir. (2) 10'un okunacak maksimum karakter sayısı olduğuna dikkat edin, bu nedenle str'nin en az 11 boyutunda bir tamponu göstermesi gerekir. (3) Buradaki son s bir biçim yönergesi değildir, ancak scanf burada onu tam olarak eşleştirmeye çalışacaktır. Etki, 1234567890s gibi e'lerin tüketileceği ancak hiçbir yere koyulmayacağı bir girişte görülecektir. Başka bir mektup tüketilmeyecek. S'den sonra başka bir format koyarsanız, sadece eşleşecek bir s varsa okunacaktır.
AProgrammer

Bir başka olası sorun, - ilkinden veya sonundan başka bir yerde kullanımı, tanımlanmış uygulamadır. Genellikle aralıklar için kullanılır, ancak aralığın ne atandığı karakter setine bağlıdır. EBCDIC harf aralıklarında deliklere sahiptir ve ASCII'den türetilmiş bir karakter
kümesini

1
"% [^ \ n]", gets (), arabellek taşması ile aynı soruna sahip. Ek olarak finalin okunmadığı \ n; bu, çoğu formatın beyaz boşlukları atlayarak başlamasıyla gizlenecektir, ancak [bunlardan biri değildir. Dizeleri okumak için scanf kullanma örneğini anlamıyorum.
AProgrammer

1
Bazı durumlarda shem gereksiz hem de yanlış olduğu için giriş dizesinin sonundan kaldırıldı (önceki yorumlarda belirtildiği gibi). [bir varyasyonundan ziyade kendi format tanımlayıcısıdır s.
paxdiablo

54

Bu örnek, tersine çevrilmiş bir tarama kümesi kullanır, bu nedenle scanf bir '\ n' - satırsonu ile karşılaşana kadar değerleri almaya devam eder, böylece boşluklar da kaydedilir.

#include <stdio.h>

int main (int argc, char const *argv[])
{
    char name[20];
    scanf("%[^\n]s",name);
    printf("%s\n", name);
    return 0;
}

1
Arabellek taşmalarına dikkat edin. Kullanıcı 50 karakterli bir "ad" yazarsa, program muhtemelen çökecektir.
brunoais

3
Arabellek boyutunu bildiğiniz gibi, %20[^\n]sarabellek taşmalarını önlemek için kullanabilirsiniz
osvein

45 puan ve kimse sorada olmanın bariz kargo kültüne dikkat çekmedi !
Antti Haapala

22

Bunu kullanabilirsin

char name[20];
scanf("%20[^\n]", name);

Veya bu

void getText(char *message, char *variable, int size){
    printf("\n %s: ", message);
    fgets(variable, sizeof(char) * size, stdin);
    sscanf(variable, "%[^\n]", variable);
}

char name[20];
getText("Your name", name, 20);

DEMO


1
Test etmedim, ancak bu sayfadaki diğer yanıtlara dayanarak, örneğinizde scanf için doğru arabellek boyutunun şöyle olacağına inanıyorum: scanf("%19[^\n]", name);(kısa yanıt için hala +1)
Dr Beco

1
Bir yan not gibi sizeof(char), tanımı gereği her zaman 1'dir, dolayısıyla onunla çarpmaya gerek yoktur.
paxdiablo

8

scanf()Alan genişliği belirtmeden dizeleri okumak için kullanmayın . Hatalar için dönüş değerlerini de kontrol etmelisiniz:

#include <stdio.h>

#define NAME_MAX    80
#define NAME_MAX_S "80"

int main(void)
{
    static char name[NAME_MAX + 1]; // + 1 because of null
    if(scanf("%" NAME_MAX_S "[^\n]", name) != 1)
    {
        fputs("io error or premature end of line\n", stderr);
        return 1;
    }

    printf("Hello %s. Nice to meet you.\n", name);
}

Alternatif olarak, şunu kullanın fgets():

#include <stdio.h>

#define NAME_MAX 80

int main(void)
{
    static char name[NAME_MAX + 2]; // + 2 because of newline and null
    if(!fgets(name, sizeof(name), stdin))
    {
        fputs("io error\n", stderr);
        return 1;
    }

    // don't print newline
    printf("Hello %.*s. Nice to meet you.\n", strlen(name) - 1, name);
}

6

fgets()Bir dizeyi okumak için işlevi kullanabilir veya scanf("%[^\n]s",name);bir satırsonu karakteriyle karşılaştığınızda dize okumasının sona erdirilmesi için kullanabilirsiniz .


Bunun arabellek taşmalarını engellemediğine dikkat edin
brunoais

soraya ait değil
Antti Haapala

5

getline()

Artık POSIX'in bir parçası, hiçbiri yok.

Ayrıca, daha önce sorduğunuz tampon ayırma sorunuyla da ilgilenir free, ancak hafızayla ilgilenmeniz gerekir .


Standart? Başvuruda alıntı yaparsınız: "Hem getline () hem de getdelim () GNU uzantılarıdır."
AProgrammer

1
POSIX 2008 getline'ı ekler. Dolayısıyla GNU, 2.9 sürümünde glibc için başlıklarını değiştirdi ve birçok proje için sorun yaratıyor. Kesin bir bağlantı değil, ancak şuraya bakın: bugzilla.redhat.com/show_bug.cgi?id=493941 . Çevrimiçi man sayfasına gelince, Google'ın bulduğu ilk sayfayı aldım.
dmckee --- eski moderatör yavru kedi

3

Birisi hala bakıyorsa, işte benim için işe yarayan şey - boşluklar da dahil olmak üzere rastgele uzunlukta bir dizi okumak.

Bu basit ve zarif çözümü paylaştığınız için web'deki birçok afişe teşekkürler. Eğer işe yararsa, kredi onlara gider ama herhangi bir hata benimdir.

char *name;
scanf ("%m[^\n]s",&name);
printf ("%s\n",name);

2
Bunun bir POSIX uzantısı olduğunu ve ISO standardında bulunmadığını belirtmek gerekir . Tamlık için, muhtemelen errnoayrılan belleği de kontrol etmeli ve temizlemelisiniz.
paxdiablo

starama setinden sonra oraya ait değil
Antti Haapala

1

scanfBu amaçla küçük bir numara ile kullanabilirsiniz . Aslında, kullanıcı Enter ( \n) tuşuna basana kadar kullanıcı girişine izin vermelisiniz . Bu, boşluk dahil her karakteri dikkate alacaktır . İşte örnek:

int main()
{
  char string[100], c;
  int i;
  printf("Enter the string: ");
  scanf("%s", string);
  i = strlen(string);      // length of user input till first space
  do
  {
    scanf("%c", &c);
    string[i++] = c;       // reading characters after first space (including it)
  } while (c != '\n');     // until user hits Enter
  string[i - 1] = 0;       // string terminating
return 0;
}

Bu nasıl çalışır? Kullanıcı standart girdiden karakterler girdiğinde, ilk boş alana kadar dize değişkeninde saklanacaktır . Bundan sonra, girişin geri kalanı giriş akışında kalacak ve bir sonraki taramayı bekleyecektir. Sonra, forgiriş akışından (kadar \n) karaktere göre karakter alan ve bunları dizenin sonuna ekleyen bir döngümüz var. değişkeninin , böylece klavyeden kullanıcı girdisiyle aynı tam bir dizgi oluşturan bir .

Umarım bu birine yardımcı olur!


Arabellek taşmasına tabidir.
paxdiablo

0

scanf()Bu tür şeyler için gerçekten kullanmamalısınız , çünkü gets()veya gibi çok daha iyi çağrılar vardır getline(), bu yapılabilir:

#include <stdio.h>

char* scan_line(char* buffer, int buffer_size);

char* scan_line(char* buffer, int buffer_size) {
   char* p = buffer;
   int count = 0;
   do {
       char c;
       scanf("%c", &c); // scan a single character
       // break on end of line, string terminating NUL, or end of file
       if (c == '\r' || c == '\n' || c == 0 || c == EOF) {
           *p = 0;
           break;
       }
       *p++ = c; // add the valid character into the buffer
   } while (count < buffer_size - 1);  // don't overrun the buffer
   // ensure the string is null terminated
   buffer[buffer_size - 1] = 0;
   return buffer;
}

#define MAX_SCAN_LENGTH 1024

int main()
{
   char s[MAX_SCAN_LENGTH];
   printf("Enter a string: ");
   scan_line(s, MAX_SCAN_LENGTH);
   printf("got: \"%s\"\n\n", s);
   return 0;
}

2
Bir var bir neden neden getskaldırılmış ve (uzaklaştırıldı stackoverflow.com/questions/30890696/why-gets-is-deprecated standardından). Daha da kötüsü , scanfçünkü en azından ikincisinin onu güvenli hale getirme yolları var.
paxdiablo

-1
/*reading string which contains spaces*/
#include<stdio.h>
int main()
{
   char *c,*p;
   scanf("%[^\n]s",c);
   p=c;                /*since after reading then pointer points to another 
                       location iam using a second pointer to store the base 
                       address*/ 
   printf("%s",p);
   return 0;
 }

4
Bunun neden doğru cevap olduğunu açıklayabilir misin? Lütfen yalnızca kod içeren yanıtları göndermeyin.
Theo

starama setinden sonra oraya ait değil
Antti Haapala
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.