C++: Aritmética Avançada de Ponteiros
Última atualização: 2026-08-26
Na Aula 23, aprendemos o básico da aritmética de ponteiros (
p + n,p - q).Mas há muitos mais detalhes na aritmética de ponteiros — por exemplo, ponteiros podem ser comparados? O que é um ponteiro
void*? O que são ponteiros para ponteiros?
1. Comparação de Ponteiros
(1) 1.1 Ponteiros Podem Ser Comparados?
Sim! Mas apenas comparações entre ponteiros que apontam para o mesmo array são significativas.
▶ Exemplo 2: Prática Básica de Programação (Dificuldade ⭐)
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[0];
int* p2 = &arr[3];
if (p1 < p2) { // ✅ elemento apontado por p1 está antes de p2
std::cout << "p1 está antes de p2" << std::endl;
}
if (p2 - p1 == 3) { // ✅ dois ponteiros estão a 3 elementos de distância
std::cout << "p1 e p2 estão a 3 elementos de distância" << std::endl;
}
return 0;
}
Saída:
p1 está antes de p2
p1 e p2 estão a 3 elementos de distância
💡 Ponto-chave: Comparações de ponteiros comparam valores de endereço (ou seja, posições dos elementos no array).
(2) 1.2 Comparação de Igualdade de Ponteiros
#include <iostream>
int main() {
int x = 5;
int* p1 = &x;
int* p2 = &x;
if (p1 == p2) { // ✅ dois ponteiros apontam para a mesma variável
std::cout << "p1 e p2 apontam para a mesma variável" << std::endl;
}
int y = 10;
int* p3 = &x;
int* p4 = &y;
if (p3 != p4) { // ✅ dois ponteiros apontam para variáveis diferentes
std::cout << "p3 e p4 apontam para variáveis diferentes" << std::endl;
}
return 0;
}
2. Ponteiros void*
(1) 2.1 O que é um Ponteiro void*?
void* é um ponteiro genérico — ele pode apontar para qualquer tipo de dado.
#include <iostream>
int main() {
int x = 5;
double y = 3.14;
char c = 'A';
void* p1 = &x; // ✅ void* pode apontar para int
void* p2 = &y; // ✅ void* pode apontar para double
void* p3 = &c; // ✅ void* pode apontar para char
return 0;
}
💡 Características:
void*não pode ser desreferenciado (o compilador não sabe quantos bytes ler)void*não pode fazer aritmética de ponteiros (o tamanho do passo é desconhecido)
(2) 2.2 Usos de void*
Uso 1: Escrever funções genéricas (mais tarde você aprenderá que std::memcpy usa void*)
#include <iostream>
#include <cstring>
int main() {
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
// std::memcpy usa void* para aceitar ponteiro de qualquer tipo
std::memcpy(arr2, arr1, 5 * sizeof(int));
for (int i = 0; i < 5; i++) {
std::cout << arr2[i] << " ";
}
std::cout << std::endl;
return 0;
}
Uso 2: Alocação dinâmica de memória (mais tarde você aprenderá que malloc retorna void*)
(3) 2.3 Convertendo void*
Para usar o valor apontado por void*, você deve convertê-lo para um ponteiro de um tipo concreto.
#include <iostream>
int main() {
int x = 5;
void* p = &x; // void* apontando para int
// ❌ Erro: Não é possível desreferenciar void* diretamente
// std::cout << *p << std::endl;
// ✅ Correto: Primeiro converta para int*
int* intPtr = static_cast<int*>(p);
std::cout << *intPtr << std::endl; // Exibe 5
return 0;
}
💡 Dica: static_cast é o operador de conversão de tipo do C++ (coberto mais tarde), que é mais seguro que o estilo C (int*)p.
3. Ponteiros Multinível (Ponteiros para Ponteiros)
(1) 3.1 O que é um Ponteiro para Ponteiro?
Um ponteiro para ponteiro é um ponteiro que armazena o endereço de outro ponteiro (bonecas russas).
#include <iostream>
int main() {
int x = 5;
int* p = &x; // p aponta para x
int** pp = &p; // pp aponta para p (ponteiro para ponteiro)
std::cout << "x = " << x << std::endl;
std::cout << "*p = " << *p << std::endl; // Desreferência uma vez: obtém valor de x
std::cout << "**pp = " << **pp << std::endl; // Desreferência duas vezes: obtém valor de x
return 0;
}
(2) 3.2 Declarando Ponteiros Multinível
| Declaração | Significado |
|---|---|
int x; |
x é um int |
int* p; |
p é um int* (ponteiro para int) |
int** pp; |
pp é um int** (ponteiro para int*) |
int*** ppp; |
ppp é um int*** (ponteiro para int**) |
💡 Como ler: Leia da direita para a esquerda.
int* p;→pé um ponteiro paraintint** pp;→ppé um ponteiro para ponteiro paraint
(3) 3.3 Usos de Ponteiros Multinível
Uso 1: Arrays 2D dinâmicos (coberto mais tarde)
// Alocar dinamicamente um array 2D 3x4
int** matrix = new int*[3]; // matrix é um ponteiro para ponteiro
for (int i = 0; i < 3; i++) {
matrix[i] = new int[4];
}
Uso 2: Funções que precisam modificar um ponteiro (raro, apenas esteja ciente)
#include <iostream>
void allocateInt(int** ptr) {
*ptr = new int(5); // Modifica *ptr (ou seja, modifica o ponteiro original)
}
int main() {
int* p = nullptr;
allocateInt(&p); // Passa o endereço do ponteiro
std::cout << *p << std::endl; // Exibe 5
delete p; // Libera memória
return 0;
}
4. Ponteiros e const: Combinações
(1) 4.1 Quatro Combinações
| Declaração | Significado | Pode mudar o ponteiro? | Pode mudar o valor apontado? |
|---|---|---|---|
int* p; |
Ponteiro ordinário | ✅ Sim | ✅ Sim |
const int* p; |
Ponteiro para int const | ✅ Sim | ❌ Não |
int* const p = &x; |
Ponteiro const (deve inicializar) | ❌ Não | ✅ Sim |
const int* const p = &x; |
Ponteiro const para const | ❌ Não | ❌ Não |
💡 Truque de memorização: Leia da direita para a esquerda.
const int* p;→pé um ponteiro paraconst intint* const p = &x;→pé um ponteiroconstparaint
(2) 4.2 Exemplo: const int* (Comumente Usado)
#include <iostream>
void printArray(const int* arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// ❌ Não pode modificar:arr[i] = 0; // Erro de compilação
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printArray(arr, 5);
return 0;
}
Saída:
1 2 3 4 5
💡 Ponto-chave: const int* arr é o tipo de parâmetro de função mais comumente usado — significa "eu apenas leio, não modifico."
5. Prática: Implementando Cópia de String com Ponteiros
▶ Exemplo 1: strcpy feito à mão (Dificuldade ⭐⭐)
#include <iostream>
void myStrcpy(char* dest, const char* src) {
while (*src != '\0') {
*dest = *src; // Copia caractere
dest++; // Move ponteiro
src++;
}
*dest = '\0'; // Adiciona terminador nulo
}
int main() {
char src[] = "Hello";
char dest[20];
myStrcpy(dest, src);
std::cout << "src = " << src << std::endl;
std::cout << "dest = " << dest << std::endl;
return 0;
}
Saída:
src = Hello
dest = Hello
💡 Dica: Este programa usa aritmética de ponteiros e desreferência — é uma prática excelente para entender ponteiros.
▶ Exemplo 3: Percorrendo um Array com Ponteiros (Dificuldade ⭐)
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* p = arr; // p aponta para o primeiro elemento do array
std::cout << "Percorrendo array com ponteiros: " << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << "*p = " << *p << std::endl;
p++; // Move ponteiro para o próximo elemento
}
// subtração de ponteiros calcula número de elementos
int* end = arr + 5;
std::cout << "Número de elementos:" << end - arr << std::endl;
return 0;
}
Saída:
Percorrendo array com ponteiros:
*p = 10
*p = 20
*p = 30
*p = 40
*p = 50
Número de elementos:5
❓ Perguntas Frequentes
P: Por que void não pode ser desreferenciado?* R: Porque
voidé um tipo sem tamanho — o compilador não sabe se deve ler 1 byte,4bytes, ou8bytes.Ele deve ser convertido para um ponteiro de um tipo concreto (como
int*,double*) antes que o compilador saiba o tamanho do passo.
P: Para que servem os ponteiros multinível? R: Em projetos reais,
int**é mais comumente visto em: > 1. Arrays 2D dinâmicos (ex:new int*[3]) > 2. Funções que precisam modificar o ponteiro em si (ex:void foo(int** p)pode modificar para ondepaponta)Ponteiros além de dois níveis (
int***etc.) não são recomendados — muito difíceis de entender.
P:
const int*eint const*são iguais? R: Sim! As duas formas seguintes são equivalentes: > > const int p; // Recomendado > int const p; // Equivalente, mas menos comum > > > Masint* const pé diferente — este é um ponteiroconst(não pode mudar para onde aponta).**
📖 Resumo
- Ponteiros podem ser comparados (apenas significativo quando apontam para o mesmo array)
void*é um ponteiro genérico que não pode ser desreferenciado ou usado em aritmética de ponteiros- Ponteiros multinível são ponteiros para ponteiros (
int**) - Ponteiros + const têm quatro combinações; a mais comumente usada é
const int*(somente leitura) - Implementar cópia de string com ponteiros é uma prática excelente para entender ponteiros
📝 Exercícios
-
Básico (Dificuldade ⭐): Declare uma variável
intx = 5, depois declare um ponteiroint*papontando parax, então declare um ponteiroint**ppapontando parap. Exibax,*p,**ppe verifique se são iguais. -
Intermediário (Dificuldade ⭐⭐): Escreva uma função
void swap(int** a, int** b)que troque para onde dois ponteiros apontam.
Entrada: p1 aponta para x, p2 aponta para y
Chame swap(&p1, &p2)
Saída: p1 aponta para y, p2 aponta para x
- Desafio (Dificuldade ⭐⭐⭐):
Use
void*e aritmética de ponteiros para escrever à mão uma funçãomyMemcpy: - Protótipo da função:
void myMemcpy(void* dest, const void* src, size_t n) - Funcionalidade: copiar
nbytes desrcparadest - Dica: copie byte a byte (use
char*para aritmética de ponteiros)
- Adição/subtração de inteiros a ponteiros: deslocamento pelo tamanho do tipo apontado
- Subtração de ponteiros: retorna a diferença em contagem de elementos
- Comparação de ponteiros: pode ser usada para determinar ordenação
- Ponteiro void*: sem tipo, não pode ser desreferenciado ou usado em aritmética
- Ponteiros de função: armazenam o endereço de entrada de uma função
6. 🚀 Próximo Passo
Agora que você aprendeu conceitos avançados de ponteiros, vamos avançar para Referências (Aula 25) — a "versão mais segura" dos ponteiros, um recurso importante do C++!