21 de septiembre de 2016

Aritmética de apuntadores.

   Las variables de tipo apuntador son variables que almacenan direcciones en memoria de otras variables pero finalmente son variables, por lo que es posible realizar algunas operaciones aritméticas sobre los valores que almacenan.

   Las operaciones válidas de apuntadores son [Kernighan]:
  1. Asignación de apuntadores del mismo tipo.
  2. Adición de un apuntador y un entero.
  3. Sustracción de un apuntador y un entero.
  4. Resta de dos apuntadores a miembros del mismo arreglo.
  5. Comparación de dos apuntadores a miembros del mismo arreglo.
  6. Asignación del valor cero.
  7. Comparación con cero.
   Cualquier otra aritmética de apuntadores es ilegal; por lo tanto, no es legal sumar dos apuntadores, multiplicarlos o dividirlos, sumarles un valor float, o realizar asignaciones entre diferentes tipos de apuntadores sin una conversión de tipo forzada (cast).

   Cuando se suma o resta un entero a un apuntador, dicho entero representa una escala que está en función del tamaño en bytes del tipo de datos a los que apunta el apuntador.

   Para visualizar mejor lo anterior, suponga que ptr es un apuntador a int que hace referencia al primer elemento de un arreglo de int, y que n es también un int, con 0 < n < TAM (donde TAM representa el tamaño del arreglo), entonces la expresión:

ptr += n;
ó
ptr = ptr + n;

indica al compilador que ptr se mueva o avance al n-ésimo elemento respecto de su posición actual.

   En resumen, no se está realizando una suma convencional de una dirección de memoria con un entero, sino una suma escalada en función del tipo de datos. Así, si cada int se almacena en cuatro bytes por ejemplo, a la dirección que almacena el apuntador se le asigna ptr + (4 * n), y en general:

ptr = ptr + (sizeof(tipo_de_dato) * n)

donde tipo_de_dato es el tipo de dato del elemento al que hace referencia el apuntador y sizeof es un operador de C que determina el número de bytes que utiliza tipo_de_dato.

   En la entrada siguiente referente a "Apuntadores y Arreglos" se hará uso de éste tipo de notación; por ahora, el Ejemplo 7.3 muestra el uso del modificador const con apuntadores y el uso de aritmética de apuntadores para realizar recorridos de cadenas.

   La línea 8 establece que se definirá una función de nombre imprimeSinEspacios que no regresará nada y que recibirá un apuntador constante a char, lo cual significa que dentro de la función, no se podrá modificar, a través del apuntador cPtr (línea 22), el elemento al que se haga referencia a través de él. La línea 9 se describe de manera análoga a la línea 8, pero para la función imprimeAlReves.

   Por otro lado, la función main se encarga de definir un palíndromo y almacenarlo en una cadena (línea 12), imprimir dicha cadena (líneas 14 y 17), y enviársela a las funciones imprimeAlReves e imprimeSinEspacios (líneas 15 y 16 respectivamente).

   La función imprimeSinEspacios (línea 22), realiza un recorrido de la cadena a través del apuntador cPtr y el ciclo while. Observe cómo el apuntador se va moviendo por los distintos elementos de la cadena (línea 26) haciendo referencia a cada uno de ellos uno a la vez (líneas 24 y 25). La función hace lo que su nombre indica: imprime sin espacios (línea 24) la cadena a la que hace referencia cPtr sin modificar la cadena.

   Finalmente, la función imprimeAlReves (línea 31) realiza un recorrido de la cadena (línea 35) para llegar al final de ella a través del apuntador cPtr y el ciclo while. Una vez que se llega al final de la cadena, se utiliza otro ciclo while (línea 36) para regresar, a través de cPtr, a la posición de inicio que se respaldó en inicioPtr (línea 32). La función hace lo que su nombre indica: imprime al revés la cadena a la que hace referencia cPtr sin modificar la cadena.

   El Ejemplo 7.3 hace uso de la aritmética de apuntadores más usual. Note que se está incrementando (líneas 26 y 35) y decrementando (línea 36) el apuntador cPtr, las cuales son operaciones clave de recorridos de arreglos con apuntadores. La salida de dicho ejemplo se muestra en la siguiente figura:

Salida del Ejemplo 7.3.
   La biblioteca estándar de funciones ctype.h incluye un conjunto de funciones para clasificar y transformar caracteres individuales. Se recomienda la revisión de esta biblioteca en algún manual de referencia o guía del lenguaje C, aquí sólo se presenta un muy breve resumen de algunas de las funciones que incorpora:
  • isalnum: regresa 1 si su argumento es alfanumérico y 0 si no lo es.
  • isalpha: regresa 1 si su argumento pertenece al alfabeto (inglés) y 0 si no pertenece.
  • isdigit: regresa 1 si su argumento es un dígito decimal y 0 si no lo es.
  • isxdigit: regresa 1 si su argumento es un dígito hexadecimal y 0 si no lo es.
  • islower: regresa 1 si su argumento es una letra minúscula y 0 si no lo es.
  • isupper: regresa 1 si su argumento es una letra mayúscula y 0 si no lo es.
  • ispunct: regresa 1 si su argumento es un carácter de puntuación y 0 si no lo es.
  • tolower: regresa su argumento convertido a una letra minúscula.
  • toupper: regresa su argumento convertido a una letra mayúscula.
   El Ejemplo 7.4 muestra el uso de las funciones islower y toupper, el resto de las funciones trabajan de manera análoga.

   Respecto al Ejemplo 7.4, note que el símbolo de fin de cadena se ha colocado ahora como una constante simbólica (línea 8), misma que es utilizada por el ciclo for de la línea 24 como parte de su expresión condicional. En este sentido, note también que ahora el recorrido de la cadena referida por cPtr se realiza con un ciclo for en lugar de un ciclo while, y que como el recorrido se realiza a través del apuntador cPtr, no es necesaria la expresión de inicialización de variables de control para el ciclo for por lo que ésta aparece vacía.

   La función convierteAMayusculas (líneas 23-27) verifica si el carácter al que hace referencia cPtr es una letra minúscula (línea 25) y si lo es, lo convierte a mayúscula y lo almacena en la misma posición en donde estaba dicho carácter (línea 26).

   Una posible salida del Ejemplo 7.4 se muestra en la siguiente figura. Note cómo solamente son afectados los caracteres en minúscula, todos los demás caracteres, incluyendo los espacios, son ignorados, es decir, se dejan sin modificación en la cadena que los contiene.

Una posible salida del Ejemplo 7.4.