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.744 ]

Por: Elgio Schlemer em 06/08/2009 | Blog: https://profelgio.duckdns.org/~elgio


O velho e bom printf



O printf, velho "amigo" dos programadores C, é esquisito a primeira vista. Principalmente ao pessoal que está aprendendo lógica, acostumado com algoritmos, demora um pouco para entender seu jeito de ser.

Basicamente o printf do C é dividido em duas partes bem distintas, sendo que pelo menos a primeira delas deve existir.

A primeira parte é chamada de string de formato ou, do inglês, format string. Ela nada mais é do que dizer ao printf que faça alguma coisa.

Exemplo:

printf("Ola");

Isto pode ser interpretado como uma ordem ao printf:
  • imprima a letra 'O';
  • imprima a letra 'l';
  • imprima a letra 'a'.

Dentro da string de formato, algumas sequências especiais de caracteres são comandos, como o '\n' por exemplo:

printf("Ola\n");

Isto pode ser interpretado como uma ordem ao printf:
  • imprima a letra 'O';
  • imprima a letra 'l';
  • imprima a letra 'a'.
  • imprima uma quebra de linha ('\n')

Na verdade sempre que na string de formato for encontrada uma contra barra ('\') ou um porcento ('%') será um comando. Alguns comandos mais usados:
  • \t - imprima uma tabulação
  • \\ - imprima uma contra barra
  • %d - imprima um valor em formato decimal (e como tem gente que confunde o 'd' de double. Para double use %lf)
  • %c - imprima um caractere
  • %i - imprima um valor no formato inteiro
  • %f - imprima um valor no formato ponto flutuante
  • %lf - imprima um valor no formato ponto flutuante long (long float), ou seja, double
  • %% - imprima o caractere %

Se a string de formato tiver um comando que mande imprimir alguma coisa, esta "alguma coisa" deverá ser fornecida. Assim, a segunda parte do printf são os parâmetros que satisfazem a string de formato. Exemplo:

printf ("Val=%d\n", 345);

  • imprima o 'V';
  • imprima o 'a';
  • imprima o 'l';
  • imprima o '=';
  • imprima um número em formato decimal ('%d');
  • imprima uma quebra de linha ('\n')

A segunda parte possui apenas um valor, o 345, que será usado pelo único %d existente.

Para cada comando que necessite de um parâmetro, um valor a mais deve estar na segunda parte. Coisas "estranhas" ocorrem quando se passa parâmetros de menos ou de mais.

Mas o objetivo deste artigo não é explicar o printf. Ele tem muitas outras opções além destas. O objetivo é tratar alguns parâmetros interessantes, pouco explorados pela maioria dos programadores. Começando pelo retorno da função, que praticamente ninguém usa.

O printf retorna um número inteiro, que é justamente a quantidade de caracteres que foram impressos.

tot = printf("Ola %d", 300);

No trecho de código anterior, tot acabará tendo o valor 7, pois será impresso na tela "Ola 300" (sem quebra de linha) e isto totalizaram sete caracteres.

Mas é no quesito "formatação" que o printf mostra do que é capaz.

É possível colocar o número de casas reservadas para a impressão, junto com o printf. Exemplo:

printf("Valor = %5d\n", 3);
printf("Valor = %5d\n", 30);
printf("Valor = %5d\n", 300);
printf("Valor = %5d\n", 3000);
printf("Valor = %5d\n", 30000);

O %5d indica que será impresso um decimal usando cinco casas decimais. As casas não usadas serão preenchidas com espaço em branco. A sequência de comandos anterior irá gerar a seguinte saída:

Valor =     3
Valor =    30
Valor =   300
Valor =  3000
Valor = 30000

Observe que os números ficaram à direita. Caso seja do interesse que o número fique à esquerda, basta colocar um sinal de menos antes:

printf("a=[%5d]\n", 3);
printf("a=[%-5d]\n", 3);

Observe como o número 3 foi ajustado entre os colchetes nas duas versões do printf.

Em se tratando de números, é ainda possível usar o ZERO para preencher os espaços não usados:

printf("a = %05d\n", 3);
printf("a = %05d\n", 30);
printf("a = %05d\n", 300);

Estes parâmetros são até bem conhecidos. Mas o que poucos sabem é que o 5, número de casas, pode ser também uma variável!

Considere este exemplo:

char frases[4][200];
int i;

for (i = 0; i < 4; i++){
    /* lendo 4 frases */
    scanf("%s", frases[i]);
}

for (i = 0; i < 4; i++){
    /* imprimindo formatado as 4 frases */
    printf("%-10s %d\n", frases[i], i);
}

Se o usuário digitar as seguintes frases:

Apenas_Um_Teste
lixo
Vol
hehehe

Será impresso na tela:

Apenas_Um_Teste 0
lixo       1
Vol        2
hehehe     3

Veja que o "Apenas_um_teste" foi maior do que o tamanho 10 inicialmente pensado, por isto ele ficou desformatado.

No entanto, o seguinte código tem um aspecto bem melhor:

char frases[4][200];
int i, m=-1,x;

for (i = 0; i < 4; i++){
    /* lendo 4 frases */
    scanf("%s", frases[i]);
    x = strlen(frases[i]);
    if (x > m)
       m = x;
}

m = m + 1;
for (i = 0; i < 4; i++){
    /* imprimindo formatado as 4 frases */
    printf("%-*s %d\n", m, frases[i], i);
}

Agora o tamanho da maior string digitada é armazenado na variável m. Quando elas foram impressas, usou-se o comando:

printf("%-*s %d\n", m, frases[i], i);

Veja que tem um asterisco no lugar de onde estaria o valor 10. Isto indica ao printf para usar um dos parâmetros no lugar do *.

    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

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

Criptografia assimétrica com o RSA

A mágica do dc

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

Estrutura do Iptables

Leitura recomendada

lib cURL - Trabalhe com URLs em C

Acessando a porta paralela via Linux

Tratamento de exceções na linguagem C

Dynamic libraries com libtool

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

  
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