28 de septiembre de 2016

Abstracción en acción.

   El segundo mecanismo de abstracción aunque simple, resulta bastante útil tanto para la abstracción como para la auto documentación de programas: typedef.

   La sentencia typedef de C se utiliza para renombrar tipos de datos existentes, y en armonía con struct, constituyen los aliados perfectos para elevar el nivel de abstracción en los programas.

Números racionales.
   Un número racional (p / q) se define como el cociente de dos números enteros, donde p, q son elementos del conjunto de los números enteros (Z).

   El lenguaje de programación C no tiene un tipo de datos racional que permita manipular variables de la forma p / q; sin embargo, es posible abstraer su representación con los tipos de datos existentes

   Observe las líneas 6-9 del Ejemplo 8.1 en las cuales se ha definido una estructura de nombre r con dos elementos miembro de tipo int p y q. Note que lo que se ha hecho es representar la definición de un número racional utilizando los elementos del lenguaje C.

   La plantilla o molde que se ha definido para generar variables que contengan dos números enteros es struct r. Observe que las variables p (línea 7) y q (línea 8) se han colocado por separado aunque su tipo de dato (int) es el mismo; lo cual es más una cuestión de estilo que una característica inherente a la definición de la estructura.

   Por otro lado, note que en la línea 14 se declara la variable r1 de tipo struct r. Es hasta ese momento cuando se genera el espacio en memoria necesario para almacenar y gestionar una variable u objeto r1 de tipo struct r.

   Es ahora cuando la utilidad de typedef toma relevancia. La línea 11 le indica al compilador que de ahí en adelante la expresión RACIONAL será exactamente equivalente a la expresión struct r, y por esta razón, la variable r2 (línea 15) y res (línea 16) son idénticas en estructura, forma, y tipo a la variable r1 de la línea 14. El typedef renombra la estructura con un nuevo identificador y aunque no es una restricción del lenguaje, se recomienda como estándar de programación, que los identificadores que representan "nuevos" tipos de datos se escriban con letras mayúsculas.

   Las variables del tipo struct r o RACIONAL lucen como se muestra en la siguiente figura:

Representación de la estructura (forma) de las variables de tipo struct r o RACIONAL del Ejemplo 8.1.
 
    Ahora bien, el acceso a los elementos miembro de una estructura se realiza a través del operador punto (.). El acceso a los elementos miembro de una estructura no sigue ningún orden específico ni está relacionado con el orden en que aparecen dichos elementos en su declaración; el acceso a los elementos miembro está únicamente condicionado a las necesidades específicas del problema en cuestión, y la sintaxis general es:

estructura.elemento_miembro;

en donde:
  • estructura es el identificador de una variable de tipo estructura (struct).
  • elemento_miembro es alguno de los elementos miembro de la estructura estructura.
   La línea 21 muestra la forma de acceder a los elementos miembro de una estructura. Note que el acceso a los elementos miembro es para leer los números racionales r1 y r2 desde la entrada estándar, que para cada uno se lee su numerador (p) y denominador (q) respectivamente, y que el acceso a los elementos miembro va precedido por el operador &.

   La cadena con los especificadores de formato de la función scanf de la línea 21 ("%d %*c %d %c %d %*c %d"), requiere de una explicación para el especificador %*c, el cual, le indica a la función scanf que ignore cualquier símbolo introducido en esa posición. Note que de hecho no es almacenado ya que resulta útil y conveniente en la representación de los números racionales, pero no para el almacenamiento, tal y como lo muestra las siguientes figuras donde se ilustra la forma de introducir números racionales para una posible ejecución del Ejemplo 8.1.

Una posible salida para los Ejemplos 8.1 y 8.2 con un operador no válido.
Una posible salida para los Ejemplos 8.1 y 8.2 con un operador válido.
 
    El ciclo do-while de las líneas 19-24 realiza una validación básica: el denominador no puede ser cero para ningún número racional.

   La sentencia switch de las líneas 26-42 determina la operación a realizar. Note que las operaciones de suma y resta no han sido especificadas y se dejan como ejercicio para el lector.

   Finalmente, observe que las líneas 31-34 representan la siguiente expresión:

r1 * r2 = (p1 / q1) * (p2 / q2) = (p1 * p2) / (q1 * q2)

donde r1 y r2 son dos números racionales (Q) definidos como:

r1 = p1 / q1 con p1, q1 números enteros (Z) y  q1 != 0
r2 = p2 / q2 con p2, q2 números enteros (Z) y  q2 != 0

y que las líneas 35-38 representan la expresión:

r1 / r2 = (p1 / q1) / (p2 / q2) = (p1 * q2) / (q1 * p2)

mientras que las líneas 44-45 presentan el resultado de las operaciones en un formato de salida intuitivo y conveniente para el usuario.

   Las restantes operaciones aritméticas con números racionales se definen a continuación:
  • Suma: r1 + r2 = (p1 / q1) + (p2 / q2) = [ (p1 * q2) + (p2 * q1) ] / (q1 * q2)
  • Resta: r1 - r2 = (p1 / q1) - (p2 / q2) = [ (p1 * q2) - (p2 * q1) ] / (q1 * q2)

Evolución.
El Ejemplo 8.2 es una evolución del Ejemplo 8.1; son esencialmente iguales por lo que no se ahondará más de lo necesario en su descripción y sólo se destacarán las siguientes diferencias:
  1. La estructura que modela o representa a los números racionales (líneas 9-11), ha sido simplificada a un renglón y fusionada con el typedef. Note que también se ha omitido el identificador del struct debido a que la estructura es renombrada desde su definición.
  2. Se incluye la función leeExpresion (líneas 13 y 55-66), la cual recibe dos argumentos de tipo apuntador a RACIONAL. Note que la función encapsula las sentencias para la lectura de datos (líneas 17-24) del Ejemplo 8.1. La función muestra el uso del operador flecha (->), el cual sirve para acceder a los elementos miembro de una estructura cuando son referidos por medio de una variable de tipo apuntador. Compare cuidadosamente la línea 21 del Ejemplo 8.1 con la línea 60 del Ejemplo 8.2.
  3. La función hazOperacion (líneas 14 y 31-53), la cual recibe dos argumentos de tipo RACIONAL y uno de tipo char. La función regresa un valor de tipo RACIONAL mismo que representa el resultado de la operación que se realiza. Observe que la función encapsula las sentencias que determinan y llevan a cabo la operación aritmética (líneas 26-42) del Ejemplo 8.1. Note que la línea 49 del Ejemplo 8.2 hace uso de la función exit, la cual hace que el programa termine como si llegara a su fin y es equivalente al return 0 de la línea 41 del Ejemplo 8.1. La función exit se encuentra definida en la biblioteca estándar stdlib.h (línea 7 del Ejemplo 8.2).
   Compruebe que la salida del Ejemplo 8.2 es idéntica a la mostrada en las figuras anteriores si se procesan los mismos datos.

   Finalmente, las siguientes figuras muestran lo que sucede desde el punto de vista del paso de parámetros para las funciones involucradas, ya que la función leeExpresion usa parámetros por referencia (apuntadores):

Paso de parámetros por referencia para la función leeExpresion().

mientras que la función hazOperacion utiliza parámetros por valor (copia de datos):

Paso de parámetros por valor para la función hazOperacion().