Texto Apoyo C# PDF
Texto Apoyo C# PDF
Texto Apoyo C# PDF
3. Estructuras de control
Esto escribe "Hola" en la pantalla. Pero hay mucho alrededor de ese "Hola", y
vamos a comentarlo antes de proseguir, aunque muchos de los detalles se irán
aclarando más adelante. En este primer análisis, iremos de dentro hacia fuera:
WriteLine("Hola"); - "Hola" es el texto que queremos escribir, y WriteLine es
la orden encargada de escribir (Write) una línea (Line) de texto en pantalla.
Console.WriteLine("Hola"); porque WriteLine es una orden de manejo de la
"consola" (la pantalla "negra" en modo texto del sistema operativo).
System.Console.WriteLine("Hola"); porque las órdenes relacionadas con el
manejo de consola (Console) pertenecen a la categoría de sistema
(System).
Las llaves { y } se usan para delimitar un bloque de programa. En nuestro
caso, se trata del bloque principal del programa.
public static void Main() - Main indica cual es "el cuerpo del programa", la
parte principal (un programa puede estar dividido en varios fragmentos,
como veremos más adelante). Todos los programas tienen que tener un
bloque "Main". Los detalles de por qué hay que poner delante "public static
void" y de por qué se pone después un paréntesis vacío los iremos
aclarando más tarde. De momento, deberemos memorizar que ésa será la
forma correcta de escribir "Main".
public class Ejemplo01 - de momento pensaremos que "Ejemplo01" es el
nombre de nuestro programa. Una línea como esa deberá existir también
siempre en nuestros programas, y eso de "public class" será obligatorio.
Nuevamente, aplazamos para más tarde los detalles sobre qué quiere decir
"class" y por qué debe ser "public".
Como se puede ver, mucha parte de este programa todavía es casi un "acto de
fé" para nosotros. Debemos creernos que "se debe hacer así". Poco a poco
iremos detallando el por qué de "public", de "static", de "void", de "class"... Por
ahora nos limitaremos a "rellenar" el cuerpo del programa para entender los
conceptos básicos de programación.
Ejercicio propuesto: Crea un programa en C# que te salude por tu nombre (ej:
"Hola, Nacho").
Sólo un par de cosas más antes de seguir adelante:
Cada orden de C# debe terminar con un punto y coma (;)
C# es un lenguaje de formato libre, de modo que puede haber varias
órdenes en una misma línea, u órdenes separadas por varias líneas o
espacios entre medias. Lo que realmente indica dónde termina una orden y
donde empieza la siguiente son los puntos y coma. Por ese motivo, el
programa anterior se podría haber escrito también así (aunque no es
aconsejable, porque puede resultar menos legible):
public class Ejemplo01 {
public
static
void Main() { System.Console.WriteLine("Hola"); } }
Ejercicio propuesto: crea un programa que diga el resultado de sumar 118 y 56.
Operador Operación
+ Suma
- Resta, negación
* Multiplicación
/ División
Ejercicio propuesto:
Hacer un programa que calcule el producto de los números 12 y 13.
Hacer un programa que calcule la diferencia (resta) entre 321 y 213.
Hacer un programa que calcule el resultado de dividir 301 entre 3.
Hacer un programa que calcule el resto de la división de 301 entre 3.
1.4.1. Orden de prioridad de los operadores
Sencillo:
En primer lugar se realizarán las operaciones indicadas entre paréntesis.
Luego la negación.
Después las multiplicaciones, divisiones y el resto de la división.
Finalmente, las sumas y las restas.
En caso de tener igual prioridad, se analizan de izquierda a derecha.
Ejercicio propuesto: Calcular (a mano y después comprobar desde C#) el
resultado de estas operaciones:
-2 + 3 * 5
(20+5) % 6
15 + -5*6 / 10
2 + 10 / 5 * 2 - 7 % 1
Para usar una cierta variable primero hay que declararla: indicar su nombre y el
tipo de datos que querremos guardar.
El primer tipo de datos que usaremos serán números enteros (sin decimales),
que se indican con "int" (abreviatura del inglés "integer"). Después de esta
palabra se indica el nombre que tendrá la variable:
int primerNumero;
Esa orden reserva espacio para almacenar un número entero, que podrá tomar
distintos valores, y al que nos referiremos con el nombre "primerNumero".
Una vez que sabemos cómo mostrar un número en pantalla, es sencillo mostrar
el valor de una variable. Para un número hacíamos cosas como
System.Console.WriteLine(3+4);
Ya sabemos todo lo suficiente para crear nuestro programa que sume dos
números usando variables:
public class Ejemplo02
{
public static void Main()
{
int primerNumero;
int segundoNumero;
int suma;
primerNumero = 234;
segundoNumero = 567;
suma = primerNumero + segundoNumero;
1.7. Comentarios
Podemos escribir comentarios, que el compilador ignora, pero que pueden servir
para aclararnos cosas a nosotros. Se escriben entre /* y */:
int suma; /* Porque guardaré el valor de la suma para usarlo más tarde */
También es posible declarar otro tipo de comentarios, que comienzan con doble
barra y terminan cuando se acaba la línea (estos comentarios, claramente, no
podrán ocupar más de una línea). Son los "comentarios al estilo de C++":
// Este es un comentario "al estilo C++"
pero eso ocurrirá en el próximo tema, cuando veamos cómo manejar textos. De
momento, nosotros sólo sabemos manipular números enteros, así que
deberemos convertir ese dato a un número entero, usando Convert.ToInt32:
primerNumero = System.Convert.ToInt32(System.Console.ReadLine());
Un ejemplo de programa que sume dos números tecleados por el usuario sería:
public class Ejemplo03
{
public static void Main()
{
int primerNumero;
int segundoNumero;
int suma;
Ejercicios propuestos:
1. Multiplicar dos números tecleados por usuario.
2. El usuario tecleará dos números (x e y), y el programa deberá calcular cual
es el resultado de su división y el resto de esa división.
3. El usuario tecleará dos números (a y b), y el programa mostrará el resultado
de la operación (a+b)*(a-b) y el resultado de la operación a2-b2.
4. Sumar tres números tecleados por usuario.
5. Pedir al usuario un número y mostrar su tabla de multiplicar. Por ejemplo, si
el número es el 3, debería escribirse algo como:
3 x 0 = 0
3 x 1 = 3
3 x 2 = 6
...
3 x 10 = 30
Los tipos de datos enteros que podemos usar en C#, junto con el espacio que
ocupan en memoria y el rango de valores que os permiten almacenar son:
byte 1 0 a 255
ushort 2 0 a 65535
uint 4 0 a 4294967295
-9223372036854775808 a
long 8
9223372036854775807
ulong 8 0 a 18446744073709551615
Como se puede observar en esta tabla, el tipo de dato más razonable para
guardar edades sería "byte", que permite valores entre 0 y 255, y ocupa 3 bytes
menos que un "int".
2.1.2. Conversiones de cadena a entero
Pues bien, en C#, existe una notación más compacta para esta operación, y
para la opuesta (el decremento):
a++; es lo mismo que a = a+1;
a--; es lo mismo que a = a-1;
Pero esto tiene más misterio todavía del que puede parecer en un primer
vistazo: podemos distinguir entre "preincremento" y "postincremento". En C# es
posible hacer asignaciones como
b = a++;
Así, si "a" valía 2, lo que esta instrucción hace es dar a "b" el valor de "a" y
aumentar el valor de "a". Por tanto, al final tenemos que b=2 y a=3
(postincremento: se incrementa "a" tras asignar su valor).
En cambio, si escribimos
b = ++a;
Pero aún hay más. Tenemos incluso formas reducidas de escribir cosas como "a
= a+5". Allá van
a += b ; es lo mismo que a = a+b;
a -= b ; es lo mismo que a = a-b;
a *= b ; es lo mismo que a = a*b;
a /= b ; es lo mismo que a = a/b;
a %= b ; es lo mismo que a = a%b;
Ejercicios propuestos:
Crear un programa que use tres variables x,y,z. Sus valores iniciales serán
15, -10, 214. Se deberá incrementar el valor de estas variables en 12,
usando el formato abreviado. ¿Qué valores esperas que se obtengan?
Contrástalo con el resultado obtenido por el programa.
¿Cuál sería el resultado de las siguientes operaciones? a=5; b=a+2; b-=3;
c=-3; c*=2; ++c; a*=b;
Tenemos tres tamaños para elegir, según si queremos guardar números con
mayor cantidad de cifras o con menos. Para números con pocas cifras
significativas (un máximo de 7, lo que se conoce como "un dato real de simple
precisión") existe el tipo "float" y para números que necesiten más precisión
(unas 15 cifras, "doble precisión") tenemos el tipo "double". En C# existe un
tercer tipo de números reales, con mayor precisión todavía, que es el tipo
"decimal":
Al igual que hacíamos con los enteros, podemos leer como cadena de texto, y
convertir cuando vayamos a realizar operaciones aritméticas. Ahora usaremos
Convert.ToDouble cuando se trate de un dato de doble precisión y
Convert.ToSingle cuando sea un dato de simple precisión (float):
using System;
23,6
34.2
En más de una ocasión nos interesará formatear los números para mostrar una
cierta cantidad de decimales: por ejemplo, nos puede interesar que una cifra que
corresponde a dinero se muestre siempre con dos cifras decimales, o que una
nota se muestre redondeada, sin decimales.
Una forma de conseguirlo es crear una cadena de texto a partir del número,
usando "ToString". A esta orden se le puede indicar un dato adicional, que es el
formato numérico que queremos usar, por ejemplo: suma.ToString("0.00")
Algunos de los códigos de formato que se pueden usar son:
Un cero (0) indica una posición en la que debe aparecer un número, y se
mostrará un 0 si no hay ninguno.
Una almohadilla (#) indica una posición en la que puede aparecer un
número, y no se escribirá nada si no hay número.
Un punto (.) indica la posición en la que deberá aparecer la coma decimal.
Alternativamente, se pueden usar otros formatos abreviados: por ejemplo,
N2 quiere decir "con dos cifras decimales" y N5 es "con cinco cifras
decimales".
Vamos a probarlos en un ejemplo:
using System;
Console.WriteLine( numero.ToString("N1") );
Console.WriteLine( numero.ToString("N3") );
Console.WriteLine( numero.ToString("0.0") );
Console.WriteLine( numero.ToString("0.000") );
Console.WriteLine( numero.ToString("#.#") );
Console.WriteLine( numero.ToString("#.###") );
}
}
12,3
12,340
12,3
12,340
12,3
12,34
Para leer valores desde teclado, lo podemos hacer de forma similar a los casos
anteriores: leemos toda una frase con ReadLine y convertimos a tipo "char"
usando Convert.ToChar:
letra = Convert.ToChar(Console.ReadLine());
Así, un programa que de un valor inicial a una letra, la muestre, lea una nueva
letra tecleada por el usuario, y la muestre, podría ser:
using System;
letra = 'a';
Console.WriteLine("La letra es {0}", letra);
Como hemos visto, los textos que aparecen en pantalla se escriben con
WriteLine, indicados entre paréntesis y entre comillas dobles. Entonces surge
una dificultad: ¿cómo escribimos una comilla doble en pantalla? La forma de
conseguirlo es usando ciertos caracteres especiales, lo que se conoce como
"secuencias de escape". Existen ciertos caracteres especiales que se pueden
escribir después de una barra invertida (\) y que nos permiten conseguir escribir
esas comillas dobles y algún otro carácter poco habitual. Por ejemplo, con \" se
escribirán unas comillas dobles, y con \' unas comillas simples, o con \n se
avanzará a la línea siguiente de pantalla.
Estas secuencias especiales son las siguientes:
Secuencia Significado
\a Emite un pitido
Juguemos mas:
otro salto
Ejercicio propuesto: Crear un programa que pida al usuario que teclee cuatro
letras y las muestre en pantalla juntas, pero en orden inverso, y entre comillas
dobles. Por ejemplo si las letras que se teclean son a, l, o, h, escribiría "hola".
Este tipo de datos hará que podamos escribir de forma sencilla algunas
condiciones que podrían resultar complejas, pero eso lo veremos en el tema 3.
3. Estructuras de control
3.1. Estructuras alternativas
3.1.1. if
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero>0) Console.WriteLine("El número es positivo.");
}
}
Este programa pide un número al usuario. Si es positivo (mayor que 0), escribe
en pantalla "El número es positivo."; si es negativo o cero, no hace nada.
Como se ve en el ejemplo, para comprobar si un valor numérico es mayor que
otro, usamos el símbolo ">". Para ver si dos valores son iguales, usaremos dos
símbolos de "igual": if (numero==0). Las demás posibilidades las veremos algo
más adelante. En todos los casos, la condición que queremos comprobar deberá
indicarse entre paréntesis.
Este programa comienza por un comentario que nos recuerda de qué se trata.
Como nuestros fuentes irán siendo cada vez más complejos, a partir de ahora
incluiremos comentarios que nos permitan recordar de un vistazo qué
pretendíamos hacer.
Ejercicios propuestos:
Crear un programa que pida al usuario un número entero y diga si es par
(pista: habrá que comprobar si el resto que se obtiene al dividir entre dos es
cero: if (x % 2 == 0) …).
Crear un programa que pida al usuario dos números enteros y diga cual es
el mayor de ellos.
Crear un programa que pida al usuario dos números enteros y diga si el
primero es múltiplo del segundo (pista: igual que antes, habrá que ver si el
resto de la división es cero: a % b == 0).
3.1.2. if y sentencias compuestas
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero>0) {
Console.WriteLine("El número es positivo.");
Console.WriteLine("Recuerde que también puede usar negativos.");
} /* Aqui acaba el "if" */
} /* Aqui acaba "Main" */
} /* Aqui acaba "Ejemplo11" */
Hemos visto que el símbolo ">" es el que se usa para comprobar si un número
es mayor que otro. El símbolo de "menor que" también es sencillo, pero los
demás son un poco menos evidentes, así que vamos a verlos:
Operador Operación
== Igual a
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero != 0) Console.WriteLine("El número no es cero.");
}
}
Ejercicios propuestos:
Crear un programa que multiplique dos números enteros de la siguiente
forma: pedirá al usuario un primer número entero. Si el número que se que
teclee es 0, escribirá en pantalla "El producto de 0 por cualquier número es
0". Si se ha tecleado un número distinto de cero, se pedirá al usuario un
segundo número y se mostrará el producto de ambos.
Crear un programa que pida al usuario dos números reales. Si el segundo
no es cero, mostrará el resultado de dividir entre el primero y el segundo.
Por elcontrario, si el segundo número es cero, escribirá "Error: No se puede
dividir entre cero".
3.1.4. if-else
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0) Console.WriteLine("El número es positivo.");
else Console.WriteLine("El número es cero o negativo.");
}
}
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0) Console.WriteLine("El número es positivo.");
if (numero <= 0) Console.WriteLine("El número es cero o negativo.");
}
}
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0)
Console.WriteLine("El número es positivo.");
else
if (numero < 0)
Console.WriteLine("El número es negativo.");
else
Console.WriteLine("El número es cero.");
}
}
Ejercicio propuesto: Mejorar la solución a los dos ejercicios del apartado anterior,
usando "else".
Estas condiciones se puede encadenar con "y", "o", etc., que se indican de la
siguiente forma
Operador Significado
&& Y
|| O
! No
Ejercicios propuestos:
Crear un programa que pida una letra al usuario y diga si se trata de una
vocal.
Crear un programa que pida al usuario dos números enteros y diga "Uno de
los números es positivo", "Los dos números son positivos" o bien "Ninguno
de los números es positivo", según corresponda.
Crear un programa que pida al usuario tres números reales y muestre cuál
es el mayor de los tres.
Crear un programa que pida al usuario dos números enteros cortos y diga si
son iguales o, en caso contrario, cuál es el mayor de ellos.
Crear un programa que pida al usuario su nombre, y le diga "Hola" si se
llama "Juan", o bien le diga "No te conozco" si teclea otro nombre.
3.1.6. El peligro de la asignación en un "if"
Cuidado con el operador de igualdad: hay que recordar que el formato es "if
(a==b)" Si no nos acordamos y escribimos "if (a=b)", estamos intentando asignar
a "a" el valor de "b".
En algunos compiladores de lenguaje C, esto podría ser un problema serio,
porque se considera válido hacer una asignación dentro de un "if" (aunque la
mayoría de compiladores modernos nos avisarían de que quizá estemos
asignando un valor por error). En el caso del lenguaje C#, la "condición" debe
ser algo cuyo resultado sea "verdadero" o "falso" (un dato de tipo "bool"), de
modo que obtendríamos un mensaje de error "Cannot implicitly convert type 'int'
to 'bool'" (no puedo convertir un "int" a "bool"), en casos como el del siguiente
programa:
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero = 0)
Console.WriteLine("El número es cero.");
else
if (numero < 0)
Console.WriteLine("El número es negativo.");
else
Console.WriteLine("El número es positivo.");
}
}
3.1.7. Comparaciones y variables de tipo "bool"
using System;
if (esCifra)
Console.WriteLine("Es una cifra numérica.");
else if (esVocal)
Console.WriteLine("Es una vocal.");
else
Console.WriteLine("Es una consonante u otro número.");
}
}
3.1.8. Introducción a los diagramas de flujo
A veces puede resultar difícil ver claro donde usar un "else" o qué instrucciones
de las que siguen a un "if" deben ir entre llaves y cuales no. Generalmente la
dificultad está en el hecho de intentar teclear directamente un programa en C#,
en vez de pensar en el problema que se pretende resolver.
Para ayudarnos a centrarnos en el problema, existen notaciones gráficas, como
los diagramas de flujo, que nos permiten ver mejor qué se debe hacer y cuando.
En primer lugar, vamos a ver los 4 elementos básicos de un diagrama de flujo, y
luego los aplicaremos a un caso concreto.
y equivale a decir "si se cumple la condición, toma el valor v1; si no, toma el
valor v2". Un ejemplo de cómo podríamos usarlo sería
numeroMayor = (a>b) ? a : b;
using System;
mayor = (a>b) ? a : b;
using System;
Ejercicios propuestos:
Crear un programa que use el operador condicional para mostrar un el valor
absoluto de un número de la siguiente forma: si el número es positivo, se
mostrará tal cual; si es negativo, se mostrará cambiado de signo.
Crear un programa que use el operador condicional para dar a una variable
llamada "iguales" (booleana) el valor "true" si los dos números que ha
tecleado el usuario son iguales, o "false" si son distintos.
Usar el operador condicional para calcular el mayor de dos números.
3.1.10. switch
Si queremos ver varios posibles valores, sería muy pesado tener que hacerlo
con muchos "if" seguidos o encadenados. La alternativa es la orden "switch",
cuya sintaxis es
switch (expresión)
{
case valor1: sentencia1;
break;
case valor2: sentencia2;
sentencia2b;
break;
...
case valorN: sentenciaN;
break;
default:
otraSentencia;
break;
};
using System;
switch (letra)
{
case ' ': Console.WriteLine("Espacio.");
break;
case '1': goto case '0';
case '2': goto case '0';
case '3': goto case '0';
case '4': goto case '0';
case '5': goto case '0';
case '6': goto case '0';
case '7': goto case '0';
case '8': goto case '0';
case '9': goto case '0';
case '0': Console.WriteLine("Dígito.");
break;
default: Console.WriteLine("Ni espacio ni dígito.");
break;
}
}
}
Cuidado quien venga del lenguaje C: en C se puede dejar que un caso sea
manejado por el siguiente, lo que se consigue si no se usa "break", mientras que
C# siempre obliga a usar "break" o "goto" en cada caso, para evitar errores
difíciles de detectar. El fragmento anterior en C habría sido:
switch (tecla)
{
case ' ': printf("Espacio.\n");
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0': printf("Dígito.\n");
break;
default: printf("Ni espacio ni dígito.\n");
}
using System;
Console.WriteLine("Introduce tu nombre");
nombre = Console.ReadLine();
switch (nombre)
{
case "Juan": Console.WriteLine("Bienvenido, Juan.");
break;
case "Pedro": Console.WriteLine("Que tal estas, Pedro.");
break;
default: Console.WriteLine("Procede con cautela, desconocido.");
break;
}
}
}
Ejercicios propuestos:
Crear un programa que lea una letra tecleada por el usuario y diga si se trata
de una vocal, una cifra numérica o una consonante.
Crear un programa que lea una letra tecleada por el usuario y diga si se trata
de un signo de puntuación, una cifra numérica o algún otro carácter.
Al igual que en el caso anterior, si queremos que se repitan varias órdenes (es lo
habitual), deberemos encerrarlas entre llaves.
Como ejemplo, vamos a ver cómo sería el típico programa que nos pide una
clave de acceso y no nos deja entrar hasta que tecleemos la clave correcta:
using System;
do
{
Console.Write("Introduzca su clave numérica: ");
clave = Convert.ToInt32(Console.ReadLine());
if (clave != valida) Console.WriteLine("No válida!\n");
}
while (clave != valida);
Console.WriteLine("Aceptada.\n");
}
}
do
{
Console.Write("Introduzca su clave: ");
clave = Console.ReadLine();
if (clave != valida) Console.WriteLine("No válida!\n");
}
while (clave != valida);
Console.WriteLine("Aceptada.\n");
}
}
Ejercicios propuestos:
Crear un programa que pida números positivos al usuario, y vaya calculando
la suma de todos ellos (terminará cuando se teclea un número negativo o
cero).
Crea un programa que escriba en pantalla los números del 1 al 10, usando
"do..while".
Crea un programa que escriba en pantalla los números pares del 26 al 10
(descendiendo), usando "do..while".
Crea un programa que pida al usuario su nombre y su contraseña, y no le
permita seguir hasta que introduzca como nombre "Pedro" y como
contraseña "Peter".
3.2.3. for
Ésta es la orden que usaremos habitualmente para crear partes del programa
que se repitan un cierto número de veces. El formato de "for" es
for (valorInicial; CondiciónRepetición; Incremento)
Sentencia;
Así, para contar del 1 al 10, tendríamos 1 como valor inicial, <=10 como
condición de repetición, y el incremento sería de 1 en 1. Por tanto, el programa
quedaría:
using System;
int contador;
}
}
Los bucles "for" se pueden anidar (incluir uno dentro de otro), de modo que
podríamos escribir las tablas de multiplicar del 1 al 5 con:
using System;
}
}
En estos ejemplos que hemos visto, después de "for" había una única sentencia.
Si queremos que se hagan varias cosas, basta definirlas como un bloque (una
sentencia compuesta) encerrándolas entre llaves. Por ejemplo, si queremos
mejorar el ejemplo anterior haciendo que deje una línea en blanco entre tabla y
tabla, sería:
using System;
}
}
Para "contar" no necesariamente hay que usar números. Por ejemplo, podemos
contar con letras así:
using System;
char letra;
}
}
using System;
char letra;
}
}
Ejercicios propuestos:
Crear un programa que muestre las letras de la Z (mayúscula) a la A
(mayúscula, descendiendo).
Crear un programa que escriba en pantalla la tabla de multiplicar del 5.
Crear un programa que escriba en pantalla los números del 1 al 50 que sean
múltiplos de 3 (pista: habrá que recorrer todos esos números y ver si el resto
de la división entre 3 resulta 0).
int contador;
}
}
1 2 3 4
using System;
int contador;
}
}
1 2 3 4 6 7 8 9 10
Respuesta: escribe 5, porque no hay llaves tras el "for", luego sólo se repite la
orden "if".
3.5. Sentencia goto
El lenguaje C# también permite la orden "goto", para hacer saltos
incondicionales. Su uso indisciplinado está muy mal visto, porque puede ayudar
a hacer programas llenos de saltos, difíciles de seguir. Pero en casos concretos
puede ser muy útil, por ejemplo, para salir de un bucle muy anidado (un "for"
dentro de otro "for" que a su vez está dentro de otro "for": en este caso, "break"
sólo saldría del "for" más interno).
El formato de "goto" es
goto donde;
donde:
using System;
int i, j;
}
}
i vale 0 y j vale 0.
i vale 0 y j vale 2.
i vale 0 y j vale 4.
i vale 0 y j vale 6.
i vale 0 y j vale 8.
i vale 0 y j vale 10.
i vale 0 y j vale 12.
i vale 0 y j vale 14.
i vale 0 y j vale 16.
i vale 0 y j vale 18.
i vale 0 y j vale 20.
i vale 1 y j vale 0.
i vale 1 y j vale 2.
i vale 1 y j vale 4.
i vale 1 y j vale 6.
Fin del programa
Ejercicios propuestos:
Crear un programa que dé al usuario la oportunidad de adivinar un número
del 1 al 100 (prefijado en el programa) en un máximo de 6 intentos. En cada
pasada deberá avisar de si se ha pasado o se ha quedado corto.
Crear un programa que descomponga un número (que teclee el usuario)
como producto de su factores primos. Por ejemplo, 60 = 2 • 2 • 3 • 5
4. Arrays, estructuras y cadenas de texto
4.1. Conceptos básicos sobre arrays o tablas
4.1.1. Definición de un array y acceso a los datos
Una tabla, vector o array (que algunos autores traducen por "arreglo") es un
conjunto de elementos, todos los cuales son del mismo tipo. Estos elementos
tendrán todos el mismo nombre, y ocuparán un espacio contiguo en la memoria.
Por ejemplo, si queremos definir un grupo de números enteros, el tipo de datos
que usaremos para declararlo será "int[]". Si sabemos desde el principio cuantos
datos tenemos (por ejemplo 4), les reservaremos espacio con "= new int[4]", así
int[] ejemplo = new int[4];
using System;
Al igual que ocurría con las variables "normales", podemos dar valor a los
elementos de una tabla al principio del programa. Será más cómodo que dar los
valores uno por uno, como hemos hecho antes, pero sólo se podrá hacer si
conocemos todos los valores. En este caso, los indicaremos todos entre llaves,
separados por comas:
using System;
Ejercicio propuesto:
Un programa almacene en una tabla el número de días que tiene cada mes
(supondremos que es un año no bisiesto), pida al usuario que le indique un
mes (1=enero, 12=diciembre) y muestre en pantalla el número de días que
tiene ese mes.
Un programa que almacene en una tabla el número de días que tiene cada
mes (año no bisiesto), pida al usuario que le indique un mes (ej. 2 para
febrero) y un día (ej. El 15) y diga qué número de día es dentro del año (por
ejemplo, el 15 de febrero sería el día número 46, el 31 de diciembre sería el
día 365).
4.1.3. Recorriendo los elementos de una tabla
Es de esperar que exista una forma más cómoda de acceder a varios elementos
de un array, sin tener siempre que repetirlos todos, como hemos hecho en
suma = numero[0] + numero[1] + numero[2] + numero[3] + numero[4];
El "truco" consistirá en emplear cualquiera de las estructuras repetitivas que ya
hemos visto (while, do..while, for), por ejemplo así:
suma = 0; /* Valor inicial de la suma */
for (i=0; i<=4; i++) /* Y hallamos la suma repetitiva */
suma += numero[i];
using System;
En este caso, que sólo sumábamos 5 números, no hemos escrito mucho menos,
pero si trabajásemos con 100, 500 o 1000 números, la ganancia en comodidad
sí que está clara.
Ejercicios propuestos:
A partir del programa propuesto en 4.1.2, que almacenaba en una tabla el
número de días que tiene cada mes, crear otro que pida al usuario que le
indique la fecha, detallando el día (1 al 31) y el mes (1=enero,
12=diciembre), como respuesta muestre en pantalla el número de días que
quedan hasta final de año.
Crear un programa que pida al usuario 10 números y luego los muestre en
orden inverso (del último al primero).
Crear un programa que pida al usuario 10 números, calcule su media y
luego muestre los que están por encima de la media.
Un programa que pida 10 nombres y los memorice (pista: esta vez se trata
de un array de "string"). Después deberá pedir que se teclee un nombre y
dirá si se encuentra o no entre los 10 que se han tecleado antes. Volverá a
pedir otro nombre y a decir si se encuentra entre ellos, y así sucesivamente
hasta que se teclee "fin".
Un programa que prepare espacio para un máximo de 100 nombres. El
usuario deberá ir introduciendo un nombre cada vez, hasta que se pulse
Intro sin teclear nada, momento en el que dejarán de pedirse más nombres y
se mostrará en pantalla la lista de los nombres que se han introducido.
4.2. Tablas bidimensionales
Podemos declarar tablas de dos o más dimensiones. Por ejemplo, si queremos
guardar datos de dos grupos de alumnos, cada uno de los cuales tiene 20
alumnos, tenemos dos opciones:
Podemos usar int datosAlumnos[40] y entonces debemos recordar que los
20 primeros datos corresponden realmente a un grupo de alumnos y los 20
siguientes a otro grupo. Es "demasiado artesanal", así que no daremos más
detalles.
O bien podemos emplear int datosAlumnos[2,20] y entonces sabemos que
los datos de la forma datosAlumnos[0,i] son los del primer grupo, y los
datosAlumnos[1,i] son los del segundo.
Una alternativa, que puede sonar más familiar a quien ya haya programado
en C es emplear int datosAlumnos[2][20] pero en C# esto no tiene
exactamente el mismo significado que [2,20], sino que se trata de dos
arrays, cuyos elementos a su vez son arrays de 20 elementos. De hecho,
podrían ser incluso dos arrays de distinto tamaño, como veremos en el
segundo ejemplo.
En cualquier caso, si queremos indicar valores iniciales, lo haremos entre llaves,
igual que si fuera una tabla de una única dimensión.
Vamos a ver un primer ejemplo del uso con arrays de la forma [2,20], lo que
podríamos llamar el "estilo Pascal", en el usemos tanto arrays con valores
prefijados, como arrays para los que reservemos espacio con "new" y a los que
demos valores más tarde:
using System;
Este tipo de tablas de varias dimensiones son las que se usan también para
guardar matrices, cuando se trata de resolver problemas matemáticos más
complejos que los que hemos visto hasta ahora.
La otra forma de tener arrays multidimensionales son los "arrays de arrays", que,
como ya hemos comentado, y como veremos en este ejemplo, pueden tener
elementos de distinto tamaño. En ese caso nos puede interesar saber su
longitud, para lo que podemos usar "a.Length":
using System;
}
}
using System;
struct tipoPersona
{
public string nombre;
public char inicial;
public int edad;
public float nota;
}
Nota: La notación 7.5f se usa para detallar que se trata de un número real de
simple precisión (un "float"), porque de lo contrario, 7.5 se consideraría un
número de doble precisión, y al tratar de compilar obtendríamos un mensaje de
error, diciendo que no se puede convertir de "double" a "float" sin pérdida de
precisión. Al añadir la "f" al final, estamos diciendo "quiero que éste número se
tome como un float; sé que habrá una pérdida de precisión pero es aceptable
para mí”.
Ejercicio propuesto: Un "struct" que almacene datos de una canción en formato
MP3: Artista, Título, Duración (en segundos), Tamaño del fichero (en KB). Un
programa debe pedir los datos de una canción al usuario, almacenarlos en dicho
"struct" y después mostrarlos en pantalla.
4.3.2. Arrays de estructuras
using System;
struct tipoPersona
{
public string nombre;
public char inicial;
public int edad;
public float nota;
}
persona[0].nombre = "Juan";
persona[0].inicial = 'J';
persona[0].edad = 20;
persona[0].nota = 7.5f;
Console.WriteLine("La edad de {0} es {1}",
persona[0].nombre, persona[0].edad);
persona[1].nombre = "Pedro";
Console.WriteLine("La edad de {0} es {1}",
persona[1].nombre, persona[1].edad);
}
}
Podemos encontrarnos con un registro que tenga varios datos, y que a su vez
ocurra que uno de esos datos esté formado por varios datos más sencillos. Por
ejemplo, una fecha de nacimiento podría estar formada por día, mes y año. Para
hacerlo desde C#, incluiríamos un "struct" dentro de otro, así:
using System;
struct fechaNacimiento
{
public int dia;
public int mes;
public int anyo;
}
struct tipoPersona
{
public string nombre;
public char inicial;
public fechaNacimiento diaDeNacimiento;
public float nota;
}
persona.nombre = "Juan";
persona.inicial = 'J';
persona.diaDeNacimiento.dia = 15;
persona.diaDeNacimiento.mes = 9;
persona.nota = 7.5f;
Console.WriteLine("{0} nació en el mes {1}",
persona.nombre, persona.diaDeNacimiento.mes);
}
}
using System;
if (nombre == "Alberto")
Console.WriteLine("Dices que eres Alberto?");
else
Console.WriteLine("Así que no eres Alberto?");
Podemos leer (o modificar) una de las letras de una cadena de igual forma que
leemos los elementos de cualquier array: si la cadena se llama "texto", el primer
elemento será texto[0], el segundo será texto[1] y así sucesivamente.
Eso sí, las cadenas en C# no se pueden modificar letra a letra: no podemos
hacer texto[0]=’a’. Para eso habrá que usar una construcción auxiliar, que
veremos más adelante.
4.4.3. Longitud de la cadena.
Podemos saber cuantas letras forman una cadena con "cadena.Length". Esto
permite que podamos recorrer la cadena letra por letra, usando construcciones
como "for".
Ejercicio propuesto: Un programa te pida tu nombre y lo muestre en pantalla
separando cada letra de la siguiente con un espacio. Por ejemplo, si tu nombre
es "Juan", debería aparecer en pantalla "J u a n".
4.4.4. Extraer una subcadena
Podemos extraer parte del contenido de una cadena con "Substring", que recibe
dos parámetros: la posición a partir de la que queremos empezar y la cantidad
de caracteres que queremos obtener. El resultado será otra cadena:
saludo = frase.Substring(0,4);
Para ver si una cadena contiene un cierto texto, podemos usar IndexOf
("posición de"), que nos dice en qué posición se encuentra (o devuelve el valor -
1 si no aparece):
if (nombre.IndexOf("Juan") >= 0) Console.Write("Bienvenido, Juan");
De forma similar, LastIndexOf ("última posición de") indica la última aparición (es
decir, busca de derecha a izquierda).
4.4.6. Otras manipulaciones de cadenas
using System;
}
}
Y su resultado sería
using System;
}
}
Fragmento 0= uno
Fragmento 1= dos
Fragmento 2= tres
Fragmento 3= cuatro
using System;
string frase;
}
}
Si tecleamos una palabra como "gol", que comienza por G, que alfabéticamente
está antes de la H de "hola", se nos dirá que esa palabra es menor:
Si escribimos "hOLa", que coindice con "hola" salvo por las mayúsculas, una
comparación normal nos dirá que es mayor (las mayúsculas se consideran
"mayores" que las minúsculas), y una comparación sin considerar mayúsculas o
minúsculas nos dirá que coinciden:
Escriba una palabra
hOLa
Es mayor que hola
Es hola (mays o mins)
using System;
using System.Text; // Usaremos un System.Text.StringBuilder
string cadenaNormal;
cadenaNormal = cadenaModificable.ToString();
Console.WriteLine("Cadena normal a partir de ella: {0}",
cadenaNormal);
}
}
Ejercicio propuesto:
Un programa que pida tu nombre, tu día de nacimiento y tu mes de
nacimiento y lo junte todo en una cadena, separando el nombre de la fecha
por una coma y el día del mes por una barra inclinada, así: "Juan, nacido el
31/12".
Crear un juego del ahorcado, en el que un primer usuario introduzca la
palabra a adivinar, se muestre esta programa oculta con guiones (-----) y el
programa acepte las letras que introduzca el segundo usuario, cambiando
los guiones por letras correctas cada vez que acierte (por ejemplo, a---a-t-).
La partida terminará cuando se acierte la palabra por completo o el usuario
agote sus 8 intentos.
4.4.9. Recorriendo con "foreach"
using System;
}
}
using System;
struct tipoFicha {
public string nombreFich; /* Nombre del fichero */
public long tamanyo; /* El tamaño en bytes */
};
do {
/* Menu principal */
Console.WriteLine();
Console.WriteLine("Escoja una opción:");
Console.WriteLine("1.- Añadir datos de un nuevo fichero");
Console.WriteLine("2.- Mostrar los nombres de todos los ficheros");
Console.WriteLine("3.- Mostrar ficheros que sean de mas de un cierto
tamaño");
Console.WriteLine("4.- Ver datos de un fichero");
Console.WriteLine("5.- Salir");
}
}
Funciona, y hace todo lo que tiene que hacer, pero es mejorable. Por supuesto,
en un caso real es habitual que cada ficha tenga que guardar más información
que sólo esos dos apartados de ejemplo que hemos previsto esta vez. Si nos
muestra todos los datos en pantalla y se trata de muchos datos, puede ocurrir
que aparezcan en pantalla tan rápido que no nos dé tiempo a leerlos, así que
sería deseable que parase cuando se llenase la pantalla de información (por
ejemplo, una pausa tras mostrar cada 25 datos). Por supuesto, se nos pueden
ocurrir muchas más preguntas que hacerle sobre nuestros datos. Y además,
cuando salgamos del programa se borrarán todos los datos que habíamos
tecleado, pero eso es lo único "casi inevitable", porque aún no sabemos manejar
ficheros.
Ejercicios propuestos:
Un programa que pida el nombre, el apellido y la edad de una persona, los
almacene en un "struct" y luego muestre los tres datos en una misma línea,
separados por comas.
Un programa que pida datos de 8 personas: nombre, dia de nacimiento, mes
de nacimiento, y año de nacimiento (que se deben almacenar en una tabla
de structs). Después deberá repetir lo siguiente: preguntar un número de
mes y mostrar en pantalla los datos de las personas que cumplan los años
durante ese mes. Terminará de repetirse cuando se teclee 0 como número
de mes.
Un programa que sea capaz de almacenar los datos de 50 personas:
nombre, dirección, teléfono, edad (usando una tabla de structs). Deberá ir
pidiendo los datos uno por uno, hasta que un nombre se introduzca vacío
(se pulse Intro sin teclear nada). Entonces deberá aparecer un menú que
permita:
Selección directa
Nota: el símbolo "<>" se suele usar en pseudocódigo para indicar que un dato
es distinto de otro, de modo que equivale al "!=" de C#. La penúltima línea
en C# saldría a ser algo como "if (menor !=i)"
Inserción directa
(Es mejorable, no intercambiando el dato que se mueve con cada elemento, sino
sólo al final de cada pasada, pero no entraremos en más detalles).
Un programa de prueba podría ser:
using System;
// BURBUJA
// (Intercambiar cada pareja consecutiva que no esté ordenada)
// Para i=1 hasta n-1
// Para j=i+1 hasta n
// Si A[i] > A[j]
// Intercambiar ( A[i], A[j])
Console.WriteLine("Ordenando mediante burbuja... ");
for(i=0; i < n-1; i++)
{
foreach (int dato in datos) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();
// SELECCIÓN DIRECTA:
// (En cada pasada busca el menor, y lo intercambia al final de la
pasada)
// Para i=1 hasta n-1
// menor = i
// Para j=i+1 hasta n
// Si A[j] < A[menor]
// menor = j
// Si menor <> i
// Intercambiar ( A[i], A[menor])
Console.WriteLine("\nOrdenando mediante selección directa... ");
int[] datos2 = {5, 3, 14, 20, 8, 9, 1};
for(i=0; i < n-1; i++)
{
foreach (int dato in datos2) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();
int menor = i;
for(j=i+1; j < n; j++)
if (datos2[j] < datos2[menor])
menor = j;
if (i != menor)
{
datoTemporal = datos2[i];
datos2[i] = datos2[menor];
datos2[menor] = datoTemporal;
}
}
Console.Write("Ordenado:");
// INSERCION DIRECTA:
// (Comparar cada elemento con los anteriores -que ya están
ordenados-
// y desplazarlo hasta su posición correcta).
// Para i=2 hasta n
// j=i-1
// mientras (j>=1) y (A[j] > A[j+1])
// Intercambiar ( A[j], A[j+1])
// j = j - 1
Console.WriteLine("\nOrdenando mediante inserción directa... ");
int[] datos3 = {5, 3, 14, 20, 8, 9, 1};
for(i=1; i < n; i++)
{
foreach (int dato in datos3) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();
j = i-1;
while ((j>=0) && (datos3[j] > datos3[j+1]))
{
datoTemporal = datos3[j];
datos3[j] = datos3[j+1];
datos3[j+1] = datoTemporal;
j--;
}
}
Console.Write("Ordenado:");
Ahora desde dentro del cuerpo de nuestro programa, podríamos "llamar" a esa
función:
public static void Main()
{
saludar();
…
}
Así conseguimos que nuestro programa principal sea más fácil de leer.
Un detalle importante: tanto la función habitual "Main" como la nueva función
"Saludar" serían parte de nuestra "class", es decir, el fuente completo sería así:
using System;
Como ejemplo más detallado, la parte principal de una agenda podría ser
simplemente:
leerDatosDeFichero();
do {
mostrarMenu();
pedirOpcion();
switch( opcion ) {
case 1: buscarDatos(); break;
case 2: modificarDatos(); break;
case 3: anadirDatos(); break;
…
5.3. Parámetros de una función
Es muy frecuente que nos interese además indicarle a nuestra función ciertos
datos especiales con los que queremos que trabaje. Por ejemplo, si escribimos
en pantalla números reales con frecuencia, nos puede resultar útil que nos los
muestre con el formato que nos interese. Lo podríamos hacer así:
public static void escribeNumeroReal( float n )
{
Console.WriteLine( n.ToString("#.###") );
}
(recordemos que el sufijo "f" es para indicar al compilador que trate ese número
como un "float", porque de lo contrario, al ver que tiene cifras decimales, lo
tomaría como "double", que permite mayor precisión... pero a cambio nosotros
tendríamos un mensaje de error en nuestro programa, diciendo que estamos
dando un dato "double" a una función que espera un "float").
El programa completo podría quedar así:
using System;
x= 5.1f;
Console.WriteLine("El primer numero real es: ");
escribeNumeroReal(x);
Console.WriteLine(" y otro distinto es: ");
escribeNumeroReal(2.3f);
}
Podemos hacer una función que nos diga cual es el mayor de dos números
reales así:
Ejercicios propuestos:
Crear una función que borre la pantalla dibujando 25 líneas en blanco. No
debe devolver ningún valor.
Crear una función que calcule el cubo de un número real (float). El resultado
deberá ser otro número real. Probar esta función para calcular el cubo de
3.2 y el de 5.
Crear una función que calcule cual es el menor de dos números enteros. El
resultado será otro número entero.
Crear una función llamada "signo", que reciba un número real, y devuelva un
número entero con el valor: -1 si el número es negativo, 1 si es positivo o 0
si es cero.
Crear una función que devuelva la primera letra de una cadena de texto.
Probar esta función para calcular la primera letra de la frase "Hola".
Crear una función que devuelva la última letra de una cadena de texto.
Probar esta función para calcular la última letra de la frase "Hola".
Crear una función que reciba un número y muestre en pantalla el perímetro
y la superficie de un cuadrado que tenga como lado el número que se ha
indicado como parámetro.
5.5. Variables locales y variables globales
Hasta ahora, hemos declarado las variables dentro de "Main". Ahora nuestros
programas tienen varios "bloques", así que se comportarán de forma distinta
según donde declaremos las variables.
Las variables se pueden declarar dentro de un bloque (una función), y entonces
sólo ese bloque las conocerá, no se podrán usar desde ningún otro bloque del
programa. Es lo que llamaremos "variables locales".
Por el contrario, si declaramos una variable al comienzo del programa, fuera de
todos los "bloques" de programa, será una "variable global", a la que se podrá
acceder desde cualquier parte.
Vamos a verlo con un ejemplo. Crearemos una función que calcule la potencia
de un número entero (un número elevado a otro), y el cuerpo del programa que
la use.
La forma de conseguir elevar un número a otro será a base de multiplicaciones,
es decir:
3 elevado a 5 = 3 • 3 • 3 • 3 • 3
using System;
En este caso, las variables "temporal" e "i" son locales a la función "potencia":
para "Main" no existen. Si en "Main" intentáramos hacer i=5; obtendríamos un
mensaje de error.
De igual modo, "num1" y "num2" son locales para "main": desde la función
"potencia" no podemos acceder a su valor (ni para leerlo ni para modificarlo),
sólo desde "main".
En general, deberemos intentar que la mayor cantidad de variables posible sean
locales (lo ideal sería que todas lo fueran). Así hacemos que cada parte del
programa trabaje con sus propios datos, y ayudamos a evitar que un error en un
trozo de programa pueda afectar al resto. La forma correcta de pasar datos entre
distintos trozos de programa es usando los parámetros de cada función, como
en el anterior ejemplo.
Ejercicios propuestos:
Crear una función "pedirEntero", que reciba como parámetros el texto que
se debe mostrar en pantalla, el valor mínimo aceptable y el valor máximo
aceptable. Deberá pedir al usuario que introduzca el valor tantas veces
como sea necesario, volvérselo a pedir en caso de error, y devolver un valor
correcto. Probarlo con un programa que pida al usuario un año entre 1800 y
2100.
Crear una función "escribirTablaMultiplicar", que reciba como parámetro un
número entero, y escriba la tabla de multiplicar de ese número (por ejemplo,
para el 3 deberá llegar desde 3x0=0 hasta 3x10=30).
Crear una función "esPrimo", que reciba un número y devuelva el valor
booleano "true" si es un número primo o "false" en caso contrario.
Crear una función que reciba una cadena y una letra, y devuelva la cantidad
de veces que dicha letra aparece en la cadena. Por ejemplo, si la cadena es
"Barcelona" y la letra es 'a', debería devolver 2 (aparece 2 veces).
Crear una función que reciba un numero cualquiera y que devuelva como
resultado la suma de sus dígitos. Por ejemplo, si el número fuera 123 la
suma sería 6.
Crear una función que reciba una letra y un número, y escriba un "triángulo"
formado por esa letra, que tenga como anchura inicial la que se ha indicado.
Por ejemplo, si la letra es * y la anchura es 4, debería escribir
****
***
**
*
using System;
¿Por qué? Sencillo: tenemos una variable local dentro de "duplica" y otra dentro
de "main". El hecho de que las dos tengan el mismo nombre no afecta al
funcionamiento del programa, siguen siendo distintas.
Si la variable es "global", declarada fuera de estas funciones, sí será accesible
por todas ellas:
using System;
static int n = 7;
Dentro de poco, hablaremos de por qué cada uno de los bloques de nuestro
programa, e incluso las "variables globales", tienen delante la palabra "static".
Será cuando tratemos la "Programación Orientada a Objetos", en el próximo
tema.
using System;
}
El resultado de este programa será:
n vale 5
El valor recibido vale 5
y ahora vale 10
Ahora n vale 5
Vemos que al salir de la función, los cambios que hagamos a esa variable que
se ha recibido como parámetro no se conservan.
Esto se debe a que, si no indicamos otra cosa, los parámetros "se pasan por
valor", es decir, la función no recibe los datos originales, sino una copia de ellos.
Si modificamos algo, estamos cambiando una copia de los datos originales, no
dichos datos.
Si queremos que los cambios se conserven, basta con hacer un pequeño
cambio: indicar que la variable se va a pasar "por referencia", lo que se indica
usando la palabra "ref", tanto en la declaración de la función como en la llamada,
así:
using System;
using System;
Podemos hacer que sea realmente un poco más aleatorio si en la primera orden
le indicamos que tome como semilla el instante actual:
De hecho, una forma muy simple de obtener un número "casi al azar" entre 0 y
999 es tomar las milésimas de segundo de la hora actual:
using System;
Ejercicios propuestos:
Crear un programa que genere un número al azar entre 1 y 100. El usuario
tendrá 6 oportunidades para acertarlo.
Mejorar el programa del ahorcado propuesto en el apartado 4.4.8, para que
la palabra a adivinar no sea tecleado por un segundo usuario, sino que se
escoja al azar de un "array" de palabras prefijadas (por ejemplo, nombres de
ciudades).
5.9.2. Funciones matemáticas
5.10. Recursividad
Una función recursiva es aquella que se define a partir de ella misma. Dentro de
las matemáticas tenemos varios ejemplos. Uno clásico es el "factorial de un
número":
n! = n • (n-1) • (n-2) • ... • 3 • 2 • 1
using System;
En este caso, la orden sería "ls", y las dos opciones (argumentos o parámetros)
que le indicamos son "-l" y "*.c".
La orden equivalente en MsDos y en el intérprete de comandos de Windows
sería
dir *.c
Environment.Exit(1);
Es decir, entre paréntesis indicamos un cierto código, que suele ser (por
convenio) un 0 si no ha habido ningún error, u otro código distinto en caso de
que sí exista algún error.
Este valor se podría comprobar desde el sistema operativo. Por ejemplo, en
MsDos y Windows se lee con "IF ERRORLEVEL", así:
IF ERRORLEVEL 1 ECHO Ha habido un error en el programa
using System;
if (args.Length == 0)
{
Console.WriteLine("Escriba algún parámetro!");
Environment.Exit(1);
}
return 0;
Ejercicios propuestos:
Crear un programa llamado "suma", que calcule (y muestre) la suma de dos
números que se le indiquen como parámetro. Por ejemplo, si se teclea
"suma 2 3" deberá responder "5", y si se teclea "suma 2" deberá responder
"no hay suficientes datos y devolver un código de error 1.
Crear una calculadora básica, llamada "calcula", que deberá sumar, restar,
multiplicar o dividir los dos números que se le indiquen como parámetros.
Ejemplos de su uso sería "calcula 2 + 3" o "calcula 5 * 60".
6. Programación orientada a objetos
6.1. ¿Por qué los objetos?
Hasta ahora hemos estado "cuadriculando" todo para obtener algoritmos:
tratábamos de convertir cualquier cosa en una función que pudiéramos emplear
en nuestros programas. Cuando teníamos claros los pasos que había que dar,
buscábamos las variables necesarias para dar esos pasos.
Pero no todo lo que nos rodea es tan fácil de cuadricular. Supongamos por
ejemplo que tenemos que introducir datos sobre una puerta en nuestro
programa. ¿Nos limitamos a programar los procedimientos AbrirPuerta y
CerrarPuerta? Al menos, deberíamos ir a la zona de declaración de variables, y
allí guardaríamos otras datos como su tamaño, color, etc.
No está mal, pero es "antinatural": una puerta es un conjunto: no podemos
separar su color de su tamaño, o de la forma en que debemos abrirla o cerrarla.
Sus características son tanto las físicas (lo que hasta ahora llamábamos
variables) como sus comportamientos en distintas circunstancias (lo que para
nosotros eran las funciones). Todo ello va unido, formando un "objeto".
using System;
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Console.WriteLine("\nVamos a abrir...");
p.Abrir();
p.MostrarEstado();
}
}
Este fuente ya no contiene una única clase (class), como todos nuestros
ejemplos anteriores, sino dos clases distintas:
La clase "Puerta", que son los nuevos objetos con los que vamos a
practicar.
La clase "Ejemplo59", que representa a nuestra aplicación.
El resultado de ese programa es el siguiente:
Valores iniciales...
Ancho: 0
Alto: 0
Color: 0
Abierta: False
Vamos a abrir...
Ancho: 0
Alto: 0
Color: 0
Abierta: True
Se puede ver que en C#, al contrario que en otros lenguajes, las variables que
forman parte de una clase (los "atributos") tienen un valor inicial predefinido: 0
para los números, una cadena vacía para las cadenas de texto, o "False" para
los datos booleanos.
Vemos también que se accede a los métodos y a los datos precediendo el
nombre de cada uno por el nombre de la variable y por un punto, como
hacíamos con los registros (struct). Aun así, en nuestro caso no podemos hacer
directamente "p.abierta = true", por dos motivos:
El atributo "abierta" no tiene delante la palabra "public"; por lo que no es
público, sino privado, y no será accesible desde otras clases (en nuestro
caso, desde Ejemplo59).
Los puristas de la Programación Orientada a Objetos recomiendan que no
se acceda directamente a los atributos, sino que siempre se modifiquen
usando métodos auxiliares (por ejemplo, nuestro "Abrir"), y que se lea su
valor también usando una función. Esto es lo que se conoce como
"ocultación de datos". Supondrá ventajas como que podremos cambiar los
detalles internos de nuestra clase sin que afecte a su uso.
También puede desconcertar que en "Main" aparezca la palabra "static",
mientras que no lo hace en los métodos de la clase "Puerta". Veremos este
detalle un poco más adelante.
Ejercicio propuesto :
Crear una clase llamada Persona, en el fichero "persona.cs". Esta clase
deberá tener un atributo "nombre", de tipo string. También deberá tener un
método "SetNombre", de tipo void y con un parámetro string, que permita
cambiar el valor del nombre. Finalmente, también tendrá un método
"Saludar", que escribirá en pantalla "Hola, soy " seguido de su nombre.
Crear también una clase llamada PruebaPersona, en el fichero
"pruebaPersona.cs". Esta clase deberá contener sólo la función Main, que
creará dos objetos de tipo Persona, les asignará un nombre y les pedirá que
saluden.
bool bloqueada;
Con "public class Porton: Puerta" indicamos que Porton debe "heredar" todo lo
que ya habíamos definido para Puerta. Por eso, no hace falta indicar
nuevamente que un Portón tendrá un cierto ancho, o un color, o que se puede
abrir: todo eso lo tiene por ser un "descendiente" de Puerta.
No tenemos por qué heredar todo; también podemos "redefinir" algo que ya
existía. Por ejemplo, nos puede interesar que "MostrarEstado" ahora nos diga
también si la puerta está bloqueada. Para eso, basta con volverlo a declarar y
añadir la palabra "new" para indicar al compilador de C# que sabemos que ya
existe ese método y que sabemos seguro que lo queremos redefinir:
public new void MostrarEstado()
{
Console.WriteLine("Ancho: {0}", ancho);
Console.WriteLine("Alto: {0}", alto);
Console.WriteLine("Color: {0}", color);
Console.WriteLine("Abierta: {0}", abierta);
Console.WriteLine("Bloqueada: {0}", bloqueada);
}
Aun así, esto todavía no funciona: los atributos de una Puerta, como el "ancho" y
el "alto" estaban declarados como "privados" (es lo que se considera si no
decimos los contrario), por lo que no son accesibles desde ninguna otra clase, ni
siquiera desde Porton.
La solución más razonable no es declararlos como "public", porque no queremos
que sean accesibles desde cualquier sitio. Sólo querríamos que esos datos
estuvieran disponibles para todos los tipos de Puerta, incluyendo sus
"descendientes", como un Porton. Esto se puede conseguir usando otro método
de acceso: "protected". Todo lo que declaremos como "protected" será accesible
por las clases derivadas de la actual, pero por nadie más:
(Si quisiéramos dejar claro que algún elemento de una clase debe ser totalmente
privado, podemos usar la palabra "private", en vez de "public" o "protected").
Un fuente completo que declarase la clase Puerta, la clase Porton a partir de
ella, y que además contuviese un pequeño "Main" de prueba podría ser:
using System;
// -------------------------------
public class Puerta
{
// -------------------------------
public class Porton: Puerta
{
bool bloqueada;
// -------------------------------
public class Ejemplo60
{
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Este diagrama es una forma más simple de ver las clases existentes y las
relaciones entre ellas. Si generamos las clases a partir del diagrama, tendremos
parte del trabajo hecho: ya "sólo" nos quedará rellenar los detalles de métodos
como "Abrir", pero el esqueleto de todas las clases ya estará "escrito" para
nosotros.
que desde dentro de "Main" (incluso perteneciente a otra clase) se usaría con el
nombre de la clase delante:
public ComienzoPartida() {
Hardware.BorrarPantalla ();
...
Desde una función "static" no se puede llamar a otras funciones que no lo sean.
Por eso, como nuestro "Main" debe ser static, deberemos siempre elegir entre:
Que todas las demás funciones de nuestro fuente también estén declaradas
como "static", por lo que podrán ser utilizadas desde "Main".
Declarar un objeto de la clase correspondiente, y entonces sí podremos
acceder a sus métodos desde "Main":
public LanzarJuego () {
Juego j = new Juego();
j.ComienzoPartida ();
...
public Puerta()
{
ancho = 100;
alto = 200;
color = 0xFFFFFF;
abierta = false;
}
Podemos tener más de un constructor, cada uno con distintos parámetros. Por
ejemplo, puede haber otro constructor que nos permita indicar el ancho y el alto:
public Puerta()
{
ancho = 100;
alto = 200;
color = 0xFFFFFF;
abierta = false;
}
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Console.WriteLine("\nVamos a abrir...");
p.Abrir();
p.MostrarEstado();
~Puerta()
{
// Liberar memoria
// Cerrar ficheros
}
puerta.Abrir ();
libro.Abrir ();
using System;
public Animal()
{
Console.WriteLine("Ha nacido un animal");
}
}
// ------------------
public Perro()
{
Console.WriteLine("Ha nacido un perro");
}
}
// ------------------
public Gato()
{
Console.WriteLine("Ha nacido un gato");
}
}
// ------------------
public GatoSiames()
{
Console.WriteLine("Ha nacido un gato siamés");
}
}
// ------------------
Ha nacido un animal
Ha nacido un animal
Ha nacido un gato
Ha nacido un gato siamés
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ha nacido un gato
Ejercicio propuesto:
Crear un único fuente que contenga las siguientes clases:
o Una clase Trabajador, cuyo constructor escriba en pantalla "Soy un
trabajador".
o Una clase Programador, que derive de Trabajador, cuyo constructor escriba
en pantalla "Soy programador".
o Una clase Analista, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy analista".
o Una clase Ingeniero, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy ingeniero".
o Una clase IngenieroInformatico, que derive de Ingeniero, cuyo constructor
escriba en pantalla "Soy ingeniero informático".
o Una clase "PruebaDeTrabajadores", que cree un objeto perteneciente a
cada una de esas clases.
6.9. Arrays de objetos
Es muy frecuente que no nos baste con tener un objeto de cada clase, sino que
necesitemos manipular varios objetos pertenecientes a la misma clase.
En ese caso, deberemos reservar memoria primero para el array, y luego para
cada uno de los elementos. Por ejemplo, podríamos tener un array de 5 perros,
que crearíamos de esta forma:
using System;
public Animal()
{
Console.WriteLine("Ha nacido un animal");
}
}
// ------------------
public Perro()
{
Console.WriteLine("Ha nacido un perro");
}
}
// ------------------
}
y su salida en pantalla, parecida a la del ejemplo anterior, sería
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ha nacido un perro
Ejercicio propuesto:
• Crea una versión ampliada del anterior ejercicio propuesto, en la que no se
cree un único objeto de cada clase, sino un array de tres objetos.
Además, existe una peculiaridad curiosa: podemos crear un array de "Animales",
pero luego indicar que unos de ellos son perros, otros gatos, etc.,
using System;
public Animal()
{
Console.WriteLine("Ha nacido un animal");
}
}
// ------------------
public Perro()
{
Console.WriteLine("Ha nacido un perro");
}
}
// ------------------
using System;
// ------------------
// ------------------
// ------------------
miGato.Hablar();
Console.WriteLine(); // Linea en blanco
miGato2.Hablar();
Su resultado sería
Miauuu
Miauuu
Pfff
return nuevaMatriz;
}
Desde "Main", calcularíamos una matriz como suma de otras dos haciendo
simplemente
Ejercicios propuestos:
• Desarrolla una clase "Matriz", que represente a una matriz de 3x3, con
métodos para indicar el valor que hay en una posición, leer el valor de una
posición, escribir la matriz en pantalla y sumar dos matrices.