Punteros: Direcciones, Desreferencia y Aritmetica
Comprende los punteros como direcciones de memoria, aprende los operadores de direccion y desreferencia, la aritmetica de punteros dentro de arrays, la correccion const con punteros, y cuando los punteros crudos aun son apropiados en C++ moderno.
Que Es un Puntero?
Un puntero es una variable que almacena una direccion de memoria. Cada objeto en C++ vive en alguna direccion en memoria, y un puntero te permite referirte a ese objeto indirectamente. Los punteros son fundamentales en C++ — habilitan estructuras de datos dinamicas, polimorfismo y paso eficiente de parametros.
Un tipo puntero se declara con *: int* p; declara un puntero a un int. El puntero en si ocupa memoria (tipicamente 8 bytes en sistemas de 64 bits) y su valor es una direccion, no los datos a los que apunta.
Direccion-De y Desreferencia
Los dos operadores fundamentales de punteros son & (direccion-de) y * (desreferencia). El operador & obtiene la direccion de una variable. El operador * sigue la direccion para acceder al objeto apuntado.
#include <iostream>
int main() {
int value = 42;
int* ptr = &value; // ptr holds the address of value
std::cout << "value: " << value << '\n'; // 42
std::cout << "&value: " << &value << '\n'; // e.g. 0x7ffd5a3c
std::cout << "ptr: " << ptr << '\n'; // same address
std::cout << "*ptr: " << *ptr << '\n'; // 42 (dereference)
*ptr = 100; // modify value through the pointer
std::cout << "value after *ptr = 100: " << value << '\n'; // 100
int* null_ptr = nullptr; // points to nothing — safe sentinel
// *null_ptr = 5; // UNDEFINED BEHAVIOR — crash on most systems
}Aritmetica de Punteros y Decaimiento de Arrays
La aritmetica de punteros es solo valida dentro de un array (o uno mas alla del final). El compilador escala la aritmetica por sizeof(T). El nombre de un array decae a un puntero a su primer elemento en la mayoria de contextos — es por esto que los arrays estilo C pierden su informacion de tamano cuando se pasan a funciones.
#include <iostream>
#include <cstddef> // std::ptrdiff_t
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // array decays to pointer to first element
std::cout << *p << '\n'; // 10
std::cout << *(p + 2) << '\n'; // 30 (moves 2*sizeof(int) bytes)
std::cout << p[3] << '\n'; // 40 (p[i] is *(p + i))
// Iterating via pointer
for (int* it = arr; it != arr + 5; ++it) {
std::cout << *it << ' ';
}
std::cout << '\n'; // 10 20 30 40 50
// Pointer difference
std::ptrdiff_t diff = (arr + 4) - arr; // 4 (elements, not bytes)
std::cout << "diff: " << diff << '\n';
// WARNING: arithmetic outside the array is UNDEFINED BEHAVIOR
// int* bad = arr + 10; // UB — don't do this
}Punteros const vs Puntero-a-const
La ubicacion de const relativa a * determina que es inmutable:
- const int* p (o int const* p) — puntero a const: no puedes modificar el valor apuntado a traves de p, pero puedes reasignar p para apuntar a otro lugar.
- int* const p — puntero const: el puntero en si es inmutable (no se puede reasignar), pero puedes modificar el valor al que apunta.
- const int* const p — tanto el puntero como el valor apuntado son inmutables.
Lee las declaraciones de derecha a izquierda: int* const p se lee como "p es un puntero const a int."
Punteros void (void*) pueden contener cualquier direccion pero no pueden ser desreferenciados sin conversion. Se usan principalmente en APIs estilo C e implementaciones de asignadores.
En C++ moderno, los punteros crudos deben usarse exclusivamente como punteros observadores sin propiedad. Dicen: "Puedo ver este objeto, pero no soy responsable de su tiempo de vida." Usos legitimos comunes:
- Implementar estructuras de datos (nodos de arbol apuntando al padre)
- Parametros de funcion que observan pero no poseen (aunque las referencias suelen ser mejores)
- Interfaz con APIs de C
- Iteradores en contenedores personalizados
Si un puntero posee un recurso, envuelvelo en std::unique_ptr o std::shared_ptr. Si te encuentras escribiendo delete, casi con certeza lo estas haciendo mal en C++ moderno.
Los punteros colgantes (dangling pointers) son el error de punteros #1. Un puntero se vuelve colgante cuando el objeto al que apunta es destruido:
- Retornar un puntero a una variable local
- Eliminar un objeto mientras otro puntero aun lo referencia
- Puntero dentro de un std::vector que se reasigna despues de push_back
Punteros sin inicializar contienen direcciones basura. Siempre inicializa a nullptr si aun no tienes un destino. El compilador NO advertira sobre todos los usos de punteros sin inicializar.
Puntero vs Referencia: Cuando Usar Cual
Las referencias y los punteros proporcionan indireccion, pero difieren en aspectos importantes. Las referencias no pueden ser nulas, no pueden ser reasignadas, y no requieren desreferencia explicita. Prefiere referencias para parametros de funcion; usa punteros cuando se necesita nulabilidad o reasignacion.
#include <iostream>
void increment_ref(int& val) { ++val; } // cannot be null
void increment_ptr(int* val) { if (val) ++(*val); } // must null-check
int main() {
int x = 10;
increment_ref(x); // clean call syntax
increment_ptr(&x); // caller must take address
std::cout << x << '\n'; // 12
int* p = &x;
p = nullptr; // OK — pointers can be re-seated and nulled
int& r = x;
// r = ???; // references cannot be re-seated
// int& bad; // ERROR — references must be initialized
}&obtiene una direccion,*la sigue — son operaciones inversas- La aritmetica de punteros solo es valida dentro de arrays y escala por
sizeof(T) - Los nombres de arrays decaen a punteros, perdiendo la informacion de tamano
- Lee la ubicacion de
constde derecha a izquierda:const int*vsint* const - En C++ moderno, los punteros crudos deben ser solo observadores sin propiedad
- Prefiere referencias sobre punteros a menos que necesites nulabilidad o reasignacion
Quiz — Pon a prueba tus conocimientos
(15 XP)1. Que significa `const int* p`?
2. Que sucede cuando un array se pasa a una funcion que espera un parametro puntero?
3. Cuando es valida la aritmetica de punteros en C++?