Em programação, constantes são valores que não podem ser alterados durante a execução do programa. Em C, existem várias formas de definir constantes, cada uma com suas particularidades e usos apropriados. Nesta aula, exploraremos as principais: a palavra-chave const, a diretiva de pré-processador #define, as enumerações (enum) e os literais. Compreender essas diferenças é essencial para escrever código claro, seguro e eficiente.

Vamos começar com a abordagem mais moderna e segura: o qualificador const.

const

O qualificador const é aplicado a variáveis para indicar que seu valor não deve ser modificado após a inicialização. É uma ferramenta poderosa para evitar alterações acidentais e melhorar a legibilidade do código. Ao declarar uma variável como const, o compilador gera um erro se houver tentativa de modificação.

Exemplo:

#include <stdio.h>

int main() {
    const double PI = 3.14159;
    // PI = 3.14; // Erro: tentativa de modificar const
    printf("Valor de PI: %.5f\n", PI);
    return 0;
}

Variáveis const seguem as mesmas regras de escopo que variáveis normais, podendo ser locais ou globais. Além disso, podem ser usadas em expressões constantes em alguns contextos (como tamanho de arrays em C99+), mas nem sempre são consideradas constantes de tempo de compilação. Uma limitação é que const não pode ser usado para criar constantes simbólicas em diretivas de pré-processador, pois o pré-processador não reconhece variáveis C.

#define

A diretiva #define é uma instrução para o pré-processador que substitui um identificador por uma sequência de tokens antes da compilação. É amplamente usada para definir constantes simbólicas, macros e até mesmo para incluir cabeçalhos condicionalmente. Diferente de const, #define não cria uma variável; é uma substituição textual.

Exemplo:

#include <stdio.h>
#define PI 3.14159
#define AREA_CIRCULO(r) (PI * (r) * (r))

int main() {
    double raio = 5.0;
    printf("Área: %.2f\n", AREA_CIRCULO(raio));
    return 0;
}

Vantagens: funciona em qualquer contexto (inclusive para tamanhos de arrays estáticos) e não ocupa memória (a substituição é feita em tempo de pré-processamento). Desvantagens: não tem tipo definido (pode causar problemas de precedência em expressões), não respeita escopo e pode gerar efeitos colaterais se não houver cuidado com parênteses. Boa prática: usar letras maiúsculas para identificadores #define e sempre envolver parâmetros de macros entre parênteses.

Enumerações (enum)

Enumerações são um tipo de dado definido pelo usuário que consiste em um conjunto de constantes inteiras nomeadas. São úteis para representar estados, opções ou valores discretos, tornando o código mais legível e menos propenso a erros.

Sintaxe:

enum dias {DOMINGO, SEGUNDA, TERCA, QUARTA, QUINTA, SEXTA, SABADO};
int main() {
    enum dias hoje = SEGUNDA;
    printf("Hoje é dia %d\n", hoje); // Imprime 1
    return 0;
}

Por padrão, os valores começam em 0 e incrementam em 1. É possível especificar valores manualmente:

enum estado {LIGADO = 1, DESLIGADO = 0};
enum cores {VERMELHO = 10, VERDE = 20, AZUL = 30};

Enumerações são úteis em switch-case e para definir constantes agrupadas. Diferente de #define, elas têm escopo e podem ser usadas como tipo, permitindo que o compilador verifique atribuições (embora em C a verificação seja fraca).

Literais

Literais são valores escritos diretamente no código-fonte que representam dados constantes. Em C, existem literais para diversos tipos: inteiros, ponto flutuante, caracteres e strings. Eles são frequentemente usados em conjunto com const ou #define para criar constantes nomeadas.

  • Literais inteiros: 42, 0x2A (hexadecimal), 052 (octal), 0b1010 (binário, C23+). Sufixos: U (unsigned), L (long), LL (long long).
  • Literais de ponto flutuante: 3.14, 2.5e-3, 1.0f (float), 1.0L (long double).
  • Literais de caractere: 'A', '\n', '\x41' (hexadecimal).
  • Literais de string: "Olá, mundo!" (termina com caractere nulo '\0').

