21 de octubre de 2019

Arreglos de apuntadores a funciones.

   En diferentes tipos de programas y aplicaciones es frecuente encontrar menús de opciones para seleccionar una determinada acción u operación a realizar.

   Cuando muchas o todas las opciones presentadas en un menú comparten características similares, una excelente opción, aunque no la única, es implementar dichas opciones a través de un arreglo de apuntadores a función.

   En el Ejemplo 4.3 se desarrolló una calculadora con las operaciones aritméticas básicas de suma, resta, multiplicación y división. En esta entrada se retomará dicho ejemplo para reescribirlo utilizando un arreglo de apuntadores a funciones.

   El Ejemplo 7.13 muestra que las cuatro funciones de suma, resta, multiplica y divide comparten un patrón común: todas reciben dos argumentos de tipo float y regresan un valor de tipo float.

   El patrón identificado se aprovecha en la línea 15, la cual declara el arreglo funciones de cuatro apuntadores a funciones que reciben dos argumentos de  tipo float y regresan un valor de tipo float. El arreglo es inicializado en la declaración con una lista de identificadores: suma, resta, multiplica y divide.

   El uso del arreglo de apuntadores funciones se presenta en la línea 29, en donde el valor de la variable op se utiliza como índice para invocar a la función correspondiente que realizará la operación seleccionada en base al menú de opciones (líneas 37-51).

   En este sentido, si op tiene el valor tres por ejemplo, la expresión:

( * funciones[op - 1]) ( a, b )

se transforma en la expresión:

multiplica(a, b)

debido a que el identificador que se encuentra en la posición dos (op - 1) del arreglo de apuntadores a funciones funciones, es precisamente multiplica. La resolución de los otros identificadores ocurre de manera análoga.

   El llamado a la función a invocar se resuelve en tiempo de ejecución, ya que el llamado a través del arreglo de apuntadores a funciones de la línea 29 es un llamado implícito.

   Finalmente, la función menu indica con void que no recibirá ningún tipo de argumento. Todos los demás detalles del programa de ejemplo ya deberían resultarle familiares al lector. Una posible salida para el Ejemplo 7.13 se muestra en la siguiente figura:

Una posible salida del Ejemplo 7.13.
   Cabe mencionar por último que la forma de utilizar los arreglos de apuntadores a funciones mostrada en el Ejemplo 7.13 no es su única posibilidad de uso, sino que es una de tantas en el amplio abanico de opciones en la programación estructurada; en este sentido, la limitante respecto al uso de apuntadores a funciones estará en general más en función de la imaginación y capacidad del programador, que de las múltiples posibilidades de su uso y aplicación.

7 de enero de 2019

Abstracción de datos.

   La abstracción es una característica, al menos hasta donde sabemos, inherente a la especie humana. Ahora bien, desde el punto de vista de la programación, por medio de la abstracción se puede comprender o describir un concepto sin tener que entrar en los detalles de su representación o especificación.

   En todo el blog se ha utilizado dicho concepto en forma de diagramas, imágenes o figuras de las representaciones de algunos de los tipos de datos en la memoria de la computadora, o de las estructuras de datos presentadas (como los arreglos y las matrices).

   Al escuchar conceptos como casa, automóvil, etcétera, se genera de manera casi inconsciente en nuestra mente una representación de ellos, y ésta podría variar de persona a persona, pero en esencia, la idea sería esencialmente la misma.

   En este sentido, si alguien nos solicitara dibujar en una hoja de papel nuestro concepto de casa o de automóvil, con toda seguridad habría unos dibujos más elaborados que otros, algunos con más detalles y otros con menos, pero sería poco probable que, en condiciones "normales" alguien dibujara una casa con una vaca en lugar de un techo o una pared; alguien podría quizá colocar en su dibujo la silueta de una chimenea, pero sería poco probable que alguien dibuje un piano sobre el techo.

   Para el caso de un automóvil, alguna persona podría dibujar un auto deportivo o quizá algo más modesto, pero al igual que antes, sería poco probable que alguien dibujara un automóvil que, en lugar de ruedas, tuviera unas guitarras eléctricas. La abstracción es en este sentido lo que nos permite comunicarnos con otras personas o colegas a un nivel conceptual.

   En las entradas subsecuentes se echará mano de un par de mecanismos utilizados en C para reforzar la abstracción en la programación a través de la representación de "nuevos" tipos de datos: struct y typedef. Cabe mencionar desde ahora que en realidad no se generarán nuevos tipos de datos, ya que un tipo de dato requiere de una representación, una interpretación, y un conjunto de operaciones bien definidas y establecidas asociadas a él; de ahí que sólo se utilizarán, y en todo caso se agruparán, los tipos de datos existentes de manera conveniente, para proporcionar o dotar al programador de una mejor abstracción.

Conceptos, representación y estructura.
   Una estructura (struct) es una colección de una o más variables de tipos de datos posiblemente diferentes, agrupadas bajo un solo nombre para un mejor y más conveniente manejo de las mismas.

   Las estructuras ayudan a organizar mejor los datos mejorando así la abstracción y la representación de información para el programador.

   Un ejemplo clave de una estructura es la representación de los empleados de una nómina. Un empleado se describe en dicho contexto por un conjunto de atributos o características, tales como (entre otros):
  • Número de empleado.
  • Nombre(s).
  • Apellido paterno.
  • Apellido materno.
  • Clave Única de Registro de Población (CURP).
  • Número de seguro social.
  • Puesto.
  • Domicilio.
  • Teléfono.
  • Salario.
   En C no es posible declarar una variable de tipo empleado o persona por ejemplo, ya que no existen dichos tipos de datos; sin embargo es posible, con los tipos de datos existentes, abstraer de manera conveniente variables que permitan representar objetos que sirvan como un tipo de datos.

   Una estructura (struct) en C tiene la siguiente estructura (la redundancia es necesaria):

         struct identificador{
               tipo_de_dato1 lista_de_identificadores1;
               tipo_de_dato2 lista_de_identificadores2;
                             .
                             .
                             .
               tipo_de_datoN lista_de_identificadoresN;
         };


donde:
  • identificador es un identificador válido en C y denota el nombre de la estructura.
  • tipo_de_dato es alguno de los tipos de datos de C.
  • lista_de_identificadores es una lista de identificadores separada por comas, para cada una de las variables definidas.
   Observe que una estructura en C puede contener diferentes tipos de datos y distintas variables de cada uno de ellos.

   A cada una de las variables que se declaran dentro de una estructura se les denomina: elemento miembro de la estructura, y cada elemento miembro se distingue por su respectivo identificador, el cual fue definido en la lista_de_identificadores.

   Al igual que antes, no puede haber identificadores repetidos para los elementos miembro de una estructura determinada. Los identificadores o nombres de las variables de los elementos miembro de una estructura son locales a ella y sólo pueden accederse a través de una variable del tipo correspondiente, tal y como se mostrará en el Ejemplo 8.1 de la entrada Abstracción en acción.

   Los elementos miembro de una estructura e1 pueden ser arreglos, apuntadores, e incluso variables cuyo tipo de dato se derive de otra estructura e2 por ejemplo, el único requisito será que e2 esté definida antes que e1.

   El número de estructuras que se pueden definir está limitado por las necesidades del programador, C no impone ninguna restricción en este sentido.

   Una estructura sólo define una plantilla o molde que agrupa otras variables. Es importante recalcar que por sí misma no crea las variables de sus elementos miembro, sólo las declara. Los elementos miembro de la estructura se crean cuando se define una variable cuyo tipo es una estructura, de ahí que se diga que las estructuras definen nuevos tipos de datos. En C una variable puede declararse varias veces pero sólo puede definirse una vez, la definición de una variable se da cuando ésta se crea y existe propiamente, mientras que la declaración sólo determina su existencia.

   Las estructuras en C se pueden copiar, asignar, pasar a funciones, y ser regresadas como valor de retorno de una función; sin embargo, no existe una relación de orden predefinida, es decir, no pueden ser comparadas.

   Sean e1 y e2 dos variables derivadas de la misma estructura, las operaciones:

e1 < e2, e1 > e2, e1 <= e2, e1 >= e2, e1 == e2, e1 != e2 

son inválidas.


12 de diciembre de 2018

Errores frecuentes.

   La siguiente es una lista de errores frecuentes observados en mis estudiantes o lectores del blog. Es una lista necesariamente incompleta y en frecuente actualización; sin embargo la considero de utilidad para el lector interesado en aprender de las experiencias de otros. Sobre todo si se considera que muchas veces se aprende más de los errores que de los aciertos.

   He tratado de clasificar los errores para facilitar al lector su ubicación, espero que ésta sea también de utilidad.

Sintaxis:
  • Omitir el punto y coma (;)  al final de las sentencias.
  • Olvidar las llaves de bloque asociadas a una estructura de control, cuando el número de sentencias es mayor a uno.
 Lectura de datos:
  • Olvidar el ampersand (&) antes del identificador de variable: 
    • Incorrecto: scanf("%d", variable_int);
    • Correcto:   scanf("%d", &variable_int);
  • Utilizar secuencias de escape en la función scanf:
    • Incorrecto: scanf("%d\n", &variable_int);
    • Correcto:   scanf("%d", &variable_int); 
  • Utilizar el especificador de formato incorrecto:
    • Incorrecto: scanf("%d",  &variable_float);
    • Correcto:   scanf("%f",  &variable_float);
    • Incorrecto: printf("Valor %d",  variable_float);
    • Correcto:   printf("Valor %.2f",  variable_float);
  • Utilizar el especificador de precisión para flotantes en la función scanf:
    • Incorrecto: scanf("%.2f", &variable_float); 
    • Correcto:   scanf("%f", &variable_float);
 Impresión de datos:
  • Utilizar el especificador de formato incorrecto:
    • Incorrecto: printf("El valor es %d\n", variable_float);
    • Correcto:   printf("El valor es %.2f\n", variable_float);
