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: 195.004 ]

Por: Elgio Schlemer em 06/08/2009 | Blog: https://elgio.prof.nom.br/~elgio


Conclusão



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.

Página anterior    

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

Estrutura do IPTables 2: a tabela nat

Criptografia chave simétrica de bloco e de fluxo

Introdução a criptografia

Túneis cifrados com SSH

Cuidado com números em Ponto Flutuante

Leitura recomendada

Programação de Jogos com SDL

Acessando a porta paralela via Linux

OneAPI: A plataforma da Intel para facilitar o desenvolvimento com chips Intel, AMD, ARM, NVIDIA POWER e FPGA

Ponteiros - Saindo de Pesadelos

Programação com números inteiros gigantes

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

[12] Comentário enviado por douglascosta10 em 17/05/2019 - 10:15h

Bom Dia,

Excelente artigo... Estou com uma duvida..

Caso fosse necessário formatar uma saída de dados assim:

nome do carro cor valor
gol vermelho R$: 45.000

se fosse com numero colocaríamos por exemplo "%s %10s %5d " (Um exemplo)

Tem como fazer essa formatação pegando o valor através de um strlen com o tamanho da variável e usar a variável como parâmetro no lugar do numero?

Abraço

[13] Comentário enviado por douglascosta10 em 17/05/2019 - 11:12h




[14] Comentário enviado por Keghtlezt em 24/02/2020 - 14:57h

Teria como fazer cálculos e usar diferentes caracteres de uma string para comandos condicionais através do "scanf" ou até mesmo do "gets"?


Contribuir com comentário