Introcsharp Version099zz PDF
Introcsharp Version099zz PDF
Introducción a la programación
con C#
Este texto ha sido escrito por Nacho Cabanes. Si quiere conseguir la última
versión, estará en mi página web:
www.nachocabanes.com
Este texto se distribuye "tal cual", sin garantía de ningún tipo, implícita ni explícita.
Aun así, mi intención es que resulte útil, así que le rogaría que me comunique
cualquier error que encuentre.
Contenido
Contenido _________________________________________________________________________________ 2
0. Conceptos básicos sobre programación ____________________________________________ 9
0.1. Programa y lenguaje _______________________________________________________________________________ 9
0.4. Pseudocódigo_______________________________________________________________________________________ 14
5.4. Valor devuelto por una función. El valor "void" _____________________________________________ 160
Ap1.2. Unidades de medida empleadas en informática (2): los bits ____________________________ 386
Ap5.2. Cambios de apariencia. Casillas de texto para sumar dos números ___________________ 424
Estas órdenes se le deben dar en un cierto lenguaje, que el ordenador sea capaz
de comprender.
Uno de los lenguajes de alto nivel más sencillos es el lenguaje BASIC. En este
lenguaje, escribir el texto Hola en pantalla, sería tan sencillo como usar la orden
PRINT "Hola"
Otros lenguajes, como Pascal, nos obligan a ser algo más estrictos y detallar
ciertas cosas como el nombre del programa o dónde empieza y termina éste, pero,
a cambio, hacen más fácil descubrir errores (ya veremos por qué):
program Saludo;
begin
write('Hola');
end.
El equivalente en lenguaje C resulta algo más difícil de leer, porque los programas
en C suelen necesitar incluir bibliotecas externas y devolver códigos de error
(incluso cuando todo ha ido bien):
#include <stdio.h>
int main()
{
printf("Hola");
return 0;
}
En C# hay que dar todavía más pasos para conseguir lo mismo, porque, como
veremos, cada programa será "una clase":
Como se puede observar, a medida que los lenguajes evolucionan, son capaces de
ayudar al programador en más tareas, pero a la vez, los programas sencillos se
vuelven más complicados. Afortunadamente, no todos los lenguajes siguen esta
regla, y algunos se han diseñado de forma que las tareas simples sean (de nuevo)
sencillas de programar. Por ejemplo, para escribir algo en pantalla usando el
lenguaje Python haríamos:
print("Hello")
Por el contrario, los lenguajes de bajo nivel son más cercanos al ordenador que a
los lenguajes humanos. Eso hace que sean más difíciles de aprender y también
que los fallos sean más difíciles de descubrir y corregir, a cambio de que podemos
optimizar al máximo la velocidad (si sabemos cómo), e incluso llegar a un nivel de
control del ordenador que a veces no se puede alcanzar con otros lenguajes. Por
ejemplo, escribir Hola en lenguaje ensamblador de un ordenador equipado con
el sistema operativo MsDos y con un procesador de la familia Intel x86 sería algo
como
dosseg
.model small
.stack 100h
.data
saludo db 'Hola',0dh,0ah,'$'
.code
main proc
mov ax,@data
mov ds,ax
mov ah,9
mov dx,offset saludo
int 21h
mov ax,4C00h
int 21h
main endp
end main
Resulta bastante más difícil de seguir. Pero eso todavía no es lo que el ordenador
entiende, aunque tiene una equivalencia casi directa. Lo que el ordenador
realmente es capaz de comprender son secuencias de ceros y unos. Por ejemplo,
las órdenes "mov ds, ax" y "mov ah, 9" (en cuyo significado no vamos a entrar) se
convertirían a lo siguiente:
(Nota: los colores de los ejemplos anteriores son una ayuda que nos dan algunos
entornos de programación, para que nos sea más fácil descubrir ciertos errores).
Por ejemplo, en el caso de Windows (y de MsDos), y del programa que nos saluda
en lenguaje Pascal, tendríamos un fichero fuente llamado SALUDO.PAS. Este
fichero no serviría de nada en un ordenador que no tuviera un compilador de
Pascal. En cambio, después de compilarlo obtendríamos un fichero SALUDO.EXE,
capaz de funcionar en cualquier otro ordenador que tuviera el mismo sistema
operativo, aunque dicho ordenador no tenga un compilador de Pascal instalado.
Eso sí, no funcionaría en otro ordenador que tuviera un sistema operativo distinto
(por ejemplo, Linux o Mac OS X).
Los intérpretes siguen siendo muy frecuentes hoy en día. Por ejemplo, en un
servidor web es habitual crear programas usando lenguajes como PHP, ASP o
Python, y que estos programas no se conviertan a un ejecutable, sino que sean
analizados y puestos en funcionamiento en el momento en el que se solicita la
correspondiente página web.
Actualmente existe una alternativa más, algo que parece intermedio entre un
compilador y un intérprete. Existen lenguajes que no se compilan a un ejecutable
para un ordenador concreto, sino a un ejecutable "genérico", que es capaz de
funcionar en distintos tipos de ordenadores, a condición de que en ese ordenador
exista una "máquina virtual" capaz de entender esos ejecutables genéricos. Esta
es la idea que se aplica en Java: los fuentes son ficheros de texto, con extensión
".java", que se compilan a ficheros ".class". Estos ficheros ".class" se podrían llevar
a cualquier ordenador que tenga instalada una "máquina virtual Java" (las hay para
la mayoría de sistemas operativos).
Esta misma idea se sigue en el lenguaje C#, que se apoya en una máquina virtual
llamada "Dot Net Framework" (algo así como "plataforma punto net"): los
programas que creemos con herramientas como Visual Studio serán unos
ejecutables que funcionarán en cualquier ordenador que tenga instalada dicha
"plataforma .Net", algo que suele ocurrir en las versiones recientes de Windows y
que se puede conseguir de forma un poco más artesanal en plataformas Linux y
Mac, gracias a un "clon" de la "plataforma .Net" que es de libre distribución,
conocido como "proyecto Mono".
Ejercicios propuestos
0.4. Pseudocódigo
A pesar de que los lenguajes de alto nivel se acercan al lenguaje natural (inglés),
que nosotros empleamos, es habitual no usar ningún lenguaje de programación
concreto cuando queremos plantear iniciamente los pasos necesarios para
resolver un problema, sino emplear un lenguaje de programación ficticio, no tan
estricto, muchas veces incluso en español. Este lenguaje recibe el nombre de
pseudocódigo.
Por ejemplo, un algoritmo que controlase los pagos que se realizan en una tienda
con tarjeta de crédito, escrito en pseudocódigo, podría ser:
Ejercicios propuestos
Proceso EjemploDeSuma
Escribir 2+3
FinProceso
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.
Esto escribe "Hola" en la pantalla. Pero hay muchas "cosas raras" alrededor de ese
"Hola", de modo vamos a comentarlas antes de proseguir, aunque muchos de los
detalles los aplazaremos para más adelante. En este primer análisis, iremos desde
dentro hacia fuera:
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".
Como se puede ver, mucha parte de este programa todavía es casi un "acto de fe"
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.
}
}
En este caso, yo comentaré los pasos necesarios para usar Linux Mint (en su
versión 17 Cinnamon) como entorno de desarrollo:
de texto llamada "Filtro rápido", en la que podemos teclear "mcs" para que nos
aparezca directamente nuestro compilador Mono:
En este editor podemos teclear nuestro programa, que inicialmente se verá con
letras negras sobre fondo blanco:
En esa "pantalla negra" ya podemos teclear las órdenes necesarias para compilar y
probar el programa:
Si alguno de los pasos ha fallado, tendrás que comprobar si has dado los pasos
anteriores de forma correcta y si tu fuente está bien tecleado.
http://www.mono-project.com/
El siguiente paso es elegir qué componentes queremos instalar (Mono, Gtk#, XSP):
Después deberemos indicar en qué carpeta del menú de Inicio queremos que
quede el acceso a Mono:
Mono está listo para usar. En nuestro menú de Inicio deberíamos tener una nueva
carpeta llamada "Mono x.x.x for Windows", y dentro de ella un acceso a "Mono-
x.x.x Command Prompt":
Quizá se nos lleve a una carpeta que esté dentro de "Documents and settings" o
quizá (algo habitual en las últimas versiones) a alguna en la que no tengamos
permiso para escribir, como "Windows\System32".
En ese caso, podemos crear una carpeta en nuestro escritorio, llamada (por
ejemplo) "Programas", y comenzar por desplazarnos hasta ella, tecleando
cd \users\yo\desktop\programas
(Esa sería la forma de hacerlo para un usuario llamado "Yo" en un sistema con
Windows 7 o Windows 8; los detalles exactos dependerán del nombre del usuario
y de la versión de Windows empleada).
Para crear un programa, el primero paso será teclear el "fuente". Para ello
podemos usar cualquier editor de texto. En este primer fuente, usaremos
simplemente el "Bloc de notas" de Windows. Para ello, tecleamos:
notepad ejemplo01.cs
Aparecerá la pantalla del "Bloc de notas", junto con un aviso que nos indica que no
existe ese fichero, y que nos pregunta si deseamos crearlo. Lo razonable es
responder que sí:
Guardamos los cambios, salimos del "Bloc de notas" y nos volvemos a encontrar
en la pantalla negra del símbolo del sistema. Nuestro fuente ya está escrito y
guardado. El siguiente paso es compilarlo. Para eso, tecleamos
mcs ejemplo01.cs
mono ejemplo01.exe
Si en nuestro ordenador está instalado el "Dot Net Framework" (algo que debería
ser cierto en las últimas versiones de Windows, y que no ocurrirá en Linux ni Mac
OsX), quizá no sea necesario decir que queremos que sea Mono quien lance
nuestro programa, y podamos ejecutarlo directamente con su nombre:
ejemplo01
Pero en ocasiones puede ocurrir que el ejecutable sea para una versión de la
plataforma ".Net" distinta de la que tenemos instalada. En ese caso, tendremos
que lanzarlo usando Mono, o bien deberemos descargar e instalar la
correspondiente versión de ."Net" (es una descarga gratuita desde la página web
de Microsoft):
Hay un posible problema que se debe tener en cuenta: algunos de estos entornos
de desarrollo muestran el resultado de nuestro programa y luego regresan al
editor tan rápido que no da tiempo a ver los resultados. Una solución provisional
puede ser añadir "System.Console.ReadLine()" al final del programa, de modo que
se quede parado hasta que pulsemos Intro:
(Nota: no deberás entregar tus programas con ese "ReadLine" al final: es una
ayuda para que compruebes que están funcionando correctamente desde ciertos
entornos, pero no debería formar parte de un programa finalizado, porque no es
parte de la lógica del programa).
Ejercicios propuestos:
(1.4.1) Crea un programa que diga el resultado de sumar 118 y 56.
(1.4.2) Crea un programa que diga el resultado de sumar 12345 y 67890.
Operador Operación
+ Suma
- Resta, negación
* Multiplicación
/ División
% Resto de la división ("módulo")
Revisión 0.99zz – Página 34
Introducción a la programación con C#, por Nacho Cabanes
Ejercicios propuestos:
(1.5.1.1) Haz un programa que calcule el producto de los números 12 y 13.
(1.5.1.2) Un programa que calcule la diferencia (resta) entre 321 y 213.
(1.5.1.3) Un programa que calcule el resultado de dividir 301 entre 3.
(1.5.1.4) Un programa que calcule el resto de la división de 301 entre 3.
Por eso necesitaremos reservar zonas de memoria a las que datemos un nombre
y en las que guardemos los datos con los que vamos a trabajar y también los
resultados temporales. A estas "zonas de memoria con nombre" les llamaremos
variables.
Como primer ejemplo, vamos a ver lo que haríamos para sumar dos números
enteros que fijásemos en el programa.
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".
int primerNumero;
...
primerNumero = 234;
Hay que tener en cuenta que esto no es una igualdad matemática, sino una
"asignación de valor": el elemento de la izquierda recibe el valor que indicamos a
la derecha. Por eso no se puede hacer 234 = primerNumero, y sí se puede
cambiar el valor de una variable tantas veces como queramos
primerNumero = 234;
primerNumero = 237;
También podemos dar un valor inicial a las variables ("inicializarlas") antes de que
empiece el programa, en el mismo momento en que las definimos:
(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:
Ejercicio propuesto (1.6.2.1): Amplía el ejercicio 1.6.1.1, para que las tres
variables n1, n2, n3 estén declaradas en la misma línea y tengan valores iniciales.
System.Console.WriteLine(3+4);
pero si se trata de una variable es idéntico (sin comillas, para que el compilador
analice su valor de antes de escribir):
System.Console.WriteLine(suma);
Ya sabemos todo lo suficiente para crear nuestro programa que sume dos
números usando variables:
{
public static void Main()
{
int primerNumero;
int segundoNumero;
int suma;
primerNumero = 234;
segundoNumero = 567;
suma = primerNumero + segundoNumero;
Ejercicios propuestos:
(1.6.3.1) Crea un programa que calcule el producto de los números 121 y 132,
usando variables.
(1.6.3.2) Crea un programa que calcule la suma de 285 y 1396, usando variables.
(1.6.3.3) Crea un programa que calcule el resto de dividir 3784 entre 16, usando
variables.
(1.6.3.4) Amplía el ejercicio 1.6.2.1, para que se muestre el resultado de la
operación n1+n2*n3.
1.7. Identificadores
Los nombres de variables (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 intermedios. También hay que
recordar que las vocales acentuadas y la eñe son problemáticas, porque no son
letras "estándar" en todos los idiomas, así que no se pueden utilizar como parte de
un identificador en la mayoría de lenguajes de programación.
PrimerNumero = 234;
primernumero = 234;
int primerNumero;
Ejercicios propuestos:
(1.7.1) Crea un programa que calcule el producto de los números 87 y 94, usando
variables llamadas "numero1" y "numero2".
(1.7.2) Intenta crear una nueva versión del programa que calcula el producto de
los números 87 y 94, usando esta vez variables llamadas "1numero" y "2numero".
(1.7.3) Intenta crear una nueva versión del programa que calcula el producto de
los números 87 y 94, usando esta vez variables llamadas "numero 1" y "numero 2".
(1.7.4) Crea una nueva versión del programa que calcula el producto de los
números 87 y 94, usando esta vez variables llamadas "número1" y "número2".
1.8. Comentarios
Podemos escribir comentarios, que el compilador ignorará, pero que pueden ser
útiles para nosotros mismos, haciendo que sea más fácil recordar el cometido un
fragmento del programa más adelante, cuando tengamos que ampliarlo o
corregirlo.
/* 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 de una línea" o
"comentarios al estilo de C++" (a diferencia de los "comentarios de múltiples
líneas" o "comentarios al estilo de C" que ya hemos visto):
En este texto, a partir de ahora los fuentes comenzarán con un comentario que
resuma su cometido, y en ocasiones incluirán también comentarios intermedios.
Ejercicios propuestos:
(1.8.1) Crea un programa que convierta una cantidad prefijada de metros (por
ejemplo, 3000) a millas. La equivalencia es 1 milla = 1609 metros. Usa comentarios
donde te parezca adecuado.
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á un poco más adelante, 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:
Un ejemplo de programa que sume dos números tecleados por el usuario sería:
Ejercicios propuestos:
(1.9.1) Crea un programa que calcule el producto de dos números introducidos
por el usuario.
(1.9.2) Crea un programa que calcule la división de dos números introducidos por
el usuario, así como el resto de esa división.
(1.9.3) Suma tres números tecleados por usuario.
(1.9.4) Pide al usuario una cantidad de "millas náuticas" y muestra la equivalencia
en metros, usando: 1 milla náutica = 1852 metros.
segundoNumero = Convert.ToInt32(Console.ReadLine());
suma = primerNumero + segundoNumero;
Ejercicios propuestos:
(1.10.1) Crea una nueva versión del programa que calcula el producto de dos
números introducidos por el usuario (1.9.1), empleando "using System". El
programa deberá contener un comentario al principio, que recuerde cual es su
objetivo.
(1.10.2) Crea una nueva versión del programa que calcula la división de dos
números introducidos por el usuario, así como el resto de esa división (1.9.2),
empleando "using System". Deberás incluir un comentario con tu nombre y la
fecha en que has realizado el programa.
Ejercicios propuestos:
(1.11.1) 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.
Ambos resultados se deben mostrar en la misma línea.
(1.11.2) Pedir al usuario un número y mostrar su tabla de multiplicar,
usando {0},{1} y {2}. Por ejemplo, si el número es el 3, debería escribirse
algo como
3x0=0
3x1=3
3x2=6
…
3 x 10 = 30
(1.11.3) Crear una variante del programa anterior, que pide al usuario un
número y muestra su tabla de multiplicar. Esta vez no deberás utilizar {0},
{1}, {2}, sino "Write".
(1.11.4) Crea un programa que convierta de grados Celsius (centígrados) a
Kelvin y a Fahrenheit: pedirá al usuario la cantidad de grados centígrados y
usará las siguiente tablas de conversión: kelvin = celsius + 273 ; fahrenheit
= celsius x 18 / 10 + 32. Emplea "Write" en vez de "{0}" cuando debas
mostrar varios datos en la misma línea.
2. Estructuras de control
Casi cualquier problema del mundo real que debamos resolver o tarea que
deseemos automatizar supondrá tomar decisiones: dar una serie de pasos en
función de si se cumplen ciertas condiciones o no. En muchas ocasiones, además
esos pasos deberán ser repetitivos. Vamos a ver cómo podemos comprobar si se
cumplen condiciones y también cómo hacer que un bloque de un programa se
repita.
if (condición) sentencia;
Es decir, debe empezar con la palabra "if", la condición se debe indicar entre
paréntesis y a continuación se detallará la orden que hay que realizar en caso de
cumplirse esa condición, terminando con un punto y coma.
// Ejemplo_02_01_01a.cs
// Condiciones con if
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero>0) Console.WriteLine("El número es positivo.");
}
}
Este programa pide un número al usuario. Si es positivo (mayor que 0), escribe en
pantalla "El número es positivo."; si es negativo o cero, no hace nada.
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.
Si la orden "if" es larga, se puede partir en dos líneas para que resulte más legible:
if (numero>0)
Console.WriteLine("El número es positivo.");
Ejercicios propuestos:
(2.1.1.1) Crea 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) …).
(2.1.1.2) Crea un programa que pida al usuario dos números enteros y diga cuál es
el mayor de ellos.
(2.1.1.3) Crea 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).
// Ejemplo_02_01_02a.cs
// Condiciones con if (2): Sentencias compuestas
// Introducción a C#, por Nacho Cabanes
using System;
{
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.");
} // Aquí acaba el "if"
} // Aquí acaba "Main"
} // Aquí acaba "Ejemplo06
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 "Ejemplo06" está un
poco más a la derecha que la cabecera "public class Ejemplo06", 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á aún más a la derecha. Este "sangrado" del texto
se suele llamar "escritura indentada". Un tamaño habitual para el sangrado es de
4 espacios, aunque en este texto en algunas ocasiones usaremos sólo dos
espacios, para que fuentes más complejos quepan entre los márgenes del papel.
Ejercicios propuestos:
(2.1.2.1) Crea un programa que pida al usuario un número entero. Si es múltiplo
de 10, informará al usuario y pedirá un segundo número, para decir a
continuación si este segundo número también es múltiplo de 10.
Operador Operación
< Menor que
> Mayor que
<= Menor o igual que
>= Mayor o igual que
== Igual a
!= No igual a (distinto de)
Revisión 0.99zz – Página 49
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_02_01_03a.cs
// Condiciones con if (3): "distinto de"
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero != 0)
Console.WriteLine("El número no es cero.");
}
}
Ejercicios propuestos:
(2.1.3.1) Crea 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.
(2.1.3.2) Crea un programa que pida al usuario dos números enteros. Si el
segundo no es cero, mostrará el resultado de dividir entre el primero y el segundo.
Por el contrario, si el segundo número es cero, escribirá "Error: No se puede dividir
entre cero".
2.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í:
// Ejemplo_02_01_04a.cs
// Condiciones con if (4): caso contrario ("else")
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0)
Console.WriteLine("El número es positivo.");
else
Console.WriteLine("El número es cero o negativo.");
}
}
// Ejemplo_02_01_04b.cs
// Condiciones con if (5): caso contrario, sin "else"
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0)
Console.WriteLine("El número es positivo.");
if (numero <= 0)
Console.WriteLine("El número es cero o negativo.");
}
}
Podemos enlazar varios "if" usando "else", para decir "si no se cumple esta
condición, mira a ver si se cumple esta otra":
// Ejemplo_02_01_04c.cs
// Condiciones con if (6): condiciones encadenadas
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0)
Console.WriteLine("El número es positivo.");
else
if (numero < 0)
Console.WriteLine("El número es negativo.");
else
Console.WriteLine("El número es cero.");
}
}
Ejercicio propuesto:
(2.1.4.1) Mejora la solución al ejercicio 2.1.3.1, usando "else".
(2.1.4.2) Mejora la solución al ejercicio 2.1.3.2, usando "else".
Operador Significado
&& Y
|| O
! No
Así, un programa que dijera si dos números introducidos por el usuario son cero,
podría ser:
// Ejemplo_02_01_05a.cs
// Condiciones con if enlazadas con &&
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(2.1.5.1) Crea un programa que pida al usuario un número entero y responda si es
múltiplo de 2 o de 3.
(2.1.5.2) Crea un programa que pida al usuario un número entero y responda si es
múltiplo de 2 y de 3 simultáneamente.
(2.1.5.3) Crea un programa que pida al usuario un número entero y responda si es
múltiplo de 2 pero no de 3.
(2.1.5.4) Crea un programa que pida al usuario un número entero y responda si no
es múltiplo de 2 ni de 3.
(2.1.5.5) Crea un programa que pida al usuario dos números enteros y diga si
ambos son pares.
(2.1.5.6) Crea un programa que pida al usuario dos números enteros y diga si (al
menos) uno es par.
Revisión 0.99zz – Página 53
Introducción a la programación con C#, por Nacho Cabanes
(2.1.5.7) Crea un programa que pida al usuario dos números enteros y diga si uno
y sólo uno es par.
(2.1.5.8) Crea 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.
(2.1.5.9) Crea un programa que pida al usuario tres números y muestre cuál es el
mayor de los tres.
(2.1.5.10) Crea un programa que pida al usuario dos números enteros y diga si son
iguales o, en caso contrario, cuál es el mayor de ellos.
En el caso del lenguaje C#, este riesgo no existe, porque la "condición" debe ser
algo cuyo resultado sea "verdadero" o "falso" (lo que pronto llamaremos un dato
de tipo "bool"), de modo que obtendríamos un error de compilación "Cannot
implicitly convert type 'int' to 'bool'" (no puedo convertir un "int" a "bool"). Es el caso
del siguiente programa:
// Ejemplo_02_01_06a.cs
// Condiciones con if: comparación incorrecta
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce un número");
numero = Convert.ToInt32(Console.ReadLine());
if (numero = 0)
Nota: en lenguajes como C y C++, en los que sí existe este riesgo de asignar un
valor en vez de comparar, se suele recomendar plantear la comparación al revés,
colocando el número en el lado izquierdo, de modo que si olvidamos el doble
signo de "=", obtendríamos una asignación no válida y el programa no compilaría:
if (0 == numero) ...
Ejercicios propuestos:
(2.1.6.1) Crea una variante del ejemplo 02_01_06a, en la que la comparación de
igualdad sea correcta y en la que las variables aparezcan en el lado derecho de la
comparación y los números en el lado izquierdo.
Es decir:
El inicio o el final del programa se indica dentro de un círculo.
Eso sí, hay que tener en cuenta que ésta es una notación anticuada, y que no
permite representar de forma fiable las estructuras repetitivas que veremos
dentro de poco, por lo que su uso actual es muy limitado.
Ejercicios propuestos:
(2.1.7.1) Crea el diagrama de flujo para el programa que pide dos números al
usuario y dice cuál es el mayor de los dos.
(2.1.7.2) Crea 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.
(2.1.7.3) Crea el diagrama de flujo para el programa que pide tres números al
usuario y dice cuál es el mayor de los tres.
y equivale a decir "si se cumple la condición, toma el valor valor1; si no, toma el
valor valor2". Un ejemplo de cómo podríamos usarlo sería para calcular el mayor
de dos números:
numeroMayor = a>b ? a : b;
if ( a > b )
numeroMayor = a;
else
numeroMayor = b;
// Ejemplo_02_01_08a.cs
// El operador condicional
// Introducción a C#, por Nacho Cabanes
using System;
int a, b, mayor;
mayor = a>b ? a : b;
Un segundo ejemplo, que sume o reste dos números según la opción que se
escoja, sería:
// Ejemplo_02_01_08b.cs
// El operador condicional (2)
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(2.1.8.1) Crea 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.
(2.1.8.2) Usa el operador condicional para calcular el menor de dos números.
2.1.9. switch
Si queremos ver varios posibles valores, sería muy pesado tener que hacerlo con
muchos "if" seguidos o encadenados. La alternativa es emplear la orden "switch",
cuya sintaxis es
switch (expresión)
{
case valor1: sentencia1;
break;
case valor2: sentencia2;
sentencia2b;
break;
case valor3:
goto case valor1;
...
case valorN: sentenciaN;
break;
default:
otraSentencia;
break;
}
Es decir:
Tras la palabra "switch" se escribe 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 la expresión tiene
un cierto 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 después de la palabra "default".
Si dos casos tienen que hacer lo mismo, se añade "goto case" a uno de
ellos para indicarlo.
Vamos a ver un ejemplo, que diga si el símbolo que introduce el usuario es una
cifra numérica, un espacio u otro símbolo. Para ello usaremos un dato de tipo
"char" (carácter), que veremos con más detalle en el próximo tema. De momento
nos basta que deberemos usar Convert.ToChar si lo leemos desde teclado con
ReadLine, y que le podemos dar un valor (o compararlo) usando comillas simples:
// Ejemplo_02_01_09a.cs
// La orden "switch" (1)
// Introducción a C#, por Nacho Cabanes
using System;
switch (letra)
{
case ' ': Console.WriteLine("Espacio.");
break;
case '1': goto case '0';
case '2': goto case '0';
case '3': goto case '0';
case '4': goto case '0';
case '5': goto case '0';
case '6': goto case '0';
case '7': goto case '0';
case '8': goto case '0';
case '9': goto case '0';
case '0': Console.WriteLine("Dígito.");
break;
default: Console.WriteLine("Ni espacio ni dígito.");
break;
}
}
}
Cuidado quien venga del lenguaje C: en C se puede dejar que un caso sea
manejado por el siguiente, lo que se consigue si no se usa "break", mientras que
C# siempre obliga a usar "break" o "goto" al final de cada cada caso (para evitar
errores provocados por una "break" olvidado) con la única excepción de que un
caso no haga absolutamente nada excepto dejar pasar el control al siguiente caso,
y en ese caso se puede dejar totalmente vacío:
// Ejemplo_02_01_09b.cs
// La orden "switch" (variante sin break)
// Introducción a C#, por Nacho Cabanes
using System;
switch (letra)
{
case ' ': Console.WriteLine("Espacio.");
break;
case '1':
case '2':
case '3':
case '4':
case '5':
Revisión 0.99zz – Página 61
Introducción a la programación con C#, por Nacho Cabanes
case '6':
case '7':
case '8':
case '9':
case '0': Console.WriteLine("Dígito.");
break;
default: Console.WriteLine("Ni espacio ni dígito.");
break;
}
}
}
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 cadenas de
texto ("strings").
Una cadena de texto, como veremos con más detalle en el próximo tema, se
declara con la palabra "string", se puede leer de teclado con ReadLine (sin
necesidad de convertir) y se le puede dar un valor desde programa si se indica
entre comillas dobles. Por ejemplo, un programa que nos salude de forma
personalizada si somos "Juan" o "Pedro" podría ser:
// Ejemplo_02_01_09c.cs
// La orden "switch" con cadenas de texto
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce tu nombre");
nombre = Console.ReadLine();
switch (nombre)
{
case "Juan": Console.WriteLine("Bienvenido, Juan.");
break;
case "Pedro": Console.WriteLine("Que tal estas, Pedro.");
break;
default: Console.WriteLine("Procede con cautela,
desconocido.");
break;
}
}
}
Ejercicios propuestos:
2.2.1. while
while (condición)
sentencia;
Un ejemplo que nos diga si cada número que tecleemos es positivo o negativo, y
que termine cuando tecleemos el número 0, podría ser:
Revisión 0.99zz – Página 63
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_02_02_01_01a.cs
// La orden "while": mientras...
// Introducción a C#, por Nacho Cabanes
using System;
while (numero != 0)
{
if (numero > 0) Console.WriteLine("Es positivo");
else Console.WriteLine("Es negativo");
Ejercicios propuestos:
(2.2.1.1.1) Crea un programa que pida al usuario su contraseña (numérica).
Deberá terminar cuando introduzca como contraseña el número 1111, pero
volvérsela a pedir tantas veces como sea necesario.
(2.2.1.1.2) Crea un "calculador de cuadrados": pedirá al usuario un número y
mostrará su cuadrado. Se repetirá mientras el número introducido no sea cero
(usa "while" para conseguirlo).
(2.2.1.1.3) Crea un programa que pida de forma repetitiva pares de números al
usuario. Tras introducir cada par de números, responderá si el primero es múltiplo
del segundo.
(2.2.1.1.4) Crea una versión mejorada del programa anterior, que, tras introducir
cada par de números, responderá si el primero es múltiplo del segundo, o el
segundo es múltiplo del primero, o ninguno de ellos es múltiplo del otro.
// Ejemplo_02_02_01_02a.cs
// Contar con "while"
// Introducción a C#, por Nacho Cabanes
using System;
while (n < 6)
{
Console.WriteLine(n);
n = n + 1;
}
}
}
Ejercicios propuestos:
(2.2.1.2.1) Crea un programa que escriba en pantalla los números del 1 al 10,
usando "while".
(2.2.1.2.2) Crea un programa que escriba en pantalla los números pares del 26 al
10 (descendiendo), usando "while".
(2.2.1.2.3) Crea un programa calcule cuantas cifras tiene un número entero
positivo (pista: se puede hacer dividiendo varias veces entre 10).
(2.2.1.2.4) Crea 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.
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:
// Ejemplo_02_02_02a.cs
// La orden "do..while" (repetir..mientras)
// Introducción a C#, por Nacho Cabanes
using System;
do
{
Console.Write("Introduzca su clave numérica: ");
clave = Convert.ToInt32(Console.ReadLine());
if (clave != valida)
Console.WriteLine("No válida!");
}
while (clave != valida);
Console.WriteLine("Aceptada.");
}
}
Como veremos con detalle un poco más adelante, si preferimos que la clave sea
un texto en vez de un número, los cambios al programa son mínimos, basta con
usar "string" e indicar su valor entre comillas dobles:
// Ejemplo_02_02_02b.cs
// La orden "do..while" (2)
// Introducción a C#, por Nacho Cabanes
using System;
string clave;
do
{
Console.Write("Introduzca su clave: ");
clave = Console.ReadLine();
if (clave != valida)
Console.WriteLine("No válida!");
}
while (clave != valida);
Console.WriteLine("Aceptada.");
}
}
Ejercicios propuestos:
(2.2.2.1) Crear un programa que pida números positivos al usuario, y vaya
calculando y mostrando la suma de todos ellos (terminará cuando se teclea un
número negativo o cero).
(2.2.2.2) Crea un programa que escriba en pantalla los números del 1 al 10,
usando "do..while".
(2.2.2.3) Crea un programa que escriba en pantalla los números pares del 26 al 10
(descendiendo), usando "do..while".
(2.2.2.4) Crea un programa que pida al usuario su identificador y su contraseña
(ambos numéricos), y no le permita seguir hasta que introduzca como
identificador "1234" y como contraseña "1111".
(2.2.2.5) Crea un programa que pida al usuario su identificador y su contraseña, y
no le permita seguir hasta que introduzca como nombre "Pedro" y como
contraseña "Peter".
2.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
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. Es muy habitual usar la
letra "i" como contador cuando se trata de tareas muy sencillas, así que el valor
inicial sería "i=1", la condición de repetición sería "i<=10" y el incremento sería
"i=i+1":
La orden para incrementar el valor de una variable ("i = i+1") se puede escribir de
la forma abreviada "i++", como veremos con más detalle en el próximo tema, de
modo que la forma habitual de crear el contador anterior sería
En general, será preferible usar nombres de variable más descriptivos que "i". Así,
un programa que escribiera los números del 1 al 10 podría ser:
// Ejemplo_02_02_03a.cs
// Uso básico de "for"
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(2.2.3.1) Crea un programa que muestre los números del 10 al 20, ambos
incluidos.
(2.2.3.2) Crea 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).
(2.2.3.1.3) Crea un programa que muestre los números del 100 al 200 (ambos
incluidos) que sean divisibles entre 7 y a la vez entre 3.
(2.2.3.4) Crea un programa que muestre la tabla de multiplicar del 9.
(2.2.3.5) Crea un programa que muestre los primeros ocho números pares: 2 4 6 8
10 12 14 16 (pista: en cada pasada habrá que aumentar de 2 en 2, o bien mostrar
el doble del valor que hace de contador).
(2.2.3.6) Crea un programa que muestre los números del 15 al 5, descendiendo
(pista: en cada pasada habrá que descontar 1, por ejemplo haciendo i=i-1, que se
puede abreviar i--).
for ( ; ; )
También se puede crear un bucle sin fin usando "while" y usando "do..while", si se
indica una condición que siempre vaya a ser cierta, como ésta:
while (1 == 1)
Ejercicios propuestos:
(2.2.4.1) Crea un programa que contenga un bucle sin fin que escriba "Hola " en
pantalla, sin avanzar de línea.
(2.2.4.2) Crea un programa que contenga un bucle sin fin que muestre los
números enteros positivos a partir del uno.
// Ejemplo_02_02_05a.cs
// "for" anidados
// Introducción a C#, por Nacho Cabanes
using System;
(Es decir: tenemos varias tablas, del 1 al 5, y para cada tabla queremos ver el
resultado que se obtiene al multiplicar por los números del 1 al 10).
Ejercicios propuestos:
(2.2.5.1) Crea un programa escriba 4 veces los números del 1 al 5, en una misma
línea, usando "for": 12345123451234512345.
(2.2.5.2) Crea un programa escriba 4 veces los números del 1 al 5, en una misma
línea, usando "while": 12345123451234512345.
(2.2.5.3) Crea un programa que, para los números entre el 10 y el 20 (ambos
incluidos) diga si son divisibles entre 5, si son divisibles entre 6 y si son divisibles
entre 7.
// Ejemplo_02_02_06a.cs
// "for" anidados (2)
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine();
}
}
Revisión 0.99zz – Página 70
Introducción a la programación con C#, por Nacho Cabanes
Ejercicios propuestos:
(2.2.6.1) Crea un programa que escriba 4 líneas de texto, cada una de las cuales
estará formada por los números del 1 al 5.
(2.2.6.2) Crea un programa que pida al usuario el ancho (por ejemplo, 4) y el alto
(por ejemplo, 3) y escriba un rectángulo formado por esa cantidad de asteriscos:
****
****
****
// Ejemplo_02_02_07a.cs
// "for" que usa "char"
// Introducción a C#, por Nacho Cabanes
using System;
// Ejemplo_02_02_07b.cs
// "for" que descuenta
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(2.2.7.1) Crea un programa que muestre las letras de la Z (mayúscula) a la A
(mayúscula, descendiendo).
(2.2.7.2) Crea un programa que muestre 5 veces las letras de la L (mayúscula) a la
N (mayúscula), en la misma línea.
// Ejemplo_02_02_08a.cs
// Reutilizacion incorrecta de la variable de un "for"
// Introducción a C#, por Nacho Cabanes
using System;
}
}
}
// Ejemplo_02_02_08b.cs
// Intento de reutilizacion incorrecta de la variable
// de un "for": no compila
// Introducción a C#, por Nacho Cabanes
using System;
Esta idea sea puede aplicar a cualquier fuente que contenga un "for". Por ejemplo,
el fuente 2.2.6a, que mostraba varias tablas de multiplicar, se podría reescribir de
forma más segura así:
// Ejemplo_02_02_08c.cs
// "for" anidados, variables en "for"
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine();
}
}
}
Ejercicios propuestos:
(2.2.8.1) Crea un programa que escriba 6 líneas de texto, cada una de las cuales
estará formada por los números del 1 al 7. Debes usar dos variables llamadas
"linea" y "numero", y ambas deben estar declaradas en el "for".
(2.2.8.2) Crea un programa que pida al usuario el ancho (por ejemplo, 4) y el alto
(por ejemplo, 3) y escriba un rectángulo formado por esa cantidad de asteriscos,
como en el ejercicio 2.2.6.2. Deberás usar las variables "ancho" y "alto" para los
datos que pidas al usuario, y las variables "filaActual" y "columnaActual"
(declaradas en el "for") para el bloque repetitivo.
Por ejemplo, el siguiente fuente puede parecer correcto, pero si lo miramos con
detenimiento, veremos que la orden "Console.WriteLine" del final, aunque esté
tabulada más a la derecha, no forma parte de ningún "for", de modo que no se
repite, y no se dejará ningún espacio en blanco entre una tabla de multiplicar y la
siguiente, sino que sólo se escribirá una línea en blanco al final, justo antes de
terminar el programa:
// Ejemplo_02_02_09a.cs
// "for" anidados de forma incorrecta, sin llaves
// Introducción a C#, por Nacho Cabanes
using System;
Por eso, una alternativa recomendable es incluir siempre las llaves, aunque
inicialmente esperemos repetir sólo una una orden:
Revisión 0.99zz – Página 74
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_02_02_09b.cs
// "for" anidados, variables en "for", llaves "redundantes"
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine();
}
}
}
Ejercicios propuestos:
(2.2.9.1) Crea un programa que pida un número al usuario y escriba los múltiplos
de 9 que haya entre 1 ese número. Debes usar llaves en todas las estructuras de
control, aunque sólo incluyan una sentencia.
(2.2.9.2) Crea un programa que pida al usuario dos números y escriba sus
divisores comunes. Debes usar llaves en todas las estructuras de control, aunque
sólo incluyan una sentencia.
// Ejemplo_02_02_10a.cs
// "for" interrumpido con "break"
// Introducción a C#, por Nacho Cabanes
using System;
}
}
}
1 2 3 4
Es una orden que se debe tratar de evitar, porque puede conducir a programas
difíciles de leer, en los que no se cumple la condición de repetición del bucle, sino
que se interrumpe por otros criterios. Como norma general, es preferible
reescribir la condición del bucle de otra forma. En el ejemplo anterior, bastaría que
la condición fuera "contador < 5". Un ejemplo ligeramente más complejo podría
ser mostrar los números del 105 al 120 hasta encontrar uno que sea múltiplo de
13, que no se mostrará. Lo podríamos hacer de esta forma (poco correcta):
// Ejemplo_02_02_10b.cs
// "for" interrumpido con "break" (2)
// Introducción a C#, por Nacho Cabanes
using System;
// Ejemplo_02_02_10c.cs
// alternativa a un "for" interrumpido con "break"
// Introducción a C#, por Nacho Cabanes
using System;
int contador=105;
Ejercicios propuestos:
(2.2.10.1) Crea un programa que pida al usuario dos números y escriba su máximo
común divisor (pista: una solución lenta pero sencilla es probar con un "for" todos
los números descendiendo a partir del menor de ambos, hasta llegar a 1; cuando
encuentres un número que sea divisor de ambos, interrumpes la búsqueda).
(2.2.10.2) Crea un programa que pida al usuario dos números y escriba su mínimo
común múltiplo (pista: una solución lenta pero sencilla es probar con un "for"
todos los números a partir del mayor de ambos, de forma creciente; cuando
encuentres un número que sea múltiplo de ambos, interrumpes la búsqueda).
// Ejemplo_02_02_11a.cs
// "for" interrumpido con "continue"
// Introducción a C#, por Nacho Cabanes
using System;
1 2 3 4 6 7 8 9 10
// Ejemplo_02_02_11b.cs
// Alternativa a "for" interrumpido con "continue"
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(2.2.11.1) Crea un programa que escriba los números del 20 al 10, descendiendo,
excepto el 13, usando "continue".
(2.2.11.2) Crea un programa que escriba los números pares del 2 al 106, excepto
los que sean múltiplos de 10, usando "continue".
// Ejemplo_02_02_12a.cs
// "for" y "while" equivalente
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine();
int n=1;
Revisión 0.99zz – Página 78
Introducción a la programación con C#, por Nacho Cabanes
while (n<=10)
{
Console.Write("{0} ", n);
n++;
}
}
}
// Ejemplo_02_02_12b.cs
// "for" y "while" equivalente... con "continue"
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine();
int n=1;
while (n<=10)
{
if (n == 5)
continue;
Console.Write("{0} ", n);
n++;
}
}
}
En este caso, el "for" muestra todos los valores menos el 5, pero en el "while" se
provoca un bucle sin fin y el programa se queda "colgado" tras escribir el número
4, porque cuando se llega al número 5, la orden "continue" hace que dicho valor
no se escriba, pero que tampoco se incremente la variable, de modo que nunca se
llega a pasar del 5.
Ejercicios propuestos:
(2.2.12.1) Crea un programa que escriba los números del 100 al 200, separados
por un espacio, sin avanzar de línea, usando "for". En la siguiente línea, vuelve a
escribirlos usando "while".
Revisión 0.99zz – Página 79
Introducción a la programación con C#, por Nacho Cabanes
(2.2.12.2) Crea un programa que escriba los números pares del 20 al 10,
descendiendo, excepto el 14, primero con "for" y luego con "while".
Respuesta: escribe 5, porque no hay llaves tras el "for", luego sólo se repite
la orden "if".
El formato de "goto" es
goto donde;
donde:
// Ejemplo_02_03a.cs
// "for" y "goto"
// Introducción a C#, por Nacho Cabanes
using System;
salida:
Console.Write("Fin del programa");
}
}
i vale 0 y j vale 0.
i vale 0 y j vale 2.
i vale 0 y j vale 4.
i vale 0 y j vale 6.
i vale 0 y j vale 8.
i vale 0 y j vale 10.
i vale 0 y j vale 12.
i vale 0 y j vale 14.
i vale 0 y j vale 16.
i vale 0 y j vale 18.
i vale 0 y j vale 20.
i vale 1 y j vale 0.
i vale 1 y j vale 2.
i vale 1 y j vale 4.
i vale 1 y j vale 6.
Fin del programa
Ejercicios propuestos:
(2.3.1) Crea un programa que escriba los números del 1 al 10, separados por un
espacio, sin avanzar de línea. No puedes usar "for", ni "while", ni "do..while", sólo
"if" y "goto".
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):
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 estos diagramas,
se representa cada orden dentro de una caja:
Pedir número n1
Pedir número n2
n1>n2?
si no
Decir "n1 es Decir "n2 es
mayor" mayor"
Abrir fichero
Mientras haya datos en fichero
Leer dato
Mostrar dato
Cerrar fichero
2.5. 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 para extraer los
datos de uno en uno. De momento, el único dato compuesto que hemos visto (y
todavía con muy poco detalle) es la cadena de texto, "string", de la que podríamos
obtener las letras una a una con "foreach" así:
// Ejemplo_02_05a.cs
// Primer ejemplo de "foreach"
// Introducción a C#, por Nacho Cabanes
Revisión 0.99zz – Página 85
Introducción a la programación con C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(2.5.1) Crea un programa que cuente cuantas veces aparece la letra 'a' en una
frase que teclee el usuario, utilizando "foreach".
Ejercicios propuestos:
(2.6.1) 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.
(2.6.2) 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
Revisión 0.99zz – Página 86
Introducción a la programación con C#, por Nacho Cabanes
Precio? 44
Pagado? 100
Su cambio es de 56: 50 5 1
Precio? 1
Pagado? 100
Su cambio es de 99: 50 20 20 5 2 2
(2.6.9) Crea un programa que "dibuje" un cuadrado formado por cifras sucesivas,
con el tamaño que indique el usuario, hasta un máximo de 9. Por ejemplo, si desea
tamaño 5, el cuadrado sería así:
11111
Revisión 0.99zz – Página 87
Introducción a la programación con C#, por Nacho Cabanes
22222
33333
44444
55555
Lo veremos más adelante con más detalle, cuando nuestros programas sean más
complejos, especialmente en el manejo de ficheros, pero podemos acercarnos
con un primer ejemplo, que intente dividir dos números, e intercepte los posibles
errores:
// Ejemplo_02_07a.cs
// Excepciones (1)
// Introducción a C#, por Nacho Cabanes
using System;
try
{
Console.WriteLine("Introduzca el primer numero");
numero1 = Convert.ToInt32( Console.ReadLine() );
Una alternativa más elegante es no "atrapar" todos los posibles errores a la vez,
sino uno por uno (con varias sentencias "catch"), para poder tomar distintas
acciones, o al menos dar mensajes de error más detallados, así:
// Ejemplo_02_07b.cs
// Excepciones (2)
// Introducción a C#, por Nacho Cabanes
using System;
{
int numero1, numero2, resultado;
try
{
Console.WriteLine("Introduzca el primer numero");
numero1 = Convert.ToInt32( Console.ReadLine() );
catch (FormatException)
{
Console.WriteLine("No es un número válido");
}
catch (DivideByZeroException)
{
Console.WriteLine("No se puede dividir entre cero");
}
}
}
Como se ve en este ejemplo, si no vamos a usar detalles adicionales del error que
ha afectado al programa, no necesitamos declarar ninguna variable de tipo
Exception: nos basta con construcciones como "catch (FormatException)" en vez de
"catch (FormatException e)".
Ejercicios propuestos:
(2.7.1) Crea un programa que pregunte al usuario su edad y su año de nacimiento.
Si la edad que introduce no es un número válido, mostrará un mensaje de aviso.
Lo mismo ocurrirá si el año de nacimiento no es un número válido.
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".
// Ejemplo_03_01_01a.cs
// Tipos de números enteros
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(3.1.1.1) Calcula el producto de 1.000.000 por 1.000.000, usando una variable
llamada "producto", de tipo "long". Prueba también a calcularlo usando una
variable de tipo "int".
// Ejemplo_03_01_02a.cs
// Conversiones para otros tipos de números enteros
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(3.1.2.1) Pregunta al usuario su edad, que se guardará en un "byte". A
continuación, le deberás decir que no aparenta tantos años (por ejemplo, "No
aparentas 20 años").
(3.1.2.2) Pide al usuario dos números de dos cifras ("byte"), calcula su
multiplicación, que se deberá guardar en un "ushort", y muestra el resultado en
pantalla.
(3.1.2.3) Pide al usuario dos números enteros largos ("long") y muestra su suma,
su resta y su producto.
a = a + 1;
Pues bien, en C# (y en otros lenguajes que derivan de C, como C++, Java y PHP),
existe una notación más compacta para esta operación, y para la opuesta (el
decremento):
// Ejemplo_03_01_03a.cs
// Incremento y decremento
// Introducción a C#, por Nacho Cabanes
using System;
Pero esto tiene algo más de dificultad de la 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;
Ejercicios propuestos:
(3.1.3.1) Crea 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.
(3.1.3.2) ¿Cuál sería el resultado de las siguientes operaciones? a=5; b=++a; c=a++;
b=b*5; a=a*2; Calcúlalo a mano y luego crea un programa que lo resuelva, para
ver si habías hallado la solución correcta.
Revisión 0.99zz – Página 95
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_03_01_04a.cs
// Operaciones abreviadas
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(3.1.4.1) Crea un programa que use tres variables x,y,z. Sus valores iniciales serán
15, -10, 214. Deberás 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.
(3.1.4.2) ¿Cuál sería el resultado de las siguientes operaciones? a=5; b=a+2; b-=3;
c=-3; c*=2; ++c; a*=b; Crea un programa que te lo muestre.
a = b = c = 1;
// Ejemplo_03_01_05a.cs
// Asignaciones múltiples
// Introducción a C#, por Nacho Cabanes
using System;
// Ejemplo_03_02_01a.cs
// Números reales (1: float)
// Introducción a C#, por Nacho Cabanes
Revisión 0.99zz – Página 97
Introducción a la programación con C#, por Nacho Cabanes
using System;
float f1 = 2, f2 = 3;
float divisionF;
Ejercicios propuestos:
(3.2.1.1) Crea un programa que muestre el resultado de dividir 3 entre 4 usando
números enteros y luego usando números de coma flotante.
(3.2.1.2) ¿Cuál sería el resultado de las siguientes operaciones, usando números
reales? a=5; a/=2; a+=1; a*=3; --a;
// Ejemplo_03_02_02a.cs
// Números reales (2: double)
// Introducción a C#, por Nacho Cabanes
using System;
float x = 2;
Otra forma alternativa es forzar una "conversión de tipos", como veremos dentro
de muy poco.
Así, podemos crear un programa que pida al usuario el radio de una circunferencia
(que será un número entero) para mostrar la longitud de la circunferencia (cuyo
valor será 2 * PI * radio) podría ser:
// Ejemplo_03_02_02a.cs
// Números reales: valor inicial de un float
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce el radio");
radio = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("La longitud de la circunferencia es");
Console.WriteLine(2 * pi * radio);
}
}
Ejercicios propuestos:
(3.2.2.1) Crea un programa que muestre el resultado de dividir 13 entre 6 usando
números enteros, luego usando números de coma flotante de simple precisión y
luego con números de doble precisión.
(3.2.2.2) Calcula el área de un círculo, dado su radio, que será un número entero
(área = pi * radio al cuadrado)
// Ejemplo_03_02_03a.cs
Revisión 0.99zz – Página 100
Introducción a la programación con C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(3.2.3.1) Calcula el volumen de una esfera, dado su radio, que será un número de
doble precisión (volumen = pi * radio al cubo * 4/3)
(3.2.3.2) Crea un programa que pida al usuario a una distancia (en metros) y el
tiempo necesario para recorrerla (como tres números: horas, minutos, segundos),
y muestre la velocidad, en metros por segundo, en kilómetros por hora y en millas
por hora (pista: 1 milla = 1.609 metros).
(3.2.3.3) Halla las soluciones de una ecuación de segundo grado del tipo y = Ax 2 +
Bx + C. Pista: la raíz cuadrada de un número x se calcula con Math.Sqrt(x)
(3.2.3.4) Si se ingresan E euros en el banco a un cierto interés I durante N años, el
dinero obtenido viene dado por la fórmula del interés compuesto: Resultado = e
Revisión 0.99zz – Página 101
Introducción a la programación con C#, por Nacho Cabanes
(1+ i)n Aplicarlo para calcular en cuanto se convierten 1.000 euros al cabo de 10
años al 3% de interés anual.
(3.2.3.5) Crea un programa que muestre los primeros 20 valores de la función y =
x2 - 1
(3.2.3.6) Crea un programa que "dibuje" la gráfica de y = (x-5)2 para valores de x
entre 1 y 10. Deberá hacerlo dibujando varios espacios en pantalla y luego un
asterisco. La cantidad de espacios dependerá del valor obtenido para "y".
(3.2.3.7) Escribe un programa que calcule una aproximación de PI mediante la
expresión: pi/4 = 1/1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + 1/13 ... El usuario deberá
indicar la cantidad de términos a utilizar, y el programa mostrará todos los
resultados hasta esa cantidad de términos. Debes hacer todas las operacion con
"double".
// Ejemplo_03_02_04a.cs
// Números reales: typecast
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Introduce el radio");
radio = Convert.ToDouble(Console.ReadLine());
longitud = 2 * pi * radio;
Revisión 0.99zz – Página 102
Introducción a la programación con C#, por Nacho Cabanes
Su resultado sería:
Introduce el radio
2,3456789
La longitud de la circunferencia es
14,7383356099727
Y con simple precisión
14,73834
Y como número entero
14
Ejercicios propuestos:
(3.2.4.1) Crea un programa que calcule la raíz cuadrada del número que
introduzca el usuario. La raíz se deberá calcular como "double", pero el resultado
se mostrará como "float"
(3.2.4.2) Crea una nueva versión del un programa que calcula una aproximación
de PI mediante la expresión: pi/4 = 1/1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + 1/13 (...) con
tantos términos como indique el usuario. Debes hacer todas las operacion con
"double", pero mostrar el resultado como "float".
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")
// Ejemplo_03_02_05a.cs
// Formato de números reales
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine( numero.ToString("N1") );
Console.WriteLine( numero.ToString("N3") );
Console.WriteLine( numero.ToString("0.0") );
Console.WriteLine( numero.ToString("0.000") );
Console.WriteLine( numero.ToString("#.#") );
Console.WriteLine( numero.ToString("#.###") );
}
}
12,3
12,340
12,3
12,340
12,3
12,34
Ejercicios propuestos:
(3.2.5.1) 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.
(3.2.5.2) Crea un programa que use tres variables x,y,z. Las tres serán números
reales, y nos bastará con dos cifras decimales. Se deberá pedir al usuario los
valores para las tres variables y mostrar en pantalla el valor de x 2 + y - z (con
exactamente dos cifras decimales).
(3.2.5.3) Calcula el perímetro, área y diagonal de un rectángulo, a partir de su
ancho y alto (perímetro = suma de los cuatro lados; área = base x altura; diagonal,
usando el teorema de Pitágoras). Muestra todos ellos con una cifra decimal.
(3.2.5.4) Calcula la superficie y el volumen de una esfera, a partir de su radio
(superficie = 4 * pi * radio al cuadrado; volumen = 4/3 * pi * radio al cubo). Usa
datos "doble" y muestra los resultados con 5 cifras decimales.
// Ejemplo_03_02_06a.cs
// De decimal a hexadecimal y binario
// Introducción a C#, por Nacho Cabanes
using System;
Su resultado sería:
f7
11110111
Revisión 0.99zz – Página 105
Introducción a la programación con C#, por Nacho Cabanes
(Si quieres saber más sobre el sistema hexadecimal, mira los apéndices al final de
este texto)
Ejercicios propuestos:
(3.2.6.1) Crea un programa que pida números (en base 10) al usuario y muestre su
equivalente en sistema binario y en hexadecimal. Debe repetirse hasta que el
usuario introduzca el número 0.
(3.2.6.2) Crea un programa que pida al usuario la cantidad de rojo (por ejemplo,
255), verde (por ejemplo, 160) y azul (por ejemplo, 0) que tiene un color, y que
muestre ese color RGB en notación hexadecimal (por ejemplo, FFA000).
(3.2.6.3) Crea un programa para mostrar los números del 0 a 255 en hexadecimal,
en 16 filas de 16 columnas cada una (la primera fila contendrá los números del 0 al
15 –decimal-, la segunda del 16 al 31 –decimal- y así sucesivamente).
// Ejemplo_03_02_06b.cs
// De hexadecimal y binario a decimal
// Introducción a C#, por Nacho Cabanes
using System;
Que mostraría:
19 26 13 12 201
Ejercicios propuestos:
(3.2.6.4) Crea un programa que pida números binarios al usuario y muestre su
equivalente en sistema hexadecimal y en decimal. Debe repetirse hasta que el
usuario introduzca la palabra "fin".
char letra;
letra = 'a';
Para leer valores desde teclado, lo podemos hacer de forma similar a los casos
anteriores: leemos toda una frase (que debería tener sólo una letra) 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:
// Ejemplo_03_03_01a.cs
// Tipo de datos "char"
// Introducción a C#, por Nacho Cabanes
using System;
letra = 'a';
Console.WriteLine("La letra es {0}", letra);
Revisión 0.99zz – Página 107
Introducción a la programación con C#, por Nacho Cabanes
Ejercicios propuestos
(3.3.1.1) Crea un programa que pida una letra al usuario y diga si se trata de una
vocal.
(3.3.1.2) Crea un programa que muestre una de cada dos letras entre la que teclee
el usuario y la "z". Por ejemplo, si el usuario introduce una "a", se escribirá
"aceg...".
(3.3.1.3) Crea un programa que pida al usuario el ancho (por ejemplo, 4) y el alto
(por ejemplo, 3) y una letra (por ejemplo, X) y escriba un rectángulo formado por
esa cantidad de letras:
XXXX
XXXX
XXXX
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
Revisión 0.99zz – Página 108
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_03_03_02a.cs
// Secuencias de escape
// Introducción a C#, por Nacho Cabanes
using System;
Juguemos mas:
otro salto
Comillas dobles: " y simples ', y barra \
ruta = @"c:\datos\ejemplos\curso\ejemplo1"
Ejercicio propuesto
(3.3.2.1) Crea 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".
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:
// Ejemplo_03_04a.cs
// Uso basico de "string"
// Introducción a C#, por Nacho Cabanes
using System;
if (frase == "Hola!")
Console.WriteLine("Hola a ti también! ");
}
}
Ejercicios propuestos:
(3.4.1) 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.4.2) Crear un programa que pida al usuario un nombre y una contraseña. La
contraseña se debe introducir dos veces. Si las dos contraseñas no son iguales, se
avisará al usuario y se le volverán a pedir las dos contraseñas.
bool encontrado;
encontrado = true;
Este tipo de datos hará que podamos escribir de forma sencilla algunas
condiciones que podrían resultar complejas. Así podemos hacer 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" también se le puede dar como valor el resultado de una
comparación:
// Ejemplo básico
partidaTerminada = false;
if (vidas == 0) partidaTerminada = true;
// Notación alternativa, sin usar "if"
partidaTerminada = vidas == 0;
// Ejemplo_03_05a.cs
// Variables bool
// Introducción a C#, por Nacho Cabanes
using System;
if (esCifra)
Console.WriteLine("Es una cifra numérica.");
else if (esVocal)
Console.WriteLine("Es una vocal.");
else
Console.WriteLine("Es una consonante u otro símbolo.");
}
}
Ejercicios propuestos:
(3.5.1) Crea 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.
(3.5.2) Crea una versión alternativa del ejercicio 3.5.1, que use "if" en vez del
operador condicional.
(3.5.3) Crea un programa que use el operador condicional para dar a una variable
llamada "ambosPares" (booleana) el valor "true" si dos números introducidos por
el usuario son pares, o "false" si alguno es impar.
(3.5.4) Crea una versión alternativa del ejercicio 3.5.3, que use "if" en vez del
operador condicional.
int[] ejemplo;
Cuando sepamos cuantos datos vamos a guardar (por ejemplo 4), podremos
reservar espacio con la orden "new", así:
ejemplo[0] = 15;
// Ejemplo_04_01_01a.cs
// Primer ejemplo de tablas (arrays)
// Introducción a C#, por Nacho Cabanes
Revisión 0.99zz – Página 113
Introducción a la programación con C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.1.1.1) Un programa que pida al usuario 4 números, los memorice (utilizando un
array), calcule su media aritmética y después muestre en pantalla la media y los
datos tecleados.
(4.1.1.2) Un programa que pida al usuario 5 números reales (pista: necesitarás un
array de "float") y luego los muestre en el orden contrario al que se introdujeron.
// Ejemplo_04_01_02a.cs
// Segundo ejemplo de tablas: valores iniciales con []
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.1.2.1) Un programa que 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.
// Ejemplo_04_01_03a.cs
// Tercer ejemplo de tablas: valores iniciales con llaves
// y recorrido con "for"
// Introducción a C#, por Nacho Cabanes
using System;
Revisión 0.99zz – Página 115
Introducción a la programación con C#, por Nacho Cabanes
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 sería evidente.
// Ejemplo_04_01_03b.cs
// Cuarto ejemplo de tablas: introducir datos repetitvos
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.1.3.1) Crea un programa que pida al usuario 6 números enteros cortos y luego
los muestre en orden inverso (pista: usa un array para almacenarlos y "for" para
mostrarlos).
(4.1.3.2) Crea un programa que pregunte al usuario cuántos números enteros va a
introducir (por ejemplo, 10), le pida todos esos números, los guarde en un array y
finalmente calcule y muestre la media de esos números.
(4.1.3.3) Un programa que pida al usuario 10 reales de doble precisión, calcule su
media y luego muestre los que están por encima de la media.
(4.1.3.4) Un programa que almacene en una tabla el número de días que tiene
cada mes (de un año no bisiesto), pida al usuario que le indique un mes (ej. 2 para
febrero) y un día (ej. el día 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.5) A partir del ejercicio anterior, crea otro que pida al usuario que le indique
la fecha, formada por día (1 al 31) y el mes (1=enero, 12=diciembre), y como
respuesta muestre en pantalla el número de días que quedan hasta final de año.
(4.1.3.6) 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".
(4.1.3.7) Un programa que prepare espacio para guardar 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.1.3.8) Un programa que reserve espacio para un vector de 3 componentes, pida
al usuario valores para dichas componentes (por ejemplo [2, -5, 7]) y muestre su
módulo (raíz cuadrada de la suma de sus componentes al cuadrado).
(4.1.3.9) Un programa que reserve espacio para dos vectores de 3 componentes,
pida al usuario sus valores y calcule la suma de ambos vectores (su primera
componente será x1+y1, la segunda será x2+y2 y así sucesivamente).
(4.1.3.10) Un programa que reserve espacio para dos vectores de 3 componentes,
pida al usuario sus valores y calcule su producto escalar (x1·y1+ x2·y2+x3·y3).
(4.1.3.11) Un programa que pida al usuario 4 números enteros y calcule (y
muestre) cuál es el mayor de ellos. Nota: para calcular el mayor valor de un array,
hay que comparar cada uno de los valores que tiene almacenados el array con el
que hasta ese momento es el máximo provisional. El valor inicial de este máximo
provisional no debería ser cero (porque el resultado sería incorrecto si todos los
números son negativos), sino el primer elemento del array.
Para ver si un dato existe, habrá que recorrer todo el array, comparando el valor
almacenado con el dato que se busca. Puede interesarnos simplemente saber si
está o no (con lo que se podría interrumpir la búsqueda en cuanto aparezca una
primera vez) o ver en qué posiciones se encuentra (para lo que habría que recorrer
todo el array). Si el array estuviera ordenado, se podría buscar de una forma más
rápida, pero la veremos más adelante.
Para poder añadir un dato al final de los ya existentes, necesitamos que el array
no esté completamente lleno, y llevar un contador de cuántas posiciones hay
ocupadas, de modo que seamos capaces de guardar el dato en la primera posición
libre.
Para insertar un dato en una cierta posición, los que queden detrás deberán
desplazarse "hacia la derecha" para dejarle hueco. Este movimiento debe empezar
desde el final para que cada dato que se mueve no destruya el que estaba a
continuación de él. También habrá que actualizar el contador, para indicar que
queda una posición libre menos.
Si se quiere borrar el dato que hay en una cierta posición, los que estaban a
continuación deberán desplazarse "hacia la izquierda" para que no queden
huecos. Como en el caso anterior, habrá que actualizar el contador, pero ahora
para indicar que queda una posición libre más.
// Ejemplo_04_01_04a.cs
Revisión 0.99zz – Página 118
Introducción a la programación con C#, por Nacho Cabanes
using System;
// Mostramos el array
for (i=0; i<cantidad; i++)
Console.Write("{0} ",datos[i]);
Console.WriteLine();
// Buscamos el máximo
int maximo = datos[0];
for (i=1; i<cantidad; i++)
if (datos[i] > maximo)
maximo = datos[i];
Console.WriteLine("El máximo es {0} ", maximo);
{
Console.WriteLine("Insertando 30 en la posición 3");
int posicionInsertar = 2;
for (i=cantidad; i>posicionInsertar; i--)
datos[i] = datos[i-1];
datos[posicionInsertar] = 30;
cantidad++;
}
10 15 12
15 encontrado en la posición 2
El máximo es 15
Añadiendo 6 al final
10 15 12 6
Borrando el segundo dato
10 12 6
Insertando 30 en la posición 3
10 12 30 6
Este programa "no dice nada" cuando no se encuentra el dato que se está
buscando. Se puede mejorar usando una variable "booleana" que nos sirva de
testigo, de forma que al final nos avise si el dato no existía (no sirve emplear un
"else", porque en cada pasada del bucle "for" no sabemos si el dato no existe, sólo
sabemos que no está en la posición actual).
Ejercicios propuestos:
(4.1.4.1) Crea una variante del ejemplo anterior (04_01_04a) que pida al usuario el
dato a buscar, avise si ese dato no aparece, y que diga cuántas veces se ha
encontrado en caso contrario.
(4.1.4.2) Crea una variante del ejemplo anterior (04_01_04a) que añada un dato
introducido por el usuario al final de los datos existentes.
(4.1.4.3) Crea una variante del ejemplo anterior (04_01_04a) que inserte un dato
introducido por el usuario en la posición que elija el usuario. Debe avisar si la
posición escogida es incorrecta (porque esté más allá del final de los datos).
(4.1.4.4) Crea una variante del ejemplo anterior (04_01_04a) que borre el dato que
se encuentre en la posición que elija el usuario. Debe avisar si la posición escogida
no es válida.
4.1.5. Constantes
En ocasiones, manejaremos valores que realmente no van a variar. Es el caso del
tamaño máximo de un array. En esos casos, por legibilidad y por facilidad de
mantenimiento del programa, puede ser preferible no usar el valor numérico, sino
una variable. Dado que el valor de ésta no debería cambiar, podemos usar la
palabra "const" para indicar que debe ser constante, y el compilador no permitirá
que la modifiquemos por error. Además, por convenio, para que sea fácil
distinguir una constante de una variable, se suele escribir su nombre totalmente
en mayúsculas:
Así, una nueva versión del fuente del apartado 4.1.3 (b), usando una constante
para la capacidad del array podría ser:
// Ejemplo_04_01_05a.cs
// Quinto ejemplo de tablas: constantes
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.1.5.1) Crea un programa que contenga un array con los nombres de los meses
del año. El usuario podrá elegir entre verlos en orden natural (de Enero a
Diciembre) o en orden inverso (de Diciembre a Enero). Usa constantes para el
valor máximo del array en ambos recorridos.
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.
// Ejemplo_04_02a.cs
// Array de dos dimensiones "rectangulares" (estilo Pascal)
// Introducción a C#, por Nacho Cabanes
using System;
Este tipo de tablas de varias dimensiones son las que se usan también para
guardar matrices, cuando se trata de resolver problemas matemáticos más
complejos que los que hemos visto hasta ahora. Si ya has estudiado la teoría de
matrices, más adelante tienes algunos ejercicios propuestos para aplicar esos
conocimientos al uso de arrays bidimensionales.
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":
// Ejemplo_04_02b.cs
// Array de arrays (array de dos dimensiones al estilo C)
// Introducción a C#, por Nacho Cabanes
Revisión 0.99zz – Página 123
Introducción a la programación con C#, por Nacho Cabanes
using System;
} // Fin de "Main"
}
0123456789
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:
(4.2.1) Un programa que pida 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.
(4.2.2) Un programa que pida 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.2.3) Un programa que pregunte al usuario el tamaño que tendrán dos bloques
de números enteros (por ejemplo, uno de 10 elementos y otro de 12). Luego
Revisión 0.99zz – Página 124
Introducción a la programación con C#, por Nacho Cabanes
pedirá los datos para ambos bloques de datos. Finalmente deberá mostrar el
mayor dato que se ha introducido en cada uno de ellos.
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 siempre 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:
// Ejemplo_04_03_01a.cs
// Registros (struct)
// Introducción a C#, por Nacho Cabanes
using System;
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);
}
}
Ejercicios propuestos:
(4.3.1.1) Crea 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.
// Ejemplo_04_03_02a.cs
// Array de struct
// Introducción a C#, por Nacho Cabanes
using System;
personas[0].nombre = "Juan";
personas[0].inicial = 'J';
personas[0].edad = 20;
personas[0].nota = 7.5f;
Console.WriteLine("La edad de {0} es {1}",
Revisión 0.99zz – Página 126
Introducción a la programación con C#, por Nacho Cabanes
personas[0].nombre, personas[0].edad);
personas[1].nombre = "Pedro";
Console.WriteLine("La edad de {0} es {1}",
personas[1].nombre, personas[1].edad);
}
}
La edad de Juan es 20
La edad de Pedro es 0
Ejercicios propuestos:
(4.3.2.1) Amplia el programa del ejercicio 4.3.1.1, para que almacene datos de
hasta 100 canciones. Deberá tener un menú que permita las opciones: añadir una
nueva canción, mostrar el título de todas las canciones, buscar la canción que
contenga un cierto texto (en el artista o en el título).
(4.3.2.2) Crea 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.
// Ejemplo_04_03_03a.cs
// Registros anidados
// Introducción a C#, por Nacho Cabanes
using System;
struct tipoPersona
{
public string nombre;
public char inicial;
public fechaNacimiento diaDeNacimiento;
public float nota;
}
persona.nombre = "Juan";
persona.inicial = 'J';
persona.diaDeNacimiento.dia = 15;
persona.diaDeNacimiento.mes = 9;
persona.nota = 7.5f;
Console.WriteLine("{0} nació en el mes {1}",
persona.nombre, persona.diaDeNacimiento.mes);
}
}
Ejercicios propuestos:
(4.3.3.1) Amplia el programa 4.3.2.1, para que el campo "duración" se almacene
como minutos y segundos, usando un "struct" anidado que contenga a su vez
estos dos campos.
Vamos a repasar todas esas posibilidades, junto con la de formar una cadena a
partir de otras si las unimos con el símbolo de la suma (+), lo que llamaremos
"concatenar" cadenas. Un ejemplo que nos pidiese nuestro nombre y nos
saludase usando todo ello podría ser:
// Ejemplo_04_04_01a.cs
// Cadenas de texto (1: manejo básico)
// Introducción a C#, por Nacho Cabanes
using System;
if (nombre == "Alberto")
Console.WriteLine("Dices que eres Alberto?");
else
Console.WriteLine("Así que no eres Alberto?");
Ejercicios propuestos:
(4.4.1.1) Crea un programa que te pida tu nombre y lo escriba 5 veces.
(4.4.1.2) Crea un programa que pida al usuario su nombre. Si se llama como tú
(por ejemplo, "Nacho"), responderá "Bienvenido, jefe". En caso contrario, le
saludará por su nombre.
(4.4.1.3) 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".
(4.4.1.4) Crea un programa que pida al usuario dos números enteros y después
una operación que realizar con ellos. La operación podrá ser "suma", "resta",
multiplicación" y "división", que también se podrán escribir de forma abreviado
con los operadores matemáticos "+", "-", "*" y "/". Para multiplicar también se
podrá usar una "x", minúscula o mayúscula. A continuación se mostrará el
resultado de esa operación (por ejemplo, si los números son 3 y 6 y la operación es
"suma", el resultado sería 9). La operación debes tomarla como una cadena de
texto y analizarla con un "switch".
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.
// Ejemplo_04_04_02a.cs
// Cadenas de texto (2: acceder a una letra)
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.4.2.1) Crea un programa que pregunte al usuario su nombre y le responda cuál
es su inicial.
// Ejemplo_04_04_03a.cs
// Cadenas de texto (3: longitud)
// Introducción a C#, por Nacho Cabanes
using System;
{
Console.WriteLine("La letra {0} es {1}", i, saludo[i]);
}
}
}
Ejercicios propuestos:
(4.4.3.1) Un programa que 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.3.2) Un programa que pida una frase al usuario y la muestra en orden inverso
(de la última letra a la primera).
(4.4.3.3) Un programa que pida al usuario una frase, después una letra y
finalmente diga si aparece esa letra como parte de esa frase o no.
(4.4.3.4) Un programa capaz de sumar dos números enteros muy grandes (por
ejemplo, de 30 cifras), que se deberán pedir como cadena de texto y analizar letra
a letra.
(4.4.3.5) Un programa capaz de multiplicar dos números enteros muy grandes
(por ejemplo, de 30 cifras), que se deberán pedir como cadena de texto y analizar
letra a letra.
saludo = frase.Substring(0,4);
// Ejemplo_04_04_04a.cs
// Cadenas de texto (4: substring)
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.4.4.1) Un programa que te pida tu nombre y lo muestre en pantalla separando
cada letra de la siguiente con un espacio, similar al 4.4.3.1, pero esta vez usando
"Substring". Por ejemplo, si tu nombre es "Juan", debería aparecer en pantalla "J u
a n".
(4.4.4.2) Un programa que te pida tu nombre y lo muestre en pantalla como un
triángulo creciente. Por ejemplo, si tu nombre es "Juan", debería aparecer en
pantalla:
J
Ju
Jua
Juan
De forma similar, LastIndexOf ("última posición de") indica la última aparición (es
decir, busca de derecha a izquierda).
Si solamente queremos ver si aparece, pero no nos importa en qué posición está,
nos bastará con usar "Contains":
if (nombre.Contains("Juan")) ...
// Ejemplo_04_04_05a.cs
// Cadenas de texto (5: substring)
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.4.5.1) Un programa que pida al usuario 10 frases, las guarde en un array, y
luego le pregunte textos de forma repetitiva, e indique si cada uno de esos textos
aparece como parte de alguno de los elementos del array. Terminará cuando el
texto introducido sea "fin".
(4.4.5.2) Crea una versión del ejercicio 4.4.5.1 en la que, en caso de que alguno de
los textos aparezca como subcadena, se avise además si se encuentra
exactamente al principio.
// Ejemplo_04_04_06a.cs
// Cadenas de texto (6: manipulaciones diversas)
// Introducción a C#, por Nacho Cabanes
using System;
Y su resultado sería
Ejercicios propuestos:
(4.4.6.1) Una variante del ejercicio 4.4.5.2, que no distinga entre mayúsculas y
minúsculas a la hora de buscar.
(4.4.6.2) Un programa que pida al usuario una frase y elimine todos los espacios
redundantes que contenga (debe quedar sólo un espacio entre cada palabra y la
siguiente).
Afortunadamente, C# nos permite hacerlo con Split, que crea un array a partir de
los fragmentos de la cadenam usando el separador que le indiquemos, así:
// Ejemplo_04_04_07a.cs
// Cadenas de texto: partir con "Split"
// Introducción a C#, por Nacho Cabanes
using System;
}
}
Fragmento 0 = uno
Fragmento 1 = dos
Fragmento 2 = tres
Fragmento 3 = cuatro
// Ejemplo_04_04_07b.cs
// Cadenas de texto: partir con "Split" - 2
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.4.7.1) Un programa que pida al usuario una frase y muestre sus palabras en
orden inverso.
(4.4.7.2) Un programa que pida al usuario varios números separados por espacios
y muestre su suma.
if (frase.CompareTo("hola") > 0)
Console.WriteLine("La frase es mayor que hola");
// Ejemplo_04_04_08a.cs
// Cadenas de texto y comparación alfabética
// Introducción a C#, por Nacho Cabanes
using System;
string frase;
Si tecleamos una palabra como "gol", que comienza por G, que alfabéticamente
está antes de la H de "hola", se nos dirá que esa palabra es menor:
Si escribimos "hOLa", que coincide 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:
Ejercicios propuestos:
(4.4.8.1) Un programa que pida al usuario dos frases y diga cual sería la "mayor"
de ellas (la que aparecería en último lugar en un diccionario).
(4.4.8.2) Un programa que pida al usuario cinco frases, las guarde en un array y
muestre la "mayor" de ellas.
que hacíamos en ciertas ocasiones con los Arrays), y se pueden convertir a una
cadena "convencional" usando "ToString":
// Ejemplo_04_04_09a.cs
// Cadenas modificables con "StringBuilder"
// Introducción a C#, por Nacho Cabanes
using System;
using System.Text; // Usaremos un System.Text.StringBuilder
string cadenaNormal;
cadenaNormal = cadenaModificable.ToString();
Console.WriteLine("Cadena normal a partir de ella: {0}",
cadenaNormal);
}
}
Ejercicios propuestos:
(4.4.9.1) Un programa que pida una cadena al usuario y la modifique, de modo
que todas las vocales se conviertan en "o".
(4.4.9.2) Un programa que pida una cadena al usuario y la modifique, de modo
que las letras de las posiciones impares (primera, tercera, etc.) estén en
minúsculas y las de las posiciones pares estén en mayúsculas, mostrando el
resultado en pantalla. Por ejemplo, a partir de un nombre como "Nacho", la
cadena resultante sería "nAcHo".
(4.4.9.3) Crea un juego del ahorcado, en el que un primer usuario introduzca la
palabra a adivinar, se muestre ésta 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.
// Ejemplo_04_05a.cs
// Ejemplo de "foreach"
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.5.1) Un programa que pida tu nombre y lo muestre con un espacio entre cada
par de letras, usando "foreach".
(4.5.2) Un programa que pida al usuario una frase y la descomponga en
subcadenas separadas por espacios, usando "Split". Luego debe mostrar cada
subcadena en una línea nueva, usando "foreach".
(4.5.3) Un programa que pida al usuario varios números separados por espacios y
muestre su suma (como el del ejercicio 4.4.7.2), pero empleando "foreach".
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.
Por otra parte, para revisar todas las fichas existentes, recorreremos desde la
posición 0 hasta la n-1, haciendo algo como
// Ejemplo_04_06a.cs
// Tabla con muchos struct y menú para manejarla
// Introducción a C#, por Nacho Cabanes
using System;
struct tipoFicha {
public string nombreFich; // Nombre del fichero
public long tamanyo; // El tamaño en KB
};
do {
// Menu principal, repetitivo
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 por encima de un cierto
tamaño");
Console.WriteLine("4.- Ver datos de un fichero");
Console.WriteLine("5.- Salir");
fichas[i].nombreFich, fichas[i].tamanyo);
break;
(Como quizá hayas notado, este fuente, que es un poco más largo que los
anteriores, abre las llaves al final de cada línea -estilo Java- y usa tabulaciones de 2
espacios, en vez de 4, para ocupar menos espacio en papel y caber en el ancho de
una página; recuerda que puedes usar estilo Java si lo prefieres, pero en general el
fuente será más legible con tabulaciones de 4 espacios en vez de 2).
Este programa 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 descontado, 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:
(4.6.1) 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.
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 comentaremos más adelante.
Método de burbuja
(Intercambiar cada pareja consecutiva que no esté ordenada)
(Nota: algunos autores hacen el bucle exterior creciente y otros decreciente, así:)
Selección directa
(En cada pasada busca el menor, y lo intercambia al final de la pasada)
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).
(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).
// Ejemplo_04_07a.cs
// Ordenaciones simples
// Introducción a C#, por Nacho Cabanes
using System;
// BURBUJA
// (Intercambiar cada pareja consecutiva que no esté ordenada)
// Para i=1 hasta n-1
// Para j=i+1 hasta n
// Si A[i] > A[j]
// Intercambiar ( A[i], A[j])
Console.WriteLine("Ordenando mediante burbuja... ");
for(i=0; i < n-1; i++)
{
foreach (int dato in datos) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();
{
datoTemporal = datos[i];
datos[i] = datos[j];
datos[j] = datoTemporal;
}
}
}
Console.Write("Ordenado:");
// 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("Ordenando mediante selección directa... ");
int[] datos2 = {5, 3, 14, 20, 8, 9, 1};
for(i=0; i < n-1; i++)
{
foreach (int dato in datos2) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();
int menor = i;
for(j=i+1; j < n; j++)
if (datos2[j] < datos2[menor])
menor = j;
if (i != menor)
{
datoTemporal = datos2[i];
datos2[i] = datos2[menor];
datos2[menor] = datoTemporal;
}
}
Console.Write("Ordenado:");
// INSERCION DIRECTA:
// (Comparar cada elemento con los anteriores -que ya están
ordenados-
// y desplazarlo hasta su posición correcta).
// Para i=2 hasta n
// j=i-1
// mientras (j>=1) y (A[j] > A[j+1])
// Intercambiar ( A[j], A[j+1])
// j = j - 1
Console.WriteLine("Ordenando mediante inserción directa... ");
Revisión 0.99zz – Página 147
Introducción a la programación con C#, por Nacho Cabanes
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:");
}
}
Y su resultado sería:
Ejercicios propuestos:
(4.7.1) Un programa que pida al usuario 6 números en coma flotante y los muestre
ordenados de menor a mayor. Escoge el método de ordenación que prefieras.
(4.7.2) Un programa que pida al usuario 5 nombres y los muestre ordenados
alfabéticamente (recuerda que para comparar cadenas no podrás usar el símbolo
">", sino "CompareTo").
(4.7.3) Un programa que pida al usuario varios números, los vaya añadiendo a un
array, mantenga el array ordenado continuamente y muestre el resultado tras
añadir cada nuevo dato (todos los datos se mostrarán en la misma línea,
separados por espacios en blanco). Terminará cuando el usuario teclee "fin".
(4.7.4) Amplia el ejercicio anterior, para añadir una segunda fase en la que el
usuario pueda "preguntar" si un cierto valor está en el array. Como el array está
ordenado, la búsqueda no se hará hasta el final de los datos, sino hasta que se
encuentre el dato buscado o un un dato mayor que él.
Una vez que los datos están ordenados, podemos buscar uno concreto dentro de
ellos empleando la búsqueda binaria: se comienza por el punto central; si el valor
buscado es mayor que el del punto central, se busca esta vez sólo en la mitad
superior (o en la inferior), y así sucesivamente, de modo que cada vez se busca
entre un conjunto de datos que tiene la mitad de tamaño que el anterior. Esto
puede suponer una enorme ganancia en velocidad: si tenemos 1.000 datos, una
búsqueda lineal hará 500 comparaciones como media, mientras que una
búsqueda lineal hará 10 comparaciones o menos. Se podría implementar así:
// Ejemplo_04_07b.cs
// Búsqueda binaria
// Introducción a C#, por Nacho Cabanes
using System;
// Y comenzamos a buscar
int valorBuscado = 1001;
Console.WriteLine("Buscando si aparece {0}...", valorBuscado);
int limiteInferior = 0;
int limiteSuperior = 999;
bool terminado = false;
while(! terminado)
{
int puntoMedio = limiteInferior+(limiteSuperior-limiteInferior) /
2;
// Aviso de dónde buscamos
Console.WriteLine("Buscando entre pos {0} y {1}, valores {2} y
{3},"+
" centro {4}:{5}",
limiteInferior, limiteSuperior,
datos[limiteInferior], datos[limiteSuperior],
puntoMedio, datos[puntoMedio]);
// Compruebo si hemos acertado
if (datos[puntoMedio] == valorBuscado)
{
Console.WriteLine("Encontrado!");
terminado = true;
}
// O si se ha terminado la búsqueda
else if (limiteInferior == limiteSuperior-1)
{
Console.WriteLine("No encontrado");
terminado = true;
}
// Si no hemos terminado, debemos seguir buscando en una mitad
if ( datos[puntoMedio] < valorBuscado )
limiteInferior = puntoMedio;
else
limiteSuperior = puntoMedio;
}
}
Revisión 0.99zz – Página 150
Introducción a la programación con C#, por Nacho Cabanes
Ejercicios propuestos:
(4.7.5) Realiza una variante del ejercicio 4.7.4, que en vez de hacer una búsqueda
lineal (desde el principio), use "búsqueda binaria": se comenzará a comparar con el
punto medio del array; si nuestro dato es menor, se vuelve a probar en el punto
medio de la mitad inferior del array, y así sucesivamente.
// Ejemplo_04_07c.cs
// Array.Sort
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(4.7.6) Crea una variante del ejercicio 4.7.3, pero usando esta vez Array.Sort para
ordenar: un programa que pida al usuario varios números, los vaya añadiendo a
un array, mantenga el array ordenado continuamente y muestre el resultado tras
añadir cada nuevo dato (todos los datos se mostrarán en la misma línea,
separados por espacios en blanco). Terminará cuando el usuario teclee "fin".
Por eso, un segundo editor que puede resultar aún más interesante (aunque a
cambio tiene menos "plugins" para ampliar sus funcionalidades) es Geany, que sí
permite compilar y ejecutar nuestros programas sin necesidad de salir del editor:
Si, por el contrario, todo ha sido correcto, la ventana que nos muestra la ejecución
de nuestro programa se quedará pausada para que podamos comprobar los
resultados:
Nos aparecerá una ventana de la que nos interesan dos casillas: la que permite
escribir el comando para compilar y la del comando para ejecutar.
C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc "%f"
Esto habría sido muy trabajoso para compilar desde línea de comando nuestros
primeros programas, pero una vez que usamos editores más avanzados, sólo hay
que configurarlo una vez en Geany y se utilizará automáticamente cada vez que
creemos un fuente en C# desde ese editor.
En este caso, en la casilla "Ejecutar" bastará con escribir "%e.exe", para que se
lance directamente el ejecutable recién creado:
"%e.exe"
Nota: Las comillas dobles que rodean a "%e.exe" (y a "%f") permiten que se lance
correctamente incluso si hubiéramos escrito algún espacio en el nombre del
fichero (costumbre poco recomendable para un fuente de un programa).
Por supuesto, existen otros muchos editores gratuitos, que puedes utilizar sin
gastar dinero y que harán tu rutina de programador mucho más cómoda que con
el Bloc de Notas. Van desde editores sencillos como Notepad2 o Scite hasta
entornos integrados, que usaremos dentro de poco para programas de mayor
tamaño, en los que intervendrán varios ficheros fuente de forma simultánea. Es el
caso de Visual Studio, Sharp Develop y MonoDevelop (o su versión modernizada y
ampliada, Xamarin Studio), que veremos un poco más adelante, cuando nuestros
programas lleguen a ese nivel de complejidad.
Por ejemplo, podríamos crear una función llamada "Saludar", que escribiera varios
mensajes en la pantalla:
Ahora desde dentro del cuerpo de nuestro programa, podríamos "llamar" a esa
función:
Así conseguimos que nuestro programa principal sea más fácil de leer.
// Ejemplo_05_02a.cs
// Funcion "Saludar"
// Introducción a C#, por Nacho Cabanes
using System;
Como ejemplo más detallado, la parte principal de una agenda o de una base de
datos simple como las que hicimos en el tema anterior, podría ser simplemente:
LeerDatosDeFichero();
Revisión 0.99zz – Página 157
Introducción a la programación con C#, por Nacho Cabanes
do {
MostrarMenu();
opcion = PedirOpcion();
switch( opcion ) {
case 1: BuscarDatos(); break;
case 2: ModificarDatos(); break;
case 3: AnadirDatos(); break;
...
Ejercicios propuestos:
(5.2.1) Crea una función llamada "BorrarPantalla", que borre la pantalla dibujando
25 líneas en blanco. Crea también un "Main" que permita probarla.
(5.2.2) Crea una función llamada "DibujarCuadrado3x3", que dibuje un cuadrado
formato por 3 filas con 3 asteriscos cada una. Incluye un "Main" para probarla.
(5.2.3) Descompón en funciones la base de datos de ficheros (ejemplo 04_06a), de
modo que el "Main" sea breve y más legible (Pista: las variables que se compartan
entre varias funciones deberán estar fuera de todas ellas, y deberán estar
precedidas por la palabra "static").
Por ejemplo, si escribimos en pantalla números reales con frecuencia, nos puede
resultar útil crear una función auxiliar que nos los muestre con el formato que nos
interese (que podría ser con exactamente 3 decimales). Lo podríamos hacer así:
EscribirNumeroReal(2.3f);
(recordemos que el sufijo "f" sirve 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
// Ejemplo_05_03a.cs
// Funcion "EscribirNumeroReal"
// Introducción a C#, por Nacho Cabanes
using System;
x= 5.1f;
Console.WriteLine("El primer numero real es: ");
EscribirNumeroReal(x);
Console.WriteLine(" y otro distinto es: ");
EscribirNumeroReal(2.3f);
}
De modo que un programa completo de ejemplo para una función con dos
parámetros podría ser:
// Ejemplo_05_03b.cs
// Funcion "EscribirSuma"
// Introducción a C#, por Nacho Cabanes
using System;
{
Console.Write( a+b );
}
Ejercicios propuestos:
(5.3.1) Crea una función "DibujarCuadrado" que dibuje en pantalla un cuadrado
del ancho (y alto) que se indique como parámetro. Completa el programa con un
Main que permita probarla.
(5.3.2) Crea una función "DibujarRectangulo" que dibuje en pantalla un rectángulo
del ancho y alto que se indiquen como parámetros. Incluye un Main para probarla.
(5.3.3) Crea una función "DibujarRectanguloHueco" que dibuje en pantalla un
rectángulo hueco del ancho y alto que se indiquen como parámetros, formado por
una letra que también se indique como parámetro. Completa el programa con un
Main que pida esos datos al usuario y dibuje el rectángulo.
Pero eso no es lo que ocurre con las funciones matemáticas que estamos
acostumbrados a manejar: sí devuelven un valor, que es el resultado de una
operación (por ejemplo, la raíz cuadrada de un número tiene como resultado otro
número).
De igual modo, para nosotros también será habitual crear funciones que realicen
una serie de cálculos y nos "devuelvan" (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í:
resultado = Cuadrado( 5 );
// Ejemplo_05_04a.cs
// Funcion "Cuadrado"
// Introducción a C#, por Nacho Cabanes
using System;
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));
}
Ejercicios propuestos:
(5.4.1) Crea una función "Cubo" que calcule el cubo de un número real (float) que
se indique como parámetro. El resultado deberá ser otro número real. Prueba esta
función para calcular el cubo de 3.2 y el de 5.
(5.4.2) Crea una función "Menor" que calcule el menor de dos números enteros
que recibirá como parámetros. El resultado será otro número entero.
(5.4.3) Crea 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.
(5.4.4) Crea una función "Inicial", que devuelva la primera letra de una cadena de
texto. Prueba esta función para calcular la primera letra de la frase "Hola".
(5.4.5) Crea una función "UltimaLetra", que devuelva la última letra de una cadena
de texto. Prueba esta función para calcular la última letra de la frase "Hola".
(5.4.6) Crea una función "MostrarPerimSuperfCuadrado" que reciba un número
entero y calcule y muestre en pantalla el valor del perímetro y de la superficie de
un cuadrado que tenga como lado el número que se ha indicado como parámetro.
Vamos a ver el uso de variables locales 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
// Ejemplo_05_05a.cs
// Ejemplo de función con variables locales
// Introducción a C#, por Nacho Cabanes
using System;
En este caso, las variables "temporal" e "i" son locales a la función "potencia": para
"Main" no existen. Si en "Main" intentáramos hacer i=5; obtendríamos un
mensaje de error. De igual modo, "num1" y "num2" son locales para "Main": desde
la función "potencia" no podemos acceder a su valor (ni para leerlo ni para
modificarlo), sólo desde "Main". Este ejemplo no contiene ninguna variable global.
Ejercicios propuestos:
(5.5.1) Crea 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.
Pruébalo con un programa que pida al usuario un año entre 1800 y 2100.
(5.5.2) Crea 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").
(5.5.3) Crea 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.
(5.5.4) Crea una función "ContarLetra", 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 (porque la "a" aparece
2 veces).
(5.5.5) Crea una función "SumaCifras" 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.
(5.5.6) Crea una función "Triángulo" 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
****
***
**
*
// Ejemplo_05_06a.cs
// Dos variables locales con el mismo nombre
// Introducción a C#, por Nacho Cabanes
using System;
n vale 5
Ahora n vale 5
¿Por qué? Sencillo: tenemos una variable local dentro de "cambiaN" y otra dentro
de "Main". El hecho de que las dos variables tengan el mismo nombre no afecta al
funcionamiento del programa, siguen siendo distintas, porque cada una está en
un bloque ("ámbito") distinto.
// Ejemplo_05_06b.cs
// Una variable global
// Introducción a C#, por Nacho Cabanes
using System;
static int n = 7;
}
Revisión 0.99zz – Página 166
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_05_07a.cs
// Modificar una variable recibida como parámetro - acercamiento
// Introducción a C#, por Nacho Cabanes
using System;
n vale 5
El valor recibido vale 5
y ahora vale 10
Ahora n vale 5
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.
// Ejemplo_05_07b.cs
// Modificar una variable recibida como parámetro - correcto
// Introducción a C#, por Nacho Cabanes
using System;
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:
tienen valor inicial. Por ejemplo, una función que devuelva la primera y segunda
letra de una frase sería así:
// Ejemplo_05_07c.cs
// Parámetros "out"
// Introducción a C#, por Nacho Cabanes
using System;
Si pruebas este ejemplo, verás que no compila si cambias "out" por "ref", a no ser
que des valores iniciales a "letra1" y "letra2".
Ejercicios propuestos:
(5.7.1) Crea una función "Intercambiar", que intercambie el valor de los dos
números enteros que se le indiquen como parámetro. Crea también un programa
que la pruebe.
(5.7.2) Crea 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. Crea un "Main" que te permita comprobar que
funciona correctamente.
// Ejemplo_05_08a.cs
// Función tras Main
// Introducción a C#, por Nacho Cabanes
using System;
También, una forma simple de obtener un único número "casi al azar" entre 0 y
999 es tomar las milésimas de segundo de la hora actual:
// Ejemplo_05_09_01a.cs
// Números al azar
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(5.9.1.1) Crea un programa que imite el lanzamiento de un dado, generando un
número al azar entre 1 y 6.
(5.9.1.2) Crea un programa que genere un número al azar entre 1 y 100. El
usuario tendrá 6 oportunidades para acertarlo.
(5.9.1.3) Mejora el programa del ahorcado (4.4.9.3), para que la palabra a adivinar
no sea tecleada por un segundo usuario, sino que se escoja al azar de un "array"
de palabras prefijadas (por ejemplo, nombres de ciudades).
(5.9.1.4) Crea un programa que genere un array relleno con 100 números reales al
azar entre -1000 y 1000. Luego deberá calcular y mostrar su media.
(5.9.1.5) Crea un programa que "dibuje" asteriscos en 100 posiciones al azar de la
pantalla. Para ayudarte para escribir en cualquier coordenada, puedes usar un
array de dos dimensiones (con tamaños 24 para el alto y 79 para el ancho), que
primero rellenes y luego dibujes en pantalla.
// Ejemplo_05_09_02a.cs
// Ejemplo de funciones trigonometricas
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(5.9.2.1) Crea un programa que halle (y muestre) la raíz cuadrada del número que
introduzca el usuario. Se repetirá hasta que introduzca 0.
(5.9.2.2) Crea un programa que halle cualquier raíz (de cualquier orden) 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.
(5.9.2.3) Haz 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. Se deberá crear
una función "CalcularRaicesSegundoGrado", que recibirá como parámetros los
coeficientes a, b y c (por valor), así como las soluciones x1 y x2 (por referencia).
Deberá devolver los valores de las dos soluciones x1 y x2. Si alguna solución no
existe, se devolverá como valor 100.000 para esa solución. Pista: la solución se
calcula con
x = -b raíz (b2 – 4·a·c) / (2·a)
(5.9.2.4) Haz un programa que pida al usuario 5 datos numéricos enteros, los
guarde en un array, pida un nuevo dato y muestre el valor del array que se
encuentra más cerca de ese dato, siendo mayor que él, o el texto "Ninguno es
mayor" si ninguno lo es.
(5.9.2.5) Crea un programa que pida al usuario 5 datos numéricos reales, los
guarde en un array, pida un nuevo dato y muestre el valor del array que se
encuentra más cerca de ese dato en valor absoluto (es decir, el más próximo, sea
mayor que él o menor que él).
(5.9.2.6) Crea una función "Distancia", que calcule la distancia entre dos puntos
(x1,y1) y (x2,y2), usando la expresión d = raíz [ (x1-x2)2 + (y1-y2)2 ].
Revisión 0.99zz – Página 173
Introducción a la programación con C#, por Nacho Cabanes
(5.9.2.7) Crea un programa que pida al usuario un ángulo (en grados) y muestre su
seno, coseno y tangente. Recuerda que las funciones trigonométricas esperan que
el ángulo se indique en radianes, no en grados. La equivalencia es que 360 grados
son 2*PI radianes.
(5.9.2.8) Crea un programa que muestre los valores de la función y = 10 *
seno(x*5), para valores de x entre 0 y 72 grados.
(5.9.2.9) Crea un programa que "dibuje" la gráfica de la función y = 10 * seno(x*5),
para valores de x entre 0 y 72 grados. Para ayudarte para escribir en cualquier
coordenada, puedes usar un array de dos dimensiones, que primero rellenes y
luego dibujes en pantalla (mira el ejercicio 5.9.1.5).
(5.9.2.10) Crea un programa que "dibuje" un círculo dentro de un array de dos
dimensiones, usando las ecuaciones x = xCentro + radio * coseno(ángulo), y =
yCentro + radio * seno(ángulo). Si tu array es de 24x79, las coordenadas del centro
serían (12,40). Recuerda que el ángulo se debe indicar en radianes (mira el
ejercicio 5.9.1.5 y el 5.9.2.9).
5.10. Recursividad
La recursividad consiste en resolver un problema a partir de casos más simples del
mismo problema. Una función recursiva es aquella que se "llama a ella misma",
reduciendo la complejidad paso a paso hasta llegar a un caso trivial.
El factorial de 1 es 1:
1! = 1
n! = n · (n-1)!
// Ejemplo_05_10a.cs
// Funciones recursivas: factorial
// Introducción a C#, por Nacho Cabanes
using System;
¿Qué utilidad tiene esto? 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. Los ejercicios propuestos te
ayudarán a descubrir otros ejemplos de situaciones en las que se puede aplicar la
recursividad.
Ejercicios propuestos:
(5.10.1) Crea una función que calcule el valor de elevar un número entero a otro
número entero (por ejemplo, 5 elevado a 3 = 5 3 = 5 ·5 ·5 = 125). Esta función se
debe crear de forma recursiva. Piensa cuál será el caso base (qué potencia se
puede calcular de forma trivial) y cómo pasar del caso "n-1" al caso "n" (por
ejemplo, si sabes el valor de 54, cómo hallarías el de 55 a partir de él).
(5.10.2) Como alternativa, crea 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".
(5.10.3) Crea 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).
(5.10.4) Crea un programa que emplee recursividad para calcular la suma de los
elementos de un vector de números enteros, desde su posición inicial a la final,
usando una función recursiva que tendrá la apariencia: SumaVector(v, desde,
hasta). Nuevamente, piensa cuál será el caso base (cuántos elementos podrías
sumar para que dicha suma sea trivial) y cómo pasar del caso "n-1" al caso "n" (por
ejemplo, si conoces la suma de los 6 primeros elementos y el valor del séptimo
elemento, cómo podrías emplear esta información para conocer la suma de los 7
primeros).
(5.10.5) Crea un programa que emplee recursividad para calcular el mayor de los
elementos de un vector. El planteamiento será muy similar al del ejercicio anterior.
(5.10.6) Crea un programa que emplee recursividad para dar la vuelta a una
cadena de caracteres (por ejemplo, a partir de "Hola" devolvería "aloH"). La función
recursiva se llamará "Invertir(cadena)". Como siempre, analiza cuál será el caso
base (qué longitud debería tener una cadena para que sea trivial darle la vuelta) y
cómo pasar del caso "n-1" al caso "n" (por ejemplo, si ya has invertido las 5
primeras letras, que ocurriría con la de la sexta posición).
(5.10.7) Crea, 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.
(5.10.8) 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.10.9) Crea dos funciones que sirvan para saber si un cierto texto es subcadena
de una cadena. No puedes usar "Contains" ni "IndexOf", sino que debes analizar
letra a letra. Una función debe ser iterativa y la otra debe ser recursiva.
(5.10.10) Crea una función que reciba una cadena de texto, y una subcadena, y
devuelva cuántas veces aparece la subcadena en la cadena, como subsecuencia
formada a partir de sus letras en orden. Por ejemplo, si recibes la palabra "Hhoola"
y la subcadena "hola", la respuesta sería 4, porque se podría tomar la primera H
con la primera O (y con la L y con la A), la primera H con la segunda O, la segunda
H con la primera O, o bien la segunda H con la segunda O. Si recibes "hobla", la
respuesta sería 1. Si recibes "ohla", la respuesta sería 0, porque tras la H no hay
ninguna O que permita completar la secuencia en orden.
(5.10.11) El algoritmo de ordenación conocido como "Quicksort", parte de la
siguiente idea: para ordenar un array entre dos posiciones "i" y "j", se comienza
por tomar un elemento del array, llamado "pivote" (por ejemplo, el punto medio);
luego se recoloca el array de modo que los elementos menores que el pivote
queden a su izquierda y los mayores a su derecha; finalmente, se llama de forma
recursiva a Quicksort para cada una de las dos mitades. El caso base de la función
recursiva es cuando se llega a un array de tamaño 0 ó 1. Implementa una función
que ordene un array usando este método.
ls –l *.cs
En este caso, la orden sería "ls", y las dos opciones (argumentos o parámetros) que
le indicamos son "-l" y "*.cs".
dir *.cs
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:
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 puede leer desde un fichero BAT o CMD usando "IF ERRORLEVEL",
así:
{
...
return 0;
}
// Ejemplo_05_11a.cs
// Parámetros y valor de retorno de "Main"
// Introducción a C#, por Nacho Cabanes
using System;
if (args.Length == 0)
{
Console.WriteLine("No ha indicado ningún parámetro!");
Environment.Exit(1);
}
return 0;
}
}
Ejercicios propuestos:
(5.11.1) Crea un programa llamado "suma", que calcule (y muestre) la suma de dos
números que se le indiquen como parámetros en línea de comandos. Por
ejemplo, si se teclea "suma 2 3" deberá responder "5", si se teclea "suma 2"
responderá "2" y si se teclea únicamente "suma" deberá responder "no hay
suficientes datos" y devolver un código de error 1.
(5.11.2) Crea 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".
(5.11.3) Crea una variante del ejercicio 5.11.2, en la que Main devuelva el código 1
si la operación indicada no es válida o 0 cuando sí sea una operación aceptable.
(5.11.4) Crea una variante del ejercicio 5.11.3, en la que Main devuelva también el
código 2 si alguno de los dos números con los que se quiere operar no tiene un
valor numérico válido.
Esta descomposición no debe ser arbitraria. Por ejemplo, será deseable que cada
bloque tenga unas responsabilidades claras, y que cada bloque no dependa de los
detalles internos de otros bloques.
Una forma de "descubrir" los objetos que forman parte de un programa es partir
de la descripción del problema, y subrayar los nombres con un color y los verbos
con otro color.
Y cuando comenzamos una partida, veremos una pantalla como ésta, que vamos a
analizar con más detalle:
A partir de esa descripción, podemos buscar los nombres (puede ayudar si los
subrayamos con un rotulador de marcar), que indicarán los objetos en los que
podemos descomponer el problema, y los verbos (con otro rotulador de marcar,
en otro color), que indicarán las acciones que puede realizar cada uno de esos
objetos.
(En general, esta descomposición no tiene por qué ser única, distintos
programadores o analistas pueden llegar a soluciones parcialmente distintas).
Esa serie de objetos, con sus relaciones y sus acciones, se puede expresar
mediante un "diagramas de clases", que en nuestro caso podría ser así
(simplificado):
Algunos de los detalles que se pueden leer de ese diagrama (y que deberían
parecerse bastante a la descripción inicial) son:
Nosotros no vamos a hacer proyectos tan grandes (al menos, no todavía), pero sí
empezaremos a crear proyectos sencillos en los que colaboren varias clases, que
permitan sentar las bases para proyectos más complejos, y también entender
algunas peculiaridades de los temas que veremos a continuación, como el manejo
de ficheros en C#.
Como curiosidad, cabe mencionar que en los proyectos grandes es habitual usar
herramientas gráficas que nos ayuden a visualizar las clases y las relaciones que
existen entre ellas, como hemos hecho para el Space Invaders. También se puede
dibujar directamente en papel para aclararnos las ideas, pero el empleo de
herramientas informáticas tiene varias ventajas adicionales:
Podemos "pinchar y arrastrar" para recolocar las clases, y así conseguir más
legibilidad o dejar hueco para nuevas clases que hayamos descubierto que
también deberían ser parte del proyecto.
aplicación, la secuencia de acciones que se debe seguir, las clases que la van a
integrar (que es lo que a nosotros nos interesa en este momento), etc.
Disponemos de herramientas gratuitas como ArgoUML, multiplataforma, que
permite crear distintos tipos de diagramas UML y que permite generar el código
correspondiente al esqueleto de nuestras clases, o Dia, también multiplataforma,
pero de uso más genérico (para crear diagramas de cualquier tipo) y que no
permite generar código por ella misma pero sí con la ayuda de Dia2code.
En los próximos ejemplos partiremos de una única clase en C#, para ampliar
posteriormente esa estructura e ir creando proyectos más complejos.
Ejercicio propuesto:
(6.1.1) Piensa en un juego que conozcas, que no sea demasiado complejo
(tampoco demasiado simple) y trata de hacer una descripción como la anterior y
una descomposición en clases.
Vamos a completar un programa de prueba que use un objeto de esta clase (una
"Puerta"), muestre su estado, la abra y vuelva a mostrar su estado:
// Ejemplo_06_02a.cs
// Primer ejemplo de clases
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Vamos a abrir...");
p.Abrir();
p.MostrarEstado();
}
Este fuente ya no contiene una única clase (class), como todos nuestros ejemplos
anteriores, sino dos clases distintas:
(Nota: al compilar, puede que obtengas algún "Aviso" -warning- que te dice que
has declarado "alto", "ancho" y "color", pero no las estás utilizando; no es
importante por ahora, puedes ignorar ese aviso).
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# (pero no en todos los 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, "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" desde
el programa principal, por dos motivos:
Por ejemplo, para conocer y modificar los valores del "ancho" de una puerta,
podríamos crear un método LeerAncho, que nos devolviera su valor, y un método
CambiarAncho, que lo reemplazase por otro valor. No hay un convenio claro sobre
cómo llamar a a estos métodos en español, por lo que es frecuente usar las
palabras inglesas "Get" y "Set" para leer y cambiar un valor, respectivamente. Así,
crearemos funciones auxiliares GetXXX y SetXXX que permitan acceder al valor de
los atributos (en C# existe una forma alternativa de hacerlo, usando
"propiedades", que veremos más adelante):
Así, una nueva versión del programa, que incluya ejemplos de Get y Set, podría
ser:
// Ejemplo_06_02b.cs
// Clases, get y set
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Vamos a abrir...");
p.Abrir();
p.SetAncho(80);
p.MostrarEstado();
Revisión 0.99zz – Página 190
Introducción a la programación con C#, por Nacho Cabanes
Se nos mostrará los tipos de proyectos para los que se nos podría crear un
esqueleto vacío que después iríamos rellenando:
Podemos cambiar el idioma del interfaz, para que todos los menús y opciones
aparezcan en español. Lo haremos desde "Options" (opciones), al final del menú
"Tools" (herramientas):
(Si la ventana de nuestro programa se cierra tan rápido que no tenemos tiempo de
leerla, nos puede interesar añadir provisionalmente una línea ReadLine() al final
del fuente, para que éste se detenga hasta que pulsemos la tecla Intro).
Normalmente, el tipo de elemento que nos interesará será una clase, cuyo nombre
deberemos indicar:
y obtendríamos un nuevo esqueleto vacío (esta vez sin "Main"), que deberíamos
completar.
Nuestro programa, que ahora estaría formado por dos clases, se compilaría y se
ejecutaría de la misma forma que cuando estaba integrado por una única clase.
Si nuestro equipo es moderno, posiblemente nos permitirá usar Visual Studio con
una cierta fluidez. Instalar y emplear este entorno es muy similar a lo que hemos
visto. La versión Express, que es gratuita para uso personal, se puede descargar
de:
http://www.visualstudio.com/es-es/products/visual-studio-express-vs
La pantalla principal, una vez completada la instalación, debería ser similar a ésta
(para Visual Studio 2013; puede ser ligeramente distinta en otras versiones o si
escogemos otras paletas de colores):
Si nuestro programa va a estar formado por varios fuentes, podríamos añadir más
clases usando la ventana de la derecha ("Explorador de soluciones") o la opción
"Agregar clase…" del menú "Proyecto":
Ejercicio propuesto:
(6.3.1) Crea un proyecto con las clases Puerta y Ejemplo_06_03a. Comprueba que
todo funciona correctamente.
(6.3.2) Modifica el fuente del ejercicio 6.2.1 (clase Persona), para dividirlo en dos
ficheros: Crea 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. Crea 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.3) Crea un proyecto a partir de la clase Libro (ejercicio 6.2.3). El "Main" pasará
a una segunda clase llamada "PruebaDeLibro" y desaparecerá de la clase Libro.
(6.3.4) Amplía el esqueleto del ConsoleInvaders (ejercicio 6.2.2): crea un proyecto
para Visual Studio o SharpDevelop. Además de la clase "Juego", crea una clase
"Bienvenida" y una clase "Partida". El método "Lanzar" de la clase Juego, ya no
escribirá nada en pantalla, sino que creará un objeto de la clase "Bienvenida" y lo
lanzará y luego un objeto de la clase "Partida" y lo lanzará. El método Lanzar de la
clase Bienvenida escribirá en pantalla "Bienvenido a Console Invaders. Pulse Intro
para jugar". El método Lanzar de la clase Partida escribirá en pantalla "Ésta sería la
pantalla de juego. Pulse Intro para salir" y se parará hasta que el usuario pulse
Intro.
(6.3.5) Amplía el esqueleto del ConsoleInvaders (ejercicio 6.3.4): El método Lanzar
de la clase Bienvenida escribirá en pantalla "Bienvenido a Console Invaders. Pulse
Intro para jugar o ESC para salir". Puedes comprobar si se pulsa ESC con
"ConsoleKeyInfo tecla = Console.ReadKey(); if (tecla.Key == ConsoleKey.Escape)
salir = true;". El código de la tecla Intro es " ConsoleKey.Enter". También puedes
usar "Console.Clear();" si quieres borrar la pantalla. Añade un método "GetSalir" a
la clase Bienvenida, que devuelva "true" si el usuario ha escogido Salir o "false" si
ha elegido Jugar. El método Lanzar de la clase Juego repetirá la secuencia
Bienvenida-Partida hasta que el usuario escoja Salir.
(6.3.6) Amplía el esqueleto del ConsoleInvaders (ejercicio 6.3.5): Crea una clase
Nave, con atributos "x" e "y" (números enteros, "x" de 0 a 1023 e "y" entre 0 y 767,
pensando en una pantalla de 1024x768), e imagen (un string formado por dos
caracteres, como "/\"). También tendrá un método MoverA(nuevaX, nuevaY) que lo
mueva a una nueva posición, y un método Dibujar, que muestre esa imagen en
pantalla (como esta versión es para consola, la X tendrá que rebajarse para que
tenga un valor entre 0 y 79, y la Y entre 0 y 24). Puedes usar
Console.SetCursorPosition(x,y) para situarte en unas coordenadas de pantalla.
Crea también clase Enemigo, con los mismos atributos. Su imagen podría ser "][".
El método Lanzar de la clase Partida creará una nave en las coordenadas (500,
600) y la dibujará, creará un enemigo en las coordenadas (100, 80) y lo dibujará, y
finalmente esperará a que el usuario pulse Intro para terminar la falsa sesión de
juego.
(6.3.7) Crea un proyecto a partir de la clase Coche (ejercicio 6.2.4): además de la
clase Coche, existirá una clase PruebaDeCoche, que contendrá la función "Main",
que creará un objeto de tipo coche, pedirá al usuario su marca, modelo, cilindrada
y potencia, y luego mostrará en pantalla el valor de esos datos.
6.4. La herencia
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:
// Porton.cs
// Clase que hereda de Puerta
// Introducción a C#, por Nacho Cabanes
using System;
Revisión 0.99zz – Página 205
Introducción a la programación con C#, por Nacho Cabanes
bool bloqueada;
Con "public class Porton: Puerta" indicamos que Porton debe "heredar" todo lo
que ya habíamos definido para Puerta. Por eso, no hace falta indicar nuevamente
que un Portón tendrá un cierto ancho, o un color, o que se puede abrir: todo eso
lo tiene por ser un "descendiente" de Puerta.
No tenemos por qué heredar todo tal y como era; 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 (si no
incluimos la palabra "new", el compilador mostrará un "warning", pero dará el
programa como válido; más adelante veremos que existe una alternativa a "new" y
en qué momento será adecuado usar cada una de ellas.
Puedes observar que ese "MostrarEstado" no dice nada sobre el ancho ni el alto
del portón. En el próximo apartado veremos cómo acceder a esos datos.
// Ejemplo_06_04a.cs
Revisión 0.99zz – Página 206
Introducción a la programación con C#, por Nacho Cabanes
using System;
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Vamos a abrir...");
p.Abrir();
p.SetAncho(80);
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Ahora el portón...");
Porton p2 = new Porton();
p2.SetAncho(300);
p2.Bloquear();
p2.MostrarEstado();
}
Y su resultado sería:
Valores iniciales...
Ancho: 0
Alto: 0
Color: 0
Abierta: False
Vamos a abrir...
Ancho: 80
Alto: 0
Color: 0
Abierta: True
Ahora el portón...
Portón.
Bloqueada: True
Ejercicios propuestos:
(6.4.1) Crea un proyecto con las clases Puerta, Portón y Ejemplo_06_04a. Prueba
que todo funciona correctamente.
(6.4.2) Crea una variante ampliada del ejercicio 6.3.2. En ella, la clase Persona no
cambia. Se creará una nueva clase PersonaInglesa, en el fichero
"personaInglesa.cs". Esta clase deberá heredar las características de la clase
"Persona", y añadir un método "TomarTe", de tipo void, que escribirá en pantalla
"Estoy tomando té". Crear también una clase llamada PruebaPersona2, en el
fichero "pruebaPersona2.cs". Esta clase deberá contener sólo la función Main, que
creará dos objetos de tipo Persona y uno de tipo PersonaInglesa, les asignará un
nombre, les pedirá que saluden y pedirá a la persona inglesa que tome té.
(6.4.3) Amplía el proyecto del ejercicio 6.3.3 (Libro): crea una clase "Documento",
de la que Libro heredará todos sus atributos y métodos. Ahora la clase Libro
contendrá sólo un atributo "paginas", número entero, con sus correspondientes
Get y Set.
(6.4.4) Amplía el esqueleto del ConsoleInvaders (ejercicio 6.3.6): Crea una clase
"Sprite", de la que heredarán "Nave" y "Enemigo". La nueva clase contendrá todos
los atributos y métodos que son comunes a las antiguas (todos los existentes, por
ahora).
(6.4.5) Amplía el proyecto de la clase Coche (ejercicio 6.3.7): Crea una clase
"Vehiculo", de la que heredarán "Coche" y una nueva clase "Moto". La clase
Vehiculo contendrá todos los atributos y métodos que antes estaban en Coche, y
tanto Coche como Moto heredarán de ella.
6.5. Visibilidad
Nuestro ejemplo todavía no funciona correctamente: los atributos de una Puerta,
como el "ancho" y el "alto" estaban declarados como "privados" (es lo que se
considera en C# si no decimos lo contrario), por lo que no son accesibles desde
ninguna otra clase, ni siquiera desde Porton.
Hemos manejado con frecuencia la palabra "public", para indicar que algo debe
ser público, porque nuestras clases y su "Main" lo han sido siempre hasta ahora.
Nos podríamos sentir tentados de declarar como "public" los atributos como el
"ancho" y el "alto", pero esa no es la solución más razonable, porque no queremos
que sean accesibles desde cualquier sitio, debemos recordar la máxima de
"ocultación de detalles", que hace nuestros programas sean más fáciles de
mantener.
Sólo querríamos que esos datos estuvieran disponibles para todos los tipos de
Puerta, incluyendo sus "descendientes", como un Porton. Esto se puede conseguir
usando otro método de acceso: "protected". Todo lo que declaremos como
"protected" será accesible por las clases derivadas de la actual, pero por nadie
más:
Si quisiéramos dejar claro que algún elemento de una clase debe ser totalmente
privado, podemos usar la palabra "private", en vez de "public" o "protected". En
general, será preferible usar "private" a no escribir nada, por legibilidad, para
ayudar a detectar errores con mayor facilidad y como costumbre por si más
adelante programamos en otros lenguajes, porque puede ocurrir que en otros
lenguajes se considere público (en vez de privado) un atributo cuya visibilidad no
hayamos indicados
Así, un único 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:
// Ejemplo_06_05a.cs
// Portón, que hereda de Puerta, con "protected"
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Vamos a abrir...");
p.Abrir();
p.SetAncho(80);
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Ahora el portón...");
Revisión 0.99zz – Página 209
Introducción a la programación con C#, por Nacho Cabanes
// ------------------------------------------
// Puerta.cs
// Clases, get y set
// Introducción a C#, por Nacho Cabanes
// ------------------------------------------
// Porton.cs
// Clase que hereda de Puerta
// Introducción a C#, por Nacho Cabanes
bool bloqueada;
Ejercicios propuestos:
(6.5.1) Crea un proyecto a partir del ejemplo 06.05a, en el que cada clase esté en
un fichero separado. Como podrás comprobar, ahora necesitarás un "using
System" en cada fuente.
(6.5.2) Amplía las clases del ejercicio 6.4.2, creando un nuevo proyecto con las
siguientes características: La clase Persona no cambia; la clase PersonaInglesa se
modificará para que redefina el método "Saludar", para que escriba en pantalla
"Hi, I am " seguido de su nombre; se creará una nueva clase PersonaItaliana, en el
fichero "personaItaliana.cs", que deberá heredar las características de la clase
"Persona", pero redefinir el método "Saludar", para que escriba en pantalla "Ciao";
crea también una clase llamada PruebaPersona3, en el fichero "
pruebaPersona3.cs", que deberá contener sólo la función Main y creará un objeto
de tipo Persona, dos de tipo PersonaInglesa, uno de tipo PersonaItaliana, les
asignará un nombre, les pedirá que saluden y pedirá a la persona inglesa que
tome té.
(6.5.3) Retoca el proyecto del ejercicio 6.4.3 (Libro): los atributos de la clase
Documento y de la clase Libro serán "protegidos".
(6.5.4) Amplía el esqueleto del ConsoleInvaders (ejercicio 6.4.4): Amplía la clase
Nave con un método "MoverDerecha", que aumente su X en 10 unidades, y un
"MoverIzquierda", que disminuya su X en 10 unidades. Necesitarás hacer que esos
atributos sean "protected". El método Lanzar de la clase Partida no esperará hasta
el usuario pulse Intro sin hacer nada, sino que ahora usará un do-while que
Revisión 0.99zz – Página 211
Introducción a la programación con C#, por Nacho Cabanes
compruebe si pulsa ESC (para salir) o flecha izquierda o flecha derecha (para
mover la nave: sus códigos son ConsoleKey.LeftArrow y ConsoleKey. RightArrow).
Si se pulsan las flechas, la nave se moverá a un lado o a otro (con los métodos que
acabas de crear). Al principio de cada pasada del do-while se borrará la pantalla
("Console.Clear();").
(6.5.5) Mejora el proyecto de la clase Coche (ejercicio 6.4.5): todos los atributos
serán "protegidos" y los métodos serán "públicos".
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:
Un programa de ejemplo que usara estos dos constructores para crear dos
puertas con características iniciales distintas podría ser:
// Ejemplo_06_06a.cs
// Constructores
// Introducción a C#, por Nacho Cabanes
using System;
Console.WriteLine("Valores iniciales...");
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Vamos a abrir...");
p.Abrir();
p.MostrarEstado();
Console.WriteLine();
Console.WriteLine("Para la segunda puerta...");
p2.MostrarEstado();
}
// ------------------------------------------
public Puerta()
{
ancho = 100;
alto = 200;
color = 0xFFFFFF;
abierta = false;
}
ancho = an;
alto = al;
color = 0xFFFFFF;
abierta = false;
}
~Puerta()
{
// Liberar memoria
// Cerrar ficheros
}
Ejercicios propuestos:
(6.6.1) Ampliar las clases del ejercicio 6.5.2, para que todas ellas contengan
constructores. Los constructores de casi todas las clases estarán vacíos, excepto
del de PersonaInglesa, que prefijará su nombre a "John". Crea también un
constructor alternativo para esta clase que permita escoger cualquier otro
nombre.
(6.6.2) Amplía el proyecto del ejercicio 6.5.3 (Libro): la clase Libro tendrá un
constructor que permita dar valores al autor, el título y la ubicación.
(6.6.3) Amplía el esqueleto del ConsoleInvaders (ejercicio 6.5.4): La clase Enemigo
tendrá un constructor, sin parámetros, que prefijará su posición inicial. El
constructor de la clase Nave recibirá como parámetros las coordenadas X e Y
iniciales, para que se puedan cambiar desde el cuerpo del programa. Elimina las
variables xNave e yNave de la clase Partida, que ya no serán necesarias.
(6.6.4) Mejora el proyecto de la clase Coche (ejercicio 6.5.5): añade un atributo
"cantidadDeRuedas" a la clase Vehiculo, junto con sus Get y Set. El constructor de
la clase Coche le dará el valor 4 y el constructor de la clase Moto le dará el valor 2.
Ejercicios propuestos:
(6.7.1) A partir de las clases del ejercicio 6.6.1, añade a la clase "Persona" un nuevo
método Saludar, que reciba un parámetro, que será el texto que debe decir esa
persona cuando salude.
(6.7.2) Amplía el proyecto del ejercicio 6.6.2 (Libro): la clase Libro tendrá un
segundo constructor que permita dar valores al autor y el título, pero no a la
ubicación, que tomará el valor por defecto "No detallada".
(6.7.3) Amplía el esqueleto del ConsoleInvaders (6.6.3): La clase Nave tendrá un
segundo constructor, sin parámetros, que prefijará su posición inicial a (500,600).
La clase Enemigo tendrá un segundo constructor, con parámetros X e Y, para
poder colocar un enemigo en cualquier punto desde Main.
(6.7.4) Crea dos nuevos métodos en la clase Vehiculo (ejercicio 6.6.4): uno llamado
Circular, que fijará su "velocidad" (un nuevo atributo) a 50, y otro Circular(v), que
fijará su velocidad al valor que se indique como parámetro.
// Ejemplo_06_08a.cs
// Orden de llamada a los constructores
// Introducción a C#, por Nacho Cabanes
using System;
// ------------------
{
public Animal()
{
Console.WriteLine("Ha nacido un animal");
}
}
// ------------------
// ------------------
// ------------------
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
Ejercicios propuestos:
(6.8.1) Crea un único fuente que contenga las siguientes clases:
// Ejemplo_07_01a.cs
// Métodos "static"
// Introducción a C#, por Nacho Cabanes
using System;
Hardware.BorrarPantalla();
Console.WriteLine("Borrado!");
}
}
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:
// Ejemplo_07_01b.cs
// Alternativa a 07_01a, sin métodos "static"
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(7.1.1) Amplía el ejemplo 07_01a con un función "static" llamada
"EscribirCentrado", que escriba centrado horizontalmente el texto que se le
indique como parámetro.
(7.1.2) Amplía el ejemplo 07_01b con un función llamada "EscribirCentrado", que
escriba centrado horizontalmente el texto que se le indique como parámetro. Al
contrario que en el ejercicio 7.1.1, esta versión no será "static".
Revisión 0.99zz – Página 220
Introducción a la programación con C#, por Nacho Cabanes
(7.1.3) Crea una nueva versión del ejercicio 5.2.3 (base de datos de ficheros,
descompuesta en funciones), en la que los métodos y variables no sean "static".
Deberemos usar "new" dos veces: primero para reservar memoria para el array, y
luego para cada uno de los elementos. Por ejemplo, podríamos partir del ejemplo
del apartado 6.8 y crear un array de 5 perros así:
// Ejemplo_07_02a.cs
// Array de objetos
// Introducción a C#, por Nacho Cabanes
using System;
// ------------------
// ------------------
public Perro()
{
Console.WriteLine("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
Ha nacido un animal
Ha nacido un perro
Ejercicio propuesto:
(7.2.1) Crea una versión ampliada del ejercicio 6.8.1 (clase Trabajador y
relacionadas), en la que no se cree un único objeto de cada clase, sino un array de
tres objetos.
(7.2.2) Amplía el proyecto Libro (ejercicio 6.7.2), de modo que permita guardar
hasta 1.000 libros. Main mostrará un menú que permita añadir un nuevo libro o
ver los datos de los ya existentes.
(7.2.3) Amplía el esqueleto del ConsoleInvaders (6.7.3), para que haya 10 enemigos
en una misma fila (todos compartirán una misma coordenada Y, pero tendrán
distinta coordenada X). Necesitarás un nuevo constructor en la clase Enemigo, que
reciba los parámetros X e Y.
// Ejemplo_07_02b.cs
Revisión 0.99zz – Página 222
Introducción a la programación con C#, por Nacho Cabanes
using System;
// ------------------
// ------------------
// ------------------
// ------------------
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ha nacido un gato
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 perro
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ha nacido un perro
Ha nacido un animal
Ejercicios propuestos:
(7.2.4) A partir del ejemplo 07.02b y del ejercicio 6.8.1 (clase Trabajador y
relacionadas), crea un array de trabajadores en el que no sean todos de la misma
clase.
(7.2.5) Amplía el proyecto Libro (ejercicio 7.2.2), de modo que permita guardar
1000 documentos de cualquier tipo. A la hora de añadir un documento, se
preguntará al usuario si desea guardar un documento genérico o un libro, para
usar el constructor adecuado.
(7.2.6) Amplía el esqueleto del ConsoleInvaders (7.2.3), para que haya tres tipos de
enemigos, y un array que contenga 3x10 enemigos (3 filas, cada una con 10
enemigos de un mismo tipo, pero distinto del tipo de los elementos de las otras
filas). Cada tipo de enemigos será una subclase de Enemigo, que se distinguirá por
usar una "imagen" diferente. Puedes usar la "imagen" que quieras (siempre que
sea un string de letras, como "}{" o "XX"). Si estas imágenes no se muestran
Revisión 0.99zz – Página 224
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_07_03a.cs
// Array de objetos, sin "virtual"
// Introducción a C#, por Nacho Cabanes
using System;
miPerro.Hablar();
miGato.Hablar();
miAnimal.Hablar();
misAnimales[0].Hablar();
misAnimales[1].Hablar();
misAnimales[2].Hablar();
}
}
// ------------------
// ------------------
// ------------------
(Recuerda que, como vimos en el apartado 6.4, ese "new" que aparece en "new
void Hablar" sirve simplemente para anular un "warning" del compilador, que dice
algo como "estás redifiniendo este método; añade la palabra new para indicar que
eso es lo que deseas". Equivale a un "sí, sé que estoy redefiniendo este método").
Guau!
Miauuu
Estoy comunicándome...
Estoy comunicándome...
Estoy comunicándome...
Estoy comunicándome...
miPerro.Hablar();
miGato.Hablar();
miAnimal.Hablar();
Revisión 0.99zz – Página 226
Introducción a la programación con C#, por Nacho Cabanes
misAnimales[0].Hablar();
misAnimales[1].Hablar();
misAnimales[2].Hablar();
// Ejemplo_07_03b.cs
// Array de objetos, con "virtual"
// Introducción a C#, por Nacho Cabanes
using System;
miPerro.Hablar();
miGato.Hablar();
miAnimal.Hablar();
misAnimales[0].Hablar();
misAnimales[1].Hablar();
misAnimales[2].Hablar();
}
}
// ------------------
// ------------------
// ------------------
Guau!
Miauuu
Estoy comunicándome...
Guau!
Miauuu
Estoy comunicándome...
Nota: Esto que nos acaba de ocurrir va a ser frecuente. Si el compilador nos avisa
de que deberíamos añadir la palabra "new" porque estamos redefiniendo algo...
es habitual que realmente lo que necesitemos sea "virtual" en la clase base y
"override" en las clases derivada, especialmente si no se trata de un programa
trivial, sino que vamos a tener objetos de varias clases coexistiendo en el
programa, y especialmente si todos esos objetos van a ser parte de un único array
o estructura similar (como las "listas", que veremos más adelante).
Ejercicios propuestos:
(7.3.1) Crea una versión ampliada del ejercicio 6.5.1 (Persona, PersonaInglesa, etc),
en la que se cree un único array que contenga personas de varios tipos.
(7.3.2) Crea una variante del ejercicio 7.2.2 (array de Trabajador y derivadas), en la
que se cree un único array "de trabajadores", que contenga un objeto de cada
clase, y exista un método "Saludar" que se redefina en todas las clases hijas,
usando "new" y probándolo desde "Main".
(7.3.3) Crea una variante del ejercicio anterior (7.3.2), que use "override" en vez de
"new".
(7.3.4) Amplía el proyecto Libro (ejercicio 7.2.5): tanto la clase Documento como la
clase Libro, tendrán un método ToString, que devuelva una cadena de texto
formada por título, autor y ubicación, separados por guiones. Crea una clase
Articulo, que añada el campo "procedencia". El cuerpo del programa permitirá
añadir Artículos o Libros, no documentos genéricos. El método ToString deberá
mostrar también el número de páginas de un libro y la procedencia de un artículo.
La opción de mostrar datos llamará a los correspondientes métodos ToString.
Recuerda usar "virtual" y "override" si en un primer momento no se comporta
como debe.
(7.3.5) Amplía el esqueleto de ConsoleInvaders (7.2.6) para que muestre las
imágenes correctas de los enemigos, usando "virtual" y "override". Además, cada
tipo de enemigos debe ser de un color distinto. (Nota: para cambiar colores
puedes usar Console.ForegroundColor = ConsoleColor.Green;). La nave que
maneja el usuario debe ser blanca.
Por ejemplo, podemos hacer que un Gato Siamés hable igual que un Gato normal,
pero diciendo "Pfff" después, así:
// Ejemplo_07_04a.cs
// Ejemplo de clases: Llamar a la superclase
// Introducción a C#, por Nacho Cabanes
using System;
miGato.Hablar();
Console.WriteLine(); // Linea en blanco
miGato2.Hablar();
}
// ------------------
// ------------------
// ------------------
{
public new void Hablar()
{
base.Hablar();
Console.WriteLine("Pfff");
}
}
Su resultado sería
Miauuu
Miauuu
Pfff
Ejercicios propuestos:
(7.4.1) Crea una versión ampliada del ejercicio 7.3.3, en la que el método "Hablar"
de todas las clases hijas se apoye en el de la clase "Trabajador".
(7.4.2) Crea una versión ampliada del ejercicio 7.4.1, en la que el constructor de
todas las clases hijas se apoye en el de la clase "Trabajador".
(7.4.3) Refina el proyecto Libro (ejercicio 7.3.4), para que el método ToString de la
clase Libro se apoye en el de la clase Documento, y también lo haga el de la clase
Articulo.
(7.4.4) Amplía el esqueleto de ConsoleInvaders (7.3.5) para que en Enemigo haya
un único constructor que reciba las coordenadas X e iniciales. Los constructores de
los tres tipos de enemigos deben basarse en éste.
// Ejemplo_07_05a.cs
// Primer ejemplo de uso de "this": parámetros
// Introducción a C#, por Nacho Cabanes
using System;
// ------------------
En muchos casos, podemos evitar este uso de "this". Por ejemplo, normalmente es
preferible usar otro nombre en los parámetros:
Pero "this" tiene también otros usos. Por ejemplo, podemos crear un constructor
a partir de otro que tenga distintos parámetros:
// Ejemplo_07_05b.cs
// Segundo ejemplo de uso de "this": constructores
// Introducción a C#, por Nacho Cabanes
using System;
// ------------------
private int x;
private int y;
private string texto;
La palabra "this" se usa mucho también para que unos objetos "conozcan" a los
otros. Por ejemplo, en un juego de 2 jugadores, podríamos tener una clase
Jugador, heredar de ella las clases Jugador1 y Jugador2, que serán muy parecidas
entre sí, y nos podríamos sentir tentados de hacer que la clase Jugador tenga un
"adversario" como atributo, y que el Jugador1 indique que su adversario es de tipo
Jugador2, y lo contrario para el otro jugador, así:
// Ejemplo_07_05c.cs
// Dos clases que se usan mutuamente: recursividad indirecta
// Introducción a C#, por Nacho Cabanes
using System;
// ------------------
// ------------------
{
public Jugador1()
{
adversario = new Jugador2();
}
}
// ------------------
// Ejemplo_07_05d.cs
// Dos clases que se usan mutuamente: "Main" coordina
// Introducción a C#, por Nacho Cabanes
using System;
// ------------------
}
}
Otra alternativa es que un Jugador le pueda decir a otro "yo soy tu adversario", y
ese "yo" equivaldrá a un "this". En general, eso simplificará el programa principal, a
cambio de complicar ligeramente las clases auxiliares:
// Ejemplo_07_05e.cs
// Dos clases que se usan mutuamente: "this"
// Introducción a C#, por Nacho Cabanes
using System;
// ------------------
public Jugador()
{
}
// Y yo soy su adversario
adversario.SetAdversario( this );
}
Hay que recordar que, en general, cuando una clase contiene a otras, la clase
contenedora sabe los detalles de las clases contenidas (la "casa" conoce a sus
"puertas"), pero las clases contenidas no saben nada de la clase que las contiene
(las "puertas" no saben otros detalles de la "casa" a la que pertenecen). Si
queremos que se puedan comunicar con la clase contenedora, deberemos usar
Revisión 0.99zz – Página 236
Introducción a la programación con C#, por Nacho Cabanes
"this" para que la conozcan, en vez de crear una nueva clase contenedora con
"new", o provocaremos una recursividad indirecta sin fin, como en el primer
ejemplo.
Ejercicios propuestos:
(7.5.1) Crea una versión ampliada del ejercicio 7.4.2, en la que el constructor sin
parámetros de la clase "Trabajador" se apoye en otro constructor que reciba como
parámetro el nombre de esa persona. La versión sin parámetros asignará el valor
"Nombre no detallado" al nombre de esa persona.
(7.5.2) Crea una clase Puerta con un ancho, un alto y un método "MostrarEstado"
que muestre su ancho y su alto. Crea una clase Casa, que contenga 3 puertas y
otro método "MostrarEstado" que escriba "Casa" y luego muestre el estado de sus
tres puertas.
(7.5.3) Crea una clase Casa, con una superficie (por ejemplo, 90 m 2) y un método
"MostrarEstado" que escriba su superficie. Cada casa debe contener 3 puertas. Las
puertas tendrán un ancho, un alto y un método "MostrarEstado" que muestre su
ancho y su alto y la superficie de la casa en la que se encuentran. Crea un
programa de prueba que cree una casa y muestre sus datos y los de sus tres
puertas.
(7.5.4) Amplía el esqueleto de ConsoleInvaders (7.4.4), de modo que el constructor
sin parámetros de la clase Nave se apoye en el constructor con parámetros de la
misma clase, prefijando unas coordenadas que te parezcan las más adecuadas.
que estamos (por ejemplo, una Matriz) y recibiría dos datos de ese mismo tipo
como parámetros:
return nuevaMatriz;
}
Desde "Main", calcularíamos una matriz como suma de otras dos haciendo
simplemente
Eso sí, debes tener presente que "no todo se puede redefinir". Por ejemplo,
puedes pensar en sobrecargar los operadores [ y ] para acceder a un cierto
elemento de la matriz, pero C# no permite redefinir cualquier operador, y los
corchetes están entre los que no podremos cambiar. En su lugar, deberías crear
métodos Get para acceder a una posición y métodos Set para cambiar el valor de
una posición.
Ejercicios propuestos:
(7.6.1) 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. (Nota: en C# puedes
sobrecargar el operador "+", pero no el operador "[]", de modo que tendrás que
crear métodos "get" y "set" para leer los valores de posiciones de la matriz y para
cambiar su valor).
(7.6.2) Si has estudiado los "números complejos", crea una clase "Complejo", que
represente a un número complejo (formado por una parte "real" y una parte
"imaginaria"). Incluye un constructor que reciba ambos datos como parámetros.
Crea también métodos "get" y "set" para leer y modificar los valores de dichos
datos, así como métodos que permitan saber los valores de su módulo y su
argumento. Crea un método que permita sumar un complejo a otro, y redefine el
operador "+" como forma alternativa de realizar esa operación.
(7.6.3) Crea una clase "Fraccion", que represente a una fracción, formada por un
numerador y un denominador. Crea un constructor que reciba ambos datos como
parámetros y otro constructor que reciba sólo el numerador. Crea un método
Escribir, que escriba la fracción en la forma "3 / 2". Redefine los operadores "+", "-",
"*" y "/" para que permitan realizar las operaciones habituales con fracciones.
(7.6.4) Crea una clase "Vector3", que represente a un vector en el espacio de 3
dimensiones. Redefine los operadores "+" y "-" para sumar y restar dos vectores,
"*" para hallar el producto escalar de dos vectores y "%" para calcular su producto
vectorial.
(7.7.1) Crea un proyecto "Space Invaders" con todas las clases que vimos al
principio del tema 6. El proyecto no será jugable, pero deberá compilar
correctamente.
(7.7.2) Crea un proyecto "PersonajeMovil", que será un esqueleto sobre el que se
podría ampliar para crear un juego de plataformas. Existirá una pantalla de
bienvenida, una pantalla de créditos y una partida, en la que el usuario podrá
mover a un personaje (que puede ser representado por una letra X). Las teclas de
movimiento quedan a tu criterio, pero podrían ser "wasd" o las flechas del cursor.
Si quieres que el movimiento sea más suave, puedes investigar sobre
Console.Readkey (que posiblemente habrás usado ya en el proyecto
ConsoleInvaders) como alternativa a Console.ReadLine.
(7.7.3) Crea un proyecto "Laberinto", a partir del proyecto "PersonajeMovil",
añadiendo tres enemigos que se muevan de lado a lado de forma autónoma y un
laberinto de fondo, cuyas paredes no se puedan "pisar" (la clase Laberinto puede
tener métodos que informen sobre si el jugador podría moverse a ciertas
coordenadas X e Y). Si el personaje toca a un enemigo, acabará la partida y se
regresará a la pantalla de bienvenida.
(7.7.4) Crea un proyecto "Laberintos", a partir del proyecto "Laberinto", añadiendo
premios que recoger, tres vidas para nuestro personaje y varios laberintos
distintos que recorrer.
(7.7.5) Crea un proyecto "SistemaDomotico", que simule un sistema domótico para
automatizar ciertas funciones en una casa: apertura y cierre de ventanas y
puertas, encendido de calefacción, etc. Además de esos elementos físicos de la
casa, también existirá un panel de control, desde el que el usuario podría controlar
el resto de elementos, así como programar el comportamiento del sistema (por
8. Manejo de ficheros
8.1. Escritura en un fichero de texto
Cuando queramos manipular un fichero, siempre deberemos hacerlo en tres
pasos:
Abrir el fichero.
Leer datos de él o escribir datos en él.
Cerrar el fichero.
Eso sí, no siempre conseguiremos realizar esas operaciones, así que además
tendremos que comprobar los posibles errores. Por ejemplo, puede ocurrir que
intentemos abrir un fichero que realmente no exista, o que tratemos de escribir en
un dispositivo que sea sólo de lectura.
Vamos a ver un ejemplo, que cree un fichero de texto y escriba algo en él:
// Ejemplo_08_01a.cs
// Escritura en un fichero de texto
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para StreamWriter
fichero = File.CreateText("prueba.txt");
fichero.WriteLine("Esto es una línea");
fichero.Write("Esto es otra");
fichero.WriteLine(" y esto es continuación de la anterior");
fichero.Close();
}
}
// Ejemplo_08_01b.cs
// Escritura en un fichero de texto, con constructor
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para StreamWriter
Existe otra sintaxis, más compacta, que nos permite olvidarnos (hasta cierto
punto) de cerrar el fichero, empleando la orden "using" para delimitar el bloque
que lee datos del fichero, de esta forma:
// Ejemplo_08_01c.cs
// Escritura en un fichero de texto, con "using"
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para StreamWriter
Ejercicios propuestos:
(8.1.1) Crea un programa que vaya leyendo las frases que el usuario teclea y las
guarde en un fichero de texto llamado "registroDeUsuario.txt". Terminará cuando
la frase introducida sea "fin" (esa frase no deberá guardarse en el fichero).
(8.1.2) Crea una versión de la base de datos de ficheros (ejercicio 5.2.3), de modo
que todos los datos se vuelquen a un fichero de texto al terminar la ejecución del
programa.
(8.1.3) Amplia el proyecto Libro (ejercicio 7.7.8), de modo que todos los datos se
vuelquen a un fichero de texto al terminar la ejecución del programa.
// Ejemplo_08_02a.cs
// Lectura de un fichero de texto
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para StreamReader
fichero = File.OpenText("prueba.txt");
linea = fichero.ReadLine();
Console.WriteLine( linea );
Console.WriteLine( fichero.ReadLine() );
fichero.Close();
}
}
// Ejemplo_08_02b.cs
// Lectura de un fichero de texto, con constructor
Revisión 0.99zz – Página 244
Introducción a la programación con C#, por Nacho Cabanes
using System;
using System.IO; // Para StreamReader
// Ejemplo_08_02c.cs
// Lectura de un fichero de texto, con "using"
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para StreamReader
Ejercicios propuestos:
(8.2.1) Crea un programa que lea las tres primeras líneas del fichero creado en el
ejercicio 8.1.1 y las muestre en pantalla. Usa OpenText para abrirlo.
(8.2.2) Crea una versión alternativa del ejercicio 8.2.1, usando el constructor de
StreamReader.
(8.2.3) Crea una versión alternativa del ejercicio 8.2.2, empleando la sintaxis
alternativa de "using".
// Ejemplo_08_03a.cs
// Lectura de un fichero de texto completo
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
public class Ejemplo_08_03a
{
public static void Main()
{
StreamReader fichero;
string linea;
fichero = File.OpenText("prueba.txt");
do
{
linea = fichero.ReadLine();
if (linea != null)
Console.WriteLine( linea );
}
while (linea != null);
fichero.Close();
}
}
Ejercicios propuestos:
(8.3.1) Crea una variante del ejemplo 08_03a, empleando un constructor en vez de
"File.OpenText".
(8.3.2) Crea una variante del ejemplo 08_03a, empleando "using" en vez de "Close".
(8.3.3) Prepara un programa que pregunte un nombre de fichero y muestre en
pantalla el contenido de ese fichero, haciendo una pausa después de cada 24
líneas, para que dé tiempo a leerlo. Cuando el usuario pulse la tecla Intro, se
mostrarán las siguientes 24 líneas, y así sucesivamente hasta que termine el
fichero.
(8.3.4) Amplía la base de datos de ficheros (ejercicio 8.1.2), de modo que los datos
se lean desde fichero (si existe) en el momento de lanzar el programa (puedes usar
try-catch para que el programa no falle en el momento inicial, en el que quizá
// Ejemplo_08_04a.cs
// Añadir a un fichero de texto
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
fichero = File.CreateText("prueba2.txt");
fichero.WriteLine("Primera línea");
fichero.Close();
fichero = File.AppendText("prueba2.txt");
fichero.WriteLine("Segunda línea");
fichero.Close();
}
}
// Ejemplo_08_04b.cs
// Añadir a un fichero de texto, con constructor y "using"
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Ejercicios propuestos:
(8.4.1) Un programa que pida al usuario que teclee frases, y las almacene en el
fichero "log.txt", que puede existir anteriormente (y que no deberá borrarse, sino
añadir al final de su contenido). Cada sesión acabará cuando el usuario pulse Intro
sin teclear nada.
Debemos recordar que, como la barra invertida que se usa en sistemas Windows
para separar los nombres de los directorios, coincide con el carácter de control
que se usa en las cadenas de C y los lenguajes que derivan de él, deberemos
escribir dichas barras invertidas repetidas, así:
Como esta sintaxis puede llegar a resultar incómoda, en C# existe una alternativa:
podemos indicar una arroba (@) justo antes de abrir las comillas, y entonces no
será necesario delimitar los caracteres de control:
Ejercicios propuestos:
(8.5.1) Crea un programa que pida al usuario pares de números enteros y escriba
su suma (con el formato "20 + 3 = 23") en pantalla y en un fichero llamado
"sumas.txt", que se encontrará en un subdirectorio llamado "resultados". Cada vez
// Ejemplo_08_06a.cs
// Saber si un fichero existe
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Ejercicios propuestos:
(8.6.1) Mejora el ejercicio 8.3.4 para que compruebe antes si el fichero existe, y
muestre un mensaje de aviso en caso de que no sea así.
(8.6.2) Mejora el ejemplo 08_06a para que no use "while (true)", sino una variable
booleana de control.
Por ello, una forma más eficaz de comprobar si ha existido algún tipo de error es
comprobar las posibles "excepciones", con las que ya tuvimos un contacto al final
del tema 2.
Típicamente, los pasos que puedan ser problemáticos irán dentro del bloque "try"
y los mensajes de error y/o acciones correctoras estarán en el bloque "catch". Así,
un primer ejemplo, que mostrara todo el contenido de un fichero de texto y que
en caso de error se limitara a mostrar un mensaje de error y a abandonar el
programa, podría ser:
// Ejemplo_08_07a.cs
// Excepciones y ficheros (1)
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
try
{
fichero = File.OpenText(nombre);
do
{
linea = fichero.ReadLine();
if (linea != null)
Console.WriteLine( linea );
Revisión 0.99zz – Página 250
Introducción a la programación con C#, por Nacho Cabanes
}
while (linea != null);
fichero.Close();
}
catch (Exception exp)
{
Console.WriteLine("Ha habido un error: {0}", exp.Message);
return;
}
}
}
El fichero existe y es de sólo lectura (se lanzará una excepción del tipo
"IOException").
La ruta del fichero es demasiado larga (excepción de tipo
"PathTooLongException").
El disco puede estar lleno (IOException).
Así, dentro de cada bloque "catch" podríamos indicar una excepción más concreta
que una simple "Exception", de forma que el mensaje de aviso sea más concreto, o
que podamos dar pasos más adecuados para solucionar el problema:
// Ejemplo_08_07b.cs
// Excepciones y ficheros (2)
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
string linea;
try
{
fichero = File.CreateText(nombre);
fichero.WriteLine( linea );
fichero.Close();
}
catch (PathTooLongException e)
{
Console.WriteLine("Nombre demasiado largo!");
}
catch (IOException e)
{
Console.WriteLine("No se ha podido escribir!");
Console.WriteLine("El error exacto es: {0}", e.Message);
}
}
}
Las excepciones más generales (IOException y, sobre todo, Exception) deberán ser
las últimas que analicemos, y las más específicas deberán ser las primeras.
Hay que recordar que, como la consola se comporta como un fichero de texto
(realmente, como un fichero de entrada y otro de salida), se puede usar
"try…catch" para comprobar ciertos errores relacionados con la entrada de datos,
como cuando no se puede convertir un dato a un cierto tipo (por ejemplo, si
queremos esperamos que introduzca un número, pero, en lugar de ello, tecleado
un texto).
Ejercicios propuestos:
(8.7.1) Un programa que pida al usuario el nombre de un fichero de origen y el de
un fichero de destino, y que vuelque al segundo fichero el contenido del primero,
convertido a mayúsculas. Se debe controlar los posibles errores, como que el
fichero de origen no exista, o que el fichero de destino no se pueda crear.
(8.7.2) Un programa que pida al usuario un número, una operación (+, -, *, /) y un
segundo número, y muestre el resultado de la correspondiente operación. Si se
teclea un dato no numérico, el programa deberá mostrar un aviso y volver a
pedirlo, en vez de interrumpir la ejecución.
(8.7.3) Un programa que pida al usuario repetidamente pares de números y la
operación a realizar con ellos (+, -, *, /) y guarde en un fichero "calculadora.txt" el
resultado de dichos cálculos (con la forma "15 * 6 = 90"). Debe controlar los
posibles errores, como que los datos no sean numéricos, la división entre cero, o
que el fichero no se haya podido crear.
// Ejemplo_08_09a.cs
// Ficheros binarios: lectura de un byte con FileStream
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
try
{
fichero = File.OpenRead(nombre);
unDato = (byte) fichero.ReadByte();
Console.WriteLine("El byte leido es un {0}",
unDato);
fichero.Close();
}
catch (Exception e)
{
Console.WriteLine("Error: "+e.Message);
return;
}
}
}
// Ejemplo_08_09b.cs
// Ficheros binarios: lectura de un byte con FileStream, constructor
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
try
{
FileStream fichero = new FileStream(nombre, FileMode.Open);
byte unDato = (byte) fichero.ReadByte();
Console.WriteLine("El byte leido es un {0}",
unDato);
fichero.Close();
}
catch (Exception e)
{
Console.WriteLine("Error: "+e.Message);
return;
}
}
}
// Ejemplo_08_09c.cs
// Ficheros binarios: lectura de un byte con FileStream, using
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
try
{
using (FileStream fichero = new FileStream(
nombre, FileMode.Open))
{
byte unDato = (byte) fichero.ReadByte();
Console.WriteLine("El byte leido es un {0}",
unDato);
}
}
catch (Exception e)
Revisión 0.99zz – Página 255
Introducción a la programación con C#, por Nacho Cabanes
{
Console.WriteLine("Error: "+e.Message);
return;
}
}
}
Los otros modos de apertura posibles (cuyos nombre en inglés son casi
autoexplicativos) son: Append (añade al final o crea si no existe), Create (crea el
fichero o lo sobreescribe si ya existía), CreateNew (crea si no existía, o falla si
existe), Open (abre si existe o falla si no existe), OpenOrCreate (abre si existe o lo
crea si no existe), Truncate (abre un fichero que debe existir y lo vacía).
Ejercicios propuestos:
(8.9.1) Abre un fichero con extensión EXE (cuyo nombre introducirá el usuario) y
comprueba si realmente se trata de un ejecutable, mirando si los dos primeros
bytes del fichero son un 77 (que corresponde a una letra "M", según el estándar
que marca el código ASCII) y un 90 (una letra "Z"), respectivamente.
(8.9.2) Abre una imagen BMP (cuyo nombre introducirá el usuario) y comprueba si
realmente se trata de un fichero en ese formato, mirando si los dos primeros
bytes del fichero corresponden a una letra "B" y una letra "M", respectivamente.
(8.9.3) Abre una imagen GIF y comprueba si sus tres primeros bytes son las letras
G, I, F.
// Ejemplo_08_10a.cs
// Ficheros binarios: lectura completa de un FileStream
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
La segunda forma de leer hasta el final del fichero es comprobar si el dato leído es
un -1. Cuando esto ocurra, será que señal de que se ha llegado al final del fichero.
El fuente correspondiente podría ser:
// Ejemplo_08_10b.cs
// Ficheros binarios: lectura completa de un FileStream
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Ejercicios propuestos:
(8.10.1) Crea un programa que compruebe si un fichero (cuyo nombre introducirá
el usuario) contiene una cierta letra (también escogida por el usuario).
(8.10.2) Crea un programa que cuente la cantidad de vocales que contiene un
fichero binario.
(8.10.3) Crea un programa que diga si un fichero (binario) contiene una cierta
palabra que introduzca el usuario.
(8.10.4) Crea un programa que extraiga a un fichero de texto todos los caracteres
alfabéticos (códigos 32 a 127, además del 10 y el 13) que contenga un fichero
binario.
Para eso, dentro de la clase "FileStream" tenemos método "Read", que nos
permite leer una cierta cantidad de datos desde el fichero. Le indicaremos en qué
array guardar esos datos, a partir de qué posición del array debe introducir los
datos (no la posición en el fichero, sino en el array, de modo que casi siempre será
0, es decir, al principio del array), y qué cantidad de datos se deben leer. Nos
devuelve un valor, que es la cantidad de datos que se han podido leer realmente
(porque puede ser menos de lo que le hemos pedido, si hay un error de lectura o
si hemos llegado al final del fichero).
// Ejemplo_08_11a.cs
// Ficheros binarios: lectura por bloques de un FileStream
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Ejercicios propuestos:
(8.11.1) Abre un fichero con extensión EXE y comprobar si realmente se trata de
un ejecutable, mirando si los dos primeros bytes del fichero corresponden a una
letra "M" y una letra "Z", respectivamente. Debes leer ambos bytes a la vez, usando
un array.
(8.11.2) Abre una imagen BMP (cuyo nombre introducirá el usuario) y comprueba
si realmente se trata de un fichero en ese formato, mirando si los dos primeros
bytes del fichero corresponden a una letra "B" y una letra "M", respectivamente.
Usa "Read" y un array.
(8.11.3) Abre una imagen GIF y comprueba si sus tres primeros bytes son las letras
G, I, F. Guarda los 3 datos en un array.
(8.11.4) Abre una imagen en formato BMP y comprueba si está comprimida,
mirando el valor del byte en la posición 30 (empezando a contar desde 0). Si ese
valor es un 0 (que es lo habitual), indicará que el fichero no está comprimido.
Deberás leer toda la cabecera (los primeros 54 bytes) con una sola orden.
Para ello, tenemos el método "Seek". A este método se le indican dos parámetros:
la posición a la que queremos saltar, y el punto desde el que queremos que se
cuente esa posición (desde el comienzo del fichero –SeekOrigin.Begin-, desde la
posición actual –SeekOrigin.Current- o desde el final del fichero –SeekOrigin.End-).
La posición es un Int64, porque puede ser un número muy grande e incluso un
número negativo (si miramos desde el final del fichero, porque desde él habrá que
retroceder).
De igual modo, podemos saber en qué posición del fichero nos encontramos,
consultando la propiedad "Position" (y también sabemos que podemos obtener la
longitud del fichero, mirando su propiedad "Length").
// Ejemplo_08_12a.cs
// Ficheros binarios: posición
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
fichero.Close();
}
}
(Nota: existe una propiedad "CanSeek" que nos permite saber si el fichero que
hemos abierto permite realmente que nos movamos a unas posiciones u otras).
Ejercicios propuestos:
(8.12.1) Abre un fichero con extensión EXE y comprueba si su segundo byte
corresponde a una letra "Z", sin leer su primer byte.
(8.12.2) Abre una imagen en formato BMP y comprueba si está comprimida,
mirando el valor del byte en la posición 30 (empezando a contar desde 0). Si ese
Revisión 0.99zz – Página 260
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_08_13a.cs
// Ficheros binarios: lectura de un short, con BinaryReader
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Si preferimos hacer esta lectura byte a byte, podremos usar tanto un FileStream
como un BinaryReader, pero deberemos recomponer ese "short", teniendo en
cuenta que, en la mayoría de sistemas actuales, en primer lugar aparece el byte
menos significativo y luego el byte más significativo, de modo que el dato sería
byte1 + byte2*256, así:
// Ejemplo_08_13b.cs
// Ficheros binarios: lectura de un short, byte a byte
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
// Ejemplo_08_13c.cs
// Ficheros binarios: lectura de un short, con BinaryReader, using
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Ejercicios propuestos:
(8.13.1) Abre un fichero con extensión EXE y comprueba si comienza con el entero
corto 23117.
(8.13.2) Abre una imagen en formato BMP y comprueba si comienza con el entero
corto 19778.
// Ejemplo_08_13d.cs
// Ficheros binarios: Seek, con BinaryReader
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Ejercicios propuestos:
(8.13.3) El alto de un fichero BMP es un entero de 32 bits que se encuentra en la
posición 22. Amplía el ejemplo 08_13d, para que muestre también el alto de ese
fichero BMP.
Un fichero BMP está compuesto por las siguientes partes: una cabecera de
fichero, una cabecera del bitmap, una tabla de colores y los bytes que
definirán la imagen.
Reservado 6-7
Reservado 8-9
Inicio de los datos de la imagen 10-13
Tamaño de la cabecera de bitmap 14-17
Anchura (píxeles) 18-21
Altura (píxeles) 22-25
Número de planos 26-27
Tamaño de cada punto 28-29
Compresión (0=no comprimido) 30-33
Tamaño de la imagen 34-37
Resolución horizontal 38-41
Resolución vertical 42-45
Tamaño de la tabla de color 46-49
Contador de colores importantes 50-53
Por tanto, será fácil hacer que se nos muestren algunos detales, como su ancho,
su alto, la resolución y si la imagen está comprimida o no:
// Ejemplo_08_14a.cs
// Información de un fichero BMP, con BinaryReader
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
if (! File.Exists( nombre) )
{
Console.WriteLine("No encontrado!");
}
else
{
BinaryReader fichero = new BinaryReader(
File.Open(nombre, FileMode.Open));
// A continuación: alto
int alto = fichero.ReadInt32();
Console.WriteLine("Alto: {0}", alto);
fichero.Close();
}
}
}
}
// Ejemplo_08_14b.cs
// Información de un fichero BMP, con FileStream
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
if (! File.Exists( nombre) )
{
Console.WriteLine("No encontrado!");
}
else
{
FileStream fichero = File.OpenRead(nombre);
int tamanyoCabecera = 54;
byte[] cabecera = new byte[tamanyoCabecera];
if (cantidadLeida != tamanyoCabecera)
{
Console.WriteLine("No se ha podido leer la cabecera");
}
else
{
// Analizo los dos primeros bytes
char marca1 = Convert.ToChar( cabecera[0] );
char marca2 = Convert.ToChar( cabecera[1] );
Ejercicios propuestos:
(8.14.1) Localiza en Internet información sobre el formato de imágenes PCX. Crea
un programa que diga el ancho, alto y número de colores de una imagen PCX.
(8.14.2) Localiza en Internet información sobre el formato de imágenes GIF. Crea
un programa que diga el subformato, ancho, alto y número de colores de una
imagen GIF.
Vamos a ver un ejemplo que junte todo ello: crearemos un fichero, guardaremos
datos, lo leeremos para comprobar que todo es correcto, añadiremos al final, y
volveremos a leer:
// Ejemplo_08_15a.cs
// Ficheros binarios: escritura en FileStream
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
byte[] datos;
nombre = "datos.dat";
datos = new byte[TAMANYO_BUFFER];
try
{
// Primero creamos el fichero, con algun dato
fichero = File.Create( nombre );
fichero.Write(datos, 0, TAMANYO_BUFFER);
fichero.Close();
}
catch (Exception e)
{
Console.WriteLine("Problemas: "+e.Message);
return;
}
}
}
(Nota: existe una propiedad "CanWrite" que nos permite saber si se puede escribir
en el fichero).
Si queremos que escribir datos básicos de C# (float, int, etc.) en vez de un array de
bytes, podemos usar un "BinaryWriter", que se maneja de forma similar a un
"BinaryReader", con la diferencia de que no tenemos métodos WriteByte,
WriteString y similares, sino un único método "Write", que se encarga de escribir el
dato que le indiquemos, sea del tipo que sea:
// Ejemplo_08_15b.cs
// Ficheros binarios: escritura en BinaryWriter
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
Console.WriteLine("Creando fichero...");
// Primero vamos a grabar datos
try
{
ficheroSalida = new BinaryWriter(
File.Open(nombre, FileMode.Create));
unDatoByte = 1;
unDatoInt = 2;
unDatoFloat = 3.0f;
unDatoDouble = 4.0;
unDatoString = "Hola";
ficheroSalida.Write(unDatoByte);
ficheroSalida.Write(unDatoInt);
ficheroSalida.Write(unDatoFloat);
ficheroSalida.Write(unDatoDouble);
ficheroSalida.Write(unDatoString);
ficheroSalida.Close();
}
catch (Exception e)
{
Console.WriteLine("Problemas al crear: "+e.Message);
return;
}
try
{
ficheroEntrada = new BinaryReader(
File.Open(nombre, FileMode.Open));
unDatoByte = ficheroEntrada.ReadByte();
Console.WriteLine("El byte leido es un {0}",
unDatoByte);
unDatoInt = ficheroEntrada.ReadInt32();
Console.WriteLine("El int leido es un {0}",
unDatoInt);
unDatoFloat = ficheroEntrada.ReadSingle();
Console.WriteLine("El float leido es un {0}",
unDatoFloat);
unDatoDouble = ficheroEntrada.ReadDouble();
Console.WriteLine("El double leido es un {0}",
unDatoDouble);
unDatoString = ficheroEntrada.ReadString();
Console.WriteLine("El string leido es \"{0}\"",
unDatoString);
Console.WriteLine("Volvamos a leer el int...");
ficheroEntrada.BaseStream.Seek(1, SeekOrigin.Begin);
unDatoInt = ficheroEntrada.ReadInt32();
Console.WriteLine("El int leido es un {0}",
unDatoInt);
ficheroEntrada.Close();
}
catch (Exception e)
{
Console.WriteLine("Problemas al leer: "+e.Message);
return;
}
}
}
En este caso hemos usado "FileMode.Create" para indicar que queremos crear el
fichero, en vez de abrir un fichero ya existente. Los modos de fichero que
podemos emplear en un BinaryReader o en un BinaryWriter son los siguientes:
Revisión 0.99zz – Página 270
Introducción a la programación con C#, por Nacho Cabanes
Ejercicios propuestos:
(8.15.1) Crea una copia de un fichero EXE. La copia debe tener el mismo nombre y
extensión BAK.
(8.15.2) Crea un programa que "encripte" el contenido de un fichero BMP,
volcando su contenido a un nuevo fichero, en el que intercambiará los dos
primeros bytes. Para desencriptar, bastará con volver a intercambiar esos dos
bytes, volcando a un tercer fichero.
Una vez que hayamos indicado que queremos leer y escribir del fichero, podremos
movernos dentro de él con "Seek", leer datos con "Read" o "ReadByte", y grabar
datos con "Write" o "WriteByte":
// Ejemplo_08_16a.cs
// Lectura y escritura en fichero binario
// Introducción a C#, por Nacho Cabanes
Revisión 0.99zz – Página 271
Introducción a la programación con C#, por Nacho Cabanes
using System;
using System.IO;
nombre = "datos.dat";
datos = new byte[TAMANYO_BUFFER];
try
{
int posicion = 0;
fichero.Seek(2, SeekOrigin.Begin);
int nuevoDato = fichero.ReadByte();
Console.WriteLine("El tercer byte es un {0}", nuevoDato);
fichero.Seek(2, SeekOrigin.Begin);
fichero.WriteByte( 4 );
fichero.Seek(2, SeekOrigin.Begin);
nuevoDato = fichero.ReadByte();
Console.WriteLine("Ahora el tercer byte es un {0}", nuevoDato);
fichero.Close();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return;
}
}
}
Ejercicios propuestos:
(8.16.1) Crea un programa que "encripte" el contenido de un fichero BMP,
intercambiando los dos primeros bytes (y modificando el mismo fichero). Para
desencriptar, bastará con volver a intercambiar esos dos bytes.
Revisión 0.99zz – Página 272
Introducción a la programación con C#, por Nacho Cabanes
9. Persistencia de objetos
9.1. ¿Por qué la persistencia?
Una forma alternativa de conseguir que la información que manipula un programa
esté disponible para una ejecución posterior es no guardarla en un "fichero
convencional", sino pedir al sistema que se conserve el estado de los objetos que
forman el programa.
// Ejemplo_09_01a.cs
// Primer acercamiento a la persistencia
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
int numero;
{
Persist01 ejemplo = new Persist01();
ejemplo.SetNumero(5);
Console.WriteLine("Valor: {0}", ejemplo.GetNumero());
ejemplo.Guardar( "ejemplo.dat" );
ejemplo2.Cargar( "ejemplo.dat" );
Console.WriteLine("Y ahora: {0}", ejemplo2.GetNumero());
}
}
Valor: 5
Valor 2: 0
Y ahora: 5
Pero esta forma de trabajar se complica mucho cuando nuestro objeto tiene
muchos atributos, y más aún si se trata de bloques de objetos (por ejemplo, un
"array") que a su vez contienen otros objetos. Por eso, existen maneras más
automatizadas y que permiten escribir menos código.
Ejercicios propuestos:
(9.1.1) Amplía la clase Persona (ejercicio 6.2.1), para que permita guardar su
estado y recuperarlo posteriormente.
[Serializable]
public class Persist02
En segundo lugar, como vamos a sobrescribir todo el objeto, en vez de sólo los
atributos, ahora los métodos "Cargar" y "Guardar" ya no pueden pertenecer a esa
misma clase: deberán estar en una clase auxiliar, que se encargue de salvar los
datos y recuperarlos. En este primer ejemplo, nos limitaremos a declararlos
"static" para que sea Main el que se encargue de esas tareas:
// Ejemplo_09_02a.cs
// Ejemplo básico de persistencia
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class Persist02
{
int numero;
Valor: 5
Valor 2: 0
Y ahora: 5
Ejercicios propuestos:
(9.2.1) Crea una variante del ejercicio 9.1.1, que use serialización para guardar y
recuperar los datos.
[Serializable]
public class ClaseAGuardar
{
Ejemplo e;
Y crear una segunda clase, que sea la encargada de guardar y recuperar los datos:
formatter.Serialize(stream, objeto);
stream.Close();
}
De modo que un único fuente que contuviera estas tres clases podría ser:
// Ejemplo_09_03a.cs
// Ejemplo de persistencia
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
// ---------------------------------------------
// La clase "de prueba"
[Serializable]
public class Ejemplo
{
int numero;
// ---------------------------------------------
// La clase "que realmente se va a guardar"
[Serializable]
public class ClaseAGuardar
{
Ejemplo e;
// ---------------------------------------------
// La clase "encargada de guardar"
// ---------------------------------------------
// Y el programa de prueba
s.Guardar(guardador);
// Ejemplo_09_03b.cs
// Ejemplo de persistencia
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
// ---------------------------------------------
// Las dos clases "de prueba": una contiene a otra
[Serializable]
public class MiniEjemplo
{
int dato;
// -----
[Serializable]
public class Ejemplo
{
int numero;
float numero2;
MiniEjemplo[] mini;
public Ejemplo()
{
mini = new MiniEjemplo[100];
for (int i=0; i<100; i++)
{
Revisión 0.99zz – Página 280
Introducción a la programación con C#, por Nacho Cabanes
// ---------------------------------------------
// La clase "que realmente se va a guardar"
[Serializable]
public class ClaseAGuardar
{
Ejemplo e;
// ---------------------------------------------
// La clase "encargada de guardar"
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, objeto);
stream.Close();
}
// ---------------------------------------------
// Y el programa de prueba
Valor: 5
Valor 2: 0
Y ahora: 5
dato 50: 500
Ejercicios propuestos:
(9.3.1) Crea una variante del ejercicio 9.2.1, que use una clase auxiliar para la
serialización.
// Ejemplo_09_04a.cs
// Ejemplo de persistencia (XML)
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
using System.Runtime.Serialization;
// Para la siguiente línea, puede ser necesario añadir a las referencias
// del proyecto "System.Runtime.Serialization.Formatters.Soap.dll"
using System.Runtime.Serialization.Formatters.Soap;
// ---------------------------------------------
// Las clases "de prueba"
[Serializable]
public class MiniEjemplo
{
int dato;
[Serializable]
public class Ejemplo
{
int numero;
float numero2;
MiniEjemplo[] mini;
public Ejemplo()
Revisión 0.99zz – Página 283
Introducción a la programación con C#, por Nacho Cabanes
{
mini = new MiniEjemplo[100];
for (int i=0; i<100; i++)
{
mini[i] = new MiniEjemplo();
mini[i].SetDato( i*2 );
}
}
// ---------------------------------------------
// La clase "que realmente se va a guardar"
[Serializable]
public class ClaseAGuardar
{
Ejemplo e;
// ---------------------------------------------
// La clase "encargada de guardar"
nombre = nombreFich;
}
// ---------------------------------------------
// Y el programa de prueba
Dentro de esta opción, deberemos buscar dentro de las referencias que ya están
previstas para nuestra versión de .Net (por ejemplo, la 4.5) el nombre
"System.Runtime.Serialization.Formatters.Soap" y después aceptar los cambios:
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-
ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-
ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-
ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:ClaseAGuardar id="ref-1"
xmlns:a1="http://schemas.microsoft.com/clr/assem/persist05%2C%20Version%3D0.0
.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<e href="#ref-3"/>
</a1:ClaseAGuardar>
<a1:Ejemplo id="ref-3"
xmlns:a1="http://schemas.microsoft.com/clr/assem/persist05%2C%20Version%3D0.0
.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<numero>5</numero>
<numero2>0</numero2>
<mini href="#ref-4"/>
</a1:Ejemplo>
<SOAP-ENC:Array id="ref-4" SOAP-ENC:arrayType="a1:MiniEjemplo[100]"
xmlns:a1="http://schemas.microsoft.com/clr/assem/persist05%2C%20Version%3D0.0
.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<item href="#ref-5"/>
<item href="#ref-6"/>
<item href="#ref-7"/>
...
<item href="#ref-102"/>
<item href="#ref-103"/>
<item href="#ref-104"/>
</SOAP-ENC:Array>
<a1:MiniEjemplo id="ref-5"
xmlns:a1="http://schemas.microsoft.com/clr/assem/persist05%2C%20Version%3D0.0
.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<dato>0</dato>
</a1:MiniEjemplo>
<a1:MiniEjemplo id="ref-6"
xmlns:a1="http://schemas.microsoft.com/clr/assem/persist05%2C%20Version%3D0.0
.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<dato>2</dato>
</a1:MiniEjemplo>
...
<a1:MiniEjemplo id="ref-104"
xmlns:a1="http://schemas.microsoft.com/clr/assem/persist05%2C%20Version%3D0.0
.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<dato>198</dato>
</a1:MiniEjemplo>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Ejercicios propuestos:
(9.4.1) Crea una variante del ejercicio 9.3.1, que guarde los datos en formato XML.
(9.4.2) Crea una versión ampliada de la "base de datos de ficheros" (ejemplo
04_06a) para que use objetos en vez de struct, y que guarde los datos (en formato
binario) usando persistencia.
(9.4.3) Añade al ejercicio de los trabajadores (6.8.1) la posibilidad de guardar sus
datos en formato XML.
primera base de datos, no pretendemos que sea perfecta, sino sencilla, así que
apenas guardaremos tres datos de cada amigo: el nombre, la dirección y la edad.
Para crear la base de datos que contiene todo usaremos "create database",
seguido del nombre que tendrá la base de datos:
En nuestro caso, nuestra base de datos almacenará una única tabla, que
contendrá los datos de nuestros amigos. Por tanto, el siguiente paso será decidir
qué datos concretos ("campos") guardaremos de cada amigo. Deberemos pensar
también qué tamaño necesitaremos para cada uno de esos datos, porque al
gestor de bases de datos habrá que dárselo bastante cuadriculado. Por ejemplo,
podríamos decidir lo siguiente:
Cada gestor de bases de datos tendrá una forma de llamar a esos tipos de datos.
Por ejemplo, es habitual tener un tipo de datos llamado "VARCHAR" para
referirnos a texto hasta una cierta longitud, y varios tipos de datos numéricos de
distinto tamaño. Si usamos "INT" para indicar que nos basta con un entero no muy
grande, la orden necesaria para crear esta tabla sería:
Este formato nos obliga a indicar valores para todos los campos, y exactamente en
el orden en que se diseñaron. Si no queremos introducir todos los datos, o
queremos hacerlo en otro orden, o no recordamos con seguridad el orden, hay
otra opción: detallar también en la orden "insert" los nombres de cada uno de los
campos, así:
que, con nuestros datos, daría como resultado (en el intérprete de comandos de
algunos gestores de bases de datos, como MySQL):
+--------+-----------+------+
| nombre | direccion | edad |
+--------+-----------+------+
| juan | su casa | 25 |
| pedro | su calle | 23 |
+--------+-----------+------+
Si queremos ver sólo ciertos campos, detallamos sus nombres, separados por
comas:
y obtendríamos
+--------+-----------+
| nombre | direccion |
+--------+-----------+
| juan | su casa |
| pedro | su calle |
+--------+-----------+
Normalmente no querremos ver todos los datos que hemos introducido, sino sólo
aquellos que cumplan cierta condición. Esta condición se indica añadiendo un
apartado WHERE a la orden "select", así:
+--------+-----------+
| nombre | direccion |
+--------+-----------+
| juan | su casa |
+--------+-----------+
A veces no querremos comparar con un texto exacto, sino sólo con parte del
contenido del campo (por ejemplo, porque sólo sepamos un apellido o parte del
nombre de la calle). En ese caso, no compararíamos con el símbolo "igual" (=), sino
que usaríamos la palabra "like", y para las partes que no conozcamos usaremos el
comodín "%", como en este ejemplo:
que nos diría el nombre y la dirección de nuestros amigos llamados que viven en
calles que contengan la palabra "calle", precedida por cualquier texto (%) y con
cualquier texto (%) a continuación:
+--------+-----------+
| nombre | direccion |
+--------+-----------+
| pedro | su calle |
+--------+-----------+
En nuestro caso, para acceder a SQLite desde C#, tenemos disponible alguna
adaptación de la biblioteca original. Una de ellas es System.Data.SQLite, que se
puede descargar de http://system.data.sqlite.org/ y tienes un pequeño proyecto de
ejemplo listo para usar desde Visual Studio en para
http://nachocabanes.com/csharp
Con esta biblioteca, los pasos a seguir para crear una base de datos y guardar
información en una base de datos de SQLite serían:
// EjemploSQLite1.cs
// Ejemplo de acceso a bases de datos con SQLite (1)
// Introducción a C#, por Nacho Cabanes
using System;
// Es necesario añadir la siguiente DLL a la "referencias" del proyecto
using System.Data.SQLite;
// Creamos la tabla
string creacion = "create table personas ("
+" nombre varchar(20),direccion varchar(40),edad int);";
SQLiteCommand cmd = new SQLiteCommand(creacion, conexion);
cmd.ExecuteNonQuery();
Console.WriteLine("Creada.");
}
}
mono ejemploSQLite.exe
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc ejemploSQLite.cs
/r:System.Data.SqLite.dll
Este fichero DLL es de 32 bits, de modo que si usamos una versión de Windows 64
bits puede no funcionar correctamente. La solución, si compilamos desde línea de
comandos, es indicar que es para "plataforma x86", añadiendo la opción
"/platform:x86", así:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc ejemploSQLite.cs
/r:System.Data.SqLite.dll /platform:x86
Si usamos Visual Studio o SharpDevelop, tendríamos que añadir ese fichero DLL a
las "referencias" de nuestro proyecto. Lo podemos hacer desde la ventana del
"Explorador de soluciones", pulsando el botón derecho sobre "References",
"Agregar referencia" y escogiendo el fichero System.Data.SQLite.DLL desde la
pestaña "Examinar":
Para mostrar los datos, el primer y último paso serían casi iguales, pero no los
intermedios:
// EjemploSQLite2.cs
// Ejemplo de acceso a bases de datos con SQLite (2)
// Introducción a C#, por Nacho Cabanes
using System;
Revisión 0.99zz – Página 295
Introducción a la programación con C#, por Nacho Cabanes
Que mostraría:
Nota: en este segundo ejemplo, mostrábamos dato[0] (el primer dato, el nombre)
y dato [2] (el tercer dato, la edad) pero no el dato intermedio, dato[1] (la dirección).
Ejercicios propuestos:
(10.3.1) Crea una versión de la base de datos de ficheros (ejemplo 04_06a) que
realmente guarde en una base de datos (de SQLite) los datos que maneja. Deberá
guardar todos antes de terminar cada sesión de uso, y volverlos a cargar al
comienzo de la siguiente.
Por una parte, podemos tener bloques de información claramente distintos. Por
ejemplo, en una base de datos que guarde la información de una empresa
tendremos datos como los artículos que distribuimos y los clientes que nos los
compran, y estos dos bloques de información no deberían guardarse en una
misma tabla.
Por otra parte, habrá ocasiones en que veamos que los datos, a pesar de que se
podrían clasificar dentro de un mismo "bloque de información" (tabla), serían
redundantes: existiría gran cantidad de datos repetitivos, y esto puede dar lugar a
dos problemas:
Espacio desperdiciado.
Posibilidad de errores al introducir los datos, lo que daría lugar a
inconsistencias:
Veamos un ejemplo:
+---------+-----------+-----------+
| nombre | direccion | ciudad |
+---------+-----------+-----------+
| juan | su casa | alicante |
| alberto | calle uno | alicante |
| pedro | su calle | alicantw |
+---------+-----------+-----------+
Por otra parte, hemos tecleado mal uno de los datos: en la tercera ficha no hemos
indicado "alicante", sino "alicantw", de modo que si hacemos consultas sobre
personas de Alicante, la última de ellas no aparecería. Al teclear menos, es
también más difícil cometer este tipo de errores.
La forma de crear la tabla con esos dos campos y con esa clave primaria sería:
Mientras que la tabla de personas sería casi igual al ejemplo anterior, pero
añadiendo un nuevo dato: el código de la ciudad
direccion varchar(40),
edad decimal(3),
codciudad varchar(3)
);
Para introducir datos, el hecho de que exista una clave primaria no supone ningún
cambio, salvo por el hecho de que no se nos permitiría introducir dos ciudades
con el mismo código:
Cuando queremos mostrar datos de varias tablas a la vez, deberemos hacer unos
pequeños cambios en las órdenes "select" que hemos visto:
Por eso, una consulta básica sería algo parecido (sólo parecido) a:
Pero esto todavía tiene problemas: estamos combinando TODOS los datos de la
tabla de personas con TODOS los datos de la tabla de ciudades, de modo que
obtenemos 3x3 = 9 resultados:
+---------+-----------+-----------+
| nombre | direccion | nombre |
+---------+-----------+-----------+
| juan | su casa | alicante |
| pedro | su calle | alicante |
| alberto | calle uno | alicante |
| juan | su casa | barcelona |
| pedro | su calle | barcelona |
| alberto | calle uno | barcelona |
| juan | su casa | madrid |
| pedro | su calle | madrid |
Revisión 0.99zz – Página 299
Introducción a la programación con C#, por Nacho Cabanes
Pero esos datos no son reales: si "juan" vive en la ciudad de código "a", sólo
debería mostrarse junto al nombre "alicante". Nos falta indicar esa condición: "el
código de ciudad que aparece en la persona debe ser el mismo que el código que
aparece en la ciudad", así:
+---------+-----------+-----------+
| nombre | direccion | nombre |
+---------+-----------+-----------+
| juan | su casa | alicante |
| alberto | calle uno | barcelona |
| pedro | su calle | madrid |
+---------+-----------+-----------+
Ese sí es el resultado correcto. Cualquier otra consulta que implique las dos tablas
deberá terminar comprobando que los dos códigos coinciden. Por ejemplo, para
ver qué personas viven en la ciudad llamada "madrid", haríamos:
+--------+-----------+------+
| nombre | direccion | edad |
+--------+-----------+------+
| pedro | su calle | 23 |
+--------+-----------+------+
Y para saber las personas de ciudades que comiencen con la letra "b", usaríamos
"like":
+---------+-----------+-----------+
| nombre | direccion | nombre |
+---------+-----------+-----------+
| alberto | calle uno | barcelona |
+---------+-----------+-----------+
Revisión 0.99zz – Página 300
Introducción a la programación con C#, por Nacho Cabanes
Si en nuestra tabla puede haber algún dato que se repita, como la dirección,
podemos pedir un listado sin duplicados, usando la palabra "distinct":
// EjemploSQLite3.cs
// Ejemplo de acceso a bases de datos con SQLite (3)
// Introducción a C#, por Nacho Cabanes
using System;
// Es necesario añadir la siguiente DLL a la "referencias" del proyecto
using System.Data.SQLite;
// E insertamos datos
Console.WriteLine(" Introduciendo ciudades");
string insercion = "insert into ciudades values "
+"('a', 'alicante');";
cmd = new SQLiteCommand(insercion, conexion);
int cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");
Revisión 0.99zz – Página 301
Introducción a la programación con C#, por Nacho Cabanes
Console.WriteLine("Datos:");
// Leemos los datos de forma repetitiva
while (datos.Read())
{
// Y los mostramos
Console.WriteLine(" {0} - {1} - {2}",
Convert.ToString(datos[0]), Convert.ToString(datos[1]),
Convert.ToString(datos[2]));
}
Su resultado sería:
Ejercicios propuestos:
(10.4.1) Mejora el ejercicio 10.3.1 para que, además de el nombre del fichero y su
tamaño, guarde una categoría (por ejemplo, "utilidad" o "vídeo"). Estas categorías
estarán almacenadas en una segunda tabla.
Esto borraría todas las personas llamadas "juan" que estén almacenadas en la
tabla "personas".
Para modificar datos de una tabla, el formato habitual es "update tabla set
campo=nuevoValor where condicion".
Y si queremos corregir todas las edades para sumarles un año se haría con
(al igual que habíamos visto para "select" y para "delete", si no indicamos la parte
del "where", los cambios se aplicarán a todos los registros de la tabla).
Ejercicios propuestos:
(10.5.1) Crea una versión del ejercicio 10.5.1 que no guarde todos los datos al salir,
sino que actualice con cada nueva modificación: inserte los nuevos datos inme-
diatamente, permita borrar un registro (reflejando los cambios inmediatamente) y
modificar los datos de un registro (ídem).
+--------+---------+---------+
| 5/2 | 5 div 2 | 5 mod 2 |
+--------+---------+---------+
| 2.5000 | 2 | 1 |
+--------+---------+---------+
La forma más habitual de usar "count" es pidiendo con "count(*)" que se nos
muestren todos los datos que cumplen una condición. Por ejemplo, podríamos
saber cuántas personas tienen una dirección que comience por la letra "s", así:
10.7 Grupos
Puede ocurrir que no nos interese un único valor agrupado para todos los datos
(el total, la media, la cantidad de datos), sino el resultado para un grupo de datos.
Por ejemplo: saber no sólo la cantidad de clientes que hay registrados en nuestra
base de datos, sino también la cantidad de clientes que viven en cada ciudad.
+----------+------+
| count(*) | edad |
+----------+------+
| 1 | 22 |
| 1 | 23 |
| 1 | 25 |
+----------+------+
Pero podemos llegar más allá: podemos no trabajar con todos los grupos posibles,
sino sólo con los que cumplen alguna condición.
La condición que se aplica a los grupos no se indica con "where", sino con "having"
(que se podría traducir como "los que tengan..."). Un ejemplo:
select count(*), edad from personas group by edad having edad > 24;
que mostraría
+----------+------+
| count(*) | edad |
+----------+------+
| 1 | 25 |
+----------+------+
En el lenguaje SQL existe mucho más que lo que hemos visto aquí, pero para
nuestro uso desde C# y SQLite debería ser suficiente.
Ejercicios propuestos:
(10.7.1) Crea una versión del ejercicio 10.4.1 que permita saber cuántos ficheros
hay pertenecientes a cada categoría.
// AgendaSQLite.cs
// Ejemplo de acceso a bases de datos con SQLite: agenda
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para File.Exists
// Constructor
public AgendaSQLite()
{
AbrirBD();
}
// Y creamos la tabla
string creacion = "CREATE TABLE persona "
+ "(nombre VARCHAR(30), direccion VARCHAR(40), "
+ " edad INT );";
SQLiteCommand cmd = new SQLiteCommand(creacion, conexion);
cmd.ExecuteNonQuery();
}
else
{
// Si ya existe, abrimos
conexion = new SQLiteConnection(
"Data
Source=agenda.sqlite;Version=3;New=False;Compress=True;");
conexion.Open();
}
}
try
{
insercion = "INSERT INTO persona " +
"VALUES ('"+nombre+"','"+direccion+"',"+edad+");";
cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
return false; // Si no se ha podido insertar
}
Revisión 0.99zz – Página 307
Introducción a la programación con C#, por Nacho Cabanes
catch (Exception e)
{
return false; // Si no se ha podido insertar (codigo repetido?)
}
return true; // Si todo ha ido bien, devolvemos true
}
~AgendaSQLite()
{
conexion.Close();
}
// ------------------------------------------------------------------
do
{
Console.WriteLine("Escoja una opción...");
Console.WriteLine("1.- Añadir");
Console.WriteLine("2.- Ver todos");
Console.WriteLine("3.- Buscar");
Console.WriteLine("0.- Salir");
opcion = Console.ReadLine();
switch (opcion)
{
case "1":
Console.Write("Nombre? ");
string n = Console.ReadLine();
Console.Write("Dirección? ");
string d = Console.ReadLine();
Console.Write("Edad? ");
int e = Convert.ToInt32(Console.ReadLine());
agenda.InsertarDatos(n, d, e);
break;
case "2":
Console.WriteLine(agenda.LeerTodosDatos());
break;
case "3":
Console.Write("Texto a buscar? ");
string txt = Console.ReadLine();
Console.WriteLine(agenda.LeerBusqueda(txt));
break;
}
} while (opcion != "0");
Ejercicios propuestos:
(10.8.1) Amplía este ejemplo (AgendaSQLite), para que se pueda borrar un dato a
partir de su nombre (que debe coincidir exactamente).
(10.8.2) Amplía el ejemplo 10.8.1, para que se pueda modificar un registro.
(10.8.3) Amplía el ejemplo 10.8.2, para que permita exportar los datos a un fichero
de texto (que contenga el nombre, la dirección y la edad correspondientes a cada
registro en líneas separados), o bien se pueda importar datos desde un fichero de
texto (añadiendo al final de los existentes).
(10.8.4) Amplía el ejemplo 10.8.2 con una tabla de ciudades, que se deberá pedir (y
mostrar) de forma independiente al resto de la dirección.
Por ejemplo, existe un "DataGridView", que nos permite recorrer los datos en una
vista de tabla, y poder hacer modificaciones sobre ellos:
Pero esto lo veremos (con poco detalle) más adelante, cuando entremos en
contacto con el entorno gráfico conocido como "Windows Forms".
Este tipo de variables son sencillas de usar y rápidas... si sólo vamos a manejar
estructuras de datos que no cambien, pero resultan poco eficientes si tenemos
estructuras cuyo tamaño no sea siempre el mismo.
La solución real suele ser crear estructuras dinámicas, que puedan ir creciendo o
disminuyendo según nos interese. En los lenguajes de programación "clásicos",
como C y Pascal, este tipo de estructuras se tienen que crear de forma
básicamente artesanal, mientras que en lenguajes modernos como C#, Java o las
últimas versiones de C++, existen esqueletos ya creados que podemos utilizar con
facilidad.
Las pilas. Una "pila de datos" se comportará de forma similar a una pila de
libros: podemos apilar cosas en la cima, o extraer de la cima. Se supone
que no se puede tomar elementos de otro sitio que no sea la cima, ni
dejarlos en otro sitio distinto. De igual modo, se supone que la pila no tiene
un tamaño máximo definido, sino que puede crecer arbitrariamente.
Las colas. Una "cola de datos" se comportará como las del cine (en teoría):
la gente llega por un sitio (la cola) y sale por el opuesto (la cabeza). Al igual
Las listas, mas versátiles pero más complejas de programar, en las que se
puede añadir elementos en cualquier posición y obtenerlos o borrarlos de
cualquier posición.
Este tipo de estructuras se suele denotar también usando las siglas "LIFO" (Last In
First Out: lo último en entrar es lo primero en salir).
Para utilizar la clase "Stack" y la mayoría de las que veremos en este tema,
necesitamos incluir en nuestro programa una referencia a "System.Collections".
Así, un ejemplo básico que creara una pila, introdujera tres palabras y luego las
volviera a mostrar sería:
// Ejemplo_11_02a.cs
// Ejemplo de clase "Stack" (Pila)
// Introducción a C#, por Nacho Cabanes
using System;
Revisión 0.99zz – Página 312
Introducción a la programación con C#, por Nacho Cabanes
using System.Collections;
yo
soy
Hola,
Como se puede ver en este ejemplo, no hemos indicado que sea una "pila de
strings", sino simplemente "una pila". Por eso, los datos que extraemos son
"objetos", que deberemos convertir al tipo de datos que nos interese utilizando un
"typecast" (conversión forzada de tipos), como en palabra = (string)
miPila.Pop();
"Peek", que mira el valor que hay en la cima, pero sin extraerlo.
"Clear", que borra todo el contenido de la pila.
"Contains", que indica si un cierto elemento está en la pila.
"GetType", para saber de qué tipo son los elementos almacenados en la
pila.
"ToString", que devuelve el elemento actual convertido a un string.
"ToArray", que devuelve toda la pila convertida a un array.
"GetEnumerator", que permite usar "enumeradores" para recorrer la pila,
una funcionalidad que veremos con algún detalle más adelante.
También tenemos una propiedad "Count", que nos indica cuántos
elementos contiene.
Ejercicios propuestos:
(11.2.1) Crea un programa que pida al usuario 5 números enteros y luego los
muestre en orden contrario, utilizando una pila.
(11.2.2) Crea un programa que pida al usuario el nombre de un fichero de texto y
muestre en orden inverso las líneas que lo forma, empleando una pila.
(11.2.3) Crea una clase que imite el comportamiento de una pila, pero usando
internamente un array (si no lo consigues, no te preocupes; en un apartado
posterior veremos una forma de hacerlo).
(11.2.4) La "notación polaca inversa" es una forma de expresar operaciones que
consiste en indicar los operandos antes del correspondiente operador. Por
ejemplo, en vez de "3+4" se escribiría "3 4 +". Es una notación que no necesita
paréntesis y que se puede resolver usando una pila: si se recibe un dato numérico,
éste se guarda en la pila; si se recibe un operador, se obtienen los dos operandos
que hay en la cima de la pila, se realiza la operación y se apila su resultado. El
proceso termina cuando sólo hay un dato en la pila. Por ejemplo, "3 4 +" se
convierte en: apilar 3, apilar 4, sacar dos datos y sumarlos, guardar 7, terminado.
Impleméntalo y comprueba si el resultado de "3 4 6 5 - + * 6 +" es 21.
// Ejemplo_11_03a.cs
// Ejemplo de clase "Queue" (Cola)
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections;
que mostraría:
Hola,
soy
yo
Al igual que ocurría con la pila, la implementación de una cola que incluye C# es
más avanzada que eso, con métodos similares a los de antes:
"Peek", que mira el valor que hay en la cabeza de la cola, pero sin extraerlo.
"Clear", que borra todo el contenido de la cola.
"Contains", que indica si un cierto elemento está en la cola.
"GetType", para saber de qué tipo son los elementos almacenados en la
cola.
"ToString", que devuelve el elemento actual convertido a un string.
"ToArray", que devuelve toda la pila convertida a un array.
"GetEnumerator", que permite usar "enumeradores" para recorrer la cola,
una funcionalidad que veremos con algún detalle más adelante.
Al igual que en la pila, también tenemos una propiedad "Count", que nos
indica cuántos elementos contiene.
Ejercicios propuestos:
(11.3.1) Crea un programa que pida al usuario 5 números reales de doble preisión,
los guarde en una cola y luego los muestre en pantalla.
(11.3.2) Crea un programa que pida frases al usuario, hasta que introduzca una
frase vacía. En ese momento, mostrará todas las frases que se habían introducido.
(11.3.3) Crea un programa que lea el contenido de un fichero de texto, lo
almacene línea por línea en una cola, luego muestre este contenido en pantalla y
finalmente lo vuelque a otro fichero de texto.
En el caso de C#, no tenemos ninguna clase "List" que represente una lista
genérica, pero sí dos variantes especialmente útiles: una lista a cuyos elementos se
puede acceder como a los de un array ("ArrayList") y una lista ordenada
("SortedList").
11.4.1. ArrayList
En un ArrayList, podemos añadir datos en la última posición con "Add", insertar en
cualquier otra con "Insert", recuperar cualquier elemento usando corchetes, o
incluso ordenar toda la lista con "Sort". Vamos a ver un ejemplo de la mayoría de
sus posibilidades:
// Ejemplo_11_04_01a.cs
// Ejemplo de ArrayList
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections;
// Buscamos un elemento
Console.WriteLine( "La palabra \"yo\" está en la posición {0}",
miLista.IndexOf("yo") );
// Ordenamos
miLista.Sort();
// Invertimos la lista
miLista.Reverse();
Contenido actual:
Hola,
soy
yo
La segunda palabra es: soy
Contenido tras insertar:
Hola,
Como estas?
soy
yo
La palabra "yo" está en la posición 3
Revisión 0.99zz – Página 317
Introducción a la programación con C#, por Nacho Cabanes
Casi todo debería resultar fácil de entender, salvo quizá el símbolo ~. Esto se debe
a que BinarySearch devuelve un número negativo cuando el texto que buscamos
no aparece, pero ese número negativo tiene un significado: es el "valor
complementario" de la posición del dato inmediatamente mayor (es decir, el dato
cambiando los bits 0 por 1 y viceversa). En el ejemplo anterior, "posición" vale -2, lo
que quiere decir que el dato no existe, y que el dato inmediatamente mayor está
en la posición 1 (que es el "complemento a 2" del número -2, que es lo que indica
la expresión "~posición"). En el apéndice 3 de este texto hablaremos de cómo se
representan internamente los números enteros, tanto positivos como negativos, y
entonces se verá con detalle en qué consiste el "complemento a 2".
Veremos los operadores a nivel de bits, como ~, en el tema 13, que estará
dedicado a otras características avanzadas de C#.
Ejercicios propuestos:
(11.4.1.1) Crea un programa que lea el contenido de un fichero de texto, lo
almacene línea por línea en un ArrayList, y luego pregunte de forma repetitiva al
usuario qué línea desea ver. Terminará cuando el usuario introduzca "-1".
(11.4.1.2) Crea un programa que lea el contenido de un fichero de texto, lo
almacene línea por línea en un ArrayList, y luego pregunte de forma repetitiva al
usuario qué texto desea buscar y muestre las líneas que contienen ese texto.
Terminará cuando el usuario introduzca una cadena vacía.
(11.4.1.3) Crea un programa que lea el contenido de un fichero de texto, lo
almacene línea por línea en un ArrayList, lo ordene y lo muestre ordenado en
pantalla.
(11.4.1.4) Crea un programa que lea el contenido de un fichero de texto, lo
almacene línea por línea en un ArrayList, luego muestre en pantalla las líneas
Revisión 0.99zz – Página 318
Introducción a la programación con C#, por Nacho Cabanes
impares (primera, tercera, etc.) y finalmente vuelque a otro fichero de texto las
líneas pares (segunda, cuarta, etc.).
(11.4.1.5) Crea una nueva versión de la "bases de datos de ficheros" (ejemplo
04_06a), pero usando ArrayList en vez de un array convencional.
11.4.2. SortedList
En un SortedList, los elementos están formados por una pareja: una clave y un
valor (como en un diccionario: la palabra y su definición). Se puede añadir
elementos con "Add", o acceder a los elementos mediante su índice numérico (con
"GetKey") o mediante su clave (con corchetes), como en este ejemplo:
// Ejemplo_11_04_02a.cs
// Ejemplo de SortedList: Diccionario esp-ing
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections;
Su resultado sería
Ejercicios propuestos:
(11.4.2.1) Crea un programa que, cuando el usuario introduzca el nombre de un
número del 1 al 10 en inglés (por ejemplo, "two"), diga su traducción en español
(por ejemplo, "dos").
(11.4.2.2) Crea un traductor básico de C# a Pascal, que tenga las traducciones
almacenadas en una SortedList (por ejemplo, "{" se convertirá a "begin", "}" se
convertirá a "begin", "WriteLine" se convertirá a "WriteLn", "ReadLine" se convertirá
a "ReadLn", "void" se convertirá a "procedure" y "Console." se convertirá a una
cadena vacía. Úsalo para convertir un abrir un fichero que contenga un fuente en
C# y mostrar su equivalente (que no será perfecto) en Pascal.
// Ejemplo_11_05a.cs
// Ejemplo de Tabla Hash: Diccionario de informática
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections;
// Ejemplo_11_05b.cs
// Ejemplo de Tabla Hash: Diccionario de informática
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections;
Una tabla hash tiene una cierta capacidad inicial, que se amplía automáticamente
cuando es necesario. Como la tabla hash es mucho más rápida cuando está
bastante vacía que cuando está casi llena, podemos usar un constructor
alternativo, en el que se le indica la capacidad inicial que queremos, si tenemos
una idea aproximada de cuántos datos vamos a guardar:
Ejercicios propuestos:
(11.5.1) Crea una versión alternativa del ejercicio 11.4.2.1, pero que tenga las
traducciones almacenadas en una tabla Hash.
(11.5.2) Crea una versión alternativa del ejercicio 11.4.2.2, pero que tenga las
traducciones almacenadas en una tabla Hash.
// Ejemplo_11_06a.cs
// Ejemplo de Enumeradores en una Tabla Hash
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections;
cuyo resultado es
Contenido:
pc = personal computer
byte = 8 bits
kilobyte = 1024 bytes
Para las colecciones "normales", como las pilas y las colas, el tipo de Enumerador a
usar será un IEnumerator, con un campo Current para saber el valor actual:
// Ejemplo_11_06b.cs
// Ejemplo de Enumeradores en una pila (Stack)
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections;
while ( miEnumerador.MoveNext() )
Console.WriteLine("{0}", miEnumerador.Current);
}
}
que escribiría
Contenido:
yo
soy
Hola,
Se puede saber más sobre las estructuras dinámicas que hay disponibles en la
plataforma .Net consultando la referencia en línea de MSDN (mucha de la cual está
sin traducir al español):
http://msdn.microsoft.com/es-es/library/system.collections(en-us,VS.71).aspx#
// Ejemplo_11_07a.cs
// Ejemplo de clase "Pila" basada en un array
// Introducción a C#, por Nacho Cabanes
using System;
// Constructor
public PilaString()
{
posicionPila = 0;
datosPila = new string[MAXPILA];
}
} // Fin de la clase
Ejercicios propuestos:
(11.7.1) Usando esta misma estructura de programa, crea una clase "Cola", que
permita introducir datos (números enteros) y obtenerlos en modo FIFO (el primer
dato que se introduzca debe ser el primero que se obtenga). Debe tener un
método "Encolar" y otro "Desencolar".
(11.7.2) Crear una clase "ListaOrdenada", que almacene un único dato (no un par
clave-valor como los SortedList). Debe contener un método "Insertar", que añadirá
un nuevo dato en orden en el array, y un "Extraer(n)", que obtenga un elemento de
la lista (el número "n"). Deberá almacenar "strings".
(11.7.3) Crea una pila de "doubles", usando internamente un ArrayList en vez de
un array.
(11.7.4) Crea una cola que almacene un bloque de datos (struct, con los campos
que tú elijas) usando un ArrayList.
(11.7.5) Crea una lista ordenada (de "strings") usando un ArrayList.
En ocasiones puede ser interesante algo un poco más rígido, que con las ventajas
de un ArrayList (crecimiento dinámico, múltiples métodos disponibles) esté
adaptado a un tipo de datos, y no necesite una conversión de tipos cada vez que
extraigamos un dato.
using System.Collections.Generic;
Con sólo estos dos cambios, el ejemplo de uso de ArrayList que vimos en el
apartado 11.4.1 funcionaría perfectamente:
Revisión 0.99zz – Página 326
Introducción a la programación con C#, por Nacho Cabanes
// Ejemplo_11_08a.cs
// Ejemplo de List<string>
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections.Generic;
// Buscamos un elemento
Console.WriteLine( "La palabra \"yo\" está en la posición {0}",
miLista.IndexOf("yo") );
// Ordenamos
miLista.Sort();
// Invertimos la lista
miLista.Reverse();
Console.WriteLine( frase );
No sólo tenemos listas. Por ejemplo, también existe un tipo "Dictionary", que
equivale a una tabla Hash, pero en la que las claves y los valores no tienen por qué
ser strings, sino el tipo de datos que nosotros decidamos. Por ejemplo, podemos
usar una cadena como clave, pero un número entero como valor obtenido:
Así, con un diccionario que tenga tanto claves string como valores string,
podríamos crear una versión alternativa del ejemplo 11_05b. Los únicos cambios
serían una declaración parecida a la anterior, el "using" correcto, y cambiar
Contains por ContainsKey:
// Ejemplo_11_08b.cs
// Ejemplo de Dictionary
// Introducción a C#, por Nacho Cabanes
using System;
using System.Collections.Generic;
if (miDiccio.ContainsKey("pc"))
Console.WriteLine( "El significado de PC es: {0}",
miDiccio["pc"]);
else
Console.WriteLine( "No existe la palabra PC");
}
}
Ejercicios propuestos:
(11.8.1) Crea una nueva versión de la "bases de datos de ficheros" (ejemplo
04_06a), pero usando List en vez de un array convencional.
(11.8.2) Crea un programa que lea todo el contenido de un fichero, lo guarde en
una lista de strings y luego lo muestre en orden inverso (de la última línea a la
primera).
int *posicion;
posicion = №
mcs unsafe1.cs
unsafe1.cs(15,31): error CS0227: Unsafe code requires the `unsafe' command
line
option to be specified
Compilation failed: 1 error(s), 0 warnings
Por tanto, deberemos compilar con la opción /unsafe como forma de decir al
compilador "sí, sé que este programa tiene zonas no seguras, pero aun así quiero
compilarlo":
// Ejemplo_11_09_03a.cs
// Primer ejemplo de punteros
// Introducción a C#, por Nacho Cabanes
using System;
// Damos un valor a x
x = 2;
// punteroAEntero será la dirección de memoria de x
punteroAEntero = &x;
// Los dos están en la misma dirección:
Console.WriteLine("x vale {0}", x);
Console.WriteLine("En punteroAEntero hay un {0}", *punteroAEntero);
x vale 2
En punteroAEntero hay un 2
x vale 5
En punteroAEntero hay un 5
Ejercicios propuestos:
(11.9.3.1) Crea un programa que intercambie la posición de memoria en la que se
encuentran dos variables y que luego muestre sus contenidos en pantalla, para
comprobar que no son los mismos que al principio.
// Ejemplo_11_09_04a.cs
// Segundo ejemplo de punteros
// Introducción a C#, por Nacho Cabanes
using System;
Incrementar(&i);
}
// Y mostramos el resultado
Console.WriteLine (i);
}
}
// Ejemplo_11_09_05a.cs
// Tercer ejemplo de punteros: stackalloc
// Introducción a C#, por Nacho Cabanes
using System;
// Rellenamos el array
for (int i = 0; i < tamanyoArray; i++)
{
datos[i] = i*10;
}
// Mostramos el array
for (int i = 0; i < tamanyoArray; i++)
{
Console.WriteLine(datos[i]);
}
}
}
Ejercicios propuestos:
(11.9.5.1) Crea una programa que pida al usuario 5 strings, los almacene usando
"stackalloc" y luego los muestre en orden inverso.
// Ejemplo_11_09_06a.cs
// Cuarto ejemplo de punteros: aritmética de punteros
// Introducción a C#, por Nacho Cabanes
using System;
Ejercicios propuestos:
(11.9.6.1) Crea una programa que pida al usuario 4 números reales, los almacene
usando "stackalloc" y luego los recorra incrementando el puntero a partir de la
posición del primer elemento.
// Ejemplo_11_09_07a.cs
// Quinto ejemplo de punteros: fixed
// Introducción a C#, por Nacho Cabanes
using System;
*posicionDato);
}
Dentro de ese tipo de datos DateTime, tenemos las herramientas para saber el día
(Day), el mes (Month) o el año (Year) de una fecha, entre otros. También podemos
calcular otras fechas sumando a la actual una cierta cantidad de segundos
(AddSeconds), días (AddDays), etc. Un ejemplo básico de su uso sería:
// Ejemplo_12_01a.cs
// Ejemplo básico de manejo de fechas
// Introducción a C#, por Nacho Cabanes
using System;
Algunos de las propiedades más útiles de este tipo de datos son: Now (fecha y
hora actual de este equipo), Today (fecha actual); Day (día del mes), Month
(número de mes), Year (año); Hour (hora), Minute (minutos), Second (segundos),
Millisecond (milisegundos); DayOfWeek (día de la semana: su nombre en inglés,
que se puede convertir en un número del 0-domingo- al 6-sábado- si se fuerza su
tipo a entero, anteponiéndole "(int)"); DayOfYear (día del año).
Para calcular nuevas fechas, podemos usar métodos que generan un nuevo
DateTime, como: AddDays (que aparece en el ejemplo anterior), AddHours,
AddMilliseconds, AddMinutes, AddMonths, AddSeconds, AddHours, o bien un Add
Revisión 0.99zz – Página 337
Introducción a la programación con C#, por Nacho Cabanes
más genérico (para sumar una fecha a otra) y un Subtract también genérico (para
restar una fecha de otra).
// Ejemplo_12_01b.cs
// Diferencia entre dos fechas
// Introducción a C#, por Nacho Cabanes
using System;
TimeSpan diferencia =
fechaActual.Subtract(fechaNacimiento) ;
// Ejemplo_12_01c.cs
// Pausas
// Introducción a C#, por Nacho Cabanes
using System;
using System.Threading;
Ejercicios propuestos:
(12.1.1) Crea una versión mejorada del ejemplo 12_01a, que muestre el nombre
del mes (usa un array para almacenar los nombres y accede a la posición
correspondiente), la hora, los minutos, los segundos y las décimas de segundo (no
las milésimas). Si los minutos o los segundos son inferiores a 10, deberán
mostrarse con un cero inicial, de modo que siempre aparezcan con dos cifras.
(12.1.2) Crea un reloj que se muestre en pantalla, y que se actualice cada segundo
(usando "Sleep"). En esta primera aproximación, el reloj se escribirá con
"WriteLine", de modo que aparecerá en la primera línea de pantalla, luego en la
segunda, luego en la tercera y así sucesivamente (en el próximo apartado veremos
cómo hacer que se mantenga fijo en unas ciertas coordenadas de la pantalla).
(12.1.3) Crea un programa que pida al usuario su fecha de nacimiento, y diga de
qué día de la semana se trataba.
(12.1.4) Crea un programa que muestre el calendario del mes actual (pista:
primero deberás calcular qué día de la semana es el día 1 de este mes). Deberá ser
algo como:
Mayo 2015
lu ma mi ju vi sá do
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
// Ejemplo_12_02a.cs
// Más posibilidades de "System.Console"
// Introducción a C#, por Nacho Cabanes
using System;
{
int posX, posY;
Console.ForegroundColor = ConsoleColor.Blue;
Console.SetCursorPosition(10, 15);
Console.Write("Pulsa 1 o 2: ");
ConsoleKeyInfo tecla;
do
{
tecla = Console.ReadKey(false);
}
while ((tecla.KeyChar != '1') && (tecla.KeyChar != '2'));
(Nota: no todas las posibilidades que se aplican en este ejemplo están disponibles
en la plataforma .Net 1.x, sino a partir de la versión 2).
KeyChar, que representa el carácter que se escribiría al pulsar esa tecla. Por
ejemplo, podríamos hacer if (tecla.KeyChar == '1') ...
Key, que se refiere a la tecla (porque hay teclas que no tienen un carácter
visualizable, como F1 o las teclas de cursor). Por ejemplo, para comprobar
la tecla ESC podríamos hacer if (tecla.Key == ConsoleKey.Escape) ... .
Algunos de los códigos de tecla disponibles son:
o Teclas de edición y control como, como: Backspace (Tecla
RETROCESO), Tab (Tecla TAB), Clear (Tecla BORRAR), Enter (Tecla
ENTRAR), Pause (Tecla PAUSA), Escape (Tecla ESC (ESCAPE)),
Spacebar (Tecla BARRA ESPACIADORA), PrintScreen (Tecla IMPR
PANT), Insert (Tecla INS (INSERT)), Delete (Tecla SUPR (SUPRIMIR))
Revisión 0.99zz – Página 341
Introducción a la programación con C#, por Nacho Cabanes
Console.Readkey hace que el programa se quede parado hasta que se pulse una
tecla. Si queremos hacer algo mientras que el usuario no pulse ninguna tecla,
podemos emplear Console.KeyAvailable para comprobar si ya se ha pulsado
alguna tecla que haya que analizar, como en este ejemplo, que permite mover un
símbolo a izquierda y derecha, pero muestra una animación simple si no pulsamos
ninguna tecla:
// Ejemplo_12_02b.cs
// No bloquear el programa con Console.Readkey
// Introducción a C#, por Nacho Cabanes
Revisión 0.99zz – Página 342
Introducción a la programación con C#, por Nacho Cabanes
using System;
using System.Threading;
do
{
Console.Clear();
Console.SetCursorPosition(posX, posY);
Console.Write( simbolos[ simboloActual ]);
Thread.Sleep(500);
if (Console.KeyAvailable)
{
ConsoleKeyInfo tecla = Console.ReadKey(true);
if (tecla.Key == ConsoleKey.RightArrow) posX++;
if (tecla.Key == ConsoleKey.LeftArrow) posX--;
if (tecla.Key == ConsoleKey.Escape) terminado = true;
}
simboloActual++;
if (simboloActual > 3) simboloActual = 0;
}
while ( ! terminado );
}
}
Al igual que en este ejemplo, será recomendable hacer una pequeña pausa entre
una comprobación de teclas y la siguiente, con Thread.Sleep, tanto para que la
animación no sea demasiado rápida como para no hacer un consumo muy alto de
procesador para tareas poco importantes.
Los colores que tenemos disponibles (y que se deben escribir precedidos con
"ConsoleColor") son: Black (negro), DarkBlue (azul marino), DarkGreen (verde
oscuro) DarkCyan (verde azulado oscuro), DarkRed (rojo oscuro), DarkMagenta
(fucsia oscuro o púrpura), DarkYellow (amarillo oscuro u ocre), Gray (gris),
DarkGray (gris oscuro), Blue (azul), Green (verde), Cyan (aguamarina o verde
azulado claro), Red (rojo), Magenta (fucsia), Yellow (amarillo), White (blanco).
Ejercicios propuestos:
(12.2.1) Crea un programa que muestre una "pelota" (la letra "O") rebotando en
los bordes de la pantalla. Para que no se mueva demasiado rápido, haz una pausa
de 50 milisegundos entre un "fotograma" y otro.
(12.2.2) Crea una versión de la "base de datos de ficheros" (ejemplo 04_06a) que
use colores para ayudar a distinguir los mensajes del programa de las respuestas
del usuario, y que no necesite pulsar Intro tras escoger cada opción.
(12.2.3) Crea un programa que permita "dibujar" en consola, moviendo el cursor
con las flechas del teclado y pulsando "espacio" para dibujar un punto o borrarlo.
(12.2.4) Crea una versión del programa de "dibujar" en consola (12.2.3), que
permita escribir más caracteres (por ejemplo, las letras), así como mostrar ayuda
(pulsando F1), guardar el contenido de la pantalla en un fichero de texto (con F2) o
recuperarlo (con F3).
(12.2.5) Crea una versión mejorada del programa 12.1.2 (mostrar el reloj
actualizado en pantalla, que lo dibuje siempre en la esquina superior derecha de la
pantalla).
// Ejemplo_12_03a.cs
// Crear un directorio
// Introducción a C#, por Nacho Cabanes
using System.IO;
(la clase Directory está declarada en el espacio de nombres System.IO, por lo que
deberemos añadirlo entre los "using" de nuestro programa).
// Ejemplo_12_03b.cs
// Lista de ficheros en un directorio
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
listaFicheros = Directory.GetFiles(miDirectorio);
foreach(string fichero in listaFicheros)
Console.WriteLine(fichero);
}
}
// Ejemplo_12_03c.cs
// Lista de ficheros en un directorio
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
// Ejemplo_12_03d.cs
// Lista detallada de ficheros en un directorio
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO;
}
}
Ejercicios propuestos:
(12.3.1) Crea un programa que muestre en pantalla el contenido de un fichero de
texto, cuyo nombre escoja el usuario. Si el usuario no sabe el nombre, podrá
pulsar "Intro" y se le mostrará la lista de ficheros existentes en el directorio actual ,
para luego volver a preguntarle el nombre del fichero.
(12.3.2) Crea un programa que cree un fichero de texto a partir del contenido de
todos los ficheros de texto existentes en la carpeta actual.
(12.3.3) Crea un programa que permita "pasear" por la carpeta actual, al estilo del
antiguo "Comandante Norton": mostrará la lista de ficheros y subdirectorios de la
carpeta actual, y permitirá al usuario moverse hacia arriba o abajo dentro de la
lista usando las flechas del cursor. El elemento seleccionado se mostrará en color
distinto del resto.
(12.3.4) Mejora el ejercicio 12.3.3 para que muestre directorios (en primer lugar) y
ficheros (a continuación), y permita entrar a un directorio si se pulsa Intro sobre él
(en ese momento, se actualizará la lista de ficheros y directorios, para mostrar el
contenido del directorio al que se ha accedido).
(12.3.5) Mejora el ejercicio 12.3.4 para que contenga dos paneles, uno al lado del
otro, cada uno de los cuales podrá estar mostrando el contenido de un directorio
distinto. Si se pulsa el "tabulador", cambiará el panel activo.
(12.3.6) Mejora el ejercicio 12.3.5, para que se pueda "seleccionar un fichero"
pulsando "Espacio" o "Insert". Los ficheros seleccionados se mostrarán en un color
distinto. Se podrán deseleccionar volviendo a pulsar "Espacio" o "Insert". Si se
pulsa F5, los ficheros seleccionados en la carpeta actual del panel actual se
copiarán a la carpeta del otro panel. Mientras se están copiando, el programa
debe mostrar una "barra de progreso" de color amarillo, que indicará el porcentaje
de ficheros que ya se han copiado.
proc.WaitForExit();
// Ejemplo_12_04a.cs
// Lanzar otro proceso y esperar
// Introducción a C#, por Nacho Cabanes
using System;
using System.Diagnostics;
Ejercicios propuestos:
(12.4.1) Mejora el ejercicio 12.3.3 (la versión básica del programa "tipo
Comandante Norton") para que, si se pulsa Intro sobre un cierto fichero, lance el
correspondiente proceso.
(12.4.2) Aplica esta misma mejora al ejercicio 12.3.5 (la versión con dos paneles del
programa "tipo Comandante Norton").
(12.4.3) Crea un programa que mida el tiempo que tarda en ejecutarse un cierto
proceso. Este proceso se le indicará como parámetro en la línea de comandos.
// Ejemplo_12_05a.cs
// Información sobre el sistema
// Introducción a C#, por Nacho Cabanes
using System;
using System.Diagnostics;
Ejercicios propuestos:
(12.5.1) Mejora el ejercicio 12.4.2 (la versión más completa del programa "tipo
Comandante Norton") para que se pueda pasar a "navegar" por otra unidad de
disco. Si se pulsa Alt+F1, se mostrará la lista de unidades lógicas, el usuario podrá
escoger una de ellas, y esa pasará a ser la unidad activa en el panel izquierdo. Si se
pulsa Alt+F2, se realizará el mismo proceso, pero cambiará la unidad activa en el
panel derecho.
Como primer ejemplo, vamos a ver cómo podríamos recibir una página web (por
ejemplo, la página principal de "www.nachocabanes.com"), línea a línea como si se
tratara de un fichero de texto (StreamReader), y mostrar sólo las líneas que
contengan un cierto texto (por ejemplo, la palabra "Pascal"):
// Ejemplo_12_06a.cs
// Ejemplo de descarga y análisis de una web:
// Muestra las líneas que contienen "Pascal"
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para Stream
using System.Net; // Para System.Net.WebClient
int contador = 0;
while ( (linea=lector.ReadLine()) != null )
{
contador ++;
if (linea.IndexOf("Pascal") >= 0)
Console.WriteLine("{0}: {1}", contador, linea);
}
conexion.Close();
}
}
Otra posibilidad que tampoco es complicada (aunque sí algo más que ésta última)
es la de comunicar dos ordenadores, para enviar información desde uno y
recibirla desde el otro. Se puede hacer de varias formas. Una de ellas es usando
directamente el protocolo TCP: emplearemos un TcpClient para enviar y un
TcpListener para recibir, y en ambos casos trataremos los datos como un tipo
especial de fichero binario, un NetworkStream:
// Ejemplo_12_06b.cs
// Ejemplo de envio y recepción de frases a través de la red
// Introducción a C#, por Nacho Cabanes
using System;
using System.IO; // Para Stream
using System.Text; // Para Encoding
using System.Net; // Para Dns, IPAddress
using System.Net.Sockets; // Para NetworkStream
conexion.Write(secuenciaLetras, 0, secuenciaLetras.Length);
conexion.Close();
cliente.Close();
}
cliente.Close();
listener.Stop();
return frase;
}
1
Esperando...
Prueba de texto
Recibido
Enviando... Enviado
Esta misma idea se podría usar como base para programas más elaborados, que
comunicaran diferentes equipos (en este caso, la dirección no sería "localhost",
sino la IP del otro equipo), como podría ser un chat o cualquier juego multijugador
en el que hubiera que avisar a otros jugadores de los cambios realizados por cada
uno de ellos.
Esto se puede conseguir a un nivel algo más alto, usando los llamados "Sockets"
en vez de los TcpClient, o de un modo "no fiable", usando el protocolo UDP en vez
de TCP, pero nosotros no veremos más detalles de ninguno de ambos métodos.
Ejercicios propuestos:
(12.6.1) Crea un juego de "3 en raya" en red, para dos jugadores, en el que cada
jugador escoja un movimiento, pero ambos vean un mismo estado del tablero.
(12.6.2) Crea un programa básico de chat, en el que dos usuarios puedan
comunicarse, sin necesidad de seguir turnos estrictos.
(12.6.3) Crea un programa que monitorice cambios en una página web,
comparando el contenido actual con una copia guardada en fichero. Deberá
mostrar en pantalla un mensaje que avise al usuario de si hay cambios o no.
(12.6.4) Crea un programa que descargue todo un sitio web, partiendo de la
página que indique el usuario, analizando los enlaces que contiene y descargando
de forma recursiva las páginas que corresponden a dichos enlaces. Sólo deberá
procesar los enlaces internos (en el mismo sitio web), no las páginas externas
(alojadas en otros sitios web).
(12.6.5) Crea un juego de "barquitos" en red, para dos jugadores, en el que cada
jugador decida dónde quiere colocar sus barcos, y luego cada uno de ellos
"dispare" por turnos, escogiendo una casilla (por ejemplo, "B5"), y siendo avisado
de si ha acertado ("impacto") o no ("agua"). Los barcos se podrán colocar en
horizontal o en vertical sobre un tablero de 8x8, y serán: 4 de longitud 1, 3 de
longitud 2, 2 de longitud 3 y uno de longitud 4. Cada jugador verá el estado de su
propio tablero y los impactos que ha conseguido en el tablero del otro jugador. El
juego terminará cuando uno de los jugadores hunda toda la flota del otro.
(12.6.6) Mejora el juego de "barquitos" (12.6.5) para que el aviso de impacto sea
más detallado ("tocado" o "hundido", según el caso).
(12.6.7) Mejora el juego de "barquitos" completo (12.6.6) para que un jugador
pueda decidir aplazar la partida, y entonces el resultado quede guardado en
ficheo, y en la siguiente sesión se reanude el juego en el punto en el que quedó.
La idea detrás de ese "using" es que puede ocurrir que distintos programadores
en distintos puntos del mundo (o incluso en distintos grupos de un mismo gran
proyecto) creen funciones o clases que se llamen igual, y, si se mezclan fuentes de
distintas procedencias, esto podría dar lugar a programas que no compilaran
correctamente, o, peor aún, que compilaran pero no funcionaran de la forma
esperada.
Por eso, se recomienda usar "espacios de nombres", que permitan distinguir unos
de otros. Por ejemplo, si yo quisiera crear mi propia clase "Console" para el acceso
a la consola, o mi propia clase "Random" para manejo de números aleatorios, lo
razonable es crear un nuevo espacio de nombres, de forma que cualquier usuario
de esas clase pueda simultanear su uso con el de las incluidas en la plataforma
.Net.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
}
using System;
namespace PruebaDeNamespaces
{
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
// Ejemplo_13_01a.cs
// Ejemplo de uso de "namespaces"
// Introducción a C#, por Nacho Cabanes
using System;
namespace ConsolaAmpliada
{
public class Console
{
public static void WriteLine(string texto)
{
System.Console.ForegroundColor = ConsoleColor.Blue;
System.Console.WriteLine("Mensaje: "+texto);
}
}
}
Como se puede ver, este ejemplo tiene dos clases Console, y ambas tienen un
método WriteLine. Una es la original de C#, que invocaríamos con
"System.Console". Otra es la que hemos creado para el ejemplo, que escribe un
texto modifica y en color (ayudándose de System.Console), y que llamaríamos
mediante "ConsolaAmpliada.Console". El resultado es que podemos tener dos
Revisión 0.99zz – Página 355
Introducción a la programación con C#, por Nacho Cabanes
clases Console accesibles desde el mismo programa, sin que existan conflictos
entre ellas. El resultado del programa sería:
Hola
Mensaje: Hola otra vez
// Ejemplo_13_02a.cs
// Operaciones a nivel de bits
// Introducción a C#, por Nacho Cabanes
using System;
El resultado es:
La variable a vale 67
y b vale 33
El complemento de a es: -68
El producto lógico de a y b es: 1
Su suma lógica es: 99
Su suma lógica exclusiva es: 98
Desplacemos a a la izquierda: 134
Desplacemos a a la derecha: 33
Para comprobar que es correcto, podemos convertir al sistema binario esos dos
números y seguir las operaciones paso a paso:
67 = 0100 0011
33 = 0010 0001
0000 0001 = 1
Después hacemos su suma lógica, sumando cada bit, de modo que 1+1 = 1, 1+0 =
1, 0+0 = 0
0110 0011 = 99
La suma lógica exclusiva devuelve un 1 cuando los dos bits son distintos: 1^1 = 0,
1^0 = 1, 0^0 = 0
0110 0010 = 98
Desplazar los bits una posición a la izquierda es como multiplicar por dos:
Desplazar los bits una posición a la derecha es como dividir entre dos:
0010 0001 = 33
¿Qué utilidades puede tener todo esto? Posiblemente, más de las que parece a
primera vista. Por ejemplo: desplazar a la izquierda es una forma muy rápida de
multiplicar por potencias de dos; desplazar a la derecha es dividir por potencias de
Revisión 0.99zz – Página 357
Introducción a la programación con C#, por Nacho Cabanes
x += 2;
x <<= 2;
x &= 2;
x |= 2;
...
Ejercicios propuestos
(13.2.1) Crea un programa que lea un fichero de texto, encripte cada línea
haciendo un XOR de los caracteres que la forman con un cierto dato prefijado (y
pequeño, como 3, por ejemplo) y vuelque el resultado a un nuevo fichero.
13.3. Enumeraciones
Cuando tenemos varias constantes, cuyos valores son números enteros, hasta
ahora estamos dando los valores uno por uno, así:
Hay una forma alternativa de hacerlo, especialmente útil si son números enteros
consecutivos. Se trata de enumerarlos:
(Al igual que las constantes de cualquier otro tipo, se puede escribir en mayúsculas
para recordar "de un vistazo" que son constantes, no variables)
Si queremos que los valores no sean exactamente estos, podemos dar valor a
cualquiera de las contantes, y las siguientes irán aumentando de uno en uno. Por
ejemplo, si escribimos
// Ejemplo_13_03a.cs
// Ejemplo de enumeraciones
// Introducción a C#, por Nacho Cabanes
using System;
y su resultado será:
Nosotros hemos usado enumeraciones muchas veces hasta ahora, sin saber
realmente que lo estábamos haciendo. Por ejemplo, el modo de apertura de un
fichero (FileMode) es una enumeración, por lo que escribimos FileMode.Open.
También son enumeraciones los códigos de color de la consola (como
ConsoleColor.Red) y las teclas de la consola (como ConsoleKey.Escape).
Nota: las enumeraciones existen también en otros lenguajes como C y C++, pero la
sintaxis es ligeramente distinta: en C# es necesario indicar el nombre de la
enumeración cada vez que se usen sus valores (como en diasSemana.MIERCOLES),
mientras que en C se usa sólo el valor (MIERCOLES).
Ejercicios propuestos
(13.3.1) Crea una versión de la base de datos de ficheros con colores (ejercicio
12.2.2) en la que las opciones sean parte de una enumeración.
13.4. Propiedades
Hasta ahora estábamos siguiendo la política de que los atributos de una clase sean
privados, y se acceda a ellos a través de métodos "get" (para leer su valor) y "set"
(para cambiarlo). En el caso de C#, existe una forma alternativa de conseguir el
mismo efecto, empleando las llamadas "propiedades", que tienen una forma
abreviada de escribir sus métodos "get" y "set":
// Ejemplo_13_04a.cs
// Ejemplo de propiedades (1)
// Introducción a C#, por Nacho Cabanes
using System;
// -------------------------
{
get
{
return anchura;
}
set
{
anchura = value;
}
}
// -------------------------
// El "Main" de prueba
public static void Main()
{
EjemploPropiedades ejemplo
= new EjemploPropiedades();
ejemplo.SetAltura(5);
Console.WriteLine("La altura es {0}",
ejemplo.GetAltura() );
ejemplo.Anchura = 6;
Console.WriteLine("La anchura es {0}",
ejemplo.Anchura );
}
}
Una curiosidad: si una propiedad tiene un "get", pero no un "set", será una
propiedad de sólo lectura, no podremos hacer cosas como "Anchura = 4", porque
el programa no compilaría. De igual modo, se podría crear una propiedad de sólo
escritura, definiendo su "set" pero no su "get".
Ejercicios propuestos
(13.4.1) Crea una nueva versión del ejercicio de la clase Persona (6.7.1), en la que
el "nombre" sea una propiedad, con sus correspondientes "get" y "set".
orden breve si todos los caracteres de una cadena son numéricos, o si empieza
por mayúscula y el resto son minúsculas, etc.
// Ejemplo_13_05a.cs
// Ejemplo de expresiones regulares
// Introducción a C#, por Nacho Cabanes
using System;
using System.Text.RegularExpressions;
class PruebaExprRegulares
{
public static bool EsNumeroEntero(String cadena)
{
Regex patronNumerico = new Regex("[^0-9]");
return !patronNumerico.IsMatch(cadena);
}
if (EsNumeroEntero("1942"))
Console.WriteLine("1942 es un número entero");
else
Console.WriteLine("1942 NO es un número entero");
if (EsNumeroEntero2("1942"))
Console.WriteLine("1942 es entero (forma 2)");
else
Console.WriteLine("1942 NO es entero (forma 2)");
if (EsNumeroEntero("23,45"))
Console.WriteLine("23,45 es un número entero");
else
Console.WriteLine("23,45 NO es un número entero");
if (EsNumeroEntero2("23,45"))
Console.WriteLine("23,45 es entero (forma 2)");
else
Console.WriteLine("23,45 NO es entero (forma 2)");
if (EsNumeroConDecimales("23,45"))
Console.WriteLine("23,45 es un número con decimales");
else
Console.WriteLine("23,45 NO es un número con decimales");
if (EsNumeroConDecimales("23,45,67"))
Console.WriteLine("23,45,67 es un número con decimales");
else
Console.WriteLine("23,45,67 NO es un número con decimales");
if (EsAlfabetico("hola"))
Revisión 0.99zz – Página 363
Introducción a la programación con C#, por Nacho Cabanes
Console.WriteLine("hola es alfabetico");
else
Console.WriteLine("hola NO es alfabetico");
if (EsAlfanumerico("hola1"))
Console.WriteLine("hola1 es alfanumerico");
else
Console.WriteLine("hola1 NO es alfanumerico");
}
}
Su salida es:
Las expresiones regulares son algo complejo. Por una parte, su sintaxis puede
llegar a ser difícil de seguir. Por otra parte, el manejo en C# no se limita a buscar,
sino que también permite otras operaciones, como reemplazar unas expresiones
por otras. Como ver muchos más detalles podría hacer el texto demasiado
extenso, puede ser recomendable ampliar información usando la página web de
MSDN (Microsoft Developer Network):
http://msdn.microsoft.com/es-
es/library/system.text.regularexpressions.regex(VS.80).aspx
Ejercicios propuestos
(13.5.1) Crea una función que valide códigos postales españoles (5 cifras
numéricas): devolverá true si la cadena recibida como parámetro es un código
postal válido.
(13.5.2) Crea una función que diga si una cadena que se le pase como parámetro
parece un correo electrónico válido.
// Ejemplo_13_06a.cs
// Operador coma
// Introducción a C#, por Nacho Cabanes
using System;
i vale 0 y j vale 1
i vale 1 y j vale 3
i vale 2 y j vale 5
i vale 3 y j vale 7
i vale 4 y j vale 9
i vale 5 y j vale 11
// Ejemplo_13_07a.cs
// Ejemplo de uso de "var"
// Introducción a C#, por Nacho Cabanes
Revisión 0.99zz – Página 365
Introducción a la programación con C#, por Nacho Cabanes
using System;
var condicion = 5 == 7;
Console.WriteLine("condicion vale {0} y es de tipo {1}",
condicion, condicion.GetType());
var pi = 3.1416;
Console.WriteLine("pi vale {0} y es de tipo {1}",
pi, pi.GetType());
Como se ve en este ejemplo, si necesitáramos saber de qué tipo es una variable (lo
que no es habitual, porque si se usa "var" es para despreocuparnos de esos
detalles), lo podríamos conseguir con "GetType()".
Ejercicios propuestos
(13.7.1) Crea un programa que pida al usuario una cantidad de kilómetros y
muestre su equivalencia en millas. El valor de conversión debe estar en una
variable definida con "var".
(13.7.2) Crea un programa que muestre la primera línea de un fichero de texto.
Tanto el fichero como la línea se deben declarar con "var".
Por ejemplo, podemos obtener los números enteros de un array cuyo valor es
mayor que 10 con:
// Ejemplo_13_08a.cs
// Ejemplo básico de LINQ
// Introducción a C#, por Nacho Cabanes
using System;
using System.Linq;
var result =
from n in datos
where n > 10
select n;
foreach(int i in result)
Console.Write("{0} ", i);
Console.WriteLine();
}
}
// Ejemplo_13_08b.cs
// Segundo ejemplo de LINQ
// Introducción a C#, por Nacho Cabanes
using System;
using System.Linq;
using System.Collections.Generic;
var resultado =
from nombre in nombres
orderby nombre
select nombre;
Profundizar en LINQ es algo que queda fuera del propósito de este texto. Si
quieres saber más, puedes acudir a la propia referencia oficial en línea, en MSDN:
https://msdn.microsoft.com/es-es/library/bb397926.aspx
Ejercicios propuestos
(13.8.1) Crea un programa que pida al usuario varios números enteros, los guarde
en una lista y luego muestre todos los que sean positivos, ordenados, empleando
LINQ.
(13.8.2) Crea un programa que prepare un array de palabras. Luego, el usuario
debe introducir una palabra, y el programa responderá si es palabra está en el
array o no, utilizando LINQ.
Delegados (delegate).
Funciones lambda.
El preprocesador.
Entornos gráficos (Windows Forms, WPF).
Uso en servidores Web (ASP.Net).
…
Para eliminar esos fallos que hacen que un programa no se comporte como
debería, se usan unas herramientas llamadas "depuradores". Estos nos permiten
avanzar paso a paso para ver cómo avanza realmente nuestro programa, y
también nos dejan ver los valores de las variables.
Veremos como ejemplo el caso de Visual Studio 2008 Express, pero las diferencias
con versiones posteriores de Visual Studio deberían ser mínimas.
using System;
namespace EjemploDeOperaciones
{
class Program
{
static void Main(string[] args)
{
int a, b, c;
a = 5;
b = a + 2;
b -= 3;
c = -3;
c *= 2;
++c;
a *= b;
}
}
Revisión 0.99zz – Página 369
Introducción a la programación con C#, por Nacho Cabanes
Para avanzar paso a paso y ver los valores de las variables, entramos al menú
"Depurar". En él aparece la opción "Paso a paso por instrucciones" (al que
corresponde la tecla F11):
Si escogemos esa opción del menú o pulsamos F11, aparece una ventana inferior
con la lista de variables, y un nuevo cursor amarillo, que se queda al principio del
programa:
Aquí hemos avanzado desde el principio del programa, pero eso no es algo
totalmente habitual. Es más frecuente que supongamos en qué zona del programa
se encuentra el error, y sólo queramos depurar una zona de programa. La forma
de conseguirlo es escoger otra de las opciones del menú de depuración: "Alternar
puntos de ruptura" (tecla F9). Aparecerá una marca granate en la línea actual
como alternativa, podemos hacer clic con el ratón en el margen izquierdo del
programa, junto a esa línea):
Si ahora iniciamos la depuración del programa, saltará sin detenerse hasta ese
punto, y será entonces cuando se interrumpa. A partir de ahí, podemos seguir
depurando paso a paso como antes.
float discriminante;
if (discriminante < 0)
{
x1 = -9999;
x2 = -9999;
}
else if (discriminante == 0)
{
x1 = - b / (2*a);
x2 = -9999;
}
else
{
x1 = (float) ((- b + Math.Sqrt(discriminante))/ 2*a);
x2 = (float) ((- b - Math.Sqrt(discriminante))/ 2*a);
}
}
}
Es decir, si alguna solución no existe, se devuelve un valor falso, que no sea fácil
que se obtenga en un caso habitual, como -9999.
x2 -1 = 0 x1 = 1, x2 = -1
x2 = 0 x1 = 0, x2 = No existe (solución única)
Revisión 0.99zz – Página 373
Introducción a la programación con C#, por Nacho Cabanes
x2 -3x = 0 x1 = 3, x2 = 0
2x2 -2 = 0 x1 = 1, x2 = -1
// Ejemplo_14_03a.cs
// Ejemplo de realización de pruebas
// Introducción a C#, por Nacho Cabanes
using System;
float discriminante;
if (discriminante < 0)
{
x1 = -9999;
x2 = -9999;
}
else if (discriminante == 0)
{
x1 = - b / (2*a);
x2 = -9999;
}
else
{
x1 = (float) ((- b + Math.Sqrt(discriminante))/ 2*a);
x2 = (float) ((- b - Math.Sqrt(discriminante))/ 2*a);
}
}
}
Console.Write("Probando x2 - 1 = 0 ...");
ecuacion.Resolver((float)1, (float) 0, (float) -1,
out soluc1, out soluc2);
if ((soluc1 == 1) && (soluc2 == -1))
Console.WriteLine("OK");
Revisión 0.99zz – Página 374
Introducción a la programación con C#, por Nacho Cabanes
else
Console.WriteLine("Falla");
Console.Write("Probando x2 = 0 ...");
ecuacion.Resolver((float)1, (float)0, (float)0,
out soluc1, out soluc2);
if ((soluc1 == 0) && (soluc2 == -9999))
Console.WriteLine("OK");
else
Console.WriteLine("Falla");
Vemos que en uno de los casos, la solución no es correcta. Revisaríamos los pasos
que da nuestro programa, corregiríamos el fallo y volveríamos a lanzar las pruebas
automatizadas. En este caso, el problema es que falta un paréntesis, para dividir
entre (2*a), de modo que estamos diviendo entre 2 y luego multiplicando por a.
// Ejemplo_14_03b.cs
// Segundo ejemplo de realización de pruebas, con Assert
// Introducción a C#, por Nacho Cabanes
using System;
using System.Diagnostics;
float discriminante;
if (discriminante < 0)
{
x1 = -9999;
x2 = -9999;
}
else if (discriminante == 0)
{
x1 = - b / (2*a);
x2 = -9999;
}
else
{
x1 = (float) ((- b + Math.Sqrt(discriminante))/ 2*a);
x2 = (float) ((- b - Math.Sqrt(discriminante))/ 2*a);
}
}
}
Console.Write("Probando x2 - 1 = 0 ...");
ecuacion.Resolver((float)1, (float) 0, (float) -1,
out soluc1, out soluc2);
Debug.Assert((soluc1 == 1) && (soluc2 == -1));
Console.Write("Probando x2 = 0 ...");
ecuacion.Resolver((float)1, (float)0, (float)0,
out soluc1, out soluc2);
Debug.Assert((soluc1 == 0) && (soluc2 == -9999));
La ventaja de crear baterías de pruebas es que es una forma muy rápida de probar
un programa, por lo que se puede aplicar tras cada pocos cambios para
comprobar que todo es correcto. El inconveniente es que NO GARANTIZA que el
programa sea correcto, sino sólo que no falla en ciertos casos. Por ejemplo, si la
batería de pruebas anterior solo contuviera las tres primeras pruebas, no habría
descubierto el fallo del programa.
Por tanto, las pruebas sólo permiten asegurar que el programa falla en caso de
encontrar problemas, pero no permiten asegurar nada en caso de que no se
encuentren problemas: puede que aun así exista un fallo que no hayamos
detectado.
Casi todos esos diagramas caen fuera del alcance de este texto: en una
introducción a la programación se realizan programas de pequeño tamaño, para
los que no es necesaria una gran planificación.
Aun así, hay un tipo de documentación que sí debe estar presente en cualquier
problema: los comentarios que aclaren todo aquello que no sea obvio.
Por eso, este apartado se va a centrar en algunas de las pautas que los expertos
suelen recomendar para los comentarios en los programas, y también veremos
como a partir de estos comentarios se puede generar documentación adicional de
forma casi automática.
http://www.variablenotfound.com/2007/12/13-consejos-para-comentar-tu-
cdigo.html
Ojo a las tabulaciones. Hay editores de texto que usan el carácter ASCII (9) y otros,
lo sustituyen por un número determinado de espacios, que suelen variar según las
preferencias personales del desarrollador. Lo mejor es usar espacios simples o
asegurarse de que esto es lo que hace el IDE correspondiente.
5. Sé correcto
Evita comentarios del tipo "ahora compruebo que el estúpido usuario no haya
introducido un número negativo", o "este parche corrige el efecto colateral
producido por la patética implementación del inepto desarrollador inicial".
Relacionado e igualmente importante: cuida la ortografía.
6. No pierdas el tiempo
No comentes si no es necesario, no escribas nada más que lo que necesites para
transmitir la idea. Nada de diseños realizados a base de caracteres ASCII, ni
florituras, ni chistes, ni poesías, ni chascarrillos.
Hay incluso quien opina que los comentarios que describen un bloque deberían
escribirse antes de codificarlo, de forma que, en primer lugar, sirvan como
referencia para saber qué es lo que hay que hacer y, segundo, que una vez
codificado éstos queden como comentarios para la posteridad. Un ejemplo:
Console.WriteLine("Resultado: " +
new Calculator()
.Set(0)
.Add(10)
.Multiply(2)
.Substract(4)
.Get()
);
Pero en algunos lenguajes modernos, como Java y C#, existe una posibilidad
adicional que puede resultar muy útil: usar comentarios que nos ayuden a crear
de forma automática cierta documentación del programa.
Esta documentación típicamente será una serie páginas HTML enlazadas, o bien
varios ficheros XML.
Por ejemplo, la herramienta (gratuita) Doxygen genera páginas como ésta a partir
de un fuente en C#:
/**
* Personaje: uno de los tipos de elementos graficos del juego
*
* @see ElemGrafico Juego
* @author 1-DAI 2008/09
*/
(...)
Las etiquetas que sugiere Microsoft son <c> <para> <see> <code> <param>
<seealso> <example> <paramref> <summary> <exception> <permission>
<typeparam> <include>* <remarks> <typeparamref> <list> <returns> <value>.
Puedes leer más sobre el uso que se sugiere para cada una de ellas aquí:
https://msdn.microsoft.com/en-us/library/5ast78ax.aspx
Eso sí, suele ocurrir que realmente un texto de 2000 letras que se guarde en el
ordenador ocupe más de 2000 bytes, porque se suele incluir información adicional
sobre los tipos de letra que se han utilizado, cursivas, negritas, márgenes y
formato de página, etc.
Un byte se queda corto a la hora de manejar textos o datos algo más largos, con lo
que se recurre a un múltiplo suyo, el kilobyte, que se suele abreviar Kb o K.
En teoría, el prefijo kilo querría decir "mil", luego un kilobyte debería ser 1000
bytes, pero en los ordenadores conviene buscar por comodidad una potencia de 2
(pronto veremos por qué), por lo que se usa 210 =1024. Así, la equivalencia exacta
es 1 Kb = 1024 bytes.
Los Kb eran unidades típicas para medir la memoria de ordenadores: 640 Kb fue
mucho tiempo la memoria habitual en los primeros IBM PC y equipos similares.
Por otra parte, una página mecanografiada suele ocupar entre 2 K (cerca de 2000
letras) y 4 Kb.
Ejercicios propuestos:
(Ap1.1.1) ¿Cuántas letras se podrían almacenar en una agenda electrónica que
tenga 32 Kb de capacidad?
(Ap1.1.2) Si suponemos que una canción típica en formato MP3 ocupa cerca de
3.500 Kb, ¿cuántas se podrían guardar en un reproductor MP3 que tenga 256 Mb
de capacidad?
(Ap1.1.3) ¿Cuántos diskettes de 1,44 Mb harían falta para hacer una copia de
seguridad de un ordenador que tiene un disco duro de 6,4 Gb? ¿Y si usamos
compact disc de 700 Mb, cuántos necesitaríamos?
(Ap1.1.4) ¿A cuantos CD de 700 Mb equivale la capacidad de almacenamiento de
un DVD de 4,7 Gb? ¿Y la de uno de 8,5 Gb?
Un bit es demasiado pequeño para un uso normal (recordemos: sólo puede tener
dos valores: 0 ó 1), por lo que se usa un conjunto de ellos, 8 bits, que forman un
byte. Las matemáticas elementales (combinatoria) nos dicen que si agrupamos los
bits de 8 en 8, tenemos 256 posibilidades distintas (variaciones con repetición de 2
elementos tomados de 8 en 8: VR2,8):
00000000
00000001
00000010
00000011
Revisión 0.99zz – Página 386
Introducción a la programación con C#, por Nacho Cabanes
00000100
...
11111110
11111111
Por tanto, si en vez de tomar los bits de 1 en 1 (que resulta cómodo para el
ordenador, pero no para nosotros) los utilizamos en grupos de 8 (lo que se conoce
como un byte), nos encontramos con 256 posibilidades distintas, que ya son más
que suficientes para almacenar una letra, o un signo de puntuación, o una cifra
numérica o algún otro símbolo.
Por ejemplo, se podría decir que cada vez que encontremos la secuencia 00000010
la interpretaremos como una letra A, y la combinación 00000011 como una letra B,
y así sucesivamente (aunque existe un estándar distinto, que comentaremos un
poco más adelante).
También existe una correspondencia entre cada grupo de bits y un número del 0
al 255: si usamos el sistema binario de numeración (que aprenderemos dentro de
muy poco), en vez del sistema decimal, tenemos que:
Aun así, hay un inconveniente con el código ASCII: sólo los primeros 127 números
son estándar. Eso quiere decir que si escribimos un texto en un ordenador y lo
llevamos a otro, las letras básicas (A a la Z, 0 al 9 y algunos símbolos) no
cambiarán, pero las letras internacionales (como la Ñ o las vocales con acentos)
puede que no aparezcan correctamente, porque se les asignan números que no
son estándar para todos los ordenadores. Por eso, existen estándares más
modernos, como UTF-8, que comentaremos en el siguiente apartado.
Revisión 0.99zz – Página 387
Introducción a la programación con C#, por Nacho Cabanes
Nota: Eso de que realmente el ordenador trabaja con ceros y unos, por lo que le
resulta más fácil manejar los números que son potencia de 2 que los números que
no lo son, es lo que explica que el prefijo kilo no quiera decir "exactamente mil",
sino que se usa la potencia de 2 más cercana: 210 =1024. Por eso, la equivalencia
exacta es 1 K = 1024 bytes.
Ejercicios propuestos:
(Ap1.2.1) Un número entero largo de 64 bits, ¿cuántos bytes ocupa?
(Ap1.2.2) En una conexión de red de 100 Mbits/segundo, ¿cuánto tiempo tardaría
en enviar 630 Mbytes de datos?
El código ASCII estándar es de 7 bits, lo que hace que cada grupo de bits desde el
0000000 hasta el 1111111 (0 a 127 en decimal) corresponda siempre a la misma
letra.
Por ejemplo, en cualquier ordenador que use código ASCII, la secuencia de bits
1000001 (65 en decimal) corresponderá a la letra "A" (a, en mayúsculas).
Los códigos estándar "visibles" van del 32 al 127. Los códigos del 0 al 31 son
códigos de control: por ejemplo, el caracter 7 (BEL) hace sonar un pitido, el
caracter 13 (CR) avanza de línea, el carácter 12 (FF) expulsa una página en la
impresora (y borra la pantalla en algunos ordenadores), etc.
| 0 1 2 3 4 5 6 7 8 9 |
| 000 NUL SOH STX ETX EOT ENQ ACK BEL BS HT |
| 010 LF VT FF CR SO SI DLE DC1 DC2 DC3 |
| 020 DC4 NAK SYN ETB CAN EM SUB ESC FS GS |
| 030 RS US SP ! " # $ % & ' |
| 040 ( ) * + , - . / 0 1 |
| 050 2 3 4 5 6 7 8 9 : ; |
| 060 < = > ? @ A B C D E |
| 070 F G H I J K L M N O |
| 080 P Q R S T U V W X Y |
| 090 Z [ \ ] ^ _ ` a b c |
| 100 d e f g h i j k l m |
| 110 n o p q r s t u v w |
| 120 x y z { | } ~ • |
Hoy en día, casi todos los ordenadores incluyen un código ASCII extendido de 8
bits, que permite 256 símbolos (del 0 al 255), lo que permite que se puedan usar
también vocales acentuadas, eñes, y otros símbolos para dibujar recuadros, por
ejemplo, aunque estos símbolos que van del número 128 al 255 no son estándar,
Una alternativa más moderna es el estándar UTF-8, que permite usar caracteres
de más de 8 bits (típicamente 16 bits, que daría lugar a 65.536 símbolos distintos),
lo que permite que la transferencia de información entre distintos sistemas no
tenga problemas de distintas interpretaciones.
Ejercicios propuestos:
(Ap2.1) Crea un programa en C# que muestre una tabla ASCII básica, desde el
carácter 32 hasta el 127, en filas de 16 caracteres cada una.
Para los ordenadores no es cómodo contar hasta 10. Como partimos de "casillas
de memoria" que están completamente vacías (0) o completamente llenas (1), sólo
les es realmente cómodo contar con 2 cifras: 0 y 1.
Por eso, dentro del ordenador cualquier número se deberá almacenar como ceros
y unos, y entonces los números se deberán desglosar en potencias de 2 (el
llamado "sistema binario"):
13 = 1 · 8 + 1 · 4 + 0 · 2 + 1 · 1
o, más detallado:
3 2 1 0
13 = 1 · 2 + 1 · 2 + 0 · 2 + 1 · 2
Convertir un número de decimal a binario resulta algo menos intuitivo. Una forma
sencilla es ir dividiendo entre las potencias de 2, y coger todos los cocientes de las
divisiones:
Si "juntamos" los cocientes que hemos obtenido, aparece el número binario que
buscábamos:
¿Y se pueden hacer operaciones con números binarios? Sí, casi igual que en
decimal:
Ejercicios propuestos:
(Ap3.1.1) Expresa en sistema binario los números decimales 17, 101, 83, 45.
(Ap3.1.2) Expresa en sistema decimal los números binarios de 8 bits: 01100110,
10110010, 11111111, 00101101
(Ap3.1.3) Suma los números 01100110+10110010, 11111111+00101101.
Comprueba el resultado sumando los números decimales obtenidos en el ejercicio
anterior.
(Ap3.1.4) Multiplica los números binarios de 4 bits 0100·1011, 1001·0011.
Comprueba el resultado convirtiéndolos a decimal.
(Ap3.1.5) Crea un programa en C# que convierta a binario los números (en base
10) que introduzca el usuario.
(Ap3.1.6) Crea un programa en C# que convierta a base 10 los números binarios
que introduzca el usuario.
Por eso, se han buscado otros sistemas de numeración que resulten más
"compactos" que el sistema binario cuando haya que expresar cifras
medianamente grandes, pero que a la vez mantengan con éste una
correspondencia algo más sencilla que el sistema decimal. Los más usados son el
sistema octal y, sobre todo, el hexadecimal.
de modo que
2 1 0
254 = 3 · 8 + 7 · 8 + 6 · 8
o bien
Hemos conseguido otra correspondencia que, si bien nos resulta a nosotros más
incómoda que usar el sistema decimal, al menos es más compacta: el número 254
ocupa 3 cifras en decimal, y también 3 cifras en octal, frente a las 8 cifras que
necesitaba en sistema binario.
Pero además existe una correspondencia muy sencilla entre el sistema octal y el
sistema binario: si agrupamos los bits de 3 en 3, el paso de binario a octal es
rapidísimo
de modo que
o bien
Ejercicios propuestos:
(Ap3.2.1)
}
}
Ejercicios propuestos
(Ap4.3.1) Amplía el ejemplo 2 de SDL (sdl02.cs) para que al moverse alterne entre
(al menos) dos imágenes, para dar una impresión de movimiento más real.
Por ejemplo, podemos buscar que el programa anterior quede simplemente así:
// sdl03.cs
// Tercer acercamiento a SDL: clases auxiliares
// Introducción a C#, por Nacho Cabanes
// Parte repetitiva
bool terminado = false;
short x = 400;
short y = 300;
Revisión 0.99zz – Página 405
Introducción a la programación con C#, por Nacho Cabanes
do
{
// Miramos si se ha pulsado alguna flecha del cursor
if (h.TeclaPulsada(Hardware.TECLA_ARR))
y -= 2;
if (h.TeclaPulsada(Hardware.TECLA_ABA))
y += 2;
if (h.TeclaPulsada(Hardware.TECLA_IZQ))
x -= 2;
if (h.TeclaPulsada(Hardware.TECLA_DER))
x += 2;
if (h.TeclaPulsada(Hardware.TECLA_ESC))
terminado = true;
img.MoverA(x, y);
// Dibujar en pantalla
h.BorrarPantallaOculta();
h.DibujarImagenOculta(img);
h.VisualizarOculta();
// Y esperamos 20 ms
h.Pausa(20);
} while (!terminado);
}
}
Para eso necesitaríamos una clase "Hardware", que oculte los detalles del acceso a
la pantalla y al teclado. Podría ser así:
// hardware.cs
// Clases auxiliar Hardware para SDL
// Introducción a C#, por Nacho Cabanes
IntPtr pantallaOculta;
// Inicializamos SDL
Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING);
pantallaOculta = Sdl.SDL_SetVideoMode(
anchoPantalla,
Revisión 0.99zz – Página 406
Introducción a la programación con C#, por Nacho Cabanes
altoPantalla,
bitsColor,
flags);
~Hardware()
{
Sdl.SDL_Quit();
}
// Definiciones de teclas
public static int TECLA_ESC = Sdl.SDLK_ESCAPE;
public static int TECLA_ARR = Sdl.SDLK_UP;
public static int TECLA_ABA = Sdl.SDLK_DOWN;
Revisión 0.99zz – Página 407
Introducción a la programación con C#, por Nacho Cabanes
Y una clase "Imagen" podría ocultar ese "IntPtr", además de añadirle datos
adicionales, como la posición en la que se encuentra esa imagen, y otros que más
adelante nos resultarán útiles, como el ancho y el alto (que nos servirán para
comprobar colisiones):
// imagen.cs
// Clases auxiliar Imagen para SDL
// Introducción a C#, por Nacho Cabanes
Ejercicios propuestos
(Ap4.4.1) Descompón en clases el ejercicio Ap4.3.1.
// sdl04.cs
// Cuarto acercamiento a SDL: bucle de juego
// Introducción a C#, por Nacho Cabanes
using Tao.Sdl;
using System; // Para IntPtr (puntero: imágenes, etc)
void inicializar()
{
h = new Hardware(800, 600, 24, pantallaCompleta);
img = new Imagen("personaje.bmp", 50, 50);
x = 400;
y = 300;
void comprobarTeclas()
{
if (h.TeclaPulsada(Hardware.TECLA_ARR))
y -= 2;
if (h.TeclaPulsada(Hardware.TECLA_ABA))
y += 2;
if (h.TeclaPulsada(Hardware.TECLA_IZQ))
x -= 2;
if (h.TeclaPulsada(Hardware.TECLA_DER))
x += 2;
if (h.TeclaPulsada(Hardware.TECLA_ESC))
terminado = true;
img.MoverA(x, y);
}
void comprobarColisiones()
{
// Todavia no comprobamos colisiones con enemigos
// ni con obstaculos
}
void moverElementos()
{
// Todavia no hay ningun elemento que se mueva solo
}
void dibujarElementos()
{
h.BorrarPantallaOculta();
h.DibujarImagenOculta(img);
h.VisualizarOculta();
}
void pausaFotograma()
{
// Esperamos 20 ms
h.Pausa(20);
}
bool partidaTerminada()
{
return terminado;
}
// Bucle de juego
do
{
j.comprobarTeclas();
j.moverElementos();
Revisión 0.99zz – Página 410
Introducción a la programación con C#, por Nacho Cabanes
j.comprobarColisiones();
j.dibujarElementos();
j.pausaFotograma();
} while (!j.partidaTerminada());
}
}
Ejercicios propuestos
(Ap4.5.1) Refina el ejercicio Ap4.4.1, para que tenga un fuente modular, siguiente
un bucle de juego clásico.
SdlTtf.TTF_Init();
El tipo de letra estará accesible a través de un puntero genérico "IntPtr", igual que
ocurría con las imágenes, por lo que, igual que hicimos con éstas, podemos crear
una clase auxiliar, llamada "Fuente":
// fuente.cs
// Clases auxiliar Fuente para SDL
// Introducción a C#, por Nacho Cabanes
Y ya lo podemos usar desde nuestro juego. Por una parte, cargaremos el tipo de
letra (con el tamaño que deseemos) dentro del "inicializar":
void inicializar()
{
h = new Hardware(800, 600, 24, pantallaCompleta);
img = new Imagen("personaje.bmp", 50, 50);
letra = new Fuente("FreeSansBold.ttf", 18);
x = 400;
y = 300;
}
Y escribiremos el texto que nos interese, en las coordenadas que queramos y con
el color que nos apetezca, desde el método "dibujarElementos":
void dibujarElementos()
{
h.BorrarPantallaOculta();
Revisión 0.99zz – Página 412
Introducción a la programación con C#, por Nacho Cabanes
h.DibujarImagenOculta(img);
h.EscribirTextoOculta("Texto de ejemplo",
200, 100, // Coordenadas
255, 0, 0, // Color: rojo
letra);
h.VisualizarOculta();
}
Ejercicios propuestos
(Ap4.6.1) Amplía el ejercicio Ap4.5.1, para que muestre una "puntuación" en
pantalla (que, por ahora, siempre será cero).
if (imgPersonaje.ColisionCon(imgEnemigo))
{
terminado = true;
}
if (imgPersonaje.ColisionCon(imgPremio))
{
puntos += 10;
xPremio = (short)generadorAzar.Next(750);
yPremio = (short)generadorAzar.Next(550);
imgPremio.MoverA(xPremio, yPremio);
}
xEnemigo += velocXEnemigo;
yEnemigo += velocYEnemigo;
imgEnemigo.MoverA(xEnemigo, yEnemigo);
// sdl06.cs
// Sexto acercamiento a SDL: colisiones
// Introducción a C#, por Nacho Cabanes
using Tao.Sdl;
using System; // Para IntPtr (puntero: imágenes, etc)
Fuente letra;
bool terminado = false;
short x, y;
short xEnemigo, yEnemigo, velocXEnemigo, velocYEnemigo;
short xPremio, yPremio;
int puntos;
Random generadorAzar;
void inicializar()
{
h = new Hardware(800, 600, 24, pantallaCompleta);
imgPersonaje = new Imagen("personaje.bmp", 32, 30);
imgEnemigo = new Imagen("enemigo.bmp", 36, 42);
imgPremio = new Imagen("premio.bmp", 36, 14);
letra = new Fuente("FreeSansBold.ttf", 18);
generadorAzar = new Random();
x = 400; y = 300;
xEnemigo = 100; yEnemigo = 100;
velocXEnemigo = 3; velocYEnemigo = -3;
xPremio = (short) generadorAzar.Next(750);
yPremio = (short) generadorAzar.Next(550);
imgPremio.MoverA(xPremio, yPremio);
puntos = 0;
}
void comprobarTeclas()
{
if (h.TeclaPulsada(Hardware.TECLA_ARR))
y -= 4;
if (h.TeclaPulsada(Hardware.TECLA_ABA))
y += 4;
if (h.TeclaPulsada(Hardware.TECLA_IZQ))
x -= 4;
if (h.TeclaPulsada(Hardware.TECLA_DER))
x += 4;
if (h.TeclaPulsada(Hardware.TECLA_ESC))
terminado = true;
imgPersonaje.MoverA(x, y);
}
void comprobarColisiones()
{
// Si toca el premio: puntos y nuevo premio
if (imgPersonaje.ColisionCon(imgPremio))
{
puntos += 10;
xPremio = (short)generadorAzar.Next(750);
yPremio = (short)generadorAzar.Next(550);
imgPremio.MoverA(xPremio, yPremio);
}
void moverElementos()
{
xEnemigo += velocXEnemigo;
yEnemigo += velocYEnemigo;
imgEnemigo.MoverA(xEnemigo, yEnemigo);
// Para que rebote en los extremos de la pantalla
if ((xEnemigo < 10) || (xEnemigo > 750))
velocXEnemigo = (short) (-velocXEnemigo);
if ((yEnemigo < 10) || (yEnemigo > 550))
velocYEnemigo = (short)(-velocYEnemigo);
}
void dibujarElementos()
{
h.BorrarPantallaOculta();
h.DibujarImagenOculta(imgPersonaje);
h.DibujarImagenOculta(imgPremio);
h.DibujarImagenOculta(imgEnemigo);
h.EscribirTextoOculta("Puntos: "+puntos,
200, 100, // Coordenadas
255, 0, 0, // Color: rojo
letra);
h.VisualizarOculta();
}
void pausaFotograma()
{
// Esperamos 20 ms
h.Pausa(20);
}
bool partidaTerminada()
{
return terminado;
}
// Bucle de juego
do
{
j.comprobarTeclas();
j.moverElementos();
j.comprobarColisiones();
j.dibujarElementos();
j.pausaFotograma();
} while (!j.partidaTerminada());
}
}
http://www.nachocabanes.com/fich/descargar.php?nombre=SdlClases04.zip
Ejercicios propuestos
(Ap4.7.1) Amplía el esqueleto Ap4.6.1, para que muestre varios premios en
posiciones al azar. Cada vez que se toque un premio, éste desparecerá y se
obtendrán 10 puntos. Al recoger todos los premios, acabará la partida.
(Ap4.7.2) Amplía el ejercicio Ap4.7.1, para que haya un "margen de la pantalla" y
una serie de obstáculos, que "nos maten": si el personaje los toca, acabará la
partida.
(Ap4.7.3) Amplía el ejercicio Ap4.7.2, para que el movimiento sea continuo: el
jugador no se moverá sólo cuando se pulse una flecha, sino que continuará
moviéndose hasta que se pulse otra flecha (y entonces cambiará de dirección) o
choque con un obstáculo (y entonces acabará la partida).
(Ap4.7.4) Amplía el ejercicio Ap4.7.3, para que el jugador sea una serpiente,
formada por varios segmentos; cada vez que se recoga un premio, la serpiente se
hará más larga en un segmento. Si la "cabeza" de la serpiente toca la cola, la
partida terminará.
(Ap4.7.5) Mejora la jugabilidad del ejercicio Ap4.7.4: el jugador tendrá 3 vidas, en
vez de una; si se recogen todos los premios, comenzará un nuevo nivel, con mayor
cantidad de premios y de obstáculos.
(Ap4.7.6) Añade dificultad creciente al ejercicio Ap4.7.5: cada cierto tiempo
aparecerá un nuevo obstáculo, y en cada nuevo nivel, el movimiento será un poco
más rápido (puedes conseguirlo reduciendo la pausa de final del bucle de juego).
(Ap4.7.7) Añade al ejercicio Ap4.7.6 un tabla de mejores puntuaciones, que se
guardará en fichero y se leerá al principio de cada nueva partida.
Los nuevos ficheros que necesitamos son: SDL_image.dll (el principal), libpng13.dll
(para imágenes PNG), zlib1.dll (auxiliar para el anterior) y jpeg.dll (si queremos usar
imágenes JPG).
Revisión 0.99zz – Página 417
Introducción a la programación con C#, por Nacho Cabanes
imagen = SdlImage.IMG_Load("personaje.png");
Ejercicios propuestos
(Ap4.8.1) Mejora el juego de la serpiente, para que tenga una imagen JPG de fondo
de la pantalla, y que los premios sean imágenes PNG con transparencia, a través
de las que se pueda ver dicho fondo.
Ejercicios propuestos
(Ap4.9.1) Crea una versión del juego de la serpiente en la que la lógica esté
repartida entre varias clases que colaborar: JuegoSerpiente (que contendrá el
Main), Presentacion, PantallaAyuda, PantallaCreditos, Nivel, Personaje, Premio.
Cuando lanzamos Visual Studio 2010, aparece una pantalla que nos muestra los
últimos proyectos que hemos realizado y nos da la posibilidad de crear un nuevo
proyecto:
Al elegir un "Nuevo proyecto", se nos preguntará de qué tipo queremos que sea,
así como su nombre. En nuestro caso, será una "Aplicación de Windows Forms",
y como nombre se nos propondrá algo como "WindowsFormsApplication1", que
nosotros podríamos cambiar por "AplicacionVentanas1" (como siempre, sin
espacios ni acentos, que suelen ser caracteres no válidos en un identificador -
aunque Visual Studio sí permitiría los acentos-):
Revisión 0.99zz – Página 420
Introducción a la programación con C#, por Nacho Cabanes
Si nuestra pantalla está así de vacía, nos interesará tener un par de herramientas
auxiliares. La primera es el "Explorador de soluciones", que nos permitirá pasear
por las distintas clases que forman nuestro proyecto. La segunda es el "Cuadro de
herramientas", que nos permitirá añadir componentes visuales, como botones,
menús y listas desplegables. Ambos los podemos añadir desde el menú "Ver":
posición. De igual modo, hacemos clic en Label para indicar que queremos una
etiqueta de teto, y luego clic en cualquier punto de nuestro formulario. Nuestra
ventana debería estar quedando así:
Ahora vamos a hacer que nuestro programa responda a un clic del ratón sobre el
botón. Para eso, hacemos doble clic sobre dicho botón, y aparecerá un esqueleto
de programa que podemos rellenar:
Antes de ver con más detalle por qué ocurre todo esto y mejorarlo, vamos a
comprobar que funciona. Pulsamos el botón de "Iniciar depuración" en la barra de
herramientas:
Ahora vamos a crear un segundo proyecto, un poco más elaborado y que nos
ayude a entender mejor el funcionamiento del entorno.
Una vez que estén colocados, vamos a cambiar sus propiedades. Sabemos
cambiar el texto de un Label desde código, pero también podemos hacerlo desde
el diseñador visual. Basta con tener la ventana de "Propiedades", que podemos
obtener al pulsar el botón derecho sobre uno de los componentes o desde el
menú "Ver". Ahí tenemos la propiedad "Text", que es el texto que cada
componente muestra en pantalla. Haciendo clic en cada uno de ellos, podemos
cambiar el texto de todos (incluyendo el "nombre de la ventana"):
Es muy mejorable, pero algunas de las mejoras son parte de los ejercicios
propuestos.
Ejercicios propuestos:
(Ap5.2.1) Mejora el ejemplo anterior para que las casillas tengan el texto alineado
a la derecha y para que no falle si las casillas están vacías o contienen texto en
lugar de números.
(Ap5.2.2) Crea una nueva versión del programa, que no solo muestre la suma, sino
también la resta, la multiplicación, la división y el resto de la división.
(Ap5.2.3) Un programa que muestre una ventana con un recuadro de texto, un
botón y 3 etiquetas. En el recuadro de texto se escribirá un número (en sistema
decimal). Cada vez que se pulse el botón, se mostrará en las 3 etiquetas de texto el
equivalente de ese número en binario, octal y hexadecimal.
(Ap5.2.4) Haz una variante del programa anterior, que no use un botón para
convertir el número a binario, octal y hexadecimal. En vez de eso, cada vez que en
el recuadro de texto se pulse una tecla, se mostrará automáticamente en las 3
etiquetas de texto el equivalente del número actual. Pista: al hacer doble clic sobre
una casilla de texto, aparecerá el evento "TextChanged", que es el que se lanza
cuando se modifica el texto que contiene un TextBox.
Y la tercera variante permite indicar además el que será el botón por defecto:
Ejercicios propuestos:
(Ap5.3.1) Mejora el ejercicio Ap5.2.1, para que muestre un mensaje de aviso si las
casillas están vacías o contienen texto en lugar de números.
Vamos a crear en ella una casilla de texto (TextBox) en la que no se podrá escribir,
sino que sólo se mostrarán resultados, y un botón que hará que aparezca la
ventana secundaria, en la que sí podremos introducir datos.
Para crear el segundo formulario (la ventana auxiliar), usamos la opción "Agregar
Windows Forms ", del menú "Proyecto":
Nos aparecerá una nueva ventana, en su vista de diseño. Añadimos dos casillas de
texto (TextBox), con sus etiquetas aclaratorias (Label), y el botón de Aceptar:
Además, para que desde la ventana principal se puedan leer los datos de ésta,
podemos hacer que sus componentes sean públicos, o, mejor, crear un método
"Get" que devuelva el contenido de estos componentes. Por ejemplo, podemos
devolver el nombre y el apellido como parte de una única cadena de texto, así:
public MainForm()
{
InitializeComponent();
ventanaIntro = new Introduccion();
}
Ejercicios propuestos
(Ap5.4.1) Crea un programa que descomponga un número como producto de sus
factores primos, usando ventanas.
(Ap5.4.2) Crea un programa que muestre una casilla de texto en la que el usuario
puede introducir frases. Todas estas frases se irán guardando en un fichero de
texto.
(Ap5.4.3) Crea una versión "con ventanas" de la "base de
datos de ficheros" (ejemplo 04_06a).
// Dibujamos
ventanaGrafica.DrawLine(contornoRojo, 200, 100, 300, 400);
ventanaGrafica.FillEllipse(rellenoAzul, new Rectangle(0, 0, 200, 300));
Los métodos para dibujar líneas, rectángulos, elipses, curvas, etc. son parte de la
clase Graphics. Algunos de los métodos que ésta contiene y que pueden ser útiles
para realizar dibujos sencillos son:
Por otra parte, un ejemplo de cómo mostrar una imagen predefinida podría ser:
Esta imagen debería estar en la carpeta del programa ejecutable (que quizá no sea
la misma que el fuente), y puede estar en formato BMP, GIF, PNG, JPG o TIFF.
http://msdn.microsoft.com/es-es/library/system.drawing.graphics_methods.aspx
Ejercicios propuestos
(Ap5.6.1) Crea una versión del "juego del ahorcado" (ejercicio 5.9.1.2) basada en
Windows Forms, en la que, a medida que el usuario falle, se vaya "dibujando un
patíbulo".
Índice alfabético
- @
-, 34 @, 109, 383
[
--, 94 [,] (arrays), 123
[] (arrays), 113
[Serializable], 275
!
!, 52 \
!=, 49
\, 109
#
^
#, 104
^, 356
%
{
%, 34
%=, 96 { y }, 17, 48
& |
&, 356 |, 356
& (dirección de una variable, 330 ||, 52
&&, 52
~
* ~, 214, 356
*, 34
*=, 96 +
+, 34
, ++, 68, 94
,, 364 +=, 96
. <
.Net, 13 <, 49
<<, 356
<> (Generics), 326
/
/, 34 =
/**, 382
=, 37
//, 42
-=, 96
///, 382
==, 49
/=, 96
: >
>, 49
: (goto), 82
>>, 356
? 0
?, 58
0x (prefijo), 106
A Bucles anidados, 69
bug, 369
Abs, 172 burbuja, 145
Acceso aleatorio, 253 Buscar en un array, 118
Acceso secuencial, 253 Button, 422
Acos, 172 byte, 93, 385
Add (ArrayList), 316
AddDays, 337
Al azar, números, 170
C
Aleatorio, acceso, 253 C, 10
Aleatorios, números, 170 C#, 10
algoritmo, 14 Cadena modificable, 138
alto nivel, 9 Cadenas de caracteres, 128
and, 356 Cadenas de texto, 110
Añadir a un array, 118 Cambio de base, 105
Añadir a un fichero, 247 Campo (bases de datos), 290
Apilar, 312 Carácter, 107
Aplicación Windows, 420 Carpetas, 248
Append, 271 case, 60
AppendText, 247 Caso contrario, 50
Arco coseno, 172 catch, 88, 250
Arco seno, 172
Arco tangente, 172
args, 178 Ch
Argumentos de un programa, 177 char, 60, 107
Aritmética de punteros, 334
array, 113
Array de objetos, 221 C
ArrayList, 316
cifrar mensajes, 358
Arrays bidimensionales, 122
Cifras decimales, 103
Arrays de struct, 126
Cifras significativas, 99
arreglo, 113
class, 18
ASCII, 387, 389, 400, 420
Clear, 340
Asignación de valores, 37
Close, 243
Asignación en un "if", 54
código máquina, 9
asignaciones múltiples, 96
Códigos de formato, 103
Asin, 172
Cola, 314
Atan, 172
Colisiones entre imágenes, 413
Atan2, 172
Color de texto, 340
azar, 170
Coma (operador, for), 364
Coma fija, 97
B Coma flotante, 97
Comentarios
BackgroundColor, 340 recomendaciones, 378
bajo nivel, 10 Comentarios de documentación, 381
base, 230 Comillas (escribir), 109
Base numérica, 105 Comparación de cadenas, 137
Bases de datos, 292 CompareTo, 137
Bases de datos con SQLite, 289 compiladores, 12
BaseStream, 262 Compilar con mono, 31
BASIC, 9 Complemento, 356
Begin (SeekOrigin), 259 Complemento a 1, 398
Binario, 105, 391 Complemento a 2, 398
BinaryReader, 261 Consola, 340
BinarySearch, 318 Console, 17, 340
BinaryWriter, 269 ConsoleKeyInfo, 341
bit, 386 constantes, 358
Bloque de programa, 70 constructor, 212
BMP (tipo de fichero), 263 Contador, 65
bool, 111 Contains, 132, 313, 320
Booleanos, 111, 121 Contains (Hash), 321
Borrar en un array, 118 ContainsKey, 321
borrar la pantalla, 340 ContainsValue, 320
break, 60 continue, 77
bucle de juego, 409 Convert, 93, 100
bucle sin fin, 69 Convert.ToInt32, 43
Bucles, 63 Convertir a binario, 105
Revisión 0.99zz – Página 440
Introducción a la programación con C#, por Nacho Cabanes