Aula 06 – Constantes: #define e const

Valores fixos no programa — macros de pré-processador versus variáveis somente leitura

O que é uma Constante?

Uma constante é um valor que não deve ser alterado durante a execução do programa. Em vez de espalhar números e textos literais pelo código — prática chamada de magic numbers — damos um nome significativo a esses valores fixos. Isso torna o código mais legível, seguro e fácil de manter.

Em C, existem duas formas principais de declarar constantes: a diretiva de pré-processador #define, que cria uma macro substituída antes da compilação, e a palavra-chave const, que declara uma variável somente leitura com tipo definido.
Por que evitar "magic numbers"?
Considere o código area = 3.14159 * r * r; versus area = PI * r * r;. O segundo é imediatamente compreensível. Se o valor de PI precisar ser atualizado para mais casas decimais, basta mudar uma linha — a definição da constante — em vez de buscar e substituir todos os 3.14159 espalhados pelo código.

Diretiva #define — Macros de Pré-processador

A diretiva #define instrui o pré-processador — uma etapa que ocorre antes da compilação propriamente dita — a substituir textualmente cada ocorrência do nome definido pelo seu valor correspondente.

#define NOME valor

Não termina com ponto e vírgula. Por convenção, nomes de macros são escritos em MAIÚSCULAS para distingui-los de variáveis comuns.
#include <stdio.h>

#define PI        3.14159
#define GRAVIDADE 9.81
#define NOME      "Curso de C"
#define MAXIMO    100

int main() {
    printf("PI = %.5f\n", PI);
    printf("Gravidade = %.2f m/s²\n", GRAVIDADE);
    printf("Bem-vindo ao %s!\n", NOME);
    printf("Valor máximo: %d\n", MAXIMO);
    return 0;
}

PI = 3.14159

Gravidade = 9.81 m/s²

Bem-vindo ao Curso de C!

Valor máximo: 100

O que o pré-processador faz

Antes de compilar, o pré-processador lê o código e substitui mecanicamente cada ocorrência do nome pelo valor — como um "localizar e substituir" automático. O compilador nunca vê o nome PI; ele vê diretamente o número 3.14159:

Código que você escreve Código que o compilador recebe
area = PI * r * r; area = 3.14159 * r * r;
if (x > MAXIMO) if (x > 100)
Atenção — #define não tem tipo: O pré-processador faz substituição de texto puro, sem verificar tipos. Isso significa que erros como usar #define TAXA "dez" onde se espera um número não são detectados durante a definição da macro — o erro só aparece quando a macro é usada. Por isso o const é geralmente preferido quando a segurança de tipos é importante.

Macros com expressões

O #define também pode definir expressões inteiras. Nesse caso, use sempre parênteses para evitar problemas de precedência na substituição textual:

// SEM parênteses — perigoso!
#define DOBRO_ERRADO(x)  x * 2

// COM parênteses — seguro
#define DOBRO(x)         ((x) * 2)
#define QUADRADO(x)      ((x) * (x))

int main() {
    printf("%d\n", DOBRO(5));          // 10
    printf("%d\n", DOBRO(3 + 1));      // 8  (com parênteses: (3+1)*2)
    printf("%d\n", DOBRO_ERRADO(3+1)); // 5  ERRADO! expande para: 3+1*2 = 5
    printf("%d\n", QUADRADO(4));       // 16
    return 0;
}

10

8

5

16

Palavra-chave const — Variável Somente Leitura

O qualificador const declara uma variável cujo valor não pode ser modificado após a inicialização. Ao contrário do #define, uma variável const tem tipo definido, ocupa espaço na memória e é verificada pelo compilador.

const tipo nome = valor;

A inicialização é obrigatória na declaração — não é possível declarar um const sem valor e atribuí-lo depois.
#include <stdio.h>

int main() {
    const float  PI       = 3.14159f;
    const double EULER    = 2.718281828;
    const int    ANO_BASE = 2024;
    const char   GRAU     = 'C';        // grau Celsius

    float raio = 5.0;
    float area = PI * raio * raio;

    printf("Área do círculo: %.2f\n", area);
    printf("e (Euler): %.9f\n", EULER);
    printf("Ano base: %d\n", ANO_BASE);
    printf("Unidade: °%c\n", GRAU);

    // PI = 3.0;  ← erro de compilação: assignment of read-only variable

    return 0;
}

Área do círculo: 78.54

e (Euler): 2.718281828

Ano base: 2024

Unidade: °C

Boa prática: Sempre inicialize constantes const no momento da declaração. Tentativas de modificar uma variável const geram erro de compilação — que é exatamente o comportamento desejado. O compilador protege o valor automaticamente.

Exemplo do Material de Referência

O programa abaixo, extraído diretamente da seção 4 do livro de referência do curso, demonstra as duas abordagens lado a lado calculando a área de um círculo. O resultado é idêntico, mas os mecanismos internos são bem diferentes:

#include <stdio.h>

#define PI_MACRO 3.14159        // substituição textual — sem tipo, sem memória

int main() {
    const float pi_const = 3.14159;  // variável somente leitura — tem tipo e endereço

    float radius = 5.0;

    float area_macro = PI_MACRO * radius * radius;
    float area_const = pi_const * radius * radius;

    printf("Area using macro: %.2f\n", area_macro);
    printf("Area using const: %.2f\n", area_const);

    return 0;
}

Area using macro: 78.54

Area using const: 78.54

