Ir para o conteúdo

Última atualização:2026-10-12

Constantes e modificadores com const em C++

Sobre o autor

Conta oficial do WeChat:

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
const int a=100;
  • 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

void f(const int i){
    i++; // error!
}
  • 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 #define tem várias cópias na memória.

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 ponteiro recebe a característica const, o ponteiro não pode mudar o endereço para o qual aponta
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

const int *ptr;
*ptr = 10; // error

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.

const int p = 10;
const void * vp = &p;
void *vp = &p; // error

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:

const int *ptr;
int val = 3;
ptr = &val; // ok

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:

int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;

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=&num; // 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=&num; // 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=&num; // 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:

const int p = 3;
const int * const ptr = &p; 

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

const int func1();

Isso em si não tem sentido, porque o retorno do parâmetro já é atribuído a outras variáveis!

(2)const int*

const int* func2();

O conteúdo apontado pelo ponteiro é imutável.

(3)int *const

int *const func2();

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!

void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变

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

void StringCopy(char *dst, const char *src);

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.

void func(const A &a)

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:

const Apple b(3);
b.add(); // error
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 é:

static const int apple_number

Segundo: inicializar fora da classe:

const int Apple::apple_number=10;

Naturalmente, se você compilar com C++11, é possível inicializar diretamente na declaração, escrevendo diretamente:

static const int apple_number=10;
// 或者
const int apple_number=10;

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:

static int ap;

Usar no arquivo de implementação da classe:

int Apple::ap=666

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

Comentários