C++ Constantes

Alguns valores nunca deveriam mudar enquanto o programa roda: uma semana tem 7 dias, um dia tem 86 400 segundos, o caderno da lojinha da Ana custa 12,50. Em C++ você pode trancar a caixa de uma variável depois de colocar o valor: ela vira uma constante, e o compilador recusa qualquer tentativa de mudá-la. Nesta aula você vai conhecer as duas formas modernas, const e constexpr, e a forma antiga, #define, que ainda aparece em muito código.

Você vai aprender
  • Como criar uma constante com const e o erro real de quem tenta mudá-la.
  • O que constexpr tem de diferente: o valor é calculado na compilação.
  • Como conferir contas na compilação com static_assert.
  • O que é #define, por que ele engana e por que o C++ moderno o evita.
  • Quando usar cada um.

Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.


A ideia: a caixa lacrada

Lembre da caixa com etiqueta da aula Variáveis. Uma constante é uma caixa que você lacra com fita logo depois de pôr o valor dentro. Você ainda pode olhar o que tem lá (ler o valor) quantas vezes quiser, mas não pode trocar.

Para que trancar? Por três motivos:

  • Proteção contra engano: se, por descuido, uma linha tentar mudar o preço, o compilador avisa antes de o programa existir.
  • Nome em vez de "número mágico": dias * horasPorDia se explica sozinho; dias * 8 obriga o leitor a adivinhar o que é o 8.
  • Um lugar só para mudar: se o preço do caderno aumentar, você muda uma linha, e não cada conta do programa.

1. const: "esta variável não muda"

Basta escrever const (de constant, "constante") antes do tipo:

#include <iostream>

int main() {
    const int diasDaSemana = 7;
    const double precoCaderno = 12.5;
    std::cout << "Uma semana tem " << diasDaSemana << " dias.\n";
    std::cout << "3 cadernos custam " << 3 * precoCaderno << "\n";
}
const
A "fita adesiva": depois desta linha, o valor fica fixo.
int diasDaSemana = 7
Uma declaração comum, exatamente como você já conhece.
3 * precoCaderno
Ler uma constante é igual a ler uma variável. O * é "vezes" (aula Operadores Aritméticos).
$ g++ -std=c++23 -Wall -Wextra const.cpp -o const
$ ./const
Uma semana tem 7 dias.
3 cadernos custam 37.5

Tentar mudar uma constante

#include <iostream>

int main() {
    const int diasDaSemana = 7;
    diasDaSemana = 8;
    std::cout << diasDaSemana << "\n";
}
$ g++ -std=c++23 -Wall -Wextra mudar.cpp -o mudar
mudar.cpp: In function ‘int main()’:
mudar.cpp:5:18: error: assignment of read-only variable ‘diasDaSemana’
    5 |     diasDaSemana = 8;
      |     ~~~~~~~~~~~~~^~~
assignment
"Atribuição": a operação = de guardar um valor.
of read-only variable
"De uma variável somente leitura": você pode ler, não pode escrever.
‘diasDaSemana’
Qual constante você tentou mudar. O ^ aponta para o = culpado, na linha 5.

É um error: o programa não foi gerado. Essa é a graça da constante: o engano é pego antes de o programa rodar, e não depois, por um cliente reclamando do preço. No editor do guia, o Clang diz o mesmo com outras palavras: cannot assign to variable 'diasDaSemana' with const-qualified type 'const int' ("não é possível atribuir a uma variável de tipo const").

Missão: provoque o erro. Rode o programa como está (funciona). Depois acrescente, antes do std::cout, uma linha que tente mudar as vidas do jogo para 5, e rode de novo: o compilador tem que recusar.

#include <iostream>

int main() {
    const int vidasIniciais = 3;
    std::cout << "Você começa com " << vidasIniciais << " vidas.\n";
}
#include <iostream>

int main() {
    const int vidasIniciais = 3;
    vidasIniciais = 5;
    std::cout << "Você começa com " << vidasIniciais << " vidas.\n";
}

Uma constante precisa nascer com valor

Uma caixa lacrada vazia nunca poderia ser preenchida. Por isso o C++ exige o valor na declaração:

$ cat semvalor.cpp
int main() {
    const int limite;
}
$ g++ -std=c++23 -Wall -Wextra semvalor.cpp -o semvalor
semvalor.cpp: In function ‘int main()’:
semvalor.cpp:2:15: error: uninitialized ‘const limite’ [-fpermissive]
    2 |     const int limite;
      |               ^~~~~~
semvalor.cpp:2:15: warning: unused variable ‘limite’ [-Wunused-variable]

Uninitialized ‘const limite’ = "constante limite sem valor inicial". O aviso de baixo (unused variable) só diz que ela também não foi usada.

O valor de um const pode vir de fora

O const não exige que você saiba o valor ao escrever o programa. Ele pode ser calculado com algo que o usuário digitou; o que ele garante é que, depois de calculado, não muda mais. (O std::cin lê do teclado; aula Entrada com cin.)

#include <iostream>

int main() {
    int idade = 0;
    std::cout << "Sua idade: ";
    std::cin >> idade;
    const int anoNascimento = 2026 - idade;
    std::cout << "Você nasceu em " << anoNascimento << " (ou no ano anterior).\n";
}
$ g++ -std=c++23 -Wall -Wextra runtime.cpp -o runtime
$ ./runtime
Sua idade: 29                « a Ana digitou 29 e apertou Enter
Você nasceu em 1997 (ou no ano anterior).

Guarde essa ideia: o const é "não muda depois de pronto". A próxima seção mostra uma constante ainda mais exigente.


2. constexpr: calculado na compilação C++11

Algumas contas não dependem de nada que aconteça quando o programa roda: quantos segundos tem um dia é sempre 24 * 60 * 60. Para esses casos existe constexpr (de constant expression, "expressão constante"). Ele diz ao compilador: "faça esta conta agora, enquanto compila". O programa final já nasce com o resultado pronto.

#include <iostream>

int main() {
    constexpr int segundosPorMinuto = 60;
    constexpr int segundosPorDia = 24 * 60 * segundosPorMinuto;
    constexpr int segundosPorAno = 365 * segundosPorDia;
    std::cout << "Um dia tem " << segundosPorDia << " segundos.\n";
    std::cout << "Um ano tem " << segundosPorAno << " segundos.\n";
}
$ g++ -std=c++23 -Wall -Wextra constexpr.cpp -o constexpr
$ ./constexpr
Um dia tem 86400 segundos.
Um ano tem 31536000 segundos.
constexpr
Constante e conhecida na compilação. Todo constexpr também é const: não pode ser mudado.
24 * 60 * segundosPorMinuto
Uma conta feita só com números escritos no código e com outros constexpr. Tudo isso o compilador conhece, então ele mesmo calcula.

Quando o compilador não consegue calcular

Se a conta depende de algo que só existe com o programa rodando (como o que o usuário digita), constexpr é recusado:

#include <iostream>

int main() {
    int idade = 0;
    std::cin >> idade;
    constexpr int anoNascimento = 2026 - idade;
    std::cout << anoNascimento << "\n";
}
$ g++ -std=c++23 -Wall -Wextra constexpr-erro.cpp -o constexpr-erro
constexpr-erro.cpp: In function ‘int main()’:
constexpr-erro.cpp:6:42: error: the value of ‘idade’ is not usable in a constant expression
    6 |     constexpr int anoNascimento = 2026 - idade;
      |                                          ^~~~~
constexpr-erro.cpp:4:9: note: ‘int idade’ is not const
    4 |     int idade = 0;
      |         ^~~~~

The value of ‘idade’ is not usable in a constant expression = "o valor de idade não pode ser usado numa expressão constante": o compilador não sabe quanto vale idade, porque ela vai ser digitada depois. A note explica: idade não é constante. Aqui, o certo é usar const, como na seção 1.

constconstexpr
Pode mudar depois?NãoNão
Quando o valor é conhecidoNa compilação ou com o programa rodandoObrigatoriamente na compilação
Valor que vem do tecladoPodeErro: not usable in a constant expression
Frase para lembrar"não vou mudar isto""isto já se sabe antes de rodar"

Conferir contas na compilação: static_assert

Como o compilador conhece os valores constexpr, ele pode conferi-los para você. O static_assert ("afirmação estática", isto é, feita na compilação) recebe uma condição: se ela for verdadeira, nada acontece; se for falsa, a compilação falha com a sua mensagem.

