Los punteros y elementos dinámicos en C++ con ejemplos y ejercicios resueltos

Los punteros en C++ (o apuntadores) son quizá uno de los temas que más confusión causan al momento de aprender a programar en C++, sin embargo verás que no es para tanto y que todo depende de dos elementos: el signo & (ampersand) y el * (asterisco) que los explicaré en breve. Durante este artículo verás entonces que no es para nada difícil hacer y usar punteros y que además son de gran ayuda al momento de necesitar valores y estructuras dinámicas, por ejemplo, para crear un array dinámico, con dinámico me refiero a que su tamaño puede ser establecido en tiempo de ejecución y lo mismo se puede hacer con las matrices (que en realidad son un array multidimensional).

Muy bien para comenzar veamos un pequeño ejemplo y luego su correspondiente explicación, una excelente forma de aprender. No te preocupes de entender todo con este primer ejemplo, pues durante el resto del artículo explicaré cada componente, su sintaxis y el final, cómo aprovechar dichas funcionalidades para nuestro beneficio junto con algún ejercicio.

Ejemplo de punteros

	
int variable; //Creamos un entero
int * apuntador = &variable;//Creamos una apuntador a la posición en memoria de "variable"
*apuntador = 20; //Le asignamos un valor a esa posición de memoria.

delete [] apuntador; //Después de operar con punteros es necesario liberar la memoria.
puntero = NULL;
	

Muy bien, ya hemos creado y usado nuestro primer puntero ¿Notaste el uso del asterisco y del ampersand? espero que sí y además de eso hay otros detalles que debemos considerar, veamos:

Detalles al crear y usar punteros en C++

Ya que sabemos algunos trucos y detalles sobre los apuntadores en C++, vamos a definir formalmente la utilidad del operador & y del asterisco.

Los punteros y el ampersand

El ampersand es un operador de C++ y es comúnmente utilizado para los punteros. Este operador nos permite obtener la dirección de memoria de una variable cualquiera y es justo esto (la dirección en memoria) lo que utilizan los punteros para referenciar valores.

Los apuntadores y el asterisco

El asterisco es, por decirlo de alguna forma, el operador por excelencia de los punteros. SU utilidad radica en que si el valor de dicho apuntador corresponde a una dirección de memoria, el asterisco nos permite resolverla y acceder al valor almacenado allí. Viéndolo desde otro enfoque, un apuntador es únicamente una dirección de memoria (un número) y el asterisco es el que hace la magia de obtener el valor referenciado por dicha dirección.

Veamos otro ejemplo con cada elemento detallado paso a paso

Ejemplo de apuntadores

	
char *apuntador = NULL; //Declaramos un puntero
//Es recomendable inicializar un puntero en null, para detectar errores fácilmente

char letra; //Declaramos una variable primitiva

apuntador = &letra; //Asignamos al apuntador la dirección de memoria de la variable primitiva

*apuntador = 'x'; //Modificamos la variable a través del apuntador

cout << letra; //Muestra x por pantalla
	

En este ejemplo vemos que podemos usar cualquier tipo de dato, que un puntero se puede inicializar independientemente y luego se le puede asignar su referencia correspondiente. Nótese que al asignar (línea 6) no utilizamos el asterisco, pues estamos definiendo la dirección de memoria y no el valor en dicha dirección (recuerda que el * resuelve la dirección de memoria y no es lo que requerimos en esa línea).

Ahora que hemos visto los ejemplos y tenemos claro el uso del ampersand y el asterisco podemos entonces realizar algunos ejercicios interesantes.

Ejercicios con punteros en C++

Parámetros por referencia

Usualmente al enviar un parámetro a una función todo lo que se haga con dicho parámetro allí adentro NO tiene efecto por fuera. Por ejemplo si a una función la se le envía una variable cuyo valor es diez y al interior de la función le sumamos un cinco, después de la ejecución de la función el valor de la variable seguirá siendo diez en vez de quince. Lo que pasó al interior de la función se quedó allí. Para solucionar esto, si queremos que el valor cambie definitivamente, usamos punteros para pasar no el valor del parámetro sino una referencia a éste (paso por referencia). Veamos:

	
#include "iostream"
#include "stdio.h"

using namespace std;

int funcion(int valor)
{
	valor = valor + 10; //Se le suma 10
	return valor;
}

int funcionPunteros(int* valor)
{
	*valor = *valor + 10; //Se le suma 10 a la posición en memoria
	return *valor;
}

int main()
{
	int numero = 10;

	cout << "Antes de funcion " << numero << "\n"; //10

	funcion(numero); //Se pasa por valor

	cout << "Despues de funcion " << numero << "\n"; //10

	cout << "Antes de funcionPunteros " << numero << "\n"; //10

	funcionPunteros(&numero); //Se envía la dirección de memoria y la función resuelve la referencia

	cout << "Despues de funcionPunteros " << numero << "\n"; //20 (10+10)

	system("pause");

	return 0;
}
	

Como podrás comprobar si ejecutas el código del ejercicio al llamar a "funcion" sólo enviamos el valor y por ende éste no es modificado por fuera de la función, con "funcionPunteros" estamos manipulando la posición en memoria del parámetro recibido (por eso usamos el *) y por ende al ejecutarla el valor de la variable se modifica. De ese modo ya hicimos el primer ejercicio con punteros en C++ y ya comprendemos el paso por referencia.

Array dinámico

Como mencioné al comienzo del artículo, por medio de apuntadores podemos crear arreglos o vectores dinámicos, es decir, un array al cual se le define su tamaño o capacidad durante la ejecución del código y no antes, lo cual nos permite definirle el tamaño deseado por el usuario.

Para este ejercicio retomaré el ejemplo del artículo de arreglos o vectores: Queremos crear un programa con el cual podamos guardar los títulos y los autores de diferentes libros sin perder ninguno de ellos. El usuario es el encargado de suministrar la información de cada libro. En esta ocasión ya sabemos usar punteros, así que será también el usuario quien nos diga cuántos libros desea ingresar, ya no necesitamos suponer que sólo ingresará 5 libros. Veamos:

	
#include "iostream"
#include "stdio.h"
#include "string"

using namespace std;

int main()
{
	string* titulos = NULL; //Se inicializa el puntero (inicia en null)
	string* autores = NULL; //Se inicializa el puntero (inicia en null)

	int tamanio ; //Se inicializa la variable

	cout << "Cuantos libros desea ingresar?";

	string entrada;

	getline(cin, entrada); //Se asigna el valor ingresado

	tamanio = stoi(entrada); //Se transforma la entrada en número

	titulos = new string[tamanio]; //Declaramos un arreglo del tamaño ingresado para los titulos
	autores = new string[tamanio]; //Declaramos un arreglo del tamaño ingresado para los autores

	cout << "Por favor ingrese la siguiente información de los Libros: \n";
    for(int i = 0; i < tamanio; i++)
    {
        cout << "\n******* Libro " << i + 1 << "********:\n";
        cout << "Titulo: ";
    	//cin >> titulos[i]; //No funciona con espacios
		getline(cin, titulos[i]);
    	cout << "Autor: ";
    	//cin >> autores[i]; //No funciona con espacios
		getline(cin, autores[i]);
    }

	//Liberamos la memoria de ambos punteros
	delete [] titulos;
	delete [] autores;
	titulos = NULL;
	autores = NULL;

	system("pause");

	return 0;
}
	

Así entonces tuvimos dos punteros, uno para todos los autores y otro para todos los títulos. Haciendo uso de ellos pudimos definir la cantidad de libros a ingresar por medio del usuario, es decir lo hicimos de manera dinámica, en tiempo de ejecución.

Matrices dinámicas

Así como lo hicimos con los array, también podemos tener matrices dinámicas y definir su tamaño, número de filas o número de columnas (o las dos) según sea necesario.

Para esto tomaré el mismo ejemplo de los libros, pero usando una matriz, en vez de dos vectores, tal y como se solucionó en la sección de matrices veamos:

	
#include "iostream"
#include "stdio.h"
#include "string"

using namespace std;

int main()
{
	int cols = 2; //El número de columnas es fijo (sólo título y autor)

    string** libros; //Si inicializa la matriz (punteros de punteros)

	int tamanio ; //Se inicializa la variable

	cout << "Cuantos libros desea ingresar?";

	string entrada;

	getline(cin, entrada); //Se asigna el valor ingresado

	tamanio = stoi(entrada); //Se transforma la entrada en número

	libros = new string*[tamanio];//Se asigna el número de filas según el usuario

    cout << "Por favor ingrese la siguiente información de los Libros: \n";
    string titulo ,autor;
    for(int i = 0; i < tamanio; i++)
    {
		libros[i] = new string[cols]; //Cada fila contendrá dos columnas
		//Notar que cols pudo haber sido ingresada por el usuario también

        cout << "\n******* Libro " << i + 1 << "********:\n";
        cout << "Titulo: ";
        getline(cin,titulo);
        cout << "Autor: ";
	    getline(cin,autor);
        libros[i][0] = titulo;
        libros[i][1] = autor;
    }

	//Para liberar la memoria debemos recorrer fila por fila primero.
	for (int i = 0; i < tamanio; ++i)
	{
		delete [] libros[i]; //Cada fila de libros es otro array de punteros
		//Por eso son punteros a punteros
	}

	//Luego de limpiar las columnas, quitamos la fila única que quedó
	delete [] libros;


	system("pause");

	return 0;
}
	

Este ejercicio es el perfecto para aclarar dudas o darse cuenta si realmente comprendes el concepto de apuntadores y su aplicación para arrays dinámicos. Debido a que la cantidad de columnas es fija, no se lo pedimos al usuario, simplemente lo declaramos con valor dos. Luego tenemos el puntero, pero no es un puntero cualquiera, al ser una matriz, será un puntero que tendrá otros punteros adentro, por eso se usa doble asterisco, luego se obtiene el tamaño del usuario (cantidad de libros) y al momento de inicializar la fila estamos indicando que es un arreglo de punteros, por eso se usa el * en la línea 23. Luego al interior del ciclo, cuando estamos llenando la matriz, debemos indicar que cada fila estará compuesta por un array de punteros de tamaño dos (dos columnas) y así construimos nuestra matriz dinámica.

Debes notar también que la liberación de la memoria es un poco más trabajosa, pues debemos ir fila por fila liberando la memoria de las columnas creadas y luego liberar la fila completa. Ahí podrás notar la diferencia en eficiencia y uso de memoria al usar arreglos o usar matrices.

Muy bien, es todo en esta función. Espero haber sido muy claro y que los ejemplos y ejercicios te hayan servido de mucho. En este momento ya debes saber qué es y cómo hacer un puntero en C++, para qué sirve el ampersand y el asterisco cuando estamos hablando de apuntadores en C++ y cómo crear arrays dinámicos y matrices usándolos. No te olvides de dejar un comentario.

Ahora podemos aprender acerca de Funciones en C++, nos leemos allí.

La última actualización de este artículo fue hace 1 año