Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Texto Apoyo C# PDF

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 115

Contenido

 1. Toma de contacto con C#

o 1.1 Escribir un texto en C


o 1.3 Mostrar números enteros en pantalla
o 1.4 Operaciones aritméticas básicas
o 1.5 Introducción a las variables: int
o 1.6 Identificadores
o 1.7 Comentarios
o 1.8 Datos por el usuario: ReadLine

 2. Tipos de datos básicos

o 2.a Tipo de datos entero


o 2.b Tipo de datos real
o 2.c Tipos de datos carácter, cadena y booleano

 3. Estructuras de control

o 3.a if, sentencias compuestas, operadores relacionales


o 3.b if y else
o 3.c Operadores lógicos
o 3.d Intro a los diagramas de flujo
o 3.e Operador condicional (?)
o 3.f switch
o 3.g while, do..while, for
o 3.h break, continue, goto
o 3.i Diagramas de Chapin
o 3.j Cuándo usar cada tipo de bucle

 4. Arrays, estructuras y cadenas de texto

o 4.a Conceptos básicos sobre arrays o tablas


o 4.b Tablas bidimensionales
o 4.c Estructuras o registros
o 4.d Cadenas de caracteres
o 4.e Ejemplo completo
o 4.f Ordenaciones simples
 5. Funciones. Programación modular.

o 5.a Conceptos básicos sobre funciones


o 5.b Parámetros de una función. Valor devuelto.
o 5.c Variables locales y variables globales. Parámetros por referencia.
o 5.d Algunas funciones útiles. Recursividad.
o 5.e Parámetros y valor de retorno de "Main".

 6. Programación Orientada a Objetos.

o 6.a ¿Por qué los objetos? Objetos y clases en C#


o 6.b Herencia y visibilidad
o 6.c ¿Cómo se diseñan las clases? La palabra "static". Constructores y
destructores.
o 6.d Sobrecarga y polimorfismo. Orden de llamada de los constructores.
o 6.e Arrays de objetos. Override.
o 6.f Llamar a métodos de la clase padre. Sobrecarga de operadores.
o 6.g Programas a partir de varios fuentes.
1. Toma de contacto con C#
C# es un lenguaje de programación de ordenadores. Se trata de un lenguaje
moderno, evolucionado a partir de C y C++, y con una sintaxis muy similar a la
de Java. Los programas creados con C# no suelen ser tan rápidos como los
creados con C, pero a cambio la productividad del programador es mucho
mayor.
Se trata de un lenguaje creado por Microsoft para crear programas para su
plataforma .NET, pero estandarizado posteriormente por ECMA y por ISO, y del
que existe una implementación alternativa de "código abierto", el "proyecto
Mono", que está disponible para Windows, Linux, Mac OS X y otros sistemas
operativos.
Nosotros comenzaremos por usar Mono como plataforma de desarrollo durante
los primeros temas. Cuando los conceptos básicos estén asentados, pasaremos
a emplear Visual C#, de Microsoft, que requiere un ordenador más potente pero
a cambio incluye un entorno de desarrollo muy avanzado, y está disponible
también en una versión gratuita (Visual Studio Express Edition).
Los pasos que seguiremos para crear un programa en C# serán:
1. Escribir el programa en lenguaje C# (fichero fuente), con cualquier editor de
textos.
2. Compilarlo con nuestro compilador. Esto creará un "fichero ejecutable".
3. Lanzar el fichero ejecutable.
La mayoría de los compiladores actuales permiten dar todos estos pasos desde
un único entorno, en el que escribimos nuestros programas, los compilamos, y
los depuramos en caso de que exista algún fallo.
En el siguiente apartado veremos un ejemplo de uno de estos entornos, dónde
localizarlo y cómo instalarlo.

1.1 Escribir un texto en C#


Vamos con un primer ejemplo de programa en C#, posiblemente el más sencillo
de los que "hacen algo útil". Se trata de escribir un texto en pantalla. La
apariencia de este programa la vimos en el tema anterior. Vamos a verlo ahora
con más detalle:
public class Ejemplo01
{
public static void Main()
{
System.Console.WriteLine("Hola");
}
}

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"); } }

 De hecho, hay dos formas especialmente frecuentes de colocar la llave de


comienzo, y yo usaré ambas indistintamente. Una es como hemos hecho en
el primer ejemplo: situar la llave de apertura en una línea, sola, y justo
encima de la llave de cierre correspondiente. Esto es lo que muchos autores
llaman el "estilo C". La segunda forma habitual es situándola a continuación
del nombre del bloque que comienza (el "estilo Java"), así:
public class Ejemplo01 {
public static void Main(){
System.Console.WriteLine("Hola");
}
}

(esta es la forma que yo emplearé preferentemente en este texto cuando


estemos trabajando con fuentes de mayor tamaño, para que ocupe un poco
menos de espacio).
 La gran mayoría de las órdenes que encontraremos en el lenguaje C# son
palabras en inglés o abreviaturas de éstas. Pero hay que tener en cuenta
que C# distingue entre mayúsculas y minúsculas, por lo que "WriteLine" es
una palabra reconocida, pero "writeLine", "WRITELINE" o "Writeline" no lo
son.
1.3. Mostrar números enteros en pantalla
Cuando queremos escribir un texto "tal cual", como en el ejemplo anterior, lo
encerramos entre comillas. Pero no siempre querremos escribir textos
prefijados. En muchos casos, se tratará de algo que habrá que calcular.
El ejemplo más sencillo es el de una operación matemática. La forma de
realizarla es sencilla: no usar comillas en WriteLine. Entonces, C# intentará
analizar el contenido para ver qué quiere decir. Por ejemplo, para sumar 3 y 4
bastaría hacer:
public class Ejemplo01suma
{
public static void Main()
{
System.Console.WriteLine(3+4);
}
}

Ejercicio propuesto: crea un programa que diga el resultado de sumar 118 y 56.

1.4. Operaciones aritméticas básicas


Está claro que el símbolo de la suma será un +, y podemos esperar cual será el
de la resta, pero alguna de las operaciones matemáticas habituales tiene
símbolos menos intuitivos. Veamos cuales son los más importantes:

Operador Operación

+ Suma

- Resta, negación

* Multiplicación

/ División

% Resto de la división ("módulo")

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

1.4.2. Introducción a los problemas de desbordamiento

El espacio del que disponemos para almacenar los números es limitado. Si el


resultado de una operación es un número "demasiado grande", obtendremos un
mensaje de error o un resultado erróneo. Por eso en los primeros ejemplos
usaremos números pequeños. Más adelante veremos a qué se debe realmente
este problema y cómo evitarlo. Como anticipo, el siguiente programa ni siquiera
compila, porque el compilador sabe que el resultado va a ser "demasiado
grande":
public class Ejemplo01multiplic
{
public static void Main()
{
System.Console.WriteLine(10000000*10000000);
}
}
1.5. Introducción a las variables: int
Las variables son algo que no contiene un valor predeterminado, un espacio de
memoria al que nosotros asignamos un nombre y en el que podremos
almacenar datos.
El primer ejemplo nos permitía escribir "Hola". El segundo nos permitía sumar
dos números que habíamos prefijado en nuestro programa. Pero esto tampoco
es "lo habitual", sino que esos números dependerán de valores que haya
tecleado el usuario o de cálculos anteriores.
Por eso necesitaremos usar variables, en las que guardemos los datos con los
que vamos a trabajar y también los resultados temporales. Vamos a ver como
ejemplo lo que haríamos para sumar dos números enteros que fijásemos en el
programa.

1.5.1. Definición de variables: números enteros

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".

1.5.2. Asignación de valores

Podemos darle un valor a esa variable durante el programa haciendo


primerNumero = 234;

O también podemos darles un valor inicial ("inicializarlas") antes de que empiece


el programa, en el mismo momento en que las definimos:
int primerNumero = 234;

O incluso podemos definir e inicializar más de una variable a la vez


int primerNumero = 234, segundoNumero = 567;
(esta línea reserva espacio para dos variables, que usaremos para almacenar
números enteros; una de ellas se llama primerNumero y tiene como valor inicial
234 y la otra se llama segundoNumero y tiene como valor inicial 567).
Después ya podemos hacer operaciones con las variables, igual que las
hacíamos con los números:
suma = primerNumero + segundoNumero;

1.5.3. Mostrar el valor de una variable en pantalla

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);

pero si se trata de una variable es idéntico:


System.Console.WriteLine(suma);

O bien, si queremos mostrar un texto además del valor de la variable, podemos


indicar el texto entre comillas, detallando con {0} en qué parte del texto
queremos que aparezca el valor de la variable, de la siguiente forma:
System.Console.WriteLine("La suma es {0}", suma);

Si se trata de más de una variable, indicaremos todas ellas tras el texto, y


detallaremos dónde debe aparecer cada una de ellas, usando {0}, {1} y así
sucesivamente:
System.Console.WriteLine("La suma de {0} y {1} es {2}",
primerNumero, segundoNumero, suma);

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;

System.Console.WriteLine("La suma de {0} y {1} es {2}",


primerNumero, segundoNumero, suma);
}
}

Repasemos lo que hace:


 (Nos saltamos todavía los detalles de qué quieren decir "public", "class",
"static" y "void").
 Main() indica donde comienza el cuerpo del programa, que se delimita entre
llaves { y }
 int primerNumero; reserva espacio para guardar un número entero, al que
llamaremos primerNumero.
 int segundoNumero; reserva espacio para guardar otro número entero, al
que llamaremos segundoNumero.
 int suma; reserva espacio para guardar un tercer número entero, al que
llamaremos suma.
 primerNumero = 234; da el valor del primer número que queremos sumar
 segundoNumero = 567; da el valor del segundo número que queremos
sumar
 suma = primerNumero + segundoNumero; halla la suma de esos dos
números y la guarda en otra variable, en vez de mostrarla directamente en
pantalla.
 System.Console.WriteLine("La suma de {0} y {1} es {2}", primerNumero,
segundoNumero, suma); muestra en pantalla el texto y los valores de las
tres variables (los dos números iniciales y su suma).
Ejercicio propuesto: Hacer un programa que calcule el producto de los números
121 y 132, usando variables.
1.6. Identificadores
Estos nombres de variable (lo que se conoce como "identificadores") pueden
estar formados por letras, números o el símbolo de subrayado (_) y deben
comenzar por letra o subrayado. No deben tener espacios entre medias, y hay
que recordar que las vocales acentuadas y la eñe son problemáticas, porque no
son letras "estándar" en todos los idiomas.
Por eso, no son nombres de variable válidos:
1numero (empieza por número)
un numero (contiene un espacio)
Año1 (tiene una eñe)
MásDatos (tiene una vocal acentuada)

Tampoco podremos usar como identificadores las palabras reservadas de C#.


Por ejemplo, la palabra "int" se refiere a que cierta variable guardará un número
entero, así que esa palabra "int" no la podremos usar tampoco como nombre de
variable (pero no vamos a incluir ahora una lista de palabras reservadas de C#,
ya nos iremos encontrando con ellas).
De momento, intentaremos usar nombres de variables que a nosotros nos
resulten claros, y que no parezca que puedan ser alguna orden de C#.
Hay que recordar que en C# las mayúsculas y minúsculas se consideran
diferentes, de modo que si intentamos hacer
PrimerNumero = 0;
primernumero = 0;

o cualquier variación similar, el compilador protestará y nos dirá que no conoce


esa variable, porque la habíamos declarado como
int primerNumero;

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 */

Es conveniente escribir comentarios que aclaren la misión de las partes de


nuestros programas que puedan resultar menos claras a simple vista. Incluso
suele ser aconsejable que el programa comience con un comentario, que nos
recuerde qué hace el programa sin que necesitemos mirarlo de arriba a abajo.
Un ejemplo casi exagerado:
/* ---- Ejemplo en C#: sumar dos números prefijados ---- */

public class Ejemplo02b


{
public static void Main()
{
int primerNumero = 234;
int segundoNumero = 567;
int suma; /* Guardaré el valor para usarlo más tarde */

/* Primero calculo la suma */


suma = primerNumero + segundoNumero;

/* Y después muestro su valor */


System.Console.WriteLine("La suma de {0} y {1} es {2}",
primerNumero, segundoNumero, suma);
}
}

Un comentario puede empezar en una línea y terminar en otra distinta, así:


/* Esto
es un comentario que
ocupa más de una línea
*/

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++"

1.8. Datos por el usuario: ReadLine


Si queremos que sea el usuario de nuestro programa quien teclee los valores,
necesitamos una nueva orden, que nos permita leer desde teclado. Pues bien, al
igual que tenemos System.Console.WriteLine ("escribir línea), también existe
System.Console.ReadLine ("leer línea"). Para leer textos, haríamos
texto = System.Console.ReadLine();

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;

System.Console.WriteLine("Introduce el primer número");


primerNumero = System.Convert.ToInt32(
System.Console.ReadLine());
System.Console.WriteLine("Introduce el segundo número");
segundoNumero = System.Convert.ToInt32(
System.Console.ReadLine());
suma = primerNumero + segundoNumero;

System.Console.WriteLine("La suma de {0} y {1} es {2}",


primerNumero, segundoNumero, suma);
}
}

Va siendo hora de hacer una pequeña mejora: no es necesario repetir "System."


