Como você permite a inserção de espaços usando o scanf?


129

Usando o seguinte código:

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

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

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

Um usuário pode digitar seu nome, mas quando ele digita um nome com um espaço como Lucas Aardvark, scanf()apenas corta tudo depois Lucas. Como faço para scanf()permitir espaços


9
Observe que mais idiomático é 'malloc (sizeof (char) * 256 + 1)' ou 'malloc (256 + 1)' ou melhor ainda (assumindo que 'name' será usado estritamente localmente) 'char name [256 + 1 ] ' O '+1' pode atuar como um mneumônico para o terminador nulo, que precisa ser incluído na alocação.
Barry Kelly

@ Barry - Eu suspeito que sizeof(char) + 256foi um erro de digitação.
22611 Chris Lutz

Respostas:


186

As pessoas (e especialmente os iniciantes) nunca devem usar scanf("%s")ou gets()ou quaisquer outras funções que não possuam proteção contra estouro de buffer, a menos que você tenha certeza de que a entrada sempre terá um formato específico (e talvez nem isso).

Lembre-se do que scanfsignifica "digitalização formatada" e há muito pouco menos formatado que os dados inseridos pelo usuário. É ideal se você tiver controle total do formato dos dados de entrada, mas geralmente inadequado para a entrada do usuário.

Use fgets()(que possui proteção contra estouro de buffer) para inserir sua entrada em uma sequência e sscanf()avaliá-la. Como você deseja apenas o que o usuário digitou sem analisar, não é realmente necessário sscanf(), neste caso:

#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
Eu não sabia fgets(). Na verdade, parece mais fácil usar então scanf(). +1
Kredns 8/08/09

7
Se você apenas deseja obter uma linha do usuário, é mais fácil. Também é mais seguro, pois você pode evitar estouros de buffer. A família scanf é realmente útil para transformar uma string em coisas diferentes (como quatro caracteres e um int, por exemplo, com "% c% c% c% c% d"), mas, mesmo assim, você deve usar fgets e sscanf, não scanf, para evitar a possibilidade de estouro de buffer.
22420

4
Você pode colocar o tamanho máximo do buffer no formato scanf, você não pode colocar um em tempo de execução computado sem criar o formato no tempo de execução (não há o equivalente a * para printf, * é um modificador válido para scanf com outro comportamento: suprimindo a atribuição )
AProgrammer 08/08/09

Observe também que scanftem comportamento indefinido se a conversão numérica exceder o limite ( N1570 7.21.6.2p10 , última frase, redação inalterada desde C89), o que significa que nenhuma das scanffunções pode ser usada com segurança na conversão numérica de entrada não confiável.
Zwol 30/04/19

@ JonathanKomar e qualquer outra pessoa que estiver lendo isso no futuro: se o seu professor lhe scanfdisser que você deve usar uma tarefa, eles estão errados ao fazê-lo, e você pode dizer a eles que eu disse e se eles querem discutir comigo sobre isso , meu endereço de e-mail é facilmente encontrado no meu perfil.
Zwol 30/04/19

124

Experimentar

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

Espero que ajude.


10
(1) Obviamente, para aceitar espaços, você precisa colocar um espaço na classe de personagem. (2) Observe que 10 é o número máximo de caracteres que serão lidos; portanto, str deve apontar para um buffer de tamanho 11 pelo menos. (3) O final s aqui não é uma diretiva de formato, mas o scanf tentará aqui corresponder exatamente. O efeito será visível em uma entrada como 1234567890s onde os s serão consumidos, mas não colocará onde. Uma outra letra não será consumida. Se você colocar outro formato após o s, ele será lido apenas se houver um s a ser correspondido.
AProgrammer 08/08/09

Outro problema em potencial, o uso de - em outro local que não o primeiro ou o último é a implementação definida. Geralmente, é usado para intervalos, mas o que o intervalo designa depende do conjunto de caracteres. EBCDIC tem buracos na carta faixas e até mesmo ao assumir um ASCII derivado charsets é ingênuo pensar que todas as letras minúsculas estão na faixa az ...
AProgrammer

1
"% [^ \ n]" tem o mesmo problema que gets (), estouro de buffer. Com a captura adicional de que a \ n final não é lida; isso ficará oculto pelo fato de que a maioria dos formatos começa ignorando os espaços em branco, mas [não é um deles. Eu não entendo a instância em usar scanf para ler seqüências de caracteres.
AProgrammer 9/08/09

1
Removido o sfinal da string de entrada, pois é supérfluo e incorreto em certos casos (como apontado nos comentários anteriores). [é o seu próprio especificador de formato, em vez de alguma variação desse s.
22415

54

Este exemplo usa um conjunto de varredura invertido, portanto o scanf continua recebendo valores até encontrar uma nova linha '\ n' -, para que os espaços sejam salvos também

#include <stdio.h>

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

1
Cuidado com estouros de buffer. Se o usuário escrever um "nome" com 50 caracteres, o programa provavelmente falhará.
Brunoais

3
Como você sabe o tamanho do buffer, você pode usar %20[^\n]spara evitar buffer overflows
osvein

45 pontos e ninguém apontou o óbvio culto de carga de ter slá!
Antti Haapala

22

Você pode usar isso

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

Ou isto

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
Eu não testei, mas com base em outras respostas nesta mesma página, eu acredito que o tamanho do buffer correto para scanf no seu exemplo seria: scanf("%19[^\n]", name);(ainda +1 para a resposta concisa)
Dr Beco

1
Apenas como uma observação lateral, sizeof(char)é por definição sempre 1, então não há necessidade de multiplicar por ela.
22416

8

Não use scanf()para ler strings sem especificar a largura do campo. Você também deve verificar os valores de retorno para erros:

#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);
}

Como alternativa, use 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

Você pode usar a fgets()função para ler uma string ou usar scanf("%[^\n]s",name);para que a leitura da string termine ao encontrar um caractere de nova linha.


Cuidado para que isso não impeça o estouro de buffer #
3055

snão pertence lá
Antti Haapala

5

getline()

Agora parte do POSIX, no entanto.

Ele também cuida do problema de alocação de buffer que você perguntou anteriormente, embora seja necessário cuidar da freememória.


Padrão? Na referência que você cita: "Ambos getline () e getdelim () são extensões GNU."
AProgrammer 09/08/2009

1
O POSIX 2008 adiciona getline. Então o GNU foi adiante e mudou seus cabeçalhos para o glibc na versão 2.9, e está causando problemas para muitos projetos. Não é um link definitivo, mas veja aqui: bugzilla.redhat.com/show_bug.cgi?id=493941 . Quanto à página de manual on-line, peguei a primeira que o google encontrou.
dmckee --- ex-moderador gatinho

3

Se alguém ainda está procurando, aqui está o que funcionou para mim - ler um comprimento arbitrário de string, incluindo espaços.

Agradecemos a muitos pôsteres na web por compartilhar esta solução simples e elegante. Se funcionar, o crédito vai para eles, mas quaisquer erros são meus.

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

2
Vale a pena notar que esta é uma extensão POSIX e não existe no padrão ISO. Para garantir a integridade, você provavelmente também deve verificar errnoe limpar a memória alocada.
paxdiablo

snão pertence a esse local após o scanset
Antti Haapala

1

Você pode usar scanfpara esse fim com um pequeno truque. Na verdade, você deve permitir a entrada do usuário até que ele pressione Enter ( \n). Isso considerará todos os personagens, incluindo o espaço . Aqui está um exemplo:

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;
}

Como isso funciona? Quando o usuário insere caracteres da entrada padrão, eles serão armazenados na variável de sequência até o primeiro espaço em branco. Depois disso, o restante da entrada permanecerá no fluxo de entrada e aguarde o próximo scanf. Em seguida, temos um forloop que pega char por char do fluxo de entrada (até \n) e os anexa ao final da string variável , formando assim uma string completa igual à entrada do usuário do teclado.

Espero que isso ajude alguém!


Sujeito a estouro de buffer.
paxdiablo

0

Embora você realmente não deva usar scanf()esse tipo de coisa, porque há chamadas muito melhores, como gets()ou getline(), isso pode ser feito:

#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
Há uma razão pela qual getsfoi preterido e removido ( stackoverflow.com/questions/30890696/why-gets-is-deprecated ) do padrão. É ainda pior que scanfporque pelo menos o último tem maneiras de torná-lo seguro.
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
Você pode explicar por que essa é a resposta correta? Por favor, não poste respostas somente de código.
Theo

snão pertence lá após a scanset
Antti Haapala
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.