C Formatos do printf

Na aula Saída com printf você usou o %d para mostrar um número inteiro. Ele é só um de uma família inteira de formatos: há um para números com vírgula, um para letras, um para textos, um para hexadecimal… e todos aceitam ajustes como "ocupe 5 espaços", "mostre 2 casas decimais" ou "complete com zeros". Nesta aula você vai conhecer cada formato, montar uma tabela alinhada (a lista de compras da Ana) e ver o que acontece quando o formato não combina com o valor, um dos erros mais comuns em C.

Você vai aprender
  • As partes de um formato: %, ajustes e a letra do tipo.
  • Os formatos de inteiros: %d, %i, %u, %ld, %lld, %x, %X, %o.
  • Os de números com vírgula: %f, %.2f, %e, %g.
  • Os de letras e textos (%c, %s), os especiais %zu e %p, e como mostrar o próprio %.
  • Largura e alinhamento: %5d, %-10s, %05d, %+d.
  • O que acontece com um formato errado: os avisos do -Wall e a saída "lixo".

Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Você pode repetir cada um no editor do guia, que compila C de verdade no navegador.


A ideia: um formulário com lacunas

Pense num formulário de papel: "Nome: ______ Idade: ___ anos". O texto já está impresso; você só preenche as lacunas. E cada lacuna espera um tipo de resposta: na de idade vai um número, não um nome.

O primeiro texto do printf é esse formulário. Cada % abre uma lacuna, e a letra depois dele diz o que vai ali. Os valores que vêm depois da vírgula preenchem as lacunas, na ordem:

#include <stdio.h>

