27 de noviembre de 2019

Arreglos de n dimensiones.

   Con base en lo descrito en las entradas anteriores, es posible realizar una generalización para declarar y manipular arreglos n-dimensionales o de n dimensiones ya que:
  • Un arreglo es una sucesión contigüa de variables de un mismo tipo de datos.
  • Un arreglo de dos dimensiones o matriz es un arreglo de arreglos.
  • Un arreglo de tres dimensiones o cubo (si las tres dimensiones son iguales) es un arreglo de arreglos de dos dimensiones, o un arreglo de matrices.
  • Un arreglo de cuatro dimensiones o hipercubo (si las cuatro dimensiones son iguales) es un arreglo de arreglos de tres dimensiones, o un arreglo de cubos para el caso de los hipercubos.
  • Un arreglo de n dimensiones es un arreglo de arreglos de n-1 dimensiones.
   En general, la declaración de un arreglo de n dimensiones tiene la siguiente estructura en el lenguaje de programación C:

tipo_de_dato   arreglo_n_dimensional[TAMAÑO1] [TAMAÑO2] . . . [TAMAÑO_N];

en donde:
  • tipo_de_dato es cualquier tipo de dato válido en C.
  • arreglo_n_dimensional es un identificador válido en C.
  • TAMAÑO1 es la longitud para la primera dimensión.
  • TAMAÑO2 es la longitud para la segunda dimensión.
  • TAMAÑO_N es la longitud para la n-ésima dimensión.
   Las siguientes figuras muestran dos posibles representaciones para un arreglo c de tres dimensiones de 4 x 4 x 4. La figura (a) muestra dicho arreglo representado como un cubo; mientras que la figura (b) muestra el mismo arreglo como una representación de malla o arreglo de matrices. Asegúrese de entender la ubicación de los elementos del arreglo tridimensional en ambas representaciones.

(a) Representación de un arreglo de tres dimensiones en forma de cubo.
(b) Representación de un arreglo de tres dimensiones en forma de malla.
 
    El Ejemplo 6.9 muestra cómo representar en C (línea 11) la abstracción ilustrada en las figuras anteriores.

   Observe cómo para la función imprimeCubo, en la línea 24 se especifica el tamaño de la segunda y tercera dimensión del arreglo c, ya que para arreglos de n dimensiones en general, se debe especificar el tamaño de todas las n-1 dimensiones a partir de la segunda, la única opcional es la primera.

   Note también cómo por cada dimensión se tiene un ciclo que controla el recorrido de la misma, y por consiguiente, cada ciclo tiene su propia variable de control. En este orden de ideas, es posible deducir que un arreglo de cuatro dimensiones requerirá de cuatro ciclos y de cuatro variables de control, un arreglo de cinco dimensiones cinco ciclos y cinco variables de control, y así sucesivamente.

   Finalmente, es importante hacer notar que aunque el Ejemplo 6.9 presenta las tres dimensiones iguales para el arreglo cubo esto no tiene por qué ser siempre así; de hecho, cada una de las tres dimensiones que definen un arreglo tridimensional pueden ser distintas.

   La salida del Ejemplo 6.9 se  muestra en la siguiente figura. Note cómo la impresión de cubo se ha hecho en la forma de un arreglo de matrices. Apóyese en la figura anterior (b) para visualizarlo mejor.

Salida del Ejemplo 6.9.

28 de octubre de 2019

Funciones de biblioteca.

   Desde el primer ejemplo de este blog se ha estado haciendo uso de funciones: main es una función, printf, scanf, getchar, rand y srand también son funciones, y al uso de una función se le denomina invocación o llamado de función.

   Considere el problema de calcular las raíces de una ecuación de segundo grado utilizando la fórmula general mostrada en la siguiente fórmula:

