Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Entrada e saída com printf e scanf

Em C, a conversa com o terminal passa pela biblioteca <stdio.h>. O printf escreve texto formatado na saída padrão: o primeiro argumento é o formato, e cada especificador (%d para int, %f para double, %c para char, %s para texto e %% para o próprio sinal de porcentagem) é trocado, na ordem, pelos argumentos seguintes. Entre o % e a letra cabem ajustes: %8.2f usa 8 colunas com 2 casas, %-10s alinha à esquerda e %05d completa com zeros.

C I/O Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
C / I/O

Entrada e saída com printf e scanf

por Beckett Hollis
Iniciante

Entenda o conceito

Em C, a conversa com o terminal passa pela biblioteca <stdio.h>. O printf escreve texto formatado na saída padrão: o primeiro argumento é o formato, e cada especificador (%d para int, %f para double, %c para char, %s para texto e %% para o próprio sinal de porcentagem) é trocado, na ordem, pelos argumentos seguintes. Entre o % e a letra cabem ajustes: %8.2f usa 8 colunas com 2 casas, %-10s alinha à esquerda e %05d completa com zeros.

O scanf faz o caminho inverso: lê da entrada padrão e converte o texto digitado conforme o formato. Como precisa gravar nas variáveis, ele recebe endereços: &idade para uma variável comum e só nome para um vetor de char. Atenção a uma assimetria: no scanf, double é %lf; no printf, %f basta.

O scanf devolve quantos campos conseguiu converter (ou EOF se a entrada acabou). Comparar esse número com o esperado é a forma de saber se o usuário digitou o que devia, antes de usar valores que podem ter ficado indefinidos.

Por fim, os especificadores %d, %lf e %s pulam espaços e quebras de linha antes do dado, mas %c lê qualquer caractere, inclusive o Enter que sobrou da leitura anterior; o remédio é um espaço no formato: " %c". E %s para no primeiro espaço e não sabe o tamanho do vetor, então informe o limite, como em %19s.

↳ Pontos-chave
  • printf: %d para int, %f para double, %c para char, %s para texto e %% para imprimir %; especificador e tipo precisam combinar.
  • Largura e precisão: %8.2f alinha à direita em 8 colunas com 2 casas, %-10s alinha à esquerda e %05d completa com zeros. A largura conta bytes, e letras acentuadas ocupam 2 em UTF-8.
  • scanf recebe endereços: &quantidade para variáveis comuns e só nome para vetores de char. double se lê com %lf.
  • O retorno do scanf é o número de campos convertidos; compare com o esperado antes de usar as variáveis.
  • %c não pula o Enter que sobrou da leitura anterior: use " %c". E limite o %s ao tamanho do vetor menos 1, como %19s para char nome[20].

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Recibo da loja com especificadores, largura e precisão

recibo.c
/* recibo.c: especificadores, largura e precisão no printf */
#include <stdio.h>

int main(void)
{
    char loja[] = "Mercadinho Sol";
    char caixa = 'B';
    int pedido = 7;
    double desconto = 5.0;

    /* %s texto, %c caractere, %d inteiro */
    printf("%s | caixa %c | pedido %d\n", loja, caixa, pedido);

    /* %-10s alinha à esquerda em 10 colunas; %3d e %8.2f à direita */
    printf("%-10s %3s %8s\n", "Item", "Qtd", "Total");
    printf("%-10s %3d %8.2f\n", "Arroz", 2, 2 * 24.90);
    printf("%-10s %3d %8.2f\n", "Leite", 6, 6 * 5.49);
    printf("%-10s %3d %8.2f\n", "Biscoito", 10, 10 * 3.15);

    /* %% imprime o próprio sinal de porcentagem */
    printf("Desconto: %.0f%%\n", desconto);

    /* %05d completa com zeros à esquerda até 5 dígitos */
    printf("Pedido nº %05d\n", pedido);

    /* Sem precisão, %f mostra 6 casas; %e usa notação científica */
    printf("Sem precisão: %f\n", 24.90);
    printf("Científica: %e\n", 24.90);
    return 0;
}

Cada especificador consome, na ordem, um argumento do tipo certo: %s recebe o texto loja, %c o caractere 'B' e %d o inteiro 7. Na tabela, %-10s reserva 10 colunas e encosta o nome à esquerda, enquanto %3d e %8.2f encostam os números à direita; por isso 49.80, 32.94 e 31.50 ficam com o ponto decimal na mesma coluna. Os nomes dos itens não têm acento de propósito: a largura conta bytes, e uma letra como ã ocupa 2 bytes em UTF-8, o que desalinharia a coluna.

As últimas linhas mostram detalhes que pegam muita gente: %% é o jeito de escrever % no texto, %05d imprime 00007, %f sem precisão mostra 24.900000 e %e mostra 2.490000e+01. Na prática, para dinheiro, use sempre a precisão explícita %.2f.

02Multa da biblioteca lida com scanf e conferida pelo retorno

emprestimo.c
/* emprestimo.c: lendo uma palavra e dois números com scanf */
#include <stdio.h>

int main(void)
{
    char leitor[30];
    int dias;
    double multa_dia;

    /* Sem \n no prompt: a resposta fica na mesma linha */
    printf("Nome do leitor: ");
    fflush(stdout);   /* garante que o prompt aparece antes da leitura */

    /* %29s lê uma palavra e deixa espaço para o '\0' final */
    if (scanf("%29s", leitor) != 1) {
        puts("Nome não informado.");
        return 1;
    }

    printf("Dias de atraso e multa por dia: ");
    fflush(stdout);

    /* Variáveis comuns vão com &; double se lê com %lf */
    if (scanf("%d %lf", &dias, &multa_dia) != 2) {
        puts("Digite um inteiro e um valor, como: 4 1.50");
        return 1;
    }

    printf("Leitor: %s\n", leitor);
    printf("Multa: %d x R$ %.2f = R$ %.2f\n", dias, multa_dia, dias * multa_dia);
    return 0;
}

Digitando Ana e depois 4 1.50, o programa imprime Leitor: Ana e Multa: 4 x R$ 1.50 = R$ 6.00. O vetor leitor já é um endereço, então vai sem &; dias e multa_dia são variáveis comuns e precisam do & para o scanf saber onde gravar. Repare no %lf: no scanf ele lê um double, enquanto no printf o %f basta. O fflush(stdout) força o prompt a aparecer na tela antes de o programa parar esperando a digitação.

Cada scanf é conferido pelo valor que devolve, o número de campos convertidos. Se alguém digitar quatro no lugar dos dias, a conversão de %d falha, o scanf devolve 0 e o programa avisa, em vez de calcular com uma variável que nunca recebeu valor. O %29s também protege o vetor: um nome enorme é cortado em 29 caracteres em vez de escrever fora da memória reservada.

03Porções da receita: o Enter que sobra e o espaço antes de %c

porcoes.c
/* porcoes.c: o Enter que sobra no buffer e o " %c" */
#include <stdio.h>

int main(void)
{
    int porcoes;
    char tamanho;
    double farinha_g;

    printf("Quantas porções? ");
    fflush(stdout);
    if (scanf("%d", &porcoes) != 1 || porcoes <= 0) {
        puts("Informe um número inteiro positivo.");
        return 1;
    }

    printf("Tamanho (p/m/g)? ");
    fflush(stdout);
    /* O espaço antes de %c pula o Enter que ficou depois do número */
    if (scanf(" %c", &tamanho) != 1) {
        puts("Tamanho não informado.");
        return 1;
    }

    switch (tamanho) {
    case 'p':
        farinha_g = 80.0;
        break;
    case 'm':
        farinha_g = 120.0;
        break;
    case 'g':
        farinha_g = 160.0;
        break;
    default:
        printf("Tamanho '%c' desconhecido.\n", tamanho);
        return 1;
    }

    /* %6.1f: 6 colunas no total, 1 casa decimal */
    printf("Porções: %d (%c)\n", porcoes, tamanho);
    printf("Farinha: %6.1f g\n", porcoes * farinha_g);
    printf("Ovos:    %6d un\n", porcoes);
    return 0;
}

Ao digitar 4 e apertar Enter, o scanf("%d", ...) lê o 4 e deixa o \n do Enter esperando na entrada. %d, %lf e %s pulam espaços e quebras de linha antes do dado, mas %c não: sem o espaço, scanf("%c", &tamanho) leria esse \n na hora, sem esperar a digitação, e o programa cairia no default. O espaço em " %c" manda o scanf descartar todo espaço em branco antes do caractere.

Com 4 e m, a saída é Porções: 4 (m), Farinha: 480.0 g e Ovos: 4 un: %6.1f e %6d ocupam 6 colunas e alinham os números à direita. Como o scanf não se importa com quebras de linha entre os campos, digitar 4 m numa linha só também funciona.


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Etiqueta de preço lida do tecladoFácil

Leia da entrada padrão, numa única linha, o código do produto, a quantidade e o preço unitário, e imprima a etiqueta com o total. A saída esperada corresponde à entrada 42 3 19.90.

↳ Requisitos
  • Ler os três valores com um único scanf, usando %lf para o preço.
  • Mostrar o código com 5 dígitos completados com zeros (%05d).
  • Mostrar preço e total com exatamente duas casas decimais.
código inicial
#include <stdio.h>

int main(void)
{
    int codigo;
    int quantidade;
    double preco;

    /* Leia os três valores com um único scanf e imprima a etiqueta */

    return 0;
}
saída esperada
Produto #00042
Quantidade: 3
Preço unitário: R$ 19.90
Total: R$ 59.70
Ver dica

Cada especificador do scanf precisa de um endereço correspondente, na mesma ordem em que os valores aparecem na linha digitada.

Ver resposta
#include <stdio.h>

int main(void)
{
    int codigo;
    int quantidade;
    double preco;

    /* Três especificadores e três endereços, na ordem da entrada */
    scanf("%d %d %lf", &codigo, &quantidade, &preco);

    printf("Produto #%05d\n", codigo);   /* zeros à esquerda até 5 dígitos */
    printf("Quantidade: %d\n", quantidade);
    printf("Preço unitário: R$ %.2f\n", preco);
    printf("Total: R$ %.2f\n", quantidade * preco);
    return 0;
}

Passo a passo

  1. As três variáveis são declaradas com o tipo do dado que vão receber: código e quantidade são contagens (int) e o preço tem centavos (double).
  2. scanf("%d %d %lf", ...) lê os três valores da mesma linha. Cada especificador corresponde, na ordem, a um endereço: &codigo, &quantidade e &preco.
  3. O & entrega ao scanf o endereço da variável; sem ele, a função recebe o valor (ainda indefinido) e não tem onde gravar.
  4. %05d imprime o código em 5 colunas completando com zeros: 42 vira 00042, como numa etiqueta de verdade.
  5. %.2f mostra o preço e o total com duas casas. O total 3 * 19.90 é calculado direto no argumento do printf, porque não será usado de novo.

Por que funciona

O scanf percorre o texto de formato e a entrada ao mesmo tempo: cada %d ou %lf pula os espaços, converte os caracteres seguintes no tipo pedido e grava no endereço correspondente. Na saída, o printf faz o caminho inverso, transformando os valores em texto com a largura e a precisão escolhidas. Com a entrada 42 3 19.90, o produto de 3 por 19.90 dá 59.699999... em double, e o %.2f arredonda para 59.70.

Erros comuns

  • Esquecer o &: scanf("%d", codigo); compila (com aviso) e o programa costuma travar com falha de segmentação, porque grava num endereço qualquer. Use &codigo.
  • Ler double com %f: scanf("%f", &preco); espera um float, grava só metade dos bytes e o preço sai com lixo. No scanf, double é %lf.
  • Ordem trocada: scanf("%d %d %lf", &quantidade, &codigo, &preco); compila, mas a etiqueta mostra o produto #00003 com 42 unidades. A ordem dos endereços segue a dos dados na entrada.

Outra forma de resolver

Dá para ler cada valor num scanf próprio:

scanf("%d", &codigo);
scanf("%d", &quantidade);
scanf("%lf", &preco);

O resultado é o mesmo, já que %d e %lf pulam espaços e Enter. Separar as leituras é útil quando cada uma tem um prompt próprio.

Saída esperada

Produto #00042
Quantidade: 3
Preço unitário: R$ 19.90
Total: R$ 59.70

02Boletim com notas digitadas e colunas alinhadasMédio

Leia o primeiro nome de um aluno e suas três notas, confira se os quatro campos foram lidos e imprima o boletim com as notas e a média alinhadas pela casa decimal. A saída esperada corresponde à entrada Marina 7.5 8 10.

↳ Requisitos
  • Guardar o nome em char nome[20] e lê-lo com largura máxima (%19s).
  • Guardar o retorno do scanf e, se for diferente de 4, imprimir Entrada inválida: esperava nome e 3 notas e encerrar com return 1.
  • Imprimir quantos campos foram lidos e as notas e a média com %5.2f, para que 10.00 e 7.50 fiquem alinhados.
código inicial
#include <stdio.h>

int main(void)
{
    char nome[20];
    double n1, n2, n3;

    /* Leia o nome e as 3 notas, confira o retorno do scanf e imprima o boletim */

    return 0;
}
saída esperada
Campos lidos: 4
Aluno: Marina
Nota 1:  7.50
Nota 2:  8.00
Nota 3: 10.00
Média:   8.50
Ver dica

O scanf conta quantas conversões deram certo; e uma nota de dois dígitos inteiros com duas casas ocupa 5 colunas.

Ver resposta
#include <stdio.h>

int main(void)
{
    char nome[20];
    double n1, n2, n3;

    /* %19s: até 19 letras, sobrando 1 posição para o '\0' */
    int lidos = scanf("%19s %lf %lf %lf", nome, &n1, &n2, &n3);
    if (lidos != 4) {
        printf("Entrada inválida: esperava nome e 3 notas\n");
        return 1;
    }

    double media = (n1 + n2 + n3) / 3;

    printf("Campos lidos: %d\n", lidos);
    printf("Aluno: %s\n", nome);
    /* %5.2f: 5 colunas e 2 casas, números alinhados à direita */
    printf("Nota 1: %5.2f\n", n1);
    printf("Nota 2: %5.2f\n", n2);
    printf("Nota 3: %5.2f\n", n3);
    printf("Média:  %5.2f\n", media);
    return 0;
}

Passo a passo

  1. char nome[20] reserva espaço para um texto de até 19 caracteres mais o '\0' que marca o fim; as notas são double porque têm casas decimais.
  2. Um único scanf lê o nome e as três notas. nome vai sem & porque o nome de um vetor já é o endereço do primeiro elemento; as notas, que são variáveis comuns, levam &.
  3. %19s limita a leitura a 19 caracteres. Sem o número, um nome comprido escreveria além dos 20 bytes do vetor.
  4. O retorno do scanf é guardado em lidos. Se não for 4, algum campo não foi convertido, e o programa avisa e encerra com return 1 antes de usar valores indefinidos.
  5. A média soma as três notas antes de dividir; como as notas são double, a divisão por 3 não perde as casas decimais.
  6. %5.2f reserva 5 colunas: 7.50 ganha um espaço à esquerda e 10.00 ocupa tudo, então os pontos decimais ficam alinhados. O rótulo Média: recebe um espaço a mais no texto para acompanhar Nota 1:.

Por que funciona

O scanf devolve quantos campos conseguiu converter e para no primeiro que falha. Com a entrada Marina 7.5 8 10, ele converte os quatro e devolve 4; com Marina 7.5 oito 10, ele devolve 2 e deixa n2 e n3 sem valor. Comparar o retorno com o número esperado é a forma padrão de validar a leitura em C. Repare também que %lf aceita 8, sem ponto, e o converte para 8.0.

Erros comuns

  • Usar & no vetor: scanf("%19s", &nome); tem o tipo errado (char (*)[20]) e gera aviso no gcc. Use só nome.
  • %s sem largura: scanf("%s", nome); aceita qualquer tamanho e um nome com 25 letras estoura o vetor. Ponha o limite: %19s.
  • Ignorar o retorno: sem o if (lidos != 4), uma nota digitada como texto deixa a variável com lixo e a média sai absurda, sem aviso.
  • Largura curta: com %4.2f, o 10.00 não cabe, ocupa 5 colunas mesmo assim e empurra a linha, desalinhando a tabela.

Outra forma de resolver

Em vez de guardar o retorno numa variável, dá para testar direto no if:

if (scanf("%19s %lf %lf %lf", nome, &n1, &n2, &n3) != 4) {
    puts("Entrada inválida: esperava nome e 3 notas");
    return 1;
}

É mais curto e comum; a variável lidos só é necessária quando o número de campos também será exibido, como aqui.

Saída esperada

Campos lidos: 4
Aluno: Marina
Nota 1:  7.50
Nota 2:  8.00
Nota 3: 10.00
Média:   8.50

03Ficha do personagem com molduraDesafio

Leia, um por linha, o nome de um personagem, a letra da classe (g guerreiro, m mago, a arqueiro, em maiúscula ou minúscula), o nível e o ouro, e imprima a ficha numa moldura de largura fixa. A saída esperada corresponde à entrada Arwen, m, 12 e 350.5, cada valor numa linha.

↳ Requisitos
  • Ler o nome com %15s, a classe com " %c" e o nível e o ouro em uma terceira chamada, somando os retornos e imprimindo Ficha incompleta. se a soma não for 4.
  • Traduzir a letra com switch; letra desconhecida imprime Classe 'x' inválida. (com a letra lida) e encerra com return 1.
  • Imprimir os valores com %-16s, %-16d e %-16.2f para que a barra da direita fique na mesma coluna em todas as linhas.
código inicial
#include <stdio.h>

int main(void)
{
    char nome[16];
    char classe;
    int nivel;
    double ouro;

    /* Leia nome, classe, nível e ouro (um por linha), valide e imprima a ficha */

    return 0;
}
saída esperada
+--------------------------+
| Nome   : Arwen           |
| Classe : Mago            |
| Nível  : 12              |
| Ouro   : 350.50          |
+--------------------------+
Ver dica

Depois de ler o nome, o Enter ainda está na entrada; pense em qual especificador não o ignora e em como pedir ao scanf que pule espaços em branco.

Ver resposta
#include <stdio.h>

int main(void)
{
    char nome[16];
    char classe;
    int nivel;
    double ouro;
    const char *nome_classe;

    /* Três leituras: 1 + 1 + 2 campos; EOF (negativo) nunca soma 4 */
    int lidos = scanf("%15s", nome);
    lidos += scanf(" %c", &classe);   /* o espaço pula o Enter do nome */
    lidos += scanf("%d %lf", &nivel, &ouro);
    if (lidos != 4) {
        puts("Ficha incompleta.");
        return 1;
    }

    switch (classe) {
    case 'g':
    case 'G':
        nome_classe = "Guerreiro";
        break;
    case 'm':
    case 'M':
        nome_classe = "Mago";
        break;
    case 'a':
    case 'A':
        nome_classe = "Arqueiro";
        break;
    default:
        printf("Classe '%c' inválida.\n", classe);
        return 1;
    }

    /* Moldura de 28 colunas: rótulo de 11 + campo de 16 + borda */
    printf("+--------------------------+\n");
    printf("| Nome   : %-16s|\n", nome);
    printf("| Classe : %-16s|\n", nome_classe);
    printf("| Nível  : %-16d|\n", nivel);
    printf("| Ouro   : %-16.2f|\n", ouro);
    printf("+--------------------------+\n");
    return 0;
}

