La Diferencia entre entender y comprender, en Lenguaje C

Lic. Guillermo Cherencio

Requisitos:Conocimientos basicos de Lenguaje C, punteros, estructuras de control y de repetecion

Objetivo:Desmenuzar conceptualmente un problema simple, hasta lograr una correcta implementacion.

Problema a resolver:

    Diseño propuesto:
  • Utilizar un arreglo de 10 elementos de tipo double para almacenar las ventas (vtas[])
  • Utilizar una estructura repetitiva del tipo "para" (for) sobre la variable i, cuyo valor inicial es 0, su valor final es 10, se incrementa de 1 en 1. Esto permitira utilizar a i para "variar" el elemento del arreglo vtas que queremos ingresar. Por lo tanto, en cada vuelta del ciclo "para", podemos ingresar vtas[i].
  • De esta forma, evitamos tener que tipear 10 veces la operacion de ingreso por teclado y si luego se amplian o quitan sucursales, se requeririan cambios importantes en el codigo y ademas tendriamos gran repeticion de codigo.

Vamos a comprobar si los datos ingresados estan correctamente cargados en la memoria. Mostramos los valores ingresados por teclado

Vamos a realizar la sumatoria de las ventas de las sucursales y vamos a mostrar la sumatoria (esto podria haberse hecho mientras se ingresaban las ventas, pero simplemente vamos a hacerlo ahora por una cuestion de simplicidad). Para ellos vamos a utilizar un variable de tipo acumulador (acumulador = acumulador + valor a acumular o bien acumulador+=valor a acumular), llamado total en donde acumularemos cada elemento del arreglo vtas[]. Para ello tambien utilizaremos otro ciclo o bucle de tipo "para" (for), identico a los anteriores.

Vamos a realizar el promedio de ventas, tomando total y dividiendo por 10. Mostramos el promedio de ventas. Observe que el divisor es 10.0 y no 10.

Con esta ultima version del programa, podemos decir que resolvimos el problema.

Como siempre, cabe preguntarnos: ¿Resolvimos correctamente este problema? ¿Que criticas podemos hacerle? ¿Hay codigo repetido? ¿Como podriamos evitarlo? ¿Que cosas podrian cambiar con el tiempo?

Este tipo de cuestiones no son faciles de contestar cuando recien comenzamos a programar, no obstante, debemos estar atentos e ir aprendiendo o considerando estas cuestiones a medida que aprendemos.

Les propongo ir avanzando en mejorar este codigo de tipo espagueti...

Definitivamente no resolvimos correctamente este codigo, es demasiado especifico, muy "a medida" (por ende, muy particular, digamos muy poco generico y eso es muy malo). ¿Que puede cambiar? obviamente el numero de sucursales, ¿Que podemos hacer? Analizar el codigo y ver en que lugares deberiamos cambiarlo en caso de que cambie el numero de sucursales. Utilizar el pre-procesador de C para definir una macro llamada NUMERO_DE_SUCURSALES que ahora, casualmente vale 10, pero que en el futuro podria tomar otro valor:

Hasta incluso queda mas legible (entendible) el codigo. Observe el calculo del promedio, el cual fue modificado para aprovechar mejor la macro creada. Pruebe de modificar el valor de NUMERO_DE_SUCURSALES, recompilar el codigo, ejecutarlo nuevamente y ver si el mismo reacciona correctamente. Este codigo es mas legible, porque es mas ABSTRACTO, se aleja de la maquina y se acerca al hombre. Debemos hacer codigo "lo mas humano posible", pues ello sera entonces "lo mas entendible y lo mas modificable".

Esta podria ser una solucion a la cuestion del cambio de la sucursal, no obstante, requiere recompilar el codigo. Puede solucionarse este problema, incluso, sin recompilar el codigo, entonces, deberiamos informarle de alguna forma al programa aquello que puede "variar", en este caso, el numero de sucursales. Es decir, desde el exterior debemos informar al programa, hay varias formas de hacer eso, una posible y facil es utilizar la linea de comandos, supongamos que el programa se llama suc6, entonces lo ejecutamos haciendo: $ ./suc6 podriamos cambiar eso y hacer que el programa se ejecute de la siguiente manera: $ ./suc6 10 en donde, 10 es el numero de sucursales (en este caso). Deberiamos controlar que el usuario efectivamente lo ejecute de esta forma, avisandole y evitando errores. Esto puede hacerse utilizando el arreglo de apuntadores a char * que el sistema operativo le pasa a la funcion main(), en el primer elemento del arreglo queda el nombre del programa, en el segundo elemento queda el primer valor que le pasamos al programa y asi sucesivamente, en nuestro caso, debemos utilizar el primer valor que le pasamos al programa:

    Observese:
  • El programa ahora tiene 3 retornos distintos. Esto permite informar al sistema operativo distintas situaciones: 0 indica que esta todo Ok, 1 indica que esta incorrecto el numero de parametros, 2 indica que el formato del parametro es erroneo.
  • Si bien es una variable entera ordinaria, NUMERO_DE_SUCURSALES lo pusimos en mayusculas, como si fuese una constante. Pues no se espera que su valor cambie y actua como parametro de esta aplicacion y facilita su lectura.
  • argv[0] apunta al nombre del programa, argv[1] apunta al numero de sucursales (en forma de cadena de caracteres), por ello se utiliza la funcion atoi() (atoi -> array to int, de cadena a entero) para convertir "10" en 10.
  • No se avanza en la ejecucion del programa hasta no asegurarse de que se tiene un numero entero mayor que cero en la variable NUMERO_DE_SUCURSALES.
  • Debe cambiar la forma de ejecucion de este programa en geany (Proyecto/Propiedades/Construir Ejecutar "./%e" 10 o el numero de sucursal que corresponda), pues sino, saldra siempre por retorno 1.

Volviendo a las preguntas anteriores, deberiamos tambien pregutarnos: "Conceptualmente, ¿Que Hace este programa?" Basicamente realiza operaciones (ingresar, mostrar, sumar, promediar) sobre un arreglo de tipo double. Y ¿Que otras operaciones podrian hacerse sobre un arreglo de tipo double? Ordenar (ascendente o descendente), obtener el numero mas grande, obtener el numero mas pequeño, etc.. Entonces, generalizar, abstraer este programa es pensarlo en terminos de una libreria (y pensar en libreria es pensar en reuso de codigo): podria diseñarse una libreria para manejo de arreglos de tipo double, que implemente funciones que hagan las operaciones antes descriptas y este programa seria un cliente o usuario de esta libreria!. Libreria que bien podria ser util en muchos otros problemas aparte de las sucursales.

    Pensemos el diseño de la libreria:
  • Una libreria que hace operaciones sobre arreglos de numeros, en este caso, double; pero bien mañana podria aparecer la necesidad de hacer lo mismo sobre arreglos de tipo int, float, char, etc. y hasta incluso, con nombres similares (ordenar,sumar,mostrar...), por lo tanto, vamos a agregar las letras "_dbl" para diferenciarlas de otras que pudieran existir en el futuro
  • Las funciones a implementar serian: ingresar_dbl(), mostrar_dbl(), sumar_dbl(), promediar_dbl(), ordenar_dbl(), mayor_dbl(), menor_dbl()

Si pretendemos hacer estas operaciones sobre un arreglo, arreglo que es externo a la operacion misma, es decir, el arreglo podria existir dentro de main() y alli se puede invocar a la funcion sumar_dbl(), pero el arreglo esta afuera de sumar_dbl(), no podemos declarar un arreglo dentro de sumar_dbl() porque no sabemos a priori sobre que arreglo debemos trabajar ni cuantos elementos tiene. Incluso el arreglo podria ser bien grande con lo cual tampoco podemos pensar en copiar el arreglo dentro de cada funcion para luego hacer la operacion, seria muy ineficiente. Conclusion: la unica forma de implementar estas funciones es a traves de punteros, las funciones deberian recibir -al menos- dos datos desde el exterior: puntero al comienzo del arreglo y cantidad de elementos del arreglo.

Entonces podemos ir deduciendo los prototipos de estas funciones. Vamos con las mas faciles. Comenzamos con sumar_dbl(), sumar implica recorrer el arreglo e ir acumulando sus valores para producir un nuevo dato: la sumatoria, por lo tanto, esta funcion podria retornar un double que seria la sumatoria, va a necesitar un puntero al comienzo del arreglo (double *) y la cantidad de elementos a sumar (int), entonces el prototipo seria: double sumar_dbl(double *,int). Idem con el promedio: double promediar_dbl(double *,int). Veamos el codigo de estas dos funciones:

Ambas funciones son muy similares, p es el puntero al comienzo del arreglo, n es la cantidad de elementos; la estructura "para" (for) hace "variar" a i desde 0 hasta n - 1 (dentro del for), terminando i con el valor n (fuera del for), por lo tanto, el for da tantas vueltas como elementos a sumar, por cada vuelta se incrementa en 1 el valor de i y se incrementa el puntero para que apunte al siguiente elemento del arreglo. A medida que se hace eso, voy acumulando en una variable double local (suma) la sumatoria "de lo apuntado por p" (*p). Podemos probar el funcionamiento de estas dos funciones incorporandolo al codigo del programa de la siguiente forma y comparando los resultados que obtenemos:

Ejecute el programa. Los valores de totales y promedios ambos deben dar igual. Una buena estrategia es implementar funciones, probarlas y luego refinarlas.

¿Como pueden refinarse estas funciones? Habiamos observado que el codigo es bastante similar, es mas, es casi redundante... por lo tanto, algo parece andar mal (en terminos conceptuales), tal vez ya se dio cuenta... , lo que esta mal es la funcion promediar_dbl(), pues un promedio es, una abstraccion mayor a una sumatoria, es algo que esta por encima de una sumatoria, es una sumatoria y una division. Y aqui radica la diferencia entre entender y comprender, en terminos de C. Si Ud. realmente comprende, deberia poder expresar la funcion promediar_dbl() en terminos de sumar_dbl() y todo el codigo se reduce a ...

A una sola linea de codigo C !!

Si se le ocurrio el codigo de la funcion promediar_dbl() tal como esta aqui, lo felicito!, Ud. comprendio.

Si no se le ocurrio el codigo de la funcion promediar_dbl() tal como esta aqui, no se desanime!, verifique nuevamente todo el proceso, relea este documento, piense, analice el codigo, modifiquelo, pruebelo y siga trabajando, esto recien comienza!.

Atte. Lic. Guillermo Cherencio
ISFT 189 Programacion I, Prog. Orientada a Objetos, Practica Profesional
UNLu Bases de Datos, Programacion III
UTN FRD Sistemas Operativos