Anda di halaman 1dari 25

http://www.elguille.info/NET/cursoCSharpErik/Entrega12/Entrega12.

htm
CONTROL DE FLUJO: ESTRUCTURAS ITERATIVAS
Pasamos ahora a un nuevo conjunto de instrucciones de mucha utilidad. En realidad,
casi todos los lenguajes cuentan con instrucciones parecidas (si no iguales) o que funcionan de
un modo muy similar a las que vamos a ver aqu. Las estructuras iterativas de control de flujo
se ocupan de repetir una serie de lneas de cdigo tantas veces como el programador indique o
bien hasta que se de una cierta condicin. A estas estructuras tambin se les llama bucles.
Aquellos de vosotros que conozcis otros lenguajes veris que todos estos bucles se
parecen mucho a los que ya conocis. Los que os estis iniciando ahora en la programacin
puede que tardis un poco en hallar la utilidad de todo esto: para qu vamos a hacer que el
programa repita varias veces el mismo cdigo? Bueno, de momento os dir que en todo
programa, al igual que los bloques if y los bloques switch, los bucles son tambin el pan
nuestro de cada da, as que no tardaris en acostumbraros a ellos.
BUCLES FOR
Los bucles for van asignando valores a una variable desde un valor inicial hasta un
valor final, y cuando la variable contiene un valor que est fuera del intervalo el bucle termina.
Veamos la sintaxis para hacernos mejor a la idea:
for (var=inicial;condicin;siguientevalor)
{
Instrucciones
}
S que esto es algo difcil de leer, incluso para aquellos que hayan programado en
otros lenguajes, puesto que los bucles for de C no se parecen mucho, en cuanto a su sintaxis,
al resto de los bucles for de los otros lenguajes, as que tratar de explicarlo con detenimiento.
Como veis, tras la sentencia for se indican las especificaciones del bucle entre parntesis.
Dichas especificaciones estn divididas en tres partes separadas por punto y coma: la parte de
asignacin del valor inicial en primer lugar; la parte que verifica la continuidad del bucle
(mediante una condicin) en segundo lugar; y la parte en que se calcula el siguiente valor en
tercer lugar. Pongamos un ejemplo: vamos a calcular el factorial de un nmero dado, que se
encuentra almacenado en la variable num. Se podra hacer de dos formas:
for (byte i=num; i>1 ; i--)
{
fact*=i;
}

O bien:
for (byte i=1; i<=num ; i++)
{
fact*=i;
}
Claro, para que esto funcione, la variable fact ha de valer 1 antes de que el programa
comience a ejecutar el bucle. Bien, veamos ahora cmo se van ejecutando estas instrucciones
paso a paso:
1 paso:

2 paso:

3 paso:

for (byte i=num; i>1 ; for (byte i=num; i>1 ; for (byte i=num; i>1 ;
i--)
i--)
i--)
{
{
{
fact*=i;
fact*=i;
fact*=i;
}
}
}
4 paso:

5 paso:

6 paso:

for (byte i=num; i>1 ; for (byte i=num; i>1 ; for (byte i=num; i>1 ;
i--)
i--)
i--)
{
{
{
fact*=i;
fact*=i;
fact*=i;
}
}
}

En primer lugar se asigna a la variable i el valor de num (vamos a suponer que num
vale 3), es decir, despus del primer paso, el valor de i es 3. Posteriormente se comprueba si
dicha variable es mayor que 1, es decir, si 3>1. Como la condicin del segundo paso se cumple
se ejecuta el cdigo del bucle en el tercer paso, fact*=i, con lo que fact (que vala 1) ahora vale
3 (1*3). En el cuarto paso se asigna el siguiente valor a i (i--), con lo que, ahora, i valdr 2. En
el quinto se vuelve a comprobar si i es mayor que 1, y como esto se cumple, el sexto paso
vuelve a ejecutar el cdigo del bucle (de nuevo, fact*=i), con lo que ahora fact vale 6 (3*2). El
sptimo paso es idntico al cuarto, es decir, se asigna el siguiente valor a la variable i (de
nuevo, i--), con lo que ahora i valdra 1. El octavo paso es idntico al quinto, comprobando por
lo tanto si i es mayor que 1. Sin embargo esta vez, la condicin no se cumple (1 no es mayor
que 1, sino igual), por lo que la ejecucin saldra del bucle y ejecutara la siguiente lnea del
programa que est fuera de l. Date cuenta de que el bucle se seguir ejecutando siempre que
la condicin ( i>1 ) se cumpla, y dejar de ejecutarse cuando la condicin no se cumpla. Por lo
tanto, no habra sido vlido poner i==2 en lugar de i>1, ya que esta condicin se cumplira
nicamente cuando num valiera 2, pero no en cualquier otro caso. Seras capaz de ver cmo
funcionara el otro bucle? Venga, intntalo.

BUCLES FOR ANIDADOS


