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

Por: Elgio Schlemer em 06/08/2009 | Blog: https://profelgio.duckdns.org/~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

Criptografia assimétrica com o RSA

Cuidado com números em Ponto Flutuante

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

Criptografia chave simétrica de bloco e de fluxo

Guerra Infinita, uma análise da Ciência da Computação

Leitura recomendada

GNA: um Coprocessador para Aceleração Neural

Algum humor e C++ Design Patterns (parte 2)

Compilando o Mono 2.2 no Ubuntu 8.10

Programação de Jogos com SDL

Projeto Icecream (parte 1)

  
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




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts