Entrada e saída com printf e scanf
por Beckett HollisEntenda 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.
printf:%dparaint,%fparadouble,%cparachar,%spara texto e%%para imprimir%; especificador e tipo precisam combinar.- Largura e precisão:
%8.2falinha à direita em 8 colunas com 2 casas,%-10salinha à esquerda e%05dcompleta com zeros. A largura conta bytes, e letras acentuadas ocupam 2 em UTF-8. scanfrecebe endereços:&quantidadepara variáveis comuns e sónomepara vetores dechar.doublese lê com%lf.- O retorno do
scanfé o número de campos convertidos; compare com o esperado antes de usar as variáveis. %cnão pula o Enter que sobrou da leitura anterior: use" %c". E limite o%sao tamanho do vetor menos 1, como%19sparachar nome[20].
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Recibo da loja com especificadores, largura e precisão
/* 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: 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: 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.
- Ler os três valores com um único
scanf, usando%lfpara 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.
#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;
}
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
- 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). scanf("%d %d %lf", ...)lê os três valores da mesma linha. Cada especificador corresponde, na ordem, a um endereço:&codigo,&quantidadee&preco.- O
&entrega aoscanfo endereço da variável; sem ele, a função recebe o valor (ainda indefinido) e não tem onde gravar. %05dimprime o código em 5 colunas completando com zeros:42vira00042, como numa etiqueta de verdade.%.2fmostra o preço e o total com duas casas. O total3 * 19.90é calculado direto no argumento doprintf, 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
doublecom%f:scanf("%f", &preco);espera umfloat, grava só metade dos bytes e o preço sai com lixo. Noscanf,doubleé%lf. - Ordem trocada:
scanf("%d %d %lf", &quantidade, &codigo, &preco);compila, mas a etiqueta mostra o produto#00003com 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.
- Guardar o nome em
char nome[20]e lê-lo com largura máxima (%19s). - Guardar o retorno do
scanfe, se for diferente de 4, imprimirEntrada inválida: esperava nome e 3 notase encerrar comreturn 1. - Imprimir quantos campos foram lidos e as notas e a média com
%5.2f, para que10.00e7.50fiquem alinhados.
#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;
}
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
char nome[20]reserva espaço para um texto de até 19 caracteres mais o'\0'que marca o fim; as notas sãodoubleporque têm casas decimais.- Um único
scanflê o nome e as três notas.nomevai sem&porque o nome de um vetor já é o endereço do primeiro elemento; as notas, que são variáveis comuns, levam&. %19slimita a leitura a 19 caracteres. Sem o número, um nome comprido escreveria além dos 20 bytes do vetor.- O retorno do
scanfé guardado emlidos. Se não for 4, algum campo não foi convertido, e o programa avisa e encerra comreturn 1antes de usar valores indefinidos. - A média soma as três notas antes de dividir; como as notas são
double, a divisão por3não perde as casas decimais. %5.2freserva 5 colunas:7.50ganha um espaço à esquerda e10.00ocupa tudo, então os pontos decimais ficam alinhados. O rótuloMédia:recebe um espaço a mais no texto para acompanharNota 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 nogcc. Use sónome. %ssem 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, o10.00nã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.
- Ler o nome com
%15s, a classe com" %c"e o nível e o ouro em uma terceira chamada, somando os retornos e imprimindoFicha incompleta.se a soma não for 4. - Traduzir a letra com
switch; letra desconhecida imprimeClasse 'x' inválida.(com a letra lida) e encerra comreturn 1. - Imprimir os valores com
%-16s,%-16de%-16.2fpara que a barra da direita fique na mesma coluna em todas as linhas.
#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;
}
+--------------------------+
| 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
nome[16]guarda até 15 letras mais o'\0', e o%15sgarante esse limite na leitura;nome_classeé um ponteiro para texto constante que oswitchvai apontar para o nome da classe.- O nome é lido com
%15s, que para no Enter digitado depois deArwen. Esse\ncontinua na entrada, esperando a próxima leitura. scanf(" %c", &classe)tem um espaço antes do%c: ele descarta o\npendente (e quaisquer espaços) e só então lê om. Sem o espaço,classereceberia o próprio\n.%d %lflê o nível e o ouro. Os retornos das três chamadas são somados: só dá4quando todos os campos foram convertidos, e comoEOFé negativo, uma entrada que acaba no meio nunca chega a 4.- O
switchtraduz a letra, aceitando maiúscula ou minúscula com casos agrupados; qualquer outra letra cai nodefault, que avisa e encerra comreturn 1. - A moldura tem 28 colunas:
| Nome :ocupa 11, o campo%-16socupa 16 e a barra final fecha a linha. O-alinha o valor à esquerda e completa com espaços até a largura. %-16de%-16.2ffazem o mesmo com números:12e350.50ficam 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
%csem o espaço:scanf("%c", &classe);lê o\nque sobrou do nome, oswitchcai nodefaulte o programa imprimeClasse 'seguido de uma quebra de linha. Escreva" %c".- Largura sem
-:%16salinha à 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 |
+--------------------------+