Introdução: Por que este tópico enlouquece até mesmo desenvolvedores C++ experientes?
\n\nSe você já abriu um código C++ de outra pessoa e viu algo como const int * const data, certamente sentiu vontade de fechar o editor e ir tomar um chá. Essa sintaxe estranha com const espalhado em ambos os lados do asterisco é uma verdadeira pedra no sapato. A pergunta «What is the difference between const int*, const int * const, and int * const?» no Stack Overflow acumulou centenas de milhares de visualizações. E isso não é por acaso.
Entender essas construções não é apenas um capricho de pedante. É a chave para escrever código seguro, autodocumentado e, o que é importante, para passar em entrevistas. Hoje vamos acabar de vez com essa confusão, usando uma regra simples que funciona sempre.
\n\nOs três pilares: o que vamos comparar?
\n\nEm C++, ponteiros e constância podem se combinar de quatro maneiras (três delas são as mais populares). Vamos defini-las:
\n\n- \n
- Opção 1:
const int* ptr(ouint const* ptr— é a mesma coisa). \n - Opção 2:
int * const ptr. \n - Opção 3:
const int * const ptr(combinação das duas primeiras). \n
A ideia chave: const sempre se refere ao que está à sua esquerda. Se não houver nada à esquerda, refere-se ao que está à direita. Parece simples, mas na prática essa regra salva.
\n\nOpção 1: const int* ptr (Ponteiro para inteiro constante)
\n\nLendo da direita para a esquerda: «ptr é um ponteiro para um número inteiro que é constante».
\n\nO que isso significa na prática?
\n- \n
- Você não pode alterar o valor da variável para a qual ptr aponta, através deste ponteiro. \n
- Você pode alterar o próprio ponteiro (fazê-lo apontar para outra variável). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n const int* ptr = &a; // ptr — ponteiro para const int\n\n // *ptr = 15; // ERRO de compilação! Não é possível alterar o valor através de const int*\n std::cout << "*ptr = " << *ptr << std::endl; // Exibirá 10\n\n ptr = &b; // OK! Alteramos o próprio ponteiro\n std::cout << "*ptr = " << *ptr << std::endl; // Exibirá 20\n\n return 0;\n}\n\nEste tipo é frequentemente usado para passar dados para uma função, quando você quer garantir que a função não estrague os dados originais. Por exemplo, a string void print(const int* data) é uma promessa de "somente leitura".
Opção 2: int * const ptr (Ponteiro constante para um número inteiro)
\n\nLendo da direita para a esquerda: «ptr é um ponteiro constante para um número inteiro».
\n\nAqui é o contrário:
\n- \n
- Você pode alterar o valor da variável para a qual ptr aponta. \n
- Você não pode alterar o próprio ponteiro (ele deve sempre apontar para a mesma área de memória). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n int * const ptr = &a; // ptr — ponteiro constante para int\n\n *ptr = 15; // OK! Alteramos o valor através do ponteiro\n std::cout << "*ptr = " << *ptr << std::endl; // Exibirá 15\n\n // ptr = &b; // ERRO de compilação! Não é possível alterar o próprio ponteiro\n\n return 0;\n}\n\nImagine que int * const é um ponteiro "fixo". Ele é frequentemente usado para trabalhar com registros de hardware ou em casos onde o ponteiro para um buffer não deve "fugir" para outro lugar.
Opção 3: const int * const ptr (Ponteiro constante para um inteiro constante)
\n\nLendo da direita para a esquerda: «ptr é um ponteiro constante para um número inteiro, c