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.
- Como criar uma constante com
conste o erro real de quem tenta mudá-la. - O que
constexprtem 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 * horasPorDiase explica sozinho;dias * 8obriga 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
constexprtambé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.
const | constexpr | |
|---|---|---|
| Pode mudar depois? | Não | Não |
| Quando o valor é conhecido | Na compilação ou com o programa rodando | Obrigatoriamente na compilação |
| Valor que vem do teclado | Pode | Erro: 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;
| ^~~~~~~~~~~~~~
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.)
#define para constantes
- Não tem tipo: o compilador não sabe se é inteiro, texto ou outra coisa.
- Copia texto, não valor:
10 + 5continua sendo10 + 5no meio da conta. - Não respeita escopo: vale do
#defineaté 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ção | Use | Exemplo |
|---|---|---|
| Valor conhecido ao escrever o programa (números fixos, contas com eles) | constexpr | constexpr int diasDaSemana = 7; |
| Valor que só existe com o programa rodando, mas não deve mudar depois | const | const int anoNascimento = 2026 - idade; |
Texto fixo com std::string | const | const std::string loja = "Lojinha da Ana"; |
| Valor que muda durante o programa | variável comum | int estoque = 40; |
| Constante em código novo | nunca #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
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.
#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
| Escrita | O 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 valor | Macro: substituição de texto antes da compilação (evite para constantes) |
g++ -E arquivo.cpp | Mostra o arquivo depois do pré-processador |
Exercícios
1. Lacrar a caixa
Complete para que limite não possa mais mudar:
2. Calcular na compilação
Complete para que a conta seja feita pelo compilador:
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 + 5virou 20 ao dobrar). Não use para constantes.- Regra de bolso: comece com
constexpr; se não der,const.