O scanf pode parecer chato ao ler strings pelo fato de não tratar espaços em branco e não retirar o ENTER do teclado. Seu domínio tem sido, junto com o printf, um dos maiores problemas para quem está aprendendo C. Mas eles são poderosos.
Existem outros parâmetros poucos usados no printf e no scanf. Por exemplo, praticamente ninguém testa o valor de retorno do scanf.
Digite uma frase para o código abaixo e irás pendurar teu código:
int v=0;
printf("Digite um número positivo:\n");
while (v<=0){
scanf("%d", &v);
if (v<=0) {
printf("Erro. Você digitou %d\n", v);
}
}
Vamos, compile e execute o código anterior. Quando te pedir um número, digite "bla" e veja o que acontece!
O que ocorreu aqui é que não sendo número, o scanf nada leu e deixou o v com o valor que tinha. Manteve o "bla" no buffer que acabou sendo lido e ignorado indefinidamente, sempre o v permanecendo em zero.
Alguns colocariam um fflush(stdin) depois do scanf. Não vou nem dizer o que penso destas saídas sujas...
Mas se o caro programador tivesse testado o retorno do scanf:
int v=0;
while (v<=0){
printf("Digite um número positivo:\n");
if (scanf("%d", &v)==0){
printf("Encontrado lixo. Você deve digitar NUMERO!! Numero, entende?\n");
while(fgetc(stdin)!='\n');
continue;
}
if (v<=0) {
printf("Erro. Você digitou %d\n", v);
}
}
O scanf retorna quantos elementos foram lidos (não bytes). Como eu quis ler um decimal, espero como retorno do scanf o valor 1. Se o usuário digitar algo que não seja número, como o "bla", o scanf nada lerá e retornará 0. Aí eu leio todos os caracteres até encontrar um enter e torno a solicitar a leitura.
Particularmente o scanf não é uma boa saída para ler strings, pois ele é limitado ao controle de overflow. Usar o gets é ainda pior, tanto que o compilador C do Linux há muito tempo reclama quando se usa um gets:
make testes
cc testes.c -o testes
/tmp/ccCr3szO.o: In function `main':
testes.c:(.text+0x1d): warning: the `gets' function is dangerous and should not be used.
Ele até compilou, mas está te dizendo que ele não gostou de teres usado o gets, pois é uma função insegura.
O melhor caminho seria usar o fgets, pois ele permite especificar o limite máximo de caracteres que se pode digitar.
[2] Comentário enviado por cesar em 07/08/2009 - 11:45h
Boa elgio,
Parabéns, aproveitando vou fazer uma pergunta =P
quando eu tenho um printf assim por exemplo, printf ("Número"); palavras com acento(entre outras) dentro do printf ao executar a impressão saí "suja" com caracter especial (estranho), como posso resolver isto?
[3] Comentário enviado por foguinho.peruca em 07/08/2009 - 22:11h
^^''
Bom artigo!
Lembrou os bons e velhos tempos da faculdade, bem no começo, onde a gente aprendia logica e implementa em C ainda....
Uma pergunta: fflush(stdin) "suja" o buffer? Eu jurava que limpava... qdo eu tinha problemas pra ler algo da entrada padrão eu "limpava" com o fflsuh(stdin). Bom, sempre resolveu.... ;)
[4] Comentário enviado por elgio em 08/08/2009 - 10:21h
Oi Jeff
Você não me entendeu.
Não quis dizer que o fflush "suja" o buffer. Ele limpa, como disseste.
Quis dizer que esta é uma solução "suja", no sentido de não tão boa. O jeito fácil de se fazer mas que não é a melhor.
Primeiro que o comportamento de fflush é distinto no Dos e no Linux.
Segundo que ao se usar o fflush PARA ESTE PROBLEMA inviabiliza completamente o uso de pipes e redirecionamentos.
Se no DOS tu criou um arquivo dados.txt:
Primeira linha
Segunda Linha
3455
34.5
E execucar:
C:\> teste.exe < dados.txt
O conteúdo o arquivo será jogado para o programa como se tivesse sido digitado.
Com a minha solução ele irá descartar as frases "Primeira linha", "Segunda linha" e irá ler o 3455 para o scanf("%d...
Com fflush ele irá descartar TUDO!
Então fflush é como explodir um prédio porque uma parede precisa ser refeita, entende! Neste caso!
[6] Comentário enviado por inacioalves em 30/10/2009 - 11:03h
Excelente artigo professor Elgio.
Eu particularmente não conhecia estes parâmetros de scanf e printf. Para fazer a leitura de uma por exemplo, eu escrivia minha própria função indicando um caractere de quebra. Vou indicar este artigo para meus alunos usarem no próximo trabalho.
[8] Comentário enviado por grammaton em 03/03/2010 - 10:37h
Amigo, bom dia.
Preciso criar um prompt para uma aplicação no console do linux, mas usando o scanf quando aperto as teclas direcionais aparece lixo com :
^[[C^[[A^[[D^[[B
Tem como fazer o scanf não imprimir esse lixo, ou funcionar como um prompt normal de console, isto é, ao pressionar as teclas o cursor mover pelo texto digitado.
Caso não seja possivel via scanf, poderia me indicar alguma lib? Pode ser em C ou C++.
[11] Comentário enviado por paulo1205 em 10/05/2017 - 15:49h
Excelente artigo, mas com um problema: ao apresentar uma forma de indicar a leitura strings formadas por caracteres em um (ou fora de um) conjunto, a especificação de conjunto foi mostrada como se fosse um modificador da conversão de strings com "%s".
Na verdade, a coisa funciona de outro modo. "%[" é uma conversão de string de seu próprio direito, com regras diferentes de "%s".
Eis algumas semelhanças e diferenças entre "%s" e "%[".
SEMELHANÇAS:
- As duas conversões aceitam modificadores como infixos (entre o sinal iniciador da conversão, "%" (ou "%n$", especificado pelo POSIX para facilitar internacionalização), e o indicador de tipo da conversão, "s" ou "[").
- Como modificadores infixados, ambos aceitam a especificação de supressão de atribuição durante a conversão ("*"), de alocação de memória ("m", em sistemas compatíveis com POSIX.1-2008), de largura máxima (número inteiro em base decimal), e para permitir trabalhar com strings com wide-characters ("l" (L minúsculo), que indica que os ponteiros de caracteres que receberão a string são do tipo "wchar_t *", em vez de "char *" -- ou "wchar_t **"/"char **", se o modificador "m" também tiver sido empregado).
- Ambas interrompem a conversão em caso de fim de arquivo ou erro de leitura.
DIFERENÇAS:
- "%s" realiza o descarte de espaços em branco antes de começar a aceitar caracteres. "%[" não faz descartes; se o usuário precisar ignorar espaços antes da string que pretende ler, deve especificar esse descarte explicitamente antes de iniciar a conversão.
- "%[" trabalha com modificador na forma de sufixo, que é a forma de indicar o conjunto de caracteres a ser considerados ou excluídos, desde o primeiro caráter após o "[" até o próximo caráter "]", que encerra o sufixo. "%s" não aceita sufixos.
Quando alguém usa scanf() na forma «scanf("%[^\n]s", char_array)», está dizendo o seguinte: "leia uma string com um ou mais caracteres diferentes de quebra de linha, gravando-os em ‘char_array’, e depois tente encontrar o caráter 's'. Durante a execução dessa chamada de função, se o usuário digitar «Fulano» e apertar a tecla <Enter>, a função vai gravar "Fulano\0" dentro de ‘char_array’, e vai interromper a execução antes de chegar ao final da string de formatação, ao notar que o próximo caráter no buffer de entrada, que é o '\n' correspondente à tecla <Enter>, não corresponde ao caráter 's' solicitado. O '\n' é então deixado no buffer de entrada para a próxima operação de leitura.
A forma certa (i.e. que esgota toda a string de formatação) de conseguir o mesmo resultado seria dizer simplesmente «scanf("%[^\n]", char_array)». Mas provavelmente o que o usuário desejaria seria algo que conseguisse perceber e consumir a quebra de linha, deixando o buffer de leitura “limpo” para a próxima operação. Isso poderia ser feito com «scanf("%[^\n]%*1[\n]", char_array)».