cpp-que-son-los-punteros

Qué son y cómo usar los punteros en C++

Un puntero es una variable que almacena la dirección de memoria de otra variable. Esta capacidad permite a los punteros manipular datos en memoria.

En lugar de contener directamente un valor de datos, un puntero contiene la dirección donde se encuentra ese valor. Por tanto, son una de las formas más simples de realizar una referencia.

Los punteros son una característica heredada de C. Son muy odiados y amados (ambas cosas de forma injustificada)

Uno de los objetivos de C++ es reducir o eliminar el uso de punteros. Sin embargo, siguen siendo necesarios.

Definición de un puntero

La sintaxis básica para definir un puntero es:

tipo *nombrePuntero;

Donde:

  • tipo es el tipo de dato al que el puntero apunta.
  • nombrePuntero es el nombre del puntero.

Por ejemplo,

int *ptr;

En este ejemplo, ptr es un puntero a un entero.

Operador de desreferencia

El operador de desreferencia * se utiliza para acceder al valor de la variable a la que apunta el puntero.

int var = 10;
int *ptr = &var;

std::cout << "Valor de var: " << var << std::endl;
std::cout << "Valor de var usando puntero: " << *ptr << std::endl;

Asignación de direcciones

Para asignar una dirección de memoria a un puntero, se utiliza el operador de dirección &.

int var = 10;
int *ptr = &var;

Aquí, ptr almacena la dirección de var.

Punteros y arrays

Los arrays y los punteros están estrechamente relacionados en C++. El nombre de un array es en sí mismo un puntero al primer elemento del array.

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;

std::cout << "Primer elemento del array: " << *ptr << std::endl;
std::cout << "Segundo elemento del array: " << *(ptr + 1) << std::endl;

En este ejemplo, ptr apunta al primer elemento del array arr.

Punteros y funciones

Los punteros pueden ser utilizados para pasar grandes estructuras de datos a funciones sin necesidad de copiarlas, lo cual es eficiente en términos de memoria y tiempo.

#include <iostream>

void incrementar(int *ptr) {
    (*ptr)++;
}

int main() {
    int num = 10;
    incrementar(&num);
    std::cout << "Valor de num después de incrementar: " << num << std::endl;
    return 0;
}

En este ejemplo, la función incrementar modifica directamente el valor de num a través de un puntero.

Punteros y memoria dinámica

En C++, se puede gestionar la memoria de forma dinámica utilizando los operadores new y delete.

Asignación dinámica de memoria

int *ptr = new int;
*ptr = 20;
std::cout << "Valor almacenado en memoria dinámica: " << *ptr << std::endl;
delete ptr;

En este ejemplo, se asigna memoria para un entero dinámicamente y luego se libera.

Arrays dinámicos

int *arr = new int[5];
for (int i = 0; i < 5; ++i) {
    arr[i] = i * 2;
}

for (int i = 0; i < 5; ++i) {
    std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}

delete[] arr;

Aquí, se asigna y libera memoria para un array dinámico.

Punteros a punteros

Un puntero a puntero es una variable que contiene la dirección de un puntero, permitiendo niveles adicionales de indireccionamiento.

int var = 5;
int *ptr = &var;
int **pptr = &ptr;

std::cout << "Valor de var: " << var << std::endl;
std::cout << "Valor de var usando ptr: " << *ptr << std::endl;
std::cout << "Valor de var usando pptr: " << **pptr << std::endl;

Buenas prácticas

Inicializar punteros

Siempre inicializar punteros.

int *ptr = nullptr;

Utilizar punteros que no han sido inicializados puede causar comportamientos indefinidos.


Liberar memoria

Asegurarse de liberar cualquier memoria dinámica asignada.

int *ptr = new int;
delete ptr;

No liberar memoria asignada dinámicamente puede resultar en fugas de memoria.


Uso de nullptr

Usar nullptr en lugar de NULL o 0 para punteros nulos.

int *ptr = nullptr;

Ejemplo completo

Vamos a ver un ejemplo algo más amplio, donde veríamos una gestión dinámica de una lista de enteros.

#include <iostream>

class ListaEnteros {
private:
    int *datos;
    int capacidad;
    int tamaño;

public:
    ListaEnteros(int cap) : capacidad(cap), tamaño(0) {
        datos = new int[capacidad];
    }

    ~ListaEnteros() {
        delete[] datos;
    }

    void agregar(int valor) {
        if (tamaño < capacidad) {
            datos[tamaño++] = valor;
        } else {
            std::cerr << "Capacidad de la lista excedida" << std::endl;
        }
    }

    void mostrar() const {
        for (int i = 0; i < tamaño; ++i) {
            std::cout << datos[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    ListaEnteros lista(10);
    lista.agregar(1);
    lista.agregar(2);
    lista.agregar(3);

    std::cout << "Elementos en la lista: ";
    lista.mostrar();

    return 0;
}

En este ejemplo, se crea una clase ListaEnteros que gestiona una lista de enteros de forma dinámica, demostrando la creación, uso y liberación de memoria dinámica.

No significa que sea la mejor forma de resolver el problema. Es un ejemplo para ilustrar el uso de punteros.