17 de junio de 2016

La estructura de control do-while.

   La estructura de control o ciclo do-while tiene la siguiente estructura general:

                         do {
                             sentencia(s);
                         } while (expresión);

   El ciclo do-while procesa o ejecuta el grupo de sentencia(s) delimitadas por su bloque de manera repetida, mientras la expresión al ser evaluada sea distinta de cero, es decir: expresión != 0.

   La llave izquierda “{” delimita el inicio de su bloque, mientras que la llave derecha “}” delimita su final.

   El conjunto de sentencia(s) delimitadas por el bloque del ciclo do-while se procesan al menos una vez, y cuando la secuencia de ejecución alcanza el indicador de fin del bloque y la palabra reservada while, se evalúa la expresión. Si expresión != 0, la estructura do-while modifica el flujo de ejecución secuencial para que brinque automáticamente a la palabra reservada do, y se repita el procesamiento de las sentencia(s) delimitadas por su bloque. En caso contrario, se procesa la siguiente sentencia que se encuentre después de la palabra reservada while.

   Observe que la descripción y el funcionamiento de la estructura do-while es similar al de la estructura while, con la diferencia de que la expresión en el ciclo while es evaluada al principio, mientras que en el ciclo do-while es evaluada al final. En base a esto, se tiene que:
  • Las sentencias de la estructura de repetición while se repiten de 0 a n-veces.
  • Las sentencias de la estructura de repetición do-while se repiten de 1 a n-veces.
   En esencia, esta es la única diferencia que existe entre las estructuras de repetición while y do-while. No olvide tenerlo en mente.

   Un ejemplo de uso de la estructura de repetición do-while se muestra en el Ejemplo 3.11. Observe la semejanza con el Ejemplo 3.9, y note que la principal diferencia radica, aparte del uso del ciclo do-while, en la expresión condicional de la línea 15, ya que se hace uso del operador de pos incremento para la variable de control en la expresión condicional, sin embargo, compruebe que la salida es exactamente la misma para ambos ejemplos.

   La estructura de repetición do-while ha sido relegada y poco apreciada por muchos programadores, pero tiene nichos de aplicación muy específicos y concretos en los que resulta más que conveniente.

   El Ejemplo 3.12 retoma el problema de indeterminación para la división discutido en la entrada correspondiente a la estructura de control if pero solucionado desde otro enfoque: repetir la solicitud del denominador y su respectiva lectura, mientras el denominador sea igual a cero, que es precisamente lo contrario a lo que se requiere.

   Aunque al principio lo anterior podría parecer contradictorio, si se analiza y reflexiona con detenimiento la aparente contradicción desaparece: el ciclo do-while de las líneas 12-15 repiten el prompt cuando no se obtiene lo deseado (que el denominador sea distinto de cero), es una lógica inversa, pero el truco está en entender que no se desea que se repita el prompt, pero si el denominador no es como se espera (distinto de cero), entonces se tiene que repetir el prompt. Por lo tanto, la expresión condicional se expresa en términos de dicha situación: la negación de distinto de cero es igual a cero.

   Observe que la línea 17 puede ejecutarse ya sin ningún tipo de verificación, ya que cuando la estructura de control secuencial la procese, es porque la validación hecha en el ciclo do-while se aseguró de que el denominador es distinto de cero, ya que en caso contrario, el flujo de control seguiría dentro del ciclo.

   Por último, es importante mencionar que es posible reescribir todo lo que se escriba con un ciclo while con un ciclo do-while y viceversa, pero existen nichos específicos en los que uno conviene más que el otro, y la lectura de datos acompañada de su respectiva validación se expresan de manera más natural con una estructura de repetición do-while.


13 de junio de 2016

La estructura de control switch.

   La estructura de control para la selección switch tiene la siguiente estructura general:

                    switch (expresión) {
                        case expresión_constante1:
                            sentencia(s)1;
                            break;
                        case expresión_constante2:
                            sentencia(s)2;
                            break;
                            .
                            .
                            .
                        case expresión_constanteN:
                            sentencia(s)N;
                            break;
                        default:
                            sentencia(s)N + 1;
                    }

   La estructura de control switch evalúa la expresión y compara el resultado con el valor de la expresión_constante1 después de la palabra reservada case; si coinciden, entonces se procesan o ejecutan el grupo de sentencia(s)1 (sentencia(s)1, sentencia(s)2, sentencia(s)N o sentencia(s)N + 1 puede ser cualquier sentencia(s) válida(s) en el lenguaje de programación C, como por ejemplo, otras estructuras de control (de selección y de repetición) incluida ella misma, a lo cual se le denomina: estructuras de control anidadas) hasta encontrar la palabra reservada break. En caso de no coincidir, la estructura switch compara el resultado de la expresión con el de expresión_constante2, si coincide, se procesan o ejecutan el grupo de sentencia(s)2 hasta encontrar la palabra reservada break. El proceso descrito continúa de manera análoga para todos los valores de expresión_constante que contenga la estructura switch.

   Por otro lado, el grupo de sentencia(s)N + 1 se procesan o ejecutan sólo si el resultado de la expresión no coincidiera con ninguna de las N expresiones_constantes, lo cual representa el caso por omisión (default).

   Debe resaltarse la importancia en el uso de la palabra reservada break, ya que si se omite, el compilador no generará ningún tipo de error, pero una posible consecuencia sería, por ejemplo, algunos minutos de "entretenidas" sesiones de depuración.

   Al igual que antes, observe que la llave izquierda “{” delimita el inicio del bloque de la estructura switch, mientras que la llave derecha “}” delimita su final.

   El Ejemplo 3.7 muestra el uso de la estructura switch. El programa lee un número entero, y a través de esta estructura de control, “convierte” el número en su correspondiente palabra.

   La estructura de selección switch trabaja de la siguiente manera:  la línea 12 evalúa el valor de la variable num y lo compara con el valor que se encuentra después de la primera palabra reservada case (línea 13); si coincide, entonces se procesa la sentencia de la línea 14 y a continuación la de la línea 15, la cual le indica a la estructura de selección switch que termine (procesar el break sería equivalente a procesar la llave de fin de bloque de la estructura switch), y procese la primera sentencia que se encuentre después del final de su bloque. En caso de no coincidir, el proceso de comparación se repetiría con los valores de las líneas 16 y 19, para finalmente procesar la sentencia default, en caso de no existir coincidencia.

   Experimente eliminando del Ejemplo 3.7 una, dos, o las tres sentencias break en diferentes combinaciones. No olvide compilar de nuevo el programa en cada modificación que realice. La intención de esta prueba controlada es la de proporcionarle una valiosa experiencia en la omisión intencional de la sentencia break.

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

Una posible salida del Ejemplo 3.7.


El operador ternario ? :

   El lenguaje de programación C incorpora un operador muy versátil y sumamente útil para escribir expresiones condicionales compactas: el operador ternario (? :).

   El operador ternario tiene la siguiente estructura general:

          valor = expresión1 ? expresión2 : expresión3

donde la expresión1 es evaluada primero, y si es distinta de cero (expresión1 != 0), entonces se evalúa la expresión2 y su resultado es asignado a valor; en caso contrario, se evalúa la expresión3 y su resultado es asignado a valor.

   Note que el operador ternario es una estructura de selección if-else compacta. En este sentido, es importante señalar que el operador ternario tiene su correspondiente equivalencia representada en dicha estructura de selección. Asegúrese de comprender la equivalencia:

                    if (expresión1)
                        valor = expresión2;
                    else
                        valor = expresión3;

   La principal ventaja del operador ternario radica en lo compacto y la expresividad, pero en general es posible representar en una estructura de selección if-else cualquier expresión que haya sido escrita utilizando dicho operador.

   Considere el Ejemplo 3.6, el cual determina el mayor de dos números enteros. La línea 12 y 13 pudieron haber sido escritas en una sola, es decir, el valor retornado por el operador ternario “? :” pudo haber sido utilizado directamente como el valor a imprimir por el especificador de formato “%d” de la función printf de la línea 13. Se propone como ejercicio realizar dicha modificación y comprobar su equivalencia.

   Una posible salida para el Ejemplo 3.6 se muestra en la siguiente figura.

Una posible salida para el Ejemplo 3.6.


La estructura de control if.

   La estructura de control if tiene la siguiente estructura general:

                    if (expresión) {
                        sentencia(s);
                   }

   La estructura de selección if procesa o ejecuta el grupo de sentencia(s) (puede ser cualquier sentencia(s) válida(s) en el lenguaje de programación C, como por ejemplo, otras estructuras de control (de selección y de repetición), incluida ella misma, a lo cual se le denomina: estructuras de control anidadas) delimitadas por su bloque, si al evaluar la expresión es distinta de cero, es decir: expresión != 0.

   La llave izquierda “{” indica el inicio del bloque correspondiente a la estructura, mientras que la llave derecha “}” delimita su final.

   Si al ser evaluada la expresión ésta es igual a cero, se ignora el grupo de sentencia(s) delimitadas por el bloque de la estructura, es decir, no se procesan (no son ejecutadas), y la ejecución continúa con la primera sentencia que se encuentre después del delimitador de fin de bloque del if.

   El Ejemplo 3.1, además de mostrar el funcionamiento de la estructura de selección if, muestra el uso de los operadores relacionales presentados en la siguiente tabla:

Operador     Descripción
  ==            Igual que
    !=             Distinto de
                  <              Menor estricto que
                  >              Mayor estricto que
                <=             Menor o igual que
                >=             Mayor o igual que

   A partir del Ejemplo 3.1 sólo se hará mención explícita de las líneas de código que introduzcan algún elemento nuevo o diferente respecto de lo hasta ahora comentado en los ejemplos y entradas anteriores con la finalidad de hacer más ágil la explicación.

   Lo primero a comentar aparece en la línea 12, la cual introduce una nueva modalidad en la lectura de datos a través de la función scanf: es posible leer más de un dato en un solo llamado a la función. Los dos especificadores de formato “%d” le indican a la función scanf que lea dos enteros decimales de la entrada estándar.

   Observe que al igual que con la función printf, por cada especificador de formato debe existir su correspondiente variable asociada para su almacenamiento, y es responsabilidad del programador el que también exista una correspondencia entre el especificador de formato y el tipo de dato de la variable.

   Por otro lado, la línea 14 muestra el uso de la estructura de selección if y el operador relacional de igualdad==”. La forma de interpretar en nuestro lenguaje dicha sentencia es: “Si num1 es igual que num2, ejecuta la sentencia de la línea 15”.

  No está de más volver a enfatizar que las llaves ({ }) delimitan el bloque de código asociado a la correspondiente estructura de control. Una práctica muy común en C y que se recomienda usar con precaución o preferiblemente no utilizar, es la omisión de los delimitadores de bloque para las estructuras de selección y repetición, si se omiten, únicamente se considera, como parte de la estructura de control, la sentencia inmediata posterior a ella, ninguna otra, sin importar que la sangría o el espaciado de las demás sentencias sugieran otra cosa.

   Continuando con el ejemplo, la línea 17 se puede interpretar como: “Si num1 es distinto de num2, ejecuta la sentencia de la línea 18”; mientras que la línea 20: “Si num1 es menor estricto que num2, ejecuta la sentencia de la línea 21”; las líneas 23, 26 y 29 tienen una interpretación análoga.

   Las líneas 15, 18, 21, 24, 27 y 30 deberían ser ya totalmente comprendidas en función de los ejemplos anteriores. Una posible salida del Ejemplo 3.1 se muestra en la siguiente figura:

Una posible salida del Ejemplo 3.1.
 
  Es importante que pruebe y experimente con otros datos, y que se asegure de cubrir todos los casos considerados en las expresiones condicionales de las estructuras de selección if. Esta actividad es conocida como pruebas de caja blanca o transparente, y la validación de todas las sentencias dentro del código de un programa, es una labor relacionada con el concepto de complejidad ciclomática, y aunque la explicación de estos conceptos está fuera de los alcances de este blog, su mención y presentación no.

   Ahora bien, en base al funcionamiento de la estructura de selección if, la explicación realizada, y considerando el caso del intento de división por cero que se mencionó en la entrada Estructuras de control, ¿se le ocurre algo para prevenir dicha indeterminación?

   En este sentido, el Ejemplo 3.2 es una primera propuesta de solución a la indeterminación matemática. Dicha propuesta está dada esencialmente por la línea 15, ya que en ella se realiza la verificación del denominador utilizando la estructura de selección if, y el operador relacional de comparación!=”.

   Si el denominador (den) es distinto de cero (línea 15), entonces es posible realizar la división (línea 16), y presentar el resultado (línea 17).

   Observe que si el denominador es cero, simplemente no se realiza la división y no se presenta ningún tipo de información en la salida estándar lo cual, además de parecer poco cordial e informativo es incorrecto, no desde la perspectiva de la lógica de funcionamiento, sino desde la perspectiva del usuario del programa, ya que no basta con escribir programas funcionales, sino también útiles.

   Por lo anterior, sería bueno proporcionar al usuario algún tipo de notificación acerca de lo que ha ocurrido, para ello, se requiere de algo que permita al programador seleccionar entre un grupo de sentencias u otro en función de una determinada situación, y ese algo es la estructura de control para la selección de sentencias if-else, misma que se describirá en la siguiente entrada. Por ahora, el Ejemplo 3.2 valida y previene una operación de indeterminación matemática, haciéndolo lógicamente funcional.

   Una posible salida para el programa del Ejemplo 3.2 se muestra en la siguiente figura. Al igual que antes, pruebe y experimente con otros datos, genere otras salidas, y verifique qué hace el programa cuando el denominador es cero.

Una posible salida del Ejemplo 3.2.

   Por último, es importante mencionar que en el lenguaje de programación C la división de enteros es entera sin importar que la división no sea exacta, esto quiere decir que en la división de enteros la parte fraccionaria es descartada y sólo se conserva la parte entera. Más adelante se introducirán nuevos tipos de datos (float y double), los cuales proporcionarán un mejor manejo de números fraccionarios.

La estructura de control while.

   La estructura de control o ciclo while tiene la siguiente estructura general:

                    while (expresión) {
                        sentencia(s);
                   }

   El ciclo while procesa o ejecuta el grupo de sentencia(s) delimitadas por su bloque de manera repetida, mientras la expresión, al ser evaluada, sea distinta de cero, es decir: expresión != 0. La llave izquierda “{” delimita el inicio de su bloque, mientras que la llave derecha “}” delimita su final.

   Cuando el conjunto de sentencia(s) delimitadas por el bloque del ciclo while se procesan y la secuencia de ejecución alcanza el delimitador de fin del bloque, la estructura de repetición while modifica el flujo secuencial de ejecución, de tal forma que el flujo de ejecución brinca hacia expresión para que ésta vuelva a ser evaluada y nuevamente, si expresión != 0, se repite el procesamiento de la(s) sentencia(s) delimitadas por su bloque. En caso contrario, se procesa la siguiente sentencia que se encuentre después de su delimitador de fin de bloque.

   El Ejemplo 3.8 imprime en la salida estándar los números del uno al diez, uno por renglón. La salida se muestra en la siguiente figura:

Salida del Ejemplo 3.8.
 
    La expresión condicional de la línea 10 está compuesta por una constante (10) y, aunque ayuda a entender mejor la intención del ejemplo, en la práctica de la programación a este tipo de constantes se les conoce como “números mágicos” y en general no se consideran una buena práctica de programación.

   Quizá se pregunte por qué se considera como una mala práctica de programación. Reflexione en lo siguiente: Suponga que está escribiendo un programa con un número considerable de líneas de código, digamos unas 300, 5 000 o 13 000 líneas de código, y en algunas de ellas hace uso de constantes o números mágicos para controlar ciclos, longitudes, capacidades o algo parecido, y que por alguna razón (y esa razón aparecerá), las constantes o números mágicos utilizados tienen que cambiar, quizá todos, quizá sólo algunos, ¿a qué situación se enfrenta?, ¿es posible realizar los cambios sin efectos colaterales?,  ¿recordará todas las líneas que contenían la constante a modificar?

   Si bien es cierto que puede utilizar el IDE o un editor de texto para realizar búsquedas y apoyarse de ellas para realizar los cambios, la latencia a introducir errores sigue presente, debido a que no deja de ser una búsqueda manual y a que los seres humanos somos propensos a cometer errores, por ello, es mejor dejarle toda la responsabilidad a la computadora, específicamente al compilador, y siendo más precisos y específicos: al pre procesador del compilador del lenguaje C.

   Tomando en cuenta esta propuesta, el Ejemplo 3.9 muestra el uso de la directiva del pre procesador #define, en base a lo descrito con anterioridad y al Ejemplo 3.8. Compile el Ejemplo 3.9, ejecútelo (no lo mate, sólo póngalo a funcionar), y asegúrese que la salida es la misma que la del Ejemplo 3.8.

   Tanto el Ejemplo 3.8 como el Ejemplo 3.9 hacen uso en las líneas 12 y 15 respectivamente, de la expresión:

i = i + 1;

la cual es una operación de incremento, también conocida como expresión de acumulación. Dicha expresión se utiliza normalmente para modificar la variable de control, que para el caso de ambos ejemplos es i. Se le denomina variable de control porque de ella depende la expresión condicional del ciclo, y porque su valor controla la continuación o la terminación del ciclo.

   El lenguaje de programación C incorpora el operador de incremento (++) para simplificar este tipo de expresiones de incremento, por lo que las líneas 12 y 15 de los Ejemplos 3.8 y 3.9 respectivamente, pueden ser substituidas por:

i++;
ó
++i;

   Se deja como ejercicio incorporar dicha substitución para comprobar su equivalencia.

   El operador de incremento puede ser utilizado como prefijo (antes de la variable) o como postfijo (después de la variable), y en ambos casos el efecto es el mismo (utilizado como expresión aislada, es decir, sin usarse dentro de otra expresión): incrementar la variable en una unidad.

   Si el operador de incremento se utiliza como prefijo, a la expresión se le denomina de pre incremento debido a que el valor de la variable es incrementado antes de que su valor se utilice, en tanto que si el operador es utilizado como postfijo, el valor de la variable es incrementado después de que su valor se ha utilizado, y a esta expresión se le denomina de pos incremento. Lo anterior significa que, en un contexto en donde el valor de la variable afectada está siendo utilizado, el pre incremento y el pos incremento tienen resultados distintos.

   El Ejemplo 3.10 muestra la diferencia de utilizar el operador de incremento como prefijo y como postfijo. En base a la explicación del párrafo anterior, determine la salida el programa antes de ejecutarlo y asegúrese de entender lo que sucede antes de continuar.

   Finalmente, se debe mencionar que C incorpora también un operador que funciona de manera análoga para los decrementos, el operador de decremento (- -), el cual sigue las mismas reglas que se comentaron para el operador de incremento. Más adelante se revisarán otros ejemplos y se trabajará más con la estructura de repetición while así como con los operadores de incremento y decremento.

9 de junio de 2016

La estructura de control if-else

   La estructura de control para la selección if-else tiene la siguiente estructura general:

                    if (expresión) {
                        sentencia(s)1;
                   } else {
                        sentencia(s)2;
                   }

   La estructura de selección if-else procesa o ejecuta el grupo de sentencia(s)1 (sentencia(s)1sentencia(s)2 puede ser cualquier sentencia(s) válida(s) en el lenguaje de programación C, como por ejemplo, otras estructuras de control (de selección y de repetición) incluida ella misma, a lo cual se le denomina: estructuras de control anidadas) delimitadas por su bloque, si al ser evaluada la expresión esta es distinta de cero, es decir: expresión != 0. Pero si al ser evaluada la expresión esta es igual a cero, se procesa o ejecuta el grupo de sentencia(s)2 delimitadas por su respectivo bloque. Las llaves izquierdas “{” delimitan el inicio del bloque, mientras que las llaves derechas “}” delimitan  su fin.

   Antes de continuar, compare los Ejemplos 3.2 y 3.3 y compruebe que son esencialmente iguales. Note que la diferencia se presenta en la línea 18, ya que el Ejemplo 3.3 muestra el uso de la estructura de selección if-else.

   La estructura de selección if-else para este ejemplo, podría interpretarse en nuestro lenguaje de la siguiente forma: “Si den es distinto de cero, procesa la división (línea 16) y presenta su resultado (línea 17); en caso contrario, escribe un mensaje que notifique el intento de división por cero”.

   La figura siguiente muestra una posible entrada de datos para generar la salida de indeterminación. Al igual que antes, pruebe con otros datos, y asegúrese de entender el funcionamiento de la estructura de selección if-else antes de continuar.

Posible salida para el Ejemplo 3.3.

   El Ejemplo 3.3 pudo haber sido escrito al menos de otra forma. Considere el Ejemplo 3.4 y compárese con el 3.3.

   Probablemente se esté preguntando ¿cuál de estas dos formas es mejor? La respuesta es: ambas, ya que las dos son lógicamente equivalentes, eficientes y previenen la indeterminación. Tome en consideración que ambos ejemplos producirán la misma salida (como la de la Figura anterior), y que los dos son un reflejo de la forma en que los seres humanos analizamos, pensamos y resolvemos las cosas: desde distintas perspectivas.

   Ahora bien, una vez que haya comprendido el funcionamiento de la estructura de selección if-else, responda lo siguiente:
  • ¿Qué sucede si el Ejemplo 3.1 se reescribe utilizando una estructura de selección if-else anidada?
  • En base a lo anterior ¿se generará la misma salida?
  • Si genera la misma salida ¿por qué?, y si no genera la misma salida ¿por qué?

   Lea, comprenda, y analice el Ejemplo 3.5 para determinar sus respuestas y asegúrese de entender lo que sucede. Trate de determinar sus respuestas antes de ejecutar el programa.