int main(void) {
    printf("Nome: %s  Idade: %d anos  Altura: %.2f m\n", "Ana", 29, 1.65);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ficha.c -o ficha
$ ./ficha
Nome: Ana  Idade: 29 anos  Altura: 1.65 m
LacunaPreenchida porResultado
%s (texto)"Ana"Ana
%d (inteiro)2929
%.2f (número com vírgula, 2 casas)1.651.65

O nome técnico de cada lacuna é especificador de formato (format specifier), ou só "formato". A regra de ouro desta aula inteira: o formato tem de combinar com o tipo do valor. O printf não confere isso sozinho; quem confere é o compilador, com os avisos do -Wall (seção 7).


1. As partes de um formato

Um formato pode ter até cinco partes. Só a primeira e a última são obrigatórias:

%  [ajustes]  [largura]  [.precisão]  [tamanho]  tipo
%     -          10          .2          l        f
%
Abre a lacuna. Tudo até a letra do tipo faz parte do formato.
ajustes (flags)
Símbolos que mudam o jeito de mostrar: - (alinhar à esquerda), 0 (completar com zeros), + (sempre mostrar o sinal), espaço e #. Seção 6.
largura
Quantos caracteres a lacuna ocupa no mínimo: %5d ocupa 5.
.precisão
Um ponto e um número. Para números com vírgula, quantas casas decimais: %.2f. Para textos, quantas letras no máximo: %.3s.
tamanho
Letras que avisam um tipo maior ou menor: l para long, ll para long long, z para size_t.
tipo
A letra que diz o que é o valor: d (inteiro), f (com vírgula), s (texto)…

As próximas seções mostram cada tipo. Ao final, a tabela de referência junta tudo.


2. Números inteiros

Este programa mostra cada formato de inteiro ao lado do que ele produz. Repare num truque: para mostrar o próprio sinal % na tela, escreve-se %% (seção 5).

#include <stdio.h>

int main(void) {
    int idade = 29;
    unsigned int visitas = 3000000000u;
    long populacao = 2147483647L;
    long long estrelas = 100000000000LL;

    printf("%%d   -> %d\n", idade);
    printf("%%i   -> %i\n", idade);
    printf("%%u   -> %u\n", visitas);
    printf("%%ld  -> %ld\n", populacao);
    printf("%%lld -> %lld\n", estrelas);
    printf("%%x   -> %x\n", 255);
    printf("%%X   -> %X\n", 255);
    printf("%%o   -> %o\n", 8);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra inteiros.c -o inteiros
$ ./inteiros
%d   -> 29
%i   -> 29
%u   -> 3000000000
%ld  -> 2147483647
%lld -> 100000000000
%x   -> ff
%X   -> FF
%o   -> 10
%d
De decimal: um int na base 10, a que usamos no dia a dia. É o formato que você mais vai usar.
%i
De integer (inteiro). No printf, é igualzinho ao %d. (No scanf eles são diferentes; veja Entrada com scanf.)
%u
De unsigned, "sem sinal": um unsigned int, que não tem negativos e por isso vai mais longe (até 4294967295). Os tipos estão em Inteiros.
%ld
O l (de long) avisa que o valor é um long.
%lld
Dois l: um long long, o maior inteiro padrão (até 9223372036854775807).
%x e %X
Mostra o número em hexadecimal (base 16, com os "algarismos" 0–9 e a–f). 255 vira ff; com X maiúsculo, FF. Aparece muito em cores (#FF8800) e em endereços de memória.
%o
Mostra em octal (base 8). 8 vira 10. Hoje quase só aparece nas permissões de arquivos do Linux (chmod 755).
Os sufixos u, L e LL

As letras no fim de 3000000000u, 2147483647L e 100000000000LL dizem ao compilador o tipo do número escrito: u = unsigned, L = long, LL = long long. A aula Inteiros explica quando são necessárias.

Negativo com %u: o número "dá a volta"

O formato não converte nada: ele só diz ao printf como ler os bits do valor. Um -1 lido como "sem sinal" vira o maior unsigned int:

$ cat volta.c
#include <stdio.h>

int main(void) {
    printf("%d\n", -1);
    printf("%u\n", -1);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra volta.c -o volta
$ ./volta
-1
4294967295

Nem o -Wall avisou. A explicação (como os negativos são guardados) está em Inteiros e em Conversão de Tipos. A lição aqui: use %u só para valores unsigned.

Uma letra é um número: %c × %d

Para o C, um char é um número pequeno (o código da letra na tabela ASCII, veja Caracteres). O formato decide se você vê a letra ou o número:

#include <stdio.h>

int main(void) {
    char letra = 'A';
    printf("%c %d\n", letra, letra);
    printf("%c\n", 66);
    printf("%x %X %o %d\n", 2026, 2026, 2026, 2026);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra letra.c -o letra
$ ./letra
A 65                     « a mesma variável: como letra e como número
B                        « 66 é o código do B
7ea 7EA 3752 2026        « 2026 em hexadecimal, hexadecimal maiúsculo, octal e decimal

3. Números com vírgula

Para double (e float), há três formatos, cada um com um jeito de escrever o número:

#include <stdio.h>

int main(void) {
    double preco = 4.5;
    printf("%%f   -> %f\n", preco);
    printf("%%.2f -> %.2f\n", preco);
    printf("%%e   -> %e\n", preco);
    printf("%%g   -> %g\n", preco);
    printf("%g %g %g\n", 0.0001, 1234567.0, 100.0);
    printf("%e\n", 1234567.0);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra reais.c -o reais
$ ./reais
%f   -> 4.500000
%.2f -> 4.50
%e   -> 4.500000e+00
%g   -> 4.5
0.0001 1.23457e+06 100
1.234567e+06
%f
De floating point ("ponto flutuante", o nome técnico dos números com vírgula). Sem precisão, mostra sempre 6 casas: 4.500000.
%.2f
Com precisão: exatamente 2 casas. É o formato de dinheiro: 4.50. %.1f dá uma casa, %.0f nenhuma.
%e
Notação científica (exponent): 1.234567e+06 quer dizer 1.234567 × 106, ou seja, 1234567. Bom para números enormes ou minúsculos.
%g
De general ("geral"): escolhe sozinho entre %f e %e, o que ficar mais curto, e tira os zeros inúteis do fim. Mostra até 6 algarismos significativos: por isso 1234567 virou 1.23457e+06 (arredondado).
O ponto, não a vírgula

O printf sempre usa ponto como separador decimal (4.50), como nos Estados Unidos, mesmo num computador em português. Mostrar 4,50 exige mudar a "localidade" do programa, assunto fora desta aula.

Arredondar com %.Nf

A precisão arredonda (não corta) o que mostra, e a variável não muda. Mas o arredondamento tem uma surpresa:

$ cat arredonda.c
#include <stdio.h>

int main(void) {
    printf("%.0f %.0f %.0f\n", 2.5, 3.5, 2.6);
    printf("%.2f\n", 1.005);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra arredonda.c -o arredonda
$ ./arredonda
2 4 3
1.00
  • 2.5 virou 2, mas 3.5 virou 4. Quando o número está exatamente no meio, a glibc arredonda para o vizinho par (é a regra padrão dos computadores, chamada "arredondamento do banqueiro"). 2.6 não está no meio e vira 3, como esperado.
  • 1.005 virou 1.00, e não 1.01. O computador não consegue guardar 1.005 exato; guarda algo como 1.00499999…, que arredonda para baixo. A aula float e double explica por quê.
%f ou %lf?

No printf, tanto faz: %f e %lf mostram um double do mesmo jeito (um float é convertido para double antes de chegar ao printf). No scanf é diferente: lá, double exige %lf. A aula Entrada com scanf mostra o estrago de errar isso.


4. Letras e textos: %c e %s

#include <stdio.h>

int main(void) {
    char inicial = 'A';
    char nome[] = "Ana";
    printf("Inicial: %c\n", inicial);
    printf("Nome: %s\n", nome);
    printf("Fruta: %.3s\n", "Banana");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra textos.c -o textos
$ ./textos
Inicial: A
Nome: Ana
Fruta: Ban
%c
De character: um caractere, guardado num char e escrito entre aspas simples ('A').
char nome[] = "Ana";
Um texto guardado numa variável. Em C, texto é uma sequência de char (aula Strings).
%s
De string: um texto inteiro, até o fim. Pode receber uma variável de texto ou um texto entre aspas duplas.
%.3s
Com precisão, o %s mostra no máximo 3 letras: Ban.

5. Formatos especiais: %zu, %p e %%

#include <stdio.h>

int main(void) {
    int idade = 29;
    printf("Bytes de um int: %zu\n", sizeof idade);
    printf("Endereço de idade: %p\n", (void *)&idade);
    printf("Desconto: 10%%\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra especiais.c -o especiais
$ ./especiais
Bytes de um int: 4
Endereço de idade: 0x7fff58e00e44
Desconto: 10%
$ ./especiais
Bytes de um int: 4
Endereço de idade: 0x7ffd974cde64     « outro endereço a cada execução
Desconto: 10%
%zu
Para o resultado de sizeof (o tamanho de algo, em bytes), que tem o tipo size_t. O z é o "tamanho" de size_t, e o u é de unsigned: tamanho nunca é negativo. Aula Tamanho dos Tipos.
%p
De pointer ("ponteiro"): um endereço de memória, em hexadecimal. O &idade é "o endereço de idade", e o (void *) é a forma correta de entregá-lo ao %p. Tudo isso é explicado em Memória e Endereços.
%%
Como o % abre uma lacuna, para mostrar um % de verdade escreva dois.
No editor do guia

O editor roda o programa em WebAssembly de 32 bits, então o %p mostra endereços curtos (como 0x7ffff8), diferentes dos do gcc no Linux de 64 bits. O %zu funciona igual. Endereços nunca são iguais entre computadores e execuções: não escreva programas que dependam deles.


6. Largura e alinhamento

Até aqui, cada lacuna ocupou exatamente o espaço do valor. Com uma largura, ela passa a ocupar um mínimo de caracteres, completando com espaços. Os colchetes no programa abaixo só servem para você enxergar onde a lacuna começa e termina:

#include <stdio.h>

int main(void) {
    printf("[%5d]\n", 42);
    printf("[%-5d]\n", 42);
    printf("[%05d]\n", 42);
    printf("[%+d] [%+d]\n", 42, -42);
    printf("[%10s]\n", "Ana");
    printf("[%-10s]\n", "Ana");
    printf("[%8.2f]\n", 3.14159);
    printf("[%2d]\n", 12345);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra largura.c -o largura
$ ./largura
[   42]
[42   ]
[00042]
[+42] [-42]
[       Ana]
[Ana       ]
[    3.14]
[12345]
FormatoSaídaO que aconteceu
%5d[ 42]5 espaços no mínimo; o número fica à direita (3 espaços + 42).
%-5d[42 ]O - alinha à esquerda; os espaços vão para o fim.
%05d[00042]O 0 antes da largura completa com zeros em vez de espaços.
%+d[+42] [-42]O + mostra o sinal sempre, também nos positivos.
%10s / %-10s[ Ana] / [Ana ]Funciona igual com texto. Textos costumam ir à esquerda, números à direita.
%8.2f[ 3.14]Largura e precisão juntas: 8 caracteres no total (contando o ponto), 2 casas.
%2d com 12345[12345]A largura é um mínimo: um valor maior nunca é cortado.

Uma tabela alinhada

A largura serve para fazer colunas. A lista de compras da Ana, com o nome à esquerda e os números à direita:

#include <stdio.h>

int main(void) {
    printf("%-10s %5s %8s\n", "Item", "Qtd", "Preço");
    printf("%-10s %5d %8.2f\n", "Arroz", 2, 6.5);
    printf("%-10s %5d %8.2f\n", "Feijão", 1, 8.9);
    printf("%-10s %5d %8.2f\n", "Café", 3, 15.75);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra compras.c -o compras
$ ./compras
Item         Qtd   Preço
Arroz          2     6.50
Feijão        1     8.90
Café          3    15.75

Quase deu certo, mas as linhas de Feijão, Café e o título Preço saíram tortas, um espaço a menos. Não é erro seu: o printf conta a largura em bytes, e cada letra acentuada (ã, é, ç) ocupa 2 bytes no UTF-8, a codificação usada no Linux. "Feijão" tem 6 letras, mas 7 bytes; o printf acha que já ocupou 7 das 10 posições e põe só 3 espaços. A aula Acentos e UTF-8 explica a fundo; por ora, em tabelas alinhadas, prefira nomes sem acento ou compense a largura à mão.

Missão: a lista abaixo sai toda grudada. Use larguras para alinhar: o nome à esquerda em 10 posições, a quantidade à direita em 5 e o preço à direita em 8, com 2 casas (com um espaço entre as colunas, como no exemplo acima). A saída deve ficar assim:

Arroz          2     6.50
Leite         12     4.25
Ovos          30    18.00
#include <stdio.h>

int main(void) {
    printf("%s %d %f\n", "Arroz", 2, 6.5);
    printf("%s %d %f\n", "Leite", 12, 4.25);
    printf("%s %d %f\n", "Ovos", 30, 18.0);
    return 0;
}
#include <stdio.h>

int main(void) {
    printf("%-10s %5d %8.2f\n", "Arroz", 2, 6.5);
    printf("%-10s %5d %8.2f\n", "Leite", 12, 4.25);
    printf("%-10s %5d %8.2f\n", "Ovos", 30, 18.0);
    return 0;
}

Outros ajustes

$ cat ajustes.c
#include <stdio.h>

int main(void) {
    printf("[%*d]\n", 6, 42);
    printf("[%#x] [%#o]\n", 255, 8);
    printf("[% d] [% d]\n", 42, -42);
    printf("Pedido nº %03d\n", 7);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ajustes.c -o ajustes
$ ./ajustes
[    42]
[0xff] [010]
[ 42] [-42]
Pedido nº 007
%*d
O * no lugar da largura diz: "a largura vem de um valor". O primeiro valor (6) é a largura; o segundo (42) é o número. Útil quando a largura é calculada pelo programa.
%#x e %#o
O # acrescenta o prefixo que identifica a base: 0x para hexadecimal, 0 para octal. São os mesmos prefixos que o C usa para escrever números nessas bases no código.
% d
Um espaço como ajuste: põe um espaço no lugar do + dos positivos, para alinhar com os negativos.
%03d
Zeros à esquerda até 3 dígitos: o jeito de numerar pedidos, notas fiscais e arquivos (foto007.jpg).

7. O formato errado

O printf confia no formato. Se você diz %f e entrega um int, ele vai procurar um double onde não há, e mostra o que encontrar. Por sorte, o gcc confere os formatos quando o texto está escrito direto no printf, e o -Wall liga esse aviso:

#include <stdio.h>

int main(void) {
    int idade = 29;
    double preco = 4.5;
    printf("Idade: %f\n", idade);
    printf("Preço: %d\n", preco);
    printf("Dois números: %d %d\n", idade);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra errado.c -o errado
errado.c: In function ‘main’:
errado.c:6:21: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
    6 |     printf("Idade: %f\n", idade);
      |                    ~^     ~~~~~
      |                     |     |
      |                     |     int
      |                     double
      |                    %d
errado.c:7:21: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
    7 |     printf("Preço: %d\n", preco);
      |                    ~^     ~~~~~
      |                     |     |
      |                     int   double
      |                    %f
errado.c:8:31: warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
    8 |     printf("Dois números: %d %d\n", idade);
      |                              ~^
      |                               |
      |                               int
$ ./errado
Idade: 0.000000
Preço: 2140133728
Dois números: 29 -2016665569
$ ./errado
Idade: 0.000000
Preço: -1266114704                   « muda a cada execução
Dois números: 29 835022879
format ‘%f’ expects argument of type ‘double’
"O formato %f espera um valor do tipo double…"
but argument 2 has type ‘int’
"…mas o argumento 2 é um int." Os argumentos são contados a partir do texto do formato, que é o argumento 1.
os desenhos com |
O gcc liga cada lacuna ao valor que a preenche e escreve o tipo de cada lado: double embaixo do %f, int embaixo de idade.
%d (na última linha do aviso)
A sugestão de conserto: "troque por %d".
expects a matching ‘int’ argument
"Espera um argumento int correspondente": há duas lacunas e só um valor.
[-Wformat=]
A regra de aviso que conferiu os formatos. Ela vem no -Wall.

São só avisos, então o programa foi gerado, e a saída mostra o estrago: 0.000000 no lugar de 29, um número sem sentido no lugar de 4.5 e um "segundo número" que ninguém passou. Ler um valor com o formato errado é comportamento indefinido: a linguagem não promete nada (aula Comportamento Indefinido). Os números mudam a cada execução e de um computador para outro.

No editor do guia

O Clang avisa a mesma coisa com outras palavras: format specifies type 'double' but the argument has type 'int' [-Wformat] ("o formato indica o tipo double, mas o argumento é int") e more '%' conversions than data arguments ("mais lacunas % do que valores"). A saída errada também é diferente: no editor saiu Preço: 0 e Dois números: 29 0. Lixo é lixo, em qualquer computador.

Quando o lixo derruba o programa

Com %s, o estrago é maior: o printf trata o valor como o endereço de um texto e vai ler a memória nesse endereço.

$ cat cliente.c
#include <stdio.h>

int main(void) {
    int codigo = 65;
    printf("Cliente: %s\n", codigo);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra cliente.c -o cliente
cliente.c: In function ‘main’:
cliente.c:5:23: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]
    5 |     printf("Cliente: %s\n", codigo);
      |                      ~^     ~~~~~~
      |                       |     |
      |                       |     int
      |                       char *
      |                      %d
$ ./cliente
Falha de segmentação     (imagem do núcleo gravada) ./cliente
$ echo $?
139

O endereço 65 não pertence ao programa, e o sistema o derrubou: Falha de segmentação (segmentation fault) é o aviso do Bash de que o programa tentou mexer numa memória proibida; o código de saída 139 confirma. O char * da mensagem é o tipo "endereço de texto" (aula Ponteiros e Strings). Regra: nunca ignore um aviso -Wformat; ele aponta um erro de verdade.

Missão: rode e leia os avisos: os três formatos estão trocados e a saída sai errada. Conserte só os formatos (as letras depois do %), sem mexer nas variáveis, para o programa mostrar Idade: 29 anos, Preço: R$ 4.50 e Inicial: A, sem nenhum aviso.

#include <stdio.h>

int main(void) {
    int idade = 29;
    double preco = 4.5;
    char inicial = 'A';
    printf("Idade: %f anos\n", idade);
    printf("Preço: R$ %d\n", preco);
    printf("Inicial: %s\n", inicial);
    return 0;
}
#include <stdio.h>

int main(void) {
    int idade = 29;
    double preco = 4.5;
    char inicial = 'A';
    printf("Idade: %d anos\n", idade);
    printf("Preço: R$ %.2f\n", preco);
    printf("Inicial: %c\n", inicial);
    return 0;
}

Formas diferentes: qual usar

Muitas vezes há mais de um formato que "funciona". Esta tabela mostra a escolha do guia:

Para mostrar…OpçõesUsePor quê
Um int%d, %i%dOs dois são iguais no printf, mas %d é o que todo mundo usa, e no scanf o %i tem uma pegadinha.
Dinheiro%f, %g, %.2f%.2fSempre 2 casas: 4.50, e não 4.500000 nem 4.5.
Um resultado de conta, sem saber o tamanho%f, %g%gTira zeros inúteis e muda sozinho para notação científica quando o número é enorme ou minúsculo.
Números de ciência (distâncias, massas)%f, %e%e1.989000e+30 é legível; %f escreveria 31 algarismos.
Um sizeof%d, %lu, %zu%zuÉ o formato feito para size_t; os outros dão aviso em algum computador.
Um texto fixo, sem lacunasprintf("Oi\n"), puts("Oi")Qualquer umputs ("put string") já põe o \n no fim. O printf é mais flexível. Se o texto tiver um %, no printf ele vira %%.

Quando dá errado

warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’

O que significa: o formato não combina com o tipo do valor. O programa vai mostrar lixo naquela lacuna.

Como consertar: troque o formato pelo do tipo certo (a sugestão aparece na última linha do aviso) ou, se você quer mesmo o valor em outro tipo, converta de propósito: (int)preco (aula Conversão de Tipos).

warning: format ‘%d’ expects a matching ‘int’ argument

O que significa: há mais lacunas % do que valores depois da vírgula. A lacuna sem valor mostra lixo. (Se for o contrário, mais valores que lacunas, o gcc avisa too many arguments for format, e os valores a mais são ignorados.)

Como consertar: conte as lacunas e os valores: tem de ser um para um, na mesma ordem.

Apareceu um número estranho ou 0.000000, sem aviso nenhum

O que significa: provavelmente você compilou sem -Wall, e o aviso de formato não apareceu.

Como consertar: compile sempre com gcc -std=c23 -Wall -Wextra e leia os avisos -Wformat.

Falha de segmentação numa linha com %s

O que significa: o %s recebeu algo que não é um texto (um número, um char sozinho), e o printf tentou ler a memória num endereço inválido.

Como consertar: para um char, use %c; para números, %d ou %f. O aviso expects argument of type ‘char *’ já tinha apontado a linha.

A tabela sai torta nas linhas com acento

O que significa: a largura (%-10s) conta bytes, e letras acentuadas ocupam 2 bytes em UTF-8.

Como consertar: use nomes sem acento nas colunas alinhadas, ou aumente a largura em 1 para cada acento. Detalhes em Acentos e UTF-8.


Referência

Os tipos (a letra do fim)

FormatoTipo do valorExemploSaída
%d ou %iint (e char, short)printf("%d", 29)29
%uunsigned intprintf("%u", 3000000000u)3000000000
%ld / %lulong / unsigned longprintf("%ld", 100L)100
%lld / %llulong long / unsigned long longprintf("%lld", 100000000000LL)100000000000
%hd / %hhdshort / signed char (raros)printf("%hd", (short)7)7
%x / %Xinteiro sem sinal, em hexadecimalprintf("%x %X", 255, 255)ff FF
%ointeiro sem sinal, em octalprintf("%o", 8)10
%f (ou %lf)double (e float)printf("%f", 4.5)4.500000
%e / %Edouble, notação científicaprintf("%e", 1234567.0)1.234567e+06
%g / %Gdouble, o mais curto entre %f e %eprintf("%g", 4.5)4.5
%Lflong doubleprintf("%Lf", 4.5L)4.500000
%cum caractere (char)printf("%c", 'A')A
%stexto (char[] ou char *)printf("%s", "Ana")Ana
%zusize_t (resultado de sizeof)printf("%zu", sizeof(int))4
%pendereço (void *)printf("%p", (void *)&x)0x7ffc… (muda)
%%nenhum: mostra um %printf("10%%")10%

Os ajustes, a largura e a precisão

EscritaEfeitoExemploSaída
%5dlargura mínima 5, à direitaprintf("[%5d]", 42)[ 42]
%-5dlargura 5, à esquerdaprintf("[%-5d]", 42)[42 ]
%05dcompleta com zerosprintf("[%05d]", 42)[00042]
%+dsinal sempreprintf("%+d", 42)+42
% despaço no lugar do +printf("[% d]", 42)[ 42]
%#x / %#oprefixo da baseprintf("%#x", 255)0xff
%.2f2 casas decimais (arredonda)printf("%.2f", 3.14159)3.14
%8.2flargura 8 e 2 casasprintf("[%8.2f]", 3.14159)[ 3.14]
%.3sno máximo 3 letrasprintf("%.3s", "Banana")Ban
%-10stexto em 10 posições, à esquerdaprintf("[%-10s]", "Ana")[Ana ]
%*dlargura vinda de um valorprintf("[%*d]", 6, 42)[ 42]

A página printf e scanf, na seção de referência, junta esta tabela com os formatos do scanf.


Exercícios

1. O formato do dinheiro

Complete para mostrar R$ 12.90, com exatamente 2 casas:

printf("R$ \n", 12.9);

2. Zeros à esquerda

Complete para que o pedido 7 apareça como Pedido 0007:

printf("Pedido \n", 7);

3. O sinal de por cento

Complete para mostrar Desconto: 15%:

printf("Desconto: %d\n", 15);

4. O que printf("[%-6s]", "Ana"); mostra?

5. Qual o formato certo para o resultado de sizeof(double)?

6. double nota = 7.5; e printf("%d\n", nota);. Compilando com -Wall, o que acontece?

Missão final: o cupom da lojinha

Missão: complete os printf do cupom (troque cada ??? pelo formato certo) para a saída ficar exatamente como abaixo, sem avisos. Dicas: o pedido tem 4 dígitos com zeros; o nome do produto ocupa 12 posições à esquerda; a quantidade, 4 à direita; o preço, 8 à direita com 2 casas.

Pedido nº 0042
Cliente: Ana
Caderno        3 x R$    12.90
Desconto: 10%
Total: R$ 34.83
#include <stdio.h>

int main(void) {
    int pedido = 42;
    char cliente[] = "Ana";
    char produto[] = "Caderno";
    int quantidade = 3;
    double preco = 12.90;
    int desconto = 10;
    double total = quantidade * preco * (100 - desconto) / 100;

    printf("Pedido nº ???\n", pedido);
    printf("Cliente: ???\n", cliente);
    printf("??????? x R$ ???\n", produto, quantidade, preco);
    printf("Desconto: ???\n", desconto);
    printf("Total: R$ ???\n", total);
    return 0;
}
#include <stdio.h>

int main(void) {
    int pedido = 42;
    char cliente[] = "Ana";
    char produto[] = "Caderno";
    int quantidade = 3;
    double preco = 12.90;
    int desconto = 10;
    double total = quantidade * preco * (100 - desconto) / 100;

    printf("Pedido nº %04d\n", pedido);
    printf("Cliente: %s\n", cliente);
    printf("%-12s%4d x R$ %8.2f\n", produto, quantidade, preco);
    printf("Desconto: %d%%\n", desconto);
    printf("Total: R$ %.2f\n", total);
    return 0;
}

A conta do total usa operadores da próxima parte do guia (Operadores Aritméticos): 3 × 12.90 = 38.70, menos 10% = 34.83.


Resumo

  • Cada % abre uma lacuna; a letra do fim diz o tipo; os valores preenchem as lacunas na ordem. O formato tem de combinar com o tipo.
  • Inteiros: %d (ou %i), %u (sem sinal), %ld, %lld; em outras bases, %x/%X (hexadecimal) e %o (octal).
  • Com vírgula: %f (6 casas), %.2f (2 casas, arredonda), %e (científica), %g (o mais curto). No printf, %lf = %f.
  • %c = um caractere, %s = texto, %zu = sizeof, %p = endereço, %% = o próprio %.
  • Largura: %5d (direita), %-10s (esquerda), %05d (zeros), %+d (sinal). A largura é mínima, conta bytes (acentos contam 2) e nunca corta.
  • Formato errado = aviso -Wformat do -Wall e saída lixo (ou até Falha de segmentação com %s). Nunca ignore esse aviso.