Efectivamente, se pueden colocar bucles for dentro de otros bucles for, con lo que
obtendramos lo que se llaman los bucles for anidados. Son tambin muy tiles: por ejemplo,
piensa que tienes almacenadas unas cuantas facturas en una base de datos, y quieres leerlas
todas para presentarlas en pantalla. El problema est en que cada factura tiene una o varias
lneas de detalle. Cmo podramos hacer para cargar cada factura con todas sus lneas de
detalle? Pues usando bucles anidados. Colocaramos un bucle for para cargar las facturas, y
otro bucle for dentro de l para que se cargaran las lneas de detalle de cada factura. As, el
segundo bucle se ejecutar completo en cada iteracin del primer bucle. Veamos un ejemplo
que nos aclare todo esto un poco ms:
using System;
namespace BuclesAnidados
{
class BuclesAnidadosApp
{
static void Main()
{
for (int i=1; i<=3; i++)
{
Console.WriteLine("Factura nmero {0}", i);
Console.WriteLine("Detalles de la factura");
for (int j=1; j<=3; j++)
{
Console.WriteLine("
}

Lnea de detalle {0}", j);

Console.WriteLine();
}
}
}

string a=Console.ReadLine();

Como ves, el bucle "j" est dentro del bucle "i", de modo que se ejecutar completo
tantas veces como se itere el bucle i. Por este motivo, la salida en consola sera la siguiente:
Factura nmero 1
Detalles de la factura
Lnea de detalle 1
Lnea de detalle 2
Lnea de detalle 3
Factura nmero 2
Detalles de la factura
Lnea de detalle 1
Lnea de detalle 2

Lnea de detalle 3
Factura nmero 3
Detalles de la factura
Lnea de detalle 1
Lnea de detalle 2
Lnea de detalle 3
Sigues sin verlo claro? Bueno, veamos cmo se van ejecutando estos bucles:
1 paso:

2 paso:

3 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}
4 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}
5 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}
6 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

7 paso:

8 paso:

9 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

10 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}
11 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}
13 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}
14 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}
15 paso:

12 paso:

for (int i=1; i<=3; i+ for (int i=1; i<=3; i+ for (int i=1; i<=3; i+

+)
{

for (int j=1; j<=3;

j++)

+)
{

for (int j=1; j<=3;

j++)

{
}

for (int j=1; j<=3;

j++)

...

+)
{

...

...

}
16 paso:

}
17 paso:

}
18 paso:

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

for (int i=1; i<=3; i+


+)
{
for (int j=1; j<=3;
j++)
{
...
}
}

El decimonoveno paso sera igual que el sexto, el vigsimo igual que el sptimo, y as
hasta terminar el bucle i. Bueno, donde estn los puntos suspensivos estara el cdigo que
forma parte del bucle j. Como ves, el segundo bucle (el bucle j) se ejecuta completo para cada
valor que toma la variable i del primero de los bucles. Vete haciendo el clculo mental de
cunto van valiendo las variables para que lo veas claro. Por supuesto, se pueden anidar
tantos bucles como sea necesario.

BUCLES WHILE
Bien, para los que no sepan ingls, "while" significa "mientras", de modo que ya os
podis hacer la idea: un bucle while se repetir mientras una condicin determinada se
cumpla, o sea, devuelva true. Veamos su sintaxis:
while (expresin bool)
{
Instrucciones
}
Efectivamente, las "Instrucciones" que se hallen dentro del bucle while se ejecutarn
continuamente mientras la expresin de tipo boolean retorne true. Por ejemplo, podemos
escribir un bucle while para pedir una contrasea de usuario. Algo as:
using System;

