C Conversão de Tipos (cast)
Em C, todo valor tem um tipo, e muitas vezes um valor de um tipo precisa virar outro: um int entra numa conta com um double, um número com vírgula é guardado numa variável inteira, uma letra vira o seu código. Às vezes o C faz essa conversão sozinho, sem avisar; às vezes você precisa pedir, com um cast. Nesta aula você vai ver as duas, entender por que a média de 8 e 9 sai 8.00 em vez de 8.50, e conhecer a conversão mais traiçoeira do C: a que faz -1 ficar maior que 0.
- O que o C converte sozinho (conversão implícita) e o que se perde no caminho.
- Por que um
doubleguardado numinté cortado, não arredondado. - A promoção: por que
char + chardá umint. - Como pedir avisos das conversões perigosas com
-Wconversion. - O cast,
(double)soma / alunos, e por que a posição dele muda o resultado. - Como arredondar de verdade.
- Letras e números:
'A' + 1e'7' - '0'. - A surpresa do
signedcomunsigned:-1 > 0ué verdade.
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: passar água de um copo para outro
Imagine três recipientes: um copinho de café, um copo comum e uma jarra. Passar a água do copinho para a jarra é tranquilo: cabe tudo, sobra espaço. Passar da jarra para o copinho é outra história: a água transborda e você perde uma parte.
Os tipos do C são esses recipientes. Um int cabe inteiro num double; um double com vírgula não cabe num int, e a parte depois da vírgula "transborda". O problema é que o C, na maioria das vezes, derrama a água sem avisar.
| Conversão | Comparação | O que acontece |
|---|---|---|
int → double | copinho → jarra | Seguro: 7 vira 7.0. |
double → int | jarra → copinho | Perde a parte fracionária: 9.99 vira 9. |
int → char | copo → copinho | Se o número for grande, sobra lixo: 300 vira 44. |
int → unsigned | copo → copo sem fundo negativo | Negativos viram números enormes: -1 vira 4294967295. |
1. Conversão implícita: o C converte sozinho
"Implícita" quer dizer "sem ninguém pedir". Ela acontece em dois lugares: quando você guarda um valor numa variável de outro tipo e quando você mistura tipos numa conta.
#include <stdio.h>
int main(void) {
int caixas = 3;
double peso = 2.5;
double total = caixas * peso;
int inteiro = 9.99;
int negativo = -9.99;
double d = 7;
printf("total = %.2f\n", total);
printf("inteiro = %d\n", inteiro);
printf("negativo = %d\n", negativo);
printf("d = %f\n", d);
return 0;
}
$ gcc -std=c23 -Wall -Wextra implicita.c -o implicita $ ./implicita total = 7.50 inteiro = 9 negativo = -9 d = 7.000000
- caixas * peso
- Uma conta com um
int(3) e umdouble(2.5). Antes de multiplicar, o C converte o3para3.0, e o resultado é umdouble:7.5. Nada se perde. - int inteiro = 9.99;
- Guardar um
doublenumint: a parte depois do ponto é jogada fora. Fica9, e não10. Isso se chama truncamento ("corte"). - int negativo = -9.99;
- Com negativos, o corte também vai "em direção ao zero":
-9.99vira-9(e não-10). - double d = 7;
- Guardar um
intnumdouble:7vira7.0, sem perda.
Repare: o gcc compilou sem nenhum aviso, mesmo com -Wall -Wextra, e o 9.99 virou 9 calado. O Clang do editor é mais atento quando o número está escrito direto no código e avisa mesmo sem opções extras: implicit conversion from 'double' to 'int' changes value from 9.99 to 9 [-Wliteral-conversion] ("conversão implícita de double para int muda o valor de 9.99 para 9").
A regra das contas: o tipo "maior" ganha
Quando uma conta mistura dois tipos, o C converte o "menor" para o "maior" antes de calcular. A ordem, do menor para o maior, é mais ou menos esta:
int → unsigned int → long → long long → float → double → long double
intcomdouble→ a conta é feita emdouble.intcomlong long→ a conta é feita emlong long.intcomint→ a conta é feita emint, e o resultado éint, mesmo que você o guarde numdoubledepois (seção 4).intcomunsigned int→ a conta é feita emunsigned, e aí moram surpresas (seção 7).
A promoção: tipos pequenos viram int
Tem mais uma regra: char, short e bool nunca entram numa conta do jeito que são. Antes de qualquer conta, eles são promovidos a int:
#include <stdio.h>
int main(void) {
char a = 100;
char b = 100;
int soma = a + b;
char c = a + b;
printf("%d %d %zu\n", soma, c, sizeof(a + b));
return 0;
}
$ gcc -std=c23 -Wall -Wextra promo.c -o promo $ ./promo 200 -56 4
somadeu200, apesar de umcharsó ir até 127: a contaa + bfoi feita emint, onde 200 cabe.sizeof(a + b)deu4: o resultado da conta é umint, não umchar(aula Tamanho dos Tipos).cdeu-56: o 200 foi guardado de volta numchar, que não o comporta. Sobraram só os 8 bits de baixo, que numcharcom sinal valem −56 (200 − 256). A água transbordou.
2. Pedir avisos: -Wconversion
Como o gcc fica calado nas conversões que perdem informação, existe uma opção para ligar esses avisos: -Wconversion. Ela não faz parte do -Wall nem do -Wextra, porque costuma avisar muito em código antigo (a aula Avisos fala das opções de aviso).
#include <stdio.h>
int main(void) {
double preco = 9.99;
int reais = preco;
int nota = 300;
char c = nota;
printf("%d %d\n", reais, c);
return 0;
}
$ gcc -std=c23 -Wall -Wextra perdas.c -o perdas « nenhum aviso
$ ./perdas
9 44
$ gcc -std=c23 -Wall -Wextra -Wconversion perdas.c -o perdas
perdas.c: In function ‘main’:
perdas.c:5:17: warning: conversion from ‘double’ to ‘int’ may change value [-Wfloat-conversion]
5 | int reais = preco;
| ^~~~~
perdas.c:7:14: warning: conversion from ‘int’ to ‘char’ may change value [-Wconversion]
7 | char c = nota;
| ^~~~
- conversion from ‘double’ to ‘int’
- "Conversão de
doubleparaint": o compilador aponta exatamente onde o tipo muda. - may change value
- "Pode mudar o valor." Ele diz "pode" porque
precoé uma variável: o compilador não sabe se ela terá parte fracionária. Com um número escrito direto (int x = 9.99;), ele diz changes value from ‘9.9900000000000002e+0’ to ‘9’, "muda o valor de 9.99 para 9". - [-Wfloat-conversion]
- A parte do
-Wconversionque vigia números com vírgula virando inteiros. - from ‘int’ to ‘char’
- O 300 não cabe num
char: de fato, virou44(300 − 256).
No Clang do editor, as mensagens são implicit conversion turns floating-point number into integer: 'double' to 'int' [-Wfloat-conversion] ("a conversão implícita transforma número com vírgula em inteiro") e implicit conversion loses integer precision: 'int' to 'char' [-Wimplicit-int-conversion] ("a conversão implícita perde precisão de inteiro").
-Wconversion de vez em quando
Para os exercícios do guia, o comando padrão basta. Mas em programas que fazem contas com dinheiro ou medidas, compile de vez em quando com -Wconversion e leia os avisos: cada um é um lugar onde algo pode estar se perdendo. Os que forem de propósito, você marca com um cast (a próxima seção), e o aviso some.
3. Conversão explícita: o cast
"Explícita" quer dizer "pedida por escrito". Para converter um valor de propósito, escreva o tipo desejado entre parênteses na frente dele. Isso se chama cast (do inglês, "moldar", como se põe metal derretido numa fôrma):
#include <stdio.h>
int main(void) {
double preco = 12.50;
int reais = (int)preco;
int centavos = (int)((preco - reais) * 100);
printf("%d reais e %d centavos\n", reais, centavos);
printf("%f\n", (double)7);
return 0;
}
- (int)preco
- "Converta
precoparaint." O resultado é12(o corte de sempre). O cast não muda a variávelpreco, que continua 12.50: ele produz um valor novo, convertido. - (int)((preco - reais) * 100)
- Primeiro a conta entre parênteses: (12.50 − 12) × 100 = 50.0, um
double. Depois o cast o transforma em50. Os parênteses em volta da conta são necessários: sem eles, o(int)valeria só para opreco(seção 4 mostra por quê). - (double)7
- Converte o inteiro 7 para
7.0.
$ gcc -std=c23 -Wall -Wextra -Wconversion cast.c -o cast $ ./cast 12 reais e 50 centavos 7.000000
Com o cast, o resultado é o mesmo da conversão implícita, mas agora nenhum aviso, nem com -Wconversion: o cast diz ao compilador (e a quem lê) "eu sei que vou perder a parte fracionária, é de propósito".
Um cast só muda o tipo. Ele não arredonda ((int)9.99 é 9) e não faz caber o que não cabe ((char)300 continua virando 44). E não transforma texto em número: (int)"42" não dá 42 (para isso existem funções próprias, aula Texto ↔ Número).
Missão: esta caixa compila com -Wconversion ligado. Rode e veja o aviso: a conversão de double para int está escondida. Ela é de propósito (queremos só a parte inteira), então deixe isso por escrito com um cast. O programa deve continuar mostrando as mesmas duas linhas, agora sem nenhum aviso.
#include <stdio.h>
int main(void) {
double pizza = 3.5;
int pedacos = pizza;
printf("Cada fatia: %d pedaços\n", pedacos);
printf("Sobra de pizza: %.2f\n", pizza - pedacos);
return 0;
}
#include <stdio.h>
int main(void) {
double pizza = 3.5;
int pedacos = (int)pizza;
printf("Cada fatia: %d pedaços\n", pedacos);
printf("Sobra de pizza: %.2f\n", pizza - pedacos);
return 0;
}
4. Divisão inteira × divisão real
Este é o lugar onde a conversão de tipos mais pega quem está começando. A Ana tirou 8 e 9; a soma é 17 e a média deveria ser 8.5:
#include <stdio.h>
int main(void) {
int soma = 17;
int alunos = 2;
double media1 = soma / alunos;
double media2 = (double)soma / alunos;
double media3 = (double)(soma / alunos);
double media4 = soma / (double)alunos;
double media5 = soma * 1.0 / alunos;
printf("%.2f %.2f %.2f %.2f %.2f\n", media1, media2, media3, media4, media5);
return 0;
}
$ gcc -std=c23 -Wall -Wextra media.c -o media $ ./media 8.00 8.50 8.00 8.50 8.50
| Linha | Passo a passo | Resultado |
|---|---|---|
soma / alunos | int / int → divisão inteira: 17 / 2 = 8 (o resto some). Só depois o 8 vira 8.0 para entrar no double. | 8.00 ✗ |
(double)soma / alunos | O cast vale só para soma: 17.0. Agora é double / int → o alunos vira 2.0 e a divisão é real: 8.5. | 8.50 ✓ |
(double)(soma / alunos) | Os parênteses fazem a divisão inteira primeiro (8) e só depois convertem: 8.0. Cast tarde demais. | 8.00 ✗ |
soma / (double)alunos | Basta um dos lados ser double. | 8.50 ✓ |
soma * 1.0 / alunos | Um truque antigo: multiplicar por 1.0 (um double) transforma a conta em real. | 8.50 ✓ |
Duas lições. Primeira: o tipo da variável que recebe o resultado (double media1) não muda como a conta é feita; o C olha só os tipos dentro da conta. Segunda: o cast vale para o que está logo depois dele. (double)soma / alunos é lido como ((double)soma) / alunos, porque o cast tem prioridade sobre a divisão (aula Precedência). Mais sobre a divisão inteira em Operadores Aritméticos.
Missão: a média das notas 8 e 9 está saindo 8.00. Use um cast (double) no lugar certo para o programa mostrar Média: 8.50, sem mudar o tipo das variáveis soma e quantidade.
#include <stdio.h>
int main(void) {
int soma = 8 + 9;
int quantidade = 2;
double media = soma / quantidade;
printf("Média: %.2f\n", media);
return 0;
}
#include <stdio.h>
int main(void) {
int soma = 8 + 9;
int quantidade = 2;
double media = (double)soma / quantidade;
printf("Média: %.2f\n", media);
return 0;
}
5. E se eu quiser arredondar?
Converter para int sempre corta. Para arredondar, há dois caminhos:
#include <stdio.h>
#include <math.h>
int main(void) {
double x = 2.7;
double y = -2.7;
printf("%d %d\n", (int)x, (int)y);
printf("%d %d\n", (int)(x + 0.5), (int)(y + 0.5));
printf("%.0f %.0f\n", round(x), round(y));
printf("%ld %ld\n", lround(x), lround(y));
return 0;
}
As funções round e lround moram na biblioteca de matemática (math.h, aula Matemática). No gcc, essa biblioteca precisa ser pedida com -lm no fim do comando. Sem ela:
$ gcc -std=c23 -Wall -Wextra arred.c -o arred /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/cc7SBp3h.o: na função "main": arred.c:(.text+0x99): referência não definida para "round" /usr/bin/x86_64-linux-gnu-ld.bfd: arred.c:(.text+0xac): referência não definida para "round" /usr/bin/x86_64-linux-gnu-ld.bfd: arred.c:(.text+0xdd): referência não definida para "lround" /usr/bin/x86_64-linux-gnu-ld.bfd: arred.c:(.text+0xee): referência não definida para "lround" collect2: error: ld returned 1 exit status $ gcc -std=c23 -Wall -Wextra arred.c -o arred -lm $ ./arred 2 -2 3 -2 3 -3 3 -3
- O erro veio do ligador (
ld), que no laboratório fala português: referência não definida para "round" (em inglês,undefined reference to 'round') quer dizer "não achei o código da funçãoround". O-lm("ligue a biblioteca m, de matemática") resolve. No editor do guia, a matemática já vem junto e não precisa de-lm. (int)xe(int)y: o corte de sempre, 2 e −2.(int)(x + 0.5): o truque de somar meio antes de cortar funciona para positivos (2.7 + 0.5 = 3.2 → 3), mas falha com negativos (−2.7 + 0.5 = −2.2 → −2, e o certo era −3).round(x)arredonda certo nos dois casos, mas devolve umdouble(por isso%.0f).lround(x)arredonda e já devolve umlong(por isso%ld).
Use lround (ou round) para arredondar. Para só mostrar um número arredondado, nem precisa converter: printf("%.0f", x) já arredonda na hora de escrever (veja Formatos do printf).
6. Letras e números: char ↔ int
Para o C, um char é um número pequeno: o código da letra na tabela ASCII (aula Caracteres). Por isso dá para fazer contas com letras:
#include <stdio.h>
int main(void) {
char letra = 'A';
char proxima = letra + 1;
char digito = '7';
int valor = digito - '0';
printf("%c %c %d\n", letra, proxima, proxima);
printf("'%c' vale %d, e '%c' - '0' = %d\n", digito, digito, digito, valor);
printf("%c\n", 'a' - 32);
return 0;
}
$ gcc -std=c23 -Wall -Wextra letras.c -o letras $ ./letras A B 66 '7' vale 55, e '7' - '0' = 7 A
- letra + 1
'A'é 65; mais 1 dá 66, que é o código do'B'. As letras estão em ordem na tabela.- %c e %d com o mesmo valor
- O formato decide se você vê a letra (
B) ou o número (66). O valor guardado é o mesmo. - digito - '0'
- O caractere
'7'não vale 7: vale 55, o código dele. Mas os algarismos também estão em ordem ('0'é 48,'1'é 49…), então'7' - '0'= 55 − 48 = 7. É o jeito clássico de transformar um algarismo digitado no seu valor. - 'a' - 32
- As minúsculas ficam 32 posições depois das maiúsculas:
'a'(97) − 32 = 65 ='A'. Funciona, mas a forma certa de trocar maiúscula e minúscula étoupper/tolower, da aula Classificar Caracteres, que também lida com o que não é letra.
7. A surpresa: signed com unsigned
Quando uma conta ou comparação mistura um int (com sinal, signed) com um unsigned int (sem sinal), a regra da seção 1 manda converter o int para unsigned. E um negativo convertido para unsigned vira um número enorme:
#include <stdio.h>
int main(void) {
int saldo = -1;
unsigned int limite = 0;
if (saldo > limite) {
printf("-1 é maior que 0?!\n");
} else {
printf("tudo normal\n");
}
printf("%d\n", -1 > 0u);
printf("%u\n", (unsigned int)saldo);
return 0;
}
$ gcc -std=c23 -Wall -Wextra sinal.c -o sinal
sinal.c: In function ‘main’:
sinal.c:6:15: warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
6 | if (saldo > limite) {
| ^
sinal.c:11:23: warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’ [-Wsign-compare]
11 | printf("%d\n", -1 > 0u);
| ^
$ ./sinal
-1 é maior que 0?!
1
4294967295
- saldo > limite
- Para comparar, o C converteu o
saldo(−1) paraunsigned. O resultado é 4294967295, o maiorunsigned int, que é mesmo maior que 0. Por isso oif(aula if e else) escolheu o caminho errado. - -1 > 0u
- O
uno fim de0ufaz dele umunsigned. A comparação dá1, que em C quer dizer "verdadeiro" (aula Operadores de Comparação). - (unsigned int)saldo
- O cast mostra o valor convertido: 4294967295. Os bits de −1 são todos "1", e lidos sem sinal formam o maior número possível (aula Inteiros).
- comparison of integer expressions of different signedness
- "Comparação de inteiros com sinais diferentes." O aviso
-Wsign-comparevem com o-Wextra, mais um motivo para nunca compilar sem ele. O Clang dizcomparison of integers of different signs: 'int' and 'unsigned int'.
Onde isso aparece na vida real: o resultado do sizeof e do strlen é size_t, que é unsigned. Comparar um int negativo com eles dá exatamente essa surpresa. Conserto: não misture. Se um valor pode ser negativo, compare-o primeiro com 0 (if (saldo < 0 || …)) ou use o mesmo tipo dos dois lados.
Formas diferentes: qual usar
| Situação | Formas | Use |
|---|---|---|
Dividir dois int e querer a vírgula | (double)a / b, a / (double)b, a * 1.0 / b | (double)a / b: deixa a intenção clara. Nunca (double)(a / b). |
Ficar só com a parte inteira de um double | int n = x; ou int n = (int)x; | (int)x: o mesmo resultado, mas por escrito e sem aviso do -Wconversion. |
| Arredondar | (int)(x + 0.5), round(x), lround(x) | lround(x) (ou round). O truque do 0.5 falha com negativos. |
| Só mostrar arredondado | converter, ou %.0f / %.2f | printf("%.2f", x): não precisa converter nada. |
Algarismo ('7') → número (7) | c - '0' | c - '0'. Para textos inteiros ("427"), strtol (aula Texto ↔ Número). |
Comparar com sizeof / strlen | misturar int e size_t | Use size_t nas suas variáveis também, ou confira o negativo antes. |
Quando dá errado
8.00 em vez de 8.50
O que significa: a divisão foi feita entre dois int, e o resto foi descartado antes de o resultado chegar ao double.
Como consertar: converta um dos lados antes da divisão: (double)soma / n. Confira que o cast não está em volta da divisão inteira.
9.99 virou 9, sem aviso
O que significa: um double foi guardado num int e o C truncou em silêncio.
Como consertar: se o valor tem centavos, a variável deveria ser double. Se você quer um inteiro, decida: cortar ((int)x) ou arredondar (lround(x)). Para achar esses lugares, compile com -Wconversion.
warning: conversion from ‘double’ to ‘int’ may change value [-Wfloat-conversion]
O que significa: com -Wconversion ligado, o gcc mostra uma conversão que pode perder a parte fracionária.
Como consertar: se foi sem querer, troque o tipo da variável. Se foi de propósito, escreva o cast: int n = (int)x;.
warning: comparison of integer expressions of different signedness: ‘int’ and ‘unsigned int’
O que significa: uma comparação mistura com sinal e sem sinal. Se o lado com sinal for negativo, o resultado vai estar errado.
Como consertar: use o mesmo tipo dos dois lados (por exemplo, size_t para contadores comparados com sizeof) ou trate o negativo antes.
referência não definida para "round" (undefined reference to 'round')
O que significa: o ligador não achou a função de math.h.
Como consertar: acrescente -lm no fim do comando do gcc: gcc … arred.c -o arred -lm.
Referência
| Escrita | Resultado | Por quê |
|---|---|---|
int n = 9.99; | 9 | Conversão implícita: corta a parte fracionária. |
int n = -9.99; | -9 | O corte vai em direção ao zero. |
double d = 7; | 7.0 | int → double não perde nada. |
3 * 2.5 | 7.5 (double) | Na conta, o tipo "maior" ganha. |
17 / 2 | 8 | int / int = divisão inteira. |
(double)17 / 2 | 8.5 | Cast antes da divisão. |
(double)(17 / 2) | 8.0 | Cast depois da divisão: tarde demais. |
(int)2.7 / lround(2.7) | 2 / 3 | Cast corta; lround arredonda (precisa de -lm no gcc). |
char a = 100, b = 100; a + b | 200 (int) | Promoção: char vira int nas contas. |
char c = 300; | 44 | Não cabe: sobram os 8 bits de baixo. |
'A' + 1 | 66 ('B') | Letra é número. |
'7' - '0' | 7 | Algarismo → valor. |
-1 > 0u | 1 (verdadeiro!) | O −1 vira unsigned: 4294967295. |
-Wconversion | avisos de conversões que perdem valor | Não vem no -Wall nem no -Wextra. |
-Wsign-compare | aviso de comparação com sinais diferentes | Vem no -Wextra. |
Exercícios
1. Converta de propósito
Complete com o cast que fica só com a parte inteira de altura:
2. Divisão real
Complete para que pct receba 37.5 (e não 37), com acertos = 3 e total = 8:
3. Do algarismo ao número
Complete para que n valha 5 quando c for o caractere '5':
4. Quanto vale x depois de int x = 7.8;?
5. Com int a = 7, b = 2;, qual expressão dá 3.5?
6. Por que -1 > 0u é verdadeiro em C?
Missão final: a conta do restaurante
Missão: a Ana e três amigos gastaram R$ 87 no restaurante e vão dividir por 4. Complete as três contas (use um cast em cada uma) para a saída ficar como abaixo, sem avisos. Dica: a primeira é uma divisão real; a segunda corta os centavos; a terceira arredonda (como o valor é positivo, o truque de somar 0.5 serve, ou use lround de math.h).
Cada um paga: R$ 21.75 Sem os centavos: R$ 21 Arredondado: R$ 22
#include <stdio.h>
int main(void) {
int conta = 87;
int pessoas = 4;
double cada = conta / pessoas; // conserte: está dando 21.00
int sem_centavos = 0; // a parte inteira de cada
int arredondado = 0; // cada, arredondado
printf("Cada um paga: R$ %.2f\n", cada);
printf("Sem os centavos: R$ %d\n", sem_centavos);
printf("Arredondado: R$ %d\n", arredondado);
return 0;
}
#include <stdio.h>
int main(void) {
int conta = 87;
int pessoas = 4;
double cada = (double)conta / pessoas;
int sem_centavos = (int)cada;
int arredondado = (int)(cada + 0.5);
printf("Cada um paga: R$ %.2f\n", cada);
printf("Sem os centavos: R$ %d\n", sem_centavos);
printf("Arredondado: R$ %d\n", arredondado);
return 0;
}
Resumo
- O C converte tipos sozinho (implícita) ao guardar um valor numa variável de outro tipo e ao misturar tipos numa conta. Na conta, o tipo "maior" ganha;
chareshortsão promovidos aint. double→intcorta a parte fracionária, em direção ao zero (9.99 → 9; −9.99 → −9), sem aviso no gcc. O-Wconversionliga esses avisos.- O cast
(tipo)valorconverte de propósito: não muda a variável, não arredonda e vale só para o que vem logo depois. int / inté divisão inteira. Para a vírgula, converta antes:(double)a / b, nunca(double)(a / b).- Para arredondar,
lround/round(com-lmno gcc); o truque do+ 0.5falha com negativos. Para só mostrar,%.2f. - Letras são números:
'A' + 1é'B','7' - '0'é 7. - Misturar
intcomunsignedconverte o negativo num número enorme:-1 > 0ué verdade. O-Wextraavisa com-Wsign-compare.