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 son similares, pero piensa en ellos como un nivel más cerca del funcionamiento interno de tu ordenador. Los punteros son variables que contienen direcciones de objetos. Se utilizan para interactuar directamente con los objetos, lo que permite la asignación dinámica de memoria y una manipulación de datos eficiente en C++.

Si eres nuevo en los punteros, pueden parecer un poco misteriosos, pero una vez te acostumbras a ellos son bastante sencillos.

Son una parte crucial de C++, así que tómate tu tiempo para entenderlos bien. La versión básica que se trata en este concepto también se conoce como puntero tonto o puntero crudo. En 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, merece 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, algo útil por dos razones principales:

  1. Al igual que las referencias, los punteros evitan copias y ayudan a reducir el consumo 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 ese 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 del nombre del puntero. Cuando se declara un puntero, no se inicializa automáticamente. Sin una asignación explícita, un puntero suele contener un valor indeterminado, al que a menudo se hace referencia como «dirección basura». Aunque algunos compiladores pueden inicializar los punteros a nullptr, este comportamiento no está garantizado en todos los compiladores, así que es esencial no confiar en él. La mejor práctica es inicializar explícitamente los punteros crudos y verificar que no son 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, se utiliza el operador de dirección (&). La desreferenciación de un puntero se realiza 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: la desreferenciación hay que hacerla explícitamente, mientras que las referencias simplemente funcionaban como un alias.

Aritmética de punteros

La aritmética de punteros te permite realizar operaciones aritméticas con punteros, algo especialmente útil cuando se trabaja con arrays. Sumar un número entero a un puntero hace que apunte a un elemento diferente.

// 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 provocar fácilmente 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 famoso de las consecuencias del comportamiento indefinido ocurrió en la explosión del cohete Ariane 5 en 1996, donde una excepción de software provocada por la conversión de un número de coma flotante de 64 bits a un entero con signo de 16 bits dio lugar a un fallo catastrófico.

Acceder a las variables miembro

En C++, el operador -> se utiliza 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 se trabaja con punteros a objetos.

Aquí tienes un breve ejemplo, con una 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). Se utiliza el operador -> para acceder a la variable miembro superpower, mostrando el icónico «Lazo de la verdad» 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

Tanto los punteros como las referencias permiten un 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 se vinculan a objetos válidos en el momento de su creación, lo que evita estos riesgos. Dado que son más seguras, se debería dar preferencia a las referencias frente a los punteros, a menos que sean necesarias las funcionalidades adicionales que ofrecen los punteros.

Advanced

Asignación dinámica de memoria y punteros nulos

En C++, new se utiliza para asignar dinámicamente memoria a los 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 scope concreto. Sin embargo, es importante emparejar siempre new con delete para liberar la memoria correctamente. La asignación dinámica de memoria debe utilizarse con cautela para evitar problemas habituales 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 utilizar 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 tienes que 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 quedarán colgando (no se establecerán automáticamente a nullptr). Es tu responsabilidad detectar estos casos y asegurarte de que esos punteros se establezcan posteriormente 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 frecuente en el código C++ moderno.

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

Aprende Punteros

La práctica está bloqueada

Desbloquea 1 ejercicio más para practicar Punteros