int main() {
    constexpr int segundosPorDia = 24 * 60 * 60;
    static_assert(segundosPorDia == 86400);
    static_assert(segundosPorDia == 86000, "a conta do dia está errada");
}
$ g++ -std=c++23 -Wall -Wextra assert.cpp -o assert
assert.cpp: In function ‘int main()’:
assert.cpp:4:34: error: static assertion failed: a conta do dia está errada
    4 |     static_assert(segundosPorDia == 86000, "a conta do dia está errada");
      |                   ~~~~~~~~~~~~~~~^~~~~~~~
assert.cpp:4:34: note: the comparison reduces to ‘(86400 == 86000)’
  • A linha 3 passou em silêncio: 86400 é mesmo igual a 86400.
  • A linha 4 falhou: static assertion failed ("afirmação estática falhou") seguida da sua mensagem. A note mostra a conta que o compilador fez: (86400 == 86000), falso. (O == compara dois valores; aula Operadores de Comparação.)

Essa é a prova de que o valor existe na compilação: o static_assert só aceita valores que o compilador conhece. Com uma variável comum, ele recusa:

$ cat assert2.cpp
int main() {
    int segundosPorDia = 24 * 60 * 60;
    static_assert(segundosPorDia == 86400);
}
$ g++ -std=c++23 -Wall -Wextra assert2.cpp -o assert2
assert2.cpp: In function ‘int main()’:
assert2.cpp:3:34: error: non-constant condition for static assertion
    3 |     static_assert(segundosPorDia == 86400);
      |                   ~~~~~~~~~~~~~~~^~~~~~~~
assert2.cpp:3:19: error: the value of ‘segundosPorDia’ is not usable in a constant expression
    3 |     static_assert(segundosPorDia == 86400);
      |                   ^~~~~~~~~~~~~~
assert2.cpp:2:9: note: ‘int segundosPorDia’ is not const
    2 |     int segundosPorDia = 24 * 60 * 60;
      |         ^~~~~~~~~~~~~~
Um detalhe histórico

Por uma regra antiga do C++, um const int com valor escrito no código (const int segundosPorDia = 24 * 60 * 60;) também é aceito pelo static_assert; conferido no g++ 15. Mas isso vale só para inteiros e confunde quem lê. Quando você quer um valor de compilação, escreva constexpr: a intenção fica clara e o compilador cobra.

constexpr vai muito além de variáveis: funções inteiras podem rodar na compilação. Isso é assunto do Nível 3, em constexpr e consteval.

Experimente: calcule quantos minutos tem uma semana usando constexpr e mostre o resultado. Depois acrescente static_assert(minutosPorSemana == 10080); e rode. Troque o 10080 por outro número e veja a compilação falhar.

#include <iostream>

int main() {
    constexpr int minutosPorHora = 60;
    constexpr int horasPorDia = 24;
    constexpr int minutosPorDia = minutosPorHora * horasPorDia;
    std::cout << "Minutos por dia: " << minutosPorDia << "\n";
}

3. #define: a forma antiga

Antes do const existir (na linguagem C dos anos 1970), constantes eram feitas com #define. Você vai encontrá-lo em muito código antigo e em bibliotecas em C:

#define PRECO_CADERNO 12.5

Repare: não tem tipo, não tem = e não tem ;. Isso porque #define não é C++ de verdade: é uma ordem para o pré-processador, um programa que passa pelo arquivo antes do compilador e faz um "localizar e substituir" de texto. Toda vez que aparecer PRECO_CADERNO, ele cola 12.5 no lugar, sem entender nada de C++. (O pré-processador tem aula própria: O Pré-processador. O #include é outra ordem dele.)

Esse "copiar e colar cego" causa surpresas. Veja a taxa de entrega da lojinha:

#include <iostream>

#define TAXA_ENTREGA 10 + 5

int main() {
    int dobro = TAXA_ENTREGA * 2;
    std::cout << "Dobro da taxa: " << dobro << "\n";
}
$ g++ -std=c++23 -Wall -Wextra define.cpp -o define
$ ./define
Dobro da taxa: 20                « a taxa é 15; o dobro deveria ser 30

Nenhum erro, nenhum aviso, resposta errada. O g++ -E mostra o arquivo depois do pré-processador (só o fim, com o tail):