namespace BuclesWhile
{
class BuclesWhileApp
{
static void Main()
{
string Clave="Compadre, cmprame un coco";
string Res="";
while (Res!=Clave)
{
Console.Write("Dame la clave: ");
Res=Console.ReadLine();
}
Console.WriteLine("La clave es correcta");
string a=Console.ReadLine();
}

}
En este pequeo ejemplo el programa pedir una y otra vez la clave al usuario, y
cuando este teclee la clave correcta ser cuando finalice la ejecucin del mismo. As, la salida
en la consola de este programa sera algo como esto (en rojo est lo que se ha tecleado
durante su ejecucin):
Dame la clave: No quiero
Dame la clave: Que no
Dame la clave: eres contumaz, eh?
Dame la clave: Vaaaaale
Dame la clave: Compadre, cmprame un coco
La clave es correcta
Alguna pregunta? Que qu habra pasado si la condicin no se hubiera cumplido
antes de ejecutar el bucle, es decir, si Res ya contuviera lo mismo que Clave antes de llegar al
while? Bien, pues, en ese caso, el bucle no se hubiera ejecutado ninguna vez, es decir, al
comprobar que la expresin de tipo boolean retorna false, la ejecucin del programa pasa a la
primera lnea que se encuentra a continuacin del bucle. Vamos a verlo para que te quede ms
claro. Modificaremos ligeramente el ejemplo anterior, as:
using System;
namespace BuclesWhile
{
class BuclesWhileApp
{
static void Main()
{
string Clave="Compadre, cmprame un coco";
string Res=Clave;

while (Res!=Clave)
{
Console.Write("Dame la clave: ");
Res=Console.ReadLine();
}
Console.WriteLine("La clave es correcta");
string a=Console.ReadLine();
}

}
En efecto, en este caso, la salida en consola sera la siguiente:
La clave es correcta
Ya que la ejecucin no pasa por el bucle. Bueno, ya veis que es muy sencillo. Por
cierto, luego os propondr algunos ejercicios para que practiquis un poco todo esto de los
bucles (a ver si pensabais que os ibais a escaquear).
BUCLES DO
Ciertamente, estos bucles tienen mucho que ver con los bucles while. La diferencia es
que estos se ejecutan siempre al menos una vez, mientras que los bucles while, como
acabamos de ver antes, pueden no ejecutarse ninguna vez. Veamos la sintaxis de los bucles
"do":
do
{
Instrucciones
} while (expresin bool);
Como ves, tambin hay un while y una expresin boolean, pero en este caso se
encuentra al final. De este modo, la ejecucin pasar siempre por las instrucciones del bucle
una vez antes de evaluar dicha expresin. Vamos a rehacer el ejemplo anterior cambiando el
bucle while por un bucle do:
using System;
namespace BuclesDo
{
class BuclesDoApp
{
static void Main()
{
string Clave="Compadre, cmprame un coco";
string Res="";

do
{

Console.Write("Dame la clave: ");


Res=Console.ReadLine();
} while (Res!=Clave);
Console.WriteLine("La clave es correcta");
string a=Console.ReadLine();
}

}
El resultado sera el mismo que antes. La diferencia est en que aqu dara
exactamente lo mismo lo que valiera la variable Res antes de llegar al bucle, puesto que este
se va a ejecutar antes de comprobar dicho valor, y al ejecutarse, el valor de Res se sustituye
por lo que se introduzca en la consola. Por lo tanto, repito, los bucles do se ejecutan siempre al
menos una vez.
Por otro lado tenemos otro tipo de bucle, los bucles foreach, pero no hablaremos de
ellos hasta que hayamos visto arrays e indizadores. Tened un poco de paciencia, que todo se
andar.
INSTRUCCIONES DE SALTO
No es que vaya a salirnos un tirinene en la pantalla dando brincos como un poseso, no.
Las instrucciones de salto permiten modificar tambin el flujo del programa, forzando la
siguiente iteracin de un bucle antes de tiempo, o la salida del mismo o bien mandando la
ejecucin directamente a un punto determinado del programa (esto ltimo est altamente
perseguido y penado por la ley, o sea, los jefes de proyecto). Son pocas y muy sencillas, as
que podis estar tranquilos, que no os voy a soltar otra biblia con esto...
LA INSTRUCCIN BREAK
Algo hemos visto ya sobre la instruccin break. Cmo que no? Anda, repsate la
entrega anterior, hombre... Mira que se te ha olvidado pronto... En fin... a lo que vamos. La
instruccin 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 switch). Ahora nos fijaremos en los
bucles, que es donde andamos. Pondremos un ejemplo sencillo: El siguiente programa
escribir mltiplos de 5 hasta llegar a 100:
using System;
namespace InstruccionBreak

{
class InstruccionBreakApp
{
static void Main()
{
int num=0;
while (true)
{
Console.WriteLine(num);
num+=5;
if (num>100) break;
}
string a=Console.ReadLine();
}

}
Qu es eso de while (true)? Pues un bucle infinito. No decamos que dentro de los
parntesis haba que colocar una expresin boolean? Pues entonces... true es una expresin
boolean. De este modo, el bucle es infinito (claro, true siempre es true). Sin embargo, cuando la
variable num tiene un valor mayor que 100 la ejecucin del bucle terminar, pues se ejecuta
una instruccin break.
LA INSTRUCCIN CONTINUE
La instruccin continue fuerza la siguiente iteracin del bucle donde se encuentre (que
puede ser un bucle for, while, do o foreach). Como esto se ve muy bien con un ejemplo, vamos
con ello: El siguiente programa mostrar todos los nmeros del uno al veinte a excepcin de los
mltiplos de tres:
using System;
namespace InstruccionContinue
{
class InstruccionContinueApp
{
static void Main()
{
for (int i=1; i<=20; i++)
{
if (i % 3 == 0) continue;
Console.WriteLine(i);
}
string a=Console.ReadLine();
}
}

En este ejemplo, el bucle for va asgnando valores a la variable i entre 1 y 20. Sin
embargo, cuando el valor de i es tres o mltiplo de tres (es decir, cuando el resto de la divisin
entre i y 3 es cero) se ejecuta una instruccin continue, de modo que se fuerza una nueva
iteracin del bucle sin que se haya escrito el valor de i en la consola. Por este motivo,
apareceran todos los nmeros del uno al veinte a excepcin de los mltiplos de tres.
"ER MARDITO GOTO"
S, C# mantiene vivo al "maldito goto". Si te digo la verdad, el goto, aparte de ser el
principal baluarte de la "programacin des-estructurada", es un maestro de la supervivencia...
de lo contrario no se explicara que siguiera vivo. En fin... Tratar de explicaros cmo funciona
sin dejarme llevar por mis sentimientos... De momento te dir que goto hace que la ejecucin
del programa salte hacia el punto que se le indique. Simple y llanamente. Luego te pongo
ejemplos, pero antes quiero contarte alguna cosilla sobre esta polmica instruccin.
Segn tengo entendido, la discusin sobre mantener o no el goto dentro del lenguaje
C# fue bastante importante. Puede que alguno se est preguntando por qu. Veamos: la
primera polmica sobre el goto surgi cuando se empezaba a hablar de la programacin
estructurada, all por finales de los 60 (hay que ver, yo an no haba nacido). Si alguno ha ledo
algn programa escrito por un "aficionado" al goto sabr perfectamente a qu me refiero: esos
programas son como la caja de pandora, puesto que no sabes nunca qu puede pasar cuando
hagas un cambio aparentemente insignificante, ya que no tienes modo se saber a qu otras
partes del programa afectar ese cambio.
En realidad, el problema no es la instruccin goto en s misma, sino el uso inadecuado
que algunos programadores le dan (pocos, gracias a Dios). Ciertamente, hay ocasiones en las
que una instruccin goto hace la lectura de un programa mucho ms fcil y natural. Recordis
de la entrega anterior el ejemplo en el que haba un switch en el que nos interesaba que se
ejecutaran el caso 2 y el caso 3? Lo habamos resuelto con un if, de este modo:
switch (opcion)
{
case 1:
descuento=10;
break;
case 2:
case 3:
if (opcion==2) regalo="Cargador de CD";
descuento=5;
break;
default:
descuento=0;
break;
}

En este ejemplo, si opcin vala 2 se asignaba una cadena a la variable regalo y,


adems se asignaba 5 a la variable descuento. Pues bien, en este caso un goto habra
resultado mucho ms natural, intuitivo y fcil de leer. Vemoslo:
switch (opcion)
{
case 1:
descuento=10;
break;
case 2:
regalo="Cargador de CD";
goto case 3;
case 3:
descuento=5;
break;
default:
descuento=0;
break;
}
Como veis, hemos resuelto el problema anterior de un modo mucho ms natural que
antes, sin tener que usar una sentencia if. Veamos ahora un ejemplo de cmo NO se debe
usar un goto:
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 CD";
}
Tres:
{
descuento=5;
goto Fin;
}
Otro:
descuento=0;
Fin:
Console.WriteLine("El descuento es {0} y el regalo {1}",
descuento, regalo);
Este fragmento de cdigo hace lo mismo que el anterior, pero, indudablemente, est
muchsimo ms enredado, es mucho ms difcil de leer, y hemos mandado a paseo a todos los

principios de la programacin estructurada. Como ves, un mal uso del goto puede hacer que un
programa sencillo en principio se convierta en un autntico desbarajuste. En resumen, no
hagis esto nunca.
Si queris mi opinin, yo soy partidario de usar el goto slo en casos muy concretos
en los que verdaderamente haga la lectura del cdigo ms fcil (como en el ejemplo del
switch), aunque, si te digo la verdad, no me hubiera molestado nada en absoluto si el goto
hubiera sido suprimido por fin. De todos modos, si no tienes muy claro cundo es bueno usarlo
y cundo no, lo mejor es no usarlo nunca, sobre todo si vives de esto y quieres seguir
hacindolo. Nadie se lleva las manos a la cabeza si se da un pequeo rodeo para evitar el
goto, pero mucha gente se pone extremadamente nerviosa nada ms ver uno, aunque est
bien puesto.
RECURSIVIDAD
Bueno, en realidad esto no tiene mucho que ver con las estructuras de control de flujo,
pero he decidido ponerlo aqu porque en algunos casos un mtodo recursivo puede reemplazar
a un bucle. Adems no sabra cmo hacer para colocarlo en otra entrega... y no quera dejarlo
sin explicar, aunque sea un poco por encima y a pesar de que esta entrega se alargue un poco
ms de lo normal.
Bien, vamos al tajo: los mtodos recursivos son mtodos que se llaman a s mismos.
S que puede dar la impresin de que, siendo as, la ejecucin no terminara nunca, pero sin
embargo esto no es cierto. Los mtodos recursivos han de finalizar la traza en algn punto.
Vemoslo con un ejemplo. Recordis cmo habamos calculado el factorial mediante un
bucle? Pues ahora vamos a hacerlo con un mtodo recursivo. Fjate bien:
static double Fact(byte num)
{
if (num==0) return 1;
return num*Fact((byte) (num-1)); // Aqu Fact se llama a s mismo
}
S, lo s, reconozco que es algo confuso, sobre todo para aquellos que estis
empezando. Pero tranquilos, que tratar de explicaros esto con detenimiento. Primero explicar
los motivos por los que uso un tipo double como valor de retorno y un tipo byte para el
argumento. Veamos, uso el tipo double porque es el que admite valores ms grandes, s, ms
que el tipo Decimal, ya que se almacena en memoria de un modo diferente. Por otro lado, uso
el tipo byte para el argumento sencillamente porque no tendra sentido usar un tipo que acepte
nmeros mayores, ya que pasando de 170 el valor del factorial no cabe ni si quiera en el tipo

double. Una vez aclarado esto, veamos cmo funciona. Primero os dibujo la traza, tal y como
funciona si se quiere calcular el factorial de 3 (o sea, num vale 3):

Para asegurarme de que comprendes esto bien, observa el cdigo y el grfico segn
vas siguiendo la explicacin. Cuando en el programa hacemos una llamada al mtodo (Fact(3)
en el grfico) este, evidentemente, comienza su ejecucin. Primero comprueba si el argumento
que se le ha pasado es igual a cero (revisa el cdigo). Como en este caso el argumento vale 3,
el mtodo retornar lo que valga el producto de 3 por el factorial de 3-1, o sea, 3 por el factorial
de 2. Claro, para poder retornar esto debe calcular previamente cunto vale el factorial de 2,
por lo se produce la segunda llamada al mtodo Fact. En esta segunda llamada, sucede algo
parecido: el argumento vale 2, y como no es igual a cero el mtodo procede a retornar 2 por el
factorial de 1 (2 - 1), pero, obviamente, vuelve a suceder igual. Para poder retornar esto ha de
calcular previamente cunto vale el factorial de 1, por lo que se produce la tercera llamada al
mtodo Fact, volviendo a darse de nuevo la misma situacin: 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
cunto vale el factorial de cero, por lo que se produce una nueva llamada al mtodo Fact. Sin
embargo esta vez s se cumple la condicin, es decir, cero es igual a cero, por lo que esta vez
el mtodo Fact retorna 1 al mtodo que lo llam, que era el que tena que calcular previamente
cunto vala el factorial de 0 y multiplicarlo por 1. As, la funcin que tena 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 mtodo que lo llam, que era el que tena que calcular cunto vala 2 *
Fact(1). Como este ya tiene el resultado de Fact(1) (que es, recuerda 1*1), ejecuta el producto,
retornando 2 al mtodo que lo llam, que era el que tena que calcular cunto vala 3*Fact(2).
Como ahora este mtodo ya sabe que Fact(2) vale 2, ejecuta el producto y retorna el resultado,

que es 6, finalizando la traza. Si te das cuenta, un mtodo recursivo va llamndose a s mismo


hasta que se cumple la condicin que hace que termine de llamarse, y empieza a retornar
valores en el orden inverso a como se fueron haciendo las llamadas.
Bueno, creo que ya est todo dicho por hoy, as que llega el momento de los ejercicios.
Sujtate fuerte a la silla, porque esta vez te voy a poner en unos cuantos aprietos.
EJERCICIO 3
Antes de nada, no te asustes que es muy fcil. Si no sabes qu es alguna cosa, en las
pistas te doy las definiciones de todo. En este ejercicio te voy a pedir que escribas seis
mtodos, los cuales te detallo a continuacin:

El mtodo rFact: debe ser recursivo y retornar el factorial de un nmero. Ahora

bien, no ve vale que copies el que est escrito en esta entrega. A ver si eres capaz de hacerlo
con una sola lnea de cdigo en lugar de dos.

El mtodo itFact: debe retornar tambin el factorial de un nmero, pero esta

vez tiene que ser iterativo (o sea, no recursivo).

El mtodo rMCD: debe ser recursivo y retornar el mximo comn divisor de dos

nmeros. En las pistas te escribo el algoritmo para poder hacerlo.

El mtodo itMCD: tambin debe retornar el mximo comn divisor de dos

nmeros, pero esta vez debe ser iterativo (o sea, no recursivo).

El mtodo MCM: debe ser iterativo y retornar el mnimo comn mltiplo de dos

nmeros.

El mtodo EsPerfecto: debe ser iterativo y retornar true si un nmero dado es

perfecto y false si el nmero no es perfecto.


Obviamente, todos ellos han de ser static, para que se puedan llamar sin necesidad de
instanciar ningn objeto. Escribe tambin un mtodo Main que pruebe si todos ellos funcionan.
Por cierto, trata de hacerlos de modo que sean lo ms eficientes posible, esto es, que hagan el
menor nmero de operaciones posible. Hala, al tajo...
Hasta ahora hemos aprendido un montn de cosas con respecto a las variables, pero
siempre tenamos que saber con antelacin el nmero de variables que el programa iba a
necesitar. Sin embargo, habr situaciones en las que no sea posible determinar este nmero
hasta que el programa no se est ejecutando. Pongamos por ejemplo que estamos diseando
un programa de facturacin. Evidentemente, cada factura tendr una serie de lneas de detalle,
pero ser imposible conocer el nmero de lneas de detalle de cada factura en tiempo de
diseo, esto es, antes de que el programa comience su ejecucin. Pues bien, para solucionar
estas dificultades contamos con los arrays y los indizadores.

ARRAYS
Antes de comenzar a explicaros con mayor claridad qu es un array quiero advertir
nuevamente a los programadores de C/C++: En C#, aunque parecidos, los arrays son
diferentes tanto semntica como sintcticamente, de modo que te recomiendo que no pases
por alto esta entrega.
Bien, una vez hechas todas las aclaraciones previas, creo que podemos comenzar. Un
array es un indicador que puede almacenar varios valores simultneamente. Cada uno de
estos valores se identifica mediante un nmero al cual se llama ndice. As, para acceder al
primer elemento del array habra que usar el ndice cero, para el segundo el ndice uno, para el
tercero el ndice dos, y as sucesivamente. Que nadie se preocupe si de momento todo esto es
un poco confuso, ya que lo voy a ir desmenuzando poco a poco. Vamos a ver cmo se declara
un array:
tipo[] variable;
Bien, como veis es muy parecido a como se declara una variable normal, slo que hay
que poner corchetes detrs del tipo. Los programadores de C/C++ habrn observado
inmediatamente la diferencia sintctica. En efecto, en la declaracin de un array en C# los
corchetes se colocan detrs del tipo y no detrs de la variable. Esta pequea diferencia
sintctica se debe a una importante diferencia semntica: aqu los arrays son objetos derivados
de la clase System.Array. Por lo tanto, y esto es muy importante, cuando declaramos un array
en C# este an no se habr creado, es decir, no se habr reservado an memoria para l. En
consecuencia, los arrays de C# son todos dinmicos, y antes de poder usarlos habr que
instanciarlos, como si fuera cualquier otro objeto. Veamos un breve ejemplo de lo que quiero
decir:
string[] nombres; // Declaracin del array
nombres = new string[3]; // Instanciacin del array
En efecto, tal como podis apreciar, el array nombres ser utilizable nicamente a partir
de su instanciacin. En este ejemplo, el nmero 3 que est dentro de los corchetes indica el
nmero total de elementos de que constar el array. No os equivoquis, puesto que todos los
arrays de C# estn basados en cero, esto es, el primer elemento del array es cero. Por lo tanto,
en este caso, el ltimo elemento sera 2 y no 3, ya que son tres los elementos que lo
componen (0, 1 y 2). Veamos un ejemplo algo ms completo y despus lo comentamos:
using System;

namespace Arrays
{
class ArraysApp
{
static void Main()
{
string[] nombres; // Declaracin del array
ushort num=0;
do
{

try
{

Console.Write("Cuntos nombres vas a

introducir? ");

num=UInt16.Parse(Console.ReadLine());
}
catch
{
continue;
}
} while (num==0);
nombres=new string[num]; // Instanciacin del array
for (int i=0; i<num; i++)
{
Console.Write("Escribe el nombre para elemento {0}: ",

i);

nombres[i]=Console.ReadLine();
}
Console.WriteLine("Introducidos los {0} nombres", num);
Console.WriteLine("Pulsa INTRO para listarlos");
string a=Console.ReadLine();
for (int i=0; i<num; i++)
{
Console.WriteLine("Elemento {0}: {1}", i, nombres[i]);
}
}

a=Console.ReadLine();

Veamos ahora la salida en la consola (en rojo, como siempre, lo que se ha escrito
durante la ejecucin del programa):
Cuntos nombres vas a introducir?
Escribe el nombre para el elemento
Escribe el nombre para el elemento
Escribe el nombre para el elemento
Introducidos los 3 nombres
Pulsa INTRO para listarlos
Elemento 0: Juanito
Elemento 1: Jaimito

3
0: Juanito
1: Jaimito
2: Joselito

Elemento 2: Joselito
En este pequeo programa hemos declarado un array y lo hemos instanciado despus
de haber preguntado al usuario cuntos elementos iba a tener. Como veis, hemos utilizado un
bucle for para recoger todos los valores que hay que meter en el array. Quiero que prestis
especial atencin a cmo hemos introducido los valores en el array: en la lnea "nombres[i] =
Console.ReadLine()" lo que hacemos es que al elemento "i" del array le asignamos lo que
devuelva el mtodo ReadLine. Como "i" tomar valores entre 0 y el nmero total de elementos
menos uno rellenaremos el array completo (fijaos en la condicin del bucle, que es i<num, es
decir, que si i es igual a num el bucle ya no se itera). Despus tenemos otro bucle for para
recorrer todo el array y escribir sus valores en la consola. En definitiva, para acceder a un
elemento del array se usa la sintaxis "array[ndice]".
Un array tambin puede inicializarse en la propia declaracin, bien instancindolo
(como cualquier otro objeto) o bien asignndole los valores directamente. Vamos a reescribir el
ejemplo anterior instanciando el array en la declaracin del mismo:
using System;
namespace Arrays2
{
class Arrays2App
{
static void Main()
{
ushort num=3;
do
{
try
{
introducir? ");

Console.Write("Cuntos nombres vas a

num=UInt16.Parse(Console.ReadLine());
}
catch
{
continue;
}
} while (num==0);
string[] nombres=new string[num]; // Declaracin e
instanciacin del array
for (int i=0; i<num; i++)
{
Console.Write("Escribe el nombre para elemento {0}: ",
i);

nombres[i]=Console.ReadLine();
}
Console.WriteLine("Introducidos los {0} nombres", num);

Console.WriteLine("Pulsa INTRO para listarlos");


string a=Console.ReadLine();
for (int i=0; i<num; i++)
{
Console.WriteLine("Elemento {0}: {1}", i, nombres[i]);
}
}
}

a=Console.ReadLine();

Bien, ahora, como puedes observar, el array ha sido instanciado en la misma lnea en
la que fue declarado. El funcionamiento de este ejemplo, por lo tanto, sera el mismo que el del
ejemplo anterior. Veamos ahora otro ejemplo de inicializacin del array asignndole los valores
en la declaracin:
using System;
namespace Arrays3
{
class Arrays3App
{
static void Main()
{
// Declaracin e inicializacin del array
string[] nombres={"Juanito", "Jaimito", "Joselito"};
for (int i=0; i<nombres.Length; i++)
{
Console.WriteLine("Elemento {0}: {1}", i, nombres[i]);
}
}
}

string a=Console.ReadLine();

En este caso, el array nombres ha sido inicializado en la propia declaracin del mismo,
asignndole los tres valores que va a contener. Como ves, dichos valores estn entre llaves y
separados por comas. Las comillas son necesarias en este caso, ya que el array es de tipo
string. Que dnde est la instanciacin del array? Bueno, cuando hacemos esto, la
instanciacin la hace por debajo el compilador, es decir, de forma implcita. Presta atencin
tambin a la condicin del bucle: ahora hemos usado la propiedad Length del array nombres en
lugar de una variable. En efecto, esta propiedad nos devuelve el nmero de elementos de un
array. Por lo tanto, la salida en consola de este programa sera esta:
Elemento 0: Juanito
Elemento 1: Jaimito
Elemento 2: Joselito

Por otro lado, el hecho de que un array haya sido inicializado no quiere decir que sea
inamovible. Si un array que ya contiene datos se vuelve a instanciar, el array volver a estar
vaco, y obtendr las dimensiones de la nueva instanciacin.
Bien, todos estos arrays que hemos explicado hasta el momento son arrays
unidimensionales, es decir, que tienen una sola dimensin (un solo ndice). Sin embargo esto
no soluciona an todas las necesidades del programador. Pongamos, por ejemplo, que
queremos almacenar las combinaciones de las ocho columnas de una quiniela de ftbol en un
array.Cmo lo hacemos? Pues bien, el mejor modo es utilizar un array multidimensional.
ARRAYS MULTIDIMENSIONALES
Los arrays multidimensionales son aquellos que constan de dos o ms dimensiones, es
decir, que cada elemento del array viene definido por dos o ms ndices. Vamos a echar un
vistazo a la declaracin de un array multidimensional (en este caso, ser tridiensional, es decir,
con tres dimensiones):
tipo[,,] variable;
Como ves, hay dos comas dentro de los corchetes, lo cual indica que el array es
tridimensional, puesto que los tres ndices del mismo se separan uno de otro por comas.
Veamos un pequeo ejemplo que lo clarifique un poco ms:
string[,] alumnos = new string[2,4];
Este array es bidimensional y servira para almacenar una lista de alumnos por aula,
esto es, tenemos dos aulas (el primer ndice del array es 2) y cuatro alumnos en cada una (el
segundo ndice es 4). Veamos un poco de cdigo y una tabla para que os hagis una idea de
cmo se almacena esto:
alumnos[0,0]="Lolo";
alumnos[0,1]="Mario";
alumnos[0,2]="Juan";
alumnos[0,3]="Pepe";
alumnos[1,0]="Lola";
alumnos[1,1]="Mara";
alumnos[1,2]="Juana";
alumnos[1,3]="Pepa";
Esto sera como almacenar los datos en esta tabla:

NOMBRE 0
NOMBRE 1
NOMBRE 2
NOMBRE 3

AULA 0

AULA 1

Lolo
Mario
Juan
Pepe

Lola
Mara
Juana
Pepa

Que quieres saber por qu he separado a los chicos de las chicas? Bueno, no es que
sea un retrgrado, es para que se vea mejor todo esto. Mira que sois detallistas... Bueno, creo
que va quedando bastante claro. Y cmo recorremos un array multidimensional? Pues con
bucles anidados. Vamos ya con un ejemplo ms completito de todo esto. Este pequeo
programa pregunta al usuario por el nmero de columnas que quiere generar de una quiniela
de ftbol, y despus las rellena al azar y las muestra en pantalla:
using System;
namespace Quinielas
{
class QuinielasApp
{
static void Main()
{
const char local='1';
const char empate='X';
const char visitante='2';
const byte numFilas=14;
byte numColumnas=0;
char[,] quiniela;
byte azar;
Random rnd=new Random(unchecked((int)
DateTime.Now.Ticks));
do
{

ocho");
");

try
{

Console.WriteLine("Mnimo una columna y mximo


Console.Write("Cuntas columnas quieres generar?

numColumnas=Byte.Parse(Console.ReadLine());
}
catch
{
continue;
}
} while (numColumnas<1 || numColumnas>8);
quiniela=new char[numColumnas, numFilas];
for (byte i=0; i<numColumnas; i++)
{
for (byte j=0; j<numFilas; j++)
{
azar=(byte) (rnd.NextDouble()*3D);

switch (azar)
{
case 0:
quiniela[i,j]=local;
break;
case 1:
quiniela[i,j]=empate;
break;
case 2:
quiniela[i,j]=visitante;
break;
}

}
Console.WriteLine("Quiniela generada. Pulsa INTRO para

verla");

string a=Console.ReadLine();
for (byte i=0; i<numColumnas; i++)
{
Console.Write("Columna {0}: ", i+1);
for (byte j=0; j<numFilas; j++)
{
Console.Write("{0} ", quiniela[i,j]);
}
Console.WriteLine();
Console.WriteLine();
}
a=Console.ReadLine();
}

}
Como veis, esto se va poniendo cada vez ms interesante. De este programa, aparte
de la clase Random, hemos visto todo excepto los bloques try y catch, de modo que si hay algo
que no entiendes te recomiendo que revises las entregas anteriores. La clase Random es para
generar nmeros aleatorios (al azar). En la instanciacin de dicha clase hemos puesto algo que
puede resultarte algo confuso. Es esta lnea:
Random rnd=new Random(unchecked((int) DateTime.Now.Ticks));
Bien, el constructor de esta clase tiene dos sobrecargas: una de ellas es sin
argumentos, y la otra acepta un argumento de tipo int, que es la que hemos usado. Por qu?
Porque de lo contrario siempre generara los mismos nmeros en cada ejecucin del programa,
lo cual no sera muy til en este caso. Como necesitamos que se generen nmeros distintos
tenemos que pasarle nmeros diferentes en el argumento int del constructor de la clase
Random, y el modo ms eficaz de conseguirlo es hacer que ese nmero dependa del tiempo
que lleve encendido el ordenador. Por otro lado, el nmero lo generamos al ejecutar el mtodo
NextDouble, el cual nos retorna un nmero mayor o igual a 0 y menor que 1. Esta es la lnea:

azar=(byte) (rnd.NextDouble()*3D);
Por qu lo hemos multiplicado por 3D? Pues bien, como queremos nmeros enteros
entre 0 y 2 (o sea, 0, 1 o 2) bastar con multiplicar este nmero (recuerda que est entre cero y
uno) por 3. Y la D? Ahora voy, hombre. Os acordis de los sufijos en los literales, para
indicar si se deba considerar si el nmero era de un tipo o de otro? Pues aqu est la
explicacin. Dado que el mtodo NextDouble retorna un valor double, tenemos que multiplicarlo
por otro valor double. Por eso le ponemos el sufijo "D" al nmero tres. Despus todo ese
resultado se convierte a byte y se asigna a la variable azar, que es la que se comprueba en el
switch para asignar el carcter necesario segn su valor a cada elemento del array.
Por lo dems creo que a estas alturas no debera tener que explicaros gran cosa:
tenemos un par de bucles anidados para asignar los valores al array y despus otros dos
bucles anidados para recorrer dicho array y mostrar su contenido en la consola.
Otra cuestin importante en la que quiero que te fijes es en que ya estoy empezando a
dejar de usar "literales y nmeros mgicos", usando constantes en su lugar. Efectivamente,
podra haberme ahorrado las cuatro constantes: local, empate, visitante y numFilas, poniendo
sus valores directamente en el cdigo, algo as:
...

for (byte i=0; i<numColumnas; i++)


{
for (byte j=0; j<14; j++)
{
azar=(byte) (rnd.NextDouble()*3D);
switch (azar)
{
case 0:
quiniela[i,j]='1';
break;
case 1:
quiniela[i,j]='X';
break;
case 2:
quiniela[i,j]='2';
break;
}
}
}

...
En efecto, funcionara exactamente igual, pero qu ocurrira si otra persona que no
sabe qu es una quiniela, o por qu tiene que ser el nmero 14, o qu significan el 1, la X o el
2? Pues que el cdigo sera menos claro. Las constantes, sin embargo, hacen la lectura del
cdigo ms fcil. Por otro lado, si algn da cambiaran los signos, por ejemplo, si hubiese que

poner una "a" en lugar del "1", una "b" en lugar de la "x" y una "c" en lugar del "2" y no
hubisemos usado constantes habra que buscar todos estos literales por todo el cdigo y
sustituirlos uno por uno, mientras que usando constantes (que estn declaradas al principio)
basta con modificar sus valores, haciendo as el cambio efectivo ya para todo el programa. As
que ya lo sabis: a partir de ahora vamos a evitar en lo posible los "literales y los nmeros
mgicos".
Para terminar con esto, el nmero de dimensiones de un array se llama rango. Para
conocer el rango de un array mediante cdigo basta con invocar la propiedad Rank del mismo
(heredada de la clase System.Array). Veamos un ejemplo de esto:
using System;
namespace Rangos
{
class RangosApp
{
static void Main()
{
int[] array1=new int[2];
int[,] array2=new int[2,2];
int[,,] array3=new int[2,2,2];
int[,,,] array4=new int[2,2,2,2];
Console.WriteLine("Rango
Console.WriteLine("Rango
Console.WriteLine("Rango
Console.WriteLine("Rango

de
de
de
de

array1:
array2:
array3:
array4:

{0}",
{0}",
{0}",
{0}",

array1.Rank);
array2.Rank);
array3.Rank);
array4.Rank);

string a=Console.ReadLine();
}

}
}

La salida en la consola de todo esto sera la siguiente:


Rango
Rango
Rango
Rango

de
de
de
de

array1:
array2:
array3:
array4:

1
2
3
4

ARRAYS DE ARRAYS
En efecto, para liar un poco ms la madeja, tenemos tambin los arrays de arrays.
Estos son arrays que pueden contener otros arrays. Y para qu diablos queremos meter un
array dentro de otro? No nos basta con los arrays multidimensionales? Pues realmente podra
bastarnos, en efecto, pero habra ocasiones en las que tendramos que hacer bastantes
cabriolas con el cdigo por no usar los arrays de arrays. Pensad en un programa en el que el

usuario tiene que manejar simultneamente mltiples objetos de distintas clases derivadas de
una clase base, por ejemplo, tringulos y cuadrados derivados de la clase figura. Si solamente
pudiramos usar arrays unidimensionales o multidimensionales tendramos que declarar un
array distinto para cada tipo de objeto (uno para tringulos y otro para cuadrados). La dificultad
viene ahora: Qu ocurre si hay que redibujar todos los objetos, ya sean cuadrados o
tringulos? Evidentemente, habra que escribir un bucle para cada uno de los arrays para poder
invocar los mtodos Redibujar de cada uno de los elementos. Sin embargo, si metemos todos
los arrays dentro de un array de arrays nos bastara con escribir un par de bucles anidados
para recorrer todos los objetos y dejar el resto en manos del polimorfismo. Ciertamente, an no
hemos estudiado a fondo ninguno de los mecanismos de la herencia. No obstante, con lo que
sabemos hasta ahora, podemos poner un ejemplo sobre los arrays de arrays, aunque
probablemente no se aprecie realmente la ventaja. Veamos el ejemplo, y luego lo comentamos.
Eso s, presta especial atencin a la sintaxis, tanto en la declaracin como en las
instanciaciones:
using System;
namespace ArraysdeArrays
{
class ArraysDeArraysApp
{
static void Main()
{
object[][] numeros; // Declaracin del array de arrays
numeros=new object[2][]; // Instanciacin del array de
arrays
numeros[0]=new object[3]; // Instanciacin del primer
array

numeros[1]=new object[4]; // Instanciacin del segundo

array
numeros[0][0]=3.325D;
numeros[0][1]=6.25D;
numeros[0][2]=3D;
numeros[1][0]=3u;
numeros[1][1]=7u;
numeros[1][2]=4u;
numeros[1][3]=87u;
for (int i=0;i<numeros.Length;i++)
{
for (int j=0;j<numeros[i].Length;j++)
{
Console.WriteLine(numeros[i][j].ToString());
}
}
string a=Console.ReadLine();
}

}
En este ejemplo vamos a usar un array en el que incluiremos dos arrays: uno para
nmeros de tipo double y otro para nmeros de tipo ulong. Como estos dos tipos estn
derivados de la clase System.Object, lo que hacemos es declarar el array de este tipo en la
primera lnea del mtodo Main, y despus lo instanciamos dicindole que contendr dos arrays
(en la segunda lnea). Despus instanciamos tambin como tipo object los dos arrays que
contendr el primero, y le asignamos valores: al array numeros[0] le asignamos valores de tipo
double, y al array numeros[1] le asignamos valores de tipo ulong. Despus usamos un par de
bucles anidados para recorrer todos los elementos del array de arrays con el objeto de invocar
el mtodo ToString() de todos ellos (heredado de la clase System.Object). Como ves, el bucle
"i" recorre el array de arrays (fjate en la condicin, i<numeros.Length), y el bucle "j" recorre
cada uno de los elementos del array numeros[i], segn sea el valor de i en cada iteracin. Con
los ejemplos de esta entrega se incluye tambin el de los cuadrados y los tringulos que te
mencion antes (en la carpeta Figuras), pero no lo reproduzco aqu porque an no hemos visto
la herencia. Sin embargo, cuando lo ejecutes, vers mejor la utilidad de los arrays de arrays.
Bien, creo que ya es suficiente para esta entrega. No te pondr ejercicios sobre los
arrays todava, pues prefiero esperar a que hayamos vistos los indizadores y los bucles
foreach. Por cierto... espero que la prxima entrega no se haga esperar tanto como esta. A ver
si hay algo de suerte..