Resultados iguais, mecanismos diferentes: Os dois produzem o mesmo número, mas internamente: PI_MACRO é substituído por 3.14159 antes do compilador ver o código; já pi_const é uma variável real alocada na memória, com tipo float verificado pelo compilador.

#define versus const — Quando usar cada um?

Ambas as formas são amplamente usadas na prática. A escolha depende do contexto:

Característica #define const
Etapa de processamento Pré-processador (antes da compilação) Compilador
Tem tipo de dado Não — é substituição textual pura Sim — tipo explícito e verificado
Ocupa memória Não — não existe em runtime Sim — é uma variável na memória
Verificação de tipo Nenhuma — erros de tipo só aparecem no uso Completa — o compilador valida o tipo
Visível no depurador Não — o depurador vê apenas o valor numérico Sim — o nome da variável é preservado
Escopo Do ponto de definição até o fim do arquivo Segue as regras normais de escopo de bloco
Pode ser ponteiro Não Sim — const int *ptr
Uso preferido Inclusão condicional, macros parametrizadas, constantes globais simples Constantes com tipo definido, escopo controlado
Recomendação geral: Para constantes numéricas e strings simples em um único arquivo, ambas funcionam bem. Para projetos maiores e multilinguagem, prefira const — ele oferece verificação de tipo, escopo controlado e melhor integração com depuradores. O material de referência deste curso usa #define extensivamente (como #define alturaMaxima 225), então é fundamental conhecer os dois.

#define além de Constantes: Include Guards

O #define tem outros usos importantes além de definir constantes numéricas. Um dos mais comuns são os include guards (guardas de inclusão), que evitam que um arquivo de cabeçalho seja incluído múltiplas vezes em um projeto — tema que aprofundaremos nas Aulas 17 e 18:

/* arquivo: geometria.h */
#ifndef GEOMETRIA_H      // "se GEOMETRIA_H NÃO estiver definido..."
#define GEOMETRIA_H      // "...define-o agora"

#define PI 3.14159

float areaCirculo(float r);
float areaRetangulo(float b, float h);

#endif                   // fim do bloco condicional
Como funciona: Na primeira vez que geometria.h é incluído, GEOMETRIA_H não está definido, então o bloco inteiro é processado e GEOMETRIA_H é definido. Se o arquivo for incluído novamente, GEOMETRIA_H já existe e todo o conteúdo é ignorado, evitando erros de declaração dupla.

Exemplo Prático: Constantes em um Programa Real

O programa abaixo usa tanto #define quanto const para calcular propriedades de um movimento uniformemente acelerado — tema diretamente relacionado ao contexto científico do curso:

#include <stdio.h>
#include <math.h>

// Constantes físicas globais — #define por serem simples e globais
#define GRAVIDADE   9.81     // m/s²
#define LUZ         299792458 // m/s

int main() {
    // Constantes locais com tipo — const para segurança
    const float altura_inicial = 100.0f;  // metros
    const float velocidade_0   = 0.0f;    // m/s (largado do repouso)

    // Tempo de queda: h = v0*t + (g/2)*t² → t = sqrt(2h/g)
    float tempo = sqrt(2.0 * altura_inicial / GRAVIDADE);
    float velocidade_final = velocidade_0 + GRAVIDADE * tempo;

    printf("Queda de %.0f metros:\n", altura_inicial);
    printf("  Tempo de queda:      %.2f s\n",   tempo);
    printf("  Velocidade final:    %.2f m/s\n", velocidade_final);
    printf("\nVelocidade da luz: %d m/s\n", LUZ);

    return 0;
}

Queda de 100 metros:

  Tempo de queda: 4.52 s

  Velocidade final: 44.29 m/s

 

Velocidade da luz: 299792458 m/s

Constantes em C versus Java

Em Java, o equivalente mais próximo de uma constante é uma variável declarada com static final. O C não tem essa combinação — as duas abordagens disponíveis são #define e const:

Aspecto C — #define C — const Java — static final
Declaração #define PI 3.14 const float PI = 3.14; static final double PI = 3.14;
Tem tipo Não Sim Sim
Verificação em compilação Não (só no uso) Sim Sim
Escopo Global no arquivo Bloco onde foi declarada Classe
Convenção de nome MAIUSCULAS Variável ou MAIUSCULAS MAIUSCULAS_COM_UNDERSCORE

Código equivalente nas duas linguagens

C Java
#define PI_MACRO 3.14159

int main() {
    const float pi = 3.14159f;
    float r = 5.0;
    float area = pi * r * r;
    printf("%.2f\n", area);
}
public class Main {
  static final double PI = 3.14159;

  public static void main(String[] args) {
    double r = 5.0;
    double area = PI * r * r;
    System.out.printf("%.2f%n", area);
  }
}

Exercícios — Aula 06

Teste seus conhecimentos sobre constantes em C:

1. Qual é a principal diferença entre #define PI 3.14 e const float pi = 3.14;?

2. O que acontece se você tentar modificar uma variável const em C?

3. Por que a macro abaixo pode causar resultados inesperados?

#define DOBRO(x) x * 2
printf("%d\n", DOBRO(3 + 1));

4. Qual das declarações abaixo está incorreta sintaticamente em C?

5. Em que etapa do processo de compilação o #define é processado?

6. Qual é o equivalente mais próximo de const float PI = 3.14; em Java?

7. Qual vantagem o const tem sobre o #define em projetos maiores?

8. Para que serve a construção #ifndef ARQUIVO_H / #define ARQUIVO_H / #endif em um arquivo de cabeçalho?