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

C
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
💡 Dica: Colocar acidentalmente um 0 antes de um número o transforma em octal. 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
⚠️ Atenção: Use 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

C
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.

🔥 Erro Comum:

C
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:

C
float f = 3.14f;

Literais de Caractere

Um único caractere delimitado por aspas simples:

C
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:

C
printf("%zu\n", sizeof('A'));
TEXT
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:

C
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).

⚠️ Atenção: Literais de string são armazenados em memória somente leitura. Tentar modificar um causa comportamento indefinido (geralmente um travamento):

C
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:

C
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:

C
const int TAM_MAX = 100;
TAM_MAX = 200;
TEXT
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?

C
const int SIZE = 10;
int arr[SIZE];
⚠️ Atenção: Em C89, isso não é permitido — tamanhos de array devem ser constantes em tempo de compilação, e 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:

C
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
💡 Dica: Olhe para qual lado do * 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:

C
#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
💡 Dica: Prefira 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

C
#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:

C
#define DOUBLE(x) ((x) + (x))

Constantes enum

enum é outra forma de definir constantes inteiras:

C
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:

C
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:

C
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
TEXT
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:

TEXT
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.

C
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
TEXT
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:

C
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.

⚠️ Atenção: Quando um valor de ponto flutuante é atribuído a um tipo inteiro, a parte fracionária é simplesmente descartada (não arredondada):

C
int n = 2.9;

n é 2, não 3.

A Armadilha da Divisão Inteira

Essa é a armadilha mais comum para iniciantes:

C
int a = 5;
int b = 2;
double resultado = a / b;
printf("%.1f\n", resultado);
TEXT
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:

C
double resultado = (double)a / b;

Ou:

C
double resultado = a * 1.0 / b;

Ou:

C
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

C
(nome_do_tipo)expressao

Exemplo

C
#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;
}
▶ Experimente
TEXT
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

C
#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;
}
▶ Experimente
TEXT
Truncamento direto: 3
Arredondamento: 4
Função round(): 4
💡 Dica: C99 especifica que o truncamento de ponto flutuante para inteiro arredonda em direção a zero. Então (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

C
int a = -5;
unsigned int b = 10;

if (a + b > 0) {
    printf("Maior que 0\n");
} else {
    printf("Menor ou igual a 0\n");
}
TEXT
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.

⚠️ Atenção: Nunca misture inteiros com e sem sinal em operações. Se precisar, use casts explícitos.

Perda de Precisão

C
int grande = 123456789;
float f = grande;
printf("%d\n", grande);
printf("%.0f\n", f);
TEXT
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

C
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:

C
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

📝 Exercícios

  1. Escreva um programa que exiba o número 255 em decimal, hexadecimal e octal, e observe os formatos de saída
  2. 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
  3. 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
100%