Entendendo Ponteiros
Um ponteiro é uma variável cujo valor representa um endereço de memória. Em vez de armazenar diretamente um dado, ele aponta para uma localização onde a informação reside. Todo ponteiro ocupa espaço em memória e, tipicamente, o endereço é expresso em notação hexadecimal.
Declaração de ponteiros
Assim como qualquer variável, o ponteiro precisa ser declarado antes do uso. A declaração indica o tipo de dado para o qual o ponteiro apontará:
TipoApontado* NomeDoPonteiro;
Também é possível apontar para uma região genérica usando void*. Uma prática segura é inicializar o ponteiro com nullptr (ou NULL), que é um valor verificável e não corresponde a um endereço real.
O operador de endereço (&)
O operador & fornece o endereço de memória de uma variável. Por exemplo, &idade retorna o endereço onde idade está armazenada.
O operador de desreferência (*)
Para acessar ou modificar o valor armazenado no endereço contido no ponteiro, utiliza-se o opreador *. Se ptr aponta para um inteiro, *ptr corresponde ao inteiro propriamente dito.
sizeof e ponteiros
O tamanho de um ponteiro depende da arquitetura do sistema e do compilador (por exemplo, 4 bytes em plataformas de 32 bits, 8 bytes em 64 bits), independentemente do tipo de dado apontado. Assim, sizeof(ptr) retorna o tamanho da variável ponteiro em si, não do dado apontado.
Memória Dinâmica: new e delete
Alocação de um único elemento
O operador new solicita um bloco de memória para o tipo especificado. Se bem‑sucedido, devolve o endereço do bloco; caso contrário, lança uma exceção (std::bad_alloc).
Tipo* ponteiro = new Tipo;
Alocação de múltiplos elementos
Para arrays, utiliza‑se new Tipo[tamanho], que reserva espaço contíguo para vários elementos.
Tipo* ponteiro = new Tipo[numElementos];
Liberação da memória
Cada alocação com new deve ser correspondida por um delete (para elemento único) ou delete[] (para arrays). A omissão causa vazamento de memória: os blocos permanecem ocupados, reduzindo a memória disponível para outros processos e podendo degradar o desempenho.
Aritmética com ponteiros
Operadores ++ e -- deslocam o ponteiro para o próximo ou anterior elemento do tipo base, não para o próximo byte. O exemplo a seguir demonstra alocação, preenchimento e percurso usando aritmética:
#include <iostream>
using namespace std;
int main() {
cout << "Quantos números deseja digitar? ";
int total;
cin >> total;
double* valores = new double[total];
cout << "Memória alocada para " << total << " elementos.\n";
for (int i = 0; i < total; ++i) {
cout << "Elemento " << i << ": ";
cin >> *(valores + i);
}
cout << "\nValores informados:\n";
for (int i = 0; i < total; ++i) {
cout << *(valores++) << " ";
}
// Retorna o ponteiro à posição original antes de liberar
valores -= total;
delete[] valores;
return 0;
}
Note o reposicionamento do ponteior após o deslocamento com ++, essencial para que delete[] receba o endereço original retornado por new.
Qualificadores const com ponteiros
Ponteiro constante (const de topo)
O endereço armazenado não pode ser alterado, mas o valor apontado pode ser modificado:
int x = 10;
int* const ptr = &x;
*ptr = 20; // ok
ptr = &outra; // erro
Ponteiro para constante (const de baixo nível)
O conteúdo apontado não pode ser modificado através do ponteiro, embora o ponteiro possa apontar para outro endereço:
const int* ptr;
ptr = &x; // ok
*ptr = 30; // erro
Ponteiro constante para constante
Ambos, endereço e conteúdo, são imutáveis:
const int* const ptr = &x;
Relação entre arrays e ponteiros
Em C++, o nome de um array pode ser tratado como um ponteiro para o seu primeiro elemento. Assim, int v[5]; int* p = v; é válido.
Erros comuns no uso de ponteiros
Vazamento de memória
Ocorre quando a memória alocada dinamicamente nunca é liberada. Sempre garanta que cada new tenha um delete correspondente.
Acesso a memória inválida
Desreferenciar um ponteiro que não aponta para um endereço válido (ex.: nullptr ou região já liberada) causa comportamento indefinido. Verifique o ponteiro antes de utilizá‑lo.
Ponteiro pendente (dangling pointer)
Após liberar a memória com delete, defina o ponteiro como nullptr. Isso evita que ele ainda aponte para uma área que pode ser reutilizada por outras operações.
Falha na alocação
Embora raro, o operador new pode falhar. Além do tratamento via exceção (std::bad_alloc), existe a variante new(std::nothrow), que retorna nullptr em caso de falha, permitindo uma verificação explícita:
int* ptr = new(std::nothrow) int[1000];
if (!ptr) {
// tratar erro
}
Referências
Definição e inicialização
Uma referência é um alias (apelido) para uma variável existente. Deve ser inicializada no momento da declaração e, a partir de então, qualquer operação sobre a referência equivale a operar sobre a variável original.
int original = 42;
int& ref = original;
ref = 10; // agora original também vale 10
O programa seguinte mostra que tanto a referência inicializada diretamente quanto uma referência criada a partir de outra referência apontam para o mesmo endereço:
#include <iostream>
using namespace std;
int main() {
int valor = 30;
cout << "valor = " << valor << ", endereço: " << &valor << endl;
int& r1 = valor;
cout << "r1 endereço: " << &r1 << endl;
int& r2 = r1;
cout << "r2 endereço: " << &r2 << endl;
cout << "r2 = " << r2 << endl;
return 0;
}
Referências como parâmetros de função
Passar argumentos por referência evita a cópia dos dados, sendo especialmente vantajoso para objetos grandes. Além disso, permite que a função altere diretamente o valor da variável original. Exemplo:
#include <iostream>
using namespace std;
void ElevarAoCubo(int& numero) {
numero = numero * numero * numero;
}
int main() {
cout << "Digite um número: ";
int n;
cin >> n;
ElevarAoCubo(n);
cout << "Elevado ao cubo: " << n << endl;
return 0;
}
Referências constantes (const &)
Quando não se deseja modificar o argumento, mas ainda assim evitar cópias, utiliza‑se const Tipo&. A função pode ler o valor, mas qualquer tentativa de alteração gera erro de compilação.