$ g++ -E define.cpp | tail -5
# 5 "define.cpp"
int main() {
    int dobro = 10 + 5 * 2;
    std::cout << "Dobro da taxa: " << dobro << "\n";
}

O TAXA_ENTREGA virou o texto 10 + 5, e a conta ficou 10 + 5 * 2. Como a multiplicação vem antes da soma (aula Precedência), o resultado é 10 + 10 = 20. Com constexpr int taxaEntrega = 10 + 5;, a constante vale 15 de verdade, e o dobro é 30.

Outras pegadinhas do #define

Um ; a mais vira parte do texto colado:

$ cat define2.cpp
#include <iostream>

#define LIMITE 100;

int main() {
    int maximo = LIMITE + 1;
    std::cout << maximo << "\n";
}
$ g++ -std=c++23 -Wall -Wextra define2.cpp -o define2
define2.cpp: In function ‘int main()’:
define2.cpp:6:25: warning: statement has no effect [-Wunused-value]
    6 |     int maximo = LIMITE + 1;
      |                         ^~~
$ ./define2
100                              « deveria ser 101

A linha virou int maximo = 100; + 1;: o ; colado terminou a declaração, e o + 1; sobrou como uma instrução que não faz nada (statement has no effect, "a instrução não tem efeito").

Mensagens de erro confusas, porque o compilador nunca viu o nome, só o texto colado:

$ cat define3.cpp
#include <iostream>

#define PRECO 9.99

int main() {
    std::cout << "Preço: " << PRECO << "\n";
    PRECO = 12.5;
}
$ g++ -std=c++23 -Wall -Wextra define3.cpp -o define3
define3.cpp: In function ‘int main()’:
define3.cpp:3:15: error: lvalue required as left operand of assignment
    3 | #define PRECO 9.99
      |               ^~~~
define3.cpp:7:5: note: in expansion of macro ‘PRECO’
    7 |     PRECO = 12.5;
      |     ^~~~~

Compare com o const: lá a mensagem dizia "variável somente leitura ‘diasDaSemana’". Aqui, o compilador viu 9.99 = 12.5; e reclamou que o lado esquerdo do = precisa ser algo que guarda valor (lvalue required), apontando primeiro para a linha do #define. A note "in expansion of macro" ("na expansão da macro") é a pista de que um #define está envolvido. (Um nome criado com #define se chama macro.)

Por que o C++ moderno evita #define para constantes
  • Não tem tipo: o compilador não sabe se é inteiro, texto ou outra coisa.
  • Copia texto, não valor: 10 + 5 continua sendo 10 + 5 no meio da conta.
  • Não respeita escopo: vale do #define até o fim do arquivo, inclusive em outros arquivos que o incluírem, e pode trocar nomes que você nem imaginava.
  • Erros piores: as mensagens falam do texto colado, não do nome.

Por tradição, macros são escritas em MAIUSCULAS, justamente para avisar o leitor "cuidado, isto é substituição de texto". O #define ainda tem usos legítimos (veja O Pré-processador), mas não para constantes.


4. Qual usar

SituaçãoUseExemplo
Valor conhecido ao escrever o programa (números fixos, contas com eles)constexprconstexpr int diasDaSemana = 7;
Valor que só existe com o programa rodando, mas não deve mudar depoisconstconst int anoNascimento = 2026 - idade;
Texto fixo com std::stringconstconst std::string loja = "Lojinha da Ana";
Valor que muda durante o programavariável comumint estoque = 40;
Constante em código novonunca #define(só para entender código antigo)

Por que const com std::string? Um std::string guarda o texto numa área de memória que só existe com o programa rodando, então, para guardar um texto constexpr, o C++ usa outra ferramenta, o std::string_view (aula string_view). Se você tentar constexpr std::string loja = "Lojinha da Ana";, o g++ 15 recusa com uma mensagem enorme, cheia de detalhes internos da biblioteca, que termina em is not a constant expression ("não é uma expressão constante"). Por enquanto, const std::string resolve:

$ cat string.cpp
#include <iostream>
#include <string>

int main() {
    const std::string loja = "Lojinha da Ana";
    std::cout << loja << "\n";
}
$ g++ -std=c++23 -Wall -Wextra string.cpp -o string
$ ./string
Lojinha da Ana
Regra de bolso

