Las estructuras son una forma en que podemos agrupar varias variables para, posteriormente, lograr referirnos a ellas como una entidad completa. Claro que es posible crear cualquier programa sin llegar a utilizar ninguna estructura pero, en ocasiones, es mucho más conveniente su uso para poder dar un significado más lógico a lo que estamos realizando.
El hecho de que podamos agrupar varias variables en una estructura nos pone a pensar que el tamaño en bytes de esta podría ser realmente grande. Si quisiéramos, por ejemplo, pasar una estructura por valor a una función, veríamos que tendría que copiarse una gran cantidad de información. En esta nota veremos cómo los apuntadores nos ofrecen una solución a este problema.
Sintaxis de apuntador a estructura
No tiene nada de especial crear un apuntador hacia una variable de tipo estructura, es exactamente lo mismo que crear un apuntador hacia cualquier otra variable, aún así, deja te muestro un ejemplo:
/* Tenemos nuestra estructura */ struct Punto { int x; int y; } /* Tenemos una variable del tipo anterior y la inicializamos */ struct Punto p1; p1.x = 0; p1.y = 0; /* Creamos nuestro apuntador y lo inicializamos */ struct Punto *ptr_p1 = &p1;
¡Listo! Tenemos nuestro apuntador con la dirección de una variable de tipo struct Point
. Como recordarás por la nota anterior, es posible acceder a una variable para modificar o leer su valor haciendo uso de algún apuntador. ¿Cómo realizamos esto para acceder a los campos de la estructura?
Básicamente no existe diferencia con el método normal para acceder a un campo (un punto después del nombre de la variable, seguido del nombre del campo), solo queda agregar nuestro operador de desreferencia (*) y estaremos bien. No olvides que dicho operador es uno de los que tienen mayor precedencia en el lenguaje C o C++, por tanto, siempre es mejor asegurarse de que este operador sea el primero que actúe sobre nuestra variable. Veamos el siguiente ejemplo:
/* Usamos el material del ejemplo anterior */ (*ptr_p1).x = 5; (*ptr_p1).y = 10; /* Si accedemos a la estructura, veremos que sus campos han sido modificados */ std::cout << "X: " << p1.x << std::endl; std::cout << "Y: " << p1.y << std::endl; // -- SALIDA -- // X: 5 // Y: 10
Aunque no lo creas, acceder a los campos de una estructura haciendo uso de algún apuntador es algo bastante común, por tanto, se decidió colocar syntactic sugar para que esta acción fuera más sencilla y sobre todo legible para los desarrolladores. Por si te lo preguntas, syntactic sugar no es más que un término que hace referencia a algo que se añadió a un lenguaje de programación para ofrecer una misma funcionalidad ya implementada pero haciendo uso de un estilo diferente. Un ejemplo de esto último sería al momento de realizar una operación aritmética sobre una misma variable:
int a = 20; /* Sumamos un valor a la variable de forma normal */ a = a + 5; /* La acción de arriba pero haciendo uso de "syntactic sugar" */ a += 5;
Así pues, si queremos acceder al campo de una estructura por medio un apuntador, podemos irnos olvidando del operador de desreferencia y de los paréntesis extra y solo hacer uso del operador flecha (->). Veamos cómo se usa:
// Retiramos el asterisco, los paréntesis y sustituimos // el punto por el operador flecha ptr_p1->x = 4; ptr_p1->y = 8; // IMPORTANTE: Si utilizas el operador flecha, el compilador // supondrá que la variable sobre la que estás actuando es un apuntador. p1->x = 0; // ERROR. Esto es igual a (*p1).x = 0
Normalmente se nos habla del operador flecha pero sin que nos enseñen qué es lo que realmente está pasando detrás, aquí vemos que más que un operador, solo es un agregado para facilitar una expresión.
Pasando estructuras por referencia
Recordemos que la pila es una región en la memoria donde se van almacenando los parámetros, las variables y los valores de retorno de cada una de las funciones que llamamos. Con base en esto, observa que si enviamos una estructura con varios campos a una función, la pila tendrá que generar espacio suficiente para guardar todos y cada uno de estos valores. Este proceso de “copiar” toma tiempo y, además, podría ocasionar un desbordamiento de la pila, provocando que el sistema operativo “mate” a nuestro programa por querer acceder a regiones de memoria no permitidas.
Tal y como lo expresamos al inicio de esta nota: ¿cómo serían de ayuda los apuntadores en este caso? Haciendo uso del famoso paso por referencia. A diferencia del paso por valor, donde cada parámetro no es más que una copia de la variable que le pasamos, en el paso por referencia tenemos que entregar solo la dirección de la variable.
El hecho de que estemos pasando una dirección como parámetro a una función, no la excluye de ser copiada en la pila. ¿Cuál es el beneficio, entonces? Que la dirección solo tomaría 8 bytes de espacio, a diferencia de lo que tomaría copiar una estructura que contiene 5 enteros, es decir, 20 bytes en total (los tamaños en bytes tanto para el apuntador como para el entero dependen del compilador). ¿Qué pasaría si nuestra estructura tuviera más estructuras como campos? Seguro que puedes imaginarlo.
Para terminar
En esta ocasión no solo vimos cómo crear un apuntador para referirnos a un tipo de dato en particular, en este caso las estructuras, sino que también vimos un caso práctico en el que los apuntadores pueden resultar ser muy útiles para mejorar el rendimiento de nuestros programas.
En la siguiente nota veremos algo sobre los apuntadores a función, otro tema que resulta confuso para varias personas pero que, si has llegado hasta esta parte de la serie, seguro no tendrás mayor problema en llegar a dominarlo.
Cualquier duda o sugerencia que tengas puedes expresarla en los comentarios, espero esta nota haya ayudado a que despejaras varias dudas. Nos vemos en la próxima. Hasta entonces.
Excelente trabajo
Muchas gracias por leernos, Emiliano.
¡Saludos!
Son los mejores, me gusto mucho la manera como lo explicaron de una manera muy clara mejor que muchos libros
Muchas gracias, le entendí mejor a usted que a mi maestra.