Estructuras de control:
  • Omitir las llaves del bloque cuando el cuerpo de la estructura de control tiene más de una sentencia.
  • Utilizar el operador de asignación "=" en lugar del operador de comparación "==".
Variables:
  • Pensar que las variables son automáticamente inicializadas con algún valor cuando no es así, utilizando en consecuencia valores "basura".
  • No inicializar (normalmente con cero) variables de tipo acumulador. 
 Funciones:
  • Olvidar colocar el tipo de dato a cada variable en la lista de parámetros.
  • Invocar a una función con más o menos parámetros de los que necesita, o con tipos de datos distintos a los especificados en la lista de parámetros.
  • Colocar corchetes a los arreglos cuando se pasan como argumento a una función (invocación o llamado de función): los arreglos se pasan como argumentos solamente con su identificador. Consulte más abajo la sección referente a Arreglos.
  • Hacer lectura de datos dentro de una función: si la función NO es de lectura de datos, la función no debe leer datos en su definición. Esta es una consideración de diseño más que un error en sí mismo.
  • Hacer impresión de datos dentro de una función: si la función NO es de impresión de datos, la función no debe imprimir los datos que gestiona en su definición. Esta es una consideración de diseño más que un error en sí mismo.
  • En una "biblioteca" personalizada de funciones, si una función A utiliza a una función B, B debe estar definida antes que A.
  • Invocar funciones que regresan un apuntador con el operador de des referencia. Consulte más abajo la sección referente a Apuntadores.
Arreglos:
  • No definir el tamaño de un arreglo.
  • Definir el tamaño de un arreglo estático con el valor de una variable. La mayoría de los compiladores modernos soportan esta característica sin problema; sin embargo, en el estándar ANSI C hacer esto es incorrecto, por lo que su programa estaría sacrificando su portabilidad.
  • No inicializar los elementos del arreglo cuando su valor inicial será utilizado.
  • Colocar más inicializadores que elementos en el arreglo.
  • Omitir el ampersand (&) en la lectura de elementos individuales en el arreglo (para un arreglo de enteros):
    • Incorrecto: scanf("%d", arreglo[i]); 
    • Correcto:   scanf("%d", &arreglo[i]);
  • Colocar corchetes al pasarle un arreglo a una función:
    • Incorrecto: funcion(arreglo[ ], n);
    • Correcto:   funcion(arreglo, n);
Apuntadores:
  • No utilizar el operador de des referencia cuando se quiere acceder al valor de a lo que apunta el apuntador (el valor al que hace referencia el apuntador): 
    • Incorrecto: printf("El valor es: %d\n", intPtr);
    • Correcto:   printf("El valor es: %d\n", *intPtr);
  • Utilizar un apuntador y asumir que se tiene espacio de almacenamiento como en un arreglo. Error común con cadenas:
    • char *s1; es distinto de char s2[100]
      • Incorrecto: fgets(s1, 100, stdin);
      • Correcto:   fgets(s2, 100, stdin); 
  • Utilizar el operador de des referencia en la invocación a funciones que regresan un apuntador. Ejemplo, considere (vea el Ejercicio 12 de la entrada Ejercicios selectos (apuntadores)):
    • int *arreglo, original[100]; (apuntador a entero y arreglo) y el siguiente prototipo int *copia(const int *original, int n); 
      • Incorrecto: *arreglo = *copia(original, n);
      • Incorrecto: *arreglo = copia(original, n);
      • Incorrecto:  arreglo = *copia(original, n);  
      • Correcto:    arreglo = copia(original, n);
Estructuras (struct):
  • Acceder a los elementos miembro de una estructura con el operador incorrecto:
    • Si la variable es de tipo estructura, se utiliza el operador punto (.).
    • Si la variable es de tipo apuntador a estructura, se utiliza el operador flecha (->).
  • Para una estructura definir elementos miembros de otra estructura que no está definida:
    • Sean e1 y e2 dos variables de tipo struct distintos: e2 puede tener elementos miembro del tipo de e1 si y sólo si el tipo struct de e1 está definido antes que e2.
Archivos:
  • Utilizar las funciones fwrite y fread para archivos de texto. Los archivos de texto se usan con fprintf, fscanf, fputs, fgets, etc.
  • Utilizar las funciones fprintf, fscanf, fputs, fgets, etc. para archivos binarios. Los archivos de binarios se usan con las funciones fwrite y fread.
Errores más frecuentes del curso
Errores más frecuentes que se cometen al tomar el curso de forma presencial.