al principio de la mayoría de las órdenes que tienen que ver con el sistema (por
ahora, las de consola y las de conversión), si al principio del programa utilizamos
"using System":
using System;

public class Ejemplo04


{
public static void Main()
{
int primerNumero;
int segundoNumero;
int suma;

Console.WriteLine("Introduce el primer número");


primerNumero = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Introduce el segundo número");
segundoNumero = Convert.ToInt32(Console.ReadLine());
suma = primerNumero + segundoNumero;

Console.WriteLine("La suma de {0} y {1} es {2}",


primerNumero, segundoNumero, 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

2. Tipos de datos básicos


2.1. Tipo de datos entero
Hemos hablado de números enteros, de cómo realizar operaciones sencillas y
de cómo usar variables para reservar espacio y poder trabajar con datos cuyo
valor no sabemos de antemano.
Empieza a ser el momento de refinar, de dar más detalles. El primer "matiz"
importante que nos hemos saltado es el tamaño de los números que podemos
emplear, así como su signo (positivo o negativo). Por ejemplo, un dato de tipo
"int" puede guardar números de hasta unas nueve cifras, tanto positivos como
negativos, y ocupa 4 bytes en memoria.
(Nota: si no sabes lo que es un byte, deberías mirar el Apéndice 1 de este texto).
Pero no es la única opción. Por ejemplo, si queremos guardar la edad de una
persona, no necesitamos usar números negativos, y nos bastaría con 3 cifras,
así que es de suponer que existirá algún tipo de datos más adecuado, que
desperdicie menos memoria. También existe el caso contrario: un banco puede
necesitar manejar números con más de 9 cifras, así que un dato "int" se les
quedaría corto. Siendo estrictos, si hablamos de valores monetarios,
necesitaríamos usar decimales, pero eso lo dejamos para el siguiente apartado.
2.1.1. Tipos de datos para números enteros

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:

Nombre Del Tamaño


Rango de valores
Tipo (bytes)
sbyte 1 -128 a 127

byte 1 0 a 255

short 2 -32768 a 32767

ushort 2 0 a 65535

int 4 -2147483648 a 2147483647

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

Si queremos obtener estos datos a partir de una cadena de texto, no siempre


nos servirá Convert.ToInt32, porque no todos los datos son enteros de 32 bits (4
bytes). Para datos de tipo "byte" usaríamos Convert.ToByte (sin signo) y
ToSByte (con signo), para datos de 2 bytes tenemos ToInt16 (con signo) y
ToUInt16 (sin signo), y para los de 8 bytes existen ToInt64 (con signo) y
ToUInt64 (sin signo).
Ejercicios propuestos:
 Preguntar al usuario su edad, que se guardará en un "byte". A continuación,
se deberá le deberá decir que no aparenta tantos años (por ejemplo, "No
aparentas 20 años").
 Pedir al usuario dos números de dos cifras ("byte"), calcular su
multiplicación, que se deberá guardar en un "ushort", y mostrar el resultado
en pantalla.
 Pedir al usuario dos números enteros largos ("long") y mostrar cuanto es su
suma, su resta y su producto.
2.1.3. Incremento y decremento

Conocemos la forma de realizar las operaciones aritméticas más habituales.


Peor también existe una operación que es muy frecuente cuando se crean
programas, y que no tiene un símbolo específico para representarla en
matemáticas: incrementar el valor de una variable en una unidad:
a = a + 1;

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;

y "a" valía 2, primero aumentamos "a" y luego los asignamos a "b"


(preincremento), de modo que a=3 y b=3.
Por supuesto, también podemos distinguir postdecremento (a--) y
predecremento (--a).
Ejercicios propuestos:
 Crear un programa que use tres variables x,y,z. Sus valores iniciales serán
15, -10, 2.147.483.647. Se deberá incrementar el valor de estas variables.
¿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; c=a++;
b=b*5; a=a*2;
Y ya que estamos hablando de las asignaciones, hay que comentar que en C#
es posible hacer asignaciones múltiples:
a = b = c = 1;
2.1.4. Operaciones abreviadas: +=

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;

2.2. Tipo de datos real


Cuando queremos almacenar datos con decimales, no nos sirve el tipo de datos
"int". Necesitamos otro tipo de datos que sí esté preparado para guardar
números "reales" (con decimales). En el mundo de la informática hay dos formas
de trabajar con números reales:
 Coma fija: el número máximo de cifras decimales está fijado de antemano, y
el número de cifras enteras también. Por ejemplo, con un formato de 3 cifras
enteras y 4 cifras decimales, el número 3,75 se almacenaría correctamente,
el número 970,4361 también, pero el 5,678642 se guardaría como 5,6786
(se perdería a partir de la cuarta cifra decimal) y el 1010 no se podría
guardar (tiene más de 3 cifras enteras).
 Coma flotante: el número de decimales y de cifras enteras permitido es
variable, lo que importa es el número de cifras significativas (a partir del
último 0). Por ejemplo, con 5 cifras significativas se podrían almacenar
números como el 13405000000 o como el 0,0000007349 pero no se
guardaría correctamente el 12,0000034, que se redondearía a un número
cercano.
2.2.1. Simple y doble precisión

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":

float double decimal

Tamaño en bits 32 64 128

Valor más pequeño -1,5•10-45 5,0•10-324 1,0•10-28

Valor más grande 3,4•1038 1,7•10308 7,9•1028

Cifras significativas 7 15-16 28-29

Para definirlos, se hace igual que en el caso de los números enteros:


float x;

o bien, si queremos dar un valor inicial en el momento de definirlos (recordando


que para las cifras decimales no debemos usar una coma, sino un punto):
float x = 12.56;

2.2.2. Mostrar en pantalla números reales

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;

public class Ejemplo05


{
public static void Main()
{
float primerNumero;
float segundoNumero;
float suma;

Console.WriteLine("Introduce el primer número");


primerNumero = Convert.ToSingle(Console.ReadLine());
Console.WriteLine("Introduce el segundo número");
segundoNumero = Convert.ToSingle(Console.ReadLine());
suma = primerNumero + segundoNumero;

Console.WriteLine("La suma de {0} y {1} es {2}",


primerNumero, segundoNumero, suma);
}
}

Cuidado al probar este programa: aunque en el fuente debemos escribir los


decimales usando un punto, como 123.456, al poner el ejecutable en marcha
parte del trabajo se le encarga al sistema operativo, de modo que si éste sabe
que en nuestro país se usa la coma para los decimales, considere la coma el
separador correcto y no el punto, como ocurre si introducimos estos datos en la
versión española de Windows XP:
ejemplo05

Introduce el primer número

23,6

Introduce el segundo número

34.2

La suma de 23,6 y 342 es 365,6

2.2.2. Formatear números

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;

public class Ejemplo06


{
public static void Main()
{
double numero = 12.34;

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("#.###") );
}
}

El resultado de este ejemplo sería:


ejemplo06

12,3

12,340

12,3

12,340

12,3

12,34

Como se puede ver, ocurre lo siguiente:


 Si indicamos menos decimales de los que tiene el número, se redondea.
 Si indicamos más decimales de los que tiene el número, se mostrarán ceros
si usamos como formato Nx o 0.000, y no se mostrará nada si usamos #.###
 Si indicamos menos cifras antes de la coma decimal de las que realmente
tiene el número, aun así se muestran todas ellas.
Ejercicios propuestos:
 El usuario de nuestro programa podrá teclear dos números de hasta 12
cifras significativas. El programa deberá mostrar el resultado de dividir el
primer número entre el segundo, utilizando tres cifras decimales.
 Crear un programa que use tres variables x,y,z. Las tres serán números
reales, y nos bastará con dos cifras decimales. Deberá pedir al usuario los
valores para las tres variables y mostrar en pantalla el valor de x2 + y - z
(con exactamente dos cifras decimales).
2.3. Tipo de datos carácter
También tenemos un tipo de datos que nos permite almacenar una única letra, el
tipo "char":
char letra;

Asignar valores es sencillo: el valor se indica entre comillas simples


letra = 'a';

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;

public class Ejemplo07


{
public static void Main()
{
char letra;

letra = 'a';
Console.WriteLine("La letra es {0}", letra);

Console.WriteLine("Introduce una nueva letra");


letra = Convert.ToChar(Console.ReadLine());
Console.WriteLine("Ahora la letra es {0}", letra);
}
}

2.3.1. Secuencias de escape: \n y otras.

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

\b Retroceso (permite borrar el último carácter)

\f Avance de página (expulsa una hoja en la impresora)

\n Avanza de línea (salta a la línea siguiente)

\r Retorno de carro (va al principio de la línea)

\t Salto de tabulación horizontal

\v Salto de tabulación vertical

\' Muestra una comilla simple

\" Muestra una comilla doble

\\ Muestra una barra invertida

\0 Carácter nulo (NULL)

Vamos a ver un ejemplo que use los más habituales:


using System;

public class Ejemplo08


{
public static void Main()
{
Console.WriteLine("Esta es una frase");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("y esta es otra, separada dos lineas");

Console.WriteLine("\n\nJuguemos mas:\n\notro salto");


Console.WriteLine("Comillas dobles: \" y simples \', y barra \\");
}
}

Su resultado sería este:


Esta es una frase
y esta es otra, separada dos lineas

Juguemos mas:

otro salto

Comillas dobles: " y simples ', y barra \

En algunas ocasiones puede ser incómodo manipular estas secuencias de


escape. Por ejemplo, cuando usemos estructuras de directorios:
c:\\datos\\ejemplos\\curso\\ejemplo1. En este caso, se puede usar una arroba
(@) antes del texto sin las barras invertidas:
ruta = @"c:\datos\ejemplos\curso\ejemplo1"

En este caso, el problema está si aparecen comillas en medio de la cadena.


Para solucionarlo, se duplican las comillas, así:
orden = @"copy ""documento de ejemplo"" f:"

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".

2.4. Toma de contacto con las cadenas de texto


Las cadenas de texto son tan fáciles de manejar como los demás tipos de datos
que hemos visto, con apenas tres diferencias:
 Se declaran con "string".
 Si queremos dar un valor inicial, éste se indica entre comillas dobles.
 Cuando leemos con ReadLine, no hace falta convertir el valor obtenido.
Así, un ejemplo que diera un valor a un "string", lo mostrara (entre comillas, para
practicar las secuencias de escape que hemos visto en el apartado anterior) y
leyera un valor tecleado por el usuario podría ser:
using System;

public class Ejemplo09


{
public static void Main()
{
string frase;

frase = "Hola, como estas?";


Console.WriteLine("La frase es \"{0}\"", frase);

Console.WriteLine("Introduce una nueva frase");


frase = Console.ReadLine();
Console.WriteLine("Ahora la frase es \"{0}\"", frase);
}
}

Se pueden hacer muchas más operaciones sobre cadenas de texto: convertir a


mayúsculas o a minúsculas, eliminar espacios, cambiar una subcadena por otra,
dividir en trozos, etc. Pero ya volveremos a las cadenas más adelante, cuando
conozcamos las estructuras de control básicas.

2.5. Los valores "booleanos"


En C# tenemos también un tipo de datos llamado "booleano" ("bool"), que puede
tomar dos valores: verdadero o falso:
bool encontrado;
encontrado = true;

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

Vamos a ver cómo podemos comprobar si se cumplen condiciones. La primera


construcción que usaremos será "si ... entonces ...". El formato es
if (condición) sentencia;

Vamos a verlo con un ejemplo:

using System;

public class Ejemplo10


{
public static void Main()
{
int numero;

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

Habíamos dicho que el formato básico de "if" es if (condición) sentencia; Esa


"sentencia" que se ejecuta si se cumple la condición puede ser una sentencia
simple o una compuesta. Las sentencias compuestas se forman agrupando
varias sentencias simples entre llaves ( { y } ), como en este ejemplo:

using System;

public class Ejemplo11


{
public static void Main()
{
int numero;

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" */

En este caso, si el número es negativo, se hacen dos cosas: escribir un texto y


luego... ¡escribir otro! (Claramente, en este ejemplo, esos dos "WriteLine"
podrían ser uno solo, en el que los dos textos estuvieran separados por un "\n";
más adelante iremos encontrando casos en lo que necesitemos hacer cosas
"más serias" dentro de una sentencia compuesta).
Como se ve en este ejemplo, cada nuevo "bloque" se suele escribir un poco más
a la derecha que los anteriores, para que sea fácil ver dónde comienza y termina
cada sección de un programa. Por ejemplo, el contenido de "Ejemplo11" está un
poco más a la derecha que la cabecera "public class Ejemplo11", y el contenido
de "Main" algo más a la derecha, y la sentencia compuesta que se debe realizar
si se cumple la condición del "if" está algo más a la derecha que la orden "if".
Este "sangrado" del texto se suele llamar "escritura indentada". Un tamaño
habitual para el sangrado es de 4 espacios, aunque en este texto habitualmente
usaremos sólo dos espacios, para no llegar al margen derecho del papel con
mucha facilidad.
3.1.3. Operadores relacionales: <, <=, >, >=, ==, !=

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

< Menor que

> Mayor que

<= Menor o igual que

>= Mayor o igual que

== Igual a

!= No igual a (distinto de)

Y un ejemplo, que diga si un número NO ES cero:

using System;

public class Ejemplo12


{
public static void Main()
{
int numero;

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

Podemos indicar lo que queremos que ocurra en caso de que no se cumpla la


condición, usando la orden "else" (en caso contrario), así:

using System;

public class Ejemplo13


{
public static void Main()
{
int numero;

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.");
}
}

Podríamos intentar evitar el uso de "else" si utilizamos un "if" a continuación de


otro, así:

using System;

public class Ejemplo14


{
public static void Main()
{
int numero;

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.");
}
}

Pero el comportamiento no es el mismo: en el primer caso (ejemplo 13) se mira


si el valor es positivo; si no lo es, se pasa a la segunda orden, pero si lo es, el
programa ya ha terminado. En el segundo caso (ejemplo 14), aunque el número
sea positivo, se vuelve a realizar la segunda comprobación para ver si es
negativo o cero, por lo que el programa es algo más lento.
Podemos enlazar los "if" usando "else", para decir "si no se cumple esta
condición, mira a ver si se cumple esta otra":

using System;

public class Ejemplo15


{
public static void Main()
{
int numero;

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".

3.1.5. Operadores lógicos: &&, ||, !

Estas condiciones se puede encadenar con "y", "o", etc., que se indican de la
siguiente forma

Operador Significado
&& Y
|| O
! No

De modo que podremos escribir cosas como


if ((opcion==1) && (usuario==2)) ...
if ((opcion==1) || (opcion==3)) ...
if ((!(opcion==opcCorrecta)) || (tecla==ESC)) ...

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;

public class Ejemplo16


{
public static void Main()
{
int numero;

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"

En muchas ocasiones, cuando las condiciones que aparecen dentro de un "if"


sean complejas, el programa resultará más legible si empleamos variables de
tipo "bool" (verdadero o falso), de forma que ciertos fragmentos de nuestro
programa no sean "if ((vidas==0) || (tiempo == 0) || ((enemigos ==0) && (nivel ==
ultimoNivel)))" sino simplemente "if (partidaTerminada) ..."
A las variables "bool" se les da un valor casi como a cualquier otra variable, con
la diferencia de que en la parte derecha de la asignación aparece "true" , "false"
o una comparación, así:
partidaTerminada = false;
partidaTerminada = (enemigos ==0) && (nivel == ultimoNivel);
if (vidas ==0) partidaTerminada = true;

Lo emplearemos a partir de ahora en los fuentes que usen condiciones un poco


complejas. Un ejemplo que pida una letra y diga si es una vocal, una cifra
numérica u otro símbolo, usando variables "bool" podría ser:

using System;

public class Ejemplo17


{
public static void Main()
{
char letra;
bool esVocal, esCifra;

Console.WriteLine("Introduce una letra");


letra = Convert.ToChar(Console.ReadLine());

esCifra = (letra >= '0') && (letra <='9');


esVocal = (letra == 'a') || (letra == 'e') || (letra == 'i') ||
(letra == 'o') || (letra == 'u');

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.

El inicio o el final del programa se indica dentro de un círculo. Los procesos


internos, como realizar operaciones, se encuadran en un rectángulo. Las
entradas y salidas (escrituras en pantalla y lecturas de teclado) se indican con
un paralelogramo que tenga su lados superior e inferior horizontales, pero no
tenga verticales los otros dos. Las decisiones se indican dentro de un rombo.
Vamos a aplicarlo al ejemplo de un programa que pida un número al usuario y
diga si es positivo, negativo o cero:
El paso de aquí al correspondiente programa en lenguaje C# (el que vimos en el
ejemplo 16) debe ser casi inmediato: sabemos como leer de teclado, como
escribir en pantalla, y las decisiones serán un "if", que si se cumple ejecutará la
sentencia que aparece en su salida "si" y si no se cumple ("else") ejecutará lo
que aparezca en su salida "no".
Ejercicios propuestos:
 Crear el diagrama de flujo y la versión en C# de un programa que dé al
usuario tres oportunidades para adivinar un número del 1 al 10.
 Crear el diagrama de flujo para el programa que pide al usuario dos
números y dice si uno de ellos es positivo, si lo son los dos o si no lo es
ninguno.

3.1.9. Operador condicional: ?

En C# hay otra forma de asignar un valor según se dé una condición o no. Es el


"operador condicional" ? : que se usa
nombreVariable = condicion ? valor1 : valor2;

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;

que, aplicado a un programa sencillo, podría ser

using System;

public class Ejemplo18


{
public static void Main()
{
int a, b, mayor;

Console.Write("Escriba un número: ");


a = Convert.ToInt32(Console.ReadLine());
Console.Write("Escriba otro: ");
b = Convert.ToInt32(Console.ReadLine());

mayor = (a>b) ? a : b;

Console.WriteLine("El mayor de los números es {0}.", mayor);


}
}
(La orden Console.Write, empleada en el ejemplo anterior, escribe un texto sin
avanzar a la línea siguiente, de modo que el próximo texto que escribamos –o
introduzcamos- quedará a continuación de éste).
Un segundo ejemplo, que sume o reste dos números según la opción que se
escoja, sería:

using System;

public class Ejemplo19


{
public static void Main()
{
int a, b, operacion, resultado;

Console.Write("Escriba un número: ");


a = Convert.ToInt32(Console.ReadLine());
Console.Write("Escriba otro: ");
b = Convert.ToInt32(Console.ReadLine());
Console.Write("Escriba una operación (1 = resta; otro = suma): ");
operacion = Convert.ToInt32(Console.ReadLine());

resultado = (operacion == 1) ? a-b : a+b;

Console.WriteLine("El resultado es {0}.\n", resultado);


}
}

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;
};

Es decir, se escribe tras "switch" la expresión a analizar, entre paréntesis.


Después, tras varias órdenes "case" se indica cada uno de los valores posibles.
Los pasos (porque pueden ser varios) que se deben dar si se trata de ese valor
se indican a continuación, terminando con "break". Si hay que hacer algo en
caso de que no se cumpla ninguna de las condiciones, se detalla tras "default".
Si dos casos tienen que hacer lo mismo, se añade "goto case" a uno de ellos
para indicarlo.
Vamos con un ejemplo, que diga si la tecla que pulsa el usuario es una cifra
numérica, un espacio u otro símbolo:

using System;

public class Ejemplo20


{
public static void Main()
{
char letra;

Console.WriteLine("Introduce una letra");


letra = Convert.ToChar(Console.ReadLine());

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");
}

En el lenguaje C, que es más antiguo, sólo se podía usar "switch" para


comprobar valores de variables "simples" (numéricas y caracteres); en C#, que
es un lenguaje más evolucionado, se puede usar también para comprobar
valores de "strings". Por ejemplo, un programa que nos salude de forma
personalizada si somos "Juan" o "Pedro" podría ser:

using System;

public class Ejemplo21


{
public static void Main()
{
string nombre;

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.

3.2. Estructuras repetitivas


Hemos visto cómo comprobar condiciones, pero no cómo hacer que una cierta
parte de un programa se repita un cierto número de veces o mientras se cumpla
una condición (lo que llamaremos un "bucle"). En C# tenemos varias formas de
conseguirlo.
3.2.1. while

Si queremos hacer que una sección de nuestro programa se repita mientras se


cumpla una cierta condición, usaremos la orden "while". Esta orden tiene dos
formatos distintos, según comprobemos la condición al principio o al final.
En el primer caso, su sintaxis es
while (condición)
sentencia;

Es decir, la sentencia se repetirá mientras la condición sea cierta. Si la condición


es falsa ya desde un principio, la sentencia no se ejecuta nunca. Si queremos
que se repita más de una sentencia, basta agruparlas entre { y }.
Un ejemplo que nos diga si cada número que tecleemos es positivo o negativo, y
que pare cuando tecleemos el número 0, podría ser:
using System;

public class Ejemplo22


{
public static void Main()
{
int numero;

Console.Write("Teclea un número (0 para salir): ");


numero = Convert.ToInt32(Console.ReadLine());
while (numero != 0)
{
if (numero > 0) Console.WriteLine("Es positivo");
else Console.WriteLine("Es negativo");
Console.WriteLine("Teclea otro número (0 para salir): ");
numero = Convert.ToInt32(Console.ReadLine());
}
}
}

En este ejemplo, si se introduce 0 la primera vez, la condición es falsa y ni


siquiera se entra al bloque del "while", terminando el programa inmediatamente.
Ejercicios propuestos:
 Crear un programa que pida al usuario su contraseña. Deberá terminar
cuando introduzca como contraseña la palabra "clave", pero volvérsela a
pedir tantas veces como sea necesario.
 Crea un programa que escriba en pantalla los números del 1 al 10, usando
"while".
 Crea un programa que escriba en pantalla los números pares del 26 al 10
(descendiendo), usando "while".
 Crear un programa calcule cuantas cifras tiene un número entero positivo
(pista: se puede hacer dividiendo varias veces entre 10).
3.2.2. do ... while

Este es el otro formato que puede tener la orden "while": la condición se


comprueba al final. El punto en que comienza a repetirse se indica con la orden
"do", así:
do
sentencia;
while (condición);

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;

public class Ejemplo23


{
public static void Main()
{

int valida = 711;


int clave;

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");

}
}

En este caso, se comprueba la condición al final, de modo que se nos


preguntará la clave al menos una vez. Mientras que la respuesta que demos no
sea la correcta, se nos vuelve a preguntar. Finalmente, cuando tecleamos la
clave correcta, el ordenador escribe "Aceptada" y termina el programa.
Si preferimos que la clave sea un texto en vez de un número, los cambios al
programa son mínimos:
using System;

public class Ejemplo24


{
public static void Main()
{

string valida = "secreto";


string clave;

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;

public class Ejemplo25


{
public static void Main()
{

int contador;

for (contador=1; contador<=10; contador++)


Console.Write("{0} ", contador);

}
}

Recordemos que "contador++" es una forma abreviada de escribir


"contador=contador+1", de modo que en este ejemplo aumentamos la variable
de uno en uno.
Ejercicios propuestos:
 Crear un programa que muestre los números del 15 al 5, descendiendo
(pista: en cada pasada habrá que descontar 1, por ejemplo haciendo i--).
 Crear un programa que muestre los primeros ocho números pares (pista: en
cada pasada habrá que aumentar de 2 en 2, o bien mostrar el doble del
valor que hace de contador).
 Crear un programa que muestre las letras minúsculas de la "b" a la "w".
En un "for", realmente, la parte que hemos llamado "Incremento" no tiene por
qué incrementar la variable, aunque ése es su uso más habitual. Es
simplemente una orden que se ejecuta cuando se termine la "Sentencia" y antes
de volver a comprobar si todavía se cumple la condición de repetición.
Por eso, si escribimos la siguiente línea:
for (contador=1; contador<=10; )

la variable "contador" no se incrementa nunca, por lo que nunca se cumplirá la


condición de salida: nos quedamos encerrados dando vueltas dentro de la orden
que siga al "for".
Un caso todavía más exagerado de algo a lo que se entra y de lo que no se sale
sería la siguiente orden:
for ( ; ; )

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;

public class Ejemplo26


{
public static void Main()
{

int tabla, numero;

for (tabla=1; tabla<=5; tabla++)


for (numero=1; numero<=10; numero++)
Console.WriteLine("{0} por {1} es {2}", tabla, numero,
tabla*numero);

}
}

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;

public class Ejemplo27


{
public static void Main()
{

int tabla, numero;

for (tabla=1; tabla<=5; tabla++)


{
for (numero=1; numero<=10; numero++)
Console.WriteLine("{0} por {1} es {2}", tabla, numero,
tabla*numero);
Console.WriteLine();
}

}
}

Para "contar" no necesariamente hay que usar números. Por ejemplo, podemos
contar con letras así:

using System;

public class Ejemplo28


{
public static void Main()
{

char letra;

for (letra='a'; letra<='z'; letra++)


Console.Write("{0} ", letra);

}
}

En este caso, empezamos en la "a" y terminamos en la "z", aumentando de uno


en uno.
Si queremos contar de forma decreciente, o de dos en dos, o como nos interese,
basta indicarlo en la condición de finalización del "for" y en la parte que lo
incrementa:

using System;

public class Ejemplo29


{
public static void Main()
{

char letra;

for (letra='z'; letra>='a'; letra--)


Console.Write("{0} ", 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).

3.3. Sentencia break: termina el bucle


Podemos salir de un bucle "for" antes de tiempo con la orden "break":
using System;

public class Ejemplo30


{
public static void Main()
{

int contador;

for (contador=1; contador<=10; contador++)


{
if (contador==5) break;
Console.Write("{0} ", contador);
}

}
}

El resultado de este programa es:

1 2 3 4

(en cuanto se llega al valor 5, se interrumpe el "for", por lo que no se alcanza el


valor 10).
3.4. Sentencia continue: fuerza la siguiente iteración
Podemos saltar alguna repetición de un bucle con la orden "continue":

using System;

public class Ejemplo31


{
public static void Main()
{

int contador;

for (contador=1; contador<=10; contador++)


{
if (contador==5) continue;
Console.Write("{0} ", contador);
}

}
}

El resultado de este programa es:

1 2 3 4 6 7 8 9 10

En él podemos observar que no aparece el valor 5.


Ejercicios resueltos:
* ¿Qué escribiría en pantalla este fragmento de código?
for (i=1; i<4; i++) Console.Write("{0} ",i);

Respuesta: los números del 1 al 3 (se empieza en 1 y se repite mientras sea


menor que 4).
* ¿Qué escribiría en pantalla este fragmento de código?
for (i=1; i>4; i++) Console.Write("{0} ",i);

Respuesta: no escribiría nada, porque la condición es falsa desde el principio.


* ¿Qué escribiría en pantalla este fragmento de código?
for (i=1; i<=4; i++); Console.Write("{0} ",i);

Respuesta: escribe un 5, porque hay un punto y coma después del "for", de


modo que repite cuatro veces una orden vacía, y cuando termina, "i" ya tiene el
valor 5.
* ¿Qué escribiría en pantalla este fragmento de código?
for (i=1; i<4; ) Console.Write("{0} ",i);

Respuesta: escribe "1" continuamente, porque no aumentamos el valor de "i",


luego nunca se llegará a cumplir la condición de salida.
* ¿Qué escribiría en pantalla este fragmento de código?
for (i=1; ; i++) Console.Write("{0} ",i);

Respuesta: escribe números crecientes continuamente, comenzando en uno y


aumentando una unidad en cada pasada, pero sin terminar.
* ¿Qué escribiría en pantalla este fragmento de código?
for ( i= 0 ; i<= 4 ; i++) {
if ( i == 2 ) continue ;
Console.Write("{0} ",i);
}

Respuesta: escribe los números del 0 al 4, excepto el 2.


* ¿Qué escribiría en pantalla este fragmento de código?
for ( i= 0 ; i<= 4 ; i++) {
if ( i == 2 ) break ;
Console.Write("{0} ",i);
}

Respuesta: escribe los números 0 y 1 (interrumpe en el 2).


* ¿Qué escribiría en pantalla este fragmento de código?
for ( i= 0 ; i<= 4 ; i++) {
if ( i == 10 ) continue ;
Console.Write("{0} ",i);
}

Respuesta: escribe los números del 0 al 4, porque la condición del "continue"


nunca se llega a dar.
* ¿Qué escribiría en pantalla este fragmento de código?
for ( i= 0 ; i<= 4 ; i++)
if ( i == 2 ) continue ;
Console.Write("{0} ",i);

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;

y la posición de salto se indica con su nombre seguido de dos puntos (:)

donde:

como en el siguiente ejemplo:

using System;

public class Ejemplo32


{
public static void Main()
{

int i, j;

for (i=0; i<=5; i++)


for (j=0; j<=20; j+=2)
{
if ((i==1) && (j>=7)) goto salida;
Console.WriteLine("i vale {0} y j vale {1}.", i, j);
}
salida:
Console.Write("Fin del programa");

}
}

El resultado de este programa es:

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

Vemos que cuando i=1 y j>=7, se sale de los dos "for".

3.6. Más sobre diagramas de flujo. Diagramas de Chapin.


Cuando comenzamos el tema, vimos cómo ayudarnos de los diagramas de flujo
para plantear lo que un programa debe hacer. Si entendemos esta herramienta,
el paso a C (o a casi cualquier otro lenguaje de programación es sencillo). Pero
este tipo de diagramas es antiguo, no tiene en cuenta todas las posibilidades del
lenguaje C (y de muchos otros lenguajes actuales). Por ejemplo, no existe una
forma clara de representar una orden "switch", que equivaldría a varias
condiciones encadenadas.
Por su parte, un bucle "while" se vería como una condición que hace que algo se
repita (una flecha que vuelve hacia atrás, al punto en el que se comprobaba la
condición):
Y un "do..while" como una condición al final de un bloque que se repite:
Aun así, existen otras notaciones más modernas y que pueden resultar más
cómodas. Sólo comentaremos una: los diagramas de Chapin. En ellos se
representa cada orden dentro de una caja:

Las condiciones se indican diviendo las cajas en dos:


Y las condiciones repetitivas se indican dejando una barra a la izquierda, que
marca qué es lo que se repite, tanto si la condición se comprueba al final
(do..while):

como si se comprueba al principio (while):

En ambos casos, no existe una gráfica "clara" para los "for".


3.7. El caso de "foreach"
Nos queda por ver otra orden que permite hacer cosas repetitivas: "foreach" (se
traduciría "para cada"). La veremos más adelante, cuando manejemos
estructuras de datos más complejas, que es en las que la nos resultará útil.

3.8. Recomendación de uso de los distintos tipos de bucle


 En general, nos interesará usar "while" cuando puede que la parte repetitiva
no se llegue a repetir nunca (por ejemplo: cuando leemos un fichero, si el
fichero está vacío, no habrá datos que leer).
 De igual modo, "do...while" será lo adecuado cuando debamos repetir al
menos una vez (por ejemplo, para pedir una clave de acceso, se le debe
preguntar al menos una vez al usuario, o quizá más veces, si la teclea
correctamente).
 En cuanto a "for", es equivalente a un "while", pero la sintaxis habitual de la
oren "for" hace que sea especialmente útil cuando sabemos exactamente
cuantas veces queremos que se repita (por ejemplo: 10 veces sería "for (i=1;
i<=10; i++)").

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];

Podemos acceder a cada uno de los valores individuales indicando su nombre


(ejemplo) y el número de elemento que nos interesa, pero con una precaución:
se empieza a numerar desde 0, así que en el caso anterior tendríamos 4
elementos, que serían ejemplo[0], ejemplo[1], ejemplo[2], ejemplo[3].
Como ejemplo, vamos a definir un grupo de 5 números enteros y hallar su suma:

using System;

public class Ejemplo33


{
public static void Main()
{

int[] numero = new int[5]; /* Un array de 5 números enteros */


int suma; /* Un entero que será la suma */

numero[0] = 200; /* Les damos valores */


numero[1] = 150;
numero[2] = 100;
numero[3] = -50;
numero[4] = 300;
suma = numero[0] + /* Y hallamos la suma */
numero[1] + numero[2] + numero[3] + numero[4];
Console.WriteLine("Su suma es {0}", suma);
/* Nota: esta es la forma más ineficiente e incómoda */
/* Ya lo iremos mejorando */
}
}
Ejercicios propuestos:
 Un programa que pida al usuario 4 números, los memorice (utilizando una
tabla), calcule su media aritmética y la muestre en pantalla.
 Un programa que pida al usuario 5 números reales y luego los muestre en el
orden contrario al que se introdujeron.
 Un programa que pida al usuario 10 números enteros y calcule (y muestre)
cuál es el mayor de ellos.
4.1.2. Valor inicial de un array

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;

public class Ejemplo34


{
public static void Main()
{

int[] numero = /* Un array de 5 números enteros */


{200, 150, 100, -50, 300};
int suma; /* Un entero que será la suma */

suma = numero[0] + /* Y hallamos la suma */


numero[1] + numero[2] + numero[3] + numero[4];
Console.WriteLine("Su suma es {0}", suma);
/* Nota: esta forma es algo menos engorrosa, pero todavía no */
/* está bien hecho. Lo seguiremos mejorando */
}
}

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];

El fuente completo podría ser así:

using System;

public class Ejemplo35


{
public static void Main()
{

int[] numero = /* Un array de 5 números enteros */


{200, 150, 100, -50, 300};
int suma; /* Un entero que será la suma */
int i; /* Para recorrer los elementos */

suma = 0; /* Valor inicial de la suma */


for (i=0; i<=4; i++) /* Y hallamos la suma repetitiva */
suma += numero[i];

Console.WriteLine("Su suma es {0}", suma);


}
}

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.

4.1.4. Datos repetitivos introducidos por el usuario

Si queremos que sea el usuario el que introduzca datos a un array, usaríamos


otra estructura repetitiva ("for", por ejemplo) para pedírselos:
using System;

public class Ejemplo36


{
public static void Main()
{

int[] numero = new int[5]; /* Un array de 5 números enteros */


int suma; /* Un entero que será la suma */
int i; /* Para recorrer los elementos */

for (i=0; i<=4; i++) /* Pedimos los datos */


{
Console.Write("Introduce el dato numero {0}: ", i+1);
numero[i] = Convert.ToInt32(Console.ReadLine());
}

suma = 0; /* Valor inicial de la suma */


for (i=0; i<=4; i++) /* Y hallamos la suma repetitiva */
suma += numero[i];

Console.WriteLine("Su suma es {0}", suma);


}
}

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;

public class Ejemplo37


{
public static void Main()
{
int[,] notas1 = new int[2,2]; // 2 bloques de 2 datos
notas1[0,0] = 1;
notas1[0,1] = 2;
notas1[1,0] = 3;
notas1[1,1] = 4;

int[,] notas2 = // 2 bloques de 10 datos


{
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
};

Console.WriteLine("La nota1 del segundo alumno del grupo 1 es {0}",


notas1[0,1]);
Console.WriteLine("La nota2 del tercer alumno del grupo 1 es {0}",
notas2[0,2]);
}
}

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;

public class Ejemplo38


{
public static void Main()
{

int[][] notas; // Array de dos dimensiones


notas = new int[3][]; // Seran 3 bloques de datos
notas[0] = new int[10]; // 10 notas en un grupo
notas[1] = new int[15]; // 15 notas en otro grupo
notas[2] = new int[12]; // 12 notas en el ultimo

// Damos valores de ejemplo


for (int i=0;i<notas.Length;i++)
{
for (int j=0;j<notas[i].Length;j++)
{
notas[i][j] = i + j;
}
}

// Y mostramos esos valores


for (int i=0;i<notas.Length;i++)
{
for (int j=0;j<notas[i].Length;j++)
{
Console.Write(" {0}", notas[i][j]);
}
Console.WriteLine();
}

}
}

La salida de este programa sería


0 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2 3 4 5 6 7 8 9 10 11 12 13
Ejercicios propuestos:
 Un programa que al usuario dos bloques de 10 números enteros (usando un
array de dos dimensiones). Después deberá mostrar el mayor dato que se
ha introducido en cada uno de ellos.
 Un programa que al usuario dos bloques de 6 cadenas de texto. Después
pedirá al usuario una nueva cadena y comprobará si aparece en alguno de
los dos bloques de información anteriores.

4.3. Estructuras o registros


4.3.1. Definición y acceso a los datos

Un registro es una agrupación de datos, los cuales no necesariamente son del


mismo tipo. Se definen con la palabra "struct". La serie de datos que van a
formar
En C# (al contrario que en C), primero deberemos declarar cual va a ser la
estructura de nuestro registro, lo que no se puede hacer dentro de "Main". Más
adelante, ya dentro de "Main", podremos declarar variables de ese nuevo tipo.
Los datos que forman un "struct" pueden ser públicos o privados. Por ahora, a
nosotros nos interesará que sean accesibles desde el resto de nuestro
programa, por lo que les añadiremos delante la palabra "public" para indicar que
queremos que sean públicos.
Ya desde el cuerpo del programa, para acceder a cada uno de los datos que
forman el registro, tanto si queremos leer su valor como si queremos cambiarlo,
se debe indicar el nombre de la variable y el del dato (o campo) separados por
un punto:

using System;

public class Ejemplo39


{

struct tipoPersona
{
public string nombre;
public char inicial;
public int edad;
public float nota;
}

public static void Main()


{
tipoPersona persona;
persona.nombre = "Juan";
persona.inicial = 'J';
persona.edad = 20;
persona.nota = 7.5f;
Console.WriteLine("La edad de {0} es {1}",
persona.nombre, persona.edad);
}
}

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

Hemos guardado varios datos de una persona. Se pueden almacenar los de


varias personas si combinamos el uso de los "struct" con las tablas (arrays) que
vimos anteriormente. Por ejemplo, si queremos guardar los datos de 100
personas podríamos hacer:

using System;

public class Ejemplo40


{

struct tipoPersona
{
public string nombre;
public char inicial;
public int edad;
public float nota;
}

public static void Main()


{
tipoPersona[] persona = new tipoPersona[100];

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);
}
}

La inicial de la primera persona sería "persona[0].inicial", y la edad del último


sería "persona[99].edad".
Al probar este programa obtenemos
La edad de Juan es 20
La edad de Pedro es 0

Porque cuando reservamos espacio para los elementos de un "array" usando


"new", sus valores se dejan "vacíos" (0 para los números, cadenas vacías para
las cadenas de texto).
Ejercicio propuesto: Un programa que permita guardar datos de "imágenes"
(ficheros de ordenador que contengan fotografías o cualquier otro tipo de
información gráfica). De cada imagen se debe guardar: nombre (texto), ancho en
píxeles (por ejemplo 2000), alto en píxeles (por ejemplo, 3000), tamaño en Kb
(por ejemplo 145,6). El programa debe ser capaz de almacenar hasta 700
imágenes (deberá avisar cuando su capacidad esté llena). Debe permitir las
opciones: añadir una ficha nueva, ver todas las fichas (número y nombre de
cada imagen), buscar la ficha que tenga un cierto nombre.
4.3.3. Estructuras anidadas

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;

public class Ejemplo41


{

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;
}

public static void Main()


{
tipoPersona persona;

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);
}
}

4.4. Cadenas de caracteres


4.4.1. Definición. Lectura desde teclado

Hemos visto cómo leer cadenas de caracteres (Console.ReadLine) y cómo


mostrarlas en pantalla (Console.Write), así como la forma de darles un valor(=).
También podemos comparar cual es su valor, usando ==, o formar una cadena a
partir de otras si las unimos con el símbolo de la suma (+):
Así, un ejemplo que nos pidiese nuestro nombre y nos saludase usando todas
estas posibilidades podría ser:

using System;

public class Ejemplo42


{

public static void Main()


{

string saludo = "Hola";


string segundoSaludo;
string nombre, despedida;

segundoSaludo = "Que tal?";


Console.WriteLine("Dime tu nombre... ");
nombre = Console.ReadLine();
Console.WriteLine("{0} {1}", saludo, nombre);
Console.WriteLine(segundoSaludo);

if (nombre == "Alberto")
Console.WriteLine("Dices que eres Alberto?");
else
Console.WriteLine("Así que no eres Alberto?");

despedida = "Adios " + nombre + "!";


Console.WriteLine(despedida);
}
}

4.4.2. Cómo acceder a las letras que forman una cadena

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);

Podemos omitir el segundo número, y entonces se extraerá desde la posición


indicada hasta el final de la cadena.
4.4.5. Buscar en una cadena

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");

Podemos añadir un segundo parámetro opcional, que es la posición a partir de


la que queremos buscar:
if (nombre.IndexOf("Juan", 5) >= 0) ...

La búsqueda termina al final de la cadena, salvo que indiquemos que termine


antes con un tercer parámetro opcional:
if (nombre.IndexOf("Juan", 5, 15) >= 0) ...

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

Ya hemos comentado que las cadenas en C# son inmutables, no se pueden


modificar. Pero sí podemos realizar ciertas operaciones sobre ellas para obtener
una nueva cadena. Por ejemplo:
 ToUpper() convierte a mayúsculas: nombreCorrecto = nombre.ToUpper();
 ToLower() convierte a minúsculas: password2 = password.ToLower();
 Insert(int posición, string subcadena): Insertar una subcadena en una cierta
posición de la cadena inicial: nombreFormal = nombre.Insert(0,"Don");
 Remove(int posición, int cantidad): Elimina una cantidad de caracteres en
cierta posición: apellidos = nombreCompleto.Remove(0,6);
 Replace(string textoASustituir, string cadenaSustituta): Sustituye una cadena
(todas las veces que aparezca) por otra: nombreCorregido =
nombre.Replace("Pepe", "Jose");
Un programa que probara todas estas posibilidades podría ser así:

using System;

public class Ejemplo43


{

public static void Main()


{

string ejemplo = "Hola, que tal estas";


Console.WriteLine("El texto es: {0}",
ejemplo);

Console.WriteLine("La primera letra es: {0}",


ejemplo[0]);

Console.WriteLine("Las tres primeras letras son: {0}",


ejemplo.Substring(0,3));

Console.WriteLine("La longitud del texto es: {0}",


ejemplo.Length);

Console.WriteLine("La posicion de \"que\" es: {0}",


ejemplo.IndexOf("que"));

Console.WriteLine("La ultima A esta en la posicion: {0}",


ejemplo.LastIndexOf("a"));

Console.WriteLine("En mayúsculas: {0}",


ejemplo.ToUpper());

Console.WriteLine("En minúsculas: {0}",


ejemplo.ToLower());

Console.WriteLine("Si insertamos \", tio\": {0}",


ejemplo.Insert(4,", tio"));

Console.WriteLine("Si borramos las 6 primeras letras: {0}",


ejemplo.Remove(0, 6));

Console.WriteLine("Si cambiamos ESTAS por ESTAMOS: {0}",


ejemplo.Replace("estas", "estamos"));

}
}

Y su resultado sería

El texto es: Hola, que tal estas


La primera letra es: H
Las tres primeras letras son: Hol
La longitud del texto es: 19
La posicion de "que" es: 6
La ultima A esta en la posicion: 17
En mayúsculas: HOLA, QUE TAL ESTAS
En minúsculas: hola, que tal estas
Si insertamos ", tio": Hola, tio, que tal estas
Si borramos las 6 primeras letras: que tal estas
Si cambiamos ESTAS por ESTAMOS: Hola, que tal estamos

Otra posibilidad interesante, aunque un poco más avanzada, es la de


descomponer una cadena en trozos, que estén separados por una serie de
delimitadores (por ejemplo, espacios o comas). Para ello se puede usar Split,
que crea un array a partir de los fragmentos de la cadena, así:

using System;

public class Ejemplo43b


{

public static void Main()


{

string ejemplo = "uno,dos.tres,cuatro";


char [] delimitadores = {',', '.'};
int i;

string [] ejemploPartido = ejemplo.Split(delimitadores);

for (i=0; i<ejemploPartido.Length; i++)


Console.WriteLine("Fragmento {0}= {1}",
i, ejemploPartido[i]);

}
}

Que mostraría en pantalla lo siguiente:

Fragmento 0= uno
Fragmento 1= dos
Fragmento 2= tres
Fragmento 3= cuatro

4.4.7. Comparación de cadenas

Sabemos comprobar si una cadena tiene exactamente un cierto valor, con el


operador de igualdad (==), pero no sabemos comparar qué cadena es "mayor"
que otra, algo que es necesario si queremos ordenar textos. El operador "mayor
que" (>) que usamos con los números no se puede aplicar directamente a las
cadenas. En su lugar, debemos usar "CompareTo", que devolverá un número
mayor que 0 si la nuestra cadena es mayor que la que indicamos como
parámetro (o un número negativo si nuestra cadena es menor, o 0 si son
iguales):
if (frase.CompareTo("hola") > 0)
Console.WriteLine("Es mayor que hola");

También podemos comparar sin distinguir entre mayúsculas y minúsculas,


usando String.Compare, al que indicamos las dos cadenas y un tercer dato
"true" cuando queramos ignorar esa distinción:
if (String.Compare(frase, "hola", true) > 0)
Console.WriteLine("Es mayor que hola (mays o mins)");

Un programa completo de prueba podría ser así:

using System;

public class Ejemplo43c


{

public static void Main()


{

string frase;

Console.WriteLine("Escriba una palabra");


frase = Console.ReadLine();

// Compruebo si es exactamente hola


if (frase == "hola")
Console.WriteLine("Ha escrito hola");

// Compruebo si es mayor o menor


if (frase.CompareTo("hola") > 0)
Console.WriteLine("Es mayor que hola");
else if (frase.CompareTo("hola") < 0)
Console.WriteLine("Es menor que hola");

// Comparo sin distinguir mayúsculas ni minúsculas


bool ignorarMays = true;
if (String.Compare(frase, "hola", ignorarMays) > 0)
Console.WriteLine("Es mayor que hola (mays o mins)");
else if (String.Compare(frase, "hola", ignorarMays) < 0)
Console.WriteLine("Es menor que hola (mays o mins)");
else
Console.WriteLine("Es hola (mays o mins)");

}
}

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:

Escriba una palabra


gol
Es menor que hola
Es menor que hola (mays o mins)

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)

4.4.8. Una cadena modificable: StringBuilder

Si tenemos la necesidad de poder modificar una cadena letra a letra, no


podemos usar un "string" convencional, deberemos recurrir a un "StringBuilder",
que sí lo permiten pero son algo más complejos de manejar: hay de reservarles
espacio con "new" (igual que hacíamos en ciertas ocasiones con los Arrays), y
se pueden convertir a una cadena "convencional" usando "ToString":

using System;
using System.Text; // Usaremos un System.Text.StringBuilder

public class Ejemplo44


{

public static void Main()


{
StringBuilder cadenaModificable = new StringBuilder("Hola");
cadenaModificable[0] = 'M';
Console.WriteLine("Cadena modificada: {0}",
cadenaModificable);

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"

Existe una construcción parecida a "for", pensada para recorrer ciertas


estructuras de datos, como los arrays (y otras que veremos más adelante).
Se usa con el formato "foreach (variable in ConjuntoDeValores)":

using System;

public class Ejemplo45


{

public static void Main()


{
int[] diasMes = {31, 28, 21};
foreach(int dias in diasMes) {
Console.WriteLine("Dias del mes: {0}", dias);
}

string[] nombres = {"Alberto", "Andrés", "Antonio"};


foreach(string nombre in nombres) {
Console.Write(" {0}", nombre);
}
Console.WriteLine();

string saludo = "Hola";


foreach(char letra in saludo) {
Console.Write("{0}-", letra);
}
Console.WriteLine();

}
}

4.5 Ejemplo completo


Vamos a hacer un ejemplo completo que use tablas ("arrays"), registros ("struct")
y que además manipule cadenas.
La idea va a ser la siguiente: Crearemos un programa que pueda almacenar
datos de hasta 1000 ficheros. Para cada fichero, debe guardar los siguientes
datos: Nombre del fichero, Tamaño (en KB, un número de 0 a 8.000.000.000). El
programa mostrará un menú que permita al usuario las siguientes operaciones:

1- Añadir datos de un nuevo fichero


2- Mostrar los nombres de todos los ficheros almacenados
3- Mostrar ficheros que sean de más de un cierto tamaño (por ejemplo, 2000
KB).
4- Ver todos los datos de un cierto fichero (a partir de su nombre)
5- Salir de la aplicación (como no usamos ficheros, los datos se perderán).
No debería resultar difícil. Vamos a ver directamente una de las formas en que
se podría plantear y luego comentaremos alguna de las mejoras que se podría
(incluso se debería) hacer.
Una opción que podemos a tomar para resolver este problema es la de contar el
número de fichas que tenemos almacenadas, y así podremos añadir de una en
una. Si tenemos 0 fichas, deberemos almacenar la siguiente (la primera) en la
posición 0; si tenemos dos fichas, serán la 0 y la 1, luego añadiremos en la
posición 2; en general, si tenemos "n" fichas, añadiremos cada nueva ficha en la
posición "n". Por otra parte, para revisar todas las fichas, recorreremos desde la
posición 0 hasta la n-1, haciendo algo como
for (i=0; i<=n-1; i++) { /* ... más órdenes ... */ }

o bien algo como


for (i=0; i<n; i++) { /* ... más órdenes ...} */

El resto del programa no es difícil: sabemos leer y comparar textos y números,


comprobar varias opciones con "switch", etc. Aun así, haremos una última
consideración: hemos limitado el número de fichas a 1000, así que, si nos piden
añadir, deberíamos asegurarnos antes de que todavía tenemos hueco
disponible.
Con todo esto, nuestro fuente quedaría así:

using System;

public class Ejemplo46


{

struct tipoFicha {
public string nombreFich; /* Nombre del fichero */
public long tamanyo; /* El tamaño en bytes */
};

public static void Main()


{
tipoFicha[] fichas /* Los datos en si */
= new tipoFicha[1000];
int numeroFichas=0; /* Número de fichas que ya tenemos */
int i; /* Para bucles */
int opcion; /* La opcion del menu que elija el usuario */
string textoBuscar; /* Para cuando preguntemos al usuario */
long tamanyoBuscar; /* Para buscar por tamaño */

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");

opcion = Convert.ToInt32( Console.ReadLine() );

/* Hacemos una cosa u otra según la opción escogida */


switch(opcion){
case 1: /* Añadir un dato nuevo */
if (numeroFichas < 1000) { /* Si queda hueco */
Console.WriteLine("Introduce el nombre del fichero: ");
fichas[numeroFichas].nombreFich = Console.ReadLine();
Console.WriteLine("Introduce el tamaño en KB: ");
fichas[numeroFichas].tamanyo = Convert.ToInt32(
Console.ReadLine() );
/* Y ya tenemos una ficha más */
numeroFichas++;
} else /* Si no hay hueco para más fichas, avisamos */
Console.WriteLine("Máximo de fichas alcanzado (1000)!");
break;
case 2: /* Mostrar todos */
for (i=0; i<numeroFichas; i++)
Console.WriteLine("Nombre: {0}; Tamaño: {1} Kb",
fichas[i].nombreFich, fichas[i].tamanyo);
break;
case 3: /* Mostrar según el tamaño */
Console.WriteLine("¿A partir de que tamaño quieres que te
muestre?");
tamanyoBuscar = Convert.ToInt64( Console.ReadLine() );
for (i=0; i<numeroFichas; i++)
if (fichas[i].tamanyo >= tamanyoBuscar)
Console.WriteLine("Nombre: {0}; Tamaño: {1} Kb",
fichas[i].nombreFich, fichas[i].tamanyo);
break;
case 4: /* Ver todos los datos (pocos) de un fichero */
Console.WriteLine("¿De qué fichero quieres ver todos los datos?");
textoBuscar = Console.ReadLine();
for (i=0; i<numeroFichas; i++)
if ( fichas[i].nombreFich == textoBuscar )
Console.WriteLine("Nombre: {0}; Tamaño: {1} Kb",
fichas[i].nombreFich, fichas[i].tamanyo);
break;
case 5: /* Salir: avisamos de que salimos */
Console.WriteLine("Fin del programa");
break;
default: /* Otra opcion: no válida */
Console.WriteLine("Opción desconocida!");
break;
}
} while (opcion != 5); /* Si la opcion es 5, terminamos */

}
}
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:

o Mostrar la lista de todos los nombres.


o Mostrar las personas de una cierta edad.
o Mostrar las personas cuya inicial sea la que el usuario indique.
o Salir del programa
o (lógicamente, este menú debe repetirse hasta que se escoja la opción
de "salir").
 Mejorar la base de datos de ficheros (ejemplo 46) para que no permita
introducir tamaños incorrectos (números negativos) ni nombres de fichero
vacíos.
 Ampliar la base de datos de ficheros (ejemplo 46) para que incluya una
opción de búsqueda parcial, en la que el usuario indique parte del nombre y
se muestre todos los ficheros que contienen ese fragmento (usando
"IndexOf"). Esta búsqueda no debe distinguir mayúsculas y minúsculas (con
la ayuda de ToUpper o ToLower).
 Ampliar el ejercicio anterior (el que permite búsqueda parcial) para que la
búsqueda sea incremental: el usuario irá indicando letra a letra el texto que
quiere buscar, y se mostrará todos los datos que lo contienen (por ejemplo,
primero los que contienen "j", luego "ju", después "jua" y finalmente "juan").
 Ampliar la base de datos de ficheros (ejemplo 46) para que se pueda borrar
un cierto dato (habrá que "mover hacia atrás" todos los datos que había
después de ese, y disminuir el contador de la cantidad de datos que
tenemos).
 Mejorar la base de datos de ficheros (ejemplo 46) para que se pueda
modificar un cierto dato a partir de su número (por ejemplo, el dato número
3). En esa modificación, se deberá permitir al usuario pulsar Intro sin teclear
nada, para indicar que no desea modificar un cierto dato, en vez de
reemplazarlo por una cadena vacía.
 Ampliar la base de datos de ficheros (ejemplo 46) para que se permita
ordenar los datos por nombre. Para ello, deberás buscar información sobre
algún método de ordenación sencillo, como el "método de burbuja", y
aplicarlo a este caso concreto.

4.6 Ordenaciones simples


Es muy frecuente querer ordenar datos que tenemos en un array. Para
conseguirlo, existen varios algoritmos sencillos, que no son especialmente
eficientes, pero son fáciles de programar. La falta de eficiencia se refiere a que
la mayoría de ellos se basan en dos bucles "for" anidados, de modo que en cada
pasada quede ordenado un dato, y se dan tantas pasadas como datos existen,
de modo que para un array con 1.000 datos, podrían llegar a tener que hacerse
un millón de comparaciones.
Existen ligeras mejoras (por ejemplo, cambiar uno de los "for" por un "while",
para no repasar todos los datos si ya estaban parcialmente ordenados), así
como métodos claramente más efectivos, pero más difíciles de programar,
alguno de los cuales veremos más adelante.
Veremos tres de estos métodos simples de ordenación, primero mirando la
apariencia que tiene el algoritmo, y luego juntando los tres en un ejemplo que los
pruebe:
Método de 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])

(Nota: algunos autores hacen el bucle exterior creciente y otros decreciente,


así:)

Para i=n descendiendo hasta 2


Para j=2 hasta i
Si A[j-1] > A[j]
Intercambiar ( A[j-1], A[j])

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])

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

(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

(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;

public class Ordenar


{

public static void Main()


{

int[] datos = {5, 3, 14, 20, 8, 9, 1};


int i,j,datoTemporal;
int n=7; // Numero de datos

// 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();

for(j=i+1; j < n; j++)


{

if (datos[i] > datos[j])


{
datoTemporal = datos[i];
datos[i] = datos[j];
datos[j] = datoTemporal;
}
}
}
Console.Write("Ordenado:");

foreach (int dato in datos) // Muestro datos finales


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:");

foreach (int dato in datos2) // Muestro datos finales


Console.Write("{0} ",dato);
Console.WriteLine();

// 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:");

foreach (int dato in datos3) // Muestro datos finales


Console.Write("{0} ",dato);
Console.WriteLine();
}
}
Y su resultado sería:

Ordenando mediante burbuja...


5 3 14 20 8 9 1
1 5 14 20 8 9 3
1 3 14 20 8 9 5
1 3 5 20 14 9 8
1 3 5 8 20 14 9
1 3 5 8 9 20 14
Ordenado:1 3 5 8 9 14 20

Ordenando mediante selección directa...


5 3 14 20 8 9 1
1 3 14 20 8 9 5
1 3 14 20 8 9 5
1 3 5 20 8 9 14
1 3 5 8 20 9 14
1 3 5 8 9 20 14
Ordenado:1 3 5 8 9 14 20

Ordenando mediante inserción directa...


5 3 14 20 8 9 1
3 5 14 20 8 9 1
3 5 14 20 8 9 1
3 5 14 20 8 9 1
3 5 8 14 20 9 1
3 5 8 9 14 20 1
Ordenado:1 3 5 8 9 14 20
5. Introducción a las funciones
5.1. Diseño modular de programas: Descomposición modular
Hasta ahora hemos estado pensando los pasos que deberíamos dar para
resolver un cierto problema, y hemos creado programas a partir de cada uno de
esos pasos. Esto es razonable cuando los problemas son sencillos, pero puede
no ser la mejor forma de actuar cuando se trata de algo más complicado.
A partir de ahora vamos a empezar a intentar descomponer los problemas en
trozos más pequeños, que sean más fáciles de resolver. Esto nos puede
suponer varias ventajas:
 Cada "trozo de programa" independiente será más fácil de programar, al
realizar una función breve y concreta.
 El "programa principal" será más fácil de leer, porque no necesitará contener
todos los detalles de cómo se hace cada cosa.
 Podremos repartir el trabajo, para que cada persona se encargue de realizar
un "trozo de programa", y finalmente se integrará el trabajo individual de
cada persona.
Esos "trozos" de programa son lo que suele llamar "subrutinas",
"procedimientos" o "funciones". En el lenguaje C y sus derivados, el nombre que
más se usa es el de funciones.

5.2. Conceptos básicos sobre funciones


En C#, al igual que en C y los demás lenguajes derivados de él, todos los "trozos
de programa" son funciones, incluyendo el propio cuerpo de programa, Main. De
hecho, la forma básica de definir una función será indicando su nombre seguido
de unos paréntesis vacíos, como hacíamos con "Main", y precediéndolo por
ciertas palabras reservadas, como "public static void", cuyo significado iremos
viendo muy pronto. Después, entre llaves indicaremos todos los pasos que
queremos que dé ese "trozo de programa".
Por ejemplo, podríamos crear una función llamada "saludar", que escribiera
varios mensajes en la pantalla:
public static void saludar()
{
Console.WriteLine("Bienvenido al programa");
Console.WriteLine(" de ejemplo");
Console.WriteLine("Espero que estés bien");
}

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;

public class Ejemplo47


{

public static void Saludar()


{
Console.WriteLine("Bienvenido al programa");
Console.WriteLine(" de ejemplo");
Console.WriteLine("Espero que estés bien");
}

public static void Main()


{
Saludar();
Console.WriteLine("Nada más por hoy...");
}

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("#.###") );
}

Y esta función se podría usar desde el cuerpo de nuestro programa así:


escribeNumeroReal(2.3f);

(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;

public class Ejemplo48


{

public static void escribeNumeroReal( float n )


{
Console.WriteLine( n.ToString("#.###") );
}

public static void Main()


{
float x;

x= 5.1f;
Console.WriteLine("El primer numero real es: ");
escribeNumeroReal(x);
Console.WriteLine(" y otro distinto es: ");
escribeNumeroReal(2.3f);
}

Estos datos adicionales que indicamos a la función es lo que llamaremos sus


"parámetros". Como se ve en el ejemplo, tenemos que indicar un nombre para
cada parámetro (puede haber varios) y el tipo de datos que corresponde a ese
parámetro. Si hay más de un parámetro, deberemos indicar el tipo y el nombre
para cada uno de ellos, y separarlos entre comas:
public static void sumar ( int x, int y ) {
...
}

5.4. Valor devuelto por una función. El valor "void".


Cuando queremos dejar claro que una función no tiene que devolver ningún
valor, podemos hacerlo indicando al principio que el tipo de datos va a ser "void"
(nulo), como hacíamos hasta ahora con "Main" y como hicimos con nuestra
función "saludar".
Pero eso no es lo que ocurre con las funciones matemáticas que estamos
acostumbrados a manejar: sí devuelven un valor, el resultado de una operación.
De igual modo, para nosotros también será habitual que queramos que nuestra
función realice una serie de cálculos y nos "devuelva" (return, en inglés) el
resultado de esos cálculos, para poderlo usar desde cualquier otra parte de
nuestro programa. Por ejemplo, podríamos crear una función para elevar un
número entero al cuadrado así:
public static int cuadrado ( int n )
{
return n*n;
}

y podríamos usar el resultado de esa función como si se tratara de un número o


de una variable, así:
resultado = cuadrado( 5 );

Un programa más detallado de ejemplo podría ser:


using System;

public class Ejemplo49


{

public static int cuadrado ( int n )


{
return n*n;
}

public static void Main()


{
int numero;
int resultado;
numero= 5;
resultado = cuadrado(numero);
Console.WriteLine("El cuadrado del numero {0} es {1}",
numero, resultado);
Console.WriteLine(" y el de 3 es {0}", cuadrado(3));
}

Podemos hacer una función que nos diga cual es el mayor de dos números
reales así:

public static float mayor ( float n1, float n2 )


{
if (n1 > n2)
return n1;
else
return n2;
}

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

(multiplicamos 5 veces el 3 por sí mismo). En general, como nos pueden pedir


cosas como "6 elevado a 100" (o en general números que pueden ser grandes),
usaremos la orden "for" para multiplicar tantas veces como haga falta:

using System;

public class Ejemplo50


{

public static int potencia(int nBase, int nExponente)


{
int temporal = 1; /* Valor que voy hallando */
int i; /* Para bucles */

for(i=1; i<=nExponente; i++) /* Multiplico "n" veces */


temporal *= nBase; /* Y calculo el valor temporal */

return temporal; /* Tras las multiplicaciones, */


} /* obtengo el valor que buscaba */

public static void Main()


{
int num1, num2;

Console.WriteLine("Introduzca la base: ");


num1 = Convert.ToInt32( Console.ReadLine() );
Console.WriteLine("Introduzca el exponente: ");
num2 = Convert.ToInt32( Console.ReadLine() );

Console.WriteLine("{0} elevado a {1} vale {2}",


num1, num2, potencia(num1,num2));
}

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
****
***
**
*

5.6. Los conflictos de nombres en las variables


¿Qué ocurre si damos el mismo nombre a dos variables locales? Vamos a
comprobarlo con un ejemplo:

using System;

public class Ejemplo51


{

public static void cambiaN() {


int n = 7;
n ++;
}

public static void Main()


{
int n = 5;
Console.WriteLine("n vale {0}", n);
cambiaN();
Console.WriteLine("Ahora n vale {0}", n);
}

El resultado de este programa es:


n vale 5
Ahora n vale 5

¿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;

public class Ejemplo52


{

static int n = 7;

public static void cambiaN() {


n ++;
}

public static void Main()


{
Console.WriteLine("n vale {0}", n);
cambiaN();
Console.WriteLine("Ahora n vale {0}", n);
}

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.

5.7. Modificando parámetros


Podemos modificar el valor de un dato que recibamos como parámetro, pero
posiblemente el resultado no será el que esperamos. Vamos a verlo con un
ejemplo:

using System;

public class Ejemplo53


{

public static void duplica(int x) {


Console.WriteLine(" El valor recibido vale {0}", x);
x = x * 2;
Console.WriteLine(" y ahora vale {0}", x);
}

public static void Main()


{
int n = 5;
Console.WriteLine("n vale {0}", n);
duplica(n);
Console.WriteLine("Ahora n vale {0}", n);
}

}
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;

public class Ejemplo54


{

public static void duplica(ref int x) {


Console.WriteLine(" El valor recibido vale {0}", x);
x = x * 2;
Console.WriteLine(" y ahora vale {0}", x);
}

public static void Main()


{
int n = 5;
Console.WriteLine("n vale {0}", n);
duplica(ref n);
Console.WriteLine("Ahora n vale {0}", n);
}

En este caso sí se modifica la variable n:


n vale 5
El valor recibido vale 5
y ahora vale 10
Ahora n vale 10
El hecho de poder modificar valores que se reciban como parámetros abre una
posibilidad que no se podría conseguir de otra forma: con "return" sólo se puede
devolver un valor de una función, pero con parámetros pasados por referencia
podríamos devolver más de un dato. Por ejemplo, podríamos crear una función
que intercambiara los valores de dos variables:
public static void intercambia(ref int x, ref int y)

La posibilidad de pasar parámetros por valor y por referencia existe en la


mayoría de lenguajes de programación. En el caso de C# existe alguna
posibilidad adicional que no existe en otros lenguajes, como los "parámetros de
salida". Las veremos más adelante.
Ejercicios propuestos:
 Crear una función "intercambia", que intercambie el valor de los dos
números enteros que se le indiquen como parámetro.
 Crear una función "iniciales", que reciba una cadena como "Nacho Cabanes"
y devuelva las letras N y C (primera letra, y letra situada tras el primer
espacio), usando parámetros por referencia.

5.8. El orden no importa


En algunos lenguajes, una función debe estar declarada antes de usarse. Esto
no es necesario en C#. Por ejemplo, podríamos rescribir el fuente anterior, de
modo que "Main" aparezca en primer lugar y "duplica" aparezca después, y
seguiría compilando y funcionando igual:

using System;

public class Ejemplo55


{

public static void Main()


{
int n = 5;
Console.WriteLine("n vale {0}", n);
duplica(ref n);
Console.WriteLine("Ahora n vale {0}", n);
}

public static void duplica(ref int x) {


Console.WriteLine(" El valor recibido vale {0}", x);
x = x * 2;
Console.WriteLine(" y ahora vale {0}", x);
}
}

5.9. Algunas funciones útiles


5.9.1. Números aleatorios

En un programa de gestión o una utilidad que nos ayuda a administrar un


sistema, no es habitual que podamos permitir que las cosas ocurran al azar.
Pero los juegos se encuentran muchas veces entre los ejercicios de
programación más completos, y para un juego sí suele ser conveniente que
haya algo de azar, para que una partida no sea exactamente igual a la anterior.
Generar números al azar ("números aleatorios") usando C# no es difícil:
debemos crear un objeto de tipo "Random", y luego llamaremos a "Next" para
obtener valores entre dos extremos:

// Creamos un objeto random


Random r = new Random();

// Generamos un número entre dos valores dados


int aleatorio = r.Next(1, 100);

Podemos hacer que sea realmente un poco más aleatorio si en la primera orden
le indicamos que tome como semilla el instante actual:

Random r = new Random(DateTime.Now.Millisecond);

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:

int falsoAleatorio = DateTime.Now.Millisecond;

Vamos a ver un ejemplo, que muestre en pantalla un número al azar entre 1 y


10:

using System;

public class Ejemplo56


{

public static void Main()


{

Random r = new Random(DateTime.Now.Millisecond);


int aleatorio = r.Next(1, 10);
Console.WriteLine("Un número entre 1 y 10: {0}",
aleatorio);
}

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

En C# tenemos muchas funciones matemáticas predefinidas, como:


 Abs(x): Valor absoluto
 Acos(x): Arco coseno
 Asin(x): Arco seno
 Atan(x): Arco tangente
 Atan2(y,x): Arco tangente de y/x (por si x o y son 0)
 Ceiling(x): El valor entero superior a x y más cercano a él
 Cos(x): Coseno
 Cosh(x): Coseno hiperbólico
 Exp(x): Exponencial de x (e elevado a x)
 Floor(x): El mayor valor entero que es menor que x
 Log(x): Logaritmo natural (o neperiano, en base "e")
 Log10(x): Logaritmo en base 10
 Pow(x,y): x elevado a y
 Round(x, cifras): Redondea un número
 Sin(x): Seno
 Sinh(x): Seno hiperbólico
 Sqrt(x): Raíz cuadrada
 Tan(x): Tangente
 Tanh(x): Tangente hiperbólica
(casi todos ellos usan parámetros X e Y de tipo "double")
y una serie de constantes como
 E, el número "e", con un valor de 2.71828...
 PI, el número "Pi", 3.14159...
Todas ellas se usan precedidas por "Math."
La mayoría de ellas son específicas para ciertos problemas matemáticos,
especialmente si interviene la trigonometría o si hay que usar logaritmos o
exponenciales. Pero vamos a destacar las que sí pueden resultar útiles en
situaciones más variadas:
 La raíz cuadrada de 4 se calcularía haciendo x = Math.Sqrt(4);
 La potencia: para elevar 2 al cubo haríamos y = Math.Pow(2, 3);
 El valor absoluto: para trabajar sólo con números positivos usaríamos n =
Math.Abs(x);
Ejercicios propuestos:
 Crear un programa que halle cualquier raíz de un número. El usuario deberá
indicar el número (por ejemplo, 2) y el índice de la raíz (por ejemplo, 3 para
la raíz cúbica). Pista: hallar la raíz cúbica de 2 es lo mismo que elevar 2 a
1/3.
 Crear un programa que resuelva ecuaciones de segundo grado, del tipo ax2
+ bx + c = 0 El usuario deberá introducir los valores de a, b y c. Pista: la
solución se calcula con x = -b ? raíz (b2 – 4•a•c) / 2•a
5.9.3. Pero hay muchas más funciones…

PPero en C# hay muchas más funciones de lo que parece. De hecho, salvo


algunas palabras reservadas (int, float, string, if, switch, for, do, while...), gran
parte de lo que hasta ahora hemos llamado "órdenes", son realmente
"funciones", como Console.ReadLine o Console.WriteLine. Nos iremos
encontrando con otras funciones a medida que avancemos.

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

(por ejemplo, el factorial de 4 es 4 • 3 • 2 • 1 = 24)


Si pensamos que el factorial de n-1 es
(n-1)! = (n-1) • (n-2) • (n-3) • ... • 3 • 2 • 1
Entonces podemos escribir el factorial de un número a partir del factorial del
siguiente número:
n! = n • (n-1)!

Esta es la definición recursiva del factorial, ni más ni menos. Esto, programando,


se haría:

using System;

public class Ejemplo57


{

public static long fact(int n) {


if (n==1) // Aseguramos que termine
return 1;
return n * fact (n-1); // Si no es 1, sigue la recursión
}

public static void Main()


{
int num;
Console.WriteLine("Introduzca un número entero: ");
num = System.Convert.ToInt32(System.Console.ReadLine());
Console.WriteLine("Su factorial es: {0}", fact(num));
}

Dos consideraciones importantes:


 Atención a la primera parte de la función recursiva: es MUY IMPORTANTE
comprobar que hay salida de la función, para que nuestro programa no se
quede dando vueltas todo el tiempo y deje el ordenador (o la tarea actual)
"colgado".
 Los factoriales crecen rápidamente, así que no conviene poner números
grandes: el factorial de 16 es 2.004.189.184, luego a partir de 17 podemos
obtener resultados erróneos, si usamos números enteros "normales".
¿Qué utilidad tiene esto? Pues más de la que parece: muchos problemas
complicados se pueden expresar a partir de otro más sencillo. En muchos de
esos casos, ese problema se podrá expresar de forma recursiva. Más adelante
veremos algún otro ejemplo.
Ejercicios propuestos:
 Crear una función que calcule el valor de elevar un número entero a otro
número entero (por ejemplo, 5 elevado a 3 = 53 = 5 •5 •5 = 125). Esta
función se debe crear de forma recursiva.
 Como alternativa, crear una función que calcule el valor de elevar un número
entero a otro número entero de forma NO recursiva (lo que llamaremos "de
forma iterativa"), usando la orden "for".
 Crear un programa que emplee recursividad para calcular un número de la
serie Fibonacci (en la que los dos primeros elementos valen 1, y para los
restantes, cada elemento es la suma de los dos anteriores).
 Crear un programa que emplee recursividad para calcular la suma de los
elementos de un vector.
 Crear un programa que emplee recursividad para calcular el mayor de los
elementos de un vector.
 Crear un programa que emplee recursividad para dar la vuelta a una cadena
de caracteres (por ejemplo, a partir de "Hola" devolvería "aloH").
 Crear, tanto de forma recursiva como de forma iterativa, una función diga si
una cadena de caracteres es simétrica (un palíndromo). Por ejemplo,
"DABALEARROZALAZORRAELABAD" es un palíndromo.
 Crear un programa que encuentre el máximo común divisor de dos números
usando el algoritmo de Euclides: Dados dos números enteros positivos m y
n, tal que m > n, para encontrar su máximo común divisor, es decir, el mayor
entero positivo que divide a ambos: - Dividir m por n para obtener el resto r
(0 <= r < n) ; - Si r = 0, el MCD es n.; - Si no, el máximo común divisor es
MCD(n,r).

5.11. Parámetros y valor de retorno de "Main"


Es muy frecuente que un programa que usamos desde la "línea de comandos"
tenga ciertas opciones que le indicamos como argumentos. Por ejemplo, bajo
Linux o cualquier otro sistema operativo de la familia Unix, podemos ver la lista
detallada de ficheros que terminan en .c haciendo
ls –l *.c

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

Ahora la orden sería "dir", y el parámetro es "*.c".


Pues bien, estas opciones que se le pasan al programa se pueden leer desde
C#. Se hace indicando un parámetro especial en Main, un array de strings:
static void Main(string[] args)

Para conocer esos parámetros lo haríamos de la misma forma que se recorre


habitualmente un array cuyo tamaño no conocemos: con un "for" que termine en
la longitud ("Length") del array:

for (int i = 0; i < args.Length; i++)


{
System.Console.WriteLine("El parametro {0} es: {1}",
i, args[i]);
}

Por otra parte, si queremos que nuestro programa se interrumpa en un cierto


punto, podemos usar la orden "Environment.Exit". Su manejo habitual es algo
como

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

Una forma alternativa de que "Main" indique errores al sistema operativo es no


declarándolo como "void", sino como "int", y empleando entonces la orden
"return" cuando nos interese:

public static int Main(string[] args)


{
...
return 1;
}
Un ejemplo que pusiera todo esto en prueba podría ser:

using System;

public class Ejemplo58


{

public static int Main(string[] args)


{
Console.WriteLine("Parámetros: {0}", args.Length);

for (int i = 0; i < args.Length; i++)


{
Console.WriteLine("El parámetro {0} es: {1}",
i, args[i]);
}

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".

Por otra parte, si tenemos que explicar a alguien lo que es el portón de un


garaje, y ese alguien no lo ha visto nunca, pero conoce cómo es la puerta de su
casa, le podemos decir "se parece a una puerta de una casa, pero es más
grande para que quepan los coches, está hecha de metal en vez de madera...".
Es decir, podemos describir unos objetos a partir de lo que conocemos de otros.
Finalmente, conviene recordar que "abrir" no se refiere sólo a una puerta.
También podemos hablar de abrir una ventana o un libro, por ejemplo.
Con esto, hemos comentado casi sin saberlo las tres características más
importantes de la Programación Orientada a Objetos (OOP):
 Encapsulación: No podemos separar los comportamientos de las
características de un objeto. Los comportamientos serán funciones, que en
OOP llamaremos métodos. Las características de un objeto serán variables,
como las que hemos usado siempre (las llamaremos atributos). La
apariencia de un objeto en C#, como veremos un poco más adelante,
recordará a un registro o "struct".
 Herencia: Unos objetos pueden heredar métodos y datos de otros. Esto
hace más fácil definir objetos nuevos a partir de otros que ya teníamos
anteriormente (como ocurría con el portón y la puerta) y facilitará la
reescritura de los programas, pudiendo aprovechar buena parte de los
anteriores... si están bien diseñados.
 Polimorfismo: Un mismo nombre de un método puede hacer referencia a
comportamientos distintos (como abrir una puerta o un libro). Igual ocurre
para los datos: el peso de una puerta y el de un portón los podemos llamar
de igual forma, pero obviamente no valdrán lo mismo.
Otro concepto importante es el de "clase": Una clase es un conjunto de objetos
que tienen características comunes. Por ejemplo, tanto mi puerta como la de mi
vecino son puertas, es decir, ambas son objetos que pertenecen a la clase
"puerta". De igual modo, tanto un Ford Focus como un Honda Civic o un Toyota
Corolla son objetos concretos que pertenecen a la clase "coche".

6.2. Objetos y clases en C#


Vamos con los detalles. Las clases en C# se definen de forma parecida a los
registros (struct), sólo que ahora también incluirán funciones. Así, la clase
"Puerta" que mencionábamos antes se podría declarar así:
public class Puerta
{

int ancho; // Ancho en centimetros


int alto; // Alto en centimetros
int color; // Color en formato RGB
bool abierta; // Abierta o cerrada

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public void MostrarEstado()


{
Console.WriteLine("Ancho: {0}", ancho);
Console.WriteLine("Alto: {0}", alto);
Console.WriteLine("Color: {0}", color);
Console.WriteLine("Abierta: {0}", abierta);
}

} // Final de la clase Puerta

Como se puede observar, los objetos de la clase "Puerta" tendrán un ancho, un


alto, un color, y un estado (abierta o no abierta), y además se podrán abrir o
cerrar (y además, nos pueden "mostrar su estado, para comprobar que todo
funciona correctamente).
Para declarar estos objetos que pertenecen a la clase "Puerta", usaremos la
palabra "new", igual que hacíamos con los "arrays":
Puerta p = new Puerta();
p.Abrir();
p.MostrarEstado();
Vamos a completar un programa de prueba que use un objeto de esta clase
(una "Puerta"):

using System;

public class Puerta


{

int ancho; // Ancho en centimetros


int alto; // Alto en centimetros
int color; // Color en formato RGB
bool abierta; // Abierta o cerrada

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public void MostrarEstado()


{
Console.WriteLine("Ancho: {0}", ancho);
Console.WriteLine("Alto: {0}", alto);
Console.WriteLine("Color: {0}", color);
Console.WriteLine("Abierta: {0}", abierta);
}

} // Final de la clase Puerta

public class Ejemplo59


{

public static void Main()


{
Puerta p = new Puerta();

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.

6.3. La herencia. Visibilidad


Vamos a ver ahora cómo definir una nueva clase de objetos a partir de otra ya
existente. Por ejemplo, vamos a crear una clase "Porton" a partir de la clase
"Puerta". Un portón tendrá las mismas características que una puerta (ancho,
alto, color, abierto o no), pero además se podrá bloquear, lo que supondrá un
nuevo atributo y nuevos métodos para bloquear y desbloquear:

public class Porton: Puerta


{

bool bloqueada;

public void Bloquear()


{
bloqueada = true;
}

public void Desbloquear()


{
bloqueada = false;
}

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:

public class Puerta


{

protected int ancho; // Ancho en centimetros


protected int alto; // Alto en centimetros
protected int color; // Color en formato RGB
protected bool abierta; // Abierta o cerrada

public void Abrir()


...

(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
{

protected int ancho; // Ancho en centimetros


protected int alto; // Alto en centimetros
protected int color; // Color en formato RGB
protected bool abierta; // Abierta o cerrada

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public void MostrarEstado()


{
Console.WriteLine("Ancho: {0}", ancho);
Console.WriteLine("Alto: {0}", alto);
Console.WriteLine("Color: {0}", color);
Console.WriteLine("Abierta: {0}", abierta);
}

} // Final de la clase Puerta

// -------------------------------
public class Porton: Puerta
{

bool bloqueada;

public void Bloquear()


{
bloqueada = true;
}

public void Desbloquear()


{
bloqueada = false;
}

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);
}

} // Final de la clase Porton

// -------------------------------
public class Ejemplo60
{

public static void Main()


{
Porton p = new Porton();

Console.WriteLine("Valores iniciales...");
p.MostrarEstado();

Console.WriteLine("\nVamos a bloquear y a abrir...");


p.Bloquear();
p.MostrarEstado();

Console.WriteLine("\nVamos a desbloquear y a abrir...");


p.Abrir();
p.Desbloquear();
p.MostrarEstado();
}

6.4. ¿Cómo se diseñan las clases?


En estos primeros ejemplos, hemos "pensado" qué objetos necesitaríamos, y
hemos empezado a teclear directamente para implementarlos. Esto no es lo
habitual. Normalmente, se usan herramientas gráficas que nos ayuden a
visualizar las clases y las relaciones que existen entre ellas. También se puede
dibujar directamente en papel para aclararnos las ideas, pero el empleo de
herramientas informáticas tiene una ventaja adicional: algunas de ellas nos
permiten generar automáticamente un esqueleto del programa.
La metodología más extendida actualmente para diseñar estos objetos y sus
interacciones (además de otras muchas cosas) se conoce como UML (Unified
Modelling Language, lenguaje de modelado unificado). El estándar UML propone
distintos tipos de diagramas para representar los posibles "casos de uso" de una
aplicación, la secuencia de acciones que se debe seguir, las clases que la van a
integrar (es lo que a nosotros nos interesa en este momento), etc.
Vamos a ver la apariencia que tendría un "diagrama de clases". En concreto,
vamos a ver un ejemplo usando ArgoUML, que es una herramienta gratuita de
modelado UML, que está creada en Java, por lo que se puede utilizar desde
multitud de sistemas operativos.
Ampliando el ejemplo anterior, vamos a suponer que queremos hacer un
sistema "domótico", para automatizar ciertas funciones en una casa: apertura y
cierre de ventanas y puertas, encendido de calefacción, etc.
Las ideas iniciales de las que partiremos son:
 La casa es el conjunto ("agregación") de varios elementos: puertas,
ventanas y calefactores.
 Cada puerta se puede abrir y cerrar.
 Cada ventana se puede abrir, cerrar. Además, las ventanas tienen
persianas, que se pueden subir y bajar.
 Cada calefactor puede encenderse, apagarse o se puede programar para
que trabaje a una cierta temperatura.
Con estas posibilidades básicas, el diagrama de clases podría ser así:

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.

6.5. La palabra "static"


Desde un principio, nos hemos encontrado con que "Main" siempre iba
acompañado de la palabra "static". En cambio, los métodos (funciones) que
pertenecen a nuestros objetos no los estamos declarando como "static". Vamos
a ver el motivo:
La palabra "static" delante de un atributo (una variable) de una clase, indica que
es una "variable de clase", es decir, que su valor es el mismo para todos los
objetos de la clase. Por ejemplo, si hablamos de coches convencionales,
podríamos suponer que el atributo "numeroDeRuedas" va a valer 4 para
cualquier objeto que pertenezca a esa clase (cualquier coches). Por eso, se
podría declarar como "static".
De igual modo, si un método (una función) está precedido por la palabra "static",
indica que es un "método de clase", es decir, un método que se podría usar sin
necesidad de declarar ningún objeto de la clase. Por ejemplo, si queremos que
se pueda usar la función "BorrarPantalla" de una clase "Hardware" sin necesidad
de crear primero un objeto perteneciente a esa clase, lo podríamos conseguir
así:

public class Hardware


{
...

public static void BorrarPantalla ()


{
...
}

que desde dentro de "Main" (incluso perteneciente a otra clase) se usaría con el
nombre de la clase delante:

public class Juego


{
...

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 class Ejemplo


{
...

public LanzarJuego () {
Juego j = new Juego();
j.ComienzoPartida ();
...

6.6. Constructores y destructores.


Hemos visto que al declarar una clase, se dan valores por defecto para los
atributos. Por ejemplo, para un número entero, se le da el valor 0. Pero puede
ocurrir que nosotros deseemos dar valores iniciales que no sean cero. Esto se
puede conseguir declarando un "constructor" para la clase.
Un constructor es una función especial, que se pone en marcha cuando se crea
un objeto de una clase, y se suele usar para dar esos valores iniciales, para
reservar memoria si fuera necesario, etc.
Se declara usando el mismo nombre que el de la clase, y sin ningún tipo de
retorno. Por ejemplo, un "constructor" para la clase Puerta que le diera los
valores iniciales de 100 para el ancho, 200 para el alto, etc., podría ser así:

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(int an, int al)


{
ancho = an;
alto = al;
color = 0xFFFFFF;
abierta = false;
}

Ahora, si declaramos un objeto de la clase puerta con "Puerta p = new Puerta();"


tendrá de ancho 100 y de alto 200, mientras que si lo declaramos con "Puerta p2
= new Puerta(90,220);" tendrá 90 como ancho y 220 como alto.
Un programa de ejemplo que usara estos dos constructores para crear dos
puertas con características iniciales distintas podría ser:
using System;

public class Puerta


{

int ancho; // Ancho en centimetros


int alto; // Alto en centimetros
int color; // Color en formato RGB
bool abierta; // Abierta o cerrada

public Puerta()
{
ancho = 100;
alto = 200;
color = 0xFFFFFF;
abierta = false;
}

public Puerta(int an, int al)


{
ancho = an;
alto = al;
color = 0xFFFFFF;
abierta = false;
}

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public void MostrarEstado()


{
Console.WriteLine("Ancho: {0}", ancho);
Console.WriteLine("Alto: {0}", alto);
Console.WriteLine("Color: {0}", color);
Console.WriteLine("Abierta: {0}", abierta);
}

} // Final de la clase Puerta

public class Ejemplo61


{

public static void Main()


{
Puerta p = new Puerta();
Puerta p2 = new Puerta(90,220);

Console.WriteLine("Valores iniciales...");
p.MostrarEstado();

Console.WriteLine("\nVamos a abrir...");
p.Abrir();
p.MostrarEstado();

Console.WriteLine("Para la segunda puerta...");


p2.MostrarEstado();
}

Nota: al igual que existen los "constructores", también podemos indicar un


"destructor" para una clase, que se encargue de liberar la memoria que
pudiéramos haber reservado en nuestra clase (no es nuestro caso, porque aún
no sabemos manejar memoria dinámica) o para cerrar ficheros abiertos (que
tampoco sabemos).
Un "destructor" se llama igual que la clase, pero precedido por el símbolo "~", no
tiene tipo de retorno, y no necesita ser "public", como ocurre en este ejemplo:

~Puerta()
{
// Liberar memoria
// Cerrar ficheros
}

6.7. Polimorfismo y sobrecarga


Esos dos constructores "Puerta()" y "Puerta(int ancho, int alto)", que se llaman
igual pero reciben distintos parámetros, y se comportan de forma que puede ser
distinta, son ejemplos de "polimorfismo" (funciones que tienen el mismo nombre,
pero distintos parámetros, y que quizá no se comporten de igual forma).
Un concepto muy relacionado con el polimorfismo es el de "sobrecarga": dos
funciones están sobrecargadas cuando se llaman igual, reciben el mismo
número de parámetros, pero se aplican a objetos distintos, así:

puerta.Abrir ();
libro.Abrir ();

En este caso, la funci&oacute;n &quot;Abrir&quot; est&aacute; sobrecargada: se


usa tanto para referirnos a abrir un libro como para abrir una puerta. Se trata de
dos acciones que no son exactamente iguales, que se aplican a objetos
distintos, pero que se llaman igual.

6.8. Orden de llamada de los constructores


Cuando creamos objetos de una clase derivada, antes de llamar a su constructor
se llama a los constructores de las clases base, empezando por la más general
y terminando por la más específica. Por ejemplo, si creamos una clase
"GatoSiamés", que deriva de una clase "Gato", que a su vez procede de una
clase "Animal", el orden de ejecución de los constructores sería: Animal, Gato,
GatoSiames, como se ve en este ejemplo:

using System;

public class Animal


{

public Animal()
{
Console.WriteLine("Ha nacido un animal");
}
}

// ------------------

public class Perro: Animal


{

public Perro()
{
Console.WriteLine("Ha nacido un perro");
}
}

// ------------------

public class Gato: Animal


{

public Gato()
{
Console.WriteLine("Ha nacido un gato");
}
}

// ------------------

public class GatoSiames: Gato


{

public GatoSiames()
{
Console.WriteLine("Ha nacido un gato siamés");
}
}

// ------------------

public class Ejemplo62


{

public static void Main()


{
Animal a1 = new Animal();
GatoSiames a2 = new GatoSiames();
Perro a3 = new Perro();
Gato a4 = new Gato();
}

El resultado de este programa es:

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:

Perro[] misPerros = new Perro[5];


for (byte i = 0; i < 5; i ++)
misPerros[i] = new Perro();

Un fuente completo de ejemplo podría ser

using System;

public class Animal


{

public Animal()
{
Console.WriteLine("Ha nacido un animal");
}
}

// ------------------

public class Perro: Animal


{

public Perro()
{
Console.WriteLine("Ha nacido un perro");
}
}

// ------------------

public class Ejemplo63


{

public static void Main()


{
Perro[] misPerros = new Perro[5];
for (byte i = 0; i < 5; i ++)
misPerros[i] = new 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.,

Animal[] misAnimales = new Animal[3];

misAnimales[0] = new Perro();


misAnimales[1] = new Gato();
misAnimales[2] = new GatoSiames();

Un ejemplo más detallado:

using System;

public class Animal


{

public Animal()
{
Console.WriteLine("Ha nacido un animal");
}
}

// ------------------

public class Perro: Animal


{

public Perro()
{
Console.WriteLine("Ha nacido un perro");
}
}

// ------------------

public class Gato: Animal


{
}

6.11. Llamando a un método de la clase "padre"


Puede ocurrir que en un método de una clase hija no nos interese redefinir por
completo las posibilidades del método equivalente, sino ampliarlas. En ese caso,
no hace falta que volvamos a teclear todo lo que hacía el método de la clase
base, sino que podemos llamarlo directamente, precediéndolo de la palabra
"base". Por ejemplo, podemos hacer que un Gato Siamés hable igual que un
Gato normal, pero diciendo "Pfff" después, así:

public new void Hablar()


{
base.Hablar();
Console.WriteLine("Pfff");
}

Este podría ser un fuente completo:

using System;

public class Animal


{

// ------------------

public class Gato: Animal


{
public void Hablar()
{
Console.WriteLine("Miauuu");
}
}

// ------------------

public class GatoSiames: Gato


{
public new void Hablar()
{
base.Hablar();
Console.WriteLine("Pfff");
}
}

// ------------------

public class Ejemplo67


{

public static void Main()


{
Gato miGato = new Gato();
GatoSiames miGato2 = new GatoSiames();

miGato.Hablar();
Console.WriteLine(); // Linea en blanco
miGato2.Hablar();

Su resultado sería

Miauuu

Miauuu
Pfff

6.12. Sobrecarga de operadores


Los "operadores" son los símbolos que se emplean para indicar ciertas
operaciones. Por ejemplo, el operador "+" se usa para indicar que queremos
sumar dos números.
Pues bien, en C# se puede "sobrecargar" operadores, es decir, redefinir su
significado, para poder sumar (por ejemplo) objetos que nosotros hayamos
creado, de forma más cómoda y legible. Por ejemplo, para sumar dos matrices,
en vez de hacer algo como "matriz3 = suma( matriz1, matriz2 )" podríamos hacer
simplemente " matriz3 = matriz1 + matriz2"
No entraremos en detalle, pero la idea está en que redefiniríamos un método
llamado "operador +", y que devolvería un dato del tipo con el que estamos (por
ejemplo, una Matriz) y recibiría dos datos de ese mismo tipo como parámetros:

public static Matriz operator +(Matriz mat1, Matriz mat2)


{
Matriz nuevaMatriz = new Matriz();

for (int x=0; x < tamanyo; x++)


for (int y=0; y < tamanyo; y++)
nuevaMatriz[x, y] = mat1[x, y] + mat2[x, y];

return nuevaMatriz;
}

Desde "Main", calcularíamos una matriz como suma de otras dos haciendo
simplemente

Matriz matriz3 = matriz1 + matriz2;

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.

También podría gustarte