Tema 10 - Punteros y gestión dinámica de memoria
📌 Concepto de Puntero
Un puntero es una variable que almacena la dirección de memoria de otra variable. En C++, los punteros son fundamentales para manejar memoria dinámica y optimizar algoritmos que acceden frecuentemente a grandes volúmenes de datos.
Características principales:
- Dirección de memoria: Un puntero almacena la ubicación de otra variable en memoria.
- Tipos: Siempre debe declararse el tipo de dato al que apunta el puntero.
- Declaración:
Ejemplo:tipo *nombrePuntero;int *pEntero;float *pReal;
- Operadores asociados:
&: Devuelve la dirección de memoria de una variable.*: Accede al contenido almacenado en la dirección apuntada por el puntero.
Ejemplo:
#include <iostream>using namespace std;
int main() { int valor = 42; int *p = &valor; // El puntero almacena la dirección de 'valor'
cout << "Dirección de valor: " << p << endl; cout << "Contenido apuntado por p: " << *p << endl;
*p = 84; // Modifica 'valor' usando el puntero cout << "Nuevo valor de valor: " << valor << endl;
return 0;}Salida:
Dirección de valor: 0x7ffee59a4a8Contenido apuntado por p: 42Nuevo valor de valor: 84🛠️ Asignación y Liberación de Memoria
En C++, la memoria puede reservarse dinámicamente durante la ejecución del programa. Esto es crucial cuando no se conoce el tamaño de los datos en tiempo de compilación. La gestión de esta memoria dinámica se realiza en el montón (heap), un área de memoria donde se alojan las variables dinámicas, cuya memoria se reserva y libera en tiempo de ejecución.
1. new: Reserva memoria
La palabra clave new se usa para solicitar un bloque de memoria del tamaño del tipo de dato especificado. Devuelve un puntero a la primera posición de la memoria asignada.
int *p = new int; // Reserva memoria para un entero*p = 10; // Asigna el valor 10 a esa memoriaPara arrays:
int *arr = new int[5]; // Reserva memoria para un array de 5 enteros2. delete: Libera memoria
Es crucial liberar la memoria asignada dinámicamente con delete cuando ya no se necesita. Esto previene fugas de memoria, un problema común cuando la memoria reservada no se libera.
delete p; // Libera la memoria de un enterodelete[] arr; // Libera la memoria de un arrayEjemplo completo
#include <iostream>using namespace std;
int main() { int *p = new int; if (p == nullptr) { //nullptr es un puntero nulo cerr << "Error al asignar memoria." << endl; return 1; } *p = 20; cout << "Valor almacenado: " << *p << endl;
delete p; // Libera la memoria return 0;}➕ Operaciones con Punteros
Direccionamiento e Indireccionamiento
Como ya dijimos, el operador & obtiene la dirección de memoria de una variable, mientras que * accede al contenido en esa dirección.
Con estes operadores, podemos manejar las variables a nuestro gusto.
Ejemplo
int x = 50;int *p = &x;cout << "Dirección de x: " << p << endl;cout << "Contenido en p: " << *p << endl;Comparación de Punteros
Los punteros pueden compararse con operadores como == o != para verificar si apuntan a la misma dirección.
Ejemplo
int a, b;int *p1 = &a, *p2 = &b;cout << (p1 == p2 ? "Apuntan al mismo lugar" : "Apuntan a lugares distintos") << endl;Aritmética de Punteros
Se pueden realizar operaciones como suma, resta y comparación sobre punteros. Estas operaciones son útiles al trabajar con arrays.
Ejemplo
int arr[5] = {10, 20, 30, 40, 50};int *p = arr;
cout << "Primer elemento: " << *p << endl;cout << "Segundo elemento: " << *(p + 1) << endl;📂 Punteros y Funciones
Los punteros pueden pasarse a funciones como argumentos, permitiendo modificar los valores originales.
Ejemplo
void modificar(int *p) { *p += 10;}
int main() { int x = 5; modificar(&x); cout << "Nuevo valor: " << x << endl; return 0;}🔄 Formas de Acceso a Variables y Arrays con Punteros
Los punteros permiten acceder y manipular variables o arrays de diferentes maneras. A continuación, exploraremos las técnicas más comunes:
1. Acceso Directo
Usando el operador de indireccionamiento (*) para acceder al valor almacenado en la dirección apuntada por el puntero.
Ejemplo
int valor = 100;int *p = &valor;cout << "Valor: " << *p << endl; // Acceso directo al contenido2. Recorrido de Arrays
Un puntero puede moverse dentro de un array usando aritmética de punteros.
Ejemplo
int arr[5] = {10, 20, 30, 40, 50};int *p = arr;
for (int i = 0; i < 5; i++) { cout << "Elemento " << i << ": " << *(p + i) << endl;}3. Iteración con Incremento
El puntero puede ser incrementado para recorrer secuencialmente un array.
Ejemplo
int arr[3] = {1, 2, 3};int *p = arr;
while (p < arr + 3) { cout << *p << " "; p++; // Incrementa el puntero}4. Manipulación de Subarrays
Un puntero puede apuntar a una sección específica de un array y operar solo con esa parte.
Ejemplo:
int arr[5] = {1, 2, 3, 4, 5};int *subArray = &arr[2];
for (int i = 0; i < 3; i++) { cout << "Elemento: " << *(subArray + i) << endl;}🧬 Punteros y Estructuras
Los punteros pueden usarse para apuntar a estructuras. Esto permite crear estructuras dinámicamente y acceder a sus miembros utilizando el operador ->.
Ejemplo
struct Persona { char nombre; int edad;};
...
Persona *pPersona = new Persona;
(*pPersona).edad = 30; // Alternativa 1
pPersona->edad = 30; // Alternativa 2, más común
delete pPersona;🏗️ Arrays Dinámicos
Un array dinámico es aquel cuyo tamaño se determina en tiempo de ejecución. Son útiles para trabajar con estructuras de datos que varían en tamaño.
Ejemplo
#include <iostream>using namespace std;
int main() { int n; cout << "Introduce el tamaño del array: "; cin >> n;
int *arr = new int[n]; for (int i = 0; i < n; i++) { arr[i] = i * 10; }
for (int i = 0; i < n; i++) { cout << arr[i] << " "; } cout << endl;
delete[] arr; // Libera la memoria return 0;}📚 Ejemplo Completo: Manejo de Memoria Dinámica
Diseñemos un programa que gestiona un grupo de estudiantes con memoria dinámica y además, usando estructuras:
#include <iostream>#include <string>using namespace std;
struct Estudiante { string nombre; int edad; float promedio;};
int main() { int n; cout << "Introduce el número de estudiantes: "; cin >> n;
Estudiante *estudiantes = new Estudiante[n];
for (int i = 0; i < n; i++) { cout << "Introduce los datos del estudiante " << i + 1 << endl; cout << "Nombre: "; cin >> estudiantes[i].nombre; cout << "Edad: "; cin >> estudiantes[i].edad; cout << "Promedio: "; cin >> estudiantes[i].promedio; }
cout << "Datos de los estudiantes:" << endl; for (int i = 0; i < n; i++) { cout << "Nombre: " << estudiantes[i].nombre << ", Edad: " << estudiantes[i].edad << ", Promedio: " << estudiantes[i].promedio << endl; }
delete[] estudiantes; // Libera la memoria return 0;}