sábado, 12 de agosto de 2017

Curso de C#: control de flujo, saltos y rupturas

Instrucciones de salto


Las instrucciones de salto permiten modificar el flujo del programa, forzando la siguiente iteración de un bucle antes de tiempo, o la salida del mismo o bien enviando la ejecución a un punto determinado del programa.

La instrucción Break


La instrucción break fuerza la salida de un bucle antes de tiempo o bien de una estructura de control de flujo condicional en la que se encuentre. Un ejemplo sencillo: 

    class InstruccionBreak
    {
        static void Main()
        {
            int num=0;

            while (true)
            {
                Console.WriteLine(num);
                num+=5;
                if (num>100) break;
            }

            string a=Console.ReadLine();
        }
    }

Curso de C#: control de flujo, saltos y rupturas


El bucle while (true) es un bucle infinito, sin embargo, cuando la variable num tiene un valor mayor que 100 la ejecución del bucle terminará, pues se ejecuta una instrucción break.

Instrucción Continue


La instrucción continue fuerza la siguiente iteración del bucle donde se encuentre (que puede ser un bucle for, while, do o foreach). El siguiente programa mostrará todos los números del uno al veinte a excepción de los múltiplos de tres:


    class InstruccionContinue
    {
        static void Main()
        {
            for (int i=1; i<=20; i++)
            {
                if (i % 3 == 0) continue;
                Console.WriteLine(i);
            }

            string a=Console.ReadLine();
        }
    }

El bucle for va asignando valores a la variable i entre 1 y 20. Sin embargo, cuando el valor de i es tres o múltiplo de tres, se ejecuta una instrucción continue, de modo que se fuerza una nueva iteración del bucle sin que se haya escrito el valor de i en la consola. Por tanto, aparecerían todos los números del uno al veinte a excepción de los múltiplos de tres.

Instrucción GOTO


Goto hace que la ejecución del programa salte hacia el punto que se le indique.
  
Hay ocasiones en las que una instrucción goto hace la lectura de un programa mucho más fácil y natural, pero hay que utilizarla con mucha precaución, pues puna programación poco cuidadosa y poco planificada puede hacer que los resultados con esta instrucción sean muy difíciles de seguir y evaluar.  

En este ejemplo, si opción vale 2 se asigna una cadena a la variable regalo y, además se le asigna 5 a la variable descuento. En este caso un goto resulta  más natural, intuitivo y fácil de leer:

switch (opcion)
{
    case 1:
        descuento=10;
        break;
    case 2:
        regalo="Cargador de DVD";
        goto case 3;
    case 3:
        descuento=5;
        break;
    default:
        descuento=0;
        break;
}

Pero hay muchos casos en los utilizar goto es contraproducente, por ejemplo:

if (opcion==1) goto Uno;
if (opcion==2) goto Dos;
if (opcion==3) goto Tres;
goto Otro;

Uno:
{
    descuento=10;
    goto Fin;
}

Dos:
{
    regalo="Cargador de DVD";
}
Tres:
{
    descuento=5;
    goto Fin;
}
Otro:
    descuento=0;
Fin:
    Console.WriteLine("El descuento es {0} y el regalo {1}",
        descuento, regalo);

Este fragmento de código hace lo mismo que el anterior, pero, es más difícil de leer, y no cumple los principios de la programación estructurada. Un mal uso de goto puede hacer que un programa sencillo en principio se convierta en una fuente de problemas.
El uso de goto debe realizarse sólo en casos muy concretos en los que verdaderamente haga la lectura del código más fácil (como en el ejemplo del switch), aunque, incluso en este caso se podría haber suprimido.

Recursividad


Los métodos recursivos son métodos que se llaman a sí mismos. Esto puede dar la impresión de que la ejecución no terminaría nunca, aunque los métodos recursivos han de finalizar en algún punto. Utilizaremos un ejemplo para calcular un factorial.

static double Fact(byte num)
{
    if (num==0) return 1;

    return num*Fact((byte) (num-1)); // Fact se llama a sí mismo
}

Se utiliza el tipo doublé, porque es el que admite valores más grandes, sí, más que el tipo Decimal, ya que se almacena en memoria de un modo diferente. Por otro lado, uso el tipo byte para el argumento porque no tendría sentido usar un tipo que acepte números mayores, ya que pasando de 170 el valor del factorial no cabe ni si quiera en el tipo double.

Cuando en el programa hace una llamada al método Fact este, comienza su ejecución. Primero comprueba si el argumento que se le ha pasado es igual a cero. Si por ejemplo el argumento vale 3, el método retornará lo que valga el producto de 3 por el factorial de 3-1, o sea, 3 por el factorial de 2. Para poder retornar esto debe calcular previamente cuánto vale el factorial de 2, por lo se produce la segunda llamada al método Fact. En esta segunda llamada, sucede algo parecido: el argumento vale 2, y como no es igual a cero el método procede a retornar 2 por el factorial de 1 (2 - 1), pero, vuelve a suceder igual. Para poder retornar esto ha de calcular previamente cuánto vale el factorial de 1, por lo que se produce la tercera llamada al método Fact, volviendo a darse de nuevo la misma situación: como 1 no es igual a cero, procede a retornar el producto de 1 por el factorial de cero, y de nuevo tiene que calcular cuánto vale el factorial de cero, por lo que se produce una nueva llamada al método Fact. Sin embargo esta vez sí se cumple la condición, es decir, cero es igual a cero, por lo que esta vez el método Fact retorna 1 al método que lo llamó, que era el que tenía que calcular previamente cuánto valía el factorial de 0 y multiplicarlo por 1. Así, la función que tenía que calcular 1*Fact(0) ya sabe que la última parte, es decir, Fact(0), vale 1, por lo que hace el producto y retorna el resultado al método que lo llamó, que era el que tenía que calcular cuánto valía 2 * Fact(1). Como este ya tiene el resultado de Fact(1) (que es, 1*1), ejecuta el producto, retornando 2 al método que lo llamó, que era el que tenía que calcular cuánto valía 3*Fact(2). Como ahora este método ya sabe que Fact(2) vale 2, ejecuta el producto y retorna el resultado, que es 6, finalizando el programa.

Un método recursivo va llamándose a sí mismo hasta que se cumple la condición que hace que termine de llamarse, y empieza a retornar valores en el orden inverso a como se fueron haciendo las llamadas.

No hay comentarios:

Publicar un comentario