Passo a passo

  1. nome[16] guarda até 15 letras mais o '\0', e o %15s garante esse limite na leitura; nome_classe é um ponteiro para texto constante que o switch vai apontar para o nome da classe.
  2. O nome é lido com %15s, que para no Enter digitado depois de Arwen. Esse \n continua na entrada, esperando a próxima leitura.
  3. scanf(" %c", &classe) tem um espaço antes do %c: ele descarta o \n pendente (e quaisquer espaços) e só então lê o m. Sem o espaço, classe receberia o próprio \n.
  4. %d %lf lê o nível e o ouro. Os retornos das três chamadas são somados: só dá 4 quando todos os campos foram convertidos, e como EOF é negativo, uma entrada que acaba no meio nunca chega a 4.
  5. O switch traduz a letra, aceitando maiúscula ou minúscula com casos agrupados; qualquer outra letra cai no default, que avisa e encerra com return 1.
  6. A moldura tem 28 colunas: | Nome : ocupa 11, o campo %-16s ocupa 16 e a barra final fecha a linha. O - alinha o valor à esquerda e completa com espaços até a largura.
  7. %-16d e %-16.2f fazem o mesmo com números: 12 e 350.50 ficam colados ao rótulo, e os espaços que sobram mantêm a barra da direita na mesma coluna.

Por que funciona

Com a entrada Arwen, m, 12 e 350.5, cada uma numa linha, todas as conversões dão certo porque %s, %d e %lf pulam espaços em branco sozinhos, e o espaço explícito resolve o único caso em que isso não acontece, o %c. A validação pelo retorno do scanf e pelo default garante que a ficha só é impressa com dados completos e uma classe conhecida.

Na saída, a largura fixa com alinhamento à esquerda transforma valores de tamanhos diferentes em colunas iguais. Os rótulos com acento (Nível) são texto fixo do formato, então não interferem na conta; a largura só é aplicada ao valor, que aqui não tem acento.

Erros comuns

  • %c sem o espaço: scanf("%c", &classe); lê o \n que sobrou do nome, o switch cai no default e o programa imprime Classe ' seguido de uma quebra de linha. Escreva " %c".
  • Largura sem -: %16s alinha à direita e a ficha mostra | Arwen|, com o valor longe do rótulo. Para encostar à esquerda, use %-16s.
  • Nome maior que o limite: com %15s, um nome de 18 letras é cortado em 15 e as 3 restantes ficam na entrada, sendo lidas como classe e nível. Avise o limite no prompt ou descarte o resto da linha antes de seguir.
  • Rótulos de larguras diferentes: escrever | Classe: %-16s| com um espaço a menos que as outras linhas desloca a barra final. Conte os caracteres de cada rótulo.

Outra forma de resolver

Uma única chamada também resolve, porque o espaço no formato pula qualquer espaço em branco, inclusive o Enter:

if (scanf("%15s %c %d %lf", nome, &classe, &nivel, &ouro) != 4) {
    puts("Ficha incompleta.");
    return 1;
}

É mais compacta e preferível quando não há prompts entre os campos; leituras separadas são melhores quando cada campo é pedido com uma pergunta própria.

Saída esperada

+--------------------------+
| Nome   : Arwen           |
| Classe : Mago            |
| Nível  : 12              |
| Ouro   : 350.50          |
+--------------------------+

Últimas lições

Ver o arquivo completo →