Exemplo de uso:

#include <stdio.h>
int main() {
    int a = 100;           // literal inteiro decimal
    double b = 3.14e2;     // literal ponto flutuante
    char c = '\x41';       // literal caractere hexadecimal (A)
    printf("%d %.2f %c\n", a, b, c);
    return 0;
}

Literais são a base para a construção de constantes nomeadas. É importante conhecer os sufixos e notações para evitar erros de tipo ou precisão.

Boas Práticas e Observações Finais

A escolha entre const, #define e enum depende do contexto. Prefira const para constantes tipadas e com escopo, use #define para constantes que precisam ser avaliadas em tempo de pré-processamento (como tamanhos de arrays estáticos) e enum para conjuntos de valores inteiros relacionados. Evite #define para constantes que podem ser expressas como variáveis const para melhor segurança de tipo. Sempre nomeie constantes com letras maiúsculas e use nomes descritivos.

Referências

Exercícios

  1. Declare uma constante chamada TAXA_JUROS usando #define com valor 0.05. Em seguida, declare uma variável const double chamada taxa_inflacao com valor 0.03. Escreva um programa que imprima ambos os valores.

    ✓ Resposta:
    #include <stdio.h>
    #define TAXA_JUROS 0.05
    
    int main() {
        const double taxa_inflacao = 0.03;
        printf("Taxa de juros: %.2f\n", TAXA_JUROS);
        printf("Taxa de inflação: %.2f\n", taxa_inflacao);
        return 0;
    }
  2. Crie uma enumeração chamada niveis com os valores BAIXO, MEDIO e ALTO, atribuindo explicitamente os números 1, 2 e 3. Escreva um programa que declare uma variável do tipo enum niveis e atribua MEDIO a ela, depois imprima seu valor numérico.

    ✓ Resposta:
    #include <stdio.h>
    
    enum niveis {BAIXO = 1, MEDIO = 2, ALTO = 3};
    
    int main() {
        enum niveis nivel = MEDIO;
        printf("Nível: %d\n", nivel);
        return 0;
    }
  3. Explique a diferença entre const int MAX = 100; e #define MAX 100. Em qual situação você usaria cada um?

    ✓ Resposta: const int MAX = 100; cria uma variável com tipo (int) e escopo, não pode ser usada em contextos que exigem constantes de tempo de compilação (ex: tamanho de array estático em C90). #define MAX 100 é uma substituição textual feita pelo pré-processador, sem tipo, e pode ser usada em qualquer lugar que aceite um literal. Use const quando precisar de segurança de tipo e escopo, e #define para constantes que precisam ser avaliadas em tempo de pré-processamento (ex: tamanho de array estático, macros).
  4. Escreva um programa que use literais de diferentes bases: um inteiro decimal, um hexadecimal, um octal e um binário (se seu compilador suportar C23). Imprima todos no formato decimal.

    ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int dec = 42;
        int hex = 0x2A;   // 42 em hex
        int oct = 052;    // 42 em octal
        int bin = 0b101010; // C23: 42 em binário
        printf("Decimal: %d\n", dec);
        printf("Hex: %d\n", hex);
        printf("Octal: %d\n", oct);
        printf("Binário: %d\n", bin);
        return 0;
    }
  5. Defina uma constante #define para o valor de pi (3.14159) e uma função macro AREA_CIRCULO(r) que calcula a área. No programa, peça ao usuário o raio e exiba a área. Certifique-se de que a macro esteja corretamente parentizada.

    ✓ Resposta:
    #include <stdio.h>
    #define PI 3.14159
    #define AREA_CIRCULO(r) (PI * (r) * (r))
    
    int main() {
        double raio;
        printf("Digite o raio: ");
        scanf("%lf", &raio);
        printf("Área: %.2f\n", AREA_CIRCULO(raio));
        return 0;
    }