Parâmetros interessantes do scanf e do printf em C

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.

[ Hits: 159.412 ]

Por: Elgio Schlemer em 06/08/2009 | Blog: http://gravatai.ulbra.tche.br/~elgio


Lendo com scanf



O scanf possui comportamento semelhante ao printf, só que para leitura. Da mesma forma que %d no printf significa imprimir um decimal, no scanf significa ler um decimal.

Exemplos comuns do uso do scanf:

int val1, val2;
float val3;

printf("Digite dois números em um ponto flutuante:\n");
scanf("%d", &val1); // lê um decimal e coloca na variável val1
scanf("%i", &val2); // lê um inteiro e coloca na variável val2
scanf("%f", &val3); // lê um ponto flutuante e coloca na variável val3

printf("val1 = %d\n", val1);
printf("val2 = %d\n", val2);
printf("val3 = %f\n", val3);

Qual a diferença em ler um decimal (%d) ou ler um inteiro (%i)?

Será que tem diferença?

Sim, tem.

Veja o que acontece quando se usa o código anterior:

make testes
cc     testes.c   -o testes
./testes
Digite dois números em um ponto flutuante:
020
020
3.4

val1 = 20
val2 = 16
val3 = 3.400000

Para ambos os inteiros foi digitado 020, como você explica que o val2 tem 16?

Acontece que quando lido com %i o scanf irá interpretar a leitura. Se o que for digitado começar com 0 (ZERO) a leitura será interpretada como octal. 020 em octal corresponde ao decimal 16. Da mesma forma se o número começar com 0x a leitura será considerada como hexadecimal. Se digitar 0x10 para val2 o resultado impresso será 16.

Contudo, é na leitura de strings que o scanf tem recursos!

Muitos reclamam do fato de que o scanf não lê espaços em branco ao ler strings:

char frase[200];

printf("Digite frase:\n");
scanf("%s", frase);

printf("Você digitou: %s\n", frase);

Ao compilar este código e executá-lo:

make testes
cc     testes.c   -o testes
./testes
Digite frase:
Apenas um teste
Você digitou: Apenas

O que houve com o resto da frase? Porque ele não leu a parte "um teste" que foi digitado?

Acontece que o scanf lê caracteres até encontrar algum que não sirva. Quebras de linha e espaços em branco indicam ao scanf que deve parar a leitura. Mas isto pode ser mudado no scanf:

char frase[200];

printf("Digite frase:\n");
scanf("%[A-Z]s", frase);

printf("Você digitou: %s\n", frase);

Agora apenas letras maiúsculas servem e tão logo o scanf encontre algo que não seja letra maiúscula, ele irá parar de ler:

./testes
Digite frase:
APEnas Um teste
Você digitou: APE

Veja que agora ele leu somente as três letras APE, parando assim que encontrou uma não maiúscula.

Posso colocar todos os tipos de caracteres que valem como leitura dentro dos colchetes:

scanf("%[A-Z a-z 0-9]s", frase);

Agora, na digitação da frase "Apenas uns 200 testes, fim" ele irá ler "Apenas uns 200 testes" parando de ler quando encontrar a vírgula, pois a vírgula não faz parte dos caracteres válidos. Posso até mesmo colocar o ENTER como uma entrada válida:

scanf("%[A-Z a-z 0-9\n]s", frase);

Agora nem mesmo o pressionar de um ENTER irá encerrar a leitura, apenas se for digitado algum caractere que não faça parte do especificado (vírgula, por exemplo).

Também é possível colocar uma exceção, ou seja, o que não pode:

scanf("%[^\n]s", frase);

Agora ele aceita tudo, menos quebra de linha. Pronto, o scanf tem agora o mesmo comportamento do gets e passou a ler espaços em branco.

Se, ainda, se desejar evitar overflow na leitura:

scanf("%199[A-Z a-z 0-9\n]s", frase);

Agora ele não deixará colocar mais do que 199 caracteres a variável frase, já que ela só suporta 200 e precisa-se deixar um de reserva para o final de string (infelizmente não para para usar o * do printf aqui).

Página anterior     Próxima página

Páginas do artigo
   1. O velho e bom printf
   2. Lendo com scanf
   3. Conclusão
Outros artigos deste autor

Programação com números inteiros gigantes

Introdução a criptografia

Iptables protege contra SYN FLOOD?

255.255.255.0: A matemática das máscaras de rede

Estrutura do IPTables 2: a tabela nat

Leitura recomendada

Desenvolvendo para microcontroladores em GNU/Linux

Linguagem C - Funções Variádicas

Bug afeta todas as distros

Utilizando técnicas recursivas em C e C++

SDL - Ótimo para criação de jogos

  
Comentários
[1] Comentário enviado por fachmann em 07/08/2009 - 11:33h

Excelente!
Vai quebrar um galhão!
Abraço.

[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?

[]'s

[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.... ;)

Jeff

[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!

[5] Comentário enviado por thiagoamm em 08/08/2009 - 23:33h

Otimo artigo.
Bastante didatico e aborda algo fundamental.
Parabens!!!

[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.

[7] Comentário enviado por tulios em 03/01/2010 - 13:34h

Excelente Elgio!

Parabens...

[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++.

Grato

[9] Comentário enviado por mbcm94 em 14/10/2011 - 17:19h

scanf("%VARIAVEL[^\n]s", frase);

Boa tarde, gostaria de saber se é possivel passar uma variavel dentro da string parametro do scanf.

[10] Comentário enviado por elgio em 14/03/2013 - 21:00h


[9] Comentário enviado por mbcm94 em 14/10/2011 - 17:19h:

scanf("%VARIAVEL[^\n]s", frase);

Boa tarde, gostaria de saber se é possivel passar uma variavel dentro da string parametro do scanf.


Não, não é.

[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)».


Contribuir com comentário