Comece toda constante com constexpr. Se o compilador reclamar de not usable in a constant expression, é porque o valor só existe com o programa rodando: troque por const. E qualquer variável que você não pretende mudar pode ganhar um const, que custa nada e evita enganos.

E o nome? Neste guia, constantes seguem o mesmo camelCase das variáveis (diasDaSemana), como faz boa parte da comunidade; MAIUSCULAS fica para as macros. Alguns projetos usam um k na frente (kDiasDaSemana). Veja Nomes de Variáveis.


Quando dá errado

error: assignment of read-only variable ‘x’

O que significa: uma linha tenta mudar uma constante (const ou constexpr).

Como consertar: se o valor realmente precisa mudar, tire o const da declaração. Se não precisa, a linha que tenta mudar é o erro: apague-a ou use outra variável.

error: uninitialized ‘const x’

O que significa: a constante foi declarada sem valor.

Como consertar: dê o valor na mesma linha: const int limite = 100;.

error: the value of ‘x’ is not usable in a constant expression

O que significa: um constexpr (ou um static_assert) depende de um valor que só existe com o programa rodando.

Como consertar: use const em vez de constexpr; ou, se o valor era mesmo fixo, transforme também a variável de origem em constexpr.

A conta com #define dá um resultado estranho

O que significa: o pré-processador colou o texto da macro no meio da conta, e a precedência dos operadores mudou o resultado (ou um ; colado cortou a linha).

Como consertar: troque o #define por constexpr. Para ver o que o pré-processador fez, use g++ -E arquivo.cpp.

error: lvalue required as left operand of assignment + note: in expansion of macro

O que significa: uma linha tenta mudar o valor de uma macro. Depois da substituição, sobrou algo como 9.99 = 12.5;.

Como consertar: macros não guardam valor. Se precisa mudar, use uma variável comum.


Referência rápida

EscritaO que faz
const int x = 10;Constante: o valor não muda depois desta linha
const int x = conta;Pode ser calculada com valores do programa rodando
constexpr int x = 24 * 60;Constante calculada na compilação
static_assert(condição, "msg");Confere a condição na compilação; falsa = erro com a mensagem
#define NOME valorMacro: substituição de texto antes da compilação (evite para constantes)
g++ -E arquivo.cppMostra o arquivo depois do pré-processador

Exercícios

1. Lacrar a caixa

Complete para que limite não possa mais mudar:

int limite = 100;

2. Calcular na compilação

Complete para que a conta seja feita pelo compilador:

int horasPorSemana = 7 * 24;

3. Qual é o erro do g++ para const int x = 1; x = 2;?

4. A idade vem do teclado. Qual declaração compila?

5. Com #define TAXA 2 + 3, quanto vale TAXA * 10?

Missão final: aposente os #define

Missão: a lojinha da Ana abre 6 dias por semana, das 9h às 18h (9 horas por dia: 8 + 1). O programa diz 49 horas, mas o certo é 54. Troque os dois #define por constantes constexpr int (dentro do main, com nomes em camelCase), apague os #define e confira a saída Horas por semana: 54.

#include <iostream>

#define DIAS_ABERTA 6
#define HORAS_POR_DIA 8 + 1

int main() {
    int horasSemana = DIAS_ABERTA * HORAS_POR_DIA;
    std::cout << "Horas por semana: " << horasSemana << "\n";
}
#include <iostream>

int main() {
    constexpr int diasAberta = 6;
    constexpr int horasPorDia = 8 + 1;
    constexpr int horasSemana = diasAberta * horasPorDia;
    std::cout << "Horas por semana: " << horasSemana << "\n";
}

Resumo

  • Constante = caixa lacrada: lê-se à vontade, não se muda. Mudar dá assignment of read-only variable.
  • const: "não muda depois de pronto". O valor pode vir do programa rodando. Precisa nascer com valor.
  • constexpr: o valor é calculado na compilação. Se depender de algo que só existe rodando, dá not usable in a constant expression.
  • static_assert(condição, "mensagem") confere contas na compilação.
  • #define é substituição cega de texto feita pelo pré-processador: sem tipo, sem escopo, com surpresas (10 + 5 virou 20 ao dobrar). Não use para constantes.
  • Regra de bolso: comece com constexpr; se não der, const.