Tracks
/
C++
C++
/
Temario
/
Punteros
Pu

Punteros en C++

1 ejercicio

Acerca de Punteros

Como muchos otros lenguajes, C++ tiene punteros. Ya sabes que las referencias y los punteros se parecen, pero piensa en ellos como un nivel más cerca del funcionamiento interno de tu computadora. Los punteros son variables que guardan direcciones de objetos. Se usan para interactuar directamente con los objetos, lo que permite la asignación dinámica de memoria y la manipulación eficiente de datos en C++.

Si es la primera vez que ves los punteros, pueden parecer un poco misteriosos, pero una vez que te acostumbras a ellos son bastante sencillos.

Son una parte crucial de C++, así que tómate un tiempo para entenderlos de verdad. La versión más básica de este concepto también se llama puntero sin procesar. Con el C++ moderno también existen los punteros inteligentes; el tipo básico no es nada inteligente y tienes que encargarte de todo el trabajo manualmente.

Antes de entrar en detalles, vale la pena entender para qué sirven los punteros. Los punteros son una forma de compartir la dirección de un objeto con otras partes de nuestro programa, lo cual es útil por dos razones principales:

  1. Como las referencias, los punteros evitan copias y ayudan a reducir la huella de recursos de tu programa.
  2. A diferencia de las referencias, los punteros se pueden reasignar a otros objetos.
  3. Los punteros también pueden apuntar a un valor nulo, para indicar que en este momento no apuntan a ningún objeto.

Sintaxis general

Una declaración de puntero en C++ implica especificar el tipo de dato al que apunta el puntero, seguido de un asterisco (*) y el nombre del puntero. Cuando se declaran punteros, no se inicializan automáticamente. Sin una asignación explícita, un puntero suele contener un valor indeterminado, al que a menudo se le llama «dirección basura». Aunque algunos compiladores podrían inicializar los punteros a nullptr, este comportamiento no está garantizado en todos los compiladores, así que es esencial no confiar en él. Es una buena práctica inicializar explícitamente los punteros sin procesar y verificar que no sean nulos antes de usarlos, para evitar posibles problemas.

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

Para asignar la dirección de una variable a un puntero, usas el operador de dirección (&). Desreferenciar un puntero se hace con el operador de indirección (*).

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

Atención: desreferenciar hay que hacerlo de forma explícita, mientras que las referencias simplemente funcionaban como un alias.

Aritmética de punteros

La aritmética de punteros te permite hacer operaciones aritméticas con punteros, lo cual es especialmente útil cuando trabajas con arrays. Sumar un entero a un puntero hace que apunte a un elemento distinto.

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

La aritmética de punteros en C++ puede llevar fácilmente a un comportamiento indefinido si no se maneja con cuidado. El comportamiento indefinido puede manifestarse en resultados inesperados del programa, fallos o incluso vulnerabilidades de seguridad. Un ejemplo tristemente célebre de las consecuencias del comportamiento indefinido ocurrió en la explosión del cohete Ariane 5 en 1996, donde una excepción de software causada por la conversión de un número de punto flotante de 64 bits a un entero con signo de 16 bits provocó un fallo catastrófico.

Acceso a las variables miembro

En C++, el operador -> se usa para acceder a los miembros de un objeto a través de un puntero a ese objeto. Es una forma abreviada que simplifica el acceso a los miembros de los objetos a los que apuntan los punteros. Por ejemplo, si ptr es un puntero a un objeto con una variable miembro x, en lugar de usar (*ptr).x puedes usar directamente ptr->x. Este operador mejora la legibilidad del código y reduce la verbosidad cuando trabajas con punteros a objetos.

Aquí tienes un ejemplo breve, con un struct Superhero que tiene una variable miembro superpower. La función main crea un puntero dianaPrince a un objeto Superhero (que representa a Wonder Woman). El operador -> se usa para acceder a la variable miembro superpower, mostrando el icónico «Lasso of Truth» de Wonder Woman.

struct Superhero {
    std::string superpower;
};

Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";

// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

// Memory cleanup:
delete dianaPrince;

Punteros frente a referencias

Los punteros y las referencias permiten ambos el acceso indirecto a los objetos, pero difieren en sus capacidades y en las consideraciones de seguridad. Los punteros ofrecen la flexibilidad de cambiar el objeto al que apuntan y se les puede asignar un valor nulo. Sin embargo, esta flexibilidad introduce riesgos, como desreferenciar punteros nulos o crear punteros colgantes. Las referencias, en cambio, no pueden ser nulas y quedan vinculadas a objetos válidos desde su creación, lo que evita estos riesgos. Dada su naturaleza más segura, conviene preferir las referencias antes que los punteros, a menos que necesites las funcionalidades adicionales que ofrecen los punteros.

Advanced

Asignación dinámica de memoria y punteros nulos

En C++, new se usa para asignar dinámicamente memoria para objetos en el heap durante la ejecución. Devuelve un puntero a la dirección de memoria asignada, lo que permite que los objetos tengan tiempos de vida no ligados a un ámbito específico. Sin embargo, es importante emparejar siempre new con delete para liberar la memoria correctamente. La asignación dinámica de memoria debe usarse con cautela para evitar problemas comunes como las fugas de memoria y el acceso a memoria ya liberada.

Los punteros también se pueden establecer a un valor especial llamado puntero nulo, que indica que no apuntan a una dirección válida. Los punteros nulos se suelen usar para inicializar punteros antes de asignarles valores válidos.

// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;

Un puntero debería contener la dirección de un objeto válido o estar establecido a nullptr. De esa forma solo necesitas comprobar si los punteros son nulos, y puedes asumir que cualquier puntero no nulo es válido. Cuando se destruye un objeto, los punteros a ese objeto destruido quedan colgantes (no se establecen automáticamente a nullptr). Es tu responsabilidad detectar estos casos y asegurarte de que esos punteros se establezcan después a nullptr.

En código más antiguo, puedes encontrarte con dos alternativas a nullptr. En primer lugar, el literal 0 se interpreta específicamente como un valor nulo para los punteros, aunque es el único caso en el que se puede asignar un literal entero a un puntero. En segundo lugar, la preprocessor macro NULL, heredada de C y definida en el encabezado <cstddef>, es otra representación de un puntero nulo, aunque su uso es menos común en el código C++ moderno.

Editar en GitHub El enlace se abre en una ventana o pestaña nueva

Aprende Punteros

La práctica está bloqueada

Desbloquea 1 ejercicio más para practicar Punteros