Última atualização:2026-10-12
Constantes e modificadores com const em C++¶
Sobre o autor¶
Conta oficial do WeChat:

1.Significado de const¶
Tipos constantes são tipos descritos com o modificador de tipo const; o valor de variáveis ou objetos de tipo constante não pode ser atualizado.
2.Funções de const¶
- Podem definir constantes
-
Verificação de tipos
-
Diferença entre constantes const e constantes definidas com a macro
#define:Constantes const têm tipo, e o compilador pode fazer verificação de segurança; constantes definidas com #define não têm tipo de dados, são apenas simples substituições de string, sem verificação de segurança possível.Obrigado aos dois colegas que apontaram o problema aqui, veja: issue -
Constantes const suportam todos os tipos.
- Nos demais casos, é apenas uma variável qualificada como
const; não a confunda com uma constante.
-
-
Evita modificações, exerce papel de proteção e aumenta a robustez do programa
-
Economiza espaço, evitando alocações de memória desnecessárias
- Do ponto de vista do assembly, uma constante definida com const apenas fornece o endereço de memória correspondente, e não um valor imediato como o
#define. - Uma constante definida com const tem apenas uma cópia durante a execução do programa, enquanto uma constante definida com
#definetem várias cópias na memória.
- Do ponto de vista do assembly, uma constante definida com const apenas fornece o endereço de memória correspondente, e não um valor imediato como o
3.Objetos const são, por padrão, locais ao arquivo¶
Nota: variáveis não const são, por padrão, extern. Para que uma variável const possa ser acessada em outros arquivos, é necessário especificá-la explicitamente como extern no arquivo.
Acesso a variáveis não modificadas com const em arquivos diferentes
// file1.cpp
int ext;
// file2.cpp
#include<iostream>
extern int ext;
int main(){
std::cout<<(ext+10)<<std::endl;
}
Acesso a constantes const em arquivos diferentes
// extern_file1.cpp
extern const int ext=12;
// extern_file2.cpp
#include<iostream>
extern const int ext;
int main(){
std::cout<<ext<<std::endl;
}
Resumo:
Percebe-se que variáveis não modificadas com const não precisam de declaração explícita de extern! Já as constantes const precisam da declaração explícita de extern e precisam ser inicializadas! Como uma constante não pode ser modificada depois de definida, ela deve ser inicializada na definição.
4.Definindo constantes¶
const int b = 10;
b = 0; // error: assignment of read-only variable ‘b’
const string s = "helloworld";
const int i,j=0 // error: uninitialized const ‘i’
Há dois erros acima: + b é uma constante, não pode ser alterada! + i é uma constante, deve ser inicializada! (Como uma constante não pode ser modificada depois de definida, ela deve ser inicializada na definição.)
5.Ponteiros e const¶
Há quatro formas de const relacionadas a ponteiros:
const char * a; // 指向const对象的指针或者说指向常量的指针。
char const * a; // 同上
char * const a; // 指向类型对象的const指针。或者说常指针、const指针。
const char * const a; // 指向const对象的const指针。
Resumo:
Se const está à esquerda do*, então o const modifica a variável apontada pelo ponteiro, ou seja, o alvo do ponteiro é constante;
se const está à direita do*, const modifica o próprio ponteiro, ou seja, o ponteiro em si é constante.
Outra forma de interpretar
Leia em inglês da direita para a esquerda, usando o to como divisor: antes do to descreve-se a característica do ponteiro, depois do to descreve-se a característica do alvo
const char * p; //p is a pointer to const char
char const * p; //同上
char * const p; //p is a const pointer to char
const char * const p; //p is a const pointer to const char
Quando o alvo apontado tem característica char, o conteúdo pode ser modificado por meio do ponteiro, como: *char='y';
Quando o alvo apontado tem característica const char, o conteúdo não pode ser modificado por meio do ponteiro
Uso concreto a seguir:
(1) Ponteiro para constante
ptr é um ponteiro para um objeto const do tipo int; o const define o tipo int, ou seja, o tipo do objeto apontado por ptr, e não o ptr em si; por isso, ptr pode ficar sem valor inicial. Mas não é possível modificar o valor do objeto apontado por meio de ptr.
Além disso, tampouco é possível usar um ponteiro void* para guardar o endereço de um objeto const; é preciso usar um ponteiro do tipo const void* para guardar o endereço de um objeto const.
Outro ponto importante: é permitido atribuir o endereço de um objeto não const a um ponteiro para objeto const.
Atribuindo o endereço de um objeto não const a um ponteiro para objeto const:
Não podemos modificar o valor de val por meio do ponteiro ptr, mesmo ele apontando para um objeto não const!
Não podemos usar um ponteiro para objeto const para modificar o objeto de base; no entanto, se esse ponteiro apontar para um objeto não const, o objeto apontado pode ser modificado por outros meios. O valor apontado por um ponteiro const pode ser modificado — só não diretamente por meio do ponteiro para objeto const! Modificação:
Resumo:
1. Com ponteiros para constantes, não é possível modificar o valor do objeto por meio do ponteiro.
2. Não é possível usar um ponteiro void*para guardar o endereço de um objeto const; é preciso usar um ponteiro do tipo const void*para guardar o endereço de um objeto const.
3. É permitido atribuir o endereço de um objeto não const a um ponteiro para objeto const; para modificar o valor do objeto apontado pelo ponteiro, é preciso fazê-lo por outro meio, e não diretamente pelo ponteiro atual.
(2) Ponteiro constante
Um ponteiro const deve ser inicializado, e o valor apontado por um ponteiro const pode ser modificado, mas o alvo apontado não.
#include<iostream>
using namespace std;
int main(){
int num=0, num1=1;
int * const ptr=# // const指针必须初始化!且const指针的指向不能修改
ptr = &num1; // error! const指针不能修改指向!
cout<<*ptr<<endl;
}
O código gera um erro de compilação: um ponteiro const não pode mudar o alvo apontado.
#include<iostream>
using namespace std;
int main(){
int num=0, num1=1;
int * const ptr=# // const指针必须初始化!且const指针的指向不能修改
*ptr = 1;
cout<<*ptr<<endl;
}
No código nada de errado acontece, e ele normalmente imprime 1.
Por fim, quando se atribui o endereço de uma constante const ao ptr, como ptr aponta para uma variável, e não para uma constante const, ocorre um erro, aparecendo o erro const int* -> int *!
#include<iostream>
using namespace std;
int main(){
const int num=0;
int * const ptr=# // error! const int* -> int*
cout<<*ptr<<endl;
}
Se o acima for alterado para const int *ptr ou para const int *const ptr, ambos funcionam normalmente!
(3)Ponteiro constante para constante
Entendidos os dois primeiros casos, o caso a seguir fica mais fácil de entender:
ptr é um ponteiro const que aponta para um objeto const do tipo int.
6.const em funções¶
const modificando o valor de retorno da função
Isso é basicamente igual ao significado de const modificando variáveis comuns e ponteiros:
(1)const int
Isso em si não tem sentido, porque o retorno do parâmetro já é atribuído a outras variáveis!
(2)const int*
O conteúdo apontado pelo ponteiro é imutável.
(3)int *const
O próprio ponteiro é imutável.
const modificando parâmetros de função
(1) Os parâmetros passados e o próprio ponteiro são imutáveis dentro da função — sem sentido!
Indica que o parâmetro não pode ser modificado no corpo da função, mas aqui isso não tem qualquer sentido: var é, ele próprio, um parâmetro formal, que não muda dentro da função. O mesmo vale para parâmetros formais passados como ponteiro.
Quando o parâmetro de entrada usa "passagem por valor", como a função gera automaticamente uma variável temporária para copiar o parâmetro, esse parâmetro de entrada já não precisa de proteção; portanto, não o modifique com const.
(2)O conteúdo apontado pelo parâmetro ponteiro é constante e imutável
Aqui src é o parâmetro de entrada e dst é o parâmetro de saída. Com src modificado por const, se alguma instrução do corpo da função tentar alterar o conteúdo de src, o compilador apontará o erro. Este é um dos efeitos de adicionar const.
(3)Parâmetro por referência, para aumentar a eficiência e, ao mesmo tempo, evitar modificações.
Para parâmetros de tipos de dados não internos, uma função declarada como void func(A a) tem, por natureza, eficiência baixa, porque o corpo da função gerará um objeto temporário do tipo A para copiar o parâmetro a, e a construção, a cópia e a destruição do objeto temporário consomem tempo.
Para melhorar a eficiência, a declaração da função pode ser alterada para void func(A &a), pois a "passagem por referência" apenas toma emprestado o alias do parâmetro, sem necessidade de gerar um objeto temporário.
Mas a função void func(A &a) tem uma desvantagem:
a "passagem por referência" pode alterar o parâmetro a, o que não esperamos. Resolver isso é fácil: basta adicionar const, e a função se torna finalmente void func(const A &a).
Por analogia, devemos reescrever void func(int x) como void func(const int &x) para melhorar a eficiência? Não há a menor necessidade: parâmetros de tipos de dados internos não passam por construção e destruição, e a cópia é muito rápida; a eficiência da "passagem por valor" e da "passagem por referência" é praticamente a mesma.
Resumo:
1. Para parâmetros de entrada de tipos de dados não internos, a "passagem por valor" deve ser substituída por "passagem por referência const", com o objetivo de melhorar a eficiência. Por exemplo, void func(A a) passa a ser void func(const A &a).
2. Para parâmetros de entrada de tipos de dados internos, não substitua a "passagem por valor" por "passagem por referência const"; caso contrário, além de não melhorar a eficiência, reduz-se a compreensibilidade da função. Por exemplo, void func(int x) não deveria ser alterada para void func(const int &x).
Com isso, respondemos a duas perguntas de entrevista:
- Se a função precisa receber um ponteiro, é preciso adicionar const a esse ponteiro? Qual a diferença entre colocar const em posições diferentes do ponteiro?
- Se a função que você escreveu precisa receber como parâmetro uma instância de tipo complexo, qual a diferença entre passar o parâmetro por valor ou por referência, e quando é preciso adicionar const ao parâmetro de referência recebido?
7.const em classes¶
Em uma classe, toda função que não modifica membros de dados deveria ser declarada como const. Se, ao escrever uma função membro const, você modificar por descuido um membro de dados, ou chamar outra função membro não const, o compilador apontará o erro — o que, sem dúvida, aumenta a robustez do programa.
Funções membro descritas com a palavra-chave const são chamadas de funções membro constantes. Somente funções membro constantes têm legitimidade para operar constantes ou objetos constantes; funções membro descritas sem a palavra-chave const não podem ser usadas para operar objetos constantes.
As variáveis membro const de uma classe devem ser inicializadas por meio da lista de inicialização, como mostrado abaixo:
class Apple{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
};
Apple::Apple(int i):apple_number(i)
{
}
Objetos const só podem acessar funções membro const, enquanto objetos não const podem acessar qualquer função membro, incluindo as funções membro const.
Por exemplo:
// apple.cpp
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
void take(int num) const;
int add();
int add(int num) const;
int getCount() const;
};
// apple.cpp
Apple::Apple(int i) : apple_number(i)
{
}
int Apple::add(int num)
{
take(num);
return 0;
}
int Apple::add(int num) const
{
take(num);
return 0;
}
void Apple::take(int num) const
{
std::cout << "take func " << num << std::endl;
}
int Apple::getCount() const
{
take(1);
// add(); // error
return apple_number;
}
int main()
{
Apple a(2);
cout << a.getCount() << endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}
// main.cpp
Compilação: bazel run basic_content/const/class_const/first_example:main
Nesse momento ocorre um erro: o método getCount() acima chama um método add, e o método add não é modificado por const, portanto a execução falha. Ou seja, funções membro const só podem acessar funções membro const.
Quando a chamada passa a ser:
Nesse caso, fica demonstrado que objetos const só podem acessar funções membro const.Além da inicialização de constantes const por lista de inicialização vista acima, também é possível usar os métodos a seguir:
Primeiro: combinar a definição da constante com static, isto é:
Segundo: inicializar fora da classe:
Naturalmente, se você compilar com C++11, é possível inicializar diretamente na declaração, escrevendo diretamente:
Ambas as formas são suportadas no C++11!
Basta adicionar -std=c++11 ao compilar!
Já que falamos de static, uma breve nota a seguir:
Em C++, variáveis membro estáticas static não podem ser inicializadas dentro da classe. Na classe há apenas a declaração; a definição deve ficar fora do corpo da definição da classe, normalmente no arquivo de implementação da classe.
Declarar na classe:
Usar no arquivo de implementação da classe:
Quanto a este item, o C++11 não permite declarar e inicializar ao mesmo tempo, ou seja, o método de uso descrito acima.
Tópicos relacionados¶
- Funções virtuais puras e classes abstratas em C++ | C++那些事
- Asserções com assert em C++ | C++那些事
- Bit fields em C++ | C++那些事
- Implementando polimorfismo de C++ em linguagem C | C++那些事
- A família completa do const em C++ | C++那些事
- O operador de desreferência * em C++ | C++那些事
- Inicialização e atribuição em C++ | C++那些事
- Formas de retornar múltiplos valores em C++ | C++那些事
- A palavra-chave mutable em C++ | C++那些事
- C++ restrict e otimização do compilador | C++那些事