Última atualização:2026-10-13
const em C++: constantes e os dois tipos de ponteiro const¶
Sobre o autor¶
Conta pública no WeChat:

1. O significado de const¶
Um tipo const é um tipo declarado com o qualificador de tipo const; o valor de uma variável ou objeto de tipo const não pode ser atualizado.
2. O papel de const¶
- Pode definir constantes
-
Verificação de tipos
-
A diferença entre constantes const e constantes definidas com a macro
#define:Uma constante const tem um tipo, e o compilador pode fazer verificação de segurança; a definição de macro #define não tem tipo de dados, é uma simples substituição de strings e não permite verificação de segurança.Agradecemos aos dois especialistas que apontaram o problema aqui; veja: issue -
Constantes const suportam todos os tipos.
- Nos demais casos, é apenas uma variável qualificada com
const; não a confunda com uma constante.
-
-
Evitar modificação, exercer papel de proteção e aumentar a robustez do programa
-
Economizar espaço e evitar alocações de memória desnecessárias
- Um const inicializado com um tipo inteiro/enum e uma expressão constante (ou seja, uma expressão constante) comporta-se, em uso, bem mais como uma constante de tempo de compilação: se você nunca obtém o endereço dele e usa apenas o valor, ele normalmente é otimizado para um imediato, tal como
#define; só ao obter o endereço ou fazer um odr-use entre unidades de tradução ele vai para a memória, e existe apenas uma cópia em todo o programa. - Depois que
#definese expande para um literal, ele é compilado nas instruções como um imediato; as próprias instruções também ocupam memória, então vários usos significam várias ocorrências (veja a discussão no issue #5). - Nota: nem toda variável const é uma expressão constante — só o são as de tipo inteiro/enum inicializadas com uma expressão constante; as demais variáveis const são apenas variáveis qualificadas com const (por exemplo, ponto flutuante, tipos definidos pelo usuário);
constexpré a forma correta de expressar constantes de tempo de compilação.
- Um const inicializado com um tipo inteiro/enum e uma expressão constante (ou seja, uma expressão constante) comporta-se, em uso, bem mais como uma constante de tempo de compilação: se você nunca obtém o endereço dele e usa apenas o valor, ele normalmente é otimizado para um imediato, tal como
3. Objetos const são locais ao arquivo por padrão¶
Nota: variáveis não const são extern por padrão. Para que uma variável const possa ser acessada em outros arquivos, é preciso especificá-la explicitamente como extern no arquivo.
Acesso a uma variável não modificada 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 uma constante 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 extern explícita! Já a constante const exige declaração extern explícita e, além disso, precisa ser inicializada! Como uma constante não pode ser modificada depois de definida, ela deve ser inicializada no momento da 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’
O código acima contém dois erros: + b é uma constante, não pode ser alterada! + i é uma constante, deve ser inicializada! (Porque uma constante não pode ser modificada depois de definida, ela deve ser inicializada no momento da 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 de*, então const serve para qualificar a variável apontada pelo ponteiro, ou seja, o apontado é uma constante;
se const está à direita de*, const qualifica o próprio ponteiro, ou seja, o ponteiro em si é uma constante.
Outra forma de interpretar
Leia o inglês da direita para a esquerda, tomando "to" como divisor: o que vem antes de "to" descreve a característica do ponteiro, e o que vem depois descreve 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 a característica do alvo é char, o conteúdo pode ser modificado através do ponteiro, por exemplo: *char='y';
Quando a característica do alvo é const char, o conteúdo não pode ser modificado através do ponteiro
O uso concreto é o seguinte:
(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 que ptr aponta, e não o ptr em si, por isso ptr não precisa de valor inicial. Mas não se pode modificar o valor do objeto apontado através de ptr.
Além disso, também não se pode 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 através do ponteiro ptr, mesmo que ele aponte para um objeto não const!
Não podemos usar um ponteiro para objeto const para modificar o objeto subjacente; contudo, se o ponteiro aponta para um objeto não const, o objeto apontado pode ser modificado por outros meios. O valor apontado por um ponteiro const pode ser alterado — só não através do ponteiro para objeto const! Modifique assim:
Resumo:
1. Para um ponteiro para constante, não se pode modificar o valor do objeto através do ponteiro.
2. Não se pode 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, é preciso fazê-lo por outros meios, e não diretamente através do ponteiro atual.
(2) Ponteiro constante
Um ponteiro const deve ser inicializado, e o valor apontado por um ponteiro const pode ser modificado, mas o seu apontamento 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 que aponta.
#include<iostream>
using namespace std;
int main(){
int num=0, num1=1;
int * const ptr=# // const指针必须初始化!且const指针的指向不能修改
*ptr = 1;
cout<<*ptr<<endl;
}
Nada acontece com este código; ele imprime 1 normalmente.
Por fim, quando o endereço de uma constante const é atribuído a ptr, como ptr aponta para uma variável e não para uma constante const, ocorre um erro: 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 casos anteriores, este caso fica bem fácil de entender:
ptr é um ponteiro const que aponta para um objeto const do tipo int.
6. Uso de const em funções¶
const qualificando o valor de retorno de uma função
Isso tem basicamente o mesmo significado de const qualificando variáveis e ponteiros comuns:
(1) const int
Isso em si não tem sentido, porque o valor retornado já é atribuído a outra variável!
(2) const int*
O conteúdo apontado pelo ponteiro é imutável.
(3) int *const
O ponteiro em si é imutável.
const qualificando parâmetros de função
(1) O parâmetro recebido e o ponteiro em si 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 nenhum significado: var é em si um parâmetro formal e não muda dentro da função. O mesmo vale quando o parâmetro formal recebido é um ponteiro.
Quando um parâmetro de entrada usa "passagem por valor", a função gera automaticamente uma variável temporária para copiá-lo, e esse parâmetro de entrada, de origem, não precisa de proteção; portanto não adicione const.
(2) O conteúdo apontado pelo parâmetro ponteiro é uma constante imutável
Aqui src é um parâmetro de entrada e dst é um parâmetro de saída. Ao adicionar const a src, 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) O parâmetro é uma referência, para ganhar eficiência e ao mesmo tempo evitar modificação.
Para parâmetros de tipos de dados não internos, uma função declarada como void func(A a) está fadada a ser ineficiente, porque o corpo da função criará um objeto temporário do tipo A para copiar o parâmetro a, e a construção, cópia e 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 e não precisa 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 desejamos. Resolver o problema é fácil: basta adicionar const, e a função finalmente se torna void func(const A &a).
Por analogia, deveria void func(int x) ser reescrita 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 têm processo de construção/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, deve-se trocar a "passagem por valor" por "passagem por referência const", com o objetivo de melhorar a eficiência. Por exemplo, trocar void func(A a) por void func(const A &a).
2. Para parâmetros de entrada de tipos de dados internos, não troque a "passagem por valor" por "passagem por referência const". Caso contrário, você não ganha eficiência e ainda reduz a compreensibilidade da função. Por exemplo, void func(int x) não deveria ser alterada para void func(const int &x).
Com isso ficam resolvidas duas perguntas de entrevista:
- Se a função recebe um ponteiro, esse ponteiro precisa de const, e qual a diferença entre colocar const em posições diferentes do ponteiro;
- Se o parâmetro que a função recebe é uma instância de um tipo complexo, qual a diferença entre passar parâmetro por valor ou por referência, e quando é preciso adicionar const ao parâmetro de referência recebido.
7. Uso de const em classes¶
Em uma classe, qualquer função que não modifique 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.
Uma função-membro especificada com a palavra-chave const é chamada de função-membro constante. Somente funções-membro constantes têm qualificação para operar constantes ou objetos constantes; funções-membro não especificadas com a palavra-chave const não podem ser usadas para operar objetos constantes.
As variáveis-membro const de uma classe devem ser inicializadas através 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 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
Compilar: bazel run basic_content/const/class_const/first_example:main
Neste momento ocorre um erro: getCount() é uma função-membro const, e a resolução de sobrecarga do add() dentro dela só corresponde à versão const (int add(int num) const); somente depois de comentar essa versão const é que o erro decorreria da chamada ao add() não const. Ou seja: objetos const só podem acessar funções-membro const, e objetos não const podem acessar qualquer função-membro (veja a issue #338).
Quando a chamada é alterada para:
Isso prova que objetos const só podem acessar funções-membro const.
Além de inicializar constantes const com a lista de inicialização como acima, também podemos usar os métodos a seguir:
Primeiro: combinar a definição da constante com static, ou seja:
Segundo: inicializar fora:
É claro que, se você compilar com C++11, pode inicializar direto na definição e escrever simplesmente:
Ambas as formas são suportadas no C++11!
Basta adicionar -std=c++11 ao compilar!
Aqui mencionamos static; vamos falar brevemente sobre isso:
Em C++, variáveis-membro estáticas static não podem ser inicializadas dentro da classe. Dentro da classe há apenas a declaração; a definição deve ficar fora do corpo da definição da classe, normalmente a inicialização é feita no arquivo de implementação da classe.
Declarada na classe:
Usada 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, vale o uso descrito acima.
Perguntas frequentes¶
Ponteiro para const ou ponteiro const?¶
const int* p é um ponteiro para constante: o valor apontado não muda. int* const p é um ponteiro constante: o ponteiro não é reatribuído. Regra: const à esquerda de * protege o valor; à direita, o ponteiro.
Um objeto const pode chamar uma função-membro não const?¶
Não. Objetos const só chamam funções-membro const; objetos não const chamam ambas.
const ou #define para constantes?¶
Prefira const: tem verificação de tipo, respeita escopo e é depurável. #define é substituição de texto sem tipo.
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++那些事