Constantes e Conversão de Tipos
Constantes são como o valor de pi na matemática — uma vez definido, nunca muda. Conversão de tipos é como câmbio de moedas: diferentes moedas têm taxas fixas, mas cuidado com a perda de precisão na conversão.
Literais
Um literal é um valor fixo escrito diretamente no código. Não precisa ser calculado — você pode ver seu valor de imediato.
Literais Inteiros
int a = 42;
int b = 0xFF;
int c = 052;
int d = 0b101010;
| Base | Prefixo | Exemplo | Valor Decimal |
|---|---|---|---|
| Decimal | Nenhum | 42 |
42 |
| Hexadecimal | 0x ou 0X |
0xFF |
255 |
| Octal | 0 |
052 |
42 |
| Binário (extensão GNU C99 / padrão C23) | 0b ou 0B |
0b101010 |
42 |
int x = 010; é igual a 8, não 10 — um bug clássico de iniciante.
Literais inteiros são por padrão do tipo int. Se o valor exceder o intervalo de int, torna-se automaticamente long ou long long. Você também pode especificar o tipo manualmente:
| Sufixo | Significado | Exemplo |
|---|---|---|
u ou U |
unsigned | 42U |
l ou L |
long | 42L |
ll ou LL |
long long | 42LL |
UL |
unsigned long | 42UL |
L maiúsculo em vez de l minúsculo, pois l é facilmente confundido com o dígito 1. 42l parece 421.
Literais de Ponto Flutuante
double a = 3.14;
double b = 2.0e8;
double c = 1.5E-3;
float d = 3.14f;
Literais de ponto flutuante são por padrão do tipo double. Adicione o sufixo f ou F para float, ou l/L para long double.
float f = 3.14;
3.14 é um literal double. Atribuí-lo a uma variável float causa truncamento implícito, e o compilador pode emitir um aviso. O jeito correto:
float f = 3.14f;
Literais de Caractere
Um único caractere delimitado por aspas simples:
char ch = 'A';
char nova_linha = '\n';
char zero = '\0';
O tipo de um literal de caractere em C é int (não char) — um detalhe sutil fácil de passar despercebido:
printf("%zu\n", sizeof('A'));
4
Em C, sizeof('A') é 4 (o tamanho de int), enquanto em C++ é 1 (o tamanho de char). Essa é uma diferença sutil entre C e C++.
Literais de String
Uma sequência de caracteres delimitada por aspas duplas:
printf("Olá, Mundo!\n");
Literais de string têm tipo char[]. O compilador adiciona automaticamente um \0 como terminador, então "abc" ocupa 4 bytes (a, b, c, \0).
char *s = "hello";
s[0] = 'H';
O Modificador const
const é uma palavra-chave de C que torna uma variável "somente leitura" — uma vez inicializada, não pode ser modificada:
const int TAM_MAX = 100;
const float PI = 3.14159f;
const char NOVA_LINHA = '\n';
Tentar modificar uma variável const causa um erro de compilação:
const int TAM_MAX = 100;
TAM_MAX = 200;
error: assignment of read-only variable 'TAM_MAX'
const e Arrays
Uma variável const pode ser usada para especificar o tamanho de um array?
const int SIZE = 10;
int arr[SIZE];
const int em C não é uma constante em tempo de compilação (é apenas uma variável somente leitura em tempo de execução). C99 introduziu arrays de tamanho variável (VLAs), então o código acima compila em C99, mas VLAs são um recurso opcional não suportado por todos os compiladores.
Se você precisa de uma verdadeira constante em tempo de compilação para o tamanho de um array, use #define ou enum.
const e Ponteiros
A combinação de const com ponteiros é um desafio clássico de C. Existem três casos:
int valor = 42;
const int *p1 = &valor;
int * const p2 = &valor;
const int * const p3 = &valor;
| Declaração | Significado |
|---|---|
const int *p |
O dado apontado não pode ser modificado; o ponteiro pode apontar para outro lugar |
int * const p |
O ponteiro não pode ser modificado (endereço fixo); o dado apontado pode ser alterado |
const int * const p |
Nem o ponteiro nem o dado apontado podem ser modificados |
* o const está. Se está à esquerda, ele modifica o dado. Se está à direita, ele modifica o ponteiro.
Constantes Macro com #define
#define é uma diretiva de pré-processamento usada para definir constantes macro:
#define PI 3.14159
#define TAM_MAX 100
#define NOVA_LINHA '\n'
O pré-processador substitui todas as ocorrências de PI por 3.14159 antes da compilação. Não é uma variável — não ocupa memória e não tem verificação de tipo.
#define vs. const
| Recurso | #define |
const |
|---|---|---|
| Quando processado | Estágio de pré-processamento (substituição de texto) | Estágio de compilação |
| Verificação de tipo | Nenhuma | Sim |
| Escopo | Da definição até o fim do arquivo | Segue regras de escopo de variáveis |
| Depuração | Depurador não vê o nome da macro | Depurador pode ver a variável const |
| Endereçável | Não | Sim |
const — tem verificação de tipo e restrições de escopo, sendo mais seguro. Use #define para flags de compilação condicional ou quando precisar de constantes em tempo de compilação.
Armadilhas Comuns de Macros
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;
Você pode esperar 3 + 3 * 2 = 12, mas a macro expande para 3 + 3 * 2 = 9. Macros são pura substituição de texto — não respeitam precedência de operadores.
Correção: Adicione parênteses à macro:
#define DOUBLE(x) ((x) + (x))
Constantes enum
enum é outra forma de definir constantes inteiras:
enum Cor {
VERMELHO,
VERDE,
AZUL
};
Por padrão, os valores começam em 0 e incrementam: VERMELHO = 0, VERDE = 1, AZUL = 2. Você também pode atribuir valores manualmente:
enum DiaSemana {
SEG = 1,
TER,
QUA,
QUI,
SEX,
SAB = 100,
DOM
};
TER = 2, QUA = 3, ..., DOM = 101 (cada valor subsequente é um a mais que o anterior).
Valores de enum são verdadeiras constantes em tempo de compilação, usáveis para tamanhos de array e cenários similares. Comparados com #define, enums carregam informação de tipo e são visíveis pelo nome no depurador — prefira-os quando possível.
Conversão Implícita de Tipos
Quando tipos diferentes são misturados em uma expressão, C realiza automaticamente a conversão de tipos. Entender as regras de conversão implícita é essencial para evitar bugs.
Promoção de Inteiros
Em expressões, char e short são automaticamente promovidos a int antes de qualquer operação:
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
4
O resultado de a + b é int (4 bytes), não char (1 byte).
Conversão Aritmética
Quando valores de tipos diferentes são combinados em uma operação, o tipo menor é automaticamente convertido para o tipo maior. A hierarquia de conversão de menor para maior:
int → unsigned int → long → unsigned long → long long → unsigned long long → float → double → long double
A regra: antes de uma operação, o operando de menor categoria é automaticamente convertido para a maior categoria.
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
2.5
a é automaticamente convertido para double, então a divisão de ponto flutuante é realizada, resultando em 2.5.
Conversão por Atribuição
Durante a atribuição, o valor do lado direito é automaticamente convertido para o tipo do lado esquerdo:
int x = 3.14;
double y = 42;
3.14 atribuído a int é truncado para 3; 42 atribuído a double torna-se 42.0.
int n = 2.9;
n é 2, não 3.
A Armadilha da Divisão Inteira
Essa é a armadilha mais comum para iniciantes:
int a = 5;
int b = 2;
double resultado = a / b;
printf("%.1f\n", resultado);
2.0
Você espera 2.5, mas obtém 2.0. O motivo: a / b é divisão inteira, resultando em 2 (um inteiro), que então é convertido para o valor double 2.0.
Correção: Faça pelo menos um operando ser de ponto flutuante:
double resultado = (double)a / b;
Ou:
double resultado = a * 1.0 / b;
Ou:
double resultado = 5.0 / 2;
Cast Explícito de Tipos
Quando a conversão implícita não faz o que você precisa, pode usar um cast explícito para dizer ao compilador exatamente qual tipo você quer:
Sintaxe
(nome_do_tipo)expressao
Exemplo
#include <stdio.h>
int main(void) {
int a = 7;
int b = 2;
printf("Divisão inteira: %d\n", a / b);
printf("Com cast: %.2f\n", (double)a / b);
return 0;
}
Divisão inteira: 3
Com cast: 3.50
(double)a primeiro converte a para 7.0. Depois, quando é combinado com b, b também é implicitamente promovido a double, e a divisão de ponto flutuante é realizada.
Exemplo
#include <stdio.h>
#include <math.h>
int main(void) {
double x = 3.7;
printf("Truncamento direto: %d\n", (int)x);
printf("Arredondamento: %d\n", (int)(x + 0.5));
printf("Função round(): %.0f\n", round(x));
return 0;
}
Truncamento direto: 3
Arredondamento: 4
Função round(): 4
(int)(-3.7) resulta em -3, não -4. Isso difere de "floor" (arredondar em direção ao infinito negativo), então cuidado.
Armadilhas Comuns de Conversão de Tipos
Misturando Signed e Unsigned
int a = -5;
unsigned int b = 10;
if (a + b > 0) {
printf("Maior que 0\n");
} else {
printf("Menor ou igual a 0\n");
}
Maior que 0
Você pode pensar que -5 + 10 = 5 > 0 está correto, mas o comportamento deste código depende das regras de conversão implícita. Quando int e unsigned int são misturados em uma operação, o int é convertido para unsigned int. -5 torna-se um número positivo muito grande quando interpretado como unsigned, então o resultado é de fato maior que 0 — mas provavelmente essa não é a lógica que você pretendia.
Perda de Precisão
int grande = 123456789;
float f = grande;
printf("%d\n", grande);
printf("%.0f\n", f);
123456789
123456792
float só tem 6-7 dígitos significativos e não consegue representar um inteiro de 9 dígitos com precisão — os últimos 3 dígitos são perdidos. Tenha muito cuidado ao atribuir inteiros grandes a float.
Conversão com Overflow
char ch = 200;
printf("%d\n", ch);
Em plataformas onde char é signed, 200 excede o intervalo de char (-128 a 127), resultando em comportamento indefinido — possivelmente exibindo -56. Se precisa armazenar valores de 0 a 255, use unsigned char.
Conversão de Ponteiros
Fazer cast entre ponteiros de tipos diferentes é perigoso:
int valor = 0x41424344;
char *p = (char *)&valor;
printf("%c\n", *p);
A saída depende da ordem de bytes do sistema (big-endian ou little-endian) e não é portável. Esse tipo de conversão só é usado em programação de baixo nível e deve ser evitado por iniciantes.
❓ Perguntas Frequentes
P: Devo usar #define ou const? R: Prefira const. Tem verificação de tipo, segue regras de escopo e é amigável ao depurador. #define é pura substituição de texto sem segurança de tipo, mas é uma constante em tempo de compilação que pode ser usada para tamanhos de array e compilação condicional. Se um valor precisa estar disponível em tempo de compilação (como tamanho de array), use #define ou enum; caso contrário, use const.
P: Por que 5/2 é igual a 2 em vez de 2.5? R: Porque tanto 5 quanto 2 são ints, e C especifica que divisão inteira produz um inteiro — a parte fracionária é descartada. Para obter 2.5, faça pelo menos um operando ser float: 5.0/2 ou (double)5/2. Essa é uma das armadilhas mais comuns de C para iniciantes.
P: O cast altera o valor da variável original? R: Não. Um cast só produz um valor temporário convertido — o valor e tipo da variável original permanecem inalterados. Por exemplo, (int)3.7 produz o valor temporário 3, mas 3.7 continua sendo 3.7.
P: Por que misturar operações signed e unsigned é tão propenso a bugs? R: Porque o padrão C diz que, quando misturados, o valor signed é implicitamente convertido para unsigned. Um número negativo torna-se um valor positivo muito grande quando unsigned, fazendo com que comparações e cálculos produzam resultados completamente não intuitivos. Evite misturá-los, ou use casts explícitos antes de operar.
📖 Resumo
- Literais vêm em quatro tipos: inteiro, ponto flutuante, caractere e string. Fique atento a sufixos (f/L/UL) e prefixos de base (0x/0/0b)
- const torna uma variável somente leitura; prefira const em vez de #define. #define é substituição de texto no pré-processamento sem verificação de tipo
- Conversão implícita segue regras de promoção de inteiros e conversão aritmética — tipos menores convertem automaticamente para tipos maiores
- Divisão inteira é a armadilha mais comum de C: 5/2 = 2; para obter 2.5, pelo menos um operando deve ser de ponto flutuante
- Misturar inteiros signed e unsigned produz resultados inesperados — evite ou use casts explícitos
📝 Exercícios
- Escreva um programa que exiba o número 255 em decimal, hexadecimal e octal, e observe os formatos de saída
- Escreva um programa para calcular a área de um círculo (raio como int 10, pi definido como const double), tomando cuidado para evitar a armadilha da divisão inteira
- Escreva um programa para testar o seguinte e registrar os resultados: o valor de (int)3.9, o valor de (int)(-3.9), e a saída %d após
char ch = 128. Explique cada resultado



