Uma constante é um valor com um nome e uma promessa de que ele não vai mudar. C te dá três mecanismos para isso, e eles são coisas genuinamente diferentes por baixo do capô - um é uma variável, um é substituição de texto e um é um tipo inteiro.
Por que dar nome a um valor
Compare estas duas linhas:
double area = 3.14159 * r * r;
double area = PI * r * r;
A segunda é melhor por três razões que aparecem depois. Ela diz o que o número significa. Ela aparece em exatamente um lugar, então mudar a precisão é uma edição em vez de uma busca. E um erro de digitação em PI é um erro de compilação, enquanto um erro de digitação em 3.14195 é uma resposta errada.
Números sem nome espalhados pelo código são chamados de números mágicos, e removê-los é uma das melhorias mais baratas que você pode fazer num programa em C.
const
A palavra-chave const marca uma variável como somente leitura:
Descomente a atribuição e o compilador recusa: assignment of read-only variable 'PI'. Esse erro em tempo de compilação é a razão de existir disso tudo.
Uma variável const é uma variável de verdade. Ela tem um tipo, então o compilador verifica como você a usa. Ela obedece ao escopo, então um const dentro de uma função não vaza para fora. Ela tem um endereço, então você pode pegar um ponteiro para ela. E um depurador consegue mostrar seu nome e seu valor.
Uma const precisa ser inicializada no ponto da declaração - não há uma segunda chance:
const int limit; /* erro (ou lixo para sempre): nada pode atribuir a ela depois */
const int limit = 50; /* correto */
#define
A alternativa do pré-processador é uma macro:
Isto não é uma variável. Antes de o compilador rodar, o pré-processador substitui cada ocorrência de PI no código-fonte pelos caracteres 3.14159265358979. O compilador nunca vê o nome - o que é exatamente o motivo de um #define não ter tipo e não poder aparecer num depurador.
Note que não há ponto e vírgula e não há =. Uma macro é texto puro, então este erro comum:
#define MAX_USERS 100; /* note o ponto e vírgula */
int limit = MAX_USERS + 1; /* expande para: int limit = 100; + 1; -> erro */
produz um erro numa linha que não se parece nada com a definição. A convenção de nomes em maiúsculas para macros existe justamente para que os leitores saibam que há uma substituição acontecendo.
Macros são convencionalmente escritas em MAIUSCULAS_COM_UNDERSCORE; variáveis const normalmente seguem a mesma convenção quando representam a mesma ideia.
Constantes de enum
A terceira forma te dá um conjunto de inteiros nomeados:
Os valores começam em 0 e aumentam de um em um, a menos que você os atribua, então MON é 0 e FRI é 4. Um enum { ... } anônimo é um idioma comum para constantes inteiras simples.
Constantes de enum têm uma vantagem real sobre as duas alternativas: elas são constantes inteiras de tempo de compilação que o compilador entende, e são visíveis para o depurador. A história completa está em enums em C.
Qual usar
| Necessidade | Use |
|---|---|
| Uma constante tipada, com escopo e depurável | const |
Um tamanho de array, um rótulo de case, a largura de um campo de bits | enum ou #define |
| Uma constante de string | #define ou const char * |
| Uma constante de ponto flutuante | const double |
| Um conjunto de valores inteiros relacionados | enum |
Qualquer coisa que o pré-processador precise ver (#if, guardas de inclusão) | #define |
O padrão deveria ser const. Recorra a #define quando o valor precisa existir antes da compilação, e a enum quando você precisa de um inteiro de tempo de compilação ou de um grupo nomeado.
O lugar onde isso realmente pesa são os tamanhos de array:
Um const int em C é uma variável somente leitura, não uma expressão constante de tempo de compilação. Dentro de uma função, o C99 aceita int local[const_size] fazendo dele um array de tamanho variável; em escopo de arquivo, dentro de uma struct ou como rótulo de case, ele é rejeitado de cara. enum e #define funcionam em qualquer lugar.
(Essa é uma das diferenças reais em relação ao C++, onde const int é uma expressão constante. Código que transita entre as duas linguagens tropeça nisso.)
const com ponteiros
const e ponteiros se combinam de três maneiras, e os significados não são intercambiáveis. Leia a declaração de dentro para fora, a partir do nome:
const char *p; /* p aponta para chars que não devem ser modificados */
char * const q; /* o próprio q não pode ser reapontado; os chars podem mudar */
const char * const r; /* nem o ponteiro nem os chars podem mudar */
A forma const char * é a que você vai escrever mais, porque é o tipo correto para um parâmetro de função que lê uma string sem alterá-la:
Acrescentar const a um parâmetro somente leitura não custa nada e documenta o contrato de um jeito que o compilador faz valer. Vale a pena fazer em todo lugar.
Literais de string são const na prática
Uma armadilha sutil que o const existe para pegar:
char *s = "hello"; /* C legal, mas s aponta para memória somente leitura */
s[0] = 'H'; /* comportamento indefinido - normalmente um travamento */
Literais de string vivem numa seção somente leitura do executável. Escrever através desse ponteiro costuma causar falha de segmentação. Declarar o ponteiro como const char *s = "hello"; faz o compilador rejeitar a atribuição em vez de deixá-la travar em tempo de execução.
Se você precisa de uma string modificável, copie-a para um array:
Perguntas frequentes
Como se declara uma constante em C?
Acrescente const a uma declaração e dê um valor a ela: const double PI = 3.14159;. O compilador então rejeita qualquer tentativa de atribuir algo a ela. A alternativa mais antiga é uma macro de pré-processador, #define PI 3.14159, que faz substituição de texto no lugar disso.
Qual é a diferença entre const e #define em C?
const cria uma variável real e tipada que o compilador conhece, então ela tem os tipos verificados, aparece no depurador e obedece ao escopo. #define é pura substituição de texto feita antes da compilação - não tem tipo, não tem escopo e não pode ser inspecionada durante a depuração. Prefira const, a menos que você precise do valor num contexto que exige uma constante de tempo de compilação.
Uma variável const pode ser usada como tamanho de array em C?
Não para um array de tamanho fixo em escopo de arquivo. const int N = 10; int arr[N]; é um array de tamanho variável no C99 (permitido dentro de uma função, não em escopo de arquivo e não dentro de uma struct). Para um tamanho genuíno de tempo de compilação, use #define N 10 ou um enum { N = 10 };.
O que significa const char *p?
É um ponteiro para um char constante: você pode mudar para onde p aponta, mas não o caractere para o qual ele aponta. char * const p é o oposto - um ponteiro constante para um char modificável. const char * const p trava os dois. Leia a declaração de dentro para fora, a partir do nome.