Fórmula general para encontrar las raíces de una ecuación cuadrática.
 
    Una posible solución a dicho problema es el programa que se muestra en el Ejemplo 4.1, el cual es el ejemplo más elaborado hasta ahora en cuanto se refiere a los elementos que contiene, y con excepción de las líneas 6, 26 y 27, debería ser completamente comprendido. La línea 6 le indica al compilador que incluya la biblioteca de funciones matemáticas (math.h), ya que en las líneas 26 y 27 se hará uso de la función sqrt (square root), la cual calcula la raíz cuadrada de su argumento.

   Las líneas 26 y 27 muestran el uso de una función de biblioteca (sqrt), y a dicho uso se le denomina invocación o llamado de función. Piense en la invocación o llamado de función como el equivalente a presionar el botón correspondiente de una calculadora para obtener el cálculo de la raíz cuadrada de un número por ejemplo.

   Sin la función sqrt, el Ejemplo 4.1 no podría ser resuelto tan sencillamente, a menos que esas líneas fueran substituidas por el algoritmo correspondiente para calcular la raíz cuadrada de un número y no sólo eso, sino que el código en C de dicho algoritmo tendría que ser repetido dos veces al menos, en base a como está escrita la propuesta de solución del Ejemplo 4.1.

   Las funciones de las bibliotecas del lenguaje son cajas negras: se puede saber lo que realizan, los datos que necesitan para trabajar, y la salida que proporcionan, pero no se sabe con certeza cómo es que lo hacen porque no se tiene acceso a su código fuente.

   Al igual que las funciones matemáticas, las funciones en C operan sobre un conjunto de datos mismos que procesan o transforman y finalmente regresan un valor; pero tome en cuenta que no todos los problemas se basan en, o modelan funciones matemáticas, y que por otro lado, no todas las funciones que se pudieran necesitar para resolver un problema se encuentran disponibles en las bibliotecas de funciones del lenguaje, por lo que en muchos casos, se necesitarán construir funciones de elaboración propia, con servicios y funcionalidades específicas y para ello, es preciso conocer su estructura y cómo es que se definen.

   Una posible salida para el programa del Ejemplo 4.1 se muestra en la siguiente figura:

Una posible salida del Ejemplo 4.1.
 
    Tome en consideración que, dependiendo del compilador o de la distribución de GNU/Linux que utilice (si es el caso), es probable que el programa del Ejemplo 4.1 requiera del argumento "-lm" para su compilación. Para más detalles al respecto, puede consultar la entrada respecto a la compilación en línea utilizando el compilador de GNU/Linux.
 

Ejemplos de definición de funciones.

   Considere el Ejemplo 4.2. Las líneas 7 y 8 constituyen los prototipos de funciones y constituyen la firma de la función, en cuanto a que le indican al compilador que se definirán dos funciones: mayor y menor, mismas que recibirán tres parámetros enteros (int) y regresarán un valor entero.

   Tanto los tipos de datos como el número de parámetros así como el identificador de la función, están en relación directa con las necesidades del problema a resolver. Para el caso del Ejemplo 4.2, se definirán dos funciones que determinan (regresan), el mayor y menor respectivamente, de tres números enteros.

   Note que en las líneas 18 y 19 se realiza el llamado de las funciones mayor y menor respectivamente, y que el valor regresado por cada una de ellas se utiliza en el contexto del especificador de formato “%d” de la función printf correspondiente, el cual está asociado al tipo de dato de retorno de la función (int).

   Note también en las líneas 18 y 19, que a las funciones mayor y menor se les están pasando los datos a, b y c, los cuales son variables de la función main (línea 11), que a su vez contienen valores específicos (línea 15), y que son transferidos o enviados a las funciones mayor y menor respectivamente como sus argumentos.

   Por otro lado, las líneas 24-46 corresponden a la definición de las funciones mayor y menor. Las líneas 24-34 definen el grupo de sentencias que componen a la función mayor (note las llaves que definen el inicio y fin del bloque de función). Observe la estructura que sigue esta función, compárela con la estructura general de una función en C, y asegúrese de comprender la equivalencia.

   La línea 24 establece (define) que la función identificada como mayor, el cual es el identificador o nombre de la función, regresa un valor de tipo entero (int), y recibe tres parámetros de tipo entero (int) identificados por x, y y (x, y y z constituyen la lista de parámetros de la función).

   Ahora bien, la línea 25 define una variable local a la función mayor identificada como max. En C es posible definir variables al inicio de cada bloque ( { ... } ), y los bloques de función no son la excepción. Se pueden definir tantas variables locales como se necesiten y del tipo que se necesiten, y se siguen las mismas reglas de declaración de variables que para la función main.

   Las líneas 27-31 son sentencias de comparación que, a través de estructuras de selección, sirven para determinar el mayor de tres números distintos. La idea subyacente es la siguiente:
  1. Se selecciona a una de las variables como la mayor, supongamos que es x, y por lo tanto la asignamos a max (línea 25).
  2. Se compara la segunda variable y con el valor de la variable supuestamente mayor (max), si y > max (línea 27), entonces max no es la mayor y se almacena en max el valor mayor hasta ese momento (línea 28). En caso contrario, max tiene el valor mayor entre x y y y no hay necesidad de intercambiar.
  3. Se compara la tercera variable z con el valor de la variable hasta el momento mayor (max), si z > max (línea 30), entonces max no es la mayor y se almacena en max el valor mayor (línea 31). En caso contrario, no hay necesidad de intercambiar, max tiene el valor mayor de x, y y z.
   La línea 33 contiene la palabra reservada return seguida de la variable max, y al igual que en main, regresa el valor contenido en max a quien llamó a la función mayor, que para el caso del Ejemplo 4.2 ocurrió en la línea 18.

   Las líneas 36-46 para la función menor, se describen de manera análoga a las de la función mayor de las líneas 24-34.

   Una posible salida para el Ejemplo 4.2 se muestra en la siguiente figura:

Una posible salida del Ejemplo 4.2.
 
    Finalmente, note que es posible definir más de una función en un programa, de hecho no hay un límite en ese sentido, por lo que se pueden definir tantas funciones como sean necesarias, y todas siguen las mismas reglas descritas hasta aquí.

   Por otro lado, el Ejemplo 4.3 implementa una calculadora básica con las cuatro operaciones aritméticas. Las líneas 6-10 muestran los prototipos de las funciones que se definirán en las líneas 37-58. Dichos prototipos deberían ser claros en lo que expresan, con excepción quizá del prototipo de la línea 6.

   El prototipo de función de la línea 6, le indica al compilador que se definirá una función que no regresa nada (void). En en lenguaje de programación C, las funciones que sólo realizan alguna labor (como la presentación de un mensaje por ejemplo), pero que no necesitan regresar algún valor, usan éste tipo de dato. El tipo void es un tipo de dato muy importante, y su utilidad se retomará en la entrada referente a apuntadores, por ahora, basta con saber que ésta es una de sus utilidades.

   Ahora bien, el bloque (cuerpo) de la función principal main se encarga de leer los datos (línea 19), de verificar que sean los apropiados o que cumplan con ciertas características (ciclo do-while), y de tomar la decisión de qué servicio (función) solicitar (llamar), en respuesta (estructura switch) al operador op leído (línea 19), para finalmente presentar el resultado (línea 32).

   La definición de funciones está de las líneas 37-58, y se describen de la siguiente manera:
  • La función instrucciones (líneas 37-42), se utiliza para mostrar un mensaje en la salida estándar que contiene instrucciones básicas del uso del programa. 
  • La función suma (líneas 44-46) es bastante simple, ya que regresa a quien la llame, el valor de la expresión de suma (a + b) de los parámetros a y b. Observe que aunque es posible, no es necesario guardar el resultado de dicha expresión en una variable local y después regresarla, de hecho, es muy común en C  escribir este tipo de expresiones como valor de retorno. 
  • Las funciones resta (líneas 48-50), multiplica (líneas 52-54), y divide (líneas 56-58), se describen de manera análoga a la función suma.
   Note que la función divide no hace ningún tipo de verificación del denominador para realizar la operación de división, debido a que asume que ya se ha realizado dicha verificación en alguna otra parte del programa (main línea 20), y que el denominador es distinto de cero. El programa del Ejemplo 4.3 es sólo un posible diseño a dicha situación, mismo que no es el único ni necesariamente el mejor, sin embargo funciona y refuerza el concepto de programación modular que se ha venido mencionado.

   Finalmente, tome en cuenta que para el programa del Ejemplo 4.3 ninguna de sus funciones, excepto main, hace uso de otra función para completar su tarea debido a su simplicidad, sin embargo, es posible hacerlo y se mostrará en ejemplos posteriores. Una posible salida para el Ejemplo 4.3 se muestra en la siguiente figura:
Una posible salida del Ejemplo 4.3.