03. Apuntadores: null y aritmética con apuntadores

Publicado por

Hasta el momento hemos aprendido lo básico sobre los apuntadores, sabemos cómo crearlos, cómo inicializarlos e incluso sabemos cómo mostrar su valor en pantalla si así lo deseamos. Se había comentado que si bien los apuntadores almacenan direcciones de memoria que pertenecen a alguna variable o constante, puede que en algún momento no queramos que estos apuntadores contengan algún valor específico, ¿qué hacer en estos casos?

Una dirección de memoria no deja de ser un valor con el que podemos realizar ciertas operaciones, entre estas podemos encontrar la suma, la resta e incluso podemos comparar el valor de dos apuntadores entre sí. ¿Sabías que en la aritmética con apuntadores importa el tipo de dato al que estamos haciendo referencia con el apuntador?

En esta nota daremos respuesta a las interrogantes que hemos planteado, te invito a continuar aprendiendo sobre cómo trabajar con apuntadores.


El término “null”

Independientemente de si lo hemos indicado explícitamente o no, un apuntador siempre contendrá un valor. Al momento de que hemos terminado de declarar nuestro apuntador, este llevará en su interior un valor basura. El tener un apuntador de este tipo puede resultar peligroso porque, en caso de que lleguemos a utilizar tal apuntador, no tenemos conocimiento alguno de a qué dirección esté refiriéndose y podría ocasionar que nuestro programa simplemente finalice de manera inmediata.

Existen ocasiones en las que podemos requerir de la existencia de un apuntador pero sin que este albergue un valor específico al momento de crearse. ¿En qué casos podemos necesitar esto? Un ejemplo que podemos mencionar es el de crear una lista enlazada. Una lista de este tipo consta de una secuencia de nodos, cada uno de los cuales, lleva un dato y un enlace al nodo posterior, puedes ver un ejemplo de esto en la imagen siguiente:

Lista enlazada
El último nodo lleva una «x» para representar que no tiene a qué apuntar.

Como puedes ver, si tenemos una lista con tres elementos, o nodos, el primero llevará un enlace al segundo, el segundo apuntará al tercero y el tercero… bueno, el tercero sería el nodo final. Este nodo no apuntaría a ningún otro sino hasta que agreguemos un nuevo elemento a la lista, entonces ¿qué hacemos mientras llega un nuevo nodo? Simple, decimos que el tercer nodo apunte a nada.

Si queremos que un apuntador se refiera a “nada”, podemos usar el tan famoso NULL. Este concepto es prácticamente exclusivo de los apuntadores. Si no deseas o no necesitas darle un valor a algún apuntador por el momento y, sobre todo, quieres evitar el error que te mencioné al principio de esta sección, simplemente inicialízalo a NULL.

NULL es básicamente lo mismo que el valor cero, puedes verlo en la siguiente sección de código:

int *ptr = 0; // Esto es lo mismo que si hubiéramos puesto: int *ptr = NULL;

¿Si puedo inicializar un apuntador a cero, puedo inicializarlo a cualquier otro valor numérico que yo quiera? La respuesta es un rotundo ¡no!

Inicializar un apuntador a cero no significa que estemos apuntando a la dirección cero, el compilador al ver esto comprende perfectamente que le queremos decir que el apuntador no se refiere a nada. Si tratamos de darle a algún apuntador un número cualquiera, el compilador nos marcará un error:

int *ptr = 10; // ERROR

int num = 45;
int *ptr_2 = num; // ERROR

¿Qué debo usar entonces, cero o NULL? La verdad es que puedes utilizar cualquiera de los dos, aunque, lo recomendable sería que siempre utilizaras NULL, esto para que tu código sea legible y evitar confusiones. Una buena forma de expresar mejor esto es si, por ejemplo, deseamos realizar algo si el apuntador no se está refiriendo a nada:

// 1ª forma
if (ptr == NULL) {
  // Haz algo
}

// 2ª forma
if (ptr == 0) {
  // Haz algo
}

¿Cuál de las dos formas anteriores crees que exprese mejor tus intenciones?


Aritmética con apuntadores

Sumar y restar valores que conocemos de toda la vida es sumamente sencillo. Si queremos sumar 2 unidades a alguna cantidad, esta aumentará exactamente 2 unidades. Si en su lugar deseamos quitarle 2 unidades, la cantidad se verá reducida exactamente por 2 unidades. ¿Existe alguna diferencia al momento de realizar estas operaciones sobre apuntadores? Veamos…

Imaginemos que tenemos 3 variables de tipo double y que cada una de estas se encuentran en direcciones contiguas de memoria. Ahora creamos un apuntador y le otorgamos la dirección de la primera variable, ¿qué sucedería si le sumamos una unidad a este apuntador? ¿A qué dirección se referirá ahora?

double val1, val2, val3;

double *ptr_double = &val1;
ptr_double++;

Nota: recuerda que si queremos modificar el valor del apuntador, ya no requerimos del operador asterisco.

Supongamos que ejecutamos nuestro programa imaginario y que la dirección de la primera variable es 200, ptr_double tendrá como primer valor este mismo 200. Si añadimos una unidad a esta dirección, podría parecer lógico que el resultado fuera 201 pero no es así, al sumarle una unidad, obtendremos el valor 208 ¿¡por qué!?

Aunque quisiera decirte que tus maestros de la primaria te han estado mintiendo, la verdad es que todo esto tiene que ver con el tipo de dato al que nos estamos refiriendo. Recordando un poco de lo que vimos en la primera nota, cada tipo de dato hace referencia al espacio que se ocupará en memoria, por tanto, si le agregamos a un apuntador “n” unidades, el resultado será añadir dicho número “n” multiplicado por el tamaño en bytes que ocupa el tipo de dato al que estamos apuntando.

Para entender un poco mejor esto, te dejo algunas imágenes para que observes qué resultado obtenemos al sumarle una unidad al apuntador en cuestión. En los 3 casos siguientes suponemos que, al momento de declararlos, los apuntadores tienen como valor inicial la dirección 200:

Sumar uno a un apuntador a char
char = 1 byte. A la dirección se le sumará (1 unidad * 1 byte) dando como resultado: 201
Sumar uno a un apuntador a short
short = 2 bytes. A la dirección se le sumará (1 unidad * 2 bytes) dando como resultado: 202
Sumar uno a un apuntador a int
int = 4 bytes. A la dirección se le sumará (1 unidad * 4 bytes) dando como resultado: 204

En lo que respecta a la resta, es precisamente lo mismo que hemos discutido, salvando claro el hecho de que ahora estaremos quitando unidades.

Una última operación que podemos realizar con apuntadores es la comparación, aquí podemos hacer uso de los operadores tan conocidos como mayor que, menor que, igual que, etc. Aunque las operaciones de comparación no son muy utilizadas, vale la pena mencionar su existencia.


Para terminar

Lo que vimos en esta nota son conceptos muy útiles y que frecuentemente llegan a provocar un poco de confusión en los que apenas inician a conocer sobre apuntadores. En verdad espero que ahora hayas aclarado varias dudas y que te sientas preparado para la siguiente entrega donde comenzaremos a hablar sobre cómo cambiar el valor de una variable por medio de un apuntador (por fin sabremos en qué otro caso podemos escribir el operador asterisco). De igual forma hablaremos un poco sobre arreglos para que los conceptos vistos en esta nota queden más claros y veas cómo puedes aprovecharlos .

Cualquier sugerencia, comentario o pregunta que tengas puedes colocarla directamente en los comentarios, estamos con toda la disposición de ayudarte. Espero que esta serie siga resultando de tu interés y nos vemos en la próxima. Hasta pronto.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *