Está en la página 1de 444

Introduccin a la programacin con C#, por Nacho Cabanes

Introduccin a la programacin
con C#

Este texto es una introduccin a la programacin de ordenadores, usando el


lenguaje C#.

Est organizado de una forma ligeramente distinta a los libros de texto


"convencionales", procurando incluir ejercicios prcticos lo antes posible, para
evitar que un exceso de teora en los primeros temas haga el texto pesado de
seguir.

Este texto ha sido escrito por Nacho Cabanes. Si quiere conseguir la ltima
versin, estar en mi pgina web:

www.nachocabanes.com

Este texto es de libre distribucin ("gratis"). Se puede distribuir a otras personas


libremente, siempre y cuando no se modifique. Si le gustan los formalismos, esto
sale a equivaler a una licencia Creative Commons BY-NC-ND: reconocimiento del
autor, no se puede hacer uso comercial (no se puede "vender" este curso), no se
puede crear obras derivadas.

Este texto se distribuye "tal cual", sin garanta de ningn tipo, implcita ni explcita.
Aun as, mi intencin es que resulte til, as que le rogara que me comunique
cualquier error que encuentre.

Para cualquier sugerencia, no dude en contactar conmigo a travs de mi web.

Revisin actual: 0.99zz

Revisin 0.99zz Pgina 1


Introduccin a la programacin con C#, por Nacho Cabanes

Contenido
Contenido _________________________________________________________________________________ 2
0. Conceptos bsicos sobre programacin ____________________________________________ 9
0.1. Programa y lenguaje _______________________________________________________________________________ 9

0.2. Lenguajes de alto nivel y de bajo nivel ___________________________________________________________ 9

0.3. Ensambladores, compiladores e intrpretes ___________________________________________________ 11

0.4. Pseudocdigo_______________________________________________________________________________________ 14

1. Toma de contacto con C# ____________________________________________________________ 16


1.1. Qu es C #? Qu entorno usaremos? __________________________________________________________ 16

1.2. Escribir un texto en C# ____________________________________________________________________________ 17

1.3. Cmo probar este programa _____________________________________________________________________ 19


1.3.1. Cmo probarlo con Mono en Linux ______________________________________________________ 19
1.3.2. Cmo probarlo con Mono en Windows __________________________________________________ 24
1.3.3. Otros editores ms avanzados para Windows ___________________________________________ 33

1.4. Mostrar nmeros enteros en pantalla __________________________________________________________ 34

1.5. Operaciones aritmticas bsicas ________________________________________________________________ 34


1.5.1. Operadores _____________________________________________________________________________ 34
1.5.2. Orden de prioridad de los operadores ___________________________________________________ 35
1.5.3. Introduccin a los problemas de desbordamiento _______________________________________ 36

1.6. Introduccin a las variables: int _________________________________________________________________ 36


1.6.1. Definicin de variables: nmeros enteros _______________________________________________ 36
1.6.2. Asignacin de valores ___________________________________________________________________ 37
1.6.3. Mostrar el valor de una variable en pantalla _____________________________________________ 38

1.7. Identificadores _____________________________________________________________________________________ 40

1.8. Comentarios ________________________________________________________________________________________ 41

1.9. Datos por el usuario: ReadLine __________________________________________________________________ 43

1.10. using System ______________________________________________________________________________________ 44

1.11. Escribir sin avanzar de lnea ____________________________________________________________________ 45

2. Estructuras de control ________________________________________________________________ 47


2.1. Estructuras alternativas __________________________________________________________________________ 47
2.1.1. if ________________________________________________________________________________________ 47
2.1.2. if y sentencias compuestas ______________________________________________________________ 48
2.1.3. Operadores relacionales: <, <=, >, >=, ==, != _____________________________________________ 49
2.1.4. if-else____________________________________________________________________________________ 50
2.1.5. Operadores lgicos: &&, ||, ! ____________________________________________________________ 52
2.1.6. El peligro de la asignacin en un "if" _____________________________________________________ 54
2.1.7. Introduccin a los diagramas de flujo ____________________________________________________ 55
2.1.8. Operador condicional: ? _________________________________________________________________ 58
2.1.9. switch ___________________________________________________________________________________ 60

2.2. Estructuras repetitivas____________________________________________________________________________ 63


2.2.1. while ____________________________________________________________________________________ 63
2.2.2. do ... while _______________________________________________________________________________ 65
2.2.3. for _______________________________________________________________________________________ 67
2.2.4. Bucles sin fin ____________________________________________________________________________ 69
Revisin 0.99zz Pgina 2
Introduccin a la programacin con C#, por Nacho Cabanes

2.2.5. Bucles anidados _________________________________________________________________________ 69


2.2.6. Repetir sentencias compuestas __________________________________________________________ 70
2.2.7. Contar con letras ________________________________________________________________________ 71
2.2.8. Declarar variables en un "for" ___________________________________________________________ 72
2.2.9. Las llaves son recomendables ___________________________________________________________ 74
2.2.10. Interrumpir un bucle: break ____________________________________________________________ 75
2.2.11. Forzar la siguiente iteracin: continue __________________________________________________ 77
2.2.12. Equivalencia entre "for" y "while" _______________________________________________________ 78
2.2.13. Ejercicios resueltos sobre bucles _______________________________________________________ 80

2.3. Saltar a otro punto del programa: goto _________________________________________________________ 82

2.4. Ms sobre diagramas de flujo. Diagramas de Chapin _________________________________________ 83

2.5. foreach ______________________________________________________________________________________________ 85

2.6. Recomendacin de uso para los distintos tipos de bucle _____________________________________ 86

2.7. Una alternativa para el control errores: las excepciones ____________________________________ 88

3. Tipos de datos bsicos _______________________________________________________________ 92


3.1. Tipo de datos entero ______________________________________________________________________________ 92
3.1.1. Tipos de datos para nmeros enteros ___________________________________________________ 92
3.1.2. Conversiones de cadena a entero _______________________________________________________ 93
3.1.3. Incremento y decremento _______________________________________________________________ 94
3.1.4. Operaciones abreviadas: += _____________________________________________________________ 96
3.1.5. Asignaciones mltiples __________________________________________________________________ 96

3.2. Tipo de datos real __________________________________________________________________________________ 97


3.2.1. Coma fija y coma flotante _______________________________________________________________ 97
3.2.2. Simple y doble precisin _________________________________________________________________ 98
3.2.3. Pedir nmeros reales al usuario ________________________________________________________ 100
3.2.4. Conversin de tipos (typecast) __________________________________________________________ 102
3.2.5. Formatear nmeros ____________________________________________________________________ 103
3.2.6. Cambios de base _______________________________________________________________________ 105

3.3. Tipo de datos carcter __________________________________________________________________________ 107


3.3.1. Leer y mostrar caracteres ______________________________________________________________ 107
3.3.2. Secuencias de escape: \n y otras________________________________________________________ 108

3.4. Toma de contacto con las cadenas de texto _________________________________________________ 110

3.5. Los valores "booleanos" ________________________________________________________________________ 111

4. Arrays, estructuras y cadenas de texto ___________________________________________ 113


4.1. Conceptos bsicos sobre arrays o tablas _____________________________________________________ 113
4.1.1. Definicin de un array y acceso a los datos _____________________________________________ 113
4.1.2. Valor inicial de un array ________________________________________________________________ 114
4.1.3. Recorriendo los elementos de una tabla ________________________________________________ 115
4.1.4. Operaciones habituales con arrays: buscar, aadir, insertar, borrar ____________________ 118
4.1.5. Constantes _____________________________________________________________________________ 121

4.2. Arrays bidimensionales _________________________________________________________________________ 122

4.3. Estructuras o registros __________________________________________________________________________ 125


4.3.1. Definicin y acceso a los datos _________________________________________________________ 125
4.3.2. Arrays de structs _______________________________________________________________________ 126
4.3.3. structs anidados ________________________________________________________________________ 127

4.4. Cadenas de caracteres __________________________________________________________________________ 128


4.4.1. Definicin. Lectura desde teclado ______________________________________________________ 128
4.4.2. Cmo acceder a las letras que forman una cadena _____________________________________ 130

Revisin 0.99zz Pgina 3


Introduccin a la programacin con C#, por Nacho Cabanes

4.4.3. Longitud de la cadena __________________________________________________________________ 130


4.4.4. Extraer una subcadena _________________________________________________________________ 131
4.4.5. Buscar en una cadena __________________________________________________________________ 132
4.4.6. Otras manipulaciones de cadenas ______________________________________________________ 133
4.4.7. Descomponer una cadena en fragmentos ______________________________________________ 135
4.4.8. Comparacin de cadenas _______________________________________________________________ 137
4.4.9. Una cadena modificable: StringBuilder _________________________________________________ 138

4.5. Recorriendo arrays y cadenas con "foreach" ________________________________________________ 139

4.6. Ejemplo completo________________________________________________________________________________ 140

4.7. Ordenaciones simples ___________________________________________________________________________ 145

4.8. Otros editores ms avanzados: Notepad++ y Geany ________________________________________ 151

5. Introduccin a las funciones _______________________________________________________ 156


5.1. Diseo modular de programas: Descomposicin modular _________________________________ 156

5.2. Conceptos bsicos sobre funciones ___________________________________________________________ 156

5.3. Parmetros de una funcin ____________________________________________________________________ 158

5.4. Valor devuelto por una funcin. El valor "void" _____________________________________________ 160

5.5. Variables locales y variables globales _________________________________________________________ 163

5.6. Los conflictos de nombres en las variables __________________________________________________ 165

5.7. Modificando parmetros _______________________________________________________________________ 167

5.8. El orden no importa _____________________________________________________________________________ 169

5.9. Algunas funciones tiles ________________________________________________________________________ 170


5.9.1. Nmeros aleatorios ____________________________________________________________________ 170
5.9.2. Funciones matemticas ________________________________________________________________ 171
5.9.3. Pero hay muchas ms funciones ______________________________________________________ 174

5.10. Recursividad ____________________________________________________________________________________ 174

5.11. Parmetros y valor de retorno de "Main" ___________________________________________________ 177

6. Programacin orientada a objetos ________________________________________________ 180


6.1. Por qu los objetos? ____________________________________________________________________________ 180

6.2. Objetos y clases en C#___________________________________________________________________________ 186

6.3. Proyectos a partir de varios fuentes __________________________________________________________ 191

6.4. La herencia _______________________________________________________________________________________ 205

6.5. Visibilidad _________________________________________________________________________________________ 208

6.6. Constructores y destructores __________________________________________________________________ 212

6.7. Polimorfismo y sobrecarga _____________________________________________________________________ 215

6.8. Orden de llamada de los constructores _______________________________________________________ 216

7. Utilizacin avanzada de clases ____________________________________________________ 219


7.1. La palabra "static" _______________________________________________________________________________ 219

7.2. Arrays de objetos ________________________________________________________________________________ 221

7.3. Funciones virtuales. La palabra "override" __________________________________________________ 225

7.4. Llamando a un mtodo de la clase "padre" __________________________________________________ 229

7.5. La palabra "this": el objeto actual _____________________________________________________________ 232


Revisin 0.99zz Pgina 4
Introduccin a la programacin con C#, por Nacho Cabanes

7.6. Sobrecarga de operadores ______________________________________________________________________ 237

7.7. Proyectos completos propuestos ______________________________________________________________ 239

8. Manejo de ficheros _________________________________________________________________ 242


8.1. Escritura en un fichero de texto _______________________________________________________________ 242

8.2. Lectura de un fichero de texto _________________________________________________________________ 244

8.3. Lectura hasta el final del fichero ______________________________________________________________ 245

8.4. Aadir a un fichero existente __________________________________________________________________ 247

8.5. Ficheros en otras carpetas _____________________________________________________________________ 248

8.6. Saber si un fichero existe _______________________________________________________________________ 249

8.7. Ms comprobaciones de errores: excepciones _______________________________________________ 250

8.8. Conceptos bsicos sobre ficheros _____________________________________________________________ 253

8.9. Leer un byte de un fichero binario ____________________________________________________________ 253

8.10. Leer hasta el final de un fichero binario ____________________________________________________ 256

8.11. Leer bloques de datos de un fichero binario ________________________________________________ 258

8.12. La posicin en el fichero _______________________________________________________________________ 259

8.13. Leer datos nativos ______________________________________________________________________________ 261

8.14. Ejemplo completo: leer informacin de un fichero BMP __________________________________ 263

8.15. Escribir en un fichero binario _________________________________________________________________ 267

8.16. Leer y escribir en un mismo fichero binario ________________________________________________ 271

9. Persistencia de objetos_____________________________________________________________ 274


9.1. Por qu la persistencia? _______________________________________________________________________ 274

9.2. Creando un objeto "serializable" ______________________________________________________________ 275

9.3. Empleando clases auxiliares ___________________________________________________________________ 277

9.4. Volcando a un fichero de texto ________________________________________________________________ 283

10. Acceso a bases de datos relacionales ___________________________________________ 289


10.1. Nociones mnimas de bases de datos relacionales ________________________________________ 289

10.2. Nociones mnimas de lenguaje SQL __________________________________________________________ 289


10.2.1. Creando la estructura _________________________________________________________________ 289
10.2.2. Introduciendo datos __________________________________________________________________ 290
10.2.3. Mostrando datos ______________________________________________________________________ 291

10.3. Acceso a bases de datos con SQLite __________________________________________________________ 292

10.4. Un poco ms de SQL: varias tablas ___________________________________________________________ 297


10.4.1. La necesidad de varias tablas _________________________________________________________ 297
10.4.2. Las claves primarias ___________________________________________________________________ 298
10.4.3. Enlazar varias tablas usando SQL ______________________________________________________ 298
10.4.4. Varias tablas con SQLite desde C# _____________________________________________________ 301

10.5. Borrado y modificacin de datos _____________________________________________________________ 303

10.6. Operaciones matemticas con los datos ____________________________________________________ 304

10.7 Grupos ____________________________________________________________________________________________ 305

10.8 Un ejemplo completo con C# y SQLite ________________________________________________________ 306

Revisin 0.99zz Pgina 5


Introduccin a la programacin con C#, por Nacho Cabanes

10.9. Nociones mnimas de acceso desde un entorno grfico __________________________________ 310

11. Punteros y gestin dinmica de memoria ______________________________________ 311


11.1. Por qu usar estructuras dinmicas? _______________________________________________________ 311

11.2. Una pila en C# __________________________________________________________________________________ 312

11.3. Una cola en C# __________________________________________________________________________________ 314

11.4. Las listas _________________________________________________________________________________________ 316


11.4.1. ArrayList ______________________________________________________________________________ 316
11.4.2. SortedList _____________________________________________________________________________ 319

11.5. Las "tablas hash" _______________________________________________________________________________ 320

11.6. Los "enumeradores" ___________________________________________________________________________ 322

11.7. Cmo "imitar" una pila usando "arrays" ____________________________________________________ 324

11.8. Introduccin a los "generics" _________________________________________________________________ 326

11.9. Los punteros en C# _____________________________________________________________________________ 329


11.9.1. Qu es un puntero? __________________________________________________________________ 329
11.9.2. Zonas "inseguras": unsafe _____________________________________________________________ 330
11.9.3. Uso bsico de punteros _______________________________________________________________ 331
11.9.4. Zonas inseguras _______________________________________________________________________ 332
11.9.5. Reservar espacio: stackalloc ___________________________________________________________ 333
11.9.6. Aritmtica de punteros ________________________________________________________________ 334
11.9.7. La palabra "fixed" _____________________________________________________________________ 335

12. Algunas bibliotecas adicionales de uso frecuente _____________________________ 337


12.1. Fecha y hora. Temporizacin _________________________________________________________________ 337

12.2. Ms posibilidades de la "consola" ____________________________________________________________ 340

12.3. Lectura de directorios _________________________________________________________________________ 344

12.4. El entorno. Llamadas al sistema______________________________________________________________ 347

12.5. Datos sobre "el entorno" ______________________________________________________________________ 348

12.6. Algunos servicios de red. ______________________________________________________________________ 349

13. Otras caractersticas avanzadas de C# _________________________________________ 354


13.1. Espacios de nombres ___________________________________________________________________________ 354

13.2. Operaciones con bits ___________________________________________________________________________ 356

13.3. Enumeraciones _________________________________________________________________________________ 358

13.4. Propiedades _____________________________________________________________________________________ 360

13.5. Introduccin a las expresiones regulares. __________________________________________________ 361

13.6. El operador coma _______________________________________________________________________________ 364

13.7. Variables con tipo implicito ___________________________________________________________________ 365

13.8. Contacto con LINQ _____________________________________________________________________________ 366

13.9. Lo que no vamos a ver... _______________________________________________________________________ 368

14. Depuracin, prueba y documentacin de programas __________________________ 369


14.1. Conceptos bsicos sobre depuracin ________________________________________________________ 369

14.2. Depurando desde Visual Studio ______________________________________________________________ 369

14.3. Prueba de programas __________________________________________________________________________ 372


Revisin 0.99zz Pgina 6
Introduccin a la programacin con C#, por Nacho Cabanes

14.4. Documentacin bsica de programas _______________________________________________________ 377


14.4.1. Consejos para comentar el cdigo_____________________________________________________ 378
14.4.2. Generacin de documentacin a partir del cdigo fuente. _____________________________ 381

Apndice 1. Unidades de medida y sistemas de numeracin _____________________ 385


Ap1.1. bytes, kilobytes, megabytes... ______________________________________________________________ 385

Ap1.2. Unidades de medida empleadas en informtica (2): los bits ____________________________ 386

Apndice 2. El cdigo ASCII ___________________________________________________________ 389


Apndice 3. Sistemas de numeracin. _______________________________________________ 391
Ap3.1. Sistema binario _______________________________________________________________________________ 391

Ap3.2. Sistema octal __________________________________________________________________________________ 393

Ap3.3. Sistema hexadecimal ________________________________________________________________________ 395

Ap3.4. Representacin interna de los enteros negativos ________________________________________ 397

Apndice 4. SDL ________________________________________________________________________ 400


Ap4.1. Juegos con Tao.SDL ___________________________________________________________________________ 400

Ap4.2. Mostrar una imagen esttica _______________________________________________________________ 400

Ap4.3. Una imagen que se mueve con el teclado _________________________________________________ 403

Ap4.4. Simplificando con clases auxiliares ________________________________________________________ 405

Ap4.5. Un fuente ms modular: el "bucle de juego" ______________________________________________ 409

Ap4.6. Escribir texto __________________________________________________________________________________ 411

Ap4.7. Colisiones simples ____________________________________________________________________________ 413

Ap4.8. Imgenes PNG y JPG __________________________________________________________________________ 417

Ap4.9. Por dnde seguir? ___________________________________________________________________________ 418

Apndice 5. Contacto con los entornos grficos ____________________________________ 420


Ap5.1. Creacin de formularios, botones y etiquetas ____________________________________________ 420

Ap5.2. Cambios de apariencia. Casillas de texto para sumar dos nmeros ___________________ 424

Ap5.3. Usando ventanas predefinidas _____________________________________________________________ 428

Ap5.4. Una aplicacin con dos ventanas ___________________________________________________________ 430

Ap5.5. Otros componentes visuales ________________________________________________________________ 433

Ap5.6. Dibujando con Windows Forms _____________________________________________________________ 434

Revisiones de este texto ______________________________________________________________ 437


ndice alfabtico _______________________________________________________________________ 439

Revisin 0.99zz Pgina 7


Introduccin a la programacin con C#, por Nacho Cabanes

Revisin 0.99zz Pgina 8


Introduccin a la programacin con C#, por Nacho Cabanes

0. Conceptos bsicos sobre programacin


0.1. Programa y lenguaje
Un programa es un conjunto de rdenes para un ordenador.

Estas rdenes se le deben dar en un cierto lenguaje, que el ordenador sea capaz
de comprender.

El problema es que los lenguajes que realmente entienden los ordenadores


resultan difciles para nosotros, porque son muy distintos de los que nosotros
empleamos habitualmente para hablar. Escribir programas en el lenguaje que
utiliza internamente el ordenador (llamado "lenguaje mquina" o "cdigo
mquina") es un trabajo duro, tanto a la hora de crear el programa como
(especialmente) en el momento de corregir algn fallo o mejorar lo que se hizo.

Por ejemplo, un programa que simplemente guardara un valor "2" en la posicin


de memoria 1 de un ordenador sencillo, con una arquitectura propia de los aos
80, basada en el procesador Z80 de 8 bits, sera as en cdigo mquina:

0011 1110 0000 0010 0011 1010 0001 0000

Prcticamente ilegible. Por eso, en la prctica se emplean lenguajes ms parecidos


al lenguaje humano, llamados "lenguajes de alto nivel". Normalmente, estos son
muy parecidos al idioma ingls, aunque siguen unas reglas mucho ms estrictas.

0.2. Lenguajes de alto nivel y de bajo nivel


Vamos a ver en primer lugar algn ejemplo de lenguaje de alto nivel, para despus
comparar con lenguajes de bajo nivel, que son los ms cercanos al ordenador.

Uno de los lenguajes de alto nivel ms sencillos es el lenguaje BASIC. En este


lenguaje, escribir el texto Hola en pantalla, sera tan sencillo como usar la orden

PRINT "Hola"

Otros lenguajes, como Pascal, nos obligan a ser algo ms estrictos y detallar
ciertas cosas como el nombre del programa o dnde empieza y termina ste, pero,
a cambio, hacen ms fcil descubrir errores (ya veremos por qu):

Revisin 0.99zz Pgina 9


Introduccin a la programacin con C#, por Nacho Cabanes

program Saludo;

begin
write('Hola');
end.

El equivalente en lenguaje C resulta algo ms difcil de leer, porque los programas


en C suelen necesitar incluir bibliotecas externas y devolver cdigos de error
(incluso cuando todo ha ido bien):

#include <stdio.h>

int main()
{
printf("Hola");
return 0;
}

En C# hay que dar todava ms pasos para conseguir lo mismo, porque, como
veremos, cada programa ser "una clase":

public class Saludo


{
public static void Main()
{
System.Console.WriteLine("Hola");
}
}

Como se puede observar, a medida que los lenguajes evolucionan, son capaces de
ayudar al programador en ms tareas, pero a la vez, los programas sencillos se
vuelven ms complicados. Afortunadamente, no todos los lenguajes siguen esta
regla, y algunos se han diseado de forma que las tareas simples sean (de nuevo)
sencillas de programar. Por ejemplo, para escribir algo en pantalla usando el
lenguaje Python haramos:

print("Hello")

Por el contrario, los lenguajes de bajo nivel son ms cercanos al ordenador que a
los lenguajes humanos. Eso hace que sean ms difciles de aprender y tambin
que los fallos sean ms difciles de descubrir y corregir, a cambio de que podemos
optimizar al mximo la velocidad (si sabemos cmo), 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

Revisin 0.99zz Pgina 10


Introduccin a la programacin con C#, por Nacho Cabanes

el sistema operativo MsDos y con un procesador de la familia Intel x86 sera 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 ms difcil de seguir. Pero eso todava 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
convertiran a lo siguiente:

1000 0011 1101 1000 1011 0100 0000 1001

(Nota: los colores de los ejemplos anteriores son una ayuda que nos dan algunos
entornos de programacin, para que nos sea ms fcil descubrir ciertos errores).

0.3. Ensambladores, compiladores e intrpretes


Como hemos visto, las rdenes que nosotros hemos escrito (lo que se conoce
como "programa fuente") deben convertirse a lo que el ordenador comprende
(obteniendo un "programa ejecutable").

Si elegimos un lenguaje de bajo nivel, como el ensamblador (en ingls Assembly,


abreviado como Asm), la traduccin es sencilla, y de hacer esa traduccin se
encargan unas herramientas llamadas ensambladores (en ingls Assembler).

Cuando el lenguaje que hemos empleado es de alto nivel, la traduccin es ms


complicada, y a veces implicar tambin recopilar varios fuentes distintos o incluir
Revisin 0.99zz Pgina 11
Introduccin a la programacin con C#, por Nacho Cabanes

posibilidades que se encuentran en otras bibliotecas que no hemos preparado


nosotros. Las herramientas encargadas de realizar todo esto son los
compiladores.

El programa ejecutable obtenido con el compilador o el ensamblador se podra


hacer funcionar en otro ordenador similar al que habamos utilizado para crearlo,
sin necesidad de que ese otro ordenador tenga instalado el compilador o el
ensamblador.

Por ejemplo, en el caso de Windows (y de MsDos), y del programa que nos saluda
en lenguaje Pascal, tendramos un fichero fuente llamado SALUDO.PAS. Este
fichero no servira de nada en un ordenador que no tuviera un compilador de
Pascal. En cambio, despus de compilarlo obtendramos 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 funcionara en otro ordenador que tuviera un sistema operativo distinto
(por ejemplo, Linux o Mac OS X).

Un intrprete es una herramienta parecida a un compilador, con la diferencia de


que en los intrpretes no se crea ningn "programa ejecutable" capaz de funcionar
"por s solo", de modo que si queremos distribuir nuestro programa a alguien,
deberemos entregarle el programa fuente y tambin el intrprete que es capaz de
entenderlo, o no le servir de nada. Cuando ponemos el programa en
funcionamiento, el intrprete se encarga de convertir el programa que hemos
escrito en lenguaje de alto nivel a su equivalente en cdigo mquina, orden por
orden, justo en el momento en que hay que procesar cada una de las rdenes.

Los intrpretes siguen siendo muy frecuentes hoy en da. 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 pgina web.

Actualmente existe una alternativa ms, algo que parece intermedio entre un
compilador y un intrprete. Existen lenguajes que no se compilan a un ejecutable
para un ordenador concreto, sino a un ejecutable "genrico", que es capaz de
funcionar en distintos tipos de ordenadores, a condicin de que en ese ordenador
exista una "mquina virtual" capaz de entender esos ejecutables genricos. Esta
es la idea que se aplica en Java: los fuentes son ficheros de texto, con extensin
".java", que se compilan a ficheros ".class". Estos ficheros ".class" se podran llevar

Revisin 0.99zz Pgina 12


Introduccin a la programacin con C#, por Nacho Cabanes

a cualquier ordenador que tenga instalada una "mquina virtual Java" (las hay para
la mayora de sistemas operativos).

Esta misma idea se sigue en el lenguaje C#, que se apoya en una mquina virtual
llamada "Dot Net Framework" (algo as como "plataforma punto net"): los
programas que creemos con herramientas como Visual Studio sern unos
ejecutables que funcionarn 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 ms artesanal en plataformas Linux y
Mac, gracias a un "clon" de la "plataforma .Net" que es de libre distribucin,
conocido como "proyecto Mono".

Ejercicios propuestos

(0.3.1) Localiza en Internet el intrprete de BASIC llamado Bywater Basic, en


su versin para el sistema operativo que ests utilizando y prueba el primer
programa de ejemplo que se ha visto en el apartado 0.1. Tambin puedes
usar cualquier "ordenador clsico" (de principios de los aos 80) y otros
muchos BASIC modernos, como Basic256. (Nota: no es necesario realizar
este ejercicio para seguir adelante con el curso; puedes omitirlo si no
encuentras ningn intrprete de BASIC).

(0.3.2) Localiza en Internet el compilador de Pascal llamado Free Pascal, en


su versin para el sistema operativo que ests utilizando, instlalo y prueba
el segundo programa de ejemplo que se ha visto en el apartado 0.1. (Nota:
no es necesario realizar este ejercicio para seguir adelante con el curso;
puedes omitirlo si no quieres instalar en tu ordenador software que luego
no vayas a seguir utilizando).

(0.3.3) Localiza un compilador de C para el sistema operativo que ests


utilizando (si es Linux o alguna otra versin de Unix, es fcil que se
encuentre ya instalado) y prueba el tercer programa de ejemplo que se ha
visto en el apartado 0.1. (Nota: no es necesario realizar este ejercicio para
seguir adelante con el curso; puedes omitirlo si no quieres instalar en tu
equipo software que despus quiz no vuelvas a utilizar).

(0.3.4) Descarga un intrprete de Python (ya estar preinstalado si usas


Linux) o busca en Internet "try Python web" para probarlo desde tu
navegador web, y prueba el quinto programa de ejemplo que se ha visto en

Revisin 0.99zz Pgina 13


Introduccin a la programacin con C#, por Nacho Cabanes

el apartado 0.1. (Nota: nuevamente, no es necesario realizar este ejercicio


para seguir adelante con el curso).

0.4. Pseudocdigo
A pesar de que los lenguajes de alto nivel se acercan al lenguaje natural (ingls),
que nosotros empleamos, es habitual no usar ningn lenguaje de programacin
concreto cuando queremos plantear iniciamente los pasos necesarios para
resolver un problema, sino emplear un lenguaje de programacin ficticio, no tan
estricto, muchas veces incluso en espaol. Este lenguaje recibe el nombre de
pseudocdigo.

Esa secuencia de pasos para resolver un problema es lo que se conoce como


algoritmo. Realmente es algo un poco ms estricto que eso: por ejemplo, un
algoritmo debe estar formado por un nmero finito de pasos. Por tanto, un
programa de ordenador es un algoritmo expresado usando un lenguaje de
programacin.

Por ejemplo, un algoritmo que controlase los pagos que se realizan en una tienda
con tarjeta de crdito, escrito en pseudocdigo, podra ser:

Leer banda magntica de la tarjeta


Conectar con central de cobros
Si hay conexin y la tarjeta es correcta:
Pedir cdigo PIN
Si el PIN es correcto
Comprobar saldo_existente
Si saldo_existente >= importe_compra
Aceptar la venta
Descontar importe del saldo.
Fin Si
Fin Si
Fin Si

Como se ve en este ejemplo, el pseudocdigo suele ser menos detallado que un


lenguaje de programacin "real" y expresar las acciones de forma ms general,
buscando concretar las ideas ms que la forma real de llevarlas a cabo. Por
ejemplo, ese "conectar con central de cobros" correspondera a varias rdenes
individuales en cualquier lenguaje de programacin.

Ejercicios propuestos

(0.4.1) Localiza en Internet el intrprete de Pseudocdigo llamado PseInt y


prueba escribir el siguiente programa. (Nota: no es necesario realizar este

Revisin 0.99zz Pgina 14


Introduccin a la programacin con C#, por Nacho Cabanes

ejercicio para seguir adelante con el curso; puedes omitirlo si no te apetece


instalar en tu equipo software que luego no vayas a seguir utilizando).

Proceso EjemploDeSuma
Escribir 2+3
FinProceso

Revisin 0.99zz Pgina 15


Introduccin a la programacin con C#, por Nacho Cabanes

1. Toma de contacto con C#


1.1. Qu es C #? Qu entorno usaremos?
C# es un lenguaje de programacin de ordenadores. Se trata de un lenguaje
moderno, evolucionado a partir de C y C++, y con una sintaxis muy similar a la de
Java. Los programas creados con C# no suelen ser tan rpidos como los creados
con C, pero a cambio la productividad del programador es mucho mayor y es ms
difcil cometer errores.

Se trata de un lenguaje creado por Microsoft para realizar programas para su


plataforma .NET, pero fue estandarizado posteriormente por ECMA y por ISO, y
existe una implementacin alternativa de "cdigo abierto", el "proyecto Mono",
que est disponible para Windows, Linux, Mac OS X y otros sistemas operativos.

Nosotros comenzaremos por usar Mono como plataforma de desarrollo durante


los primeros temas, junto con un editor de texto para programadores. Cuando los
conceptos bsicos estn asentados, pasaremos a emplear Visual C#, de Microsoft,
que requiere un ordenador ms potente pero a cambio incluye un entorno de
desarrollo muy avanzado, y est disponible tambin en una versin gratuita (Visual
Studio Express Edition). Existen otros entornos alternativos, como SharpDevelop o
MonoDevelop, que tambin comentaremos.

Los pasos que seguiremos para crear un programa en C# sern:

Escribir el programa en lenguaje C# (fichero fuente), con cualquier editor


de textos.
Compilarlo, con nuestro compilador. Esto crear un "fichero ejecutable".
Lanzar el fichero ejecutable.

La mayora 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 algn fallo.

Tras el siguiente apartado veremos un ejemplo de entorno desde el que realizar


nuestros programas, dnde localizarlo y cmo instalarlo.

Revisin 0.99zz Pgina 16


Introduccin a la programacin con C#, por Nacho Cabanes

1.2. Escribir un texto en C#


Vamos con un primer ejemplo de programa en C#, posiblemente el ms sencillo
de los que "hacen algo til". Se trata de escribir un texto en pantalla. La apariencia
de este programa la vimos en el tema anterior. Vamos a analizarlo ahora con ms
detalle:

public class Ejemplo_01_02a


{
public static void Main()
{
System.Console.WriteLine("Hola");
}
}

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 ms adelante. En este primer anlisis, iremos desde
dentro hacia fuera:

WriteLine("Hola"); : "Hola" es el texto que queremos escribir, y WriteLine


es la orden encargada de escribir (Write) una lnea (Line) de texto en
pantalla.

Console.WriteLine("Hola"); : WriteLine siemprp ir precedido de "Console."


porque es una orden de manejo de la "consola" (la pantalla "negra" en
modo texto del sistema operativo).

System.Console.WriteLine("Hola"); : Las rdenes relacionadas con el


manejo de consola (Console) pertenecen a la categora de sistema (System).

Las llaves { y } se usan para delimitar un bloque de programa. En nuestro


caso, se trata del bloque principal del programa (Main).

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 ms 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 despus un parntesis vaco los iremos
aclarando ms tarde. De momento, deberemos memorizar que sa ser la
forma correcta de escribir "Main".

Revisin 0.99zz Pgina 17


Introduccin a la programacin con C#, por Nacho Cabanes

public class Ejemplo_01_02a : De momento pensaremos que


"Ejemplo_01_02a" es el nombre de nuestro programa. Una lnea como esa
deber existir tambin siempre en nuestros programas (aunque el nombre
no tiene por qu ser tan "rebuscado"), y eso de "public class" ser
obligatorio. Nuevamente, aplazamos para ms tarde los detalles sobre qu
quiere decir "class" y por qu debe ser "public".

Como se puede ver, mucha parte de este programa todava 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
bsicos de programacin.

Ejercicio propuesto (1.2.1): Crea un programa en C# que te salude por tu nombre


(por ejemplo, "Hola, Nacho").

Slo un par de cosas ms antes de seguir adelante:

Cada orden de C# debe terminar con un punto y coma (;)

C# es un lenguaje de formato libre, de modo que puede haber varias


rdenes en una misma lnea, u rdenes separadas por varias lneas o
espacios entre medias. Lo que realmente indica donde termina una orden
y donde empieza la siguiente son los puntos y coma y las llaves. Por ese
motivo, el programa anterior se podra haber escrito tambin as (aunque
no es aconsejable, porque puede resultar menos legible):

public class Ejemplo_01_02b {


public
static
void Main() { System.Console.WriteLine("Hola"); } }

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


comienzo, y yo usar ambas indistintamente. Una es como hemos hecho en
el primer ejemplo: situar la llave de apertura en una lnea, sola, y justo
encima de la llave de cierre correspondiente. Esto es lo que muchos autores
llaman el "estilo C". La segunda forma habitual es situndola a continuacin
del nombre del bloque que comienza (el "estilo Java"), as:

public class Ejemplo_01_02c {


public static void Main(){
System.Console.WriteLine("Hola");
Revisin 0.99zz Pgina 18
Introduccin a la programacin con C#, por Nacho Cabanes

}
}

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


estemos trabajando con fuentes de mayor tamao, para que ocupe un
poco menos de espacio; en los primeros fuentes usaremos el "estilo C", que
tiende a resultar ms legible).

La gran mayora de las rdenes que encontraremos en el lenguaje C# son palabras


en ingls o abreviaturas de stas, pero hay que tener en cuenta que C# distingue
entre maysculas y minsculas, por lo que "WriteLine" es una palabra
reconocida, pero "writeLine", "WRITELINE" o "Writeline" no lo son.

1.3. Cmo probar este programa


1.3.1. Cmo probarlo con Mono en Linux
Para alguien acostumbrado a sistemas como Windows o Mac OS, hablar de Linux
puede sonar a que se trata de algo apto slo para expertos. Eso no
necesariamente es as, y, de hecho, para un aprendiz de programador puede
resultar justo al contrario, porque Linux tiene compiladores e intrpretes de varios
lenguajes ya preinstalados, y otros son fciles de instalar en unos pocos clics.

La instalacin de Linux, que podra tener la dificultad de crear particiones para


coexistir con nuestro sistema operativo habitual, hoy en da puede realizarse de
forma simple, usando software de virtualizacin gratuito, como VirtualBox, que
permite tener un "ordenador virtual" dentro del nuestro, e instalar Linux en ese
"ordenador virtual" sin interferir con nuestro sistema operativo habitual.

As, podemos instalar VirtualBox, descargar la imagen ISO del CD o DVD de


instalacin de algn Linux que sea reciente y razonablemente amigable, arrancar
VirtualBox, crear una nueva mquina virtual y "cargar esa imagen de CD" para
instalar Linux en esa mquina virtual.

En este caso, yo comentar los pasos necesarios para usar Linux Mint (en su
versin 17 Cinnamon) como entorno de desarrollo:

Revisin 0.99zz Pgina 19


Introduccin a la programacin con C#, por Nacho Cabanes

En primer lugar, deberemos entrar al instalador de software de nuestro sistema,


que para las versiones de Linux Mint basadas en escritorios derivados de Gnome
(como es el caso de Mint 17 Cinnamon), suele ser un tal "Gestor de paquetes
Synaptic":

Se nos pedir nuestra contrasea de usuario (la que hayamos utilizado en el


momento de instalar Linux), y aparecer la pantalla principal de Synaptic, con una
enorme lista de software que podemos instalar. En esta lista, aparece una casilla

Revisin 0.99zz Pgina 20


Introduccin a la programacin con C#, por Nacho Cabanes

de texto llamada "Filtro rpido", en la que podemos teclear "mcs" para que nos
aparezca directamente nuestro compilador Mono:

Entre otros paquetes, posiblemente veremos uno llamado "mono-mcs", en cuya


descripcin se nos dir que es el "Mono C# Compiler". Al
hacer doble clic se nos avisar en el caso (habitual) de que
haya que instalar algn otro paquete adicional y entonces
ya podremos pulsar el botn "Aplicar":

Se descargarn los ficheros necesarios, se instalarn y al cabo de un instante se


nos avisar de que se han aplicado todos los cambios:

Ya tenemos instalado el compilador, que convertir nuestros programas en algo


que el ordenador entienda. Para teclear nuestros programas necesitaremos un
editor de texto, pero eso es algo que viene preinstalado en cualquier Linux. Por
Revisin 0.99zz Pgina 21
Introduccin a la programacin con C#, por Nacho Cabanes

ejemplo, en esta versin de Linux encontraremos un editor de textos llamado


"gedit" dentro del apartado de accesorios:

En este editor podemos teclear nuestro programa, que inicialmente se ver con
letras negras sobre fondo blanco:

Revisin 0.99zz Pgina 22


Introduccin a la programacin con C#, por Nacho Cabanes

Cuando lo guardemos con un nombre terminado en ".cs" (como "ejemplo01.cs"), el


editor sabr que se trata de un fuente en lenguaje C# y nos mostrar cada palabra
en un color que nos ayude a saber la misin de esa palabra:

Para compilar y lanzar el programa usaremos un "terminal", que habitualmente


estar accesible en la parte inferior de la pantalla:

En esa "pantalla negra" ya podemos teclear las rdenes necesarias para compilar y
probar el programa:

Si hemos guardado el fuente en la carpeta "Documentos", el primer paso


ser entrar a esa carpeta con la orden "cd Documentos".

Despus lanzaremos el compilador con la orden "mcs" seguida del nombre


del fuente: "mcs ejemplo01.cs" (recuerda que en Linux debes respetar las
maysculas y minsculas tal y como las hayas escrito en el nombre del
fichero).

Revisin 0.99zz Pgina 23


Introduccin a la programacin con C#, por Nacho Cabanes

Si no aparece ningn mensaje de error, ya podemos lanzar el programa


ejecutable, con la orden "mono" seguida del nombre del programa
(terminado en ".exe"): "mono ejemplo01.exe", as:

Si alguno de los pasos ha fallado, tendrs que comprobar si has dado los pasos
anteriores de forma correcta y si tu fuente est bien tecleado.

Ejercicio propuesto (1.3.1.1): Desde Linux, crea y prueba un programa en C# que


escriba en pantalla "Bienvenido a Linux".

1.3.2. Cmo probarlo con Mono en Windows


Si (utilices Linux o no) has ledo las instrucciones para compilar usando Linux,
tienes mucho trabajo adelantado. Instalar Mono para Windows no es mucho ms
complicado que para Linux, pero deberemos descargarlo desde su pgina oficial y
responder a ms preguntas durante la instalacin. Adems, tendremos
(inicialmente) un editor de texto mucho ms pobre que el de Linux.

Comenzaremos por descargar Mono desde su pgina oficial:

http://www.mono-project.com/

Revisin 0.99zz Pgina 24


Introduccin a la programacin con C#, por Nacho Cabanes

En la parte superior derecha aparece el enlace para descargar ("download"), que


nos lleva a una nueva pgina en la que debemos elegir la plataforma para la que
queremos nuestro Mono. Nosotros descargaremos la versin ms reciente para
Windows (la 3.4.0 en el momento de escribir este texto, aunque esa es la
numeracin para Mac y realmente en Windows corresponde a la 3.2.3).

Se trata de un fichero de cerca de 100 Mb. Cuando termine la descarga, haremos


doble clic en el fichero recibido, aceptaremos el aviso de seguridad que
posiblemente nos mostrar Windows, y comenzar la instalacin, en la que
primero se nos muestra el mensaje de bienvenida:

Revisin 0.99zz Pgina 25


Introduccin a la programacin con C#, por Nacho Cabanes

Deberemos aceptar el acuerdo de licencia y despus se nos muestra una ventana


de informacin y se nos pregunta en qu carpeta queremos instalar. Como es
habitual, se nos propone que sea dentro de "Archivos de programa", pero
podemos cambiarla por otra carpeta o incluso por otra unidad de disco:

El siguiente paso es elegir qu componentes queremos instalar (Mono, Gtk#, XSP):

Revisin 0.99zz Pgina 26


Introduccin a la programacin con C#, por Nacho Cabanes

Yo no soy partidario de instalar todo. Mono es imprescindible. La creacin de


interfaces de usuario con Gtk# queda fuera del alcance que se pretende con este
texto, pero aun as puede ser interesante para quien quiera investigar por su
cuenta para profundizar. El servidor web XSP es algo claramente innecesario por
ahora, y que adems instalara un "listener" que ralentizara ligeramente el
ordenador, as que puede ser razonable no instalarlo por ahora:

Despus deberemos indicar en qu carpeta del men de Inicio queremos que


quede el acceso a Mono:

Revisin 0.99zz Pgina 27


Introduccin a la programacin con C#, por Nacho Cabanes

A continuacin se nos muestra el resumen de lo que se va a instalar. Si


confirmamos que todo nos parece correcto, comienza la copia de ficheros y al
cabo de un instante tendremos el mensaje de confirmacin de que la instalacin
se ha completado:

Mono est listo para usar. En nuestro men de Inicio deberamos tener una nueva
carpeta llamada "Mono x.x.x for Windows", y dentro de ella un acceso a "Mono-
x.x.x Command Prompt":

Revisin 0.99zz Pgina 28


Introduccin a la programacin con C#, por Nacho Cabanes

Si hacemos clic en esa opcin, accedemos al smbolo de sistema ("command


prompt"), la pantalla negra del sistema operativo, pero con el "path" (la ruta de
bsqueda) preparada para que podamos acceder al compilador desde ella:

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 sera la forma de hacerlo para un usuario llamado "Yo" en un sistema con
Windows 7 o Windows 8; los detalles exactos dependern del nombre del usuario
y de la versin de Windows empleada).

Revisin 0.99zz Pgina 29


Introduccin a la programacin con C#, por Nacho Cabanes

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:

Podemos empezar a teclear el ejemplo que habamos visto anteriormente. En este


caso no veremos ninguna palabra destacada en colores, ni siquiera despus de
guardar el programa, porque el "Bloc de notas" es mucho ms limitado que los
editores que incorporan los sistemas Linux. La alternativa ser usar otro editor
mejor, pero eso lo haremos ms adelante.

Revisin 0.99zz Pgina 30


Introduccin a la programacin con C#, por Nacho Cabanes

Guardamos los cambios, salimos del "Bloc de notas" y nos volvemos a encontrar
en la pantalla negra del smbolo del sistema. Nuestro fuente ya est escrito y
guardado. El siguiente paso es compilarlo. Para eso, tecleamos

mcs ejemplo01.cs

Si no se nos responde nada, quiere decir que no ha habido errores. Si todo va


bien, se acaba de crear un fichero "ejemplo01.exe". En ese caso, podramos lanzar
el programa tecleando

mono ejemplo01.exe

y el mensaje "Hola" debera aparecer en pantalla.

Si en nuestro ordenador est instalado el "Dot Net Framework" (algo que debera
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 versin 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 versin de ."Net" (es una descarga gratuita desde la pgina web
de Microsoft):

Revisin 0.99zz Pgina 31


Introduccin a la programacin con C#, por Nacho Cabanes

Si el "Mono Command Prompt" se nos abre en Windows\System32 y queremos


evitar tener que comenzar siempre con la orden "cd" para cambiar a nuestra
carpeta de programas, tenemos una alternativa: cambiar la carpeta de arranque
de Mono.

Es algo fcil en la mayora de versiones de Windows. El primer paso ser abrir la


carpeta que hemos creado y hacer clic en
su barra superior, de modo que se nos
muestre la ruta completa de la carpeta.

Podemos "copiar" esa ruta (botn


derecho del ratn o Ctrl+C) para
memorizarla.

Ahora vamos al men de Inicio de


Windows, pulsamos el botn derecho
sobre la opcin "Mono-x.x.x Command
Prompt" del men de inicio y escogemos
"Propiedades".

En la pestaa llamada "Acceso directo"


veremos la opcin "Iniciar en". Si
cambiamos el contenido de esa casilla
por el nombre de nuestra carpeta
("pegando" lo que antes habamos
copiado) conseguiremos que el

Revisin 0.99zz Pgina 32


Introduccin a la programacin con C#, por Nacho Cabanes

Command Prompt de Mono se abra en la carpeta que habamos preparado para


nuestros fuentes.

1.3.3. Otros editores ms avanzados para Windows


Si quieres un editor ms potente que el Bloc de notas de Windows, puedes probar
Notepad++, que es gratuito (realmente ms que eso: es de "cdigo abierto") y
podrs localizar fcilmente en Internet. Geany tambin es un alternativa muy
interesante, disponible para muchos sistemas operativos, y que permite incluso
compilar y lanzar los programas desde el propio editor, sin necesidad de salir al
"intrprete de comandos" del sistema operativo. Un poco ms adelante veremos
cmo instalarlo y configurarlo, pero todava no, para que durante los primeros
temas tengas la obligacin de repetir el proceso "teclear - compilar - ejecutar -
probar" de forma un tanto "artesanal".

Si prefieres un entorno desde el que puedas teclear, compilar y probar tus


programas, incluso los de gran tamao que estn formados por varios ficheros, en
un apartado posterior hablaremos de SharpDevelop (para Windows) y de
MonoDevelop (para Windows, Linux y Mac), as como del entorno "oficial" de
desarrollo, llamado Visual Studio.

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 rpido que no da tiempo a ver los resultados. Una solucin provisional
puede ser aadir "System.Console.ReadLine()" al final del programa, de modo que
se quede parado hasta que pulsemos Intro:

public class Ejemplo_01_03_03a


{
public static void Main()
{
System.Console.WriteLine("Hola");
System.Console.ReadLine();
}
}

(Nota: no debers entregar tus programas con ese "ReadLine" al final: es una
ayuda para que compruebes que estn funcionando correctamente desde ciertos
entornos, pero no debera formar parte de un programa finalizado, porque no es
parte de la lgica del programa).

Revisin 0.99zz Pgina 33


Introduccin a la programacin con C#, por Nacho Cabanes

1.4. Mostrar nmeros enteros en pantalla


Cuando queremos escribir un texto "tal cual", como en el ejemplo anterior, lo
encerramos entre comillas. Pero no siempre querremos escribir textos prefijados.
En muchos casos, se tratar de algo que habr que calcular.

El ejemplo ms sencillo es el de una operacin matemtica. La forma de realizarla


es simple: no usar comillas en WriteLine. Entonces, C# intentar analizar el
contenido para ver qu puede significar. Por ejemplo, para sumar 3 y 4 bastara
hacer:

public class Ejemplo_01_04a


{
public static void Main()
{
System.Console.WriteLine(3+4);
}
}

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.

(Recomendacin: no "copies y pegues" aunque dos ejercicios se parezcan. Volver


a teclear cada nuevo ejercicio te ayudar a memorizar las estructuras bsicas del
lenguaje).

1.5. Operaciones aritmticas bsicas


1.5.1. Operadores
Parece evidente que el smbolo de la suma ser un +, y podemos esperar cual ser
el de la resta, pero alguna de las operaciones matemticas habituales tienen
smbolos menos intuitivos. Veamos cuales son los ms importantes:

Operador Operacin
+ Suma
- Resta, negacin
* Multiplicacin
/ Divisin
% Resto de la divisin ("mdulo")
Revisin 0.99zz Pgina 34
Introduccin a la programacin con C#, por Nacho Cabanes

As, podemos calcular el resto de la divisin entre dos nmeros de la siguiente


forma:

public class Ejemplo_01_05_01a


{
public static void Main()
{
System.Console.WriteLine("El resto de dividir 19 entre 5 es");
System.Console.WriteLine(19 % 5);
}
}

Ejercicios propuestos:
(1.5.1.1) Haz un programa que calcule el producto de los nmeros 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 divisin de 301 entre 3.

1.5.2. Orden de prioridad de los operadores


Sencillo:
En primer lugar se realizarn las operaciones indicadas entre parntesis.
Luego la negacin.
Despus las multiplicaciones, divisiones y el resto de la divisin.
Finalmente, las sumas y las restas.
En caso de tener igual prioridad, se analizan de izquierda a derecha.

As, el siguiente ejemplo da como resultado 23 (primero se multiplica 4*5 y luego


se le suma 3) en vez de 35 (no se suma 3+4 antes de multiplicar, aunque aparezca
a la izquierda, porque la prioridad de la suma es menor que la de la
multiplicacin).

public class Ejemplo_01_05_02a


{
public static void Main()
{
System.Console.WriteLine("Ejemplo de precedencia de operadores");
System.Console.WriteLine("3+4*5=");
System.Console.WriteLine(3+4*5);
}
}

Ejercicios propuestos: Calcular (a mano y despus comprobar desde C#) el


resultado de las siguientes operaciones:
Revisin 0.99zz Pgina 35
Introduccin a la programacin con C#, por Nacho Cabanes

(1.5.2.1) Calcular el resultado de -2 + 3 * 5


(1.5.2.2) Calcular el resultado de (20+5) % 6
(1.5.2.3) Calcular el resultado de 15 + -5*6 / 10
(1.5.2.4) Calcular el resultado de 2 + 10 / 5 * 2 - 7 % 1

1.5.3. Introduccin a los problemas de desbordamiento


El espacio del que disponemos para almacenar los nmeros es limitado. Veremos
los lmites exactos ms adelante, pero de momento nos basta saber que si el
resultado de una operacin es un nmero "demasiado grande", obtendremos un
mensaje de error o un resultado errneo. Por eso en los primeros ejemplos
usaremos nmeros pequeos. Ms adelante veremos a qu se debe realmente
este problema y cmo evitarlo. Como anticipo, el siguiente programa ni siquiera
compila, porque el compilador sabe que el resultado va a ser "demasiado grande":

public class Ejemplo_01_05_03a


{
public static void Main()
{
System.Console.WriteLine(10000000*10000000);
}
}

1.6. Introduccin a las variables: int


El primer ejemplo nos permita escribir "Hola". El segundo llegaba un poco ms
all y nos permita sumar dos nmeros que habamos prefijado en nuestro
programa. Pero esto tampoco es "lo habitual", sino que esos nmeros dependern
de valores que haya tecleado el usuario o de clculos anteriores.

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 tambin los
resultados temporales. A estas "zonas de memoria con nombre" les llamaremos
variables.

Como primer ejemplo, vamos a ver lo que haramos para sumar dos nmeros
enteros que fijsemos en el programa.

1.6.1. Definicin de variables: nmeros enteros


Para usar una cierta variable primero hay que declararla: indicar su nombre y el
tipo de datos que querremos guardar.

Revisin 0.99zz Pgina 36


Introduccin a la programacin con C#, por Nacho Cabanes

El primer tipo de datos que usaremos sern nmeros enteros (sin decimales), que
se indican con "int" (abreviatura del ingls "integer"). Despus de esta palabra se
indica el nombre que tendr la variable:

int primerNumero;

Esa orden reserva espacio para almacenar un nmero entero, que podr tomar
distintos valores, y al que nos referiremos con el nombre "primerNumero".

Ejercicio propuesto (1.6.1.1): Ampla el "Ejemplo 01.05.02a" para declarar tres


variables, llamadas n1, n2, n3.

1.6.2. Asignacin de valores


Podemos darle un valor a esa variable durante el programa haciendo

int primerNumero;
...
primerNumero = 234;

Hay que tener en cuenta que esto no es una igualdad matemtica, sino una
"asignacin 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;

Tambin podemos dar un valor inicial a las variables ("inicializarlas") antes de que
empiece el programa, en el mismo momento en que las definimos:

int primerNumero = 234;

Si varias variables son del mismo tipo, podemos declararlas a la vez

int primerNumero, segundoNumero;

O incluso podemos definir e inicializar ms de una variable a la vez

int primerNumero = 234, segundoNumero = 567;

Revisin 0.99zz Pgina 37


Introduccin a la programacin con C#, por Nacho Cabanes

(esta lnea reserva espacio para dos variables, que usaremos para almacenar
nmeros 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).

Despus ya podemos hacer operaciones con las variables, igual que las hacamos
con los nmeros:

suma = primerNumero + segundoNumero;

Ejercicio propuesto (1.6.2.1): Ampla el ejercicio 1.6.1.1, para que las tres
variables n1, n2, n3 estn declaradas en la misma lnea y tengan valores iniciales.

1.6.3. Mostrar el valor de una variable en pantalla


Una vez que sabemos cmo mostrar un nmero en pantalla, es sencillo mostrar el
valor de una variable. Para un nmero hacamos cosas como

System.Console.WriteLine(3+4);

pero si se trata de una variable es idntico (sin comillas, para que el compilador
analice su valor de antes de escribir):

System.Console.WriteLine(suma);

O bien, si queremos mostrar un texto prefijado adems del valor de la


variable, podemos indicar el texto entre comillas, detallando con {0} en qu parte
de dicho texto queremos que aparezca el valor de la variable, de la siguiente
forma:

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

Si queremos mostrar de ms de una variable, detallaremos en el texto dnde debe


aparecer cada una de ellas, usando {0}, {1} y tantos nmeros sucesivos como sea
necesario, y tras el texto incluiremos los nombres de cada una de esas variables,
separados por comas:

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


primerNumero, segundoNumero, suma);

Ya sabemos todo lo suficiente para crear nuestro programa que sume dos
nmeros usando variables:

public class Ejemplo_01_06_03a


Revisin 0.99zz Pgina 38
Introduccin a la programacin con C#, por Nacho Cabanes

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

primerNumero = 234;
segundoNumero = 567;
suma = primerNumero + segundoNumero;

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


primerNumero, segundoNumero, suma);
}
}

Repasemos lo que hace:


(Aplazamos todava los detalles de qu significan "public", "class", "static" y
"void").
Main() indica donde comienza el cuerpo del programa, que se delimita entre
llaves: { y }
int primerNumero; reserva espacio para guardar un nmero entero, al que
llamaremos primerNumero.
int segundoNumero; reserva espacio para guardar otro nmero entero, al
que llamaremos segundoNumero.
int suma; reserva espacio para guardar un tercer nmero entero, al que
llamaremos suma.
primerNumero = 234; da el valor del primer nmero que queremos sumar
segundoNumero = 567; da el valor del segundo nmero que queremos
sumar
suma = primerNumero + segundoNumero; halla la suma de esos dos
nmeros y la guarda en otra variable, en vez de mostrarla directamente en
pantalla.
System.Console.WriteLine("La suma de {0} y {1} es {2}", primerNumero,
segundoNumero, suma); muestra en pantalla el texto y los valores de las
tres variables (los dos nmeros iniciales y su suma).

El resultado de este programa sera:

La suma de 234 y 567 es 801

Ejercicios propuestos:
(1.6.3.1) Crea un programa que calcule el producto de los nmeros 121 y 132,
usando variables.
(1.6.3.2) Crea un programa que calcule la suma de 285 y 1396, usando variables.

Revisin 0.99zz Pgina 39


Introduccin a la programacin con C#, por Nacho Cabanes

(1.6.3.3) Crea un programa que calcule el resto de dividir 3784 entre 16, usando
variables.
(1.6.3.4) Ampla el ejercicio 1.6.2.1, para que se muestre el resultado de la
operacin n1+n2*n3.

1.7. Identificadores
Los nombres de variables (lo que se conoce como "identificadores") pueden estar
formados por letras, nmeros o el smbolo de subrayado (_) y deben comenzar por
letra o subrayado. No deben tener espacios intermedios. Tambin hay que
recordar que las vocales acentuadas y la ee son problemticas, porque no son
letras "estndar" en todos los idiomas, as que no se pueden utilizar como parte de
un identificador en la mayora de lenguajes de programacin.

Por eso, no son nombres de variable vlidos:

1numero (empieza por nmero)


un numero (contiene un espacio)
Ao1 (tiene una ee)
MsDatos (tiene una vocal acentuada)

(Nota: algunos entornos de programacin modernos s permitirn variables que


contengan ee y vocales acentuadas, pero como no es lo habitual en todos los
lenguajes de programacin, durante este curso introductorio nosotros no
consideraremos vlido un nombre de variable como "ao", aun sabiendo que si
estamos programando en C# con Visual Studio, el sistema s lo considerara
aceptable).

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


Por ejemplo, la palabra "int" se refiere a que cierta variable guardar un nmero
entero, as que esa palabra "int" no la podremos usar tampoco como nombre de
variable (pero no vamos a incluir ahora una lista de palabras reservadas de C#, ya
nos iremos encontrando con ellas).

Hay que recordar que en C# las maysculas y minsculas se consideran


diferentes, de modo que si intentamos hacer

PrimerNumero = 234;
primernumero = 234;

o cualquier variacin similar, el compilador protestar y nos dir que no conoce


esa variable, porque la habamos declarado como

Revisin 0.99zz Pgina 40


Introduccin a la programacin con C#, por Nacho Cabanes

int primerNumero;

Ejercicios propuestos:
(1.7.1) Crea un programa que calcule el producto de los nmeros 87 y 94, usando
variables llamadas "numero1" y "numero2".
(1.7.2) Intenta crear una nueva versin del programa que calcula el producto de
los nmeros 87 y 94, usando esta vez variables llamadas "1numero" y "2numero".
(1.7.3) Intenta crear una nueva versin del programa que calcula el producto de
los nmeros 87 y 94, usando esta vez variables llamadas "numero 1" y "numero 2".
(1.7.4) Crea una nueva versin del programa que calcula el producto de los
nmeros 87 y 94, usando esta vez variables llamadas "nmero1" y "nmero2".

1.8. Comentarios
Podemos escribir comentarios, que el compilador ignorar, pero que pueden ser
tiles para nosotros mismos, haciendo que sea ms fcil recordar el cometido un
fragmento del programa ms adelante, cuando tengamos que ampliarlo o
corregirlo.

Existen dos formas de indicar comentarios. En su forma ms general, los


escribiremos entre /* y */:

int suma; /* Guardar el valor para usarlo ms tarde */

Es conveniente escribir comentarios que aclaren la misin de las partes de


nuestros programas que puedan resultar menos claras a simple vista. Incluso
suele ser aconsejable que el programa comience con un comentario, que nos
recuerde qu hace el programa sin que necesitemos mirarlo de arriba a abajo. Un
ejemplo casi exagerado podra ser:

/* ---- Ejemplo en C#: sumar dos nmeros prefijados ---- */

public class Ejemplo_01_08a


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

/* Primero calculo la suma */


suma = primerNumero + segundoNumero;

/* Y despus muestro su valor */


System.Console.WriteLine("La suma de {0} y {1} es {2}",
primerNumero, segundoNumero, suma);
}
Revisin 0.99zz Pgina 41
Introduccin a la programacin con C#, por Nacho Cabanes

Un comentario puede empezar en una lnea y terminar en otra distinta, as:

/* Esto
es un comentario que
ocupa ms de una lnea
*/

Tambin es posible declarar otro tipo de comentarios, que comienzan con doble
barra y terminan cuando se acaba la lnea (estos comentarios, claramente, no
podrn ocupar ms de una lnea). Son los "comentarios de una lnea" o
"comentarios al estilo de C++" (a diferencia de los "comentarios de mltiples
lneas" o "comentarios al estilo de C" que ya hemos visto):

// Este es un comentario "al estilo C++"

De modo que el programa anterior se podra reescribir usando comentarios de


una lnea:

// ---- Ejemplo en C#: sumar dos nmeros prefijados ----

public class Ejemplo_01_08b


{
public static void Main()
{
int primerNumero = 234;
int segundoNumero = 567;
int suma; // Guardar el valor para usarlo ms tarde

// Primero calculo la suma


suma = primerNumero + segundoNumero;

// Y despus muestro su valor


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

En este texto, a partir de ahora los fuentes comenzarn con un comentario que
resuma su cometido, y en ocasiones incluirn tambin 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.

Revisin 0.99zz Pgina 42


Introduccin a la programacin con C#, por Nacho Cabanes

1.9. Datos por el usuario: ReadLine


Hasta ahora hemos utilizado datos prefijados, pero eso es poco frecuente en el
mundo real. Es mucho ms habitual que los datos los introduzca el usuario, o que
se lean desde un fichero, o desde una base de datos, o se reciban de Internet o
cualquier otra red. El primer caso que veremos ser el de interaccionar
directamente con el usuario.

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 lnea"), tambin existe
System.Console.ReadLine ("leer lnea"). Para leer textos, haramos

texto = System.Console.ReadLine();

pero eso ocurrir un poco ms adelante, cuando veamos cmo manejar textos. De
momento, nosotros slo sabemos manipular nmeros enteros, as que
deberemos convertir ese dato a un nmero entero, usando Convert.ToInt32:

primerNumero = System.Convert.ToInt32( System.Console.ReadLine() );

Un ejemplo de programa que sume dos nmeros tecleados por el usuario sera:

// Ejemplo en C#: sumar dos nmeros introducidos por el usuario


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

System.Console.WriteLine("Introduce el primer nmero");


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

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


primerNumero, segundoNumero, suma);
}
}

Ejercicios propuestos:
(1.9.1) Crea un programa que calcule el producto de dos nmeros introducidos
por el usuario.

Revisin 0.99zz Pgina 43


Introduccin a la programacin con C#, por Nacho Cabanes

(1.9.2) Crea un programa que calcule la divisin de dos nmeros introducidos por
el usuario, as como el resto de esa divisin.
(1.9.3) Suma tres nmeros tecleados por usuario.
(1.9.4) Pide al usuario una cantidad de "millas nuticas" y muestra la equivalencia
en metros, usando: 1 milla nutica = 1852 metros.

1.10. using System


Va siendo hora de hacer una pequea mejora: no es necesario repetir "System." al
principio de la mayora de las rdenes que tienen que ver con el sistema (por
ahora, las de consola y las de conversin), si al principio del programa utilizamos
"using System":

// Ejemplo en C#: "using System" en vez de "System.Console"


using System;

public class Ejemplo_01_10a


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

Console.WriteLine("Introduce el primer nmero");


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

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


primerNumero, segundoNumero, suma);
}
}

Si adems declaramos varias variables a la vez, como vimos en el apartado 1.5.2, el


programa podra ser an ms compacto:

// Ejemplo en C#: "using System" y declaraciones mltiples de variables


using System;

public class Ejemplo_01_10b


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

Console.WriteLine("Introduce el primer nmero");


primerNumero = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Introduce el segundo nmero");
Revisin 0.99zz Pgina 44
Introduccin a la programacin con C#, por Nacho Cabanes

segundoNumero = Convert.ToInt32(Console.ReadLine());
suma = primerNumero + segundoNumero;

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


primerNumero, segundoNumero, suma);
}
}

Ejercicios propuestos:
(1.10.1) Crea una nueva versin del programa que calcula el producto de dos
nmeros 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 versin del programa que calcula la divisin de dos
nmeros introducidos por el usuario, as como el resto de esa divisin (1.9.2),
empleando "using System". Debers incluir un comentario con tu nombre y la
fecha en que has realizado el programa.

1.11. Escribir sin avanzar de lnea


En el apartado 1.6.3 vimos cmo usar {0} para escribir en una misma lnea datos
calculados y textos prefijados. Pero hay otra alternativa, que adems nos permite
tambin escribir un texto y pedir un dato a continuacin, en la misma lnea de
pantalla: emplear "Write" en vez de "WriteLine", as:

// Ejemplo en C#: escribir sin avanzar de lnea


using System;

public class Ejemplo_01_11a


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

Console.Write("Introduce el primer nmero: ");


primerNumero = Convert.ToInt32(Console.ReadLine());
Console.Write("Introduce el segundo nmero: ");
segundoNumero = Convert.ToInt32(Console.ReadLine());
suma = primerNumero + segundoNumero;

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


primerNumero, segundoNumero, suma);
}
}

Incluso el ltimo "WriteLine" de varios datos se podra convertir en varios Write


(aunque generalmente eso har el programa ms largo y no necesariamente ms
legible), as

Revisin 0.99zz Pgina 45


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo en C#: escribir sin avanzar de lnea (2)


using System;

public class Ejemplo_01_11b


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

Console.Write("Introduce el primer nmero: ");


primerNumero = Convert.ToInt32(Console.ReadLine());
Console.Write("Introduce el segundo nmero: ");
segundoNumero = Convert.ToInt32(Console.ReadLine());
suma = primerNumero + segundoNumero;
Console.Write("La suma de ");
Console.Write(primerNumero);
Console.Write(" y ");
Console.Write(segundoNumero);
Console.Write(" es ");
Console.WriteLine(suma);
}
}

Ejercicios propuestos:
(1.11.1) El usuario teclear dos nmeros (a y b), y el programa mostrar el
resultado de la operacin (a+b)*(a-b) y el resultado de la operacin a2-b2.
Ambos resultados se deben mostrar en la misma lnea.
(1.11.2) Pedir al usuario un nmero y mostrar su tabla de multiplicar,
usando {0},{1} y {2}. Por ejemplo, si el nmero es el 3, debera 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
nmero y muestra su tabla de multiplicar. Esta vez no debers utilizar {0},
{1}, {2}, sino "Write".
(1.11.4) Crea un programa que convierta de grados Celsius (centgrados) a
Kelvin y a Fahrenheit: pedir al usuario la cantidad de grados centgrados y
usar las siguiente tablas de conversin: kelvin = celsius + 273 ; fahrenheit
= celsius x 18 / 10 + 32. Emplea "Write" en vez de "{0}" cuando debas
mostrar varios datos en la misma lnea.

Revisin 0.99zz Pgina 46


Introduccin a la programacin con C#, por Nacho Cabanes

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
funcin de si se cumplen ciertas condiciones o no. En muchas ocasiones, adems
esos pasos debern ser repetitivos. Vamos a ver cmo podemos comprobar si se
cumplen condiciones y tambin cmo hacer que un bloque de un programa se
repita.

2.1. Estructuras alternativas


2.1.1. if
La primera construccin que emplearemos para comprobar si se cumple una
condicin ser "si ... entonces ...". Su formato es

if (condicin) sentencia;

Es decir, debe empezar con la palabra "if", la condicin se debe indicar entre
parntesis y a continuacin se detallar la orden que hay que realizar en caso de
cumplirse esa condicin, terminando con un punto y coma.

Vamos a verlo con un ejemplo:

// Ejemplo_02_01_01a.cs
// Condiciones con if
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_01a


{
public static void Main()
{
int numero;

Console.WriteLine("Introduce un nmero");
numero = Convert.ToInt32(Console.ReadLine());
if (numero>0) Console.WriteLine("El nmero es positivo.");
}
}

Este programa pide un nmero al usuario. Si es positivo (mayor que 0), escribe en
pantalla "El nmero es positivo."; si es negativo o cero, no hace nada.

Revisin 0.99zz Pgina 47


Introduccin a la programacin con C#, por Nacho Cabanes

Como se ve en el ejemplo, para comprobar si un valor numrico es mayor que


otro, usamos el smbolo ">". Para ver si dos valores son iguales, usaremos dos
smbolos de "igual": if (numero==0). Las dems posibilidades las veremos algo ms
adelante. En todos los casos, la condicin que queremos comprobar deber
indicarse entre parntesis.

Este programa comienza por un comentario que nos recuerda de qu se trata.


Como nuestros fuentes irn siendo cada vez ms complejos, a partir de ahora
incluiremos comentarios que nos permitan recordar de un vistazo qu
pretendamos hacer.

Si la orden "if" es larga, se puede partir en dos lneas para que resulte ms legible:

if (numero>0)
Console.WriteLine("El nmero es positivo.");

Ejercicios propuestos:
(2.1.1.1) Crea un programa que pida al usuario un nmero 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 nmeros enteros y diga cul es
el mayor de ellos.
(2.1.1.3) Crea un programa que pida al usuario dos nmeros enteros y diga si el
primero es mltiplo del segundo (pista: igual que antes, habr que ver si el resto
de la divisin es cero: a % b == 0).

2.1.2. if y sentencias compuestas


Habamos dicho que el formato bsico de "if" es if (condicin) sentencia; Esa
"sentencia" que se ejecuta si se cumple la condicin puede ser una sentencia
simple o una compuesta. Las sentencias compuestas se forman agrupando varias
sentencias simples entre llaves ( { y } ), como en este ejemplo:

// Ejemplo_02_01_02a.cs
// Condiciones con if (2): Sentencias compuestas
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_02a


{
public static void Main()
Revisin 0.99zz Pgina 48
Introduccin a la programacin con C#, por Nacho Cabanes

{
int numero;

Console.WriteLine("Introduce un nmero");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0)
{
Console.WriteLine("El nmero es positivo.");
Console.WriteLine("Recuerde que tambin puede usar negativos.");
} // Aqu acaba el "if"
} // Aqu acaba "Main"
} // Aqu acaba "Ejemplo06

En este caso, si el nmero es positivo, se hacen dos cosas: escribir un texto y


luego... escribir otro! (no es gran cosa; ms adelante iremos encontrando casos
en lo que necesitemos hacer cosas "ms serias" dentro de una sentencia
compuesta).

Como se ve en este ejemplo, cada nuevo "bloque" se suele escribir un poco ms a


la derecha que los anteriores, para que sea fcil ver dnde comienza y termina
cada seccin de un programa. Por ejemplo, el contenido de "Ejemplo06" est un
poco ms a la derecha que la cabecera "public class Ejemplo06", y el contenido de
"Main" algo ms a la derecha, y la sentencia compuesta que se debe realizar si se
cumple la condicin del "if" est an ms a la derecha. Este "sangrado" del texto
se suele llamar "escritura indentada". Un tamao habitual para el sangrado es de
4 espacios, aunque en este texto en algunas ocasiones usaremos slo dos
espacios, para que fuentes ms complejos quepan entre los mrgenes del papel.

Ejercicios propuestos:
(2.1.2.1) Crea un programa que pida al usuario un nmero entero. Si es mltiplo
de 10, informar al usuario y pedir un segundo nmero, para decir a
continuacin si este segundo nmero tambin es mltiplo de 10.

2.1.3. Operadores relacionales: <, <=, >, >=, ==, !=


Hemos visto que el smbolo ">" es el que se usa para comprobar si un nmero es
mayor que otro. El smbolo de "menor que" tambin es sencillo, pero los dems
son un poco menos evidentes, as que vamos a verlos:

Operador Operacin
< Menor que
> Mayor que
<= Menor o igual que
>= Mayor o igual que
== Igual a
!= No igual a (distinto de)
Revisin 0.99zz Pgina 49
Introduccin a la programacin con C#, por Nacho Cabanes

As, un ejemplo, que diga si un nmero no es cero sera:

// Ejemplo_02_01_03a.cs
// Condiciones con if (3): "distinto de"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_03a


{
public static void Main()
{
int numero;

Console.WriteLine("Introduce un nmero");
numero = Convert.ToInt32(Console.ReadLine());
if (numero != 0)
Console.WriteLine("El nmero no es cero.");
}
}

Ejercicios propuestos:
(2.1.3.1) Crea un programa que multiplique dos nmeros enteros de la siguiente
forma: pedir al usuario un primer nmero entero. Si el nmero que se que teclee
es 0, escribir en pantalla "El producto de 0 por cualquier nmero es 0". Si se ha
tecleado un nmero distinto de cero, se pedir al usuario un segundo nmero y se
mostrar el producto de ambos.
(2.1.3.2) Crea un programa que pida al usuario dos nmeros enteros. Si el
segundo no es cero, mostrar el resultado de dividir entre el primero y el segundo.
Por el contrario, si el segundo nmero 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
condicin, usando la orden "else" (en caso contrario), as:

// Ejemplo_02_01_04a.cs
// Condiciones con if (4): caso contrario ("else")
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_04a


{
public static void Main()
{
int numero;
Revisin 0.99zz Pgina 50
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("Introduce un nmero");
numero = Convert.ToInt32(Console.ReadLine());
if (numero > 0)
Console.WriteLine("El nmero es positivo.");
else
Console.WriteLine("El nmero es cero o negativo.");
}
}

Podramos intentar evitar el uso de "else" si utilizamos un "if" a continuacin de


otro, as:

// Ejemplo_02_01_04b.cs
// Condiciones con if (5): caso contrario, sin "else"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_04b


{
public static void Main()
{
int numero;

Console.WriteLine("Introduce un nmero");
numero = Convert.ToInt32(Console.ReadLine());

if (numero > 0)
Console.WriteLine("El nmero es positivo.");

if (numero <= 0)
Console.WriteLine("El nmero es cero o negativo.");
}
}

Pero el comportamiento no es el mismo: en el primer caso (ejemplo 02_01_04a)


se mira si el valor es positivo; si no lo es, se pasa a la segunda orden, pero si lo es,
el programa ya ha terminado. En el segundo caso (ejemplo 02_01_04b), aunque el
nmero sea positivo, se vuelve a realizar la segunda comprobacin para ver si es
negativo o cero, por lo que el programa es algo ms lento.

Podemos enlazar varios "if" usando "else", para decir "si no se cumple esta
condicin, mira a ver si se cumple esta otra":

// Ejemplo_02_01_04c.cs
// Condiciones con if (6): condiciones encadenadas
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_04c


{
Revisin 0.99zz Pgina 51
Introduccin a la programacin con C#, por Nacho Cabanes

public static void Main()


{
int numero;

Console.WriteLine("Introduce un nmero");
numero = Convert.ToInt32(Console.ReadLine());

if (numero > 0)
Console.WriteLine("El nmero es positivo.");
else
if (numero < 0)
Console.WriteLine("El nmero es negativo.");
else
Console.WriteLine("El nmero es cero.");
}
}

Ejercicio propuesto:
(2.1.4.1) Mejora la solucin al ejercicio 2.1.3.1, usando "else".
(2.1.4.2) Mejora la solucin al ejercicio 2.1.3.2, usando "else".

2.1.5. Operadores lgicos: &&, ||, !


Las condiciones se puede encadenar con "y", "o", "no". Por ejemplo, una partida
de un juego puede acabar si nos quedamos sin vidas o si superamos el ltimo
nivel. Y podemos avanzar al nivel siguiente si hemos llegado hasta la puerta y
hemos encontrado la llave. O deberemos volver a pedir una contrasea si no es
correcta y no hemos agotado los intentos.

Esos operadores se indican de la siguiente forma

Operador Significado
&& Y
|| O
! No

De modo que, ya con la sintaxis de C#, podremos escribir cosas como

if ((opcion==1) && (usuario==2)) ...


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

As, un programa que dijera si dos nmeros introducidos por el usuario son cero,
podra ser:

// Ejemplo_02_01_05a.cs
// Condiciones con if enlazadas con &&
// Introduccin a C#, por Nacho Cabanes

using System;

Revisin 0.99zz Pgina 52


Introduccin a la programacin con C#, por Nacho Cabanes

public class Ejemplo_02_01_05a


{
public static void Main()
{
int n1, n2;

Console.Write("Introduce un nmero: ");


n1 = Convert.ToInt32(Console.ReadLine());
Console.Write("Introduce otro nmero: ");
n2 = Convert.ToInt32(Console.ReadLine());

if ((n1 > 0) && (n2 > 0))


Console.WriteLine("Ambos nmeros son positivos.");
else
Console.WriteLine("Al menos uno no es positivo.");
}
}

Una curiosidad: en C# (y en algn otro lenguaje de programacin), la evaluacin de


dos condiciones que estn enlazadas con "Y" se hace "en cortocircuito": si la
primera de las condiciones no se cumple, ni siquiera se llega a comprobar la
segunda, porque se sabe de antemano que la condicin formada por ambas no
podr ser cierta. Eso supone que en el primer ejemplo anterior, if ((opcion==1)
&& (usuario==2)), si "opcion" no vale 1, el compilador no se molesta en ver cul es
el valor de "usuario", porque, sea el que sea, no podr hacer que sea "verdadera"
toda la expresin. Lo mismo ocurrira si hay dos condiciones enlazadas con "o", y
la primera de ellas es "verdadera": no ser necesario comprobar la segunda,
porque ya se sabe que la expresin global ser "verdadera".

Como la mejor forma de entender este tipo de expresiones es practicndolas,


vamos a ver unos cuantos ejercicios propuestos...

Ejercicios propuestos:
(2.1.5.1) Crea un programa que pida al usuario un nmero entero y responda si es
mltiplo de 2 o de 3.
(2.1.5.2) Crea un programa que pida al usuario un nmero entero y responda si es
mltiplo de 2 y de 3 simultneamente.
(2.1.5.3) Crea un programa que pida al usuario un nmero entero y responda si es
mltiplo de 2 pero no de 3.
(2.1.5.4) Crea un programa que pida al usuario un nmero entero y responda si no
es mltiplo de 2 ni de 3.
(2.1.5.5) Crea un programa que pida al usuario dos nmeros enteros y diga si
ambos son pares.
(2.1.5.6) Crea un programa que pida al usuario dos nmeros enteros y diga si (al
menos) uno es par.
Revisin 0.99zz Pgina 53
Introduccin a la programacin con C#, por Nacho Cabanes

(2.1.5.7) Crea un programa que pida al usuario dos nmeros enteros y diga si uno
y slo uno es par.
(2.1.5.8) Crea un programa que pida al usuario dos nmeros enteros y diga "Uno
de los nmeros es positivo", "Los dos nmeros son positivos" o bien "Ninguno de
los nmeros es positivo", segn corresponda.
(2.1.5.9) Crea un programa que pida al usuario tres nmeros y muestre cul es el
mayor de los tres.
(2.1.5.10) Crea un programa que pida al usuario dos nmeros enteros y diga si son
iguales o, en caso contrario, cul es el mayor de ellos.

2.1.6. El peligro de la asignacin en un "if"


Cuidado con el comparador de igualdad: hay que recordar que el formato es if
(a==b) ... Si no nos acordamos y escribimos if (a=b), estamos intentando
asignar a "a" el valor de "b".

En algunos compiladores de lenguaje C, esto podra ser un problema serio, porque


se considera vlido hacer una asignacin dentro de un "if" (aunque la mayora de
compiladores modernos al menos nos avisaran de que quiz estemos asignando
un valor sin pretenderlo, pero no es un "error" que invalide la compilacin, sino un
"aviso", lo que permite que se genere un ejecutable, y podramos pasar por alto el
aviso, dando lugar a un funcionamiento incorrecto de nuestro programa).

En el caso del lenguaje C#, este riesgo no existe, porque la "condicin" debe ser
algo cuyo resultado sea "verdadero" o "falso" (lo que pronto llamaremos un dato
de tipo "bool"), de modo que obtendramos un error de compilacin "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: comparacin incorrecta
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_06a


{
public static void Main()
{
int numero;

Console.WriteLine("Introduce un nmero");
numero = Convert.ToInt32(Console.ReadLine());
if (numero = 0)

Revisin 0.99zz Pgina 54


Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("El nmero es cero.");


else
if (numero < 0)
Console.WriteLine("El nmero es negativo.");
else
Console.WriteLine("El nmero es positivo.");
}
}

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 comparacin al revs,
colocando el nmero en el lado izquierdo, de modo que si olvidamos el doble
signo de "=", obtendramos una asignacin no vlida y el programa no compilara:

if (0 == numero) ...

Ejercicios propuestos:
(2.1.6.1) Crea una variante del ejemplo 02_01_06a, en la que la comparacin de
igualdad sea correcta y en la que las variables aparezcan en el lado derecho de la
comparacin y los nmeros en el lado izquierdo.

2.1.7. Introduccin a los diagramas de flujo


A veces puede resultar difcil ver claro donde usar un "else" o qu instrucciones de
las que siguen a un "if" deben ir entre llaves y cuales no. Generalmente la
dificultad est en el hecho de intentar teclear directamente un programa en C#, en
vez de pensar en el problema que se pretende resolver.

Para ayudarnos a centrarnos en el problema, existen notaciones grficas, como los


diagramas de flujo, que nos permiten ver mejor qu se debe hacer y cuando.

En primer lugar, vamos a ver los 4 elementos bsicos de un diagrama de flujo, y


luego los aplicaremos a un caso concreto.

Es decir:
El inicio o el final del programa se indica dentro de un crculo.

Revisin 0.99zz Pgina 55


Introduccin a la programacin con C#, por Nacho Cabanes

Los procesos internos, como realizar operaciones, se encuadran en un


rectngulo.
Las entradas y salidas (escrituras en pantalla y lecturas de teclado) se
indican con un paralelogramo que tenga su lados superior e inferior
horizontales, pero no tenga verticales los otros dos.
Las decisiones se indican dentro de un rombo, desde el que saldrn dos
flechas. Cada una de ellas corresponder a la secuencia de pasos a dar si se
cumple una de las dos opciones posibles.

Vamos a aplicarlo al ejemplo de un programa que pida un nmero al usuario y


diga si es positivo, negativo o cero:

Revisin 0.99zz Pgina 56


Introduccin a la programacin con C#, por Nacho Cabanes

El paso de aqu al correspondiente programa en lenguaje C# (el que vimos en el


ejemplo 11) debe ser casi inmediato: sabemos como leer de teclado, como escribir
en pantalla, y las decisiones sern un "if", que si se cumple ejecutar la sentencia
que aparece en su salida "si" y si no se cumple ("else") ejecutar lo que aparezca
en su salida "no".

Eso s, hay que tener en cuenta que sta es una notacin 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.

Revisin 0.99zz Pgina 57


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(2.1.7.1) Crea el diagrama de flujo para el programa que pide dos nmeros al
usuario y dice cul es el mayor de los dos.
(2.1.7.2) Crea el diagrama de flujo para el programa que pide al usuario dos
nmeros 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 nmeros al
usuario y dice cul es el mayor de los tres.

2.1.8. Operador condicional: ?


En C#, al igual que en la mayora de lenguajes que derivan de C, hay otra forma de
asignar un valor segn se cumpla una condicin o no, ms compacta pero tambin
ms difcil de leer. Es el "operador condicional" ? : (tambin conocido como
"operador ternario"), que se usa

nombreVariable = condicion ? valor1 : valor2;

y equivale a decir "si se cumple la condicin, toma el valor valor1; si no, toma el
valor valor2". Un ejemplo de cmo podramos usarlo sera para calcular el mayor
de dos nmeros:

numeroMayor = a>b ? a : b;

esto equivale a la siguiente orden "if":

if ( a > b )
numeroMayor = a;
else
numeroMayor = b;

Al igual que en este ejemplo, podremos usar el operador condicional cuando


queramos optar entre dos valores posibles para una variable, dependiendo de si
se cumple o no una condicin.

Aplicado a un programa sencillo, podra ser

// Ejemplo_02_01_08a.cs
// El operador condicional
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_08a


{
public static void Main()
{
Revisin 0.99zz Pgina 58
Introduccin a la programacin con C#, por Nacho Cabanes

int a, b, mayor;

Console.Write("Escriba un nmero: ");


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

mayor = a>b ? a : b;

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


}
}

Un segundo ejemplo, que sume o reste dos nmeros segn la opcin que se
escoja, sera:

// Ejemplo_02_01_08b.cs
// El operador condicional (2)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_08b


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

Console.Write("Escriba un nmero: ");


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

Console.Write("Escriba otro: ");


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

Console.Write("Escriba una operacin (1 = resta; otro = suma): ");


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

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


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

Ejercicios propuestos:
(2.1.8.1) Crea un programa que use el operador condicional para mostrar un el
valor absoluto de un nmero de la siguiente forma: si el nmero 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 nmeros.

Revisin 0.99zz Pgina 59


Introduccin a la programacin con C#, por Nacho Cabanes

2.1.9. switch
Si queremos ver varios posibles valores, sera muy pesado tener que hacerlo con
muchos "if" seguidos o encadenados. La alternativa es emplear la orden "switch",
cuya sintaxis es

switch (expresin)
{
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 expresin a analizar, entre parntesis.
Despus, tras varias rdenes "case" se indica cada uno de los valores
posibles.
Los pasos (porque pueden ser varios) que se deben dar si la expresin tiene
un cierto valor se indican a continuacin, terminando con "break".
Si hay que hacer algo en caso de que no se cumpla ninguna de las
condiciones, se detalla despus de la palabra "default".
Si dos casos tienen que hacer lo mismo, se aade "goto case" a uno de
ellos para indicarlo.

Vamos a ver un ejemplo, que diga si el smbolo que introduce el usuario es una
cifra numrica, un espacio u otro smbolo. Para ello usaremos un dato de tipo
"char" (carcter), que veremos con ms detalle en el prximo 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)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_09a


{
public static void Main()
{
char letra;
Revisin 0.99zz Pgina 60
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("Introduce una letra");


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

switch (letra)
{
case ' ': Console.WriteLine("Espacio.");
break;
case '1': goto case '0';
case '2': goto case '0';
case '3': goto case '0';
case '4': goto case '0';
case '5': goto case '0';
case '6': goto case '0';
case '7': goto case '0';
case '8': goto case '0';
case '9': goto case '0';
case '0': Console.WriteLine("Dgito.");
break;
default: Console.WriteLine("Ni espacio ni dgito.");
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 excepcin de que un
caso no haga absolutamente nada excepto dejar pasar el control al siguiente caso,
y en ese caso se puede dejar totalmente vaco:

// Ejemplo_02_01_09b.cs
// La orden "switch" (variante sin break)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_09b


{
public static void Main()
{
char letra;

Console.WriteLine("Introduce una letra");


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

switch (letra)
{
case ' ': Console.WriteLine("Espacio.");
break;
case '1':
case '2':
case '3':
case '4':
case '5':
Revisin 0.99zz Pgina 61
Introduccin a la programacin con C#, por Nacho Cabanes

case '6':
case '7':
case '8':
case '9':
case '0': Console.WriteLine("Dgito.");
break;
default: Console.WriteLine("Ni espacio ni dgito.");
break;
}
}
}

En el lenguaje C, que es ms antiguo, slo se poda usar "switch" para comprobar


valores de variables "simples" (numricas y caracteres); en C#, que es un lenguaje
ms evolucionado, se puede usar tambin para comprobar valores de cadenas de
texto ("strings").

Una cadena de texto, como veremos con ms detalle en el prximo 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" podra ser:

// Ejemplo_02_01_09c.cs
// La orden "switch" con cadenas de texto
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_01_09c


{
public static void Main()
{
string nombre;

Console.WriteLine("Introduce tu nombre");
nombre = Console.ReadLine();

switch (nombre)
{
case "Juan": Console.WriteLine("Bienvenido, Juan.");
break;
case "Pedro": Console.WriteLine("Que tal estas, Pedro.");
break;
default: Console.WriteLine("Procede con cautela,
desconocido.");
break;
}
}
}

Ejercicios propuestos:

Revisin 0.99zz Pgina 62


Introduccin a la programacin con C#, por Nacho Cabanes

(2.1.9.1) Crea un programa que pida un nmero del 1 al 5 al usuario, y escriba el


nombre de ese nmero, usando "switch" (por ejemplo, si introduce "1", el
programa escribir "uno").
(2.1.9.2) Crea un programa que lea una letra tecleada por el usuario y diga si se
trata de un signo de puntuacin (. , ; :), una cifra numrica (del 0 al 9) o algn otro
carcter, usando "switch" (pista: habr que usar un dato de tipo "char").
(2.1.9.3) Crea un programa que lea una letra tecleada por el usuario y diga si se
trata de una vocal, una cifra numrica o una consonante, usando "switch".
(2.1.9.4) Repite el ejercicio 2.1.9.1, empleando "if" en lugar de "switch".
(2.1.9.5) Repite el ejercicio 2.1.9.2, empleando "if" en lugar de "switch" (pista: como
las cfras numricas del 0 al 9 estn ordenadas, no hace falta comprobar los 10
valores, sino que se puede hacer con "if ((simbolo >= '0') && (simbolo <='9'))").
(2.1.9.6) Repite el ejercicio 2.1.9.3, empleando "if" en lugar de "switch".

2.2. Estructuras repetitivas


Hemos visto cmo comprobar condiciones, pero no cmo hacer que una cierta
parte de un programa se repita un cierto nmero de veces o mientras se cumpla
una condicin (lo que llamaremos un "bucle"). En C# tenemos varias formas de
conseguirlo.

2.2.1. while

2.2.1.1. Estructura bsica de un bucle "while"


Si queremos hacer que una seccin de nuestro programa se repita mientras se
cumpla una cierta condicin, usaremos la orden "while". Esta orden tiene dos
formatos distintos, segn comprobemos la condicin al principio o al final del
bloque repetitivo.

En el primer caso, su sintaxis es

while (condicin)
sentencia;

Es decir, la sentencia se repetir mientras la condicin sea cierta. Si la condicin


es falsa ya desde un principio, la sentencia no se ejecuta nunca. Si queremos que
se repita ms de una sentencia, basta agruparlas entre llaves: { y }.

Un ejemplo que nos diga si cada nmero que tecleemos es positivo o negativo, y
que termine cuando tecleemos el nmero 0, podra ser:
Revisin 0.99zz Pgina 63
Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_02_02_01_01a.cs
// La orden "while": mientras...
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_01_01a


{
public static void Main()
{
int numero;

Console.Write("Teclea un nmero (0 para salir): ");


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

while (numero != 0)
{
if (numero > 0) Console.WriteLine("Es positivo");
else Console.WriteLine("Es negativo");

Console.WriteLine("Teclea otro nmero (0 para salir): ");


numero = Convert.ToInt32(Console.ReadLine());
}
}
}

En este ejemplo, si se introduce 0 la primera vez, la condicin es falsa y ni siquiera


se entra al bloque del "while", terminando el programa inmediatamente.

Ejercicios propuestos:
(2.2.1.1.1) Crea un programa que pida al usuario su contrasea (numrica).
Deber terminar cuando introduzca como contrasea el nmero 1111, pero
volvrsela a pedir tantas veces como sea necesario.
(2.2.1.1.2) Crea un "calculador de cuadrados": pedir al usuario un nmero y
mostrar su cuadrado. Se repetir mientras el nmero introducido no sea cero
(usa "while" para conseguirlo).
(2.2.1.1.3) Crea un programa que pida de forma repetitiva pares de nmeros al
usuario. Tras introducir cada par de nmeros, responder si el primero es mltiplo
del segundo.
(2.2.1.1.4) Crea una versin mejorada del programa anterior, que, tras introducir
cada par de nmeros, responder si el primero es mltiplo del segundo, o el
segundo es mltiplo del primero, o ninguno de ellos es mltiplo del otro.

Revisin 0.99zz Pgina 64


Introduccin a la programacin con C#, por Nacho Cabanes

2.2.1.2. Contadores usando un bucle "while"


Ahora que sabemos "repetir" cosas, podemos utilizarlo tambin para contar. Por
ejemplo, si queremos contar del 1 al 5, usaramos una variable que empezase en
1, que aumentara una unidad en cada repeticin y se repetira hasta llegar al valor
5, as:

// Ejemplo_02_02_01_02a.cs
// Contar con "while"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_01_02a


{
public static void Main()
{
int n = 1;

while (n < 6)
{
Console.WriteLine(n);
n = n + 1;
}
}
}

Ejercicios propuestos:
(2.2.1.2.1) Crea un programa que escriba en pantalla los nmeros del 1 al 10,
usando "while".
(2.2.1.2.2) Crea un programa que escriba en pantalla los nmeros pares del 26 al
10 (descendiendo), usando "while".
(2.2.1.2.3) Crea un programa calcule cuantas cifras tiene un nmero entero
positivo (pista: se puede hacer dividiendo varias veces entre 10).
(2.2.1.2.4) Crea el diagrama de flujo y la versin en C# de un programa que d al
usuario tres oportunidades para adivinar un nmero del 1 al 10.

2.2.2. do ... while


Este es el otro formato que puede tener la orden "while": en este caso, la condicin
se comprueba al final, de modo que siempre se dar al menos una pasada por la
zona repetitiva (se podra traducir como "repetirmientras"). El punto en que
comienza la parte repetitiva se indica con la orden "do", as:

do
sentencia;
while (condicin);

Revisin 0.99zz Pgina 65


Introduccin a la programacin con C#, por Nacho Cabanes

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 cmo sera el tpico 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)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_02a


{
public static void Main()
{
int valida = 711;
int clave;

do
{
Console.Write("Introduzca su clave numrica: ");
clave = Convert.ToInt32(Console.ReadLine());

if (clave != valida)
Console.WriteLine("No vlida!");
}
while (clave != valida);

Console.WriteLine("Aceptada.");
}
}

En este caso, se comprueba la condicin al final, de modo que se nos preguntar


la clave al menos una vez. Mientras que la respuesta que demos no sea la
correcta, se nos vuelve a preguntar. Finalmente, cuando tecleamos la clave
correcta, el ordenador escribe "Aceptada" y termina el programa.

Como veremos con detalle un poco ms adelante, si preferimos que la clave sea
un texto en vez de un nmero, los cambios al programa son mnimos, basta con
usar "string" e indicar su valor entre comillas dobles:

// Ejemplo_02_02_02b.cs
// La orden "do..while" (2)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_02b


{
public static void Main()
{
string valida = "secreto";
Revisin 0.99zz Pgina 66
Introduccin a la programacin con C#, por Nacho Cabanes

string clave;

do
{
Console.Write("Introduzca su clave: ");
clave = Console.ReadLine();

if (clave != valida)
Console.WriteLine("No vlida!");
}
while (clave != valida);

Console.WriteLine("Aceptada.");
}
}

Ejercicios propuestos:
(2.2.2.1) Crear un programa que pida nmeros positivos al usuario, y vaya
calculando y mostrando la suma de todos ellos (terminar cuando se teclea un
nmero negativo o cero).
(2.2.2.2) Crea un programa que escriba en pantalla los nmeros del 1 al 10,
usando "do..while".
(2.2.2.3) Crea un programa que escriba en pantalla los nmeros pares del 26 al 10
(descendiendo), usando "do..while".
(2.2.2.4) Crea un programa que pida al usuario su identificador y su contrasea
(ambos numricos), y no le permita seguir hasta que introduzca como
identificador "1234" y como contrasea "1111".
(2.2.2.5) Crea un programa que pida al usuario su identificador y su contrasea, y
no le permita seguir hasta que introduzca como nombre "Pedro" y como
contrasea "Peter".

2.2.3. for
sta es la orden que usaremos habitualmente para crear partes del programa que
se repitan un cierto nmero de veces. El formato de "for" es

for (valorInicial; CondicinRepeticin; Incremento)


Sentencia;

As, para contar del 1 al 10, tendramos "1" como valor inicial, "<=10" como
condicin de repeticin, y el incremento sera 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 sera "i=1", la condicin de repeticin sera "i<=10" y el incremento sera
"i=i+1":

Revisin 0.99zz Pgina 67


Introduccin a la programacin con C#, por Nacho Cabanes

for (i=1; i<=10; 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 ms detalle en el prximo tema, de
modo que la forma habitual de crear el contador anterior sera

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


...

En general, ser preferible usar nombres de variable ms descriptivos que "i". As,
un programa que escribiera los nmeros del 1 al 10 podra ser:

// Ejemplo_02_02_03a.cs
// Uso bsico de "for"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_03a


{
public static void Main()
{
int contador;

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


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

Ejercicios propuestos:
(2.2.3.1) Crea un programa que muestre los nmeros del 10 al 20, ambos
incluidos.
(2.2.3.2) Crea un programa que escriba en pantalla los nmeros del 1 al 50 que
sean mltiplos de 3 (pista: habr que recorrer todos esos nmeros y ver si el resto
de la divisin entre 3 resulta 0).
(2.2.3.1.3) Crea un programa que muestre los nmeros 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 nmeros 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 nmeros del 15 al 5, descendiendo
(pista: en cada pasada habr que descontar 1, por ejemplo haciendo i=i-1, que se
puede abreviar i--).

Revisin 0.99zz Pgina 68


Introduccin a la programacin con C#, por Nacho Cabanes

2.2.4. Bucles sin fin


Realmente, en un "for", la parte que hemos llamado "Incremento" no tiene por qu
incrementar la variable, aunque se es su uso ms habitual. Es simplemente una
orden que se ejecuta cuando se termine la "Sentencia" y antes de volver a
comprobar si todava se cumple la condicin de repeticin.

Por eso, si escribimos la siguiente lnea:

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

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


condicin de salida: nos quedamos encerrados dando vueltas dentro de la orden
que siga al "for". El programa no termina nunca. Se trata de un "bucle sin fin".

Un caso todava ms exagerado de algo a lo que se entra y de lo que no se sale


nunca ("bucle sin fin") sera la siguiente orden:

for ( ; ; )

Tambin se puede crear un bucle sin fin usando "while" y usando "do..while", si se
indica una condicin 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 lnea.
(2.2.4.2) Crea un programa que contenga un bucle sin fin que muestre los
nmeros enteros positivos a partir del uno.

2.2.5. Bucles anidados


Los bucles se pueden anidar (incluir uno dentro de otro), de modo que podramos
escribir las tablas de multiplicar del 1 al 5 con:

// Ejemplo_02_02_05a.cs
// "for" anidados
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_05a


{
public static void Main()
{

Revisin 0.99zz Pgina 69


Introduccin a la programacin con C#, por Nacho Cabanes

int tabla, numero;

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


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

(Es decir: tenemos varias tablas, del 1 al 5, y para cada tabla queremos ver el
resultado que se obtiene al multiplicar por los nmeros del 1 al 10).

Ejercicios propuestos:
(2.2.5.1) Crea un programa escriba 4 veces los nmeros del 1 al 5, en una misma
lnea, usando "for": 12345123451234512345.
(2.2.5.2) Crea un programa escriba 4 veces los nmeros del 1 al 5, en una misma
lnea, usando "while": 12345123451234512345.
(2.2.5.3) Crea un programa que, para los nmeros 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.

2.2.6. Repetir sentencias compuestas


En los ltimos ejemplos que hemos visto, despus de "for" haba una nica
sentencia. Si queremos que se hagan varias cosas, basta definirlas como un
bloque (una sentencia compuesta) encerrndolas entre llaves. Por ejemplo, si
queremos mejorar el ejemplo anterior haciendo que deje una lnea en blanco
entre tabla y tabla, sera:

// Ejemplo_02_02_06a.cs
// "for" anidados (2)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_06a


{
public static void Main()
{
int tabla, numero;

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


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

Console.WriteLine();
}
}
Revisin 0.99zz Pgina 70
Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(2.2.6.1) Crea un programa que escriba 4 lneas de texto, cada una de las cuales
estar formada por los nmeros 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 rectngulo formado por esa cantidad de asteriscos:
****
****
****

2.2.7. Contar con letras


Para "contar" no necesariamente hay que usar nmeros. Por ejemplo, podemos
usar letras, si el contador lo declaramos como "char" y los valores inicial y final se
detallan entre comillas simples, as:

// Ejemplo_02_02_07a.cs
// "for" que usa "char"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_07a


{
public static void Main()
{
char letra;

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


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

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


uno.

Como ya hemos comentado, si queremos contar de forma decreciente, o de dos


en dos, o como nos interese, basta indicarlo en la condicin de finalizacin del
"for" y en la parte que lo incrementa. As, podramos escribir las letras de la "z" a la
"a" de la siguiente manera:

// Ejemplo_02_02_07b.cs
// "for" que descuenta
// Introduccin a C#, por Nacho Cabanes

using System;

Revisin 0.99zz Pgina 71


Introduccin a la programacin con C#, por Nacho Cabanes

public class Ejemplo_02_02_07b


{
public static void Main()
{
char letra;

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


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

Ejercicios propuestos:
(2.2.7.1) Crea un programa que muestre las letras de la Z (mayscula) a la A
(mayscula, descendiendo).
(2.2.7.2) Crea un programa que muestre 5 veces las letras de la L (mayscula) a la
N (mayscula), en la misma lnea.

2.2.8. Declarar variables en un "for"


Se puede incluso declarar una nueva variable en el interior de "for", y esa variable
dejar de estar definida cuando el "for" acabe. Es una forma recomendable de
trabajar, porque ayuda a evitar un fallo frecuente: reutilizar variables pero olvidar
volver a darles un valor inicial:

for (int i=1; i<=10; i++) ...

Por ejemplo, el siguiente fuente compila correctamente y puede parecer mostrar


dos veces la tabla de multiplicar del 3, pero el "while" no muestra nada, porque no
hemos vuelto a inicializar la variable "n", as que ya est por encima del valor 10 y
ya no es un valor aceptable para entrar al bloque "while":

// Ejemplo_02_02_08a.cs
// Reutilizacion incorrecta de la variable de un "for"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_08a


{
public static void Main()
{
int n = 1;
// Vamos a mostrar la tabla de multiplicar del 3 con "for"
for (n=1; n<=10; n++)
Console.WriteLine("{0} x 3 = {1}", n, n*3);

// Y ahora con "while"... pero no funcionar correctamente


while (n<=10)
{
Console.WriteLine("{0} x 3 = {1}", n, n*3);
n++;
Revisin 0.99zz Pgina 72
Introduccin a la programacin con C#, por Nacho Cabanes

}
}
}

Si declaramos la variable dentro del "for", la zona de "while" no compilara, lo que


hace que el error de diseo sea evidente:

// Ejemplo_02_02_08b.cs
// Intento de reutilizacion incorrecta de la variable
// de un "for": no compila
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_08b


{
public static void Main()
{
// Vamos a mostrar la tabla de multiplicar del 3 con "for"
for (int n=1; n<=10; n++)
Console.WriteLine("{0} x 3 = {1}", n, n*3);

// Y ahora con "while"... pero no compila


while (n<=10)
{
Console.WriteLine("{0} x 3 = {1}", n, n*3);
n++;
}
}
}

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 podra reescribir de
forma ms segura as:

// Ejemplo_02_02_08c.cs
// "for" anidados, variables en "for"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_08c


{
public static void Main()
{
for (int tabla=1; tabla<=5; tabla++)
{
for (int numero=1; numero<=10; numero++)
Console.WriteLine("{0} por {1} es {2}", tabla, numero,
tabla*numero);

Console.WriteLine();
}
}
}

Revisin 0.99zz Pgina 73


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(2.2.8.1) Crea un programa que escriba 6 lneas de texto, cada una de las cuales
estar formada por los nmeros 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 rectngulo formado por esa cantidad de asteriscos,
como en el ejercicio 2.2.6.2. Debers 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.

2.2.9. Las llaves son recomendables


Sabemos que las "llaves" no son necesarias cuando una orden "for" va a repetir
una nica sentencia, sino cuando se repite un bloque de dos o ms sentencias, y
que lo mismo ocurre con "while", "do-while" e "if". Pero un error frecuente es
repetir inicialmente una nica orden, aadir despus una segunda orden
repetitiva y olvidar las llaves. llaves. Por eso, una alternativa recomendable es
incluir siempre las llaves, aunque esperemos repetir slo una una orden.

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 ms a la derecha, no forma parte de ningn "for", de modo que no se
repite, y no se dejar ningn espacio en blanco entre una tabla de multiplicar y la
siguiente, sino que slo se escribir una lnea en blanco al final, justo antes de
terminar el programa:

// Ejemplo_02_02_09a.cs
// "for" anidados de forma incorrecta, sin llaves
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_09a


{
public static void Main()
{
for (int tabla=1; tabla<=5; tabla++)
for (int numero=1; numero<=10; numero++)
Console.WriteLine("{0} por {1} es {2}", tabla, numero,
tabla*numero);
Console.WriteLine();
}
}

Por eso, una alternativa recomendable es incluir siempre las llaves, aunque
inicialmente esperemos repetir slo una una orden:
Revisin 0.99zz Pgina 74
Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_02_02_09b.cs
// "for" anidados, variables en "for", llaves "redundantes"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_09b


{
public static void Main()
{
for (int tabla=1; tabla<=5; tabla++)
{
for (int numero=1; numero<=10; numero++)
{
Console.WriteLine("{0} por {1} es {2}", tabla, numero,
tabla*numero);
}

Console.WriteLine();
}
}
}

Ejercicios propuestos:
(2.2.9.1) Crea un programa que pida un nmero al usuario y escriba los mltiplos
de 9 que haya entre 1 ese nmero. Debes usar llaves en todas las estructuras de
control, aunque slo incluyan una sentencia.
(2.2.9.2) Crea un programa que pida al usuario dos nmeros y escriba sus
divisores comunes. Debes usar llaves en todas las estructuras de control, aunque
slo incluyan una sentencia.

2.2.10. Interrumpir un bucle: break


Podemos salir de un bucle antes de tiempo si lo interrumpimos con la orden
"break":

// Ejemplo_02_02_10a.cs
// "for" interrumpido con "break"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_10a


{
public static void Main()
{
for (int contador=1; contador<=10; contador++)
{
if (contador==5)
break;

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


Revisin 0.99zz Pgina 75
Introduccin a la programacin con C#, por Nacho Cabanes

}
}
}

El resultado de este programa es:

1 2 3 4

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


valor 10).

Es una orden que se debe tratar de evitar, porque puede conducir a programas
difciles de leer, en los que no se cumple la condicin de repeticin del bucle, sino
que se interrumpe por otros criterios. Como norma general, es preferible
reescribir la condicin del bucle de otra forma. En el ejemplo anterior, bastara que
la condicin fuera "contador < 5". Un ejemplo ligeramente ms complejo podra
ser mostrar los nmeros del 105 al 120 hasta encontrar uno que sea mltiplo de
13, que no se mostrar. Lo podramos hacer de esta forma (poco correcta):

// Ejemplo_02_02_10b.cs
// "for" interrumpido con "break" (2)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_10b


{
public static void Main()
{
for (int contador=105; contador<=120; contador++)
{
if (contador % 13 == 0)
break;

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


}
}
}

O reescribirlo de esta otra (preferible):

// Ejemplo_02_02_10c.cs
// alternativa a un "for" interrumpido con "break"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_10c


{
public static void Main()
{
Revisin 0.99zz Pgina 76
Introduccin a la programacin con C#, por Nacho Cabanes

int contador=105;

while ( (contador<=120) && (contador % 13 != 0) )


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

(S, en la mayora de los casos, un "for" se puede convertir en un "while"; veremos


ms detalles dentro de muy poco).

Ejercicios propuestos:
(2.2.10.1) Crea un programa que pida al usuario dos nmeros y escriba su mximo
comn divisor (pista: una solucin lenta pero sencilla es probar con un "for" todos
los nmeros descendiendo a partir del menor de ambos, hasta llegar a 1; cuando
encuentres un nmero que sea divisor de ambos, interrumpes la bsqueda).
(2.2.10.2) Crea un programa que pida al usuario dos nmeros y escriba su mnimo
comn mltiplo (pista: una solucin lenta pero sencilla es probar con un "for"
todos los nmeros a partir del mayor de ambos, de forma creciente; cuando
encuentres un nmero que sea mltiplo de ambos, interrumpes la bsqueda).

2.2.11. Forzar la siguiente iteracin: continue


Podemos saltar alguna repeticin de un bucle con la orden "continue":

// Ejemplo_02_02_11a.cs
// "for" interrumpido con "continue"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_11a


{
public static void Main()
{
for (int contador=1; contador<=10; contador++)
{
if (contador==5)
continue;

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


}
}
}

El resultado de este programa es:

1 2 3 4 6 7 8 9 10

Revisin 0.99zz Pgina 77


Introduccin a la programacin con C#, por Nacho Cabanes

En l podemos observar que no aparece el valor 5. Se podra haber usado tambin


un "if" que escriba los valores que no sean 5, as:

// Ejemplo_02_02_11b.cs
// Alternativa a "for" interrumpido con "continue"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_11b


{
public static void Main()
{
for (int contador=1; contador<=10; contador++)
{
if (contador != 5)
Console.Write("{0} ", contador);
}
}
}

Ejercicios propuestos:
(2.2.11.1) Crea un programa que escriba los nmeros del 20 al 10, descendiendo,
excepto el 13, usando "continue".
(2.2.11.2) Crea un programa que escriba los nmeros pares del 2 al 106, excepto
los que sean mltiplos de 10, usando "continue".

2.2.12. Equivalencia entre "for" y "while"


En la gran mayora de condiciones, un bucle "for" equivale a un "while"
compactado, de modo que casi cualquier "for" se puede escribir de forma
alternativa como un "while", como en este ejemplo:

// Ejemplo_02_02_12a.cs
// "for" y "while" equivalente
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_12a


{
public static void Main()
{
for (int contador=1; contador<=10; contador++)
{
Console.Write("{0} ", contador);
}

Console.WriteLine();

int n=1;
Revisin 0.99zz Pgina 78
Introduccin a la programacin con C#, por Nacho Cabanes

while (n<=10)
{
Console.Write("{0} ", n);
n++;
}
}
}

Incluso se comportaran igual si no se avanza de uno en uno, o se interrumpe con


"break", pero no en caso de usar un "continue", como muestra este ejemplo:

// Ejemplo_02_02_12b.cs
// "for" y "while" equivalente... con "continue"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_02_12b


{
public static void Main()
{
for (int contador=1; contador<=10; contador++)
{
if (contador == 5)
continue;
Console.Write("{0} ", contador);
}

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 nmero
4, porque cuando se llega al nmero 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 nmeros del 100 al 200, separados
por un espacio, sin avanzar de lnea, usando "for". En la siguiente lnea, vuelve a
escribirlos usando "while".
Revisin 0.99zz Pgina 79
Introduccin a la programacin con C#, por Nacho Cabanes

(2.2.12.2) Crea un programa que escriba los nmeros pares del 20 al 10,
descendiendo, excepto el 14, primero con "for" y luego con "while".

2.2.13. Ejercicios resueltos sobre bucles


Existen varios errores frecuentes en el manejo de los bucles. Por ejemplo, incluir
un "punto" y coma tras una orden "for" o "while" puede hacer que nada se repita y
que el programa se comporte de forma errnea. Por eso, aqu tienes varios
ejercicios resueltos, que te ayudarn a "entrenar la vista" para localizar ese tipo de
problemas:

Qu escribira en pantalla este fragmento de cdigo?

for (i=1; i<4; i++) Console.Write("{0} ",i);

Respuesta: los nmeros del 1 al 3 (se empieza en 1 y se repite mientras sea


menor que 4).

Qu escribira en pantalla este fragmento de cdigo?

for (i=1; i>4; i++) Console.Write("{0} ",i);

Respuesta: no escribira nada, porque la condicin es falsa desde el


principio.

Qu escribira en pantalla este fragmento de cdigo?

for (i=1; i<=4; i++); Console.Write("{0} ",i);

Respuesta: escribe un 5, porque hay un punto y coma despus del "for", de


modo que repite cuatro veces una orden vaca, y cuando termina el "for", "i"
ya tiene el valor 5.

Qu escribira en pantalla este fragmento de cdigo?

for (i=1; i<4; ) Console.Write("{0} ",i);

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


"i", luego nunca se llegar a cumplir la condicin de salida.

Revisin 0.99zz Pgina 80


Introduccin a la programacin con C#, por Nacho Cabanes

Qu escribira en pantalla este fragmento de cdigo?

for (i=1; ; i++) Console.Write("{0} ",i);

Respuesta: escribe nmeros crecientes continuamente, comenzando en


uno y aumentando una unidad en cada pasada, pero sin terminar.

Qu escribira en pantalla este fragmento de cdigo?

for ( i = 0 ; i <= 4 ; i++) {


if ( i == 2 ) continue ;
Console.Write("{0} ",i);
}

Respuesta: escribe los nmeros del 0 al 4, excepto el 2.

Qu escribira en pantalla este fragmento de cdigo?

for ( i = 0 ; i <= 4 ; i++) {


if ( i == 2 ) break ;
Console.Write("{0} ",i);
}

Respuesta: escribe los nmeros 0 y 1 (interrumpe en el 2).

Qu escribira en pantalla este fragmento de cdigo?

for ( i = 0 ; i <= 4 ; i++) {


if ( i == 10 ) continue ;
Console.Write("{0} ",i);
}

Respuesta: escribe los nmeros del 0 al 4, porque la condicin del


"continue" nunca se llega a dar.

Qu escribira en pantalla este fragmento de cdigo?

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


if ( i == 2 ) continue ;
Console.Write("{0} ",i);

Revisin 0.99zz Pgina 81


Introduccin a la programacin con C#, por Nacho Cabanes

Respuesta: escribe 5, porque no hay llaves tras el "for", luego slo se repite
la orden "if".

2.3. Saltar a otro punto del programa: goto


El lenguaje C# tambin permite la orden "goto", para hacer saltos incondicionales.
Su uso indisciplinado est muy mal visto, porque puede ayudar a hacer
programas llenos de saltos, muy difciles de seguir. Pero en casos concretos
puede ser muy til, por ejemplo, para salir de un bucle muy anidado (un "for"
dentro de otro "for" que a su vez est dentro de otro "for": en este caso, "break"
slo saldra del "for" ms interno). Al igual que ocurra con la orden "break", ser
preferible replantear las condiciones de forma ms natural, y no utilizar "goto".

El formato de "goto" es

goto donde;

y la posicin de salto se indica con su nombre seguido de dos puntos (:)

donde:

como en el siguiente ejemplo:

// Ejemplo_02_03a.cs
// "for" y "goto"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_03a


{
public static void Main()
{
int i, j;

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


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

salida:
Console.Write("Fin del programa");

}
}

Revisin 0.99zz Pgina 82


Introduccin a la programacin con C#, por Nacho Cabanes

El resultado de este programa es:

i vale 0 y j vale 0.
i vale 0 y j vale 2.
i vale 0 y j vale 4.
i vale 0 y j vale 6.
i vale 0 y j vale 8.
i vale 0 y j vale 10.
i vale 0 y j vale 12.
i vale 0 y j vale 14.
i vale 0 y j vale 16.
i vale 0 y j vale 18.
i vale 0 y j vale 20.
i vale 1 y j vale 0.
i vale 1 y j vale 2.
i vale 1 y j vale 4.
i vale 1 y j vale 6.
Fin del programa

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

Ejercicios propuestos:
(2.3.1) Crea un programa que escriba los nmeros del 1 al 10, separados por un
espacio, sin avanzar de lnea. No puedes usar "for", ni "while", ni "do..while", slo
"if" y "goto".

2.4. Ms sobre diagramas de flujo. Diagramas de Chapin


Cuando comenzamos el tema, vimos cmo ayudarnos de los diagramas de flujo
para plantear lo que un programa debe hacer. Si entendemos esta herramienta, el
paso a C# (o a casi cualquier otro lenguaje de programacin es sencillo). Pero este
tipo de diagramas es antiguo, no tiene en cuenta todas las posibilidades del
lenguaje C# (y de muchos otros lenguajes actuales). Por ejemplo, no existe una
forma clara de representar una orden "switch", que equivaldra a varias
condiciones encadenadas.

Por su parte, un bucle "while" se vera como una condicin que hace que algo se
repita (una flecha que vuelve hacia atrs, al punto en el que se comprobaba la
condicin):

Revisin 0.99zz Pgina 83


Introduccin a la programacin con C#, por Nacho Cabanes

Y un "do...while" se representara como una condicin al final de un bloque que se


repite:

Aun as, existen otras notaciones ms modernas y que pueden resultar ms


cmodas. Slo comentaremos una: los diagramas de Chapin. En estos diagramas,
se representa cada orden dentro de una caja:

Pedir primer nmero


Pedir segundo nmero
Mostrar primer num+segundo num
Revisin 0.99zz Pgina 84
Introduccin a la programacin con C#, por Nacho Cabanes

Las condiciones se denotan dividiendo las cajas en dos:

Pedir nmero n1
Pedir nmero n2
n1>n2?
si no
Decir "n1 es Decir "n2 es
mayor" mayor"

Y las condiciones repetitivas se indican dejando una barra a la izquierda, que


marca qu zona es la que se repite, tanto si la condicin se comprueba al final
(do..while):

Escribir "Teclee su clave"


Leer clave de acceso
Mientras clave correcta

como si se comprueba al principio (while):

Abrir fichero
Mientras haya datos en fichero
Leer dato
Mostrar dato
Cerrar fichero

En ambos casos, no existe una grfica "clara" para los "for".

2.5. foreach
Nos queda por ver otra orden que permite hacer cosas repetitivas: "foreach" (se
traducira "para cada"). La veremos ms adelante, cuando manejemos estructuras
de datos ms 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
todava con muy poco detalle) es la cadena de texto, "string", de la que podramos
obtener las letras una a una con "foreach" as:

// Ejemplo_02_05a.cs
// Primer ejemplo de "foreach"
// Introduccin a C#, por Nacho Cabanes
Revisin 0.99zz Pgina 85
Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class Ejemplo_02_05a


{
public static void Main()
{
Console.Write("Dime tu nombre: ");
string nombre = Console.ReadLine();
foreach(char letra in nombre)
{
Console.WriteLine(letra);
}
}
}

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

2.6. Recomendacin de uso para los distintos tipos de


bucle
En general, nos interesar usar "while" cuando puede que la parte repetitiva no se
llegue a repetir nunca (por ejemplo: cuando leemos un fichero, si el fichero est
vaco, no habr datos que leer).

De igual modo, "do...while" ser lo adecuado cuando debamos repetir al menos


una vez (por ejemplo, para pedir una clave de acceso, se le debe preguntar al
menos una vez al usuario, o quiz ms veces, si la teclea correctamente).

En cuanto a "for", es equivalente a un "while", pero la sintaxis habitual de la orden


"for" hace que sea especialmente til cuando sabemos exactamente cuantas veces
queremos que se repita (por ejemplo: 10 veces sera "for (i=1; i<=10; i++)").
Conceptualmente, si un "for" necesita un "break" para ser interrumpido en un caso
especial, es porque realmente no se trata de un contador, y en ese caso debera
ser reemplazado por un "while", para que el programa resulte ms legible.

Ejercicios propuestos:
(2.6.1) Crear un programa que d al usuario la oportunidad de adivinar un nmero
del 1 al 100 (prefijado en el programa) en un mximo 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 nmero (que teclee el usuario)
como producto de su factores primos. Por ejemplo, 60 = 2 2 3 5
Revisin 0.99zz Pgina 86
Introduccin a la programacin con C#, por Nacho Cabanes

(2.6.3) Crea un programa que calcule un nmero elevado a otro, usando


multiplicaciones sucesivas.
(2.6.4) Crea un programa que "dibuje" un rectngulo formado por asteriscos, con
el ancho y el alto que indique el usuario, usando dos "for" anidados. Por ejemplo,
si desea anchura 4 y altura 3, el rectngulo sera as:
****
****
****
(2.6.5) Crea un programa que "dibuje" un tringulo decreciente, con la altura que
indique el usuario. Por ejemplo, si el usuario dice que desea 4 caracteres de alto, el
tringulo sera as:
****
***
**
*
(2.6.6) Crea un programa que "dibuje" un rectngulo hueco, cuyo borde sea una
fila (o columna) de asteriscos y cuyo interior est formado por espacios en blanco,
con el ancho y el alto que indique el usuario. Por ejemplo, si desea anchura 4 y
altura 3, el rectngulo sera as:
****
* *
****
(2.6.7) Crea un programa que "dibuje" un tringulo creciente, alineado a la
derecha, con la altura que indique el usuario. Por ejemplo, si el usuario dice que
desea 4 caracteres de alto, el tringulo sera as:
*
**
***
****
(2.6.8) Crea un programa que devuelva el cambio de una compra, utilizando
monedas (o billetes) del mayor valor posible. Supondremos que tenemos una
cantidad ilimitada de monedas (o billetes) de 100, 50, 20, 10, 5, 2 y 1, y que no hay
decimales. La ejecucin podra ser algo como:

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 tamao que indique el usuario, hasta un mximo de 9. Por ejemplo, si desea
tamao 5, el cuadrado sera as:
11111
Revisin 0.99zz Pgina 87
Introduccin a la programacin con C#, por Nacho Cabanes

22222
33333
44444
55555

2.7. Una alternativa para el control errores: las


excepciones
La forma "clsica" del control de errores es usar instrucciones "if", que vayan
comprobando cada una de los posibles situaciones que pueden dar lugar a un
error, a medida que estas situaciones llegan. Esto tiende a hacer el programa ms
difcil de leer, porque la lgica de la resolucin del problema se ve interrumpida
por rdenes que no tienen que ver con el problema en s, sino con las posibles
situaciones de error. Por eso, los lenguajes modernos, como C#, permiten una
alternativa: el manejo de "excepciones".

La idea es la siguiente: "intentaremos" dar una serie de pasos, y al final de todos


ellos indicaremos qu hay que hacer en caso de que alguno no se consiga
completar. Esto permite que el programa sea ms legible que la alternativa
"convencional".

Lo haremos dividiendo el fragmento de programa en dos bloques:

En un primer bloque, indicaremos los pasos que queremos "intentar" (try).


A continuacin, detallaremos las posibles situaciones de error (excepciones)
que queremos "interceptar" (catch), y lo que se debe hacer en ese caso.

Lo veremos ms adelante con ms detalle, cuando nuestros programas sean ms


complejos, especialmente en el manejo de ficheros, pero podemos acercarnos
con un primer ejemplo, que intente dividir dos nmeros, e intercepte los posibles
errores:

// Ejemplo_02_07a.cs
// Excepciones (1)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_07a


{

public static void Main()


{
int numero1, numero2, resultado;
Revisin 0.99zz Pgina 88
Introduccin a la programacin con C#, por Nacho Cabanes

try
{
Console.WriteLine("Introduzca el primer numero");
numero1 = Convert.ToInt32( Console.ReadLine() );

Console.WriteLine("Introduzca el segundo numero");


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

resultado = numero1 / numero2;


Console.WriteLine("Su divisin es: {0}", resultado);
}
catch (Exception errorEncontrado)
{
Console.WriteLine("Ha habido un error: {0}",
errorEncontrado.Message);
}
}
}

(La variable "errorEncontrado" es de tipo "Exception", y nos sirve para poder


acceder a detalles como el mensaje correspondiente a ese tipo de excepcin:
errorEncontrado.Message)

En este ejemplo, si escribimos un texto en vez de un nmero, obtendramos como


respuesta

Introduzca el primer numero


hola
Ha habido un error: La cadena de entrada no tiene el formato correcto.

Y si el segundo nmero es 0, se nos dira

Introduzca el primer numero


3
Introduzca el segundo numero
0
Ha habido un error: Intento de dividir por cero.

Una alternativa ms 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 ms detallados, as:

// Ejemplo_02_07b.cs
// Excepciones (2)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_02_07b


{
public static void Main()
Revisin 0.99zz Pgina 89
Introduccin a la programacin con C#, por Nacho Cabanes

{
int numero1, numero2, resultado;

try
{
Console.WriteLine("Introduzca el primer numero");
numero1 = Convert.ToInt32( Console.ReadLine() );

Console.WriteLine("Introduzca el segundo numero");


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

resultado = numero1 / numero2;


Console.WriteLine("Su divisin es: {0}", resultado);
}

catch (FormatException)
{
Console.WriteLine("No es un nmero vlido");
}
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)".

Y cmo sabemos qu excepciones debemos interceptar? La mejor forma es mirar


en la "referencia oficial" para programadores de C#, la MSDN (Microsoft Developer
Network): si tecleamos en un buscador de Internet algo como "msdn convert
toint32" nos llevar a una pgina en la que podemos ver que hay dos excepciones
que podemos obtener en ese intento de conversin de texto a entero:
FormatException (no se ha podido convertir) y OverflowException (nmero
demasiado grande). Otra alternativa ms peligrosa es "probar el programa" y ver
qu errores obtenemos en pantalla al introducir un valor no vlido. Esta
alternativa es la menos deseable, porque quiz pasemos por alto algn tipo de
error que pueda surgir y que nosotros no hayamos contemplado. En cualquier
caso, volveremos a las excepciones ms adelante.

Ejercicios propuestos:
(2.7.1) Crea un programa que pregunte al usuario su edad y su ao de nacimiento.
Si la edad que introduce no es un nmero vlido, mostrar un mensaje de aviso.
Lo mismo ocurrir si el ao de nacimiento no es un nmero vlido.

Revisin 0.99zz Pgina 90


Introduccin a la programacin con C#, por Nacho Cabanes

(2.7.2) Crea un programa que pregunte al usuario su edad y su ao de nacimiento.


Si la edad que introduce no es un nmero vlido, mostrar un mensaje de aviso,
pero aun as le preguntar su ao de nacimiento.

Revisin 0.99zz Pgina 91


Introduccin a la programacin con C#, por Nacho Cabanes

3. Tipos de d atos bsicos


3.1. Tipo de datos entero
Hemos hablado de nmeros enteros, de cmo realizar operaciones sencillas y de
cmo usar variables para reservar espacio y as poder trabajar con datos cuyo
valor no sabemos de antemano.

Empieza a ser el momento de refinar, de dar ms detalles. El primer "matiz"


importante que hemos esquivado hasta ahora es el tamao de los nmeros que
podemos emplear, as como su signo (positivo o negativo). Por ejemplo, un dato
de tipo "int" puede guardar nmeros de hasta unas nueve cifras, tanto positivos
como negativos, y ocupa 4 bytes en memoria.

(Nota: si no sabes lo que es un byte, deberas mirar el Apndice 1 de este texto).

Pero no es la nica opcin. Por ejemplo, si queremos guardar la edad de una


persona, no necesitamos usar nmeros negativos, y nos bastara con 3 cifras, as
que es de suponer que existir algn tipo de datos ms adecuado, que desperdicie
menos memoria. Tambin existe el caso contrario: un banco puede necesitar
manejar nmeros con ms de 9 cifras, as que un dato "int" se les quedara corto.
Siendo estrictos, si hablamos de valores monetarios, necesitaramos usar
decimales, pero eso lo dejamos para el siguiente apartado.

3.1.1. Tipos de datos para nmeros enteros


Los tipos de datos enteros que podemos usar en C#, junto con el espacio que
ocupan en memoria y el rango de valores que nos permiten almacenar son:

Revisin 0.99zz Pgina 92


Introduccin a la programacin con C#, por Nacho Cabanes

Nombre Tamao (bytes) Rango de valores


sbyte 1 -128 a 127
byte 1 0 a 255
short 2 -32768 a 32767
ushort 2 0 a 65535
int 4 -2147483648 a 2147483647
uint 4 0 a 4294967295
long 8 -9223372036854775808 a
9223372036854775807
ulong 8 0 a 18446744073709551615

Como se puede observar en esta tabla, el tipo de dato ms razonable para guardar
edades sera "byte", que permite valores entre 0 y 255, y ocupa 3 bytes menos que
un "int".

// Ejemplo_03_01_01a.cs
// Tipos de nmeros enteros
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_01_01a


{
public static void Main()
{
byte edad = 74;
ushort anyo = 2001;
long resultado = 10000000000;
Console.WriteLine("Los datos son {0}, {1} y {2}",
edad, anyo, resultado);
}
}

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 tambin a calcularlo usando una
variable de tipo "int".

3.1.2. Conversiones de cadena a entero


Si queremos obtener estos datos a partir de una cadena de texto, no siempre nos
servir Convert.ToInt32, porque no todos los datos son enteros de 32 bits (4
bytes). Para datos de tipo "byte" usaramos Convert.ToByte (sin signo) y ToSByte
(con signo), para datos de 2 bytes tenemos ToInt16 (con signo) y ToUInt16 (sin
signo), y para los de 8 bytes existen ToInt64 (con signo) y ToUInt64 (sin signo).

Revisin 0.99zz Pgina 93


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_03_01_02a.cs
// Conversiones para otros tipos de nmeros enteros
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_01_02a


{
public static void Main()
{
string ejemplo1 = "74";
string ejemplo2 = "2001";
string ejemplo3 = "10000000000";

byte edad = Convert.ToByte(ejemplo1);


ushort anyo = Convert.ToUInt16(ejemplo2);
long resultado = Convert.ToInt64(ejemplo3);
Console.WriteLine("Los datos son {0}, {1} y {2}",
edad, anyo, resultado);
}
}

Ejercicios propuestos:
(3.1.2.1) Pregunta al usuario su edad, que se guardar en un "byte". A
continuacin, le debers decir que no aparenta tantos aos (por ejemplo, "No
aparentas 20 aos").
(3.1.2.2) Pide al usuario dos nmeros de dos cifras ("byte"), calcula su
multiplicacin, que se deber guardar en un "ushort", y muestra el resultado en
pantalla.
(3.1.2.3) Pide al usuario dos nmeros enteros largos ("long") y muestra su suma,
su resta y su producto.

3.1.3. Incremento y decremento


Conocemos la forma de realizar las operaciones aritmticas ms habituales. Pero
tambin existe una operacin que es muy frecuente cuando se crean programas,
especialmente (como ya hemos visto) a la hora de controlar bucles: incrementar el
valor de una variable en una unidad:

a = a + 1;

Pues bien, en C# (y en otros lenguajes que derivan de C, como C++, Java y PHP),
existe una notacin ms compacta para esta operacin, y para la opuesta (el
decremento):

a++; es lo mismo que a = a+1;


a--; es lo mismo que a = a-1;

Revisin 0.99zz Pgina 94


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_03_01_03a.cs
// Incremento y decremento
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_01_03a


{
public static void Main()
{
int n = 10;
Console.WriteLine("n vale {0}", n);
n++;
Console.WriteLine("Tras incrementar vale {0}", n);
n--;
n--;
Console.WriteLine("Tras decrementar dos veces, vale {0}", n);
}
}

Pero esto tiene algo ms 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" vala 2, lo que esta instruccin hace es dar a "b" el valor de "a" y
aumentar el valor de "a". Por tanto, al final tenemos que b=2 y a=3
(postincremento: se incrementa "a" tras asignar su valor).

En cambio, si escribimos

b = ++a;

y "a" vala 2, primero aumentamos "a" y luego los asignamos a "b"


(preincremento), de modo que a=3 y b=3.

Por supuesto, tambin podemos distinguir postdecremento (a--) y


predecremento (--a).

Ejercicios propuestos:
(3.1.3.1) Crea un programa que use tres variables x,y,z. Sus valores iniciales sern
15, -10, 2.147.483.647. Se deber incrementar el valor de estas variables. Qu
valores esperas que se obtengan? Contrstalo con el resultado obtenido por el
programa.
(3.1.3.2) Cul sera el resultado de las siguientes operaciones? a=5; b=++a; c=a++;
b=b*5; a=a*2; Calclalo a mano y luego crea un programa que lo resuelva, para
ver si habas hallado la solucin correcta.
Revisin 0.99zz Pgina 95
Introduccin a la programacin con C#, por Nacho Cabanes

3.1.4. Operaciones abreviadas: +=


An hay ms. Tenemos incluso formas reducidas de escribir cosas como "a = a+5".
All van
a += b ; es lo mismo que a = a+b;
a -= b ; es lo mismo que a = a-b;
a *= b ; es lo mismo que a = a*b;
a /= b ; es lo mismo que a = a/b;
a %= b ; es lo mismo que a = a%b;

// Ejemplo_03_01_04a.cs
// Operaciones abreviadas
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_01_04a


{
public static void Main()
{
int n = 10;
Console.WriteLine("n vale {0}", n);
n *= 2;
Console.WriteLine("Tras duplicarlo, vale {0}", n);
n /= 3;
Console.WriteLine("Tras dividirlo entre tres, vale {0}", n);
}
}

Ejercicios propuestos:
(3.1.4.1) Crea un programa que use tres variables x,y,z. Sus valores iniciales sern
15, -10, 214. Debers incrementar el valor de estas variables en 12, usando el
formato abreviado. Qu valores esperas que se obtengan? Contrstalo con el
resultado obtenido por el programa.
(3.1.4.2) Cul sera 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.

3.1.5. Asignaciones mltiples


Ya que estamos hablando de las asignaciones, es interesante comentar que en C#
es posible hacer asignaciones mltiples:

a = b = c = 1;

// Ejemplo_03_01_05a.cs
// Asignaciones mltiples
// Introduccin a C#, por Nacho Cabanes

Revisin 0.99zz Pgina 96


Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class Ejemplo_03_01_05a


{
public static void Main()
{
int a=5, b=2, c=-3;
Console.WriteLine("a={0}, b={1}, c={2}", a, b, c);
a = b = c = 4;
Console.WriteLine("Ahora a={0}, b={1}, c={2}", a, b, c);
a++; b--; c*=2;
Console.WriteLine("Y finalmente a={0}, b={1}, c={2}", a, b, c);
}
}

3.2. Tipo de datos real


Cuando queremos almacenar datos con decimales, no nos sirve el tipo de datos
"int". Necesitamos otro tipo de datos que s est preparado para guardar nmeros
"reales" (con decimales). Al igual que ocurra con los nmeros enteros, tendremos
ms de un tipo de nmero real para elegir.

3.2.1. Coma fija y coma flotante


En el mundo de la informtica hay dos formas de trabajar con nmeros reales:

Coma fija: el nmero mximo de cifras decimales est fijado de antemano, y el


nmero de cifras enteras tambin. Por ejemplo, con un formato de 3 cifras enteras
y 4 cifras decimales, el nmero 3,75 se almacenara correctamente (como
003,7500), el nmero 970,4361 tambin se guardara sin problemas, pero el
5,678642 se guardara como 5,6786 (se perdera a partir de la cuarta cifra decimal)
y el 1020 no se podra guardar (tiene ms de 3 cifras enteras).

Coma flotante: el nmero de decimales y de cifras enteras permitido es variable,


lo que importa es el nmero de cifras significativas (a partir del ltimo 0). Por
ejemplo, con 5 cifras significativas se podran almacenar nmeros como el
13405000000 o como el 0,0000007349 pero no se guardara correctamente el
12,0000034, que se redondeara a un nmero cercano.

Casi cualquier lenguaje de programacin actual va a emplear nmeros de coma


flotante. En C# corresponden al tipo de datos llamado "float".

// Ejemplo_03_02_01a.cs
// Nmeros reales (1: float)
// Introduccin a C#, por Nacho Cabanes
Revisin 0.99zz Pgina 97
Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_01a


{
public static void Main()
{
int i1 = 2, i2 = 3;
float divisionI;

Console.WriteLine("Vamos a dividir 2 entre 3 usando enteros");


divisionI = i1/i2;
Console.WriteLine("El resultado es {0}", divisionI);

float f1 = 2, f2 = 3;
float divisionF;

Console.WriteLine("Vamos a dividir 2 entre 3 usando reales");


divisionF = f1/f2;
Console.WriteLine("El resultado es {0}", divisionF);
}
}

Usando "float" s hemos podido dividir con decimales. El resultado de este


programa es:

Vamos a dividir 2 entre 3 usando enteros


El resultado es 0
Vamos a dividir 2 entre 3 usando reales
El resultado es 0,6666667

Ejercicios propuestos:
(3.2.1.1) Crea un programa que muestre el resultado de dividir 3 entre 4 usando
nmeros enteros y luego usando nmeros de coma flotante.
(3.2.1.2) Cul sera el resultado de las siguientes operaciones, usando nmeros
reales? a=5; a/=2; a+=1; a*=3; --a;

3.2.2. Simple y doble precisin


En la mayora de lenguajes de programacin, contamos con dos tamaos de
nmeros reales para elegir, segn si queremos guardar nmeros con mayor
cantidad de cifras o con menos. Para nmeros con pocas cifras significativas (un
mximo de 7, lo que se conoce como "un dato real de simple precisin") usaremos
el tipo "float" y para nmeros que necesiten ms precisin (unas 15 cifras, "doble
precisin") tenemos el tipo "double". En C# existe un tercer tipo de nmeros
reales, con mayor precisin todava, el tipo "decimal", que se acerca a las 30 cifras
significativas:

Revisin 0.99zz Pgina 98


Introduccin a la programacin con C#, por Nacho Cabanes

float double decimal


Tamao en bits 32 64 128
-45 -324
Valor ms pequeo -1,510 5,010 1,010-28
Valor ms grande 3,41038 1,710308 7,91028
Cifras significativas 7 15-16 28-29

As, podramos plantear el ejemplo anterior con un "double" para obtener un


resultado ms preciso:

// Ejemplo_03_02_02a.cs
// Nmeros reales (2: double)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_02a


{
public static void Main()
{
double n1 = 2, n2 = 3;
double division;

Console.WriteLine("Vamos a dividir 2 entre 3");


division = n1/n2;
Console.WriteLine("El resultado es {0}", division);
}
}

Ahora su resultado sera:

Vamos a dividir 2 entre 3


El resultado es 0,666666666666667

Si queremos dar un valor inicial a un dato "float", debemos llevar cuidado. Es


vlido darle un valor entero, como hemos hecho en el ejemplo anterior:

float x = 2;

pero no podremos dar un valor que contenga cifras decimales, porque el


compilador mostrar un mensaje de error diciendo que lo que aparece a la
derecha es para l un dato "double" y el tipo de la variable es "float", as que se
perder precisin:

float pi = 3.14; // No vlido

Hay un par de formas de solucionarlo. La ms sencilla es aadir el sufijo "f" al


nmero, para indicar al compilar que debe ser tratado como un "float":
Revisin 0.99zz Pgina 99
Introduccin a la programacin con C#, por Nacho Cabanes

float pi = 3.14f; // Dato correcto

Otra forma alternativa es forzar una "conversin 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 nmero entero) para mostrar la longitud de la circunferencia (cuyo
valor ser 2 * PI * radio) podra ser:

// Ejemplo_03_02_02a.cs
// Nmeros reales: valor inicial de un float
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_02b


{
public static void Main()
{
int radio;
float pi = 3.14f; // Atencin a la "f" del final

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
nmeros enteros, luego usando nmeros de coma flotante de simple precisin y
luego con nmeros de doble precisin.
(3.2.2.2) Calcula el rea de un crculo, dado su radio, que ser un nmero entero
(rea = pi * radio al cuadrado)

3.2.3. Pedir nmeros reales al usuario


Al igual que hacamos con los enteros, podemos leer como cadena de texto, y
convertir cuando vayamos a realizar operaciones aritmticas. Ahora usaremos
Convert.ToDouble cuando se trate de un dato de doble precisin, Convert.ToSingle
cuando sea un dato de simple precisin (float) y Convert.ToDecimal para un dato
de precisin extra (decimal):

// Ejemplo_03_02_03a.cs
Revisin 0.99zz Pgina 100
Introduccin a la programacin con C#, por Nacho Cabanes

// Nmeros reales: pedir al usuario


// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_03a


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

Console.WriteLine("Introduce el primer nmero");


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

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


primerNumero, segundoNumero, suma);
}
}

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


decimales usando un punto, como 123.456, al poner el ejecutable en marcha,
cuando se pidan datos al usuario, parte del trabajo se le encarga al sistema
operativo, de modo que si ste sabe que en nuestro pas se usa la "coma" para
separar los decimales, considerar que la coma es el separador correcto y no el
punto, que ser ignorado. Por ejemplo, ocurre si introducimos los datos 23,6 y
34.2 en la versin espaola de Windows 8 obtendremos como respuesta:

Introduce el primer nmero


23,6
Introduce el segundo nmero
34.2
La suma de 23,6 y 342 es 365,6

Ejercicios propuestos:
(3.2.3.1) Calcula el volumen de una esfera, dado su radio, que ser un nmero de
doble precisin (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 nmeros: horas, minutos, segundos),
y muestre la velocidad, en metros por segundo, en kilmetros por hora y en millas
por hora (pista: 1 milla = 1.609 metros).
(3.2.3.3) Halla las soluciones de una ecuacin de segundo grado del tipo y = Ax 2 +
Bx + C. Pista: la raz cuadrada de un nmero x se calcula con Math.Sqrt(x)
(3.2.3.4) Si se ingresan E euros en el banco a un cierto inters I durante N aos, el
dinero obtenido viene dado por la frmula del inters compuesto: Resultado = e
Revisin 0.99zz Pgina 101
Introduccin a la programacin con C#, por Nacho Cabanes

(1+ i)n Aplicarlo para calcular en cuanto se convierten 1.000 euros al cabo de 10
aos al 3% de inters anual.
(3.2.3.5) Crea un programa que muestre los primeros 20 valores de la funcin y =
x2 - 1
(3.2.3.6) Crea un programa que "dibuje" la grfica 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 aproximacin de PI mediante la
expresin: pi/4 = 1/1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + 1/13 ... El usuario deber
indicar la cantidad de trminos a utilizar, y el programa mostrar todos los
resultados hasta esa cantidad de trminos. Debes hacer todas las operacion con
"double".

3.2.4. Conversin de tipos (typecast)


Cuando queremos convertir de un tipo de nmero a otro (por ejemplo, para
quedarnos con la parte entera de un nmero real), tenemos dos alternativas:
Usar Convert, como en x = Convert.ToInt32(y);
Hacer un forzado de tipos, que es ms rpido pero no siempre es posible,
slo cuando el tipo de origen y el de destino se parecen lo suficiente. Para
ello, se precede el valor de la variable con el nuevo tipo de datos entre
parntesis, as: x = (int) y;

Por ejemplo, podramos retocar el programa que calculaba la longitud de la


circunferencia, de modo que su resultado sea un "double", que luego
convertiremos a "float" y a "int" forzando el nuevo tipo de datos:

// Ejemplo_03_02_04a.cs
// Nmeros reales: typecast
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_04a


{
public static void Main()
{
double radio;
float pi = (float) 3.141592654;
double longitud;
float longitudSimplePrec;
int longitudEntera;

Console.WriteLine("Introduce el radio");
radio = Convert.ToDouble(Console.ReadLine());

longitud = 2 * pi * radio;
Revisin 0.99zz Pgina 102
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("La longitud de la circunferencia es");


Console.WriteLine(longitud);

longitudSimplePrec = (float) longitud;


Console.WriteLine("Y con simple precisin");
Console.WriteLine(longitudSimplePrec);

longitudEntera = (int) longitud;


Console.WriteLine("Y como nmero entero");
Console.WriteLine(longitudEntera);
}
}

Su resultado sera:

Introduce el radio
2,3456789
La longitud de la circunferencia es
14,7383356099727
Y con simple precisin
14,73834
Y como nmero entero
14

Ejercicios propuestos:
(3.2.4.1) Crea un programa que calcule la raz cuadrada del nmero que
introduzca el usuario. La raz se deber calcular como "double", pero el resultado
se mostrar como "float"
(3.2.4.2) Crea una nueva versin del un programa que calcula una aproximacin
de PI mediante la expresin: pi/4 = 1/1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + 1/13 (...) con
tantos trminos como indique el usuario. Debes hacer todas las operacion con
"double", pero mostrar el resultado como "float".

3.2.5. Formatear nmeros


En ms de una ocasin nos interesar afinar la apariencia de los nmeros en
pantalla, para mostrar slo una cierta cantidad de decimales: por ejemplo, nos
puede interesar que una cifra que corresponde a dinero se muestre siempre con
dos cifras decimales, o que una nota se muestre redondeada, sin decimales, o con
slo un decimal.

Una forma de conseguirlo es crear una cadena de texto a partir del nmero,
usando "ToString". A esta orden se le puede indicar un dato adicional, que es el
formato numrico que queremos usar, por ejemplo: suma.ToString("0.00")

Algunos de los cdigos de formato que se pueden usar son:

Revisin 0.99zz Pgina 103


Introduccin a la programacin con C#, por Nacho Cabanes

Un cero (0) indica una posicin en la que debe aparecer un nmero, y se


mostrar un 0 si no hay ninguno.
Una almohadilla (#) indica una posicin en la que puede aparecer un
nmero, y no se escribir nada si no hay nmero.
Un punto (.) indica la posicin en la que deber aparecer la coma decimal.
Alternativamente, se pueden usar otros formatos abreviados: por ejemplo,
N2 quiere decir "con dos cifras decimales" y N5 es "con cinco cifras
decimales".

Vamos a probarlos en un ejemplo:

// Ejemplo_03_02_05a.cs
// Formato de nmeros reales
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_05a


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

Console.WriteLine( numero.ToString("N1") );
Console.WriteLine( numero.ToString("N3") );
Console.WriteLine( numero.ToString("0.0") );
Console.WriteLine( numero.ToString("0.000") );
Console.WriteLine( numero.ToString("#.#") );
Console.WriteLine( numero.ToString("#.###") );
}
}

El resultado de este ejemplo sera:

12,3
12,340
12,3
12,340
12,3
12,34

Como se puede ver, ocurre lo siguiente:


Si indicamos menos decimales de los que tiene el nmero, se redondea.
Si indicamos ms decimales de los que tiene el nmero, se mostrarn ceros
si usamos como formato Nx o 0.000, y no se mostrar nada si usamos
#.###
Si indicamos menos cifras antes de la coma decimal de las que realmente
tiene el nmero, aun as se muestran todas ellas.

Revisin 0.99zz Pgina 104


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(3.2.5.1) El usuario de nuestro programa podr teclear dos nmeros de hasta 12
cifras significativas. El programa deber mostrar el resultado de dividir el primer
nmero entre el segundo, utilizando tres cifras decimales.
(3.2.5.2) Crea un programa que use tres variables x,y,z. Las tres sern nmeros
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 permetro, rea y diagonal de un rectngulo, a partir de su
ancho y alto (permetro = suma de los cuatro lados; rea = base x altura; diagonal,
usando el teorema de Pitgoras). 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.

3.2.6. Cambios de base


Un uso alternativo de ToString es el de cambiar un nmero de base. Por ejemplo,
habitualmente trabajamos con nmeros decimales (en base 10), pero en
informtica son tambin muy frecuentes la base 2 (el sistema binario) y la base 16
(el sistema hexadecimal). Podemos convertir un nmero a binario o hexadecimal
(o a base octal, menos frecuente) usando Convert.ToString e indicando la base,
como en este ejemplo:

// Ejemplo_03_02_06a.cs
// De decimal a hexadecimal y binario
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_06a


{
public static void Main()
{
int numero = 247;

Console.WriteLine( Convert.ToString(numero, 16) );


Console.WriteLine( Convert.ToString(numero, 2) );
}
}

Su resultado sera:

f7
11110111
Revisin 0.99zz Pgina 105
Introduccin a la programacin con C#, por Nacho Cabanes

(Si quieres saber ms sobre el sistema hexadecimal, mira los apndices al final de
este texto)

Ejercicios propuestos:
(3.2.6.1) Crea un programa que pida nmeros (en base 10) al usuario y muestre su
equivalente en sistema binario y en hexadecimal. Debe repetirse hasta que el
usuario introduzca el nmero 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 notacin hexadecimal (por ejemplo, FFA000).
(3.2.6.3) Crea un programa para mostrar los nmeros del 0 a 255 en hexadecimal,
en 16 filas de 16 columnas cada una (la primera fila contendr los nmeros del 0 al
15 decimal-, la segunda del 16 al 31 decimal- y as sucesivamente).

Para convertir en sentido contrario, de hexadecimal o binario a decimal,


podemos usar Convert.ToInt32, como se ve en el siguiente ejemplo. Es importante
destacar que una constante hexadecimal se puede expresar precedida por "0x",
como en "int n1 = 0x13;" (donde n1 tendra el valor 16+3=19, expresado en base
10). En los lenguajes C y C++, un valor precedido por "0" se considera octal, de
modo que para "int n2 = 013;" el valor decimal de n2 sera 8+3=11, pero en C# no
es as: un nmero que empiece por 0 (no por 0x) se considera que est escrito en
base 10, como se ve en este ejemplo:

// Ejemplo_03_02_06b.cs
// De hexadecimal y binario a decimal
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_02_06b


{
public static void Main()
{
int n1 = 0x13;
int n2 = Convert.ToInt32("1a", 16);

int n3 = 013; // No es octal, al contrario que en C y C++


int n4 = Convert.ToInt32("14", 8);

int n5 = Convert.ToInt32("11001001", 2);

Console.WriteLine( "{0} {1} {2} {3} {4}",


n1, n2, n3, n4, n5);
}
}

Revisin 0.99zz Pgina 106


Introduccin a la programacin con C#, por Nacho Cabanes

Que mostrara:

19 26 13 12 201

Ejercicios propuestos:
(3.2.6.4) Crea un programa que pida nmeros binarios al usuario y muestre su
equivalente en sistema hexadecimal y en decimal. Debe repetirse hasta que el
usuario introduzca la palabra "fin".

3.3. Tipo de datos carcter


3.3.1. Leer y mostrar caracteres
Como ya vimos brevemente, en C# tambin tenemos un tipo de datos que nos
permite almacenar una nica letra, el tipo "char":

char letra;

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

letra = 'a';

Para leer valores desde teclado, lo podemos hacer de forma similar a los casos
anteriores: leemos toda una frase (que debera tener slo 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, podra ser:

// Ejemplo_03_03_01a.cs
// Tipo de datos "char"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_03_01a


{
public static void Main()
{
char letra;

letra = 'a';
Console.WriteLine("La letra es {0}", letra);
Revisin 0.99zz Pgina 107
Introduccin a la programacin con C#, por Nacho Cabanes

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


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

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 rectngulo formado por
esa cantidad de letras:
XXXX
XXXX
XXXX

3.3.2. Secuencias de escape: \n y otras


Como hemos visto, los textos que aparecen en pantalla se escriben con WriteLine,
indicados entre parntesis y entre comillas dobles. Entonces surge una dificultad:
cmo escribimos una comilla doble en pantalla? La forma de conseguirlo es
usando ciertos caracteres especiales, lo que se conoce como "secuencias de
escape". Existen ciertos caracteres especiales que se pueden escribir despus de
una barra invertida (\) y que nos permiten conseguir escribir esas comillas dobles y
algn otro carcter poco habitual. Por ejemplo, con \" se escribirn unas comillas
dobles, con \' unas comillas simples, con \\ se escribe una barra invertida y con \n
se avanzar a la lnea siguiente de pantalla (es preferible evitar este ltimo, porque
puede no funcionar correctamente en todos los sistemas operativos; ms adelante
veremos una alternativa ms segura).

Estas secuencias especiales son las siguientes:

Secuencia Significado
\a Emite un pitido
\b Retroceso (permite borrar el ltimo carcter)
\f Avance de pgina (expulsa una hoja en la impresora)
\n Avanza de lnea (salta a la lnea siguiente)
\r Retorno de carro (va al principio de la lnea)
\t Salto de tabulacin horizontal
\v Salto de tabulacin vertical
Revisin 0.99zz Pgina 108
Introduccin a la programacin con C#, por Nacho Cabanes

\' Muestra una comilla simple


\" Muestra una comilla doble
\\ Muestra una barra invertida
\0 Carcter nulo (NULL)

Vamos a ver un ejemplo que use los ms habituales:

// Ejemplo_03_03_02a.cs
// Secuencias de escape
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_03_02a


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

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


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

Su resultado sera este:

Esta es una frase

y esta es otra, separada dos lineas

Juguemos mas:

otro salto
Comillas dobles: " y simples ', y barra \

En algunas ocasiones puede ser incmodo manipular estas secuencias de escape.


Por ejemplo, cuando usemos estructuras de directorios al estilo de MsDos y
Windows, deberamos duplicar todas las barras invertidas:
c:\\datos\\ejemplos\\curso\\ejemplo1. En este caso, se puede usar una arroba (@)
antes del texto, en vez de usar las barras invertidas:

ruta = @"c:\datos\ejemplos\curso\ejemplo1"

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


solucionarlo, se duplican las comillas, as:
Revisin 0.99zz Pgina 109
Introduccin a la programacin con C#, por Nacho Cabanes

orden = @"copy ""documento de ejemplo"" f:"

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, escribira "hola".

3.4. Toma de contacto con las cadenas de texto


Al contrario que en lenguajes ms antiguos (como C), las cadenas de texto en C#
son tan fciles de manejar como los dems tipos de datos que hemos visto. Los
detalles que hay que tener en cuenta en un primer acercamiento son:

Se declaran con "string".


Si queremos dar un valor inicial, ste se indica entre comillas dobles.
Cuando leemos con ReadLine, no hace falta convertir el valor obtenido.
Podemos comparar su valor usando "==" (igualdad) o "!=" (desigualdad).

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 podra ser:

// Ejemplo_03_04a.cs
// Uso basico de "string"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_04a


{
public static void Main()
{
string frase;

frase = "Hola, como estas?";


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

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


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

if (frase == "Hola!")
Console.WriteLine("Hola a ti tambin! ");
}
}

Revisin 0.99zz Pgina 110


Introduccin a la programacin con C#, por Nacho Cabanes

Se pueden hacer muchas ms operaciones sobre cadenas de texto: convertir a


maysculas o a minsculas, eliminar espacios, cambiar una subcadena por otra,
dividir en trozos, etc. Pero ya volveremos a las cadenas ms adelante, en el
prximo tema.

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 contrasea. La
contrasea se debe introducir dos veces. Si las dos contraseas no son iguales, se
avisar al usuario y se le volvern a pedir las dos contraseas.

3.5. Los valores "booleanos"


En C# tenemos tambin un tipo de datos llamado "booleano" ("bool"), que puede
tomar dos valores: verdadero ("true") o falso ("false"):

bool encontrado;
encontrado = true;

Este tipo de datos har que podamos escribir de forma sencilla algunas
condiciones que podran 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" tambin se le puede dar como valor el resultado de una
comparacin:

// Ejemplo bsico
partidaTerminada = false;
if (vidas == 0) partidaTerminada = true;
// Notacin alternativa, sin usar "if"
partidaTerminada = vidas == 0;

// Ejemplo ms desarrollado
if (enemigos ==0) && (nivel == ultimoNivel)
partidaTerminada = true;
else
partidaTerminada = true;
// Notacin alternativa, sin usar "if"
partidaTerminada = (enemigos ==0) && (nivel == ultimoNivel);

Revisin 0.99zz Pgina 111


Introduccin a la programacin con C#, por Nacho Cabanes

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


complejas (es la alternativa ms natural a los "break"). Un ejemplo que pida una
letra y diga si es una vocal, una cifra numrica u otro smbolo, usando variables
"bool" podra ser:

// Ejemplo_03_05a.cs
// Variables bool
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_03_05a


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

Console.WriteLine("Introduce una letra");


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

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

esVocal = (letra == 'a') || (letra == 'e') || (letra == 'i') ||


(letra == 'o') || (letra == 'u');

if (esCifra)
Console.WriteLine("Es una cifra numrica.");
else if (esVocal)
Console.WriteLine("Es una vocal.");
else
Console.WriteLine("Es una consonante u otro smbolo.");
}
}

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 nmeros que ha tecleado el
usuario son iguales, o "false" si son distintos.
(3.5.2) Crea una versin 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 nmeros introducidos por
el usuario son pares, o "false" si alguno es impar.
(3.5.4) Crea una versin alternativa del ejercicio 3.5.3, que use "if" en vez del
operador condicional.

Revisin 0.99zz Pgina 112


Introduccin a la programacin con C#, por Nacho Cabanes

4. Arrays, estructuras y cadenas de texto


4.1. Conceptos bsicos sobre arrays o tablas
4.1.1. Definicin de un array y acceso a los datos
Una tabla, vector, matriz o array (que algunos autores traducen por "arreglo") es
un conjunto de elementos, todos los cuales son del mismo tipo, y a los que
accederemos usando el mismo nombre.

Por ejemplo, si queremos definir un grupo de nmeros enteros, el tipo de datos


que usaremos para declararlo ser "int []":

int[] ejemplo;

Cuando sepamos cuantos datos vamos a guardar (por ejemplo 4), podremos
reservar espacio con la orden "new", as:

ejemplo = new int[4];

Si sabemos el tamao desde el principio, podemos reservar espacio a la vez que


declaramos la variable:

int[] ejemplo = new int[4];

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


(ejemplo) y el nmero de elemento que nos interesa, pero con una precaucin: se
empieza a numerar desde 0, as que en el caso anterior tendramos 4 elementos,
que seran ejemplo[0], ejemplo[1], ejemplo[2], ejemplo[3]. Por tanto, podramos
dar el valor 15 al primer elemento de nuestro array as:

ejemplo[0] = 15;

Habitualmente, como un array representa un conjunto de nmeros, utilizaremos


nombres en plural, como en

int[] datos = new int[100];

Como ejemplo, vamos a definir un grupo de 5 nmeros enteros y hallar su suma:

// Ejemplo_04_01_01a.cs
// Primer ejemplo de tablas (arrays)
// Introduccin a C#, por Nacho Cabanes
Revisin 0.99zz Pgina 113
Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class Ejemplo_04_01_01a


{
public static void Main()
{

int[] numeros = new int[5]; // Un array de 5 nmeros enteros


int suma; // Un entero que ser la suma

numeros[0] = 200; // Les damos valores


numeros[1] = 150;
numeros[2] = 100;
numeros[3] = -50;
numeros[4] = 300;
suma = numeros[0] + // Y calculamos la suma
numeros[1] + numeros[2] + numeros[3] + numeros[4];
Console.WriteLine("Su suma es {0}", suma);
// Nota: esta es la forma ms ineficiente e incmoda
// Lo mejoraremos...
}
}

Ejercicios propuestos:
(4.1.1.1) Un programa que pida al usuario 4 nmeros, los memorice (utilizando un
array), calcule su media aritmtica y despus muestre en pantalla la media y los
datos tecleados.
(4.1.1.2) Un programa que pida al usuario 5 nmeros reales (pista: necesitars un
array de "float") y luego los muestre en el orden contrario al que se introdujeron.

4.1.2. Valor inicial de un array


Al igual que ocurra con las variables "normales", podemos dar valor inicial a los
elementos de una tabla al principio del programa, si conocemos todos su valores.
En este caso, los indicaremos todos entre llaves, separados por comas:

// Ejemplo_04_01_02a.cs
// Segundo ejemplo de tablas: valores iniciales con []
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_01_02a


{
public static void Main()
{
int[] numeros = // Un array de 5 nmeros enteros
{200, 150, 100, -50, 300};
int suma; // Un entero que ser su suma

suma = numeros[0] + // Hallamos la suma


numeros[1] + numeros[2] + numeros[3] + numeros[4];
Console.WriteLine("Su suma es {0}", suma);
Revisin 0.99zz Pgina 114
Introduccin a la programacin con C#, por Nacho Cabanes

// Nota: esta forma es algo menos engorrosa, pero todava no


// est bien hecho. Lo seguiremos mejorando.
}
}

Nota: el formato completo para la declaracin de un array con valores iniciales es


ste. incluyendo tanto la orden "new" como los valores entre llaves:

int[] numeros = new int[5] {200, 150, 100, -50, 300};

Pero, como se ve en el ejemplo anterior, casi siempre se podr abreviar, y no ser


necesario usar "new" junto con el tamao, ya que el compilador lo puede deducir
al analizar el contenido del array:

int[] numeros = {200, 150, 100, -50, 300};

Ejercicios propuestos:
(4.1.2.1) Un programa que almacene en una tabla el nmero de das que tiene
cada mes (supondremos que es un ao no bisiesto), pida al usuario que le indique
un mes (1=enero, 12=diciembre) y muestre en pantalla el nmero de das que
tiene ese mes.

4.1.3. Recorriendo los elementos de una tabla


Es de esperar que exista una forma ms cmoda de acceder a varios elementos de
un array, sin tener siempre que repetirlos todos, como hemos hecho en

suma = numeros[0] + numeros[1] + numeros[2] + numeros[3] + numeros[4];

El "truco" consistir en emplear cualquiera de las estructuras repetitivas que ya


hemos visto (while, do..while, for), por ejemplo as:

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


for (int i=0; i<=4; i++) // Y calculamos la suma repetitiva
suma += numeros[i];

El fuente completo podra ser as:

// Ejemplo_04_01_03a.cs
// Tercer ejemplo de tablas: valores iniciales con llaves
// y recorrido con "for"
// Introduccin a C#, por Nacho Cabanes

using System;
Revisin 0.99zz Pgina 115
Introduccin a la programacin con C#, por Nacho Cabanes

public class Ejemplo_04_01_03a


{
public static void Main()
{
int[] numeros = // Un array de 5 nmeros enteros
{200, 150, 100, -50, 300};
int suma; // Un entero que ser su suma

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


for (int i=0; i<=4; i++) // Y calculamos la suma repetitiva
suma += numeros[i];

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


}
}

En este caso, que slo sumbamos 5 nmeros, no hemos escrito mucho menos,
pero si trabajsemos con 100, 500 o 1000 nmeros, la ganancia en comodidad s
que sera evidente.

Lgicamente, tambin podemos pedir los datos al usuario de forma repetitiva,


usando un "for", "while" o "do..while":

// Ejemplo_04_01_03b.cs
// Cuarto ejemplo de tablas: introducir datos repetitvos
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_01_03b


{
public static void Main()
{

int[] numeros = new int[5];


int suma;

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


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

suma = 0; // Y calculamos la suma


for (int i=0; i<=4; i++)
suma += numeros[i];

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


}
}

Ejercicios propuestos:

Revisin 0.99zz Pgina 116


Introduccin a la programacin con C#, por Nacho Cabanes

(4.1.3.1) Crea un programa que pida al usuario 6 nmeros 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 cuntos nmeros enteros va a
introducir (por ejemplo, 10), le pida todos esos nmeros, los guarde en un array y
finalmente calcule y muestre la media de esos nmeros.
(4.1.3.3) Un programa que pida al usuario 10 reales de doble precisin, calcule su
media y luego muestre los que estn por encima de la media.
(4.1.3.4) Un programa que almacene en una tabla el nmero de das que tiene
cada mes (de un ao no bisiesto), pida al usuario que le indique un mes (ej. 2 para
febrero) y un da (ej. el da 15) y diga qu nmero de da es dentro del ao (por
ejemplo, el 15 de febrero sera el da nmero 46, el 31 de diciembre sera el da
365).
(4.1.3.5) A partir del ejercicio anterior, crea otro que pida al usuario que le indique
la fecha, formada por da (1 al 31) y el mes (1=enero, 12=diciembre), y como
respuesta muestre en pantalla el nmero de das que quedan hasta final de ao.
(4.1.3.6) Un programa que pida 10 nombres y los memorice (pista: esta vez se
trata de un array de "string"). Despus 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 mximo de 100
nombres. El usuario deber ir introduciendo un nombre cada vez, hasta que se
pulse Intro sin teclear nada, momento en el que dejarn de pedirse ms 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
mdulo (raz 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 (x1y1+ x2y2+x3y3).
(4.1.3.11) Un programa que pida al usuario 4 nmeros enteros y calcule (y
muestre) cul 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 mximo provisional. El valor inicial de este mximo
provisional no debera ser cero (porque el resultado sera incorrecto si todos los
nmeros son negativos), sino el primer elemento del array.

Revisin 0.99zz Pgina 117


Introduccin a la programacin con C#, por Nacho Cabanes

4.1.4. Operaciones habituales con arrays: buscar, aadir,


insertar, borrar
Algunas operaciones con datos pertenecientes a un array son especialmente
frecuentes: buscar si existe un cierto dato, aadir un dato al final de los existentes,
insertar un dato entre dos que ya hay, borrar uno de los datos almacenados, etc.
Por eso, vamos a ver las pautas bsicas para realizar estas operaciones, y un
fuente de ejemplo.

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 podra interrumpir la bsqueda en cuanto aparezca una
primera vez) o ver en qu posiciones se encuentra (para lo que habra que recorrer
todo el array). Si el array estuviera ordenado, se podra buscar de una forma ms
rpida, pero la veremos ms adelante.

Para encontrar el mximo o el mnimo de los datos, tomaremos el primero de los


datos como valor provisional, y compararemos con cada uno de los dems, para
ver si est por encima o debajo de ese mximo o mnimo provisional, y cambiarlo
si fuera necesario.

Para poder aadir un dato al final de los ya existentes, necesitamos que el array
no est completamente lleno, y llevar un contador de cuntas posiciones hay
ocupadas, de modo que seamos capaces de guardar el dato en la primera posicin
libre.

Para insertar un dato en una cierta posicin, los que queden detrs debern
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
continuacin de l. Tambin habr que actualizar el contador, para indicar que
queda una posicin libre menos.

Si se quiere borrar el dato que hay en una cierta posicin, los que estaban a
continuacin debern 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 posicin libre ms.

Vamos a verlo con un ejemplo:

// Ejemplo_04_01_04a.cs
Revisin 0.99zz Pgina 118
Introduccin a la programacin con C#, por Nacho Cabanes

// Aadir y borrar en un array


// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_01_04a


{
public static void Main()
{
int[] datos = {10, 15, 12, 0, 0};

int capacidad = 5; // Capacidad maxima del array


int cantidad = 3; // Nmero real de datos guardados

int i; // Para recorrer los elementos

// Mostramos el array
for (i=0; i<cantidad; i++)
Console.Write("{0} ",datos[i]);
Console.WriteLine();

// Buscamos el dato "15"


for (i=0; i<cantidad; i++)
if (datos[i] == 15)
Console.WriteLine("15 encontrado en la posicin {0} ", i+1);

// Buscamos el mximo
int maximo = datos[0];
for (i=1; i<cantidad; i++)
if (datos[i] > maximo)
maximo = datos[i];
Console.WriteLine("El mximo es {0} ", maximo);

// Aadimos un dato al final


Console.WriteLine("Aadiendo 6 al final");
if (cantidad < capacidad)
{
datos[cantidad] = 6;
cantidad++;
}

// Y volvemos a mostrar el array


for (i=0; i<cantidad; i++)
Console.Write("{0} ",datos[i]);
Console.WriteLine();

// Borramos el segundo dato


Console.WriteLine("Borrando el segundo dato");
int posicionBorrar = 1;
for (i=posicionBorrar; i<cantidad-1; i++)
datos[i] = datos[i+1];
cantidad--;

// Y volvemos a mostrar el array


for (i=0; i<cantidad; i++)
Console.Write("{0} ",datos[i]);
Console.WriteLine();

// Insertamos 30 en la tercera posicin


if (cantidad < capacidad)
Revisin 0.99zz Pgina 119
Introduccin a la programacin con C#, por Nacho Cabanes

{
Console.WriteLine("Insertando 30 en la posicin 3");
int posicionInsertar = 2;
for (i=cantidad; i>posicionInsertar; i--)
datos[i] = datos[i-1];
datos[posicionInsertar] = 30;
cantidad++;
}

// Y volvemos a mostrar el array


for (i=0; i<cantidad; i++)
Console.Write("{0} ",datos[i]);
Console.WriteLine();
}
}

que tendra como resultado:

10 15 12
15 encontrado en la posicin 2
El mximo es 15
Aadiendo 6 al final
10 15 12 6
Borrando el segundo dato
10 12 6
Insertando 30 en la posicin 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 exista (no sirve emplear un
"else", porque en cada pasada del bucle "for" no sabemos si el dato no existe, slo
sabemos que no est en la posicin 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 cuntas veces se ha
encontrado en caso contrario.
(4.1.4.2) Crea una variante del ejemplo anterior (04_01_04a) que aada 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 posicin que elija el usuario. Debe avisar si la
posicin escogida es incorrecta (porque est ms 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 posicin que elija el usuario. Debe avisar si la posicin escogida
no es vlida.

Revisin 0.99zz Pgina 120


Introduccin a la programacin con C#, por Nacho Cabanes

(4.1.4.5) Crea un programa que prepare espacio para un mximo de 10 nombres.


Deber mostrar al usuario un men que le permita realizar las siguientes
operaciones:
Aadir un dato al final de los ya existentes.
Insertar un dato en una cierta posicin (como ya se ha comentado, los que
quedn detrs debern desplazarse "a la derecha" para dejarle hueco; por
ejemplo, si el array contiene "hola", "adios" y se pide insertar "bien" en la
segunda posicin, el array pasar a contener "hola", "bien", "adios".
Borrar el dato que hay en una cierta posicin (como se ha visto, lo que
estaban detrs debern desplazarse "a la izquierda" para que no haya
huecos; por ejemplo, si el array contiene "hola", "bien", "adios" y se pide
borrar el dato de la segunda posicin, el array pasar a contener "hola",
"adios"
Mostrar los datos que contiene el array.
Salir del programa.

4.1.5. Constantes
En ocasiones, manejaremos valores que realmente no van a variar. Es el caso del
tamao mximo de un array. En esos casos, por legibilidad y por facilidad de
mantenimiento del programa, puede ser preferible no usar el valor numrico, sino
una variable. Dado que el valor de sta no debera cambiar, podemos usar la
palabra "const" para indicar que debe ser constante, y el compilador no permitir
que la modifiquemos por error. Adems, por convenio, para que sea fcil
distinguir una constante de una variable, se suele escribir su nombre totalmente
en maysculas:

const int MAXIMO = 5;

As, una nueva versin del fuente del apartado 4.1.3 (b), usando una constante
para la capacidad del array podra ser:

// Ejemplo_04_01_05a.cs
// Quinto ejemplo de tablas: constantes
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_01_05a


{
public static void Main()
{

const int MAXIMO = 5; // Cantidad de datos


Revisin 0.99zz Pgina 121
Introduccin a la programacin con C#, por Nacho Cabanes

int[] numeros = new int[MAXIMO];


int suma;

for (int i=0; i<MAXIMO; i++) // Pedimos los datos


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

suma = 0; // Y calculamos la suma


for (int i=0; i<MAXIMO; i++)
suma += numeros[i];

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


}
}

Ejercicios propuestos:
(4.1.5.1) Crea un programa que contenga un array con los nombres de los meses
del ao. 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 mximo del array en ambos recorridos.

4.2. Arrays bidimensionales


Podemos declarar tablas de dos o ms dimensiones. Por ejemplo, si un profesor
quiere guardar datos de dos grupos de alumnos, cada uno de los cuales tiene 20
personas, tendra dos opciones:

Se puede usar int datosAlumnos[40] y entonces debemos recordar que


los 20 primeros datos corresponden realmente a un grupo de alumnos y
los 20 siguientes a otro grupo. Es "demasiado artesanal", as que no
daremos ms detalles.

O bien podemos emplear int datosAlumnos[2,20] y entonces sabemos


que los datos de la forma datosAlumnos[0,i] son los del primer grupo, y los
datosAlumnos[1,i] son los del segundo.

Una alternativa, que puede sonar ms 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,
podran ser incluso dos arrays de distinto tamao, como veremos en el
segundo ejemplo.

Revisin 0.99zz Pgina 122


Introduccin a la programacin con C#, por Nacho Cabanes

En cualquier caso, si queremos indicar valores iniciales, lo haremos entre llaves,


igual que si fuera una tabla de una nica dimensin.

Vamos a ver un primer ejemplo de uso con arrays "rectangulares", de la forma


[2,20], lo que podramos llamar el "estilo Pascal". En este ejemplo usaremos tanto
arrays con valores prefijados, como arrays para los que reservemos espacio con
"new" y a los que demos valores en un segundo paso:

// Ejemplo_04_02a.cs
// Array de dos dimensiones "rectangulares" (estilo Pascal)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_02a


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

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


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

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


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

Este tipo de tablas de varias dimensiones son las que se usan tambin para
guardar matrices, cuando se trata de resolver problemas matemticos ms
complejos que los que hemos visto hasta ahora. Si ya has estudiado la teora de
matrices, ms 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 tamao. 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)
// Introduccin a C#, por Nacho Cabanes
Revisin 0.99zz Pgina 123
Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class Ejemplo_04_02b


{
public static void Main()
{
int[][] notas; // Array de dos dimensiones
notas = new int[3][]; // Seran 3 bloques de datos
notas[0] = new int[10]; // 10 notas en un grupo
notas[1] = new int[15]; // 15 notas en otro grupo
notas[2] = new int[12]; // 12 notas en el ultimo

// Damos valores de ejemplo


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

// Y mostramos esos valores


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

} // Fin de "Main"
}

La salida de este programa sera

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 nmeros enteros
(usando un array de dos dimensiones). Despus 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.
Despus pedir al usuario una nueva cadena y comprobar si aparece en alguno
de los dos bloques de informacin anteriores.
(4.2.3) Un programa que pregunte al usuario el tamao que tendrn dos bloques
de nmeros enteros (por ejemplo, uno de 10 elementos y otro de 12). Luego
Revisin 0.99zz Pgina 124
Introduccin a la programacin 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.

Si has estudiado lgebra de matrices:


(4.2.4) Un programa que calcule el determinante de una matriz de 2x2.
(4.2.5) Un programa que calcule el determinante de una matriz de 3x3.
(4.2.6) Un programa que calcule si las filas de una matriz son linealmente
dependientes.
(4.2.7) Un programa que use matrices para resolver un sistema de ecuaciones
lineales mediante el mtodo de Gauss.

4.3. Estructuras o registros


4.3.1. Definicin y acceso a los datos
Un registro es una agrupacin de datos, llamados campos, los cuales no
necesariamente son del mismo tipo. Se definen con la palabra "struct".

En C# (al contrario que en C), primero deberemos declarar cual va a ser la


estructura de nuestro registro, lo que no se puede hacer dentro de "Main". Ms
adelante, ya dentro de "Main", podremos declarar variables de ese nuevo tipo.

Los datos que forman un "struct" pueden ser pblicos o privados. Por ahora, a
nosotros nos interesar que sean accesibles desde el resto de nuestro programa,
por lo que siempre les aadiremos delante la palabra "public" para indicar que
queremos que sean pblicos.

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)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_03_01a


{
struct tipoPersona
{
public string nombre;
public char inicial;
public int edad;
Revisin 0.99zz Pgina 125
Introduccin a la programacin con C#, por Nacho Cabanes

public float nota;


}

public static void Main()


{
tipoPersona persona;

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

Ejercicios propuestos:
(4.3.1.1) Crea un "struct" que almacene datos de una cancin en formato MP3:
Artista, Ttulo, Duracin (en segundos), Tamao del fichero (en KB). Un programa
debe pedir los datos de una cancin al usuario, almacenarlos en dicho "struct" y
despus mostrarlos en pantalla.

4.3.2. Arrays de structs


Hemos guardado varios datos de una persona. Se pueden almacenar los de
varias personas si combinamos el uso de los "struct" con las tablas (arrays) que
vimos anteriormente. Por ejemplo, si queremos guardar los datos de 100
personas podramos hacer:

// Ejemplo_04_03_02a.cs
// Array de struct
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_03_02a


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

public static void Main()


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

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}",
Revisin 0.99zz Pgina 126
Introduccin a la programacin 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 inicial de la primera persona sera "personas[0].inicial", y la edad del ltimo


sera "personas[99].edad".

Al probar este programa obtenemos

La edad de Juan es 20
La edad de Pedro es 0

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


"new", sus valores se dejan "vacos" (0 para los nmeros, cadenas vacas para las
cadenas de texto).

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: aadir una
nueva cancin, mostrar el ttulo de todas las canciones, buscar la cancin que
contenga un cierto texto (en el artista o en el ttulo).
(4.3.2.2) Crea un programa que permita guardar datos de "imgenes" (ficheros de
ordenador que contengan fotografas o cualquier otro tipo de informacin grfica).
De cada imagen se debe guardar: nombre (texto), ancho en pxeles (por ejemplo
2000), alto en pxeles (por ejemplo, 3000), tamao en Kb (por ejemplo 145,6). El
programa debe ser capaz de almacenar hasta 700 imgenes (deber avisar cuando
su capacidad est llena). Debe permitir las opciones: aadir una ficha nueva, ver
todas las fichas (nmero y nombre de cada imagen), buscar la ficha que tenga un
cierto nombre.

4.3.3. structs anidados


Podemos encontrarnos con un registro que tenga varios datos, y que a su vez
ocurra que uno de esos datos est formado por varios datos ms sencillos. Por
ejemplo, una fecha de nacimiento podra estar formada por da, mes y ao. Para
hacerlo desde C#, incluiramos un "struct" dentro de otro, as:

// Ejemplo_04_03_03a.cs
// Registros anidados
// Introduccin a C#, por Nacho Cabanes

Revisin 0.99zz Pgina 127


Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class Ejemplo_04_03_03a


{
struct fechaNacimiento
{
public int dia;
public int mes;
public int anyo;
}

struct tipoPersona
{
public string nombre;
public char inicial;
public fechaNacimiento diaDeNacimiento;
public float nota;
}

public static void Main()


{
tipoPersona persona;

persona.nombre = "Juan";
persona.inicial = 'J';
persona.diaDeNacimiento.dia = 15;
persona.diaDeNacimiento.mes = 9;
persona.nota = 7.5f;
Console.WriteLine("{0} naci en el mes {1}",
persona.nombre, persona.diaDeNacimiento.mes);
}
}

Ejercicios propuestos:
(4.3.3.1) Amplia el programa 4.3.2.1, para que el campo "duracin" se almacene
como minutos y segundos, usando un "struct" anidado que contenga a su vez
estos dos campos.

4.4. Cadenas de caracteres


4.4.1. Definicin. Lectura desde teclado
Hemos visto cmo leer cadenas de caracteres (Console.ReadLine) y cmo
mostrarlas en pantalla (Console.Write), as como la forma de darles un valor (=) y
de comparar cual es su valor (==).

Vamos a repasar todas esas posibilidades, junto con la de formar una cadena a
partir de otras si las unimos con el smbolo de la suma (+), lo que llamaremos
"concatenar" cadenas. Un ejemplo que nos pidiese nuestro nombre y nos
saludase usando todo ello podra ser:

Revisin 0.99zz Pgina 128


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_04_04_01a.cs
// Cadenas de texto (1: manejo bsico)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_01a


{
public static void Main()
{
string saludo = "Hola";
string segundoSaludo;
string nombre, despedida;

segundoSaludo = "Que tal?";


Console.WriteLine("Dime tu nombre... ");
nombre = Console.ReadLine();

Console.WriteLine("{0} {1}", saludo, nombre);


Console.WriteLine(segundoSaludo);

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

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


Console.WriteLine(despedida);
}
}

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 da 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 da del mes por una barra inclinada, as: "Juan, nacido el 31/12".
(4.4.1.4) Crea un programa que pida al usuario dos nmeros enteros y despus
una operacin que realizar con ellos. La operacin podr ser "suma", "resta",
multiplicacin" y "divisin", que tambin se podrn escribir de forma abreviado
con los operadores matemticos "+", "-", "*" y "/". Para multiplicar tambin se
podr usar una "x", minscula o mayscula. A continuacin se mostrar el
resultado de esa operacin (por ejemplo, si los nmeros son 3 y 6 y la operacin es
"suma", el resultado sera 9). La operacin debes tomarla como una cadena de
texto y analizarla con un "switch".

Revisin 0.99zz Pgina 129


Introduccin a la programacin con C#, por Nacho Cabanes

4.4.2. Cmo acceder a las letras que forman una cadena


Podemos leer una de las letras de una cadena, de igual forma que leemos los
elementos de cualquier array: si la cadena se llama "texto", el primer elemento
ser texto[0], el segundo ser texto[1] y as sucesivamente.

Eso s, las cadenas en C# no se pueden modificar letra a letra: no podemos hacer


texto[0]='a'. Para eso habr que usar una construccin auxiliar, que veremos ms
adelante.

// Ejemplo_04_04_02a.cs
// Cadenas de texto (2: acceder a una letra)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_02a


{
public static void Main()
{
string saludo = "Hola";
Console.WriteLine("La tercera letra de {0} es {1}",
saludo, saludo[2]);
}
}

Ejercicios propuestos:
(4.4.2.1) Crea un programa que pregunte al usuario su nombre y le responda cul
es su inicial.

4.4.3. Longitud de la cadena


Podemos saber cuantas letras forman una cadena con "cadena.Length". Esto
permite que podamos recorrer la cadena letra por letra, usando construcciones
como "for".

// Ejemplo_04_04_03a.cs
// Cadenas de texto (3: longitud)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_03a


{
public static void Main()
{
string saludo = "Hola";
int longitud = saludo.Length;
Console.WriteLine("La longitud de {0} es {1}", saludo, longitud);
for (int i=0; i<longitud; i++)
Revisin 0.99zz Pgina 130
Introduccin a la programacin con C#, por Nacho Cabanes

{
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",
debera 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, despus 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 nmeros enteros muy grandes (por
ejemplo, de 30 cifras), que se debern pedir como cadena de texto y analizar letra
a letra.
(4.4.3.5) Un programa capaz de multiplicar dos nmeros enteros muy grandes
(por ejemplo, de 30 cifras), que se debern pedir como cadena de texto y analizar
letra a letra.

4.4.4. Extraer una subcadena


Podemos extraer parte del contenido de una cadena con "Substring", que recibe
dos parmetros: la posicin a partir de la que queremos empezar y la cantidad de
caracteres que queremos obtener. El resultado ser otra cadena:

saludo = frase.Substring(0,4);

Podemos omitir el segundo nmero, y entonces se extraer desde la posicin


indicada hasta el final de la cadena.

// Ejemplo_04_04_04a.cs
// Cadenas de texto (4: substring)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_04a


{
public static void Main()
{
string saludo = "Hola";
string subcadena = saludo.Substring(1,3);
Console.WriteLine("Una subcadena de {0} es {1}",
saludo, subcadena);
}
Revisin 0.99zz Pgina 131
Introduccin a la programacin con C#, por Nacho Cabanes

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", debera 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
tringulo creciente. Por ejemplo, si tu nombre es "Juan", debera aparecer en
pantalla:
J
Ju
Jua
Juan

4.4.5. Buscar en una cadena


Para ver si una cadena contiene un cierto texto, podemos usar IndexOf ("posicin
de"), que nos dice en qu posicin se encuentra, siendo 0 la primera posicin (o
devuelve el valor -1, si no aparece):

if (nombre.IndexOf("Juan") >= 0) Console.Write("Bienvenido, Juan");

Podemos aadir un segundo parmetro opcional, que es la posicin a partir de la


que queremos buscar:

if (nombre.IndexOf("Juan", 5) >= 0) ...

La bsqueda termina al final de la cadena, salvo que indiquemos que termine


antes con un tercer parmetro opcional:

if (nombre.IndexOf("Juan", 5, 15) >= 0) ...

De forma similar, LastIndexOf ("ltima posicin de") indica la ltima aparicin (es
decir, busca de derecha a izquierda).

Si solamente queremos ver si aparece, pero no nos importa en qu posicin est,


nos bastar con usar "Contains":

if (nombre.Contains("Juan")) ...

Revisin 0.99zz Pgina 132


Introduccin a la programacin con C#, por Nacho Cabanes

Un ejemplo de la utilizacin de IndexOf podra ser:

// Ejemplo_04_04_05a.cs
// Cadenas de texto (5: substring)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_05a


{
public static void Main()
{
string saludo = "Hola";
string subcadena = "ola";
Console.WriteLine("{0} aparece dentro de {1} en la posicin {2}",
subcadena, saludo, saludo.IndexOf(subcadena) );
}
}

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 versin del ejercicio 4.4.5.1 en la que, en caso de que alguno de
los textos aparezca como subcadena, se avise adems si se encuentra
exactamente al principio.

4.4.6. Otras manipulaciones de cadenas


Ya hemos comentado que las cadenas en C# son inmutables, no se pueden
modificar. Pero s podemos realizar ciertas operaciones sobre ellas para obtener
una nueva cadena. Por ejemplo:

ToUpper() convierte a maysculas: nombreCorrecto = nombre.ToUpper();


ToLower() convierte a minsculas: password2 = password.ToLower();
Insert(int posicin, string subcadena): Insertar una subcadena en una cierta
posicin de la cadena inicial: nombreFormal = nombre.Insert(0,"Don");
Remove(int posicin, int cantidad): Elimina una cantidad de caracteres en
cierta posicin: apellidos = nombreCompleto.Remove(0,6);
Replace(string textoASustituir, string cadenaSustituta): Sustituye una
cadena (todas las veces que aparezca) por otra: nombreCorregido =
nombre.Replace("Pepe", "Jose");

Revisin 0.99zz Pgina 133


Introduccin a la programacin con C#, por Nacho Cabanes

Un programa que probara todas estas posibilidades podra ser as:

// Ejemplo_04_04_06a.cs
// Cadenas de texto (6: manipulaciones diversas)
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_06a


{

public static void Main()


{
string ejemplo = "Hola, que tal estas";

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


ejemplo);

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


ejemplo[0]);

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


ejemplo.Substring(0,3));

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


ejemplo.Length);

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


ejemplo.IndexOf("que"));

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


ejemplo.LastIndexOf("a"));

Console.WriteLine("En maysculas: {0}",


ejemplo.ToUpper());

Console.WriteLine("En minsculas: {0}",


ejemplo.ToLower());

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


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

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


ejemplo.Remove(0, 6));

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


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

Y su resultado sera

El texto es: Hola, que tal estas


La primera letra es: H
Las tres primeras letras son: Hol
La longitud del texto es: 19
Revisin 0.99zz Pgina 134
Introduccin a la programacin con C#, por Nacho Cabanes

La posicion de "que" es: 6


La ultima A esta en la posicion: 17
En maysculas: HOLA, QUE TAL ESTAS
En minsculas: hola, que tal estas
Si insertamos ", tio": Hola, tio, que tal estas
Si borramos las 6 primeras letras: que tal estas
Si cambiamos ESTAS por ESTAMOS: Hola, que tal estamos

Ejercicios propuestos:
(4.4.6.1) Una variante del ejercicio 4.4.5.2, que no distinga entre maysculas y
minsculas 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 slo un espacio entre cada palabra y la
siguiente).

4.4.7. Descomponer una cadena en fragmentos


Una operacin relativamente frecuente, pero trabajosa, es descomponer una
cadena en varios fragmentos que estn delimitados por ciertos separadores. Por
ejemplo, podramos descomponer una frase en varias palabras que estaban
separadas por espacios en blanco.

Si lo queremos hacer "de forma artesanal", podemos recorrer la cadena buscando


y contando los espacios (o los separadores que nos interesen). As podremos
saber el tamao del array que deber almacenar las palabras (por ejemplo, si hay
dos espacios, tendremos tres palabras). En una segunda pasada, obtendremos las
subcadenas que hay entre cada dos espacios y las guardaramos en el array. No es
especialmente sencillo.

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"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_07a


{
public static void Main()
{

string ejemplo = "uno dos tres cuatro";


char delimitador = ' ';
int i;

Revisin 0.99zz Pgina 135


Introduccin a la programacin con C#, por Nacho Cabanes

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

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


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

}
}

Que mostrara en pantalla lo siguiente:

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

Pero tambin podemos usar como delimitador un conjunto (un array de


caracteres). Por ejemplo, podramos considerar un separador vlido el espacio,
pero tambin la coma, el punto y cualquier otro. Los cambios en el programa
seran mnimos:

// Ejemplo_04_04_07b.cs
// Cadenas de texto: partir con "Split" - 2
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_07b


{
public static void Main()
{
string ejemplo = "uno,dos.tres,cuatro";
char [] delimitadores = {',', '.'};
int i;

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

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


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

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 nmeros separados por espacios
y muestre su suma.

Revisin 0.99zz Pgina 136


Introduccin a la programacin con C#, por Nacho Cabanes

4.4.8. Comparacin de cadenas


Sabemos comprobar si una cadena tiene exactamente un cierto valor, con el
operador de igualdad (==), pero no sabemos comparar qu cadena es "mayor" que
otra, y eso algo que es necesario si queremos ordenar textos. El operador "mayor
que" (>) que usamos con los nmeros no se puede aplicar directamente a las
cadenas. En su lugar, debemos usar "CompareTo", que devolver un nmero
mayor que 0 si la nuestra cadena es mayor que la que indicamos como parmetro
(o un nmero negativo si nuestra cadena es menor, o 0 si son iguales):

if (frase.CompareTo("hola") > 0)
Console.WriteLine("La frase es mayor que hola");

Esto tiene una limitacin: si lo usamos de esa manera, las maysculas y


minsculas se consideran diferentes. Es ms habitual que deseemos comparar sin
distinguir entre maysculas y minsculas, y eso se puede conseguir convirtiendo
ambas cadenas a maysculas antes de convertir, o bien empleando
String.Compare, al que indicamos las dos cadenas y un tercer dato booleano, que
ser "true" cuando queramos ignorar esa distincin:

if (String.Compare(frase, "hola", true) > 0)


Console.WriteLine("Es mayor que hola (mays o mins)");

Un programa completo de prueba podra ser as:

// Ejemplo_04_04_08a.cs
// Cadenas de texto y comparacin alfabtica
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_04_08a


{
public static void Main()
{

string frase;

Console.WriteLine("Escriba una palabra");


frase = Console.ReadLine();

// Compruebo si es exactamente hola


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

// Compruebo si es mayor o menor


if (frase.CompareTo("hola") > 0)
Console.WriteLine("Es mayor que hola");
else if (frase.CompareTo("hola") < 0)
Console.WriteLine("Es menor que hola");
Revisin 0.99zz Pgina 137
Introduccin a la programacin con C#, por Nacho Cabanes

// Comparo sin distinguir maysculas ni minsculas


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

Si tecleamos una palabra como "gol", que comienza por G, que alfabticamente
est antes de la H de "hola", se nos dir que esa palabra es menor:

Escriba una palabra


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

Si escribimos "hOLa", que coincide con "hola" salvo por las maysculas, una
comparacin normal nos dir que es mayor (las maysculas se consideran
"mayores" que las minsculas), y una comparacin sin considerar maysculas o
minsculas nos dir que coinciden:

Escriba una palabra


hOLa
Es mayor que hola
Es hola (mays o mins)

Ejercicios propuestos:
(4.4.8.1) Un programa que pida al usuario dos frases y diga cual sera la "mayor"
de ellas (la que aparecera 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.

4.4.9. Una cadena modificable: StringBuilder


Si tenemos la necesidad de modificar una cadena letra a letra, no podemos usar
un "string" convencional, porque no es vlido hacer cosas como texto[1]='h';
Deberamos formar una nueva cadena en la que modificsemos esa letra a base
de unir varios substring o de borrar un fragmento con Remove y aadir otro con
Insert.

Como alternativa, podemos recurrir a un "StringBuilder", que s lo permiten pero


son algo ms complejos de manejar: hay de reservarles espacio con "new" (igual

Revisin 0.99zz Pgina 138


Introduccin a la programacin con C#, por Nacho Cabanes

que hacamos 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"
// Introduccin a C#, por Nacho Cabanes

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

public class Ejemplo_04_04_09a


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

string cadenaNormal;
cadenaNormal = cadenaModificable.ToString();
Console.WriteLine("Cadena normal a partir de ella: {0}",
cadenaNormal);
}
}

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.) estn en
minsculas y las de las posiciones pares estn en maysculas, mostrando el
resultado en pantalla. Por ejemplo, a partir de un nombre como "Nacho", la
cadena resultante sera "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.

4.5. Recorriendo arrays y cadenas con "foreach"


Existe una construccin parecida a "for", pensada para recorrer ciertas estructuras
de datos, como los arrays y las cadenas de texto (y otras que veremos ms
adelante).

Se usa con el formato "foreach (variable in ConjuntoDeValores)":


Revisin 0.99zz Pgina 139
Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_04_05a.cs
// Ejemplo de "foreach"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_05a


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

string[] nombres = {"Alberto", "Andrs", "Antonio"};


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

string saludo = "Hola";


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

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 lnea nueva, usando "foreach".
(4.5.3) Un programa que pida al usuario varios nmeros separados por espacios y
muestre su suma (como el del ejercicio 4.4.7.2), pero empleando "foreach".

4.6. Ejemplo completo


Vamos a hacer un ejemplo completo que use tablas ("arrays"), registros ("struct") y
que adems manipule cadenas.

La idea va a ser la siguiente: Crearemos un programa que pueda almacenar datos


de hasta 1000 ficheros (archivos de ordenador). Para cada fichero, debe guardar
los siguientes datos: Nombre del fichero, Tamao (en KB, un nmero de 0 a
8.000.000.000). El programa mostrar un men que permita al usuario las
siguientes operaciones:
Revisin 0.99zz Pgina 140
Introduccin a la programacin con C#, por Nacho Cabanes

1- Aadir datos de un nuevo fichero


2- Mostrar los nombres de todos los ficheros almacenados
3- Mostrar ficheros que sean de ms de un cierto tamao (por ejemplo, 2000 KB).
4- Ver todos los datos de un cierto fichero (a partir de su nombre)
5- Salir de la aplicacin (como no usamos ficheros, los datos se perdern).

No debera resultar difcil. Vamos a ver directamente una de las formas en que se
podra plantear y luego comentaremos alguna de las mejoras que se podra
(incluso se debera) hacer.

La nica complicacin real es que el array no estar completamente lleno: habr


espacio para 1000 datos, pero iremos aadiendo de uno en uno. Basta con contar
el nmero de fichas que tenemos almacenadas, y as sabremos en qu posicin
ira la siguiente: si tenemos 0 fichas, deberemos almacenar la siguiente (la
primera) en la posicin 0; si tenemos dos fichas, sern la 0 y la 1, luego
aadiremos en la posicin 2; en general, si tenemos "n" fichas, aadiremos cada
nueva ficha en la posicin "n".

Por otra parte, para revisar todas las fichas existentes, recorreremos desde la
posicin 0 hasta la n-1, haciendo algo como

for (i=0; i<=n-1; i++) { /* ... ms rdenes ... */ }

o bien algo como

for (i=0; i<n; i++) { /* ... ms rdenes ... */ }

(esta segunda alternativa es la que se suele considerar "ms natural" para un


programador en un lenguaje como C#).

El resto del programa no es difcil: sabemos leer y comparar textos y nmeros,


comprobar varias opciones con "switch", etc. Aun as, haremos una ltima
consideracin: hemos limitado el nmero de fichas a 1000, as que, si nos piden
aadir, deberamos asegurarnos antes de que todava tenemos hueco disponible.

Con todo esto, nuestro fuente quedara as:

// Ejemplo_04_06a.cs
// Tabla con muchos struct y men para manejarla
// Introduccin a C#, por Nacho Cabanes

Revisin 0.99zz Pgina 141


Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class Ejemplo_04_06a {

struct tipoFicha {
public string nombreFich; // Nombre del fichero
public long tamanyo; // El tamao en KB
};

public static void Main() {


tipoFicha[] fichas // Los datos en si
= new tipoFicha[1000];
int numeroFichas=0; // Nmero de fichas que ya tenemos
int i; // Para bucles
int opcion; // La opcion del menu que elija el usuario
string textoBuscar; // Para cuando preguntemos al usuario
long tamanyoBuscar; // Para buscar por tamao

do {
// Menu principal, repetitivo
Console.WriteLine();
Console.WriteLine("Escoja una opcin:");
Console.WriteLine("1.- Aadir 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
tamao");
Console.WriteLine("4.- Ver datos de un fichero");
Console.WriteLine("5.- Salir");

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

// Hacemos una cosa u otra segn la opcin escogida


switch(opcion) {

case 1: // Aadir un dato nuevo


if (numeroFichas < 1000) { // Si queda hueco
Console.WriteLine("Introduce el nombre del fichero: ");
fichas[numeroFichas].nombreFich = Console.ReadLine();
Console.WriteLine("Introduce el tamao en KB: ");
fichas[numeroFichas].tamanyo = Convert.ToInt32(
Console.ReadLine() );
// Y ya tenemos una ficha ms
numeroFichas++;
} else // Si no hay hueco para ms fichas, avisamos
Console.WriteLine("Mximo de fichas alcanzado (1000)!");
break;

case 2: // Mostrar todos


for (i=0; i<numeroFichas; i++)
Console.WriteLine("Nombre: {0}; Tamao: {1} KB",
fichas[i].nombreFich, fichas[i].tamanyo);
break;

case 3: // Mostrar segn el tamao


Console.WriteLine("A partir de que tamao quieres ver?");
tamanyoBuscar = Convert.ToInt64( Console.ReadLine() );
for (i=0; i<numeroFichas; i++)
if (fichas[i].tamanyo >= tamanyoBuscar)
Console.WriteLine("Nombre: {0}; Tamao: {1} KB",
Revisin 0.99zz Pgina 142
Introduccin a la programacin con C#, por Nacho Cabanes

fichas[i].nombreFich, fichas[i].tamanyo);
break;

case 4: // Ver todos los datos (pocos) de un fichero


Console.WriteLine("De qu fichero quieres ver todos los datos?");
textoBuscar = Console.ReadLine();
for (i=0; i<numeroFichas; i++)
if ( fichas[i].nombreFich == textoBuscar )
Console.WriteLine("Nombre: {0}; Tamao: {1} KB",
fichas[i].nombreFich, fichas[i].tamanyo);
break;

case 5: // Salir: avisamos de que salimos */


Console.WriteLine("Fin del programa");
break;

default: // Otra opcion: no vlida


Console.WriteLine("Opcin desconocida!");
break;
}
} while (opcion != 5); // Si la opcion es 5, terminamos
}
}

(Como quiz hayas notado, este fuente, que es un poco ms largo que los
anteriores, abre las llaves al final de cada lnea -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 pgina; recuerda que puedes usar estilo Java si lo prefieres, pero en general el
fuente ser ms 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 ms
informacin que slo 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 rpido que no nos d tiempo a leerlos, as
que sera deseable que parase cuando se llenase la pantalla de informacin (por
ejemplo, una pausa tras mostrar cada 25 datos). Por descontado, se nos pueden
ocurrir muchas ms preguntas que hacerle sobre nuestros datos. Y adems,
cuando salgamos del programa se borrarn todos los datos que habamos
tecleado, pero eso es lo nico "casi inevitable", porque an 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 lnea,
separados por comas.

Revisin 0.99zz Pgina 143


Introduccin a la programacin con C#, por Nacho Cabanes

(4.6.2) Un programa que pida datos de 8 personas: nombre, dia de nacimiento,


mes de nacimiento, y ao de nacimiento (que se deben almacenar en un array de
structs). Despus deber repetir lo siguiente: preguntar un nmero de mes y
mostrar en pantalla los datos de las personas que cumplan los aos durante ese
mes. Terminar de repetirse cuando se teclee 0 como nmero de mes.
(4.6.3) Un programa que sea capaz de almacenar los datos de 50 personas:
nombre, direccin, telfono, edad (usando una tabla de structs). Deber ir
pidiendo los datos uno por uno, hasta que un nombre se introduzca vaco (se
pulse Intro sin teclear nada). Entonces deber aparecer un men que permita:
Mostrar la lista de todos los nombres.
Mostrar las personas de una cierta edad.
Mostrar las personas cuya inicial sea la que el usuario indique.
Salir del programa
(lgicamente, este men debe repetirse hasta que se escoja la opcin de "salir").
(4.6.4) Mejorar la base de datos de ficheros (ejemplo 04_06a) para que no permita
introducir tamaos incorrectos (nmeros negativos) ni nombres de fichero vacos.
(4.6.5) Ampliar la base de datos de ficheros (ejemplo 04_06a) para que incluya una
opcin de bsqueda parcial, en la que el usuario indique parte del nombre y se
muestre todos los ficheros que contienen ese fragmento (usando "Contains" o
"IndexOf"). Esta bsqueda no debe distinguir maysculas y minsculas (con la
ayuda de ToUpper o ToLower).
(4.6.6) Ampliar el ejercicio anterior (4.6.5) para que la bsqueda sea incremental: el
usuario ir indicando letra a letra el texto que quiere buscar, y se mostrar todos
los datos que lo contienen (por ejemplo, primero los que contienen "j", luego "ju",
despus "jua" y finalmente "juan").
(4.6.7) Ampliar la base de datos de ficheros (ejemplo 04_06a) para que se pueda
borrar un cierto dato (habr que "mover hacia atrs" todos los datos que haba
despus de ese, y disminuir el contador de la cantidad de datos que tenemos).
(4.6.8) Mejorar la base de datos de ficheros (ejemplo 04_06a) para que se pueda
modificar un cierto dato a partir de su nmero (por ejemplo, el dato nmero 3). En
esa modificacin, se deber permitir al usuario pulsar Intro sin teclear nada, para
indicar que no desea modificar un cierto dato, en vez de reemplazarlo por una
cadena vaca.
(4.6.9) Ampliar la base de datos de ficheros (ejemplo 04_06a) para que se permita
ordenar los datos por nombre. Para ello, debers buscar informacin sobre algn
mtodo de ordenacin sencillo, como el "mtodo de burbuja" (en el siguiente
apartado tienes algunos), y aplicarlo a este caso concreto.

Revisin 0.99zz Pgina 144


Introduccin a la programacin con C#, por Nacho Cabanes

4.7. Ordenaciones simples


Es muy frecuente querer ordenar datos que tenemos en un array. Para
conseguirlo, existen varios algoritmos sencillos, que no son especialmente
eficientes, pero son fciles de programar. La falta de eficiencia se refiere a que la
mayora de ellos se basan en dos bucles "for" anidados, de modo que en cada
pasada quede ordenado un nico dato, y habr que dar dan tantas pasadas como
datos existen. Por tanto, para un array con 1.000 datos, podran llegar ser
necesarias un milln de comparaciones (1.000 x 1.000).

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
mtodos claramente ms efectivos, pero ms difciles de programar, alguno de los
cuales comentaremos ms adelante.

Veremos tres de estos mtodos simples de ordenacin, primero mirando la


apariencia que tiene el algoritmo, y luego juntando los tres mtodos en un
ejemplo que los pruebe:

Mtodo de burbuja
(Intercambiar cada pareja consecutiva que no est ordenada)

Para i=1 hasta n-1


Para j=i+1 hasta n
Si A[i] > A[j]
Intercambiar ( A[i], A[j])

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

Para i=n descendiendo hasta 2


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

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

Revisin 0.99zz Pgina 145


Introduccin a la programacin con C#, por Nacho Cabanes

Nota: el smbolo "<>" se suele usar en pseudocdigo para indicar que un dato es
distinto de otro, de modo que equivale al "!=" de C#. La penltima lnea en C#
saldra a ser algo como "if (menor !=i)"

Insercin directa
(Comparar cada elemento con los anteriores -que ya estn ordenados- y
desplazarlo hasta su posicin correcta).

Para i=2 hasta n


j=i-1
mientras (j>=1) y (A[j] > A[j+1])
Intercambiar ( A[j], A[j+1])
j = j - 1

(Es mejorable, no intercambiando el dato que se mueve con cada elemento, sino
slo al final de cada pasada, pero no entraremos en ms detalles).

Un programa de prueba podra ser:

// Ejemplo_04_07a.cs
// Ordenaciones simples
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_07a


{

public static void Main()


{

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


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

// BURBUJA
// (Intercambiar cada pareja consecutiva que no est ordenada)
// Para i=1 hasta n-1
// Para j=i+1 hasta n
// Si A[i] > A[j]
// Intercambiar ( A[i], A[j])
Console.WriteLine("Ordenando mediante burbuja... ");
for(i=0; i < n-1; i++)
{
foreach (int dato in datos) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();

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


{

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


Revisin 0.99zz Pgina 146
Introduccin a la programacin con C#, por Nacho Cabanes

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

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


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

// SELECCIN 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 seleccin directa... ");
int[] datos2 = {5, 3, 14, 20, 8, 9, 1};
for(i=0; i < n-1; i++)
{
foreach (int dato in datos2) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();

int menor = i;
for(j=i+1; j < n; j++)
if (datos2[j] < datos2[menor])
menor = j;

if (i != menor)
{
datoTemporal = datos2[i];
datos2[i] = datos2[menor];
datos2[menor] = datoTemporal;
}
}
Console.Write("Ordenado:");

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


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

// INSERCION DIRECTA:
// (Comparar cada elemento con los anteriores -que ya estn
ordenados-
// y desplazarlo hasta su posicin 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 insercin directa... ");
Revisin 0.99zz Pgina 147
Introduccin a la programacin con C#, por Nacho Cabanes

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


for(i=1; i < n; i++)
{
foreach (int dato in datos3) // Muestro datos
Console.Write("{0} ",dato);
Console.WriteLine();

j = i-1;
while ((j>=0) && (datos3[j] > datos3[j+1]))
{
datoTemporal = datos3[j];
datos3[j] = datos3[j+1];
datos3[j+1] = datoTemporal;
j--;
}

}
Console.Write("Ordenado:");

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


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

}
}

Y su resultado sera:

Ordenando mediante burbuja...


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

Ordenando mediante seleccin directa...


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

Ordenando mediante insercin directa...


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

Revisin 0.99zz Pgina 148


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(4.7.1) Un programa que pida al usuario 6 nmeros en coma flotante y los muestre
ordenados de menor a mayor. Escoge el mtodo de ordenacin que prefieras.
(4.7.2) Un programa que pida al usuario 5 nombres y los muestre ordenados
alfabticamente (recuerda que para comparar cadenas no podrs usar el smbolo
">", sino "CompareTo").
(4.7.3) Un programa que pida al usuario varios nmeros, los vaya aadiendo a un
array, mantenga el array ordenado continuamente y muestre el resultado tras
aadir cada nuevo dato (todos los datos se mostrarn en la misma lnea,
separados por espacios en blanco). Terminar cuando el usuario teclee "fin".
(4.7.4) Amplia el ejercicio anterior, para aadir una segunda fase en la que el
usuario pueda "preguntar" si un cierto valor est en el array. Como el array est
ordenado, la bsqueda 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 estn ordenados, podemos buscar uno concreto dentro de
ellos empleando la bsqueda binaria: se comienza por el punto central; si el valor
buscado es mayor que el del punto central, se busca esta vez slo 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 tamao que el anterior. Esto
puede suponer una enorme ganancia en velocidad: si tenemos 1.000 datos, una
bsqueda lineal har 500 comparaciones como media, mientras que una
bsqueda lineal har 10 comparaciones o menos. Se podra implementar as:

// Ejemplo_04_07b.cs
// Bsqueda binaria
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_07b


{

public static void Main()


{
const int n = 1000;
int[] datos = new int[n];
int i,j,datoTemporal;

// Primero generamos datos al azar


Console.Write("Generando... ");
Random r = new Random();
for(i=0; i < n; i++)
datos[i] = r.Next(1, n*2);
Revisin 0.99zz Pgina 149
Introduccin a la programacin con C#, por Nacho Cabanes

// Luego los ordenamos mediante burbuja


Console.Write("Ordenando... ");
for(i=0; i < n-1; i++)
{
for(j=i+1; j < n; j++)
{
if (datos[i] > datos[j])
{
datoTemporal = datos[i];
datos[i] = datos[j];
datos[j] = datoTemporal;
}
}
}

// Mostramos los datos


foreach (int dato in datos)
Console.Write("{0} ",dato);
Console.WriteLine();

// 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 dnde 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 bsqueda
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;
}

}
Revisin 0.99zz Pgina 150
Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(4.7.5) Realiza una variante del ejercicio 4.7.4, que en vez de hacer una bsqueda
lineal (desde el principio), use "bsqueda 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.

Y no se puede ordenar de forma ms sencilla? S, existe un "Array.Sort" que


hace todo por nosotros... pero recuerda que no (slo) se trata de que conozcas la
forma ms corta posible de hacer un ejercicio, sino de que aprendas a resolver
problemas...

// Ejemplo_04_07c.cs
// Array.Sort
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_04_07c


{
public static void Main()
{
int[] datos = {5, 3, 14, 20, 8, 9, 1};
Array.Sort(datos); // Ordeno

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


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

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 nmeros, los vaya aadiendo a
un array, mantenga el array ordenado continuamente y muestre el resultado tras
aadir cada nuevo dato (todos los datos se mostrarn en la misma lnea,
separados por espacios en blanco). Terminar cuando el usuario teclee "fin".

4.8. Otros editores ms avanzados: Notepad++ y Geany

Revisin 0.99zz Pgina 151


Introduccin a la programacin con C#, por Nacho Cabanes

Ahora que conocemos los fundamentos, puede ser el momento de pasar a un


editor de texto un poco ms avanzado. Por ejemplo, en Windows podemos usar
Notepad++, que es gratuito,
destaca la sintaxis en colores,
muestra el nmero de lnea y
columna en la que nos
encontramos (lo que es til
para corregir errores), ayuda
a encontrar las llaves
emparejadas, realza la lnea
en la que nos encontramos,
permite tabular y des-tabular
bloques completos de
cdigo, tiene soporte para mltiples ventanas, etc.

El problema de Notepad++ es que sigue siendo necesario teclear la orden de


compilacin, ya sea volviendo a la pantalla de consola o desde el men Ejecutar (o
Run, si tenemos una versin en ingls).

Por eso, un segundo editor que puede resultar an ms 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:

Revisin 0.99zz Pgina 152


Introduccin a la programacin con C#, por Nacho Cabanes

No es slo que la barra de herramientas tenga botones para Compilar y para


Ejecutar. Es que adems, en caso de error de compilacin, se nos mostrar la lista
de errores y se destacar (con un subrayado rojo) las lneas causantes de esos
errores:

Si, por el contrario, todo ha sido correcto, la ventana que nos muestra la ejecucin
de nuestro programa se quedar pausada para que podamos comprobar los
resultados:

La instalacin de Geany en Linux es trivial: basta descargarlo usando nuestro


gestor de paquetes (Synaptic o el que sea) y todo suele funcionar a la primera (si
habamos instalado Mono previamente). En Windows es fcil descargarlo desde su
pgina oficial (geany.org) pero no podremos compilar desde l directamente,
Revisin 0.99zz Pgina 153
Introduccin a la programacin con C#, por Nacho Cabanes

porque lo habitual es que no sepa en qu ruta se encuentra nuestro compilador.


Lo podemos solucionar abriendo un fuente en lenguaje C# y entrando al men
"Construir", en la opcin "Establecer comandos de construccin":

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.

Si queremos utilizar Mono: en la primera casilla, pondremos la ruta completa de


nuestro compilador Mono (mcs.exe) seguida por "%f" para indicar que hay que
detallar a Mono el nombre de nuestro fichero actual. En la casilla "Ejecutar",
escribiremos la ruta de "mono.exe" seguida por "%e.exe", para que a Mono se le
indique el nombre del ejecutable que habremos obtenido al compilar.

Revisin 0.99zz Pgina 154


Introduccin a la programacin con C#, por Nacho Cabanes

De forma alternativa, podemos usar el propio compilador de ".Net" que viene


incluido con Windows, y que se encontrar en el disco de sistema (habitualmente
C:), en la carpeta de Windows, subcarpeta "Microsoft.NET", dentro de "Framework"
o "Framework64" (segn si queremos usar la versin de 32 bits o de 64 bits), en
una carpeta que indica el nmero de versin de la plataforma .Net (por ejemplo,
"v2.0.50727" para la versin 2; puede haber varias versiones instaladas). El
compilador se llama "csc.exe", y, al igual que para Mono, la lnea debe terminar
con "%f" para decir a Geany que cuando lance el compilador debe indicarle el
nombre del fichero que estamos editando:

C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc "%f"

Esto habra sido muy trabajoso para compilar desde lnea de comando nuestros
primeros programas, pero una vez que usamos editores ms avanzados, slo hay
que configurarlo una vez en Geany y se utilizar automticamente 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 recin creado:

"%e.exe"

Nota: Las comillas dobles que rodean a "%e.exe" (y a "%f") permiten que se lance
correctamente incluso si hubiramos escrito algn 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 harn tu rutina de programador mucho ms cmoda 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
tamao, en los que intervendrn varios ficheros fuente de forma simultnea. Es el
caso de Visual Studio, Sharp Develop y MonoDevelop (o su versin modernizada y
ampliada, Xamarin Studio), que veremos un poco ms adelante, cuando nuestros
programas lleguen a ese nivel de complejidad.

Revisin 0.99zz Pgina 155


Introduccin a la programacin con C#, por Nacho Cabanes

5. Introduccin a las funciones


5.1. Diseo modular de programas: Descomposicin
modular
Hasta ahora hemos estado pensando los pasos que deberamos dar para resolver
un cierto problema, y hemos creado programas a partir de cada uno de esos
pasos. Esto es razonable cuando los problemas son sencillos, pero puede no ser la
mejor forma de actuar cuando se trata de algo ms complicado.

A partir de ahora vamos a empezar a intentar descomponer los problemas en


trozos ms pequeos, que sean ms fciles de resolver. Esto nos puede suponer
varias ventajas:

Cada "trozo de programa" independiente ser ms fcil de programar, al


realizar una funcin breve y concreta.
El "programa principal" ser ms fcil de leer, porque no necesitar
contener todos los detalles de cmo se hace cada cosa.
Evitaremos mucho cdigo repetitivo.
Podremos repartir el trabajo, para que cada persona se encargue de
realizar un "trozo de programa", y finalmente se integrar el trabajo
individual de cada persona (aunque para proyectos realmente grandes,
pronto veremos una alternativa que hace que el reparto y la integracin
sean ms sencillos).

Esos "trozos" de programa son lo que suele llamar "subrutinas", "procedimientos"


o "funciones". En el lenguaje C y sus derivados (entre los que est C#), el nombre
que ms se usa es el de funciones.

5.2. Conceptos bsicos sobre funciones


En C#, al igual que en C y los dems lenguajes derivados de l, todos los "trozos de
programa" son funciones, incluyendo el propio cuerpo de programa, Main. De
hecho, la forma bsica de definir una funcin ser indicando su nombre seguido
de unos parntesis vacos, como hacamos con "Main", y precedindolo por ciertas
palabras reservadas, como "public static void", cuyo significado iremos viendo
muy pronto. Despus, entre llaves indicaremos todos los pasos que queremos que
d ese "trozo de programa".

Revisin 0.99zz Pgina 156


Introduccin a la programacin con C#, por Nacho Cabanes

Por ejemplo, podramos crear una funcin llamada "Saludar", que escribiera varios
mensajes en la pantalla:

public static void Saludar()


{
Console.Write("Bienvenido al programa ");
Console.WriteLine("de ejemplo");
Console.WriteLine("Espero que ests bien");
}

Ahora desde dentro del cuerpo de nuestro programa, podramos "llamar" a esa
funcin:

public static void Main()


{
Saludar();

}

As conseguimos que nuestro programa principal sea ms fcil de leer.

Un detalle importante: tanto la funcin habitual "Main" como la nueva funcin


"Saludar" seran parte de nuestra "class", es decir, el fuente completo sera as:

// Ejemplo_05_02a.cs
// Funcion "Saludar"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_02a


{

public static void Saludar()


{
Console.Write("Bienvenido al programa ");
Console.WriteLine("de ejemplo");
Console.WriteLine("Espero que ests bien");
}

public static void Main()


{
Saludar();
Console.WriteLine("Nada ms por hoy...");
}

Como ejemplo ms detallado, la parte principal de una agenda o de una base de


datos simple como las que hicimos en el tema anterior, podra ser simplemente:

LeerDatosDeFichero();
Revisin 0.99zz Pgina 157
Introduccin a la programacin 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 funcin llamada "BorrarPantalla", que borre la pantalla dibujando
25 lneas en blanco. Crea tambin un "Main" que permita probarla.
(5.2.2) Crea una funcin llamada "DibujarCuadrado3x3", que dibuje un cuadrado
formato por 3 filas con 3 asteriscos cada una. Incluye un "Main" para probarla.
(5.2.3) Descompn en funciones la base de datos de ficheros (ejemplo 04_06a), de
modo que el "Main" sea breve y ms legible (Pista: las variables que se compartan
entre varias funciones debern estar fuera de todas ellas, y debern estar
precedidas por la palabra "static").

5.3. Parmetros de una funcin


Es muy frecuente que nos interese indicarle a nuestra funcin ciertos datos con los
que queremos que trabaje. Los llamaremos "parmetros" y los indicaremos dentro
del parntesis que sigue al nombre de la funcin, separados por comas. Para cada
uno de ellos, deberemos indicar su tipo de datos (por ejemplo "int") y luego su
nombre.

Por ejemplo, si escribimos en pantalla nmeros reales con frecuencia, nos puede
resultar til crear una funcin auxiliar que nos los muestre con el formato que nos
interese (que podra ser con exactamente 3 decimales). Lo podramos hacer as:

public static void EscribirNumeroReal( float n )


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

Y esta funcin se podra usar desde el cuerpo de nuestro programa as:

EscribirNumeroReal(2.3f);

(recordemos que el sufijo "f" sirve para indicar al compilador que trate ese nmero
como un "float", porque de lo contrario, al ver que tiene cifras decimales, lo
tomara como "double", que permite mayor precisin... pero a cambio nosotros

Revisin 0.99zz Pgina 158


Introduccin a la programacin con C#, por Nacho Cabanes

tendramos un mensaje de error en nuestro programa, diciendo que estamos


pasando un dato "double" a una funcin que espera un "float").

El programa completo podra quedar as:

// Ejemplo_05_03a.cs
// Funcion "EscribirNumeroReal"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_03a


{

public static void EscribirNumeroReal( float n )


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

public static void Main()


{
float x;

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

Como ya hemos anticipado, si hay ms de un parmetro, deberemos indicar el


tipo y el nombre para cada uno de ellos (incluso si todos son del mismo tipo), y
separarlos entre comas:

public static void EscribirSuma( int a, int b )


{
...
}

De modo que un programa completo de ejemplo para una funcin con dos
parmetros podra ser:

// Ejemplo_05_03b.cs
// Funcion "EscribirSuma"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_03b


{

public static void EscribirSuma( int a, int b )


Revisin 0.99zz Pgina 159
Introduccin a la programacin con C#, por Nacho Cabanes

{
Console.Write( a+b );
}

public static void Main()


{
Console.Write("La suma de 4 y 7 es: ");
EscribirSuma(4, 7);
}

Como se ve en estos ejemplos, se suele seguir un par de convenios:

Ya que las funciones expresan acciones, en general su nombre ser un


verbo.

En C# se recomienda que los elementos pblicos se escriban comenzando


por una letra mayscula (y recordemos que, hasta que conozcamos las
alternativas y el motivo para usarlas, nuestras funciones comienzan con la
palabra "public"). Este criterio depende del lenguaje. Por ejemplo, en
lenguaje Java es habitual seguir el convenio de que los nombres de las
funciones deban comenzar con una letra minscula.

Ejercicios propuestos:
(5.3.1) Crea una funcin "DibujarCuadrado" que dibuje en pantalla un cuadrado
del ancho (y alto) que se indique como parmetro. Completa el programa con un
Main que permita probarla.
(5.3.2) Crea una funcin "DibujarRectangulo" que dibuje en pantalla un rectngulo
del ancho y alto que se indiquen como parmetros. Incluye un Main para probarla.
(5.3.3) Crea una funcin "DibujarRectanguloHueco" que dibuje en pantalla un
rectngulo hueco del ancho y alto que se indiquen como parmetros, formado por
una letra que tambin se indique como parmetro. Completa el programa con un
Main que pida esos datos al usuario y dibuje el rectngulo.

5.4. Valor devuelto por una funcin. El valor "void"


Cuando queremos dejar claro que una funcin no tiene que devolver ningn valor,
podemos hacerlo indicando al principio que el tipo de datos va a ser "void" (nulo),
como hacamos hasta ahora con "Main" y como hicimos con nuestra funcin
"Saludar".

Pero eso no es lo que ocurre con las funciones matemticas que estamos
acostumbrados a manejar: s devuelven un valor, que es el resultado de una

Revisin 0.99zz Pgina 160


Introduccin a la programacin con C#, por Nacho Cabanes

operacin (por ejemplo, la raz cuadrada de un nmero tiene como resultado otro
nmero).

De igual modo, para nosotros tambin ser habitual crear funciones que realicen
una serie de clculos y nos "devuelvan" (return, en ingls) el resultado de esos
clculos, para poderlo usar desde cualquier otra parte de nuestro programa. Por
ejemplo, podramos crear una funcin para elevar un nmero entero al cuadrado
as:

public static int Cuadrado ( int n )


{
return n*n;
}

En este caso, nuestra funcin no es "void", sino "int", porque va a devolver un


nmero entero. Eso s, todas nuestras funciones seguirn siendo "public static"
hasta que avancemos un poco ms.

Podramos usar el resultado de esa funcin como si se tratara de un nmero o de


una variable, as:

resultado = Cuadrado( 5 );

En general, en las operaciones matemticas, no ser necesario que el nombre de


la funcin sea un verbo. El programa debera ser suficientemente legible si el
nombre expresa qu operacin se va a realizar en la funcin.

Un programa ms detallado de ejemplo podra ser:

// Ejemplo_05_04a.cs
// Funcion "Cuadrado"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_04a


{

public static int Cuadrado ( int n )


{
return n*n;
}

public static void Main()


{
int numero;
int resultado;

Revisin 0.99zz Pgina 161


Introduccin a la programacin con C#, por Nacho Cabanes

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

Podremos devolver cualquier tipo de datos, no slo nmeros enteros. Como


segundo ejemplo, podemos hacer una funcin que nos diga cul es el mayor de
dos nmeros reales as:

public static float Mayor ( float n1, float n2 )


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

Como se ve en este ejemplo, una funcin puede tener ms de un "return".

En cuanto se alcance un "return", se sale de la funcin por completo. Eso puede


hacer que una funcin mal diseada haga que el compilador nos d un aviso de
"cdigo inalcanzable", como en el siguiente ejemplo:

public static string Inalcanzable()


{
return "Aqu s llegamos";

string ejemplo = "Aqu no llegamos";


return ejemplo;
}

Ejercicios propuestos:
(5.4.1) Crea una funcin "Cubo" que calcule el cubo de un nmero real (float) que
se indique como parmetro. El resultado deber ser otro nmero real. Prueba esta
funcin para calcular el cubo de 3.2 y el de 5.
(5.4.2) Crea una funcin "Menor" que calcule el menor de dos nmeros enteros
que recibir como parmetros. El resultado ser otro nmero entero.
(5.4.3) Crea una funcin llamada "Signo", que reciba un nmero real, y devuelva un
nmero entero con el valor: -1 si el nmero es negativo, 1 si es positivo o 0 si es
cero.
(5.4.4) Crea una funcin "Inicial", que devuelva la primera letra de una cadena de
texto. Prueba esta funcin para calcular la primera letra de la frase "Hola".

Revisin 0.99zz Pgina 162


Introduccin a la programacin con C#, por Nacho Cabanes

(5.4.5) Crea una funcin "UltimaLetra", que devuelva la ltima letra de una cadena
de texto. Prueba esta funcin para calcular la ltima letra de la frase "Hola".
(5.4.6) Crea una funcin "MostrarPerimSuperfCuadrado" que reciba un nmero
entero y calcule y muestre en pantalla el valor del permetro y de la superficie de
un cuadrado que tenga como lado el nmero que se ha indicado como parmetro.

5.5. Variables locales y variables globales


Hasta ahora, hemos declarado las variables dentro de "Main". Ahora nuestros
programas tienen varios "bloques", as que las variables se comportarn de forma
distinta segn donde las declaremos.

Las variables se pueden declarar dentro de un bloque (una funcin), y entonces


slo ese bloque las conocer, no se podrn usar desde ningn otro bloque del
programa. Es lo que llamaremos "variables locales".

Por el contrario, si declaramos una variable al comienzo del programa, fuera de


todos los "bloques" de programa, ser una "variable global", a la que se podr
acceder desde cualquier parte. Por ahora, para nosotros, una variable global
deber llevar siempre la palabra "static" (dentro de poco veremos el motivo real y
cundo no ser necesario).

En general, deberemos intentar que la mayor cantidad de variables posible sean


locales (lo ideal sera que todas lo fueran). As hacemos que cada parte del
programa trabaje con sus propios datos, y ayudamos a evitar que un error en un
trozo de programa pueda afectar al resto. La forma correcta de pasar datos entre
distintos trozos de programa no es a travs de variables globales, sino usando los
parmetros de cada funcin y los valores devueltos, como en el anterior ejemplo.
Aun as, esta restriccin es menos grave en lenguajes modernos, como C#, que en
otros lenguajes ms antiguos, como C, porque, como veremos en el prximo tema,
el hecho de descomponer un programa en varias clases minimiza los efectos
negativos de esas variables que se comparten entre varias funciones, adems de
que muchas veces tendremos datos compartidos, que no sern realmente
"variables globales" sino datos especficos del problema, que llamaremos
"atributos"

Vamos a ver el uso de variables locales con un ejemplo. Crearemos una funcin
que calcule la potencia de un nmero entero (un nmero elevado a otro), y el

Revisin 0.99zz Pgina 163


Introduccin a la programacin con C#, por Nacho Cabanes

cuerpo del programa que la use. La forma de conseguir elevar un nmero a otro
ser a base de multiplicaciones, es decir:

3 elevado a 5 = 3 3 3 3 3

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


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

// Ejemplo_05_05a.cs
// Ejemplo de funcin con variables locales
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_05a


{

public static int Potencia(int nBase, int nExponente)


{
int temporal = 1; // Valor inicial que voy incrementando

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


temporal *= nBase; // Para aumentar el valor temporal

return temporal; // Al final, obtengo el valor que buscaba


}

public static void Main()


{
int num1, num2;

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


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

Console.WriteLine("Introduzca el exponente: ");


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

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


num1, num2, Potencia(num1,num2));
}

En este caso, las variables "temporal" e "i" son locales a la funcin "potencia": para
"Main" no existen. Si en "Main" intentramos hacer i=5; obtendramos un
mensaje de error. De igual modo, "num1" y "num2" son locales para "Main": desde
la funcin "potencia" no podemos acceder a su valor (ni para leerlo ni para
modificarlo), slo desde "Main". Este ejemplo no contiene ninguna variable global.

Revisin 0.99zz Pgina 164


Introduccin a la programacin con C#, por Nacho Cabanes

(Nota: el parmetro no se llama "base" sino "nBase" porque la palabra "base" es


una palabra reservada en C#, que no podremos usar como nombre de variable).

Ejercicios propuestos:
(5.5.1) Crea una funcin "PedirEntero", que reciba como parmetros el texto que
se debe mostrar en pantalla, el valor mnimo aceptable y el valor mximo
aceptable. Deber pedir al usuario que introduzca el valor tantas veces como sea
necesario, volvrselo a pedir en caso de error, y devolver un valor correcto.
Prubalo con un programa que pida al usuario un ao entre 1800 y 2100.
(5.5.2) Crea una funcin "EscribirTablaMultiplicar", que reciba como parmetro un
nmero entero, y escriba la tabla de multiplicar de ese nmero (por ejemplo, para
el 3 deber llegar desde "3x0=0" hasta "3x10=30").
(5.5.3) Crea una funcin "EsPrimo", que reciba un nmero y devuelva el valor
booleano "true" si es un nmero primo o "false" en caso contrario.
(5.5.4) Crea una funcin "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', debera devolver 2 (porque la "a" aparece
2 veces).
(5.5.5) Crea una funcin "SumaCifras" que reciba un numero cualquiera y que
devuelva como resultado la suma de sus dgitos. Por ejemplo, si el nmero fuera
123 la suma sera 6.
(5.5.6) Crea una funcin "Tringulo" que reciba una letra y un nmero, y escriba un
"tringulo" 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, debera escribir
****
***
**
*

5.6. Los conflictos de nombres en las variables


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

// Ejemplo_05_06a.cs
// Dos variables locales con el mismo nombre
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_06a


{

Revisin 0.99zz Pgina 165


Introduccin a la programacin con C#, por Nacho Cabanes

public static void CambiaN()


{
int n = 7;
n ++;
}

public static void Main()


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

El resultado de este programa es:

n vale 5
Ahora n vale 5

Por qu? Sencillo: tenemos una variable local dentro de "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.

Si la variable es "global", declarada fuera de estas funciones, s ser accesible por


todas ellas:

// Ejemplo_05_06b.cs
// Una variable global
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_06b


{

static int n = 7;

public static void CambiaN()


{
n ++;
}

public static void Main()


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

}
Revisin 0.99zz Pgina 166
Introduccin a la programacin con C#, por Nacho Cabanes

5.7. Modificando parmetros


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

// Ejemplo_05_07a.cs
// Modificar una variable recibida como parmetro - acercamiento
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_07a


{

public static void Duplicar(int x)


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

public static void Main()


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

El resultado de este programa ser:

n vale 5
El valor recibido vale 5
y ahora vale 10
Ahora n vale 5

Vemos que al salir de la funcin, no se conservan los cambios que hagamos a


esa variable que se ha recibido como parmetro.

Esto se debe a que, si no indicamos otra cosa, los parmetros "se pasan por
valor", es decir, la funcin no recibe los datos originales, sino una copia de ellos. Si
modificamos algo, estamos cambiando una copia de los datos originales, no
dichos datos.

Revisin 0.99zz Pgina 167


Introduccin a la programacin con C#, por Nacho Cabanes

Si queremos que las modificaciones se conserven, basta con hacer un pequeo


cambio: indicar que la variable se va a pasar "por referencia", lo que se indica
usando la palabra "ref", tanto en la declaracin de la funcin como en la llamada,
as:

// Ejemplo_05_07b.cs
// Modificar una variable recibida como parmetro - correcto
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_07b


{

public static void Duplicar(ref int x)


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

public static void Main()


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

En este caso s se modifica la variable n:

n vale 5
El valor recibido vale 5
y ahora vale 10
Ahora n vale 10

El hecho de poder modificar valores que se reciban como parmetros abre una
posibilidad que no se podra conseguir de otra forma: con "return" slo se puede
devolver un valor de una funcin, pero con parmetros pasados por referencia
podramos devolver ms de un dato. Por ejemplo, podramos crear una funcin
que intercambiara los valores de dos variables:

public static void Intercambia(ref int x, ref int y)

La posibilidad de pasar parmetros por valor y por referencia existe en la mayora


de lenguajes de programacin. En el caso de C# existe alguna posibilidad adicional
que no existe en otros lenguajes, como los "parmetros de salida". No podemos
llamar a una funcin que tenga parmetros por referencia si los parmetros no
Revisin 0.99zz Pgina 168
Introduccin a la programacin con C#, por Nacho Cabanes

tienen valor inicial. Por ejemplo, una funcin que devuelva la primera y segunda
letra de una frase sera as:

// Ejemplo_05_07c.cs
// Parmetros "out"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_07c


{

public static void DosPrimerasLetras(string cadena,


out char l1, out char l2)
{
l1 = cadena[0];
l2 = cadena[1];
}

public static void Main()


{
char letra1, letra2;
DosPrimerasLetras("Nacho", out letra1, out letra2);
Console.WriteLine("Las dos primeras letras son {0} y {1}",
letra1, letra2);
}

Si pruebas este ejemplo, vers 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 funcin "Intercambiar", que intercambie el valor de los dos
nmeros enteros que se le indiquen como parmetro. Crea tambin un programa
que la pruebe.
(5.7.2) Crea una funcin "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 parmetros por referencia. Crea un "Main" que te permita comprobar que
funciona correctamente.

5.8. El orden no importa


En algunos lenguajes, una funcin debe estar declarada antes de usarse. Esto no
es necesario en C#. Por ejemplo, podramos rescribir el ejemplo 05_07b, de modo
que "Main" aparezca en primer lugar y "Duplicar" aparezca despus, y seguira
compilando y funcionando igual:

Revisin 0.99zz Pgina 169


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_05_08a.cs
// Funcin tras Main
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_08a


{

public static void Main()


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

public static void Duplicar(ref int x)


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

5.9. Algunas funciones tiles


5.9.1. Nmeros aleatorios
En un programa de gestin o una utilidad que nos ayuda a administrar un sistema,
no es habitual que podamos permitir que las cosas ocurran al azar. Pero los juegos
se encuentran muchas veces entre los ejercicios de programacin ms completos,
y para un juego s suele ser conveniente que haya algo de azar, para que una
partida no sea exactamente igual a la anterior.

Generar nmeros al azar ("nmeros aleatorios") usando C# no es difcil: debemos


crear un objeto de tipo "Random" (una nica vez), y luego llamaremos a "Next"
cada vez que queramos obtener valores entre dos extremos:

// Creamos un objeto Random


Random generador = new Random();

// Generamos un nmero entre dos valores dados


// (el segundo lmite no est incluido)
int aleatorio = generador.Next(1, 101);

Tambin, una forma simple de obtener un nico nmero "casi al azar" entre 0 y
999 es tomar las milsimas de segundo de la hora actual:

Revisin 0.99zz Pgina 170


Introduccin a la programacin con C#, por Nacho Cabanes

int falsoAleatorio = DateTime.Now.Millisecond;

Pero esta forma simplificada no sirve si necesitamos obtener dos nmeros


aleatorios a la vez, porque los dos se obtendran en el mismo milisegundo y
tendran el mismo valor; en ese caso, no habra ms remedio que utilizar
"Random" y llamar dos veces a "Next".

Vamos a ver un ejemplo, que muestre en pantalla dos nmeros al azar:

// Ejemplo_05_09_01a.cs
// Nmeros al azar
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_09_01a


{
public static void Main()
{
Random r = new Random();
int aleatorio = r.Next(1, 11);
Console.WriteLine("Un nmero entre 1 y 10: {0}", aleatorio);
int aleatorio2 = r.Next(10, 21);
Console.WriteLine("Otro entre 10 y 20: {0}", aleatorio2);
}
}

Ejercicios propuestos:
(5.9.1.1) Crea un programa que imite el lanzamiento de un dado, generando un
nmero al azar entre 1 y 6.
(5.9.1.2) Crea un programa que genere un nmero 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 nmeros 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 tamaos 24 para el alto y 79 para el ancho), que
primero rellenes y luego dibujes en pantalla.

5.9.2. Funciones matemticas


En C# tenemos muchas funciones matemticas predefinidas, como:

Revisin 0.99zz Pgina 171


Introduccin a la programacin con C#, por Nacho Cabanes

Abs(x): Valor absoluto


Acos(x): Arco coseno
Asin(x): Arco seno
Atan(x): Arco tangente
Atan2(y,x): Arco tangente de y/x (por si x o y son 0)
Ceiling(x): El valor entero superior a x y ms cercano a l
Cos(x): Coseno
Cosh(x): Coseno hiperblico
Exp(x): Exponencial de x (e elevado a x)
Floor(x): El mayor valor entero que es menor que x
Log(x): Logaritmo natural (o neperiano, en base "e")
Log10(x): Logaritmo en base 10
Pow(x,y): x elevado a y
Round(x, cifras): Redondea un nmero
Sin(x): Seno
Sinh(x): Seno hiperblico
Sqrt(x): Raz cuadrada
Tan(x): Tangente
Tanh(x): Tangente hiperblica

(casi todos ellos usan parmetros X e Y de tipo "double"; en el caso de las


funciones trigonomtricas, el ngulo se debe indicar en radianes, no en grados)

y tambin tenemos una serie de constantes como

E, el nmero "e", con un valor de 2.71828...


PI, el nmero "Pi", 3.14159...

Todas ellas se usan precedidas por "Math."

La mayora de ellas son especficas para ciertos problemas matemticos,


especialmente si interviene la trigonometra o si hay que usar logaritmos o
exponenciales. Pero vamos a destacar las que s pueden resultar tiles en
situaciones ms variadas, que casi cualquier programador pueda necesitar:

La raz cuadrada de 4 se calculara haciendo x = Math.Sqrt(4);


La potencia: para elevar 2 al cubo haramos y = Math.Pow(2, 3);
El valor absoluto: para trabajar slo con nmeros positivos usaramos n =
Math.Abs(x);

Revisin 0.99zz Pgina 172


Introduccin a la programacin con C#, por Nacho Cabanes

Un ejemplo ms avanzado, usando funciones trigonomtricas, que calculase el


"coseno de 45 grados" podra ser:

// Ejemplo_05_09_02a.cs
// Ejemplo de funciones trigonometricas
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_09_02a


{
public static void Main()
{
double anguloGrados = 45;
double anguloRadianes = anguloGrados * Math.PI / 180.0;

Console.WriteLine("El coseno de 45 grados es: {0}",


Math.Cos(anguloRadianes));
}
}

Ejercicios propuestos:
(5.9.2.1) Crea un programa que halle (y muestre) la raz cuadrada del nmero que
introduzca el usuario. Se repetir hasta que introduzca 0.
(5.9.2.2) Crea un programa que halle cualquier raz (de cualquier orden) de un
nmero. El usuario deber indicar el nmero (por ejemplo, 2) y el ndice de la raz
(por ejemplo, 3 para la raz cbica). Pista: hallar la raz cbica 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 funcin "CalcularRaicesSegundoGrado", que recibir como parmetros 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 solucin no
existe, se devolver como valor 100.000 para esa solucin. Pista: la solucin se
calcula con
x = -b raz (b2 4ac) / (2a)
(5.9.2.4) Haz un programa que pida al usuario 5 datos numricos enteros, los
guarde en un array, pida un nuevo dato y muestre el valor del array que se
encuentra ms 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 numricos reales, los
guarde en un array, pida un nuevo dato y muestre el valor del array que se
encuentra ms cerca de ese dato en valor absoluto (es decir, el ms prximo, sea
mayor que l o menor que l).
(5.9.2.6) Crea una funcin "Distancia", que calcule la distancia entre dos puntos
(x1,y1) y (x2,y2), usando la expresin d = raz [ (x1-x2)2 + (y1-y2)2 ].
Revisin 0.99zz Pgina 173
Introduccin a la programacin 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 trigonomtricas 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 funcin y = 10 *
seno(x*5), para valores de x entre 0 y 72 grados.
(5.9.2.9) Crea un programa que "dibuje" la grfica de la funcin 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 crculo 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
seran (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.9.3. Pero hay muchas ms funciones


En C# hay muchas ms funciones de lo que parece. De hecho, salvo algunas
palabras reservadas (int, float, string, if, switch, for, do, while...), gran parte de lo
que hasta ahora hemos llamado "rdenes", son realmente "funciones", como
Console.ReadLine (que devuelve la cadena que se ha introducido por teclado) o
Console.WriteLine (que es "void", no devuelve nada). Nos iremos encontrando con
otras muchas funciones a medida que avancemos.

5.10. Recursividad
La recursividad consiste en resolver un problema a partir de casos ms simples del
mismo problema. Una funcin recursiva es aquella que se "llama a ella misma",
reduciendo la complejidad paso a paso hasta llegar a un caso trivial.

Dentro de las matemticas tenemos varios ejemplos de funciones recursivas. Uno


clsico es el "factorial de un nmero":

El factorial de 1 es 1:

1! = 1

Y el factorial de un nmero arbitrario es el producto de ese nmero por los que le


siguen, hasta llegar a uno:

Revisin 0.99zz Pgina 174


Introduccin a la programacin con C#, por Nacho Cabanes

n! = n (n-1) (n-2) ... 3 2 1

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

Si pensamos que el factorial de n-1 es

(n-1)! = (n-1) (n-2) (n-3) ... 3 2 1

Entonces podemos escribir el factorial de un nmero a partir del factorial del


siguiente nmero:

n! = n (n-1)!

Esta es la definicin recursiva del factorial, ni ms ni menos. Esto se podra


programar as:

// Ejemplo_05_10a.cs
// Funciones recursivas: factorial
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_10a


{

public static long Factorial(int n)


{
if (n==1) // Aseguramos que termine (caso base)
return 1;
return n * Factorial(n-1); // Si no es 1, sigue la recursin
}

public static void Main()


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

Dos consideraciones importantes:

Atencin a la primera parte de la funcin recursiva: es muy importante


comprobar que hay salida de la funcin, para que nuestro programa no se
quede dando vueltas todo el tiempo y deje el ordenador (o la tarea actual)
"colgado". Debemos encontrar un "caso trivial" que alcanzar, y un modo de
disminuir la complejidad del problema acercndolo a ese caso.

Revisin 0.99zz Pgina 175


Introduccin a la programacin con C#, por Nacho Cabanes

Los factoriales crecen rpidamente, as que no conviene poner nmeros


grandes: el factorial de 16 es 2.004.189.184, luego a partir de 17 podemos
obtener resultados errneos, si usamos nmeros enteros "normales".

Qu utilidad tiene esto? Ms de la que parece: muchos problemas complicados


se pueden expresar a partir de otro ms sencillo. En muchos de esos casos, ese
problema se podr expresar de forma recursiva. Los ejercicios propuestos te
ayudarn a descubrir otros ejemplos de situaciones en las que se puede aplicar la
recursividad.

Ejercicios propuestos:
(5.10.1) Crea una funcin que calcule el valor de elevar un nmero entero a otro
nmero entero (por ejemplo, 5 elevado a 3 = 5 3 = 5 5 5 = 125). Esta funcin se
debe crear de forma recursiva. Piensa cul ser el caso base (qu potencia se
puede calcular de forma trivial) y cmo pasar del caso "n-1" al caso "n" (por
ejemplo, si sabes el valor de 54, cmo hallaras el de 55 a partir de l).
(5.10.2) Como alternativa, crea una funcin que calcule el valor de elevar un
nmero entero a otro nmero 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 nmero 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 nmeros enteros, desde su posicin inicial a la final,
usando una funcin recursiva que tendr la apariencia: SumaVector(v, desde,
hasta). Nuevamente, piensa cul ser el caso base (cuntos elementos podras
sumar para que dicha suma sea trivial) y cmo pasar del caso "n-1" al caso "n" (por
ejemplo, si conoces la suma de los 6 primeros elementos y el valor del sptimo
elemento, cmo podras emplear esta informacin 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" devolvera "aloH"). La funcin
recursiva se llamar "Invertir(cadena)". Como siempre, analiza cul ser el caso
base (qu longitud debera tener una cadena para que sea trivial darle la vuelta) y
cmo pasar del caso "n-1" al caso "n" (por ejemplo, si ya has invertido las 5
primeras letras, que ocurrira con la de la sexta posicin).

Revisin 0.99zz Pgina 176


Introduccin a la programacin con C#, por Nacho Cabanes

(5.10.7) Crea, tanto de forma recursiva como de forma iterativa, una funcin diga
si una cadena de caracteres es simtrica (un palndromo). Por ejemplo,
"DABALEARROZALAZORRAELABAD" es un palndromo.
(5.10.8) Crear un programa que encuentre el mximo comn divisor de dos
nmeros usando el algoritmo de Euclides: Dados dos nmeros enteros positivos m
y n, tal que m > n, para encontrar su mximo comn 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 mximo comn 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 funcin debe ser iterativa y la otra debe ser recursiva.
(5.10.10) Crea una funcin que reciba una cadena de texto, y una subcadena, y
devuelva cuntas 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 sera 4, porque se podra 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 sera 1. Si recibes "ohla", la respuesta sera 0, porque tras la H no hay
ninguna O que permita completar la secuencia en orden.
(5.10.11) El algoritmo de ordenacin 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 funcin
recursiva es cuando se llega a un array de tamao 0 1. Implementa una funcin
que ordene un array usando este mtodo.

5.11. Parmetros y valor de retorno de "Main"


Es muy frecuente que un programa llamado desde la "lnea de comandos" tenga
ciertas opciones que le indicamos como argumentos. Por ejemplo, bajo Linux o
cualquier otro sistema operativo de la familia Unix, podemos ver la lista detallada
de ficheros que terminan en .cs haciendo

ls l *.cs

En este caso, la orden sera "ls", y las dos opciones (argumentos o parmetros) que
le indicamos son "-l" y "*.cs".

Revisin 0.99zz Pgina 177


Introduccin a la programacin con C#, por Nacho Cabanes

La orden equivalente en MsDos y en el intrprete de comandos de Windows sera

dir *.cs

Ahora la orden sera "dir", y el parmetro es "*.cs".

Pues bien, estas opciones que se le pasan al programa se pueden leer desde C#.
Se hace indicando un parmetro especial en Main, un array de strings:

static void Main(string[] args)

Para conocer esos parmetros lo haramos de la misma forma que se recorre


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

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


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

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


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

Environment.Exit(1);

Es decir, entre parntesis indicamos un cierto cdigo, que suele ser (por convenio)
un 0 si no ha habido ningn error, u otro cdigo distinto en caso de que s exista
algn error.

Este valor se podra comprobar desde el sistema operativo. Por ejemplo, en MsDos
y Windows se puede leer desde un fichero BAT o CMD usando "IF ERRORLEVEL",
as:

IF ERRORLEVEL 1 ECHO Ha habido un error en el programa

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


declarndolo como "void", sino como "int", y empleando entonces la orden
"return" cuando nos interese (igual que antes, por convenio, devolviendo 0 si todo
ha funcionado correctamente u otro cdigo en caso contrario):

public static int Main(string[] args)


Revisin 0.99zz Pgina 178
Introduccin a la programacin con C#, por Nacho Cabanes

{
...
return 0;
}

Un ejemplo que pusiera todo esto a prueba podra ser:

// Ejemplo_05_11a.cs
// Parmetros y valor de retorno de "Main"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_05_11a


{
public static int Main(string[] args)
{
Console.WriteLine("Parmetros: {0}", args.Length);

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


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

if (args.Length == 0)
{
Console.WriteLine("No ha indicado ningn parmetro!");
Environment.Exit(1);
}

return 0;
}
}

Ejercicios propuestos:
(5.11.1) Crea un programa llamado "suma", que calcule (y muestre) la suma de dos
nmeros que se le indiquen como parmetros en lnea 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 cdigo de error 1.
(5.11.2) Crea una calculadora bsica, llamada "calcula", que deber sumar, restar,
multiplicar o dividir los dos nmeros que se le indiquen como parmetros.
Ejemplos de su uso sera "calcula 2 + 3" o "calcula 5 * 60".
(5.11.3) Crea una variante del ejercicio 5.11.2, en la que Main devuelva el cdigo 1
si la operacin indicada no es vlida o 0 cuando s sea una operacin aceptable.
(5.11.4) Crea una variante del ejercicio 5.11.3, en la que Main devuelva tambin el
cdigo 2 si alguno de los dos nmeros con los que se quiere operar no tiene un
valor numrico vlido.

Revisin 0.99zz Pgina 179


Introduccin a la programacin con C#, por Nacho Cabanes

6. Programacin orientad a a objetos


6.1. Por qu los objetos?
Cuando tenemos que realizar un proyecto grande, ser necesario descomponerlo
en varios subprogramas, de forma que podamos repartir el trabajo entre varias
personas.

Esta descomposicin 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.

Existen varias formas de descomponer un proyecto, pero posiblemente la ms


recomendable consiste en tratar de verlo como una serie de "objetos" que
colaboran entre s.

Una forma de "descubrir" los objetos que forman parte de un programa es partir
de la descripcin del problema, y subrayar los nombres con un color y los verbos
con otro color.

Como ejemplo, vamos a dedicar un momento a pensar qu elementos ("objetos")


hay en un juego como el clsico Space Invaders. Cuando entramos al juego,
aparece una pantalla de bienvenida, que nos recuerda detalles como la cantidad
de puntos que obtendremos al "destruir" cada "enemigo":

Revisin 0.99zz Pgina 180


Introduccin a la programacin con C#, por Nacho Cabanes

Y cuando comenzamos una partida, veremos una pantalla como sta, que vamos a
analizar con ms detalle:

Revisin 0.99zz Pgina 181


Introduccin a la programacin con C#, por Nacho Cabanes

Observando la pantalla anterior, o (preferiblemente) tras jugar algunas partidas,


podramos hacer una primera descripcin del juego en lenguaje natural (que
posiblemente habra que refinar ms adelante, pero que nos servir como punto
de partida):

Nosotros manejamos una "nave", que se puede mover a izquierda y derecha y


que puede disparar. Nuestra nave se esconde detrs de "torres defensivas", que
se destruyen poco a poco cuando les impactan los disparos. Nos atacan (nos
disparan) "enemigos". Adems, estos enemigos se mueven de lado a lado, pero
no de forma independiente, sino como un "bloque". En concreto, hay tres "tipos"
de enemigos, que no se diferencian en su comportamiento, pero s en su imagen.
Tanto nuestro disparo como los de los enemigos desaparecen cuando salen de la
pantalla o cuando impactan con algo. Si un disparo del enemigo impacta con
nosotros, perderemos una vida; si un disparo nuestro impacta con un enemigo,
lo destruye. Adems, en ocasiones aparece un "OVNI" en la parte superior de la
pantalla, en la parte superior de la pantalla, que se mueve del lado izquierdo al
lado derecho y nos permite obtener puntuacin extra si le impactamos con un
disparo. Igualmente, hay un "marcador", que muestra la puntuacin actual (que
se ir incrementando) y el rcord (mejor puntuacin hasta el momento). El
marcador se reinicia al comienzo de cada partida. Antes de cada "partida",
pasamos por una pantalla de "bienvenida", que muestra una animacin que nos
informa de cuntos puntos obtenemos al destruir cada tipo de enemigo.

A partir de esa descripcin, podemos buscar los nombres (puede ayudar si los
subrayamos con un rotulador de marcar), que indicarn los objetos en los que
podemos descomponer el problema, y los verbos (con otro rotulador de marcar,
en otro color), que indicarn las acciones que puede realizar cada uno de esos
objetos.

De la descripcin subrayada de este juego concreto podemos extraer los


siguientes objetos y las siguientes acciones:

Nave : mover izquierda, mover derecha, disparar, perder vida


Torre defensiva: destruir (un fragmento, en ciertas coordenadas)
Enemigos : mover, disparar, desaparecer
Ovni : mover
Bloque (formado por enemigos) : mover
Disparo : mover, desaparecer
Marcador : mostrar, reiniciar, incrementar puntos
Partida (contiene nave, enemigos, torres, ovni)

Revisin 0.99zz Pgina 182


Introduccin a la programacin con C#, por Nacho Cabanes

Juego (formado por bienvenida y partida)

(En general, esta descomposicin 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 podra ser as
(simplificado):

El nombre "diagrama de clases" se debe a que se llama "clase" a un conjunto de


objetos que tienen una serie de caractersticas comunes. Por ejemplo, un Honda
Civic Type-R con matrcula 0001-AAA sera un objeto concreto perteneciente a la
clase "Coche".
Revisin 0.99zz Pgina 183
Introduccin a la programacin con C#, por Nacho Cabanes

Algunos de los detalles que se pueden leer de ese diagrama (y que deberan
parecerse bastante a la descripcin inicial) son:

El objeto principal de nuestro proyecto se llama "Juego" (el diagrama


tpicamente se leer de arriba a abajo).
El juego contiene una "Bienvenida" y una "Partida" (ese relacin de que un
objeto "contiene" a otros se indica mediante un rombo en el extremo de la
lnea que une ambas clases, junto a la clase "contenedora").
La "Bienvenida" se puede "Lanzar" al comienzo del juego.
Si as lo elige el jugador, se puede "Lanzar" una "Partida".
En una partida participan una "Nave", cuatro "Torres" defensivas, un
"BloqueDeEnemigos" formado por varios "Enemigos" (que, a su vez,
podran ser de tres tipos distintos, pero no afinaremos tanto por ahora) y
un "Ovni".
El "Ovni" se puede "Mover".
Una "TorreDefensiva" se puede "Destruir" poco a poco, a partir de un
impacto en ciertas coordenadas x,y.
El "BloqueDeEnemigos" se puede "Mover".
Cada "Enemigo" individual se puede mover a la derecha, a la izquierda,
puede disparar o puede desaparecer (cuando un disparo le acierte).
El "Disparo" se puede "Mover" y puede "Desaparecer".
Nuestra "Nave" se puede mover a la derecha, a la izquierda y puede
disparar.
Tanto la "Nave" como las "Torres", los "Enemigos" y el "Ovni" son tipos
concretos de "Sprite" (esa relacin entre un objeto ms genrico y uno
ms especfico se indica con las puntas de flecha, que sealan al objeto
ms genrico).
Un "Sprite" es una figura grfica de las que aparecen en el juego. Cada
sprite tendr detalles (que llamaremos "atributos") como una "imagen" y
una posicin, dada por sus coordenadas "x" e "y". Ser capaz de hacer
operaciones (que llamaremos "mtodos") como "dibujarse" (aparecer en
pantalla) o "moverse" a una nueva posicin. Cuando toda esta estructura
de clases se convierte en un programa, los atributos se representarn
variables, mientras que los "mtodos" sern funciones. Los subtipos de
sprite "heredarn" las caractersticas de esta clase. Por ejemplo, como un
Sprite tiene una coordenada X y una Y, tambin lo tendr el OVNI, que es
una subclase de Sprite. De igual modo, la nave se podr "Dibujar" en
pantalla, porque tambin es una subclase de Sprite.

Revisin 0.99zz Pgina 184


Introduccin a la programacin con C#, por Nacho Cabanes

El propio juego tambin tendr mtodos adicionales, relacionados con la


lgica del juego, como "ComprobarTeclas" (para ver qu teclas ha pulsado
el usuario), "MoverElementos" (para actualizar el movimiento de los
elementos que deban moverse por ellos mismos, como los enemigos o el
OVNI), "ComprobarColisiones" (para ver si dos elementos chocan, como un
disparo y un enemigo, y actualizar el estado del juego segn corresponda),
o "DibujarElementos" (para mostrar en pantalla todos los elementos
actualizados).

Faltan detalles, pero no es un mal punto de partida. A partir de este diagrama


podramos crear un "esqueleto" de programa que compilase correctamente pero
an "no hiciese nada", y entonces comenzaramos a repartir trabajo: una persona
se podra encargar de crear la pantalla de bienvenida, otra de la lgica del juego,
otra del movimiento del bloque de enemigos, otra de las peculiaridades de cada
tipo de enemigo, otra del OVNI...

Nosotros no vamos a hacer proyectos tan grandes (al menos, no todava), pero s
empezaremos a crear proyectos sencillos en los que colaboren varias clases, que
permitan sentar las bases para proyectos ms complejos, y tambin entender
algunas peculiaridades de los temas que veremos a continuacin, como el manejo
de ficheros en C#.

Como curiosidad, cabe mencionar que en los proyectos grandes es habitual usar
herramientas grficas que nos ayuden a visualizar las clases y las relaciones que
existen entre ellas, como hemos hecho para el Space Invaders. Tambin se puede
dibujar directamente en papel para aclararnos las ideas, pero el empleo de
herramientas informticas tiene varias ventajas adicionales:

Podemos "pinchar y arrastrar" para recolocar las clases, y as conseguir ms


legibilidad o dejar hueco para nuevas clases que hayamos descubierto que
tambin deberan ser parte del proyecto.

Algunas de estas herramientas grficas permiten generar automticamente


un esqueleto del programa, que nosotros rellenaremos despus con los
detalles de la lgica del programa.

La metodologa ms extendida actualmente para disear estos objetos y sus


interacciones (adems de otras muchas cosas) se conoce como UML (Unified
Modelling Language, lenguaje de modelado unificado). El estndar UML propone
distintos tipos de diagramas para representar los posibles "casos de uso" de una

Revisin 0.99zz Pgina 185


Introduccin a la programacin con C#, por Nacho Cabanes

aplicacin, 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 cdigo
correspondiente al esqueleto de nuestras clases, o Dia, tambin multiplataforma,
pero de uso ms genrico (para crear diagramas de cualquier tipo) y que no
permite generar cdigo por ella misma pero s con la ayuda de Dia2code.

En los prximos ejemplos partiremos de una nica clase en C#, para ampliar
posteriormente esa estructura e ir creando proyectos ms 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 descripcin como la anterior y
una descomposicin en clases.

6.2. Objetos y clases en C#


Las clases en C# se definen de forma parecida a los registros (struct), slo que
ahora, adems de variables (que representan sus detalles internos, y que
llamaremos sus "atributos"), tambin incluirn funciones (las acciones que puede
realizar ese objeto, que llamaremos sus "mtodos"). Atributos y mtodos formarn
parte de "un todo", en vez de estar separados en distintas partes del programa.
Esto es lo que se conoce como "Encapsulacin".

As, una clase "Puerta" se podra declarar as:

public class Puerta


{

int ancho; // Ancho en centimetros


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

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public void MostrarEstado()


{
Revisin 0.99zz Pgina 186
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("Ancho: {0}", ancho);


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

} // Final de la clase Puerta

Como se puede observar, los objetos de la clase "Puerta" tendrn un ancho, un


alto, un color, y un estado (abierta o no abierta), y adems se podrn abrir o cerrar
(y adems, nos pueden "mostrar su estado", para comprobar que todo funciona
correctamente).

Para declarar estos objetos que pertenecen a la clase "Puerta", usaremos la


palabra "new", igual que hacamos con los "arrays":

Puerta p = new Puerta();


p.Abrir();
p.MostrarEstado();

Vamos a completar un programa de prueba que use un objeto de esta clase (una
"Puerta"), muestre su estado, la abra y vuelva a mostrar su estado:

// Ejemplo_06_02a.cs
// Primer ejemplo de clases
// Introduccin a C#, por Nacho Cabanes

using System;

public class Puerta


{

int ancho; // Ancho en centimetros


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

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public void MostrarEstado()


{
Console.WriteLine("Ancho: {0}", ancho);
Console.WriteLine("Alto: {0}", alto);
Console.WriteLine("Color: {0}", color);
Console.WriteLine("Abierta: {0}", abierta);
}
Revisin 0.99zz Pgina 187
Introduccin a la programacin con C#, por Nacho Cabanes

} // Final de la clase Puerta

public class Ejemplo_06_02a


{

public static void Main()


{
Puerta p = new Puerta();

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:

La clase "Puerta", que es el nuevo objetos con el que vamos a practicar.


La clase "Ejemplo_06_02a", que representa a nuestra aplicacin.

(Nota: al compilar, puede que obtengas algn "Aviso" -warning- que te dice que
has declarado "alto", "ancho" y "color", pero no las ests utilizando; no es
importante por ahora, puedes ignorar ese aviso).

El resultado de ese programa es el siguiente:

Valores iniciales...
Ancho: 0
Alto: 0
Color: 0
Abierta: False

Vamos a abrir...
Ancho: 0
Alto: 0
Color: 0
Abierta: True

Se puede ver que en C# (pero no en todos los lenguajes), las variables que forman
parte de una clase (los "atributos") tienen un valor inicial predefinido: 0 para los

Revisin 0.99zz Pgina 188


Introduccin a la programacin con C#, por Nacho Cabanes

nmeros, una cadena vaca para las cadenas de texto, "false" para los datos
booleanos.

Vemos tambin que se accede a los mtodos y a los datos precediendo el nombre
de cada uno por el nombre de la variable y por un punto, como hacamos con los
registros (struct).

Aun as, en nuestro caso no podemos hacer directamente "p.abierta = true" desde
el programa principal, por dos motivos:

El atributo "abierta" no tiene delante la palabra "public"; por lo que no es


pblico, sino privado, y no ser accesible desde otras clases (en nuestro
caso, no lo ser desde Ejemplo_06_02a).

Los puristas de la Programacin Orientada a Objetos recomiendan que no


se acceda directamente a los atributos, sino que siempre se modifiquen
usando mtodos auxiliares (por ejemplo, nuestro "Abrir"), y que se lea su
valor tambin usando una funcin. Esto es lo que se conoce como
"ocultacin de datos". Supondr ventajas como que podremos cambiar
los detalles internos de nuestra clase sin que afecte a su uso.

Por ejemplo, para conocer y modificar los valores del "ancho" de una puerta,
podramos crear un mtodo LeerAncho, que nos devolviera su valor, y un mtodo
CambiarAncho, que lo reemplazase por otro valor. No hay un convenio claro sobre
cmo llamar a a estos mtodos en espaol, 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 ms adelante):

public int GetAncho()


{
return ancho;
}

public void SetAncho(int nuevoValor)


{
ancho = nuevoValor;
}

As, una nueva versin del programa, que incluya ejemplos de Get y Set, podra
ser:

Revisin 0.99zz Pgina 189


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_06_02b.cs
// Clases, get y set
// Introduccin a C#, por Nacho Cabanes

using System;

public class Puerta


{

int ancho; // Ancho en centimetros


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

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public int GetAncho()


{
return ancho;
}

public void SetAncho(int nuevoValor)


{
ancho = nuevoValor;
}

public void MostrarEstado()


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

} // Final de la clase Puerta

public class Ejemplo_06_02b


{

public static void Main()


{
Puerta p = new Puerta();

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

Console.WriteLine();

Console.WriteLine("Vamos a abrir...");
p.Abrir();
p.SetAncho(80);
p.MostrarEstado();
Revisin 0.99zz Pgina 190
Introduccin a la programacin con C#, por Nacho Cabanes

Tambin puede desconcertar que en "Main" aparezca la palabra "static", mientras


que no lo hace en los mtodos de la clase "Puerta". Veremos el motivo un poco
ms adelante, pero de momento perderemos la costumbre de escribir "static"
antes de cada funcin: a partir de ahora, slo Main ser "static".

Ejercicios propuestos:
(6.2.1) Crea una clase llamada Persona, en el fichero "persona.cs". Esta clase
deber tener un atributo "nombre", de tipo string. Tambin deber tener un
mtodo "SetNombre", de tipo void y con un parmetro string, que permita
cambiar el valor del nombre. Finalmente, tambin tendr un mtodo "Saludar",
que escribir en pantalla "Hola, soy " seguido de su nombre. Crea tambin una
clase llamada PruebaPersona. Esta clase deber contener slo la funcin Main,
que crear dos objetos de tipo Persona, les asignar un nombre a cada uno y les
pedir que saluden.
(6.2.2) Tras leer la descripcin de Space Invaders que vimos en el apartado
anterior, crea una clase Juego,que slo contenga un mtodo Lanzar, void, sin
parmetros, que escriba en pantalla "Bienvenido a Console Invaders. Pulse Intro
para salir" y se parar hasta que el usuario pulse Intro. Prepara tambin un Main
(en la misma clase), que cree un objeto de la clase Juego y lo lance.
(6.2.3) Para guardar informacin sobre libros, vamos a comenzar por crear una
clase "Libro", que contendr atributos "autor", "titulo", "ubicacion" (todos ellos
strings) y mtodos Get y Set adecuados para leer su valor y cambiarlo. Prepara
tambin un Main (en la misma clase), que cree un objeto de la clase Libro, d
valores a sus tres atributos y luego los muestre.
(6.2.4) Crea una clase "Coche", con atributos "marca" (texto), "modelo" (texto),
"cilindrada" (nmero entero), potencia (nmero real). No hace falta que crees un
Main de prueba.

6.3. Proyectos a partir de varios fuentes


En un proyecto grande, es recomendable que cada clase est en su propio
fichero fuente, de forma que se puedan localizar con rapidez (en los que hemos
hecho en el curso hasta ahora, no era necesario, porque eran muy simples).

Es recomendable (aunque no obligatorio) que cada clase est en un fichero que


tenga el mismo nombre: que la clase Puerta se encuentre en el fichero "Puerta.cs".

Revisin 0.99zz Pgina 191


Introduccin a la programacin con C#, por Nacho Cabanes

Para compilar un programa formado por varios fuentes, basta con indicar los
nombres de todos ellos. Por ejemplo, con Mono sera

mcs fuente1.cs fuente2.cs fuente3.cs

En ese caso, el ejecutable obtenido tenda el nombre del primero de los fuentes
(fuente1.exe). Podemos cambiar el nombre del ejecutable con la opcin "-out" de
Mono:

mcs fuente1.cs fuente2.cs fuente3.cs -out:ejemplo.exe

En general, esto no lo podremos hacer de forma sencilla desde editores como


Notepad++ y Geany: Notepad++ tiene un men "Ejecutar", que nos permite
teclear una orden como la anterior, pero no supone ninguna gran ganancia;
Geany permite crear "proyectos" formados por varios fuentes, pero no resulta una
tarea especialmente cmoda. Por eso, veremos un primer ejemplo de cmo
compilar desde "lnea de comandos" y luego lo haremos con un entorno mucho
ms avanzado y que usaremos con frecuencia a partir de ahora: Visual Studio.

Vamos a dividir en dos fuentes el ltimo ejemplo y a ver cmo se compilara. La


primera clase podra ser sta:

// Puerta.cs
// Clases, get y set
// Introduccin a C#, por Nacho Cabanes

using System;

public class Puerta


{

int ancho; // Ancho en centimetros


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

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public int GetAncho()


{
return ancho;
Revisin 0.99zz Pgina 192
Introduccin a la programacin con C#, por Nacho Cabanes

public void SetAncho(int nuevoValor)


{
ancho = nuevoValor;
}

public void MostrarEstado()


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

} // Final de la clase Puerta

Y la segunda clase podra ser:

// Ejemplo_06_03a.cs
// Usa la clase Puerta
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_06_03a


{

public static void Main()


{
Puerta p = new Puerta();

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

Console.WriteLine();

Console.WriteLine("Vamos a abrir...");
p.Abrir();
p.SetAncho(80);
p.MostrarEstado();
}

Y lo compilaramos con:

mcs ejemplo06_03a.cs puerta.cs -out:ejemploPuerta.exe

Aun as, para estos proyectos formados por varias clases, lo ideal es usar algn
entorno ms avanzado, como SharpDevelop (que adems es gratuito) o
VisualStudio (del que existe una versin gratuita, conocida como Express), que nos
Revisin 0.99zz Pgina 193
Introduccin a la programacin con C#, por Nacho Cabanes

permitan crear todas las clases con comodidad, saltar de una clase a clase otra
rpidamente, que marquen dentro del propio editor la lnea en la que estn los
errores...

El entorno de desarrollo ms conocido para crear programas de una cierta


complejidad es Visual Studio, de Microsoft, que en su versin Express incluye todo
lo que un programador novel como nosotros puede necesitar. Aun as, como
puede requerir un equipo relativamente potente, vamos a comenzar por ver una
alternativa muy similar, pero algo ms sencilla, llamada SharpDevelop, disponible
para Windows.

Comenzamos por descargar el fichero de instalacin del entorno, desde su pgina


oficial (http://www.icsharpcode.net/OpenSource/SD/Download/). La versin 4.4,
para las versiones 2.0 a 4.5.1 de la plataforma .Net, ocupa unos 15 Mb. En el
momento de escribir este texto tambin existe una versin 5.0, pero todava est
en "fase beta" (pruebas previas a la versin definitiva, as que puede contener
algn error).

La instalacin comenzar al hacer doble clic en el fichero descargado. Deberamos


ver una ventana parecida a sta:

Como es habitual, el siguiente paso ser aceptar el contrato de licencia, despus


deberemos decir en qu carpeta queremos instalarlo, comenzar a copiar archivos
y al cabo de un instante, tendremos un nuevo icono en nuestro escritorio:

Revisin 0.99zz Pgina 194


Introduccin a la programacin con C#, por Nacho Cabanes

La instalacin debera ser muy sencilla en Windows Vista y superiores, pero en


Windows XP quiz necesite que instalemos una versin reciente de la plataforma
.Net (se puede descargar gratuitamente desde la web de Microsoft).

Cuando hagamos doble clic en nuestro nuevo icono, veremos la pantalla principal
de SharpDevelop, que nos muestra la lista de los ltimos proyectos ("soluciones")
que hemos realizado, y nos permite crear uno nuevo:

En nuestro caso, comenzaremos por crear una "Nueva solucin", desde el men
"File", en la opcin "New Solution":

Revisin 0.99zz Pgina 195


Introduccin a la programacin con C#, por Nacho Cabanes

Se nos mostrar los tipos de proyectos para los que se nos podra crear un
esqueleto vaco que despus iramos rellenando:

De estos tipos, el nico que conocemos es una "Aplicacin de Consola" (Console


Application) en C#. Deberemos escribir tambin el nombre, y aparecer un
esqueleto de aplicacin que nosotros slo tendramos que completar:

Revisin 0.99zz Pgina 196


Introduccin a la programacin con C#, por Nacho Cabanes

Podemos cambiar el idioma del interfaz, para que todos los mens y opciones
aparezcan en espaol. Lo haremos desde "Options" (opciones), al final del men
"Tools" (herramientas):

Cuando hayamos terminado de realizar nuestros cambios, podemos compilar el


programa con el botn "Generar":

Revisin 0.99zz Pgina 197


Introduccin a la programacin con C#, por Nacho Cabanes

Si hubiera algn error, se nos avisara en la parte inferior de la pantalla, y se


subrayaran en rojo las lneas correspondientes de nuestro programa; si todo ha
ido bien, podremos ejecutar nuestro programa para verlo funcionando:

(Si la ventana de nuestro programa se cierra tan rpido que no tenemos tiempo de
leerla, nos puede interesar aadir provisionalmente una lnea ReadLine() al final
del fuente, para que ste se detenga hasta que pulsemos la tecla Intro).

As prepararamos y lanzaramos un programa formado por un solo fuente. Si se


trata de varios fuentes, basta con ir aadiendo nuevas clases al proyecto. Lo
conseguimos pulsando el botn derecho sobre el nombre del proyecto (en la
ventana izquierda, "Proyectos") y escogiendo las opciones Agregar / Agregar
Elemento:

Revisin 0.99zz Pgina 198


Introduccin a la programacin con C#, por Nacho Cabanes

Normalmente, el tipo de elemento que nos interesar ser una clase, cuyo nombre
deberemos indicar:

y obtendramos un nuevo esqueleto vaco (esta vez sin "Main"), que deberamos
completar.

Revisin 0.99zz Pgina 199


Introduccin a la programacin con C#, por Nacho Cabanes

Nuestro programa, que ahora estara formado por dos clases, se compilara y se
ejecutara 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 versin Express, que es gratuita para uso personal, se puede descargar
de:

http://www.visualstudio.com/es-es/products/visual-studio-express-vs

(En nuestro caso, se tratara de la versin Express para escritorio de Windows -


Windows Desktop-)

La instalacin es muy similar a la de SharpDevelop: poco ms que descargar el


fichero, hacer doble clic para comenzar (si descargamos y esperar un momento a
que se complete el proceso (puede ser necesario tener que reiniciar el ordenador
en algn punto intermedio, si nuestra versin de la plataforma "punto net" no es
lo suficientemente moderna).

Si escogemos el instalador basado en Web, descargaremos un pequeo fichero al


principio, y necesitaremos estar conectados a Internet durante la instalacin para
que se reciban los ficheros necesarios. Si, por el contrario, descargamos la "imagen
ISO de DVD", deberemos grabarla en un DVD o extraerla con 7-zip o WinRar para
acceder a los ficheros de instalacin.

Revisin 0.99zz Pgina 200


Introduccin a la programacin con C#, por Nacho Cabanes

Si eres estudiante y tu centro de estudios est adscrito a programas como la


"Academic Alliance" de Microsoft (MSDNAA), posiblemente tendrs acceso gratuito
tambin a otras versiones ms potentes de Visual Studio, como Professional y
Ultimate, con ms caractersticas adicionales (que no necesitars siendo un
principiante, como las pruebas de rendimiento o automatizacin de tests para
interfaces de usuario).

La pantalla principal, una vez completada la instalacin, debera ser similar a sta
(para Visual Studio 2013; puede ser ligeramente distinta en otras versiones o si
escogemos otras paletas de colores):

En ella tenemos la opcin de crear un "Nuevo proyecto". De entre los tipos de


proyectos existentes, escogeramos crear una "Aplicacin de consola":

Revisin 0.99zz Pgina 201


Introduccin a la programacin con C#, por Nacho Cabanes

En la parte inferior escribiremos el nombre de nuestro proyecto, y pulsaremos el


botn "Aceptar", con lo que debera aparecer un esqueleto de programa vaco:

Si nuestro programa va a estar formado por varios fuentes, podramos aadir ms


clases usando la ventana de la derecha ("Explorador de soluciones") o la opcin
"Agregar clase" del men "Proyecto":

Revisin 0.99zz Pgina 202


Introduccin a la programacin con C#, por Nacho Cabanes

Que, al igual que en el caso de SharpDevelop, nos mostrara el esqueleto de un


fuente, que en este caso no contiene "Main". Cuando el programa est listo,
podemos lanzarlo haciendo clic en el botn "Iniciar ":

Hay alguna diferencia ms en el manejo normal de Visual Studio Express,


comparado con el de SharpDevelop, que conviene tener en cuenta:

No es necesario compilar para saber los errores: se irn marcando en rojo


las lneas incorrectas a medida que las tecleemos.

Segn la versin de Visual Studio, puede ser que "compile desde


memoria": si pulsamos el botn de Iniciar, nuestro proyecto no se guarda
automticamente, sino que deberemos usar explcitamente la opcin de
Guardar cuando nosotros lo deseemos. Si no lo hacemos, corremos el
riesgo de que no se haya llegado a guardar nuestro proyecto, y perdamos
el trabajo en caso de fallo del sistema operativo o del suministro de
corriente elctrica.

La mayora de versiones de Visual Studio permiten lanzar nuestro programa


de modo que la ejecucin se pause al terminar, igual que ocurre en Geany.
Para conseguirlo, en vez de pulsar el botn "Iniciar" de la barra de
herramientas, utilizaremos la combinacin de teclas Ctrl+F5.

Revisin 0.99zz Pgina 203


Introduccin a la programacin con C#, por Nacho Cabanes

Quien trabaja con Linux o con Mac, no podr emplear SharpDevelop ni


VisualStudio, pero tiene una alternativa muy similar, desarrollada por el mismo
equipo que ha creado Mono. Se trata de MonoDevelop, que se debera poder
instalar apenas en un par de clics con el gestor de paquetes del sistema (por
ejemplo, Synaptic en el caso de Ubuntu y de Linux Mint).

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. Tambin deber tener un
mtodo "SetNombre", de tipo void y con un parmetro string, que permita
cambiar el valor del nombre. Finalmente, tambin tendr un mtodo "Saludar",
que escribir en pantalla "Hola, soy " seguido de su nombre. Crea tambin una
clase llamada PruebaPersona, en el fichero "pruebaPersona.cs". Esta clase deber
contener slo la funcin 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) Ampla el esqueleto del ConsoleInvaders (ejercicio 6.2.2): crea un proyecto
para Visual Studio o SharpDevelop. Adems de la clase "Juego", crea una clase
"Bienvenida" y una clase "Partida". El mtodo "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 mtodo Lanzar de la
clase Bienvenida escribir en pantalla "Bienvenido a Console Invaders. Pulse Intro
para jugar". El mtodo Lanzar de la clase Partida escribir en pantalla "sta sera la

Revisin 0.99zz Pgina 204


Introduccin a la programacin con C#, por Nacho Cabanes

pantalla de juego. Pulse Intro para salir" y se parar hasta que el usuario pulse
Intro.
(6.3.5) Ampla el esqueleto del ConsoleInvaders (ejercicio 6.3.4): El mtodo 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 cdigo de la tecla Intro es " ConsoleKey.Enter". Tambin puedes
usar "Console.Clear();" si quieres borrar la pantalla. Aade un mtodo "GetSalir" a
la clase Bienvenida, que devuelva "true" si el usuario ha escogido Salir o "false" si
ha elegido Jugar. El mtodo Lanzar de la clase Juego repetir la secuencia
Bienvenida-Partida hasta que el usuario escoja Salir.
(6.3.6) Ampla el esqueleto del ConsoleInvaders (ejercicio 6.3.5): Crea una clase
Nave, con atributos "x" e "y" (nmeros 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 "/\"). Tambin tendr un mtodo MoverA(nuevaX, nuevaY) que lo
mueva a una nueva posicin, y un mtodo Dibujar, que muestre esa imagen en
pantalla (como esta versin 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 tambin clase Enemigo, con los mismos atributos. Su imagen podra ser "][".
El mtodo 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 sesin de
juego.
(6.3.7) Crea un proyecto a partir de la clase Coche (ejercicio 6.2.4): adems de la
clase Coche, existir una clase PruebaDeCoche, que contendr la funcin "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 cmo 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 portn tendr las mismas caractersticas que una puerta (ancho, alto,
color, abierto o no), pero adems se podr bloquear, lo que supondr un nuevo
atributo y nuevos mtodos para bloquear y desbloquear:

// Porton.cs
// Clase que hereda de Puerta
// Introduccin a C#, por Nacho Cabanes

using System;
Revisin 0.99zz Pgina 205
Introduccin a la programacin con C#, por Nacho Cabanes

public class Porton : Puerta


{

bool bloqueada;

public void Bloquear()


{
bloqueada = true;
}

public void Desbloquear()


{
bloqueada = false;
}

Con "public class Porton: Puerta" indicamos que Porton debe "heredar" todo lo
que ya habamos definido para Puerta. Por eso, no hace falta indicar nuevamente
que un Portn tendr un cierto ancho, o un color, o que se puede abrir: todo eso
lo tiene por ser un "descendiente" de Puerta.

En este ejemplo, la clase Puerta no cambiara nada.

No tenemos por qu heredar todo tal y como era; tambin podemos "redefinir"
algo que ya exista. Por ejemplo, nos puede interesar que "MostrarEstado" ahora
nos diga tambin si la puerta est bloqueada. Para eso, basta con volverlo a
declarar y aadir la palabra "new" para indicar al compilador de C# que sabemos
que ya existe ese mtodo y que sabemos seguro que lo queremos redefinir (si no
incluimos la palabra "new", el compilador mostrar un "warning", pero dar el
programa como vlido; ms adelante veremos que existe una alternativa a "new" y
en qu momento ser adecuado usar cada una de ellas.

public new void MostrarEstado()


{
Console.WriteLine("Portn.");
Console.WriteLine("Bloqueada: {0}", bloqueada);
}

Puedes observar que ese "MostrarEstado" no dice nada sobre el ancho ni el alto
del portn. En el prximo apartado veremos cmo acceder a esos datos.

Un programa de prueba, que ampliase el anterior para incluir un "portn", podra


ser:

// Ejemplo_06_04a.cs
Revisin 0.99zz Pgina 206
Introduccin a la programacin con C#, por Nacho Cabanes

// Portn, que hereda de Puerta


// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_06_04a


{

public static void Main()


{
Puerta p = new Puerta();

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 portn...");
Porton p2 = new Porton();
p2.SetAncho(300);
p2.Bloquear();
p2.MostrarEstado();
}

Y su resultado sera:

Valores iniciales...
Ancho: 0
Alto: 0
Color: 0
Abierta: False

Vamos a abrir...
Ancho: 80
Alto: 0
Color: 0
Abierta: True

Ahora el portn...
Portn.
Bloqueada: True

Ejercicios propuestos:
(6.4.1) Crea un proyecto con las clases Puerta, Portn y Ejemplo_06_04a. Prueba
que todo funciona correctamente.

Revisin 0.99zz Pgina 207


Introduccin a la programacin con C#, por Nacho Cabanes

(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 caractersticas de la clase
"Persona", y aadir un mtodo "TomarTe", de tipo void, que escribir en pantalla
"Estoy tomando t". Crear tambin una clase llamada PruebaPersona2, en el
fichero "pruebaPersona2.cs". Esta clase deber contener slo la funcin 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) Ampla el proyecto del ejercicio 6.3.3 (Libro): crea una clase "Documento",
de la que Libro heredar todos sus atributos y mtodos. Ahora la clase Libro
contendr slo un atributo "paginas", nmero entero, con sus correspondientes
Get y Set.
(6.4.4) Ampla el esqueleto del ConsoleInvaders (ejercicio 6.3.6): Crea una clase
"Sprite", de la que heredarn "Nave" y "Enemigo". La nueva clase contendr todos
los atributos y mtodos que son comunes a las antiguas (todos los existentes, por
ahora).
(6.4.5) Ampla el proyecto de la clase Coche (ejercicio 6.3.7): Crea una clase
"Vehiculo", de la que heredarn "Coche" y una nueva clase "Moto". La clase
Vehiculo contendr todos los atributos y mtodos que antes estaban en Coche, y
tanto Coche como Moto heredarn de ella.

6.5. Visibilidad
Nuestro ejemplo todava 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 pblico, porque nuestras clases y su "Main" lo han sido siempre hasta ahora.
Nos podramos sentir tentados de declarar como "public" los atributos como el
"ancho" y el "alto", pero esa no es la solucin ms razonable, porque no queremos
que sean accesibles desde cualquier sitio, debemos recordar la mxima de
"ocultacin de detalles", que hace nuestros programas sean ms fciles de
mantener.

Slo querramos que esos datos estuvieran disponibles para todos los tipos de
Puerta, incluyendo sus "descendientes", como un Porton. Esto se puede conseguir
usando otro mtodo de acceso: "protected". Todo lo que declaremos como

Revisin 0.99zz Pgina 208


Introduccin a la programacin con C#, por Nacho Cabanes

"protected" ser accesible por las clases derivadas de la actual, pero por nadie
ms:

public class Puerta


{

protected int ancho; // Ancho en centimetros


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

public void Abrir()


...

Si quisiramos dejar claro que algn 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 ms
adelante programamos en otros lenguajes, porque puede ocurrir que en otros
lenguajes se considere pblico (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 adems contuviese un pequeo "Main" de prueba podra ser:

// Ejemplo_06_05a.cs
// Portn, que hereda de Puerta, con "protected"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_06_05a


{

public static void Main()


{
Puerta p = new Puerta();

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 portn...");
Revisin 0.99zz Pgina 209
Introduccin a la programacin con C#, por Nacho Cabanes

Porton p2 = new Porton();


p2.SetAncho(300);
p2.Bloquear();
p2.MostrarEstado();
}

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

// Puerta.cs
// Clases, get y set
// Introduccin a C#, por Nacho Cabanes

public class Puerta


{

protected int ancho; // Ancho en centimetros


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

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public int GetAncho()


{
return ancho;
}

public void SetAncho(int nuevoValor)


{
ancho = nuevoValor;
}

public void MostrarEstado()


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

} // Final de la clase Puerta

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

// Porton.cs
// Clase que hereda de Puerta
// Introduccin a C#, por Nacho Cabanes

public class Porton : Puerta


Revisin 0.99zz Pgina 210
Introduccin a la programacin con C#, por Nacho Cabanes

bool bloqueada;

public void Bloquear()


{
bloqueada = true;
}

public void Desbloquear()


{
bloqueada = false;
}

public new void MostrarEstado()


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

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 podrs comprobar, ahora necesitars un "using
System" en cada fuente.
(6.5.2) Ampla las clases del ejercicio 6.4.2, creando un nuevo proyecto con las
siguientes caractersticas: La clase Persona no cambia; la clase PersonaInglesa se
modificar para que redefina el mtodo "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 caractersticas de la clase
"Persona", pero redefinir el mtodo "Saludar", para que escriba en pantalla "Ciao";
crea tambin una clase llamada PruebaPersona3, en el fichero "
pruebaPersona3.cs", que deber contener slo la funcin 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 sern "protegidos".
(6.5.4) Ampla el esqueleto del ConsoleInvaders (ejercicio 6.4.4): Ampla la clase
Nave con un mtodo "MoverDerecha", que aumente su X en 10 unidades, y un
"MoverIzquierda", que disminuya su X en 10 unidades. Necesitars hacer que esos
atributos sean "protected". El mtodo Lanzar de la clase Partida no esperar hasta
el usuario pulse Intro sin hacer nada, sino que ahora usar un do-while que
Revisin 0.99zz Pgina 211
Introduccin a la programacin con C#, por Nacho Cabanes

compruebe si pulsa ESC (para salir) o flecha izquierda o flecha derecha (para
mover la nave: sus cdigos son ConsoleKey.LeftArrow y ConsoleKey. RightArrow).
Si se pulsan las flechas, la nave se mover a un lado o a otro (con los mtodos 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
sern "protegidos" y los mtodos sern "pblicos".

6.6. Constructores y destructores


Hemos visto que al declarar una clase, automticamente se dan valores por
defecto para los atributos. Por ejemplo, para un nmero entero, se le da el valor 0.
Pero puede ocurrir que nosotros deseemos dar valores iniciales que no sean cero.
Esto se puede conseguir declarando un "constructor" para la clase.

Un constructor es una funcin especial, que se pone en marcha cuando se crea


un objeto de una clase, y se suele usar para dar esos valores iniciales, para
reservar memoria si fuera necesario, para leer informacin desde fichero, etc.

Un constructor se declara usando el mismo nombre que el de la clase, y sin ningn


tipo de retorno. Por ejemplo, un "constructor" para la clase Puerta que le diera los
valores iniciales de 100 para el ancho, 200 para el alto, etc., podra ser as:

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

Podemos tener ms de un constructor, cada uno con distintos parmetros. Por


ejemplo, puede haber otro constructor que nos permita indicar el ancho y el alto:

public Puerta(int an, int al)


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

Revisin 0.99zz Pgina 212


Introduccin a la programacin con C#, por Nacho Cabanes

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


tendr de ancho 100 y de alto 200, mientras que si lo declaramos con "Puerta p2 =
new Puerta(90,220);" tendr 90 como ancho y 220 como alto.

Un programa de ejemplo que usara estos dos constructores para crear dos
puertas con caractersticas iniciales distintas podra ser:

// Ejemplo_06_06a.cs
// Constructores
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_06_06a


{

public static void Main()


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

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


{

protected int ancho; // Ancho en centimetros


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

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

public Puerta(int an, int al)


{
Revisin 0.99zz Pgina 213
Introduccin a la programacin con C#, por Nacho Cabanes

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

public void Abrir()


{
abierta = true;
}

public void Cerrar()


{
abierta = false;
}

public void MostrarEstado()


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

} // Final de la clase Puerta

Nota: al igual que existen los "constructores", tambin podemos crear un


"destructor" para una clase, que se encargue de liberar la memoria que
pudiramos haber reservado en nuestra clase (no es nuestro caso, porque an no
sabemos manejar memoria dinmica) o para cerrar ficheros abiertos (que
tampoco sabemos). Nuestros programas son tan sencillos que todava no los
necesitarn.

Un destructor se crear con el mismo nombre que la clase y que el constructor,


pero precedido por el smbolo "~", y no tiene tipo de retorno, ni parmetros, ni
especificador de acceso ("public" ni ningn otro), como ocurre en este ejemplo:

~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 estarn vacos, excepto
del de PersonaInglesa, que prefijar su nombre a "John". Crea tambin un
constructor alternativo para esta clase que permita escoger cualquier otro
nombre.

Revisin 0.99zz Pgina 214


Introduccin a la programacin con C#, por Nacho Cabanes

(6.6.2) Ampla el proyecto del ejercicio 6.5.3 (Libro): la clase Libro tendr un
constructor que permita dar valores al autor, el ttulo y la ubicacin.
(6.6.3) Ampla el esqueleto del ConsoleInvaders (ejercicio 6.5.4): La clase Enemigo
tendr un constructor, sin parmetros, que prefijar su posicin inicial. El
constructor de la clase Nave recibir como parmetros 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 sern necesarias.
(6.6.4) Mejora el proyecto de la clase Coche (ejercicio 6.5.5): aade 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.

6.7. Polimorfismo y sobrecarga


El concepto de "polimorfismo" se refiere a que una misma funcin (un mtodo)
puede tener varias formas, ya sea porque reciba distintos tipos de parmetros y/o
en distinta cantidad, o porque incluso se aplique a distintos objetos.

Existen dos tipos especialmente importantes de polimorfismo:

En nuestro ltimo ejemplo (06_06a), los dos constructores "Puerta()" y


"Puerta(int ancho, int alto)", que se llaman igual pero reciben distintos
parmetros, y se comportan de forma que puede ser distinta, son ejemplos
de "sobrecarga" (tambin conocida como "polimorfismo ad-hoc"). Es un
tipo de polimorfismo en el que el compilador sabe en tiempo de
compilacin a qu mtodo se debe llamar.

El caso opuesto es el "polimorfismo puro", en el que un mismo mtodo se


aplica a distintos objetos de una misma jerarqua (como el "MostrarEstado"
del ejemplo 06_05a, que se puede aplicar a una puerta o a un portn), y en
ese caso el compilador puede llegar a no ser capaz de saber en tiempo de
compilacin a qu mtodo se debe llamar, y lo tiene que descubrir en
tiempo de ejecucin. Es algo que nos encontraremos un poco ms
adelante, cuando hablemos de "funciones virtuales".

Ejercicios propuestos:
(6.7.1) A partir de las clases del ejercicio 6.6.1, aade a la clase "Persona" un nuevo
mtodo Saludar, que reciba un parmetro, que ser el texto que debe decir esa
persona cuando salude.

Revisin 0.99zz Pgina 215


Introduccin a la programacin con C#, por Nacho Cabanes

(6.7.2) Ampla el proyecto del ejercicio 6.6.2 (Libro): la clase Libro tendr un
segundo constructor que permita dar valores al autor y el ttulo, pero no a la
ubicacin, que tomar el valor por defecto "No detallada".
(6.7.3) Ampla el esqueleto del ConsoleInvaders (6.6.3): La clase Nave tendr un
segundo constructor, sin parmetros, que prefijar su posicin inicial a (500,600).
La clase Enemigo tendr un segundo constructor, con parmetros X e Y, para
poder colocar un enemigo en cualquier punto desde Main.
(6.7.4) Crea dos nuevos mtodos 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 parmetro.

6.8. Orden de llamada de los constructores


Cuando creamos objetos de una clase derivada, antes de llamar a su constructor
se llama a los constructores de las clases base, empezando por la ms general y
terminando por la ms especfica. Por ejemplo, si creamos una clase
"GatoSiams", que deriva de una clase "Gato", que a su vez procede de una clase
"Animal", el orden de ejecucin de los constructores sera: Animal, Gato,
GatoSiames, como se ve en este ejemplo:

// Ejemplo_06_08a.cs
// Orden de llamada a los constructores
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_06_08a


{
public static void Main()
{
Animal a1 = new Animal();
Console.WriteLine();

GatoSiames a2 = new GatoSiames();


Console.WriteLine();

Perro a3 = new Perro();


Console.WriteLine();

Gato a4 = new Gato();


Console.WriteLine();
}
}

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

public class Animal


Revisin 0.99zz Pgina 216
Introduccin a la programacin con C#, por Nacho Cabanes

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

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

public class Perro: Animal


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

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

public class Gato: Animal


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

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

public class GatoSiames: Gato


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

El resultado de este programa es:

Ha nacido un animal

Ha nacido un animal
Ha nacido un gato
Ha nacido un gato siams

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:

Revisin 0.99zz Pgina 217


Introduccin a la programacin con C#, por Nacho Cabanes

Una clase Trabajador, cuyo constructor escriba en pantalla "Soy un


trabajador".
Una clase Programador, que derive de Trabajador, cuyo constructor escriba
en pantalla "Soy programador".
Una clase Analista, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy analista".
Una clase Ingeniero, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy ingeniero".
Una clase IngenieroInformatico, que derive de Ingeniero, cuyo constructor
escriba en pantalla "Soy ingeniero informtico".
Una clase "PruebaDeTrabajadores", que cree un objeto perteneciente a
cada una de esas clases.
(6.8.2) Crea una variante del proyecto Libro (ejercicio 6.7.2) en la que el
constructor de Documento escriba en pantalla "Creando documento" y el
constructor de Libro escriba en pantalla "Creando libro". Comprueba su
funcionamiento.
(6.8.3) Crea una versin alternativa del esqueleto del ConsoleInvaders (6.7.3) en la
que el constructor de Sprite escriba en pantalla "Creando sprite" y los
constructores de Nave escriba en pantalla "Creando nave en posicin prefijada" o
"Creando nave en posicin indicada por el usuario", segn el caso. Comprueba su
funcionamiento.
(6.8.4) Crea una versin alternativa de las clases Vehiculo, Coche, Moto (6.7.4), que
te avise del momento en que se entra a cada constructor. Crea un programa de
prueba que defina un coche y una moto, y comprueba su funcionamiento.

Revisin 0.99zz Pgina 218


Introduccin a la programacin con C#, por Nacho Cabanes

7. Utilizacin avanzad a de clases


7.1. La palabra "static"
Desde un principio, nos hemos encontrado con que "Main" siempre iba
acompaado de la palabra "static". Lo mismo ocurra con las funciones que
creamos en el tema 5. En cambio, los mtodos (funciones) que pertenecen a
nuestros objetos no los estamos declarando como "static". Vamos a ver el motivo:

La palabra "static" delante de un atributo (una variable) de una clase, indica


que es una "variable de clase", es decir, que su valor es el mismo para todos
los objetos de la clase. Por ejemplo, si hablamos de coches convencionales,
podramos suponer que el atributo "numeroDeRuedas" va a tener el valor 4
para cualquier objeto que pertenezca a esa clase (cualquier coche). Por eso,
se podra declarar como "static". En el mundo real, esto es muy poco
habitual, y por eso casi nunca usaremos atributos "static" (por ejemplo, no
todos los coches que podamos encontrar tendrn 4 ruedas, aunque esa sea
la cantidad ms frecuente).

De igual modo, si un mtodo (una funcin) est precedido por la palabra


"static", indica que es un "mtodo de clase", es decir, un mtodo que se
podra usar sin necesidad de declarar ningn objeto de la clase. Por
ejemplo, si queremos que se pueda usar la funcin "BorrarPantalla" de una
clase "Hardware" sin necesidad de crear primero un objeto perteneciente a
esa clase, lo podramos conseguir as:

// Ejemplo_07_01a.cs
// Mtodos "static"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Hardware


{
public static void BorrarPantalla()
{
for (byte i = 0; i < 25; i ++)
Console.WriteLine();
}
}

public class Ejemplo_07_01a


{
public static void Main()
{
Console.WriteLine("Pulsa Intro para borrar");
Console.ReadLine();
Revisin 0.99zz Pgina 219
Introduccin a la programacin con C#, por Nacho Cabanes

Hardware.BorrarPantalla();
Console.WriteLine("Borrado!");
}
}

Desde una funcin "static" no se puede llamar a otras funciones que no lo sean.
Por eso, como nuestro "Main" debe ser static, deberemos siempre elegir entre:

Que todas las dems funciones de nuestro fuente tambin estn


declaradas como "static", por lo que podrn ser utilizadas directamente
desde "Main" (como hicimos en el tema 5, cuando an no conocamos las
"clases").

Declarar un objeto de la clase correspondiente, y entonces s podremos


acceder a sus mtodos desde "Main":

// Ejemplo_07_01b.cs
// Alternativa a 07_01a, sin mtodos "static"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Hardware


{
public void BorrarPantalla()
{
for (byte i = 0; i < 25; i ++)
Console.WriteLine();
}
}

public class Ejemplo_07_01b


{
public static void Main()
{
Console.WriteLine("Pulsa Intro para borrar");
Console.ReadLine();
Hardware miPantalla = new Hardware();
miPantalla.BorrarPantalla();
Console.WriteLine("Borrado!");
}
}

Ejercicios propuestos:
(7.1.1) Ampla el ejemplo 07_01a con un funcin "static" llamada
"EscribirCentrado", que escriba centrado horizontalmente el texto que se le
indique como parmetro.
(7.1.2) Ampla el ejemplo 07_01b con un funcin llamada "EscribirCentrado", que
escriba centrado horizontalmente el texto que se le indique como parmetro. Al
contrario que en el ejercicio 7.1.1, esta versin no ser "static".
Revisin 0.99zz Pgina 220
Introduccin a la programacin con C#, por Nacho Cabanes

(7.1.3) Crea una nueva versin del ejercicio 5.2.3 (base de datos de ficheros,
descompuesta en funciones), en la que los mtodos y variables no sean "static".

7.2. Arrays de objetos


Es muy frecuente que no nos baste con tener un nico objeto de una clase, sino
que necesitemos manipular varios objetos pertenecientes a la misma clase. En
ese caso, podemos almacenar todos ellos en un "array".

Deberemos usar "new" dos veces: primero para reservar memoria para el array, y
luego para cada uno de los elementos. Por ejemplo, podramos partir del ejemplo
del apartado 6.8 y crear un array de 5 perros as:

Perro[] misPerros = new Perro[5];


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

Un fuente completo de ejemplo podra ser

// Ejemplo_07_02a.cs
// Array de objetos
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_02a


{
public static void Main()
{
Perro[] misPerros = new Perro[5];
for (byte i = 0; i < 5; i ++)
misPerros[i] = new Perro();
}
}

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

public class Animal


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

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

public class Perro: Animal


{
Revisin 0.99zz Pgina 221
Introduccin a la programacin con C#, por Nacho Cabanes

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

y su salida en pantalla, que recuerda a la del apartado 6.8, sera

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 versin 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) Ampla el proyecto Libro (ejercicio 6.7.2), de modo que permita guardar
hasta 1.000 libros. Main mostrar un men que permita aadir un nuevo libro o
ver los datos de los ya existentes.
(7.2.3) Ampla el esqueleto del ConsoleInvaders (6.7.3), para que haya 10 enemigos
en una misma fila (todos compartirn una misma coordenada Y, pero tendrn
distinta coordenada X). Necesitars un nuevo constructor en la clase Enemigo, que
reciba los parmetros X e Y.

Adems, existe una peculiaridad curiosa: podemos crear un array de "Animales",


que realmente contenga objetos de distintas subclases (en este caso, unos de
ellos sern perros, otros gatos, etc.),

Animal[] misAnimales = new Animal[3];

misAnimales[0] = new Perro();


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

Un ejemplo ms detallado:

// Ejemplo_07_02b.cs
Revisin 0.99zz Pgina 222
Introduccin a la programacin con C#, por Nacho Cabanes

// Array de objetos de distintas clases


// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_02b


{
public static void Main()
{
Animal[] misAnimales = new Animal[8];

misAnimales[0] = new Perro();


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

for (byte i=3; i<7; i++)


misAnimales[i] = new Perro();

misAnimales[7] = new Animal();


}
}

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

public class Animal


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

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

public class Perro: Animal


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

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

public class Gato: Animal


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

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

public class GatoSiames: Gato


{
public GatoSiames()
{
Revisin 0.99zz Pgina 223
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("Ha nacido un gato siams");


}
}

La salida de este programa sera:

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 siams

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

Si los objetos tienen otros mtodos, no slo el constructor, no todo funcionar a la


primera. Pronto veremos los posibles problemas y la forma de solucionarlos.

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) Ampla el proyecto Libro (ejercicio 7.2.2), de modo que permita guardar
1000 documentos de cualquier tipo. A la hora de aadir un documento, se
preguntar al usuario si desea guardar un documento genrico o un libro, para
usar el constructor adecuado.
(7.2.6) Ampla 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 imgenes no se muestran
Revisin 0.99zz Pgina 224
Introduccin a la programacin con C#, por Nacho Cabanes

correctamente en pantalla al lanzar una partida, no te preocupes, lo


solucionaremos en el siguiente apartado.

7.3. Funciones virtuales. La palabra "override"


En el ejemplo anterior hemos visto cmo crear un array de objetos, usando slo la
clase base en el momento de definirlo, pero insertando realmente objetos de cada
una de las clases derivadas, y hemos visto que los constructores se llaman
correctamente... pero con los mtodos podemos encontrar problemas.

Vamos a verlo con un ejemplo que, en vez de tener constructores, va a tener un


nico mtodo "Hablar", que se redefinir en cada una de las clases hijas, y
despus comentaremos qu ocurre al ejecutarlo y cmo solucionarlo:

// Ejemplo_07_03a.cs
// Array de objetos, sin "virtual"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_03a


{
public static void Main()
{
// Primero creamos un animal de cada tipo
Perro miPerro = new Perro();
Gato miGato = new Gato();
Animal miAnimal = new Animal();

miPerro.Hablar();
miGato.Hablar();
miAnimal.Hablar();

// Linea en blanco, por legibilidad


Console.WriteLine();

// Ahora los creamos desde un array


Animal[] misAnimales = new Animal[3];

misAnimales[0] = new Perro();


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

misAnimales[0].Hablar();
misAnimales[1].Hablar();
misAnimales[2].Hablar();
}
}

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

public class Animal


{

Revisin 0.99zz Pgina 225


Introduccin a la programacin con C#, por Nacho Cabanes

public void Hablar()


{
Console.WriteLine("Estoy comunicndome...");
}
}

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

public class Perro: Animal


{
public new void Hablar()
{
Console.WriteLine("Guau!");
}
}

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

public class Gato: Animal


{

public new void Hablar()


{
Console.WriteLine("Miauuu");
}
}

(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 "ests redifiniendo este mtodo; aade la palabra new para indicar que
eso es lo que deseas". Equivale a un "s, s que estoy redefiniendo este mtodo").

La salida de este programa es:

Guau!
Miauuu
Estoy comunicndome...

Estoy comunicndome...
Estoy comunicndome...
Estoy comunicndome...

La primera parte era de esperar: si creamos un perro, debera decir "Guau", un


gato debera decir "Miau" y un animal genrico debera comunicarse. Eso es lo que
se consigue con este fragmento:

Perro miPerro = new Perro();


Gato miGato = new Gato();
Animal miAnimal = new Animal();

miPerro.Hablar();
miGato.Hablar();
miAnimal.Hablar();
Revisin 0.99zz Pgina 226
Introduccin a la programacin con C#, por Nacho Cabanes

En cambio, si creamos un array de animales, no se comporta correctamente, a


pesar de que despus digamos que el primer elemento del array es un perro, el
segundo es un gato y el tercero es un animal genrico:

Animal[] misAnimales = new Animal[3];

misAnimales[0] = new Perro();


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

misAnimales[0].Hablar();
misAnimales[1].Hablar();
misAnimales[2].Hablar();

Es decir, como la clase base es "Animal", y el array es de "animales", cada elemento


hace lo que corresponde a un Animal genrico (dice "Estoy comunicndome"), a
pesar de que hayamos indicado que se trata de un Perro u otra subclase distinta.

Generalmente, no ser esto lo que queramos. Sera interesante no necesitar crear


un array de perros y otros de gatos, sino poder crear un array de animales, y que
pudiera contener distintos tipos de animales.

Para conseguir este comportamiento, debemos indicar a nuestro compilador que


el mtodo "Hablar" que se usa en la clase Animal quiz sea redefinido por otras
clases hijas, y que, en ese caso, debe prevalecer lo que indiquen las clases hijas.

La forma de conseguirlo es declarar ese mtodo "Hablar" como "virtual" (para


indicar al compilador que ese mtodo probablemente ser redefinido en las
subclases), y emplear en las clases hijas la palabra "override" en vez de "new" (para
dejar claro que debe reemplazar al mtodo "virtual" original), as:

// Ejemplo_07_03b.cs
// Array de objetos, con "virtual"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_03b


{
public static void Main()
{
// Primero creamos un animal de cada tipo
Perro miPerro = new Perro();
Gato miGato = new Gato();
Animal miAnimal = new Animal();

Revisin 0.99zz Pgina 227


Introduccin a la programacin con C#, por Nacho Cabanes

miPerro.Hablar();
miGato.Hablar();
miAnimal.Hablar();

// Linea en blanco, por legibilidad


Console.WriteLine();

// Ahora los creamos desde un array


Animal[] misAnimales = new Animal[3];

misAnimales[0] = new Perro();


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

misAnimales[0].Hablar();
misAnimales[1].Hablar();
misAnimales[2].Hablar();
}
}

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

public class Animal


{
public virtual void Hablar()
{
Console.WriteLine("Estoy comunicndome...");
}
}

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

public class Perro: Animal


{
public override void Hablar()
{
Console.WriteLine("Guau!");
}
}

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

public class Gato: Animal


{

public override void Hablar()


{
Console.WriteLine("Miauuu");
}
}

El resultado de este programa ya s es el que posiblemente desebamos: tenemos


un array de animales, pero cada uno "Habla" como corresponde a su especie:

Guau!
Miauuu
Estoy comunicndome...

Revisin 0.99zz Pgina 228


Introduccin a la programacin con C#, por Nacho Cabanes

Guau!
Miauuu
Estoy comunicndome...

Nota: Esto que nos acaba de ocurrir va a ser frecuente. Si el compilador nos avisa
de que deberamos aadir 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 ms adelante).

Ejercicios propuestos:
(7.3.1) Crea una versin 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 mtodo "Saludar" que se redefina en todas las clases hijas,
usando "new" y probndolo desde "Main".
(7.3.3) Crea una variante del ejercicio anterior (7.3.2), que use "override" en vez de
"new".
(7.3.4) Ampla el proyecto Libro (ejercicio 7.2.5): tanto la clase Documento como la
clase Libro, tendrn un mtodo ToString, que devuelva una cadena de texto
formada por ttulo, autor y ubicacin, separados por guiones. Crea una clase
Articulo, que aada el campo "procedencia". El cuerpo del programa permitir
aadir Artculos o Libros, no documentos genricos. El mtodo ToString deber
mostrar tambin el nmero de pginas de un libro y la procedencia de un artculo.
La opcin de mostrar datos llamar a los correspondientes mtodos ToString.
Recuerda usar "virtual" y "override" si en un primer momento no se comporta
como debe.
(7.3.5) Ampla el esqueleto de ConsoleInvaders (7.2.6) para que muestre las
imgenes correctas de los enemigos, usando "virtual" y "override". Adems, 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.

7.4. Llamando a un mtodo de la clase "padre"


Puede ocurrir que en un mtodo de una clase hija no nos interese redefinir por
completo las posibilidades del mtodo equivalente de la superclase, sino
ampliarlas. En ese caso, no hace falta que volvamos a teclear todo lo que haca el
Revisin 0.99zz Pgina 229
Introduccin a la programacin con C#, por Nacho Cabanes

mtodo de la clase base, sino que podemos llamarlo directamente, precedindolo


de la palabra "base".

Por ejemplo, podemos hacer que un Gato Siams hable igual que un Gato normal,
pero diciendo "Pfff" despus, as:

public new void Hablar()


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

Este podra ser un fuente completo:

// Ejemplo_07_04a.cs
// Ejemplo de clases: Llamar a la superclase
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_04a


{

public static void Main()


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

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

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

public class Animal


{
// Todos los detalles estn en las subclases
}

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

public class Gato: Animal


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

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

public class GatoSiames: Gato


Revisin 0.99zz Pgina 230
Introduccin a la programacin con C#, por Nacho Cabanes

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

Su resultado sera

Miauuu

Miauuu
Pfff

Tambin podemos llamar a un constructor de la clase base desde un constructor


de una clase derivada. Por ejemplo, si tenemos una clase "RectanguloRelleno" que
hereda de otra clase "Rectangulo" y queremos que el constructor de
"RectanguloRelleno" que recibe las coordenadas "x" e "y" se base en el constructor
equivalente de la clase "Rectangulo", lo haramos as:

public RectanguloRelleno (int x, int y )


: base (x, y)
{
// Pasos adicionales
// que no da un rectangulo "normal"
}

(Si no hacemos esto, el constructor de RectanguloRelleno se basara en el


constructor sin parmetros de Rectangulo, no en el que tiene x e y como
parmetros).

Ejercicios propuestos:
(7.4.1) Crea una versin ampliada del ejercicio 7.3.3, en la que el mtodo "Hablar"
de todas las clases hijas se apoye en el de la clase "Trabajador".
(7.4.2) Crea una versin 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 mtodo ToString de la
clase Libro se apoye en el de la clase Documento, y tambin lo haga el de la clase
Articulo.
(7.4.4) Ampla 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.

Revisin 0.99zz Pgina 231


Introduccin a la programacin con C#, por Nacho Cabanes

7.5. La palabra "this": el objeto actual


Podemos hacer referencia al objeto que estamos usando, con "this". Un primer
uso es dar valor a los atributos, incluso si los parmetros tienen el mismo nombre
que stos:

public void MoverA (int x, int y)


{
this.x = x;
this.y = y;
}

Un fuente completo sera as:

// Ejemplo_07_05a.cs
// Primer ejemplo de uso de "this": parmetros
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_05a


{
public static void Main()
{
Titulo t = new Titulo(38,5,"Hola");
t.Escribir();
}
}

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

public class Titulo


{
private int x;
private int y;
private string texto;

public Titulo(int x, int y, string texto)


{
this.x = x;
this.y = y;
this.texto = texto;
}

public void Escribir()


{
Console.SetCursorPosition(x,y);
Console.WriteLine(texto);
}
}

En muchos casos, podemos evitar este uso de "this". Por ejemplo, normalmente es
preferible usar otro nombre en los parmetros:

Revisin 0.99zz Pgina 232


Introduccin a la programacin con C#, por Nacho Cabanes

public void MoverA (int nuevaX, int nuevaY)


{
this.x = nuevaX;
this.y = nuevaY;
}

Y en ese caso se puede (y se suele) omitir "this":

public void MoverA (int nuevaX, int nuevaY)


{
x = nuevaX;
y = nuevaY;
}

Pero "this" tiene tambin otros usos. Por ejemplo, podemos crear un constructor
a partir de otro que tenga distintos parmetros:

public RectanguloRelleno (int x, int y ) : this (x)


{
fila = y;
// Pasos adicionales
}

En ese ejemplo, existira un constructor de RectanguloRelleno que recibiera slo la


coordenada X (y que podra prefijar la Y al valor 0, por ejemplo), y tambin existe
este otro constructor, que recibe X e Y, y que se basa en el anterior para fijar el
valor de X.

Nuevamente, podramos hacer un fuente completo de ejemplo:

// Ejemplo_07_05b.cs
// Segundo ejemplo de uso de "this": constructores
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_05b


{
public static void Main()
{
Titulo t = new Titulo(38,5,"Hola");
t.Escribir();
}
}

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

public class Titulo


{
Revisin 0.99zz Pgina 233
Introduccin a la programacin con C#, por Nacho Cabanes

private int x;
private int y;
private string texto;

public Titulo(int nuevaX, int nuevaY, string txt)


{
x = nuevaX;
y = nuevaY;
texto = txt;
}

public Titulo(int nuevaY, string txt)


: this (40-txt.Length/2, nuevaY, txt)
{
}

public void Escribir()


{
Console.SetCursorPosition(x,y);
Console.WriteLine(texto);
}
}

La palabra "this" se usa mucho tambin para que unos objetos "conozcan" a los
otros. Por ejemplo, en un juego de 2 jugadores, podramos tener una clase
Jugador, heredar de ella las clases Jugador1 y Jugador2, que sern muy parecidas
entre s, y nos podramos 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
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_05c


{
public static void Main()
{
Jugador1 j1 = new Jugador1();
Jugador2 j2 = new Jugador2();
}
}

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

public class Jugador


{
protected Jugador adversario;
}

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

public class Jugador1 : Jugador


Revisin 0.99zz Pgina 234
Introduccin a la programacin con C#, por Nacho Cabanes

{
public Jugador1()
{
adversario = new Jugador2();
}
}

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

public class Jugador2 : Jugador


{
public Jugador2()
{
adversario = new Jugador1();
}
}

Pero este programa se queda colgado un instante, hasta terminar finalmente


lanzando una excepcin de desbordamiento de pila (Stack Overflow), porque
estamos creando una recursividad indirecta sin fin: el jugador1 crea un jugador2,
ste crea un nuevo jugador1 (en vez de utilizar el ya existente), que a su vez crea
un nuevo jugador2 y as sucesivamente, hasta finalmente desbordar la zona de
memoria que est reservada para llamadas recursivas.

Una alternativa mucho menos peligrosa (pero que, a cambio, complica el


programa principal), es que sea el programa principal el que indique a cada
jugador quin es su adversario:

// Ejemplo_07_05d.cs
// Dos clases que se usan mutuamente: "Main" coordina
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_05d


{
public static void Main()
{
Jugador j1 = new Jugador();
Jugador j2 = new Jugador();
j1.SetAdversario(j2);
j2.SetAdversario(j1);
}
}

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

public class Jugador


{
protected Jugador adversario;

public void SetAdversario(Jugador nuevoAdversario)


{
adversario = nuevoAdversario;
Revisin 0.99zz Pgina 235
Introduccin a la programacin con C#, por Nacho Cabanes

}
}

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"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_07_05e


{
public static void Main()
{
Jugador j1 = new Jugador(); // Creado sin ms detalles
Jugador j2 = new Jugador(j1); // Indicando su adversario
}
}

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

public class Jugador


{
protected Jugador adversario;

public Jugador()
{
}

public Jugador(Jugador adversario)


{
// Mi adversario es el que me indican
SetAdversario( adversario );

// Y yo soy su adversario
adversario.SetAdversario( this );
}

public void SetAdversario(Jugador nuevoAdversario)


{
adversario = nuevoAdversario;
}
}

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
Revisin 0.99zz Pgina 236
Introduccin a la programacin 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 versin ampliada del ejercicio 7.4.2, en la que el constructor sin
parmetros de la clase "Trabajador" se apoye en otro constructor que reciba como
parmetro el nombre de esa persona. La versin sin parmetros 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 mtodo "MostrarEstado"
que muestre su ancho y su alto. Crea una clase Casa, que contenga 3 puertas y
otro mtodo "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 mtodo
"MostrarEstado" que escriba su superficie. Cada casa debe contener 3 puertas. Las
puertas tendrn un ancho, un alto y un mtodo "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) Ampla el esqueleto de ConsoleInvaders (7.4.4), de modo que el constructor
sin parmetros de la clase Nave se apoye en el constructor con parmetros de la
misma clase, prefijando unas coordenadas que te parezcan las ms adecuadas.

7.6. Sobrecarga de operadores


Los "operadores" son los smbolos que se emplean para indicar ciertas
operaciones. Por ejemplo, el operador "+" se usa para indicar que queremos
sumar dos nmeros.

Pues bien, en C# se puede "sobrecargar" operadores, es decir, redefinir su


significado, para poder sumar (por ejemplo) objetos que nosotros hayamos
creado, de forma ms cmoda y legible. As, para sumar dos matrices, en vez de
hacer algo como "matriz3 = suma( matriz1, matriz2 )" podramos hacer
simplemente " matriz3 = matriz1 + matriz2"

No entraremos mucho ms en detalle, pero la idea es que redefiniramos un


mtodo llamado "operador +", y que devolvera un dato esttico del tipo con el

Revisin 0.99zz Pgina 237


Introduccin a la programacin con C#, por Nacho Cabanes

que estamos (por ejemplo, una Matriz) y recibira dos datos de ese mismo tipo
como parmetros:

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


{
Matriz nuevaMatriz = new Matriz();

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


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

return nuevaMatriz;
}

Desde "Main", calcularamos una matriz como suma de otras dos haciendo
simplemente

Matriz matriz3 = matriz1 + matriz2;

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 estn entre los que no podremos cambiar. En su lugar, deberas crear
mtodos Get para acceder a una posicin y mtodos Set para cambiar el valor de
una posicin.

Ejercicios propuestos:
(7.6.1) Desarrolla una clase "Matriz", que represente a una matriz de 3x3, con
mtodos para indicar el valor que hay en una posicin, leer el valor de una
posicin, escribir la matriz en pantalla y sumar dos matrices. (Nota: en C# puedes
sobrecargar el operador "+", pero no el operador "[]", de modo que tendrs que
crear mtodos "get" y "set" para leer los valores de posiciones de la matriz y para
cambiar su valor).
(7.6.2) Si has estudiado los "nmeros complejos", crea una clase "Complejo", que
represente a un nmero complejo (formado por una parte "real" y una parte
"imaginaria"). Incluye un constructor que reciba ambos datos como parmetros.
Crea tambin mtodos "get" y "set" para leer y modificar los valores de dichos
datos, as como mtodos que permitan saber los valores de su mdulo y su
argumento. Crea un mtodo que permita sumar un complejo a otro, y redefine el
operador "+" como forma alternativa de realizar esa operacin.
(7.6.3) Crea una clase "Fraccion", que represente a una fraccin, formada por un
numerador y un denominador. Crea un constructor que reciba ambos datos como
parmetros y otro constructor que reciba slo el numerador. Crea un mtodo

Revisin 0.99zz Pgina 238


Introduccin a la programacin con C#, por Nacho Cabanes

Escribir, que escriba la fraccin 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. Proyectos completos propuestos


La mejor forma de aplicar todos los conocimientos sobre clases y de ponerlos a
prueba es crear algn proyecto de una cierta complejidad, que incluya diferentes
clases, herencia, arrays de objetos, comunicacin entre dichos objetos, etc. Aqu
tienes unos cuantos ejercicios propuestos, sin solucin (y para los que no existir
una solucin nica):

(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
podra ampliar para crear un juego de plataformas. Existir una pantalla de
bienvenida, una pantalla de crditos 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 podran ser "wasd" o las flechas del cursor.
Si quieres que el movimiento sea ms suave, puedes investigar sobre
Console.Readkey (que posiblemente habrs usado ya en el proyecto
ConsoleInvaders) como alternativa a Console.ReadLine.
(7.7.3) Crea un proyecto "Laberinto", a partir del proyecto "PersonajeMovil",
aadiendo tres enemigos que se muevan de lado a lado de forma autnoma y un
laberinto de fondo, cuyas paredes no se puedan "pisar" (la clase Laberinto puede
tener mtodos que informen sobre si el jugador podra 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", aadiendo
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 domtico para
automatizar ciertas funciones en una casa: apertura y cierre de ventanas y
puertas, encendido de calefaccin, etc. Adems de esos elementos fsicos de la
casa, tambin existir un panel de control, desde el que el usuario podra controlar
el resto de elementos, as como programar el comportamiento del sistema (por

Revisin 0.99zz Pgina 239


Introduccin a la programacin con C#, por Nacho Cabanes

ejemplo, subir una persiana inmediatamente o hacer que la calefaccin se


encienda todos los das desde las 19h hasta las 23h, a una cierta temperatura).
(7.7.6) Completa el proyecto Libro (ejercicio 7.4.3), para que las clases de datos
tengan un mtodo "Contiene", que reciba un texto y devuelva el valor booleano
"true" cuando ese texto est contenido en el ttulo o en el autor (y en la
procedencia, para los artculos). El Main deber permitir al usuario realizar
bsquedas, aprovechando esta nueva funcionalidad.
(7.7.7) Aade al proyecto Libro cualquier otra funcionalidad que te parezca
interesante, como la de modificar datos, borrarlos u ordenarlos.
(7.7.8) Crea en el proyecto Libro una clase ListaDeDocumentos, que oculte a Main
los detalles de que internamente se trata de un array: deber permitir opciones
como aadir un documento, obtener los datos de uno de ellos, buscar entre la
lista de todos ellos, etc. Los criterios de diseo quedan a tu criterio. Por ejemplo,
puedes hacer que la bsqueda devuelve un array con los nmeros de ficha
encontrados, o un array con el contenido de esas fichas, o el contenido la siguiente
ficha que cumpla con esos criterios.
(7.7.9) Ampla el esqueleto de ConsoleInvaders (7.5.4), con una clase
"BloqueDeEnemigos", que ser la que contenga el array de enemigos, de modo
que se simplifique la lgica de la clase Partida. Esta clase tendr un mtodo
Dibujar, que mostrar todo el array en pantalla, y un mtodo Mover, que mover
todo el bloque hacia la derecha y la izquierda de forma alternativa (debers
comprobar la posicin inicial del primer enemigo y la posicin final del ltimo
enemigo). En cada pasada por el bucle de juego debers llamar a Mover.
(7.7.10) Crea una clase Disparo en ConsoleInvaders. Cuando el usuario pulse cierta
tecla (Espacio, por ejemplo), aparecer un disparo encima de la nave, y se mover
hacia arriba hasta que desaparezca por la parte superior de la pantalla. Existir un
nico disparo, y no se podr volver a disparar si est activo (en pantalla).
Inicialmente estar desactivado, y lo volver a estar cuando llegue al margen de la
pantalla.
(7.7.11) En ConsoleInvaders, crea un mtodo "ColisionaCon" en la clase Sprite, que
reciba otro Sprite como parmetro y devuelva el valor booleano "true" si ambos
sprites estn "chocando" o "false" en caso contrario. Tendrs que pensar qu
relacin habr entre las coordenadas X e Y de ambos sprites para que "choquen".
(7.7.12) En ConsoleInvaders, crea una clase Ovni, con un nuevo tipo de Enemigo
que no estar siempre activo. Su mtodo Mover har que se mueva hacia la
derecha si ya est activo o, en caso contrario, que genere un nmero al azar para
decidir si debe activarse.
(7.7.13) En ConsoleInvaders, comprueba colisiones entre el disparo y el Ovni. Si
hay colisin, desaparecern ambos y el jugador obtendr 50 puntos.

Revisin 0.99zz Pgina 240


Introduccin a la programacin con C#, por Nacho Cabanes

(7.7.14) En ConsoleInvaders, comprueba colisiones entre el disparo y el bloque de


enemigos. Si el disparo toca algn enemigo, ambos desaparecern y el jugador
obtendr 10 puntos.
(7.7.15) En ConsoleInvaders, aade una clase "Marcador", que muestre la
puntuacin y la cantidad de vidas restantes (que por ahora, siempre ser 3).
(7.7.16) En ConsoleInvaders, aade la posibilidad de que algunos enemigos al azar
puedan disparar (los disparos de los enemigos "va hacia abajo"; piensa si crear
una nueva clase o ampliar las funcionalidades de la clase Disparo que ya tienes).
Ajusta la frecuencia de disparos, de modo que el juego contine siendo jugable. Si
uno de esos disparos impacta con la nave, se perder una vida y el disparo
desaparecer. Si se pierden las 3 vidas, acaba la partida y se volver a la pantalla
de presentacin.
(7.7.17) En ConsoleInvaders, crea la estructura que sea necesaria para almacenar
las "mejores puntuaciones", que se actualizarn al terminar cada partida y se
mostrarn en la pantalla de bienvenida.
(7.7.18) En ConsoleInvaders, aade las "torres defensivas", que protegen al
jugador y que se van rompiendo poco a poco cada vez que un disparo impacta con
ellas.

Revisin 0.99zz Pgina 241


Introduccin a la programacin con C#, por Nacho Cabanes

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 adems


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 slo 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
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO; // Para StreamWriter

public class Ejemplo_08_01a


{
public static void Main()
{
StreamWriter fichero;

fichero = File.CreateText("prueba.txt");
fichero.WriteLine("Esto es una lnea");
fichero.Write("Esto es otra");
fichero.WriteLine(" y esto es continuacin de la anterior");
fichero.Close();
}
}

Hay varias cosas que comentar sobre este programa:

StreamWriter es la clase que representa a un fichero de texto en el que


podemos escribir informacin.
El fichero de texto lo creamos con el mtodo (esttico) CreateText, que
pertenece a la clase File.

Revisin 0.99zz Pgina 242


Introduccin a la programacin con C#, por Nacho Cabanes

Para escribir en el fichero, empleamos Write y WriteLine, al igual que


hacemos en la consola.
Finalmente, debemos cerrar el fichero con Close, o podra quedar algn
dato sin guardar.

Una forma alternativa de crear el fichero es no usar "File.CreateText", sino un


constructor que reciba el nombre de fichero, as:

// Ejemplo_08_01b.cs
// Escritura en un fichero de texto, con constructor
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO; // Para StreamWriter

public class Ejemplo_08_01b


{
public static void Main()
{
StreamWriter fichero = new StreamWriter("prueba.txt");
fichero.WriteLine("Esto es una lnea");
fichero.Write("Esto es otra");
fichero.WriteLine(" y esto es continuacin de la anterior");
fichero.Close();
}
}

Existe otra sintaxis, ms 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"
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO; // Para StreamWriter

public class Ejemplo_08_01c


{
public static void Main()
{
using (StreamWriter fichero = new StreamWriter("prueba.txt"))
{
fichero.WriteLine("Esto es una lnea");
fichero.Write("Esto es otra");
fichero.WriteLine(" y esto es continuacin de la anterior");
}
}
}

Revisin 0.99zz Pgina 243


Introduccin a la programacin con C#, por Nacho Cabanes

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 versin 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 ejecucin 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 ejecucin del programa.

8.2. Lectura de un fichero de texto


La estructura de un programa que leyera de un fichero de texto sera parecida a:

// Ejemplo_08_02a.cs
// Lectura de un fichero de texto
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO; // Para StreamReader

public class Ejemplo_08_02a


{
public static void Main()
{
StreamReader fichero;
string linea;

fichero = File.OpenText("prueba.txt");
linea = fichero.ReadLine();
Console.WriteLine( linea );
Console.WriteLine( fichero.ReadLine() );
fichero.Close();
}
}

Las diferencias son:


Para leer de un fichero no usaremos StreamWriter, sino StreamReader.
Si queremos abrir un fichero que ya existe, usaremos OpenText, en lugar de
CreateText.
Para leer del fichero, usaramos ReadLine, como hacamos en la consola.
Nuevamente, deberemos cerrar el fichero al terminar de usarlo.

Nuevamente, podemos usar un constructor en vez de OpenText:

// Ejemplo_08_02b.cs
// Lectura de un fichero de texto, con constructor
Revisin 0.99zz Pgina 244
Introduccin a la programacin con C#, por Nacho Cabanes

// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO; // Para StreamReader

public class Ejemplo_08_02b


{
public static void Main()
{
StreamReader fichero = new StreamReader("prueba.txt");
string linea = fichero.ReadLine();
Console.WriteLine( linea );
Console.WriteLine( fichero.ReadLine() );
fichero.Close();
}
}

o bien utilizar la sintaxis alternativa, con la palabra "using":

// Ejemplo_08_02c.cs
// Lectura de un fichero de texto, con "using"
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO; // Para StreamReader

public class Ejemplo_08_02c


{
public static void Main()
{
using (StreamReader fichero = new StreamReader("prueba.txt"))
{
string linea = fichero.ReadLine();
Console.WriteLine( linea );
Console.WriteLine( fichero.ReadLine() );
}
}
}

Ejercicios propuestos:
(8.2.1) Crea un programa que lea las tres primeras lneas del fichero creado en el
ejercicio 8.1.1 y las muestre en pantalla. Usa OpenText para abrirlo.
(8.2.2) Crea una versin alternativa del ejercicio 8.2.1, usando el constructor de
StreamReader.
(8.2.3) Crea una versin alternativa del ejercicio 8.2.2, empleando la sintaxis
alternativa de "using".

8.3. Lectura hasta el final del fichero


Normalmente no querremos leer slo una frase del fichero, sino procesar todo su
contenido, de principio a fin.

Revisin 0.99zz Pgina 245


Introduccin a la programacin con C#, por Nacho Cabanes

En un fichero de texto, la forma de saber si hemos llegado al final es intentar leer


una lnea, y comprobar si el resultado ha sido "null", lo que nos indicara que no se
ha podido leer y que, por tanto estamos en el final del fichero.

Habitualmente, si queremos procesar todo un fichero, esta lectura y


comprobacin debera ser repetitiva, as:

// Ejemplo_08_03a.cs
// Lectura de un fichero de texto completo
// Introduccin 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();
}
}

Por supuesto, podras emplear el constructor o "using" para acceder al fichero.


Esos cambios sern parte de los ejercicios propuestos.

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 despus de cada 24
lneas, para que d tiempo a leerlo. Cuando el usuario pulse la tecla Intro, se
mostrarn las siguientes 24 lneas, y as sucesivamente hasta que termine el
fichero.
(8.3.4) Ampla 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

Revisin 0.99zz Pgina 246


Introduccin a la programacin con C#, por Nacho Cabanes

todava no existan datos en fichero; dentro de poco veremos formas alternativas


de saber si existe).
(8.3.5) Amplia el proyecto Libro (ejercicio 8.1.3), de modo que los datos se lean
desde fichero (si existe) en el momento de poner el programa en marcha.
(8.3.6) Crea un programa que pida al usuario el nombre de un fichero de texto y
una frase a buscar, y que muestre en pantalla todas las lneas de ese fichero que
contengan esa frase. Cada frase se debe preceder del nmero de lnea (la primera
lnea del fichero ser la 1, la segunda ser la 2, y as sucesivamente).

8.4. Aadir a un fichero existente


La idea es sencilla: en vez de abrirlo con CreateText ("crear texto"), usaremos
AppendText ("aadir texto"). Por ejemplo, podramos crear un fichero, guardar
informacin, cerrarlo, y luego volverlo a abrir para aadir datos, de la siguiente
forma:

// Ejemplo_08_04a.cs
// Aadir a un fichero de texto
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_04a


{
public static void Main()
{
StreamWriter fichero;

fichero = File.CreateText("prueba2.txt");
fichero.WriteLine("Primera lnea");
fichero.Close();

fichero = File.AppendText("prueba2.txt");
fichero.WriteLine("Segunda lnea");
fichero.Close();
}
}

Tambin podemos usar un constructor para aadir datos a un fichero existente,


pero entonces tendremos que emplear un segundo parmetro booleano, que ser
"true" para aadir (o "false" para sobreescribir el fichero, si ya existiera):

// Ejemplo_08_04b.cs
// Aadir a un fichero de texto, con constructor y "using"
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

Revisin 0.99zz Pgina 247


Introduccin a la programacin con C#, por Nacho Cabanes

public class Ejemplo_08_04b


{
public static void Main()
{
using (StreamWriter fichero = new StreamWriter("prueba2.txt", false))
{
fichero.WriteLine("Primera lnea");
}

using (StreamWriter fichero = new StreamWriter("prueba2.txt", true))


{
fichero.WriteLine("Segunda lnea");
}
}
}

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
aadir al final de su contenido). Cada sesin acabar cuando el usuario pulse Intro
sin teclear nada.

8.5. Ficheros en otras carpetas


Si un fichero al que queremos acceder se encuentra en otra carpeta, basta con que
el nombre de fichero incluya tambin la ruta.

Debemos recordar que, como la barra invertida que se usa en sistemas Windows
para separar los nombres de los directorios, coincide con el carcter de control
que se usa en las cadenas de C y los lenguajes que derivan de l, deberemos
escribir dichas barras invertidas repetidas, as:

string nombreFichero = "d:\\ejemplos\\ejemplo1.txt"; // Ruta absoluta


string nombreFichero2 = "..\\datos\\configuracion.txt"; // Ruta relativa

Como esta sintaxis puede llegar a resultar incmoda, 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:

string nombreFichero = @"d:\ejemplos\ejemplo1.txt";

Ejercicios propuestos:
(8.5.1) Crea un programa que pida al usuario pares de nmeros 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

Revisin 0.99zz Pgina 248


Introduccin a la programacin con C#, por Nacho Cabanes

que se ejecute el programa, deber aadir los nuevos resultados a continuacin


de los resultados de las ejecuciones anteriores.

8.6. Saber si un fichero existe


Hasta ahora, estamos intentando abrir ficheros para lectura, pero sin comprobar
realmente si el fichero existe o no, lo que puede suponer que nuestro programa
falle en caso de que el fichero no se encuentre donde nosotros esperamos o de
que introduzcamos un nombre incorrecto.

Una primera solucin es usar "File.Exists(nombre)", para comprobar si est, antes


de intentar abrirlo:

// Ejemplo_08_06a.cs
// Saber si un fichero existe
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_06a


{
public static void Main()
{
StreamReader fichero;
string nombre;

while (true) // Interrumpimos desde dentro con "break"


{
Console.Write( "Nombre del fichero (\"fin\" para terminar)? ");
nombre = Console.ReadLine();
if (nombre == "fin")
break;
if ( File.Exists(nombre) )
{
fichero = File.OpenText( nombre );
Console.WriteLine("Su primera linea es: {0}",
fichero.ReadLine() );
fichero.Close();
}
else
Console.WriteLine( "No existe!" );
}
}
}

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.

Revisin 0.99zz Pgina 249


Introduccin a la programacin con C#, por Nacho Cabanes

Otra forma de comprobar si un fichero existe o no es usar "excepciones", con las


que ya nos habamos encontrado en el tema 3 y que veremos con ms detalle en
el prximo apartado.

8.7. Ms comprobaciones de errores: excepciones


El uso de "File.Exists" nos permite saber si el fichero existe, pero ese no es el nico
problema que podemos tener al acceder a un fichero. Puede ocurrir que no
tengamos permiso para acceder al fichero, a pesar de que exista, o que
intentemos escribir en un dispositivo que sea slo de lectura (como un CD-ROM,
por ejemplo).

Por ello, una forma ms eficaz de comprobar si ha existido algn tipo de error es
comprobar las posibles "excepciones", con las que ya tuvimos un contacto al final
del tema 2.

Tpicamente, los pasos que puedan ser problemticos irn dentro del bloque "try"
y los mensajes de error y/o acciones correctoras estarn 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, podra ser:

// Ejemplo_08_07a.cs
// Excepciones y ficheros (1)
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_07a


{
public static void Main()
{
StreamReader fichero;
string nombre;
string linea;

Console.WriteLine("Introduzca el nombre del fichero");


nombre = Console.ReadLine();

try
{
fichero = File.OpenText(nombre);
do
{
linea = fichero.ReadLine();
if (linea != null)
Console.WriteLine( linea );
Revisin 0.99zz Pgina 250
Introduccin a la programacin con C#, por Nacho Cabanes

}
while (linea != null);

fichero.Close();
}
catch (Exception exp)
{
Console.WriteLine("Ha habido un error: {0}", exp.Message);
return;
}
}
}

El resultado, si ese fichero no existe, sera algo como

Introduzca el nombre del fichero


prueba
Ha habido un error: No se pudo encontrar el archivo
'C:\Fuentes\nacho\prueba'.

Pero, como ya vimos, generalmente lo razonable no es interceptar "todas las


excepciones a la vez", sino crear un anlisis para cada caso, que permita
recuperarse del error y seguir adelante, para lo que se suelen crear varios bloques
"catch". Por ejemplo, en el caso de que queramos crear un fichero, podemos tener
excepciones como stas:

El fichero existe y es de slo lectura (se lanzar una excepcin del tipo
"IOException").
La ruta del fichero es demasiado larga (excepcin de tipo
"PathTooLongException").
El disco puede estar lleno (IOException).

As, dentro de cada bloque "catch" podramos indicar una excepcin ms concreta
que una simple "Exception", de forma que el mensaje de aviso sea ms concreto, o
que podamos dar pasos ms adecuados para solucionar el problema:

// Ejemplo_08_07b.cs
// Excepciones y ficheros (2)
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_07b


{
public static void Main()
{
StreamWriter fichero;
string nombre;
Revisin 0.99zz Pgina 251
Introduccin a la programacin con C#, por Nacho Cabanes

string linea;

Console.Write("Introduzca el nombre del fichero: ");


nombre = Console.ReadLine();
Console.Write("Introduzca la frase a guardar: ");
linea = Console.ReadLine();

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 ms generales (IOException y, sobre todo, Exception) debern ser


las ltimas que analicemos, y las ms especficas debern 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
"trycatch" 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 nmero, 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 maysculas. 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 nmero, una operacin (+, -, *, /) y un
segundo nmero, y muestre el resultado de la correspondiente operacin. Si se
teclea un dato no numrico, el programa deber mostrar un aviso y volver a
pedirlo, en vez de interrumpir la ejecucin.
(8.7.3) Un programa que pida al usuario repetidamente pares de nmeros y la
operacin a realizar con ellos (+, -, *, /) y guarde en un fichero "calculadora.txt" el
resultado de dichos clculos (con la forma "15 * 6 = 90"). Debe controlar los

Revisin 0.99zz Pgina 252


Introduccin a la programacin con C#, por Nacho Cabanes

posibles errores, como que los datos no sean numricos, la divisin entre cero, o
que el fichero no se haya podido crear.

8.8. Conceptos bsicos sobre ficheros


Llega el momento de concretar algunos conceptos que hemos pasado por encima,
y que es necesario conocer:
Fichero fsico. Es un fichero que existe realmente en el disco, como
"agenda.txt".
Fichero lgico. Es un identificador que aparece dentro de nuestro
programa, y que permite acceder a un fichero fsico. Por ejemplo, si
declaramos "StreamReader fichero1", esa variable "fichero1" representa un
fichero lgico.
Equivalencia entre fichero lgico y fichero fsico. No necesariamente tiene
por qu existir una correspondencia "1 a 1": puede que accedamos a un
fichero fsico mediante dos o ms ficheros lgicos (por ejemplo, un
StreamReader para leer de l y un StreamWriter para escribir en l), y
tambin podemos usar un nico fichero lgico para acceder a distintos
ficheros fsicos (por ejemplo, los mapas de los niveles de un juego, que
estn guardados en distintos ficheros fsicos, pero los abramos y los leamos
utilizando siempre una misma variable).
Registro: Un bloque de datos que forma un "todo", como el conjunto de los
datos de una persona: nombre, direccin, e-mail, telfono, etc. Cada uno
de esos "apartados" de un registro se conoce como "campo".
Acceso secuencial: Cuando debemos "recorrer" todo el contenido de un
fichero si queremos llegar hasta cierto punto (como ocurre con las cintas de
video o de cassette). Es lo que estamos haciendo hasta ahora en los
ficheros de texto.
Acceso aleatorio: Cuando podemos saltar hasta cualquier posicin del
fichero directamente, sin necesidad de recorrer todo lo anterior. Es algo
que comenzaremos a hacer pronto, en los ficheros binarios (en un fichero
de texto en general no podremos hacerlo, porque no sabremos el tamao
de cada lnea de texto, as que no podremos saltar hasta una cierta lnea).

8.9. Leer un byte de un fichero binario


Los ficheros de texto son habituales, pero es an ms frecuente encontrarnos con
ficheros en los que la informacin est estructurada como una secuencia de bytes,
ms o menos ordenada. Esto ocurre en las imgenes, los ficheros de sonido, de
video, etc.

Revisin 0.99zz Pgina 253


Introduccin a la programacin con C#, por Nacho Cabanes

Vamos a ver cmo leer de un fichero "general", y lo aplicaremos a descifrar la


informacin almacenada en ciertos formatos habituales, como una imagen BMP o
un fichero de sonido MP3.

Como primer acercamiento, vamos a ver cmo abrir un fichero (no


necesariamente de texto) y leer el primer byte que contiene. Comenzaremos por
utilizar la clase FileStream, diseada para acceder a un byte o a bloques de bytes.
Esta clase tiene un mtodo ReadByte, que devuelve un entero con valor -1 en caso
de error, o un dato que podramos convertir a byte si la lectura ha sido correcta,
as:

// Ejemplo_08_09a.cs
// Ficheros binarios: lectura de un byte con FileStream
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_09a


{
public static void Main()
{
FileStream fichero;
string nombre;
byte unDato;

Console.WriteLine("Introduzca el nombre del fichero");


nombre = Console.ReadLine();

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

Podemos usar tambin un constructor para acceder al fichero. Este constructor


recibir dos parmetros: el nombre del fichero y el modo de apertura. Por ahora,
nos interesar un nico modo, que ser "FileMode.Open", que nos permite leer
datos desde un fichero que ya existe:

Revisin 0.99zz Pgina 254


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_08_09b.cs
// Ficheros binarios: lectura de un byte con FileStream, constructor
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_09b


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();

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

Y tambin podemos emplear la sintaxis alternativa, con "using", de modo que no


sea necesario cerrar el fichero de forma explcita:

// Ejemplo_08_09c.cs
// Ficheros binarios: lectura de un byte con FileStream, using
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_09c


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();

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)
Revisin 0.99zz Pgina 255
Introduccin a la programacin con C#, por Nacho Cabanes

{
Console.WriteLine("Error: "+e.Message);
return;
}
}
}

Los otros modos de apertura posibles (cuyos nombre en ingls son casi
autoexplicativos) son: Append (aade al final o crea si no existe), Create (crea el
fichero o lo sobreescribe si ya exista), CreateNew (crea si no exista, 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 vaca).

Si quisiramos leer un segundo dato, volveramos a llamar a ReadByte, porque


tras cada lectura, avanza la posicin actual dentro del fichero.

Ejercicios propuestos:
(8.9.1) Abre un fichero con extensin 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", segn el estndar
que marca el cdigo 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.

8.10. Leer hasta el final de un fichero binario


Tenemos dos formas de leer byte a byte todos datos que forma un FileStream. La
primera es descubrir el tamao del fichero, que podemos obtener con ".Length".
A partir de entonces, podramos usar un "for" para recorrer todo el fichero, as:

// Ejemplo_08_10a.cs
// Ficheros binarios: lectura completa de un FileStream
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_10a


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
Revisin 0.99zz Pgina 256
Introduccin a la programacin con C#, por Nacho Cabanes

string nombre = Console.ReadLine();


FileStream fichero = File.OpenRead(nombre);
int count = 0;
for ( int pos = 0; pos < fichero.Length; pos++)
{
byte unDato = (byte) fichero.ReadByte();
char letra = Convert.ToChar( unDato );
if (letra == 'A')
count++;
}
Console.WriteLine("Tiene {0} letras A", count);
fichero.Close();
}
}

(Por simplicidad, este fuente no realiza ningn tipo de comprobacin de errores.


Lo mismo ocurrir en la mayora de fuentes de ejemplo de este tema, pero un
programa real s debera comprobar si el fichero existe y si se ha producido algn
error durante la lectura).

La segunda forma de leer hasta el final del fichero es comprobar si el dato ledo es
un -1. Cuando esto ocurra, ser que seal de que se ha llegado al final del fichero.
El fuente correspondiente podra ser:

// Ejemplo_08_10b.cs
// Ficheros binarios: lectura completa de un FileStream
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_10b


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();
FileStream fichero = File.OpenRead(nombre);
int count = 0;
int dato;
do
{
dato = fichero.ReadByte();
if (dato != -1)
{
char letra = Convert.ToChar( (byte) dato );
if (letra == 'A')
count++;
}
}
while (dato != -1);
Console.WriteLine("Tiene {0} letras A", count);
fichero.Close();
}
}
Revisin 0.99zz Pgina 257
Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(8.10.1) Crea un programa que compruebe si un fichero (cuyo nombre introducir
el usuario) contiene una cierta letra (tambin 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
alfabticos (cdigos 32 a 127, adems del 10 y el 13) que contenga un fichero
binario.

8.11. Leer bloques de datos de un fichero binario


Leer byte a byte puede ser cmodo, pero tambin es lento. Por eso, en la prctica
es ms habitual leer grandes bloques de datos. Tpicamente, esos datos se
guardarn como un array de bytes.

Para eso, dentro de la clase "FileStream" tenemos mtodo "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 posicin del array debe introducir los
datos (no la posicin 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
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_11a


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();

FileStream fichero = File.OpenRead(nombre);


byte[] datos = new byte[10];
int posicion = 0;
int cantidadALeer = 10;
int cantidadLeida = fichero.Read(datos, posicion, cantidadALeer);
Revisin 0.99zz Pgina 258
Introduccin a la programacin con C#, por Nacho Cabanes

if (cantidadLeida < 10)


Console.WriteLine("No se han podido leer todos los datos!");
else
{
Console.WriteLine("El primer byte leido es {0}", datos[0]);
Console.WriteLine("El tercero es {0}", datos[2]);
}
fichero.Close();
}
}

Ejercicios propuestos:
(8.11.1) Abre un fichero con extensin 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 posicin 30 (empezando a contar desde 0). Si ese
valor es un 0 (que es lo habitual), indicar que el fichero no est comprimido.
Debers leer toda la cabecera (los primeros 54 bytes) con una sola orden.

8.12. La posicin en el fichero


Cuando leemos byte a byte (y tambin a veces en el caso de leer todo un bloque),
habitualmente nos interesar situarnos antes en la posicin exacta en la que se
encuentra el dato que nos interesa leer. En un fichero binario, esto podemos
conseguirlo sin necesidad de leer todos los bytes anteriores, accediendo
directamente a la posicin que buscamos (acceso aleatorio).

Para ello, tenemos el mtodo "Seek". A este mtodo se le indican dos parmetros:
la posicin a la que queremos saltar, y el punto desde el que queremos que se
cuente esa posicin (desde el comienzo del fichero SeekOrigin.Begin-, desde la
posicin actual SeekOrigin.Current- o desde el final del fichero SeekOrigin.End-).
La posicin es un Int64, porque puede ser un nmero muy grande e incluso un
nmero negativo (si miramos desde el final del fichero, porque desde l habr que
retroceder).

Revisin 0.99zz Pgina 259


Introduccin a la programacin con C#, por Nacho Cabanes

De igual modo, podemos saber en qu posicin del fichero nos encontramos,


consultando la propiedad "Position" (y tambin sabemos que podemos obtener la
longitud del fichero, mirando su propiedad "Length").

// Ejemplo_08_12a.cs
// Ficheros binarios: posicin
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_12a


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();
FileStream fichero = File.OpenRead(nombre);
byte[] datos = new byte[10];
int posicion = 0;
int cantidadALeer = 10;
int cantidadLeida = fichero.Read(datos, posicion, cantidadALeer);

if (cantidadLeida < 10)


Console.WriteLine("No se han podido leer todos los datos!");
else
{
Console.WriteLine("El primer byte leido es {0}", datos[0]);
Console.WriteLine("El tercero es {0}", datos[2]);
}

if (fichero.Length > 30)


{
fichero.Seek(19, SeekOrigin.Begin);
int nuevoDato = fichero.ReadByte();
Console.WriteLine("El byte 20 es un {0}", nuevoDato);

Console.WriteLine("La posicin actual es {0}",


fichero.Position);
Console.WriteLine("Y el tamao del fichero es {0}",
fichero.Length);
}

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 extensin 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 posicin 30 (empezando a contar desde 0). Si ese
Revisin 0.99zz Pgina 260
Introduccin a la programacin con C#, por Nacho Cabanes

valor es 0 (que es lo habitual), indicar que el fichero no est comprimido. Salta a


esa posicin directamente, sin leer toda la cabecera.

8.13. Leer datos nativos


Un FileStream es cmodo para leer byte a byte, pero no tanto cuando queremos
leer otros tipos de datos bsicos existentes en C# (short, int, float, etc.). Para eso
usaremos la clase "BinaryReader". Por ejemplo, podramos leer el entero corto que
forman los dos bytes iniciales de un fichero con:

// Ejemplo_08_13a.cs
// Ficheros binarios: lectura de un short, con BinaryReader
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_13a


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();

BinaryReader fichero = new BinaryReader(


File.Open(nombre, FileMode.Open));
short dato = fichero.ReadInt16();
Console.WriteLine("El dato leido es {0}", dato);
fichero.Close();
}
}

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 mayora de sistemas actuales, en primer lugar aparece el byte
menos significativo y luego el byte ms significativo, de modo que el dato sera
byte1 + byte2*256, as:

// Ejemplo_08_13b.cs
// Ficheros binarios: lectura de un short, byte a byte
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_13b


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();

Revisin 0.99zz Pgina 261


Introduccin a la programacin con C#, por Nacho Cabanes

BinaryReader fichero = new BinaryReader(


File.Open(nombre, FileMode.Open));
short dato1 = fichero.ReadByte();
short dato2 = fichero.ReadByte();
Console.WriteLine("El dato leido es {0}", dato1 + dato2*256);
fichero.Close();
}
}

Tambin podemos emplear la clusula "using", para que el fichero se cierre


automticamente, as:

// Ejemplo_08_13c.cs
// Ficheros binarios: lectura de un short, con BinaryReader, using
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_13c


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero");
string nombre = Console.ReadLine();

using (BinaryReader fichero = new BinaryReader(


File.Open(nombre, FileMode.Open)))
{
short dato = fichero.ReadInt16();
Console.WriteLine("El dato leido es {0}", dato);
}
}
}

Ejercicios propuestos:
(8.13.1) Abre un fichero con extensin 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.

Tambin podemos usar "Seek" para movernos a un punto u otro de un fichero si


usamos un "BinaryReader", pero est un poco ms escondido: no se lo pedimos
directamente a nuestro fichero, sino al "Stream" (flujo de datos) que hay por
debajo, a su "BaseStream", as: ficheroEntrada.BaseStream.Seek( 1,
SeekOrigin.Begin);

Revisin 0.99zz Pgina 262


Introduccin a la programacin con C#, por Nacho Cabanes

Por ejemplo, la anchura de una imagen en formato BMP es un entero de 32 bits


que se encuentra en la posicin 18, de modo que podramos hacer:

// Ejemplo_08_13d.cs
// Ficheros binarios: Seek, con BinaryReader
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_13d


{
public static void Main()
{
Console.WriteLine("Introduzca el nombre del fichero BMP");
string nombre = Console.ReadLine();

BinaryReader fichero = new BinaryReader(


File.Open(nombre, FileMode.Open));
fichero.BaseStream.Seek(18, SeekOrigin.Begin);
int ancho = fichero.ReadInt32();
Console.WriteLine("El ancho es {0}", ancho);
fichero.Close();
}
}

Ejercicios propuestos:
(8.13.3) El alto de un fichero BMP es un entero de 32 bits que se encuentra en la
posicin 22. Ampla el ejemplo 08_13d, para que muestre tambin el alto de ese
fichero BMP.

8.14. Ejemplo completo: leer informacin de un fichero


BMP
Hemos estado extrayendo algo de informacin de ficheros BMP. Ahora vamos a ir
un poco ms all y a tratar de obtener muchos de los detalles que hay en su
cabecera. El formato de dicha cabecera es el siguiente:

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
definirn la imagen.

En concreto, los datos que forman la cabecera de fichero y la cabecera de


bitmap son los siguientes:

TIPO DE INFORMACIN POSICIN EN EL ARCHIVO


Tipo de fichero (letras BM) 0-1
Tamao del archivo 2-5

Revisin 0.99zz Pgina 263


Introduccin a la programacin con C#, por Nacho Cabanes

Reservado 6-7
Reservado 8-9
Inicio de los datos de la imagen 10-13
Tamao de la cabecera de bitmap 14-17
Anchura (pxeles) 18-21
Altura (pxeles) 22-25
Nmero de planos 26-27
Tamao de cada punto 28-29
Compresin (0=no comprimido) 30-33
Tamao de la imagen 34-37
Resolucin horizontal 38-41
Resolucin vertical 42-45
Tamao de la tabla de color 46-49
Contador de colores importantes 50-53

Por tanto, ser fcil hacer que se nos muestren algunos detales, como su ancho,
su alto, la resolucin y si la imagen est comprimida o no:

// Ejemplo_08_14a.cs
// Informacin de un fichero BMP, con BinaryReader
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_14a


{
public static void Main()
{
Console.WriteLine("Informacin de imgenes BMP");
Console.WriteLine("Dime el nombre del fichero: ");
string nombre = Console.ReadLine();

if (! File.Exists( nombre) )
{
Console.WriteLine("No encontrado!");
}
else
{
BinaryReader fichero = new BinaryReader(
File.Open(nombre, FileMode.Open));

// Leo los dos primeros bytes


char marca1 = Convert.ToChar( fichero.ReadByte() );
char marca2 = Convert.ToChar( fichero.ReadByte() );

if ((marca1 !='B') || (marca2 !='M'))


Console.WriteLine("No parece un fichero BMP");
else
{
Console.WriteLine("Marca del fichero: {0}{1}",
marca1, marca2);
Revisin 0.99zz Pgina 264
Introduccin a la programacin con C#, por Nacho Cabanes

// Posicin 18: ancho


fichero.BaseStream.Seek(18, SeekOrigin.Begin);
int ancho = fichero.ReadInt32();
Console.WriteLine("Ancho: {0}", ancho);

// A continuacin: alto
int alto = fichero.ReadInt32();
Console.WriteLine("Alto: {0}", alto);

// Posicin 30: compresin


fichero.BaseStream.Seek(30, SeekOrigin.Begin);
int compresion = fichero.ReadInt32();
switch (compresion)
{
case 0: Console.WriteLine("Sin compresin");
break;
case 1: Console.WriteLine("Compresin RLE 8 bits");
break;
case 2: Console.WriteLine("Compresin RLE 4 bits");
break;
}

// 4 bytes despus: resolucin horizontal


fichero.BaseStream.Seek(4, SeekOrigin.Current);
int resolH = fichero.ReadInt32();
Console.WriteLine("Resolucin Horiz.: {0}", resolH);

// A continuacin: resolucin vertical


int resolV = fichero.ReadInt32();
Console.WriteLine("Resolucin Vertical: {0}", resolV);

fichero.Close();
}
}
}
}

Podemos hacerlo tambin con un FileStream, que simplificar ciertas operaciones,


como la lectura, que se puede hacer toda en un bloque, pero a cambio complicar
otras operaciones, como el clculo de los valores enteros, que habr que
componer a partir de los 4 bytes que los forman:

// Ejemplo_08_14b.cs
// Informacin de un fichero BMP, con FileStream
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_14b


{
public static void Main()
{
Console.WriteLine("Informacin de imgenes BMP");
Console.WriteLine("Dime el nombre del fichero: ");
string nombre = Console.ReadLine();
Revisin 0.99zz Pgina 265
Introduccin a la programacin con C#, por Nacho Cabanes

if (! File.Exists( nombre) )
{
Console.WriteLine("No encontrado!");
}
else
{
FileStream fichero = File.OpenRead(nombre);
int tamanyoCabecera = 54;
byte[] cabecera = new byte[tamanyoCabecera];

int cantidadLeida = fichero.Read(cabecera, 0, tamanyoCabecera);


fichero.Close();

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

if ((marca1 !='B') || (marca2 !='M'))


Console.WriteLine("No parece un fichero BMP");
else
{
Console.WriteLine("Marca del fichero: {0}{1}",
marca1, marca2);

int ancho = cabecera[18] + // Convierto 4 bytes a Int32


cabecera[19] * 256 + cabecera[20] * 256 * 256 +
cabecera[21] * 256 * 256 * 256;
Console.WriteLine("Ancho: {0}", ancho);

int alto = cabecera[22] +


cabecera[23] * 256 + cabecera[24] * 256 * 256 +
cabecera[25] * 256 * 256 * 256;
Console.WriteLine("Alto: {0}", alto);

int compresion = cabecera[30];


switch (compresion)
{
case 0: Console.WriteLine("Sin compresin");
break;
case 1: Console.WriteLine("Compresin RLE 8 bits");
break;
case 2: Console.WriteLine("Compresin RLE 4 bits");
break;
}

int resolH = cabecera[38] +


cabecera[39] * 256 + cabecera[40] * 256 * 256 +
cabecera[41] * 256 * 256 * 256;
Console.WriteLine("Resolucin Horiz.: {0}", resolH);

int resolV = cabecera[42] +


cabecera[43] * 256 + cabecera[44] * 256 * 256 +
cabecera[45] * 256 * 256 * 256;
Revisin 0.99zz Pgina 266
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("Resolucin Vertical: {0}", resolV);


}
}
}
}
}

Ejercicios propuestos:
(8.14.1) Localiza en Internet informacin sobre el formato de imgenes PCX. Crea
un programa que diga el ancho, alto y nmero de colores de una imagen PCX.
(8.14.2) Localiza en Internet informacin sobre el formato de imgenes GIF. Crea
un programa que diga el subformato, ancho, alto y nmero de colores de una
imagen GIF.

8.15. Escribir en un fichero binario

Para escribir en un FileStream, usaremos rdenes similares a las que


emplebamos para leer de l:
Un mtodo WriteByte, para escribir slo un byte, o bien...
Un mtodo Write, para escribir un bloque de informacin (desde cierta
posicin de un array -normalmente 0-, y con cierto tamao).

Adems, a la hora de abrir el fichero, tenemos dos alternativas:


Abrir un fichero existente con "OpenWrite".
Crear un nuevo fichero con "Create".

Vamos a ver un ejemplo que junte todo ello: crearemos un fichero, guardaremos
datos, lo leeremos para comprobar que todo es correcto, aadiremos al final, y
volveremos a leer:

// Ejemplo_08_15a.cs
// Ficheros binarios: escritura en FileStream
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_15a


{
const int TAMANYO_BUFFER = 10;

public static void Main()


{
FileStream fichero;
string nombre;
Revisin 0.99zz Pgina 267
Introduccin a la programacin con C#, por Nacho Cabanes

byte[] datos;

nombre = "datos.dat";
datos = new byte[TAMANYO_BUFFER];

// Damos valores iniciales al array


for (byte i=0; i<TAMANYO_BUFFER; i++)
datos[i] = (byte) (i + 10);

try
{
// Primero creamos el fichero, con algun dato
fichero = File.Create( nombre );
fichero.Write(datos, 0, TAMANYO_BUFFER);
fichero.Close();

// Ahora leemos dos datos


fichero = File.OpenRead(nombre);
Console.WriteLine("El tamao es {0}", fichero.Length);
fichero.Seek(2, SeekOrigin.Begin);
int nuevoDato = fichero.ReadByte();
Console.WriteLine("El tercer byte es un {0}", nuevoDato);
fichero.Seek(-2, SeekOrigin.End);
nuevoDato = fichero.ReadByte();
Console.WriteLine("El penultimo byte es un {0}", nuevoDato);
fichero.Close();

// Ahora aadimos 10 datos ms, al final


fichero = File.OpenWrite(nombre);
fichero.Seek(0, SeekOrigin.End);
fichero.Write(datos, 0, TAMANYO_BUFFER);
// y modificamos el tercer byte
fichero.Seek(2, SeekOrigin.Begin);
fichero.WriteByte( 99 );
fichero.Close();

// Volvemos a leer algun dato


fichero = File.OpenRead(nombre);
Console.WriteLine("El tamao es {0}", fichero.Length);
fichero.Seek(2, SeekOrigin.Begin);
nuevoDato = fichero.ReadByte();
Console.WriteLine("El tercer byte es un {0}", nuevoDato);
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).

Revisin 0.99zz Pgina 268


Introduccin a la programacin con C#, por Nacho Cabanes

Si queremos que escribir datos bsicos 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 mtodos WriteByte,
WriteString y similares, sino un nico mtodo "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
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_15b


{
public static void Main()
{
BinaryWriter ficheroSalida;
BinaryReader ficheroEntrada;
string nombre;

// Los datos que vamos a guardar/leer


byte unDatoByte;
int unDatoInt;
float unDatoFloat;
double unDatoDouble;
string unDatoString;

Console.Write("Introduzca el nombre del fichero a crear: ");


nombre = Console.ReadLine();

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

// Ahora vamos a leerlos


Console.WriteLine("Leyendo fichero...");
Revisin 0.99zz Pgina 269
Introduccin a la programacin con C#, por Nacho Cabanes

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

El resultado de este programa es:

Introduzca el nombre del fichero a crear: 1234


Creando fichero...
Leyendo fichero...
El byte leido es un 1
El int leido es un 2
El float leido es un 3
El double leido es un 4
El string leido es "Hola"
Volvamos a leer el int...
El int leido es un 2

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:
Revisin 0.99zz Pgina 270
Introduccin a la programacin con C#, por Nacho Cabanes

CreateNew: Crear un archivo nuevo. Si existe, se produce una excepcin


IOException.
Create: Crear un archivo nuevo. Si ya existe, se sobrescribir.
Open: Abrir un archivo existente. Si el archivo no existe, se produce una
excepcin System.IO.FileNotFoundException.
OpenOrCreate: Se debe abrir un archivo si ya existe; en caso contrario,
debe crearse uno nuevo.
Truncate: Abrir un archivo existente y truncarlo para que su tamao sea de
cero bytes.
Append: Abre el archivo si existe y se desplaza hasta el final del mismo, o
crea un archivo nuevo si no existe.

Ejercicios propuestos:
(8.15.1) Crea una copia de un fichero EXE. La copia debe tener el mismo nombre y
extensin 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.

8.16. Leer y escribir en un mismo fichero binario


Tambin es posible que nos interese leer y escribir en un mismo fichero (por
ejemplo, para poder modificar algn dato errneo, o para encriptar un fichero sin
necesidad de crear una copia, o para poder crear herramientas como un editor
hexadecimal). Podemos conseguirlo abriendo (en modo de lectura o de escritura)
o cerrando el fichero cada vez, pero tambin tenemos la alternativa de usar un
"FileStream", que tambin tiene un mtodo llamado simplemente "Open", al que
se le puede indicar el modo de apertura (FileMode, como se vio en el apartado
8.12) y el modo de acceso (FileAccess.Read si queremos leer, FileAccess.Write si
queremos escribir, o FileAccess.ReadWrite si queremos leer y escribir).

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
// Introduccin a C#, por Nacho Cabanes
Revisin 0.99zz Pgina 271
Introduccin a la programacin con C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_08_16a


{
const int TAMANYO_BUFFER = 10;

public static void Main()


{
FileStream fichero;
string nombre;
byte[] datos;

nombre = "datos.dat";
datos = new byte[TAMANYO_BUFFER];

// Damos valores iniciales al array


for (byte i=0; i<TAMANYO_BUFFER; i++)
datos[i] = (byte) (i + 10);

try
{
int posicion = 0;

// Primero creamos el fichero, con algun dato


fichero = File.Create( nombre );
fichero.Write(datos, posicion, TAMANYO_BUFFER);
fichero.Close();

// Ahora leemos dos datos


fichero = File.Open(nombre, FileMode.Open, FileAccess.ReadWrite);

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.
Revisin 0.99zz Pgina 272
Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios de repaso sobre ficheros:


(8.16.2) Crea un programa que vuelque todo el contenido de un fichero de texto a
otro, convirtiendo cada frase a maysculas. Los nombres de ambos ficheros se
deben indican como parmetros en la lnea de comandos.
(8.16.3) Un programa que vuelque todo el contenido de un fichero binario a otro,
convirtiendo cada letra entre la A y la Z a maysculas. Los nombres de ambos
ficheros se deben indican como parmetros en la lnea de comandos. Si no hay
parmetros, se le preguntarn al usuario.
(8.16.4) Mejora la base de datos de ficheros (ejemplo 04_06a) para que los datos
almacenados se guarden automticamente en fichero al terminar una ejecucin, y
se vuelvan a cargar al principio de la siguiente.
(8.16.5) Crea un "traductor bsico de C# a C", que volcar todo el contenido de un
fichero de texto a otro, pero reemplazando "Console.WriteLine" con "printf",
"Main" con "main", "string" con "char[80]", "Console.ReadLine" con "scanf", y
eliminando "static" y "public" y las lneas que comiencen con "Using".
(8.16.6) Prepara un programa que pida al usuario el nombre de un fichero y una
secuencia de 4 bytes, y diga si el fichero contiene esa secuencia de bytes.
(8.16.7) Haz un programa que duplique un fichero, copiando todo su contenido a
un nuevo fichero, byte a byte. El nombre del fichero de salida se tomar del
nombre del fichero de entrada, al que se aadir ".out".
(8.16.8) Crea un programa que duplique un fichero, copiando todo su contenido a
un nuevo fichero, volcando para ello todo el contenido en un array. El nombre de
ambos ficheros se debe leer de la lnea de comandos, o pedir al usuario en caso de
no existir parmetros.
(8.16.9) Crea un programa que compare si dos ficheros son iguales byte a byte,
comprobando primero su tamao y leyendo despus cada vez un byte de cada
uno de ellos.
(8.16.10) Crea un programa que compare si dos ficheros son iguales, volcando
todo su contenido en sendos arrays, que comparars.
(8.16.11) Un programa que muestre el nombre del autor de un fichero de msica
en formato MP3 (tendrs que localizar en Internet la informacin sobre dicho
formato y sobre la cabecera ID3 V1, que se encuentra -si est presente- en los
ltimos 128 bytes del fichero).

Revisin 0.99zz Pgina 273


Introduccin a la programacin con C#, por Nacho Cabanes

9. Persistencia de objetos
9.1. Por qu la persistencia?
Una forma alternativa de conseguir que la informacin que manipula un programa
est disponible para una ejecucin posterior es no guardarla en un "fichero
convencional", sino pedir al sistema que se conserve el estado de los objetos que
forman el programa.

Podramos conseguirlo "de forma artesanal" si creamos un mtodo que guarde


cada uno de los atributos en un fichero y otro mtodo que recupere cada uno de
esos atributos desde el mismo fichero:

// Ejemplo_09_01a.cs
// Primer acercamiento a la persistencia
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Persist01


{

int numero;

public void SetNumero(int n)


{
numero = n;
}

public int GetNumero()


{
return numero;
}

public void Guardar(string nombre)


{
BinaryWriter ficheroSalida = new BinaryWriter(
File.Open(nombre, FileMode.Create));
ficheroSalida.Write(numero);
ficheroSalida.Close();
}

public void Cargar(string nombre)


{
BinaryReader ficheroEntrada = new BinaryReader(
File.Open(nombre, FileMode.Open));
numero = ficheroEntrada.ReadInt32();
ficheroEntrada.Close();
}

public static void Main()

Revisin 0.99zz Pgina 274


Introduccin a la programacin con C#, por Nacho Cabanes

{
Persist01 ejemplo = new Persist01();
ejemplo.SetNumero(5);
Console.WriteLine("Valor: {0}", ejemplo.GetNumero());
ejemplo.Guardar( "ejemplo.dat" );

Persist01 ejemplo2 = new Persist01();


Console.WriteLine("Valor 2: {0}", ejemplo2.GetNumero());

ejemplo2.Cargar( "ejemplo.dat" );
Console.WriteLine("Y ahora: {0}", ejemplo2.GetNumero());
}
}

Que dara como resultado:

Valor: 5
Valor 2: 0
Y ahora: 5

Pero esta forma de trabajar se complica mucho cuando nuestro objeto tiene
muchos atributos, y ms an si se trata de bloques de objetos (por ejemplo, un
"array") que a su vez contienen otros objetos. Por eso, existen maneras ms
automatizadas y que permiten escribir menos cdigo.

Ejercicios propuestos:
(9.1.1) Ampla la clase Persona (ejercicio 6.2.1), para que permita guardar su
estado y recuperarlo posteriormente.

9.2. Creando un objeto "serializable"


Vamos a guardar todo un objeto (incluyendo los valores de sus atributos) en un
fichero. En primer lugar, tendremos que indicar "[Serializable]" antes de la clase:

[Serializable]
public class Persist02

En segundo lugar, como vamos a sobrescribir todo el objeto, en vez de slo los
atributos, ahora los mtodos "Cargar" y "Guardar" ya no pueden pertenecer a esa
misma clase: debern 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:

public static void Guardar(string nombre, Persist02 objeto)

El programa completo podra ser algo como


Revisin 0.99zz Pgina 275
Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_09_02a.cs
// Ejemplo bsico de persistencia
// Introduccin 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;

public void SetNumero(int n)


{
numero = n;
}

public int GetNumero()


{
return numero;
}

// Mtodos para guardar en fichero y leer desde l

public static void Guardar(string nombre, Persist02 objeto)


{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, objeto);
stream.Close();
}

public static Persist02 Cargar(string nombre)


{
Persist02 objeto;
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Open, FileAccess.Read, FileShare.Read);
objeto = (Persist02)formatter.Deserialize(stream);
stream.Close();
return objeto;
}

public static void Main()


{
Persist02 ejemplo = new Persist02();
ejemplo.SetNumero(5);
Console.WriteLine("Valor: {0}", ejemplo.GetNumero());
Guardar("ejemplo.dat", ejemplo);

Persist02 ejemplo2 = new Persist02();


Console.WriteLine("Valor 2: {0}", ejemplo2.GetNumero());
ejemplo2 = Cargar("ejemplo.dat");
Console.WriteLine("Y ahora: {0}", ejemplo2.GetNumero());
}
Revisin 0.99zz Pgina 276
Introduccin a la programacin con C#, por Nacho Cabanes

Y su resultado sera el mismo que antes:

Valor: 5
Valor 2: 0
Y ahora: 5

Ejercicios propuestos:
(9.2.1) Crea una variante del ejercicio 9.1.1, que use serializacin para guardar y
recuperar los datos.

9.3. Empleando clases auxiliares


Una solucin un poco ms elegante podra ser encapsular la clase (o clases) que
vamos a guardar, incluyndola(s) dentro de otra clase auxiliar, de modo que
podamos reutilizar esa estructura:

[Serializable]
public class ClaseAGuardar
{
Ejemplo e;

public void SetDatos(Ejemplo e1)


{
e = e1;
}

public Ejemplo GetDatos()


{
return e;
}
}

Y crear una segunda clase, que sea la encargada de guardar y recuperar los datos:

public class Serializador


{
string nombre;

public Serializador(string nombreFich)


{
nombre = nombreFich;
}

public void Guardar(ClaseAGuardar objeto)


{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Create, FileAccess.Write, FileShare.None);
Revisin 0.99zz Pgina 277
Introduccin a la programacin con C#, por Nacho Cabanes

formatter.Serialize(stream, objeto);
stream.Close();
}

public ClaseAGuardar Cargar()


{
ClaseAGuardar objeto;
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Open, FileAccess.Read, FileShare.Read);
objeto = (ClaseAGuardar)formatter.Deserialize(stream);
stream.Close();
return objeto;
}
}

De modo que un nico fuente que contuviera estas tres clases podra ser:

// Ejemplo_09_03a.cs
// Ejemplo de persistencia
// Introduccin 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;

public void SetNumero(int n)


{
numero = n;
}

public int GetNumero()


{
return numero;
}
}

// ---------------------------------------------
// La clase "que realmente se va a guardar"
[Serializable]
public class ClaseAGuardar
{
Ejemplo e;

public void SetDatos(Ejemplo e1)


{
e = e1;
Revisin 0.99zz Pgina 278
Introduccin a la programacin con C#, por Nacho Cabanes

public Ejemplo GetDatos()


{
return e;
}
}

// ---------------------------------------------
// La clase "encargada de guardar"

public class Serializador


{
string nombre;

public Serializador(string nombreFich)


{
nombre = nombreFich;
}

public void Guardar(ClaseAGuardar objeto)


{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, objeto);
stream.Close();
}

public ClaseAGuardar Cargar()


{
ClaseAGuardar objeto;
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Open, FileAccess.Read, FileShare.Read);
objeto = (ClaseAGuardar)formatter.Deserialize(stream);
stream.Close();
return objeto;
}
}

// ---------------------------------------------
// Y el programa de prueba

public class Prueba


{

public static void Main()


{
Ejemplo ejemplo = new Ejemplo();
ejemplo.SetNumero(5);
Console.WriteLine("Valor: {0}", ejemplo.GetNumero());

ClaseAGuardar guardador = new ClaseAGuardar();


guardador.SetDatos(ejemplo);

Serializador s = new Serializador("ejemplo.dat");


Revisin 0.99zz Pgina 279
Introduccin a la programacin con C#, por Nacho Cabanes

s.Guardar(guardador);

Ejemplo ejemplo2 = new Ejemplo();


Console.WriteLine("Valor 2: {0}", ejemplo2.GetNumero());
ejemplo2 = s.Cargar().GetDatos();
Console.WriteLine("Y ahora: {0}", ejemplo2.GetNumero());
}
}

Vamos a comprobar que se comporta correctamente en un caso ms complicado,


haciendo que nuestra clase contenga varios atributos, entre ellos un array de
objetos de otra clase. La clase "Serializador" no cambiar, y en un caso real apenas
cambiara la clase "ClaseAGuardar" (que aqu tampoco va a modificarse):

// Ejemplo_09_03b.cs
// Ejemplo de persistencia
// Introduccin 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;

public void SetDato(int n)


{
dato = n;
}

public int GetDato()


{
return dato;
}
}

// -----
[Serializable]
public class Ejemplo
{

int numero;
float numero2;
MiniEjemplo[] mini;

public Ejemplo()
{
mini = new MiniEjemplo[100];
for (int i=0; i<100; i++)
{
Revisin 0.99zz Pgina 280
Introduccin a la programacin con C#, por Nacho Cabanes

mini[i] = new MiniEjemplo();


mini[i].SetDato( i*2 );
}
}

public void SetNumero(int n)


{
numero = n;
}

public int GetNumero()


{
return numero;
}

public void SetMini(int n, int valor)


{
mini[n].SetDato(valor);
}

public int GetMini(int n)


{
return mini[n].GetDato();
}

// ---------------------------------------------
// La clase "que realmente se va a guardar"

[Serializable]
public class ClaseAGuardar
{
Ejemplo e;

public void SetDatos(Ejemplo e1)


{
e = e1;
}

public Ejemplo GetDatos()


{
return e;
}
}

// ---------------------------------------------
// La clase "encargada de guardar"

public class Serializador


{
string nombre;

public Serializador(string nombreFich)


{
nombre = nombreFich;
}

public void Guardar(ClaseAGuardar objeto)


Revisin 0.99zz Pgina 281
Introduccin a la programacin con C#, por Nacho Cabanes

{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, objeto);
stream.Close();
}

public ClaseAGuardar Cargar()


{
ClaseAGuardar objeto;
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(nombre,
FileMode.Open, FileAccess.Read, FileShare.Read);
objeto = (ClaseAGuardar)formatter.Deserialize(stream);
stream.Close();
return objeto;
}
}

// ---------------------------------------------
// Y el programa de prueba

public class Prueba


{

public static void Main()


{
Ejemplo ejemplo = new Ejemplo();
ejemplo.SetNumero(5);
ejemplo.SetMini(50, 500);
Console.WriteLine("Valor: {0}", ejemplo.GetNumero());

ClaseAGuardar guardador = new ClaseAGuardar();


guardador.SetDatos(ejemplo);

Serializador s = new Serializador("ejemplo.dat");


s.Guardar(guardador);

Ejemplo ejemplo2 = new Ejemplo();


Console.WriteLine("Valor 2: {0}", ejemplo2.GetNumero());
ejemplo2 = s.Cargar().GetDatos();
Console.WriteLine("Y ahora: {0}", ejemplo2.GetNumero());
Console.WriteLine("dato 50: {0}", ejemplo2.GetMini(50));
}
}

Esto dara como resultado:

Valor: 5
Valor 2: 0
Y ahora: 5
dato 50: 500

Revisin 0.99zz Pgina 282


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(9.3.1) Crea una variante del ejercicio 9.2.1, que use una clase auxiliar para la
serializacin.

9.4. Volcando a un fichero de texto


La forma de trabajar que estamos empleando guarda los datos en un fichero
binario. Si queremos guardarlos en un fichero XML (por ejemplo para maximizar la
portabilidad, garantizando que se va a leer correctamente desde algn otro
sistema en el que quiz el orden de los bytes sea distinto), los cambios son
mnimos: cambiar "BinaryFormatter" por "SoapFormatter", as como el correspon-
diente "using" y quiz aadir una DLL adicional al proyecto:

// Ejemplo_09_04a.cs
// Ejemplo de persistencia (XML)
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;
using System.Runtime.Serialization;
// Para la siguiente lnea, puede ser necesario aadir 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;

public void SetDato(int n)


{
dato = n;
}

public int GetDato()


{
return dato;
}
}

[Serializable]
public class Ejemplo
{

int numero;
float numero2;
MiniEjemplo[] mini;

public Ejemplo()
Revisin 0.99zz Pgina 283
Introduccin a la programacin 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 );
}
}

public void SetNumero(int n)


{
numero = n;
}

public int GetNumero()


{
return numero;
}

public void SetMini(int n, int valor)


{
mini[n].SetDato(valor);
}

public int GetMini(int n)


{
return mini[n].GetDato();
}

// ---------------------------------------------
// La clase "que realmente se va a guardar"

[Serializable]
public class ClaseAGuardar
{
Ejemplo e;

public void SetDatos(Ejemplo e1)


{
e = e1;
}

public Ejemplo GetDatos()


{
return e;
}
}

// ---------------------------------------------
// La clase "encargada de guardar"

public class Serializador


{
string nombre;

public Serializador(string nombreFich)


{
Revisin 0.99zz Pgina 284
Introduccin a la programacin con C#, por Nacho Cabanes

nombre = nombreFich;
}

public void Guardar(ClaseAGuardar objeto)


{
IFormatter formatter = new SoapFormatter();
Stream stream = new FileStream(nombre,
FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, objeto);
stream.Close();
}

public ClaseAGuardar Cargar()


{
ClaseAGuardar objeto;
IFormatter formatter = new SoapFormatter();
Stream stream = new FileStream(nombre,
FileMode.Open, FileAccess.Read, FileShare.Read);
objeto = (ClaseAGuardar)formatter.Deserialize(stream);
stream.Close();
return objeto;
}
}

// ---------------------------------------------
// Y el programa de prueba

public class Prueba


{

public static void Main()


{
Ejemplo ejemplo = new Ejemplo();
ejemplo.SetNumero(5);
ejemplo.SetMini(50, 500);
Console.WriteLine("Valor: {0}", ejemplo.GetNumero());

ClaseAGuardar guardador = new ClaseAGuardar();


guardador.SetDatos(ejemplo);

Serializador s = new Serializador("ejemplo2.dat");


s.Guardar(guardador);

Ejemplo ejemplo2 = new Ejemplo();


Console.WriteLine("Valor 2: {0}", ejemplo2.GetNumero());
ejemplo2 = s.Cargar().GetDatos();
Console.WriteLine("Y ahora: {0}", ejemplo2.GetNumero());
Console.WriteLine("dato 50: {0}", ejemplo2.GetMini(50));
}
}

Este programa, que compilar correctamente en versiones de .Net como la 2.0,


puede no compilar tal cual en versiones ms recientes, como la 4. Si es el caso,
deberemos aadir una "referencia" adicional al proyecto.

Revisin 0.99zz Pgina 285


Introduccin a la programacin con C#, por Nacho Cabanes

Si usamos Visual Studio, deberemos ir al panel derecho (Explorador de soluciones),


hacer clic con el botn derecho en "References" e indicar que deseamos "Agregar
referencia":

Dentro de esta opcin, deberemos buscar dentro de las referencias que ya estn
previstas para nuestra versin de .Net (por ejemplo, la 4.5) el nombre
"System.Runtime.Serialization.Formatters.Soap" y despus aceptar los cambios:

Si usamos XML, el fichero de datos resultante ser de mayor tamao, pero a


cambio se podr analizar con mayor cantidad de herramientas, al ser texto puro.
Por ejemplo, este es un fragmento del fichero de datos generado por el programa
anterior:

Revisin 0.99zz Pgina 286


Introduccin a la programacin con C#, por Nacho Cabanes

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

(Se ha omitido la mayor parte de los datos repetitivos)


Revisin 0.99zz Pgina 287
Introduccin a la programacin con C#, por Nacho Cabanes

Como curiosidad, SOAP (que da su nombre al SoapFormatter) es un protocolo


diseado para poder intercambiar de forma sencilla informacin estructurada
(como objetos y sus componentes) en aplicaciones Web.

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 versin 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) Aade al ejercicio de los trabajadores (6.8.1) la posibilidad de guardar sus
datos en formato XML.

Revisin 0.99zz Pgina 288


Introduccin a la programacin con C#, por Nacho Cabanes

10. Acceso a bases de datos relacionales


10.1. Nociones mnimas de bases de datos relacionales
Una base de datos relacional es una estructura mucho ms compleja (pero
tambin ms verstil) que un simple fichero de texto o binario. Hay ciertos
conceptos que debemos conocer antes de empezar a trabajar con ellas:

En una base de datos relacional hay varios bloques de datos, llamados


"tablas" (por ejemplo, la tabla de "clientes", la tabla de "proveedores", la
tabla de "productos").
Cada tabla est formada por una serie de "registros" (por ejemplo, el
cliente "Juan Lpez", el cliente "Pedro lvarez").
Cada registro est formado por varios "campos" (de cada cliente podemos
almacenar su nombre, su direccin postal, su telfono, su correo
electrnico, etc).
Adems, existen relaciones entre las tablas: por ejemplo una factura es
para un cierto cliente, de modo que la tabla "factura" se relaciona con la
tabla "cliente", y el nombre de esta relacin es "ser para".

Vamos a aplicar estos conceptos bsicos a la vez que vamos conociendo el


lenguaje de consulta ms habitual.

10.2. Nociones mnimas de lenguaje SQL


El lenguaje SQL (Structured Query Language) es un lenguaje muy extendido, que
permite hacer consultas a una base de datos relacional. Por eso, vamos a ver
algunas de las rdenes que podemos usar para almacenar y obtener datos, y
posteriormente las aplicaremos desde un programa en C# que conecte a una base
de datos relacional. Cuando ya seamos capaces de guardar datos y recuperarlos,
veremos alguna orden ms avanzada para extraer la informacin o para
modificarla.

10.2.1. Creando la estructura


Como primer ejemplo, crearemos una base de datos sencilla, que llamaremos
"ejemplo1". Esta base de datos contendr una nica tabla, llamada "agenda", que
contendr algunos datos de cada uno de nuestros amigos. Como es nuestra

Revisin 0.99zz Pgina 289


Introduccin a la programacin con C#, por Nacho Cabanes

primera base de datos, no pretendemos que sea perfecta, sino sencilla, as que
apenas guardaremos tres datos de cada amigo: el nombre, la direccin y la edad.

Para crear la base de datos que contiene todo usaremos "create database",
seguido del nombre que tendr la base de datos:

create database ejemplo1;

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
tambin qu tamao necesitaremos para cada uno de esos datos, porque al
gestor de bases de datos habr que drselo bastante cuadriculado. Por ejemplo,
podramos decidir lo siguiente:

nombre - texto, hasta 20 letras


direccin - texto, hasta 40 letras
edad - nmeros, de hasta 3 cifras

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 numricos de
distinto tamao. Si usamos "INT" para indicar que nos basta con un entero no muy
grande, la orden necesaria para crear esta tabla sera:

create table personas (


nombre varchar(20),
direccion varchar(40),
edad int
);

10.2.2. Introduciendo datos


Para introducir datos usaremos la orden "insert", e indicaremos tras la palabra
"values" los valores para los campos de texto entre comillas, y los valores para
campos numricos sin comillas, as:

insert into personas values ('juan', 'su casa', 25);

Este formato nos obliga a indicar valores para todos los campos, y exactamente en
el orden en que se disearon. Si no queremos introducir todos los datos, o
queremos hacerlo en otro orden, o no recordamos con seguridad el orden, hay

Revisin 0.99zz Pgina 290


Introduccin a la programacin con C#, por Nacho Cabanes

otra opcin: detallar tambin en la orden "insert" los nombres de cada uno de los
campos, as:

insert into personas


(nombre, direccion, edad)
values (
'pedro', 'su calle', 23
);

10.2.3. Mostrando datos


Para ver los datos almacenados en una tabla usaremos el formato "SELECT
campos FROM tabla". Si queremos ver todos los campos, lo indicaremos usando
un asterisco:

select * from personas;

que, con nuestros datos, dara como resultado (en el intrprete 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 slo ciertos campos, detallamos sus nombres, separados por
comas:

select nombre, direccion from personas;

y obtendramos

+--------+-----------+
| nombre | direccion |
+--------+-----------+
| juan | su casa |
| pedro | su calle |
+--------+-----------+

Normalmente no querremos ver todos los datos que hemos introducido, sino slo
aquellos que cumplan cierta condicin. Esta condicin se indica aadiendo un
apartado WHERE a la orden "select", as:

Revisin 0.99zz Pgina 291


Introduccin a la programacin con C#, por Nacho Cabanes

select nombre, direccion from personas where nombre = 'juan';

que nos dira el nombre y la direccin de nuestros amigos llamados "juan":

+--------+-----------+
| nombre | direccion |
+--------+-----------+
| juan | su casa |
+--------+-----------+

A veces no querremos comparar con un texto exacto, sino slo con parte del
contenido del campo (por ejemplo, porque slo sepamos un apellido o parte del
nombre de la calle). En ese caso, no compararamos con el smbolo "igual" (=), sino
que usaramos la palabra "like", y para las partes que no conozcamos usaremos el
comodn "%", como en este ejemplo:

select nombre, direccion from personas where direccion like '%calle%';

que nos dira el nombre y la direccin de nuestros amigos llamados que viven en
calles que contengan la palabra "calle", precedida por cualquier texto (%) y con
cualquier texto (%) a continuacin:

+--------+-----------+
| nombre | direccion |
+--------+-----------+
| pedro | su calle |
+--------+-----------+

10.3. Acceso a bases de datos con SQLite


SQLite es un gestor de bases de datos de pequeo tamao, que emplea el
lenguaje SQL para las consultas, y del que existe una versin que se distribuye
como un fichero DLL que distribuiramos junto al ejecutable de nuestro programa,
o bien como un fichero en C que se podra integrar con los fuentes de nuestro
programa en caso de que usemos lenguaje C en nuestro proyecto.

En nuestro caso, para acceder a SQLite desde C#, tenemos disponible alguna
adaptacin de la biblioteca original. Una de ellas es System.Data.SQLite, que se
puede descargar de http://system.data.sqlite.org/ y tienes un pequeo proyecto de
ejemplo listo para usar desde Visual Studio en para
http://nachocabanes.com/csharp

Revisin 0.99zz Pgina 292


Introduccin a la programacin con C#, por Nacho Cabanes

Con esta biblioteca, los pasos a seguir para crear una base de datos y guardar
informacin en una base de datos de SQLite seran:

Crear una conexin a la base de datos, mediante un objeto de la clase


SQLiteConnection, en cuyo constructor indicaremos detalles como la ruta
del fichero, la versin de SQLite, y si el fichero se debe crear (new=True) o
ya existe.
Empleando un objeto de la clase SQLiteCommand, detallaremos cul es la
orden SQL a ejecutar, y la lanzaremos con ExecuteNonQuery.
Nos devolver la cantidad de filas afectadas, que en nuestro caso, para una
introduccin de un dato, debera ser 1; si nos devuelve un dato menor que
uno, nos indica que no se ha podido guardar correctamente.
Finalmente cerraremos la conexin con Close:

Un fuente que d estos pasos podra ser:

// EjemploSQLite1.cs
// Ejemplo de acceso a bases de datos con SQLite (1)
// Introduccin a C#, por Nacho Cabanes

using System;
// Es necesario aadir la siguiente DLL a la "referencias" del proyecto
using System.Data.SQLite;

public class EjemploSQLite1


{
public static void Main()
{
Console.WriteLine("Creando la base de datos...");

// Creamos la conexion a la BD.


// El Data Source contiene la ruta del archivo de la BD
SQLiteConnection conexion =
new SQLiteConnection
("Data
Source=ejemplo01.sqlite;Version=3;New=True;Compress=True;");
conexion.Open();

// Creamos la tabla
string creacion = "create table personas ("
+" nombre varchar(20),direccion varchar(40),edad int);";
SQLiteCommand cmd = new SQLiteCommand(creacion, conexion);
cmd.ExecuteNonQuery();

// E insertamos dos datos


string insercion = "insert into personas values ('juan', 'su casa',
25);";
cmd = new SQLiteCommand(insercion, conexion);
int cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

Revisin 0.99zz Pgina 293


Introduccin a la programacin con C#, por Nacho Cabanes

insercion = "insert into personas (nombre, direccion, edad)"


+" values ('pedro', 'su calle', 23);";
cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

// Finalmente, cerramos la conexion


conexion.Close();

Console.WriteLine("Creada.");
}
}

Deberemos compilar con la versin 2 (o superior) de la "plataforma punto Net" y


aadir el fichero "System.Data.SQLite.dll" a las "referencias" del proyecto.

Por ejemplo, con Mono haramos

gmcs ejemploSQLite.cs /r:System.Data.SQLite.dll

Y para lanzar el ejecutable necesitaremos tener tambin la versin 2 (o superior)


de la plataforma .Net, o bien lanzarlo a travs de Mono:

mono ejemploSQLite.exe

Tanto el fichero "System.Data.SQLite.dll" como el "sqlite3.dll" debern estar en la


misma carpeta que nuestro ejecutable, para que ste funcione correctamente.

Como alternativa, tambin podemos crear el ejecutable usando el compilador


que incorpora la "plataforma punto Net", que es un fichero llamado "csc.exe"
en la carpeta de la versin de la plataforma que tengamos instalada (es frecuente
tener varias). Por ejemplo, para la versin 2 haramos

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 versin de Windows 64
bits puede no funcionar correctamente. La solucin, si compilamos desde lnea de
comandos, es indicar que es para "plataforma x86", aadiendo la opcin
"/platform:x86", as:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc ejemploSQLite.cs
/r:System.Data.SqLite.dll /platform:x86

Revisin 0.99zz Pgina 294


Introduccin a la programacin con C#, por Nacho Cabanes

Si usamos Visual Studio o SharpDevelop, tendramos que aadir ese fichero DLL a
las "referencias" de nuestro proyecto. Lo podemos hacer desde la ventana del
"Explorador de soluciones", pulsando el botn derecho sobre "References",
"Agregar referencia" y escogiendo el fichero System.Data.SQLite.DLL desde la
pestaa "Examinar":

Al igual que si compilamos desde la lnea de comandos, tanto el fichero


"System.Data.SQLite.dll" como el "sqlite3.dll" debern estar en la misma carpeta
que nuestro ejecutable (tpicamente "bin\debug").

Como curiosidad, esta base de datos de prueba se podra haber creado


previamente desde el propio entorno de SQLite, o con algn gestor auxiliar, como
la extensin de Firefox llamada SQLite Manager o como la utilidad SQLite
Database Browser.

Para mostrar los datos, el primer y ltimo paso seran casi iguales, pero no los
intermedios:

Crear una conexin a la base de datos, indicando en este caso que el


fichero ya existe (new=false).
Con un objeto de la clase SQLiteCommand detallaremos cul es la orden
SQL a ejecutar, y la lanzaremos con ExecuteReader.
Con "Read", leeremos cada dato (devuelve un bool que indica si se ha
conseguido leer correctamente), y accederemos a los campos de cada dato
como parte de un array: dato[0] ser el primer campo, dato[1] ser el
segundo y as sucesivamente.
Finalmente cerraremos la conexin con Close:

// EjemploSQLite2.cs
// Ejemplo de acceso a bases de datos con SQLite (2)
// Introduccin a C#, por Nacho Cabanes

using System;
Revisin 0.99zz Pgina 295
Introduccin a la programacin con C#, por Nacho Cabanes

// Es necesario aadir la siguiente DLL a la "referencias" del proyecto


using System.Data.SQLite;

public class EjemploSQLite2


{

public static void Main()


{
// Creamos la conexion a la BD
// El Data Source contiene la ruta del archivo de la BD
SQLiteConnection conexion =
new SQLiteConnection
("Data
Source=ejemplo01.sqlite;Version=3;New=False;Compress=True;");
conexion.Open();

// Lanzamos la consulta y preparamos la estructura para leer datos


string consulta = "select * from personas";
SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);
SQLiteDataReader datos = cmd.ExecuteReader();

// Leemos los datos de forma repetitiva


while (datos.Read())
{
string nombre = Convert.ToString(datos[0]);
int edad = Convert.ToInt32(datos[2]);
// Y los mostramos
System.Console.WriteLine("Nombre: {0}, edad: {1}",
nombre, edad);
}

// Finalmente, cerramos la conexion


conexion.Close();
}
}

Que mostrara:

Nombre: juan, edad: 25


Nombre: pedro, edad: 23

Nota: en este segundo ejemplo, mostrbamos dato[0] (el primer dato, el nombre)
y dato [2] (el tercer dato, la edad) pero no el dato intermedio, dato[1] (la direccin).

Ejercicios propuestos:
(10.3.1) Crea una versin 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 sesin de uso, y volverlos a cargar al
comienzo de la siguiente.

Revisin 0.99zz Pgina 296


Introduccin a la programacin con C#, por Nacho Cabanes

10.4. Un poco ms de SQL: varias tablas


10.4.1. La necesidad de varias tablas
Puede haber varios motivos por los que nos interese trabajar con ms de una
tabla.

Por una parte, podemos tener bloques de informacin claramente distintos. Por
ejemplo, en una base de datos que guarde la informacin de una empresa
tendremos datos como los artculos que distribuimos y los clientes que nos los
compran, y estos dos bloques de informacin no deberan guardarse en una
misma tabla.

Por otra parte, habr ocasiones en que veamos que los datos, a pesar de que se
podran clasificar dentro de un mismo "bloque de informacin" (tabla), seran
redundantes: existira gran cantidad de datos repetitivos, y esto puede dar lugar a
dos problemas:

Espacio desperdiciado.
Posibilidad de errores al introducir los datos, lo que dara lugar a
inconsistencias:

Veamos un ejemplo:

+---------+-----------+-----------+
| nombre | direccion | ciudad |
+---------+-----------+-----------+
| juan | su casa | alicante |
| alberto | calle uno | alicante |
| pedro | su calle | alicantw |
+---------+-----------+-----------+

Si en vez de repetir "alicante" en cada una de esas fichas (registros), utilizsemos


un cdigo de ciudad, por ejemplo "a", gastaramos menos espacio (en este
ejemplo, 7 bytes menos en cada ficha).

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 aparecera. Al teclear menos, es
tambin ms difcil cometer este tipo de errores.

Revisin 0.99zz Pgina 297


Introduccin a la programacin con C#, por Nacho Cabanes

A cambio, necesitaremos una segunda tabla, en la que guardemos los cdigos de


las ciudades, y el nombre al que corresponden (por ejemplo: si cdigoDeCiudad =
"a", la ciudad es "alicante").

10.4.2. Las claves primarias


Generalmente, ser necesario tener algn dato que nos permita distinguir de
forma clara los datos que tenemos almacenados. Por ejemplo, el nombre de una
persona no es nico: pueden aparecer en nuestra base de datos varios usuarios
llamados "Juan Lpez". Si son nuestros clientes, debemos saber cul es cul, para
no cobrar a uno de ellos un dinero que corresponde a otro. Eso se suele
solucionar guardando algn dato adicional que s sea nico para cada cliente,
como puede ser el Documento Nacional de Identidad, o el Pasaporte. Si no hay
ningn dato claro que nos sirva, en ocasiones aadiremos un "cdigo de cliente",
inventado por nosotros, o algo similar.

Llamaremos "claves primarias" a estos datos que distinguen claramente unas


"fichas" (registros) de otras.

10.4.3. Enlazar varias tablas usando SQL


Vamos a crear la tabla de ciudades, que guardar el nombre de cada una de ellas y
su cdigo. Este cdigo ser el que acte como "clave primaria", para distinguir otra
ciudad. Por ejemplo, hay una ciudad llamado "Toledo" en Espaa, pero tambin
otra en Argentina, otra en Uruguay, dos en Colombia, una en Ohio (Estados
Unidos)... el nombre claramente no es nico, as que podramos usar cdigos
como "te" para Toledo de Espaa, "ta" para Toledo de Argentina y as
sucesivamente.

La forma de crear la tabla con esos dos campos y con esa clave primaria sera:

create table ciudades (


codigo varchar(3),
nombre varchar(30),
primary key (codigo)
);

Mientras que la tabla de personas sera casi igual al ejemplo anterior, pero
aadiendo un nuevo dato: el cdigo de la ciudad

create table personas (


nombre varchar(20),
Revisin 0.99zz Pgina 298
Introduccin a la programacin con C#, por Nacho Cabanes

direccion varchar(40),
edad decimal(3),
codciudad varchar(3)
);

Para introducir datos, el hecho de que exista una clave primaria no supone ningn
cambio, salvo por el hecho de que no se nos permitira introducir dos ciudades
con el mismo cdigo:

insert into ciudades values ('a', 'alicante');


insert into ciudades values ('b', 'barcelona');
insert into ciudades values ('m', 'madrid');

insert into personas values ('juan', 'su casa', 25, 'a');


insert into personas values ('pedro', 'su calle', 23, 'm');
insert into personas values ('alberto', 'calle uno', 22, 'b');

Cuando queremos mostrar datos de varias tablas a la vez, deberemos hacer unos
pequeos cambios en las rdenes "select" que hemos visto:

En primer lugar, indicaremos varios nombres despus de "FROM" (los de


cada una de las tablas que necesitemos).
Adems, puede ocurrir que tengamos campos con el mismo nombre en
distintas tablas (por ejemplo, el nombre de una persona y el nombre de
una ciudad), y en ese caso deberemos escribir el nombre de la tabla antes
del nombre del campo.

Por eso, una consulta bsica sera algo parecido (slo parecido) a:

select personas.nombre, direccion, ciudades.nombre from personas, ciudades;

Pero esto todava 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 |
Revisin 0.99zz Pgina 299
Introduccin a la programacin con C#, por Nacho Cabanes

| alberto | calle uno | madrid |


+---------+-----------+-----------+

Pero esos datos no son reales: si "juan" vive en la ciudad de cdigo "a", slo
debera mostrarse junto al nombre "alicante". Nos falta indicar esa condicin: "el
cdigo de ciudad que aparece en la persona debe ser el mismo que el cdigo que
aparece en la ciudad", as:

select personas.nombre, direccion, ciudades.nombre from personas, ciudades


where personas.codciudad = ciudades.codigo;

Esta ser la forma en que trabajaremos normalmente. El resultado de esta


consulta sera:

+---------+-----------+-----------+
| 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 cdigos coinciden. Por ejemplo, para
ver qu personas viven en la ciudad llamada "madrid", haramos:

select personas.nombre, direccion, edad from personas, ciudades where


ciudades.nombre='madrid' and personas.codciudad = ciudades.codigo;

+--------+-----------+------+
| nombre | direccion | edad |
+--------+-----------+------+
| pedro | su calle | 23 |
+--------+-----------+------+

Y para saber las personas de ciudades que comiencen con la letra "b", usaramos
"like":

select personas.nombre, direccion, ciudades.nombre from personas, ciudades


where ciudades.nombre like 'b%' and personas.codciudad = ciudades.codigo;

+---------+-----------+-----------+
| nombre | direccion | nombre |
+---------+-----------+-----------+
| alberto | calle uno | barcelona |
+---------+-----------+-----------+
Revisin 0.99zz Pgina 300
Introduccin a la programacin con C#, por Nacho Cabanes

Si en nuestra tabla puede haber algn dato que se repita, como la direccin,
podemos pedir un listado sin duplicados, usando la palabra "distinct":

select distinct direccion from personas;

10.4.4. Varias tablas con SQLite desde C#


Vamos a crear un fuente de C# que ponga a prueba los ejemplos anteriores:

// EjemploSQLite3.cs
// Ejemplo de acceso a bases de datos con SQLite (3)
// Introduccin a C#, por Nacho Cabanes

using System;
// Es necesario aadir la siguiente DLL a la "referencias" del proyecto
using System.Data.SQLite;

public class EjemploSQLite3


{

public static void Crear()


{
Console.WriteLine("Creando la base de datos...");

// Creamos la conexion a la BD.


// El Data Source contiene la ruta del archivo de la BD
SQLiteConnection conexion =
new SQLiteConnection
("Data
Source=ejemplo02.sqlite;Version=3;New=True;Compress=True;");
conexion.Open();

// Creamos las tablas


Console.WriteLine(" Creando la tabla de ciudades");
string creacion = "create table ciudades ( "
+ " codigo varchar(3), nombre varchar(30),"
+ " primary key (codigo));";
SQLiteCommand cmd = new SQLiteCommand(creacion, conexion);
cmd.ExecuteNonQuery();

Console.WriteLine(" Creando la tabla de personas");


creacion = "create table personas ( "
+ " nombre varchar(20), direccion varchar(40),"
+ " edad decimal(3), codciudad varchar(3));";
cmd = new SQLiteCommand(creacion, conexion);
cmd.ExecuteNonQuery();

// 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");
Revisin 0.99zz Pgina 301
Introduccin a la programacin con C#, por Nacho Cabanes

insercion = "insert into ciudades values "


+"('b', 'barcelona');";
cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

insercion = "insert into ciudades values "


+"('m', 'madrid');";
cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

Console.WriteLine(" Introduciendo personas");


insercion = "insert into personas values "
+"('juan', 'su casa', 25, 'a');";
cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

insercion = "insert into personas values "


+"('pedro', 'su calle', 23, 'm');";
cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

insercion = "insert into personas values "


+"('alberto', 'calle uno', 22, 'b');";
cmd = new SQLiteCommand(insercion, conexion);
cantidad = cmd.ExecuteNonQuery();
if (cantidad < 1)
Console.WriteLine("No se ha podido insertar");

// Finalmente, cerramos la conexion


conexion.Close();

Console.WriteLine("Base de datos creada.");


}

public static void Mostrar()


{
// Creamos la conexion a la BD
// El Data Source contiene la ruta del archivo de la BD
SQLiteConnection conexion =
new SQLiteConnection
("Data
Source=ejemplo02.sqlite;Version=3;New=False;Compress=True;");
conexion.Open();

// Lanzamos la consulta y preparamos la estructura para leer datos


string consulta = "select personas.nombre, direccion, ciudades.nombre
"
+"from personas, ciudades where personas.codciudad =
ciudades.codigo;";
SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);
SQLiteDataReader datos = cmd.ExecuteReader();
Revisin 0.99zz Pgina 302
Introduccin a la programacin 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]));
}

// Finalmente, cerramos la conexion


conexion.Close();
}

public static void Main()


{
Crear();
Mostrar();
}
}

Su resultado sera:

Creando la base de datos...


Creando la tabla de ciudades
Creando la tabla de personas
Introduciendo ciudades
Introduciendo personas
Base de datos creada.
Datos:
juan - su casa - alicante
pedro - su calle - madrid
alberto - calle uno - barcelona

Ejercicios propuestos:
(10.4.1) Mejora el ejercicio 10.3.1 para que, adems de el nombre del fichero y su
tamao, guarde una categora (por ejemplo, "utilidad" o "vdeo"). Estas categoras
estarn almacenadas en una segunda tabla.

10.5. Borrado y modificacin de datos


Podemos borrar los datos que cumplen una cierta condicin. La orden es "delete
from", y con "where" indicamos las condiciones que se deben cumplir, de forma
similar a como hacamos en la orden "select":

delete from personas where nombre = 'juan';

Revisin 0.99zz Pgina 303


Introduccin a la programacin con C#, por Nacho Cabanes

Esto borrara todas las personas llamadas "juan" que estn almacenadas en la
tabla "personas".

Cuidado: si no se indica la parte de "where", no se borraran los datos que


cumplen una condicin, sino TODOS los datos.

Para modificar datos de una tabla, el formato habitual es "update tabla set
campo=nuevoValor where condicion".

Por ejemplo, si hemos escrito "Alberto" en minsculas ("alberto"), lo podramos


corregir con:

update personas set nombre = 'Alberto' where nombre = 'alberto';

Y si queremos corregir todas las edades para sumarles un ao se hara con

update personas set edad = edad+1;

(al igual que habamos visto para "select" y para "delete", si no indicamos la parte
del "where", los cambios se aplicarn a todos los registros de la tabla).

Ejercicios propuestos:
(10.5.1) Crea una versin del ejercicio 10.5.1 que no guarde todos los datos al salir,
sino que actualice con cada nueva modificacin: inserte los nuevos datos inme-
diatamente, permita borrar un registro (reflejando los cambios inmediatamente) y
modificar los datos de un registro (dem).

10.6. Operaciones matemticas con los datos


Desde SQL podemos realizar operaciones a partir de los datos antes de
mostrarlos. Por ejemplo, podemos mostrar cul era la edad de una persona hace
un ao, con

select edad-1 from personas;

Los operadores matemticos que podemos emplear son los habituales en


cualquier lenguaje de programacin, ligeramente ampliados: + (suma), - (resta y
negacin), * (multiplicacin), / (divisin) . La divisin calcula el resultado con
decimales; si queremos trabajar con nmeros enteros, tambin tenemos los
operadores DIV (divisin entera) y MOD (resto de la divisin):

Revisin 0.99zz Pgina 304


Introduccin a la programacin con C#, por Nacho Cabanes

select 5/2, 5 div 2, 5 mod 2;

Dara como resultado

+--------+---------+---------+
| 5/2 | 5 div 2 | 5 mod 2 |
+--------+---------+---------+
| 2.5000 | 2 | 1 |
+--------+---------+---------+

Tambin podemos aplicar ciertas funciones matemticas a todo un conjunto de


datos de una tabla. Por ejemplo, podemos saber cul es la edad ms baja de entre
las personas que tenemos en nuestra base de datos, haramos:

select min(edad) from personas;

Las "funciones de agregacin" ms habituales son:

min = mnimo valor


max = mximo valor
sum = suma de los valores
avg = media de los valores
count = cantidad de valores

La forma ms habitual de usar "count" es pidiendo con "count(*)" que se nos


muestren todos los datos que cumplen una condicin. Por ejemplo, podramos
saber cuntas personas tienen una direccin que comience por la letra "s", as:

select count(*) from personas where direccion like 's%';

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 slo la cantidad de clientes que hay registrados en nuestra
base de datos, sino tambin la cantidad de clientes que viven en cada ciudad.

La forma de obtener subtotales es creando grupos con la orden "group by", y


entonces pidiendo una valor agrupado (count, sum, avg, ...) para cada uno de esos
grupos. Por ejemplo, en nuestra tabla "personas", podramos saber cuntas
personas aparecen de cada edad, con:

Revisin 0.99zz Pgina 305


Introduccin a la programacin con C#, por Nacho Cabanes

select count(*), edad from personas group by edad;

que dara como resultado

+----------+------+
| count(*) | edad |
+----------+------+
| 1 | 22 |
| 1 | 23 |
| 1 | 25 |
+----------+------+

Pero podemos llegar ms all: podemos no trabajar con todos los grupos posibles,
sino slo con los que cumplen alguna condicin.

La condicin que se aplica a los grupos no se indica con "where", sino con "having"
(que se podra traducir como "los que tengan..."). Un ejemplo:

select count(*), edad from personas group by edad having edad > 24;

que mostrara

+----------+------+
| count(*) | edad |
+----------+------+
| 1 | 25 |
+----------+------+

En el lenguaje SQL existe mucho ms que lo que hemos visto aqu, pero para
nuestro uso desde C# y SQLite debera ser suficiente.

Ejercicios propuestos:
(10.7.1) Crea una versin del ejercicio 10.4.1 que permita saber cuntos ficheros
hay pertenecientes a cada categora.

10.8 Un ejemplo completo con C# y SQLite


Vamos a crear un pequeo ejemplo que, para una nica tabla, permita aadir
datos, mostrar todos ellos o buscar los que cumplan una cierta condicin:

// AgendaSQLite.cs
// Ejemplo de acceso a bases de datos con SQLite: agenda
// Introduccin a C#, por Nacho Cabanes

Revisin 0.99zz Pgina 306


Introduccin a la programacin con C#, por Nacho Cabanes

using System;
using System.IO; // Para File.Exists

// Es necesario aadir la siguiente DLL a la "referencias" del proyecto


using System.Data.SQLite;

public class AgendaSQLite


{
static SQLiteConnection conexion;

// Constructor
public AgendaSQLite()
{
AbrirBD();
}

// Abre la base de datos, o la crea si no existe


private void AbrirBD()
{
if (!File.Exists("agenda.sqlite"))
{
// Si no existe, creamos la base de datos
conexion =new SQLiteConnection (
"Data
Source=agenda.sqlite;Version=3;New=True;Compress=True;");
conexion.Open();

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

public bool InsertarDatos(string nombre, string direccion, int edad)


{
string insercion; // Orden de insercion, en SQL
SQLiteCommand cmd; // Comando de SQLite
int cantidad; // Resultado: cantidad de datos

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
}
Revisin 0.99zz Pgina 307
Introduccin a la programacin 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
}

// Leer todos los datos y devolverlos en un string de varias lneas


public string LeerTodosDatos()
{
// Lanzamos la consulta y preparamos la estructura para leer datos
string consulta = "select * from persona";
string resultado = "";
SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);
SQLiteDataReader datos = cmd.ExecuteReader();
// Leemos los datos de forma repetitiva
while (datos.Read())
{
resultado += Convert.ToString(datos[0]) + " - "
+ Convert.ToString(datos[1]) + " - "
+ Convert.ToInt32(datos[2]) + "\n";
}
return resultado;
}

// Leer todos los datos y devolverlos en un string de varias lneas


public string LeerBusqueda(string texto)
{
// Lanzamos la consulta y preparamos la estructura para leer datos
string consulta = "select * from persona where nombre like '%"
+ texto + "%' or direccion like '%" + texto + "%';";
string resultado = "";
SQLiteCommand cmd = new SQLiteCommand(consulta, conexion);
SQLiteDataReader datos = cmd.ExecuteReader();
// Leemos los datos de forma repetitiva
while (datos.Read())
{
resultado += Convert.ToString(datos[0]) + " - "
+ Convert.ToString(datos[1]) + " - "
+ Convert.ToInt32(datos[2]) + "\n";
}
return resultado;
}

~AgendaSQLite()
{
conexion.Close();
}

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

public class pruebaSQLite01


{
public static void Main()
{
AgendaSQLite agenda = new AgendaSQLite();
string opcion;
Revisin 0.99zz Pgina 308
Introduccin a la programacin con C#, por Nacho Cabanes

do
{
Console.WriteLine("Escoja una opcin...");
Console.WriteLine("1.- Aadir");
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("Direccin? ");
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) Ampla este ejemplo (AgendaSQLite), para que se pueda borrar un dato a
partir de su nombre (que debe coincidir exactamente).
(10.8.2) Ampla el ejemplo 10.8.1, para que se pueda modificar un registro.
(10.8.3) Ampla el ejemplo 10.8.2, para que permita exportar los datos a un fichero
de texto (que contenga el nombre, la direccin y la edad correspondientes a cada
registro en lneas separados), o bien se pueda importar datos desde un fichero de
texto (aadiendo al final de los existentes).
(10.8.4) Ampla el ejemplo 10.8.2 con una tabla de ciudades, que se deber pedir (y
mostrar) de forma independiente al resto de la direccin.

Revisin 0.99zz Pgina 309


Introduccin a la programacin con C#, por Nacho Cabanes

10.9. Nociones mnimas de acceso desde un entorno


grfico
En la mayora de los casos, cuando trabajamos desde un entorno "de ventanas"
(en un apndice de este texto vers las nociones bsicas sobre cmo crearlos),
tendremos a nuestra disposicin ciertos componentes visuales que nos permitan
"navegar" por los datos de forma muy simple.

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) ms adelante, cuando entremos en


contacto con el entorno grfico conocido como "Windows Forms".

Revisin 0.99zz Pgina 310


Introduccin a la programacin con C#, por Nacho Cabanes

11. Punteros y gestin dinmica de memoria


11.1. Por qu usar estructuras dinmicas?
Hasta ahora tenamos una serie de variables que declarbamos al principio del
programa o de cada funcin. Estas variables, que reciben el nombre de estticas,
tienen un tamao asignado desde el momento en que se crea el programa.

Este tipo de variables son sencillas de usar y rpidas... si slo vamos a manejar
estructuras de datos que no cambien, pero resultan poco eficientes si tenemos
estructuras cuyo tamao no sea siempre el mismo.

Por ejemplo, si queremos crear una agenda, necesitaremos ir aadiendo nuevos


datos. Si reservamos espacio para un mximo de 10 fichas, no podremos llegar a
aadir la nmero 11. Una solucin "tpica" (pero mala) es sobredimensionar:
preparar una agenda contando con 1000 fichas, aunque supongamos que no
vamos a pasar de 200. Esto tiene varios inconvenientes: se desperdicia memoria,
obliga a conocer bien los datos con los que vamos a trabajar, sigue pudiendo verse
sobrepasado, etc. Otra (mala) solucin sera la de trabajar siempre en el disco y
usar acceso aleatorio para leer cada ficha desde disco cuando sea necesario, pero
esta alternativa es muchsimo ms lenta.

La solucin real suele ser crear estructuras dinmicas, que puedan ir creciendo o
disminuyendo segn nos interese. En los lenguajes de programacin "clsicos",
como C y Pascal, este tipo de estructuras se tienen que crear de forma
bsicamente artesanal, mientras que en lenguajes modernos como C#, Java o las
ltimas versiones de C++, existen esqueletos ya creados que podemos utilizar con
facilidad.

Algunos ejemplos de estructuras de este tipo son:

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 tamao mximo definido, sino que puede crecer arbitrariamente.

Las colas. Una "cola de datos" se comportar como las del cine (en teora):
la gente llega por un sitio (la cola) y sale por el opuesto (la cabeza). Al igual

Revisin 0.99zz Pgina 311


Introduccin a la programacin con C#, por Nacho Cabanes

que antes, supondremos que un elemento no puede entrar a la cola ni salir


de ella en posiciones intermedias y que la cola puede crecer hasta un
tamao indefinido.

Las listas, mas verstiles pero ms complejas de programar, en las que se


puede aadir elementos en cualquier posicin y obtenerlos o borrarlos de
cualquier posicin.

Y existen otras estructuras dinmicas ms complejas y que nosotros no


trataremos, como los rboles, en los que cada elemento puede tener varios
sucesores (se parte de un elemento "raz", y la estructura se va ramificando), o los
grafos, formados por una serie de nodos unidos por aristas.

Todas estas estructuras tienen en comn que, si se programan correctamente,


pueden ir creciendo o decreciendo segn haga falta, al contrario que un array, que
tiene su tamao (mximo) prefijado. Adems, ciertas operaciones, como las
ordenaciones o los borrados, pueden ser ms rpidas que en un array.

Veremos ejemplos de cmo crear estructuras dinmicas de estos tipos en C#,


usando las posibilidades que incluye la plataforma ".Net" y despus
comentaremos los pasos para crear una estructura dinmica de forma "artesanal".

11.2. Una pila en C#


Para crear una pila, emplearemos la clase Stack. Una pila nos permitir introducir
un nuevo elemento en la cima ("apilar", en ingls "push") y quitar el elemento que
hay en la cima ("desapilar", en ingls "pop").

Este tipo de estructuras se suele denotar tambin usando las siglas "LIFO" (Last In
First Out: lo ltimo en entrar es lo primero en salir).

Para utilizar la clase "Stack" y la mayora de las que veremos en este tema,
necesitamos incluir en nuestro programa una referencia a "System.Collections".

As, un ejemplo bsico que creara una pila, introdujera tres palabras y luego las
volviera a mostrar sera:

// Ejemplo_11_02a.cs
// Ejemplo de clase "Stack" (Pila)
// Introduccin a C#, por Nacho Cabanes

using System;
Revisin 0.99zz Pgina 312
Introduccin a la programacin con C#, por Nacho Cabanes

using System.Collections;

public class Ejemplo_11_02a


{
public static void Main()
{
string palabra;

Stack miPila = new Stack();


miPila.Push("Hola,");
miPila.Push("soy");
miPila.Push("yo");

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


{
palabra = (string) miPila.Pop();
Console.WriteLine( palabra );
}
}
}

cuyo resultado sera:

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" (conversin forzada de tipos), como en palabra = (string)
miPila.Pop();

La implementacin de una pila en C# es algo ms avanzada que lo que podramos


esperar en una pila estndar: incorpora tambin mtodos como:

"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 algn detalle ms adelante.
Tambin tenemos una propiedad "Count", que nos indica cuntos
elementos contiene.

Revisin 0.99zz Pgina 313


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(11.2.1) Crea un programa que pida al usuario 5 nmeros 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 lneas 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 "notacin 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 escribira "3 4 +". Es una notacin que no necesita
parntesis y que se puede resolver usando una pila: si se recibe un dato numrico,
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 operacin y se apila su resultado. El
proceso termina cuando slo 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.
Implemntalo y comprueba si el resultado de "3 4 6 5 - + * 6 +" es 21.

11.3. Una cola en C#


Podemos crear colas si nos apoyamos en la clase Queue. En una cola podremos
introducir elementos por la cabeza ("Enqueue", encolar) y extraerlos por el
extremo opuesto, el final de la cola ("Dequeue", desencolar). Este tipo de
estructuras se nombran a veces tambin por las siglas FIFO (First In First Out, lo
primero en entrar es lo primero en salir). Un ejemplo bsico similar al anterior,
que creara una cola, introdujera tres palabras y luego las volviera a mostrar sera:

// Ejemplo_11_03a.cs
// Ejemplo de clase "Queue" (Cola)
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections;

public class Ejemplo_11_03a


{
public static void Main()
{
string palabra;

Queue miCola = new Queue();


miCola.Enqueue("Hola,");
miCola.Enqueue("soy");
miCola.Enqueue("yo");

Revisin 0.99zz Pgina 314


Introduccin a la programacin con C#, por Nacho Cabanes

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


{
palabra = (string) miCola.Dequeue();
Console.WriteLine( palabra );
}
}
}

que mostrara:

Hola,
soy
yo

Al igual que ocurra con la pila, la implementacin de una cola que incluye C# es
ms avanzada que eso, con mtodos 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 algn detalle ms adelante.
Al igual que en la pila, tambin tenemos una propiedad "Count", que nos
indica cuntos elementos contiene.

Ejercicios propuestos:
(11.3.1) Crea un programa que pida al usuario 5 nmeros reales de doble preisin,
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 vaca. En ese momento, mostrar todas las frases que se haban introducido.
(11.3.3) Crea un programa que lea el contenido de un fichero de texto, lo
almacene lnea por lnea en una cola, luego muestre este contenido en pantalla y
finalmente lo vuelque a otro fichero de texto.

Revisin 0.99zz Pgina 315


Introduccin a la programacin con C#, por Nacho Cabanes

11.4. Las listas


Una lista es una estructura dinmica en la que se puede aadir elementos sin
tantas restricciones. Es habitual que se puedan introducir nuevos datos en ambos
extremos, as como entre dos elementos existentes, o bien incluso de forma
ordenada, de modo que cada nuevo dato se introduzca automticamente en la
posicin adecuada para que todos ellos queden en orden.

En el caso de C#, no tenemos ninguna clase "List" que represente una lista
genrica, 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 aadir datos en la ltima posicin 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 mayora de
sus posibilidades:

// Ejemplo_11_04_01a.cs
// Ejemplo de ArrayList
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections;

public class Ejemplo_11_04_01a


{
public static void Main()
{
ArrayList miLista = new ArrayList();
// Aadimos en orden
miLista.Add("Hola,");
miLista.Add("soy");
miLista.Add("yo");

// Mostramos lo que contiene


Console.WriteLine( "Contenido actual:");
foreach (string frase in miLista)
Console.WriteLine( frase );

// Accedemos a una posicin


Console.WriteLine( "La segunda palabra es: {0}",
miLista[1] );

// Insertamos en la segunda posicion


miLista.Insert(1, "Como estas?");

// Mostramos de otra forma lo que contiene


Console.WriteLine( "Contenido tras insertar:");
Revisin 0.99zz Pgina 316
Introduccin a la programacin con C#, por Nacho Cabanes

for (int i=0; i<miLista.Count; i++)


Console.WriteLine( miLista[i] );

// Buscamos un elemento
Console.WriteLine( "La palabra \"yo\" est en la posicin {0}",
miLista.IndexOf("yo") );

// Ordenamos
miLista.Sort();

// Mostramos lo que contiene


Console.WriteLine( "Contenido tras ordenar");
foreach (string frase in miLista)
Console.WriteLine( frase );

// Buscamos con bsqueda binaria


Console.WriteLine( "Ahora \"yo\" est en la posicin {0}",
miLista.BinarySearch("yo") );

// Invertimos la lista
miLista.Reverse();

// Borramos el segundo dato y la palabra "yo"


miLista.RemoveAt(1);
miLista.Remove("yo");

// Mostramos nuevamente lo que contiene


Console.WriteLine( "Contenido dar la vuelta y tras eliminar dos:");
foreach (string frase in miLista)
Console.WriteLine( frase );

// Ordenamos y vemos dnde ira un nuevo dato


miLista.Sort();
Console.WriteLine( "La frase \"Hasta Luego\"...");
int posicion = miLista.BinarySearch("Hasta Luego");
if (posicion >= 0)
Console.WriteLine( "Est en la posicin {0}", posicion );
else
Console.WriteLine( "No est. El dato inmediatamente mayor "+
"es el {0}: {1}",
~posicion, miLista[~posicion] );
}
}

El resultado de este programa es:

Contenido actual:
Hola,
soy
yo
La segunda palabra es: soy
Contenido tras insertar:
Hola,
Como estas?
soy
yo
La palabra "yo" est en la posicin 3
Revisin 0.99zz Pgina 317
Introduccin a la programacin con C#, por Nacho Cabanes

Contenido tras ordenar


Como estas?
Hola,
soy
yo
Ahora "yo" est en la posicin 3
Contenido dar la vuelta y tras eliminar dos:
Hola,
Como estas?
La frase "Hasta Luego"...
No est. El dato inmediatamente mayor es el 1: Hola,

Casi todo debera resultar fcil de entender, salvo quiz el smbolo ~. Esto se debe
a que BinarySearch devuelve un nmero negativo cuando el texto que buscamos
no aparece, pero ese nmero negativo tiene un significado: es el "valor
complementario" de la posicin del dato inmediatamente mayor (es decir, el dato
cambiando los bits 0 por 1 y viceversa). En el ejemplo anterior, "posicin" vale -2, lo
que quiere decir que el dato no existe, y que el dato inmediatamente mayor est
en la posicin 1 (que es el "complemento a 2" del nmero -2, que es lo que indica
la expresin "~posicin"). En el apndice 3 de este texto hablaremos de cmo se
representan internamente los nmeros enteros, tanto positivos como negativos, y
entonces se ver con detalle en qu consiste el "complemento a 2".

A efectos prcticos, lo que nos interesa es que si quisiramos insertar la frase


"Hasta Luego", su posicin correcta para que todo el ArrayList permaneciera
ordenado sera la 1, que viene indicada por "~posicion".

Veremos los operadores a nivel de bits, como ~, en el tema 13, que estar
dedicado a otras caractersticas avanzadas de C#.

Ejercicios propuestos:
(11.4.1.1) Crea un programa que lea el contenido de un fichero de texto, lo
almacene lnea por lnea en un ArrayList, y luego pregunte de forma repetitiva al
usuario qu lnea 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 lnea por lnea en un ArrayList, y luego pregunte de forma repetitiva al
usuario qu texto desea buscar y muestre las lneas que contienen ese texto.
Terminar cuando el usuario introduzca una cadena vaca.
(11.4.1.3) Crea un programa que lea el contenido de un fichero de texto, lo
almacene lnea por lnea 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 lnea por lnea en un ArrayList, luego muestre en pantalla las lneas
Revisin 0.99zz Pgina 318
Introduccin a la programacin con C#, por Nacho Cabanes

impares (primera, tercera, etc.) y finalmente vuelque a otro fichero de texto las
lneas pares (segunda, cuarta, etc.).
(11.4.1.5) Crea una nueva versin 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 estn formados por una pareja: una clave y un
valor (como en un diccionario: la palabra y su definicin). Se puede aadir
elementos con "Add", o acceder a los elementos mediante su ndice numrico (con
"GetKey") o mediante su clave (con corchetes), como en este ejemplo:

// Ejemplo_11_04_02a.cs
// Ejemplo de SortedList: Diccionario esp-ing
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections;

public class Ejemplo_11_04_02a


{
public static void Main()
{
// Creamos e insertamos datos
SortedList miDiccio = new SortedList();
miDiccio.Add("hola", "hello");
miDiccio.Add("adis", "good bye");
miDiccio.Add("hasta luego", "see you later");

// Mostramos los datos


Console.WriteLine( "Cantidad de palabras en el diccionario: {0}",
miDiccio.Count );
Console.WriteLine( "Lista de palabras y su significado:" );
for (int i=0; i<miDiccio.Count; i++)
{
Console.WriteLine( "{0} = {1}",
miDiccio.GetKey(i), miDiccio.GetByIndex(i) );
}
Console.WriteLine( "Traduccin de \"hola\": {0}",
miDiccio.GetByIndex( miDiccio.IndexOfKey("hola") ));
Console.WriteLine( "Que tambin se puede obtener con corchetes: {0}",
miDiccio["hola"]);
}
}

Su resultado sera

Cantidad de palabras en el diccionario: 3


Lista de palabras y su significado:
adis = good bye
Revisin 0.99zz Pgina 319
Introduccin a la programacin con C#, por Nacho Cabanes

hasta luego = see you later


hola = hello
Traduccin de "hola": hello

Otras posibilidades de la clase SortedList son:


"Contains", para ver si la lista contiene una cierta clave.
"ContainsValue", para ver si la lista contiene un cierto valor.
"Remove", para eliminar un elemento a partir de su clave.
"RemoveAt", para eliminar un elemento a partir de su posicin.
"SetByIndex", para cambiar el valor que hay en una cierta posicin.

Ejercicios propuestos:
(11.4.2.1) Crea un programa que, cuando el usuario introduzca el nombre de un
nmero del 1 al 10 en ingls (por ejemplo, "two"), diga su traduccin en espaol
(por ejemplo, "dos").
(11.4.2.2) Crea un traductor bsico 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 vaca. salo para convertir un abrir un fichero que contenga un fuente en
C# y mostrar su equivalente (que no ser perfecto) en Pascal.

11.5. Las "tablas hash"


En una "tabla hash", los elementos estn formados por una pareja: una clave y un
valor, como en un SortedList, pero la diferencia est en la forma en que se
manejan internamente estos datos: la "tabla hash" usa una "funcin de
dispersin" para colocar los elementos, de forma que no se pueden recorrer
secuencialmente y ocupan ms espacio, pero a cambio el acceso a partir de la
clave es muy rpido, ms que si hacemos una bsqueda secuencial (como en un
array) o binaria (como en un ArrayList ordenado).

Un ejemplo de diccionario, parecido al anterior (que es ms rpido de consultar


para un dato concreto, pero que no se puede recorrer en orden), podra ser:

// Ejemplo_11_05a.cs
// Ejemplo de Tabla Hash: Diccionario de informtica
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections;

public class Ejemplo_11_05a


{
Revisin 0.99zz Pgina 320
Introduccin a la programacin con C#, por Nacho Cabanes

public static void Main()


{
// Creamos e insertamos datos
Hashtable miDiccio = new Hashtable();
miDiccio.Add("byte", "8 bits");
miDiccio.Add("pc", "personal computer");
miDiccio.Add("kilobyte", "1024 bytes");

// Mostramos algn dato


Console.WriteLine( "Cantidad de palabras en el diccionario: {0}",
miDiccio.Count );
try
{
Console.WriteLine( "El significado de PC es: {0}",
miDiccio["pc"]);
}
catch (Exception)
{
Console.WriteLine( "No existe esa palabra!");
}
}
}

que escribira en pantalla:

Cantidad de palabras en el diccionario: 3


El significado de PC es: personal computer

Si un elemento que se busca no existe, se lanzara una excepcin, por lo que


deberamos controlarlo con un bloque try-catch. Lo mismo ocurre si intentamos
introducir un dato que ya existe. Una alternativa a usar try-catch es comprobar si
el dato ya existe, con el mtodo "Contains" (o su sinnimo "ContainsKey"), como
en este ejemplo:

// Ejemplo_11_05b.cs
// Ejemplo de Tabla Hash: Diccionario de informtica
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections;

public class Ejemplo_11_05b


{
public static void Main()
{
// Creamos e insertamos datos
Hashtable miDiccio = new Hashtable();
miDiccio.Add("byte", "8 bits");
miDiccio.Add("pc", "personal computer");
miDiccio.Add("kilobyte", "1024 bytes");

// Mostramos algn dato


Console.WriteLine( "Cantidad de palabras en el diccionario: {0}",
miDiccio.Count );
if (miDiccio.Contains("pc"))
Revisin 0.99zz Pgina 321
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine( "El significado de PC es: {0}",


miDiccio["pc"]);
else
Console.WriteLine( "No existe la palabra PC");
}
}

Otras posibilidades son: borrar un elemento ("Remove"), vaciar toda la tabla


("Clear"), o ver si contiene un cierto valor ("ContainsValue", mucho ms lento que
buscar entre las claves con "Contains").

Una tabla hash tiene una cierta capacidad inicial, que se ampla automticamente
cuando es necesario. Como la tabla hash es mucho ms rpida cuando est
bastante vaca 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 cuntos datos vamos a guardar:

Hashtable miDiccio = new Hashtable(500);

Ejercicios propuestos:
(11.5.1) Crea una versin alternativa del ejercicio 11.4.2.1, pero que tenga las
traducciones almacenadas en una tabla Hash.
(11.5.2) Crea una versin alternativa del ejercicio 11.4.2.2, pero que tenga las
traducciones almacenadas en una tabla Hash.

11.6. Los "enumeradores"


Un enumerador es una estructura auxiliar que permite recorrer las estructuras
dinmicas de forma secuencial. Casi todas ellas contienen un mtodo
GetEnumerator, que permite obtener un enumerador para recorrer todos sus
elementos. Por ejemplo, en una tabla hash podramos hacer:

// Ejemplo_11_06a.cs
// Ejemplo de Enumeradores en una Tabla Hash
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections;

public class Ejemplo_11_06a


{
public static void Main()
{
// Creamos e insertamos datos
Hashtable miDiccio = new Hashtable();
miDiccio.Add("byte", "8 bits");
Revisin 0.99zz Pgina 322
Introduccin a la programacin con C#, por Nacho Cabanes

miDiccio.Add("pc", "personal computer");


miDiccio.Add("kilobyte", "1024 bytes");

// Mostramos todos los datos


Console.WriteLine("Contenido:");
IDictionaryEnumerator miEnumerador = miDiccio.GetEnumerator();
while ( miEnumerador.MoveNext() )
Console.WriteLine("{0} = {1}",
miEnumerador.Key, miEnumerador.Value);
}
}

cuyo resultado es

Contenido:
pc = personal computer
byte = 8 bits
kilobyte = 1024 bytes

Como se puede ver, los enumeradores tendrn un mtodo "MoveNext", que


intenta moverse al siguiente elemento y devuelve "false" si no lo consigue. En el
caso de las tablas hash, que tiene dos campos (clave y valor), el enumerador a usar
ser un "enumerador de diccionario" (IDictionaryEnumerator), que contiene los
campos Key y Value.

Como se ve en el ejemplo, es habitual que no obtengamos la lista de elementos en


el mismo orden en el que los introdujimos, debido a que se colocan siguiendo la
funcin de dispersin.

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)
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections;

public class Ejemplo_11_06b


{
public static void Main()
{
Stack miPila = new Stack();
miPila.Push("Hola,");
miPila.Push("soy");
miPila.Push("yo");

// Mostramos todos los datos


Console.WriteLine("Contenido:");
IEnumerator miEnumerador = miPila.GetEnumerator();
Revisin 0.99zz Pgina 323
Introduccin a la programacin con C#, por Nacho Cabanes

while ( miEnumerador.MoveNext() )
Console.WriteLine("{0}", miEnumerador.Current);
}
}

que escribira

Contenido:
yo
soy
Hola,

Nota: los "enumeradores" existen tambin en otras plataformas, como Java,


aunque all reciben el nombre de "iteradores".

Se puede saber ms sobre las estructuras dinmicas que hay disponibles en la


plataforma .Net consultando la referencia en lnea de MSDN (mucha de la cual est
sin traducir al espaol):

http://msdn.microsoft.com/es-es/library/system.collections(en-us,VS.71).aspx#

11.7. Cmo "imitar" una pila usando "arrays"


Las estructuras dinmicas se pueden imitar usando estructuras estticas
sobredimensionadas, y esto puede ser un ejercicio de programacin interesante.
Por ejemplo, podramos imitar una pila dando los siguientes pasos:

Utilizamos internamente un array ms grande que la cantidad de datos que


esperemos que vaya a almacenar la pila.
Creamos una funcin "Apilar", que aade en la primera posicin libre del
array (inicialmente la 0) y despus incrementa esa posicin, para que el
siguiente dato se introduzca a continuacin.
Creamos tambin una funcin "Desapilar", que devuelve el dato que hay en
la ltima posicin, y que disminuye el contador que indica la posicin, de
modo que el siguiente dato que se obtuviera sera el que se introdujo con
anterioridad a ste.

El fuente podra ser as:

// Ejemplo_11_07a.cs
// Ejemplo de clase "Pila" basada en un array
// Introduccin a C#, por Nacho Cabanes

using System;

Revisin 0.99zz Pgina 324


Introduccin a la programacin con C#, por Nacho Cabanes

public class PilaString


{
string[] datosPila;
int posicionPila;
const int MAXPILA = 200;

// Constructor
public PilaString()
{
posicionPila = 0;
datosPila = new string[MAXPILA];
}

// Aadir a la pila: Apilar


public void Apilar(string nuevoDato)
{
if (posicionPila == MAXPILA)
Console.WriteLine("Pila llena!");
else
{
datosPila[posicionPila] = nuevoDato;
posicionPila ++;
}
}

// Extraer de la pila: Desapilar


public string Desapilar()
{
if (posicionPila < 0)
Console.WriteLine("Pila vacia!");
else
{
posicionPila --;
return datosPila[posicionPila];
}
return null;
}

public static void Main()


{
string palabra;

PilaString miPila = new PilaString();


miPila.Apilar("Hola,");
miPila.Apilar("soy");
miPila.Apilar("yo");

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


{
palabra = (string) miPila.Desapilar();
Console.WriteLine( palabra );
}
} // Fin da Main de prueba

} // Fin de la clase

Revisin 0.99zz Pgina 325


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(11.7.1) Usando esta misma estructura de programa, crea una clase "Cola", que
permita introducir datos (nmeros enteros) y obtenerlos en modo FIFO (el primer
dato que se introduzca debe ser el primero que se obtenga). Debe tener un
mtodo "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 mtodo "Insertar", que aadir
un nuevo dato en orden en el array, y un "Extraer(n)", que obtenga un elemento de
la lista (el nmero "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.

11.8. Introduccin a los "generics"


Una ventaja, pero tambin a la vez un inconveniente, de las estructuras dinmicas
que hemos visto, es que permiten guardar datos de cualquier tipo, incluso datos
de distinto tipo en una misma estructura: un ArrayList que contenga primero un
string, luego un nmero entero, luego uno de coma flotante, despus un struct
Esto obliga a que hagamos una "conversin de tipos" con cada dato que
obtenemos (excepto con los "strings").

En ocasiones puede ser interesante algo un poco ms rgido, que con las ventajas
de un ArrayList (crecimiento dinmico, mltiples mtodos disponibles) est
adaptado a un tipo de datos, y no necesite una conversin de tipos cada vez que
extraigamos un dato.

Por ello, en la versin 2 de la "plataforma .Net" se introdujeron los "generics", que


definen estructuras de datos genricas, que nosotros podemos particularizar en
cada uso. Por ejemplo, una lista de strings se definira con:

List<string> miLista = new List<string>();

Y necesitaramos incluir un nuevo "using" al principio del programa:

using System.Collections.Generic;

Con slo estos dos cambios, el ejemplo de uso de ArrayList que vimos en el
apartado 11.4.1 funcionara perfectamente:
Revisin 0.99zz Pgina 326
Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_11_08a.cs
// Ejemplo de List<string>
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections.Generic;

public class Ejemplo_11_08a


{
public static void Main()
{
List<string> miLista = new List<string>();
// Aadimos en orden
miLista.Add("Hola,");
miLista.Add("soy");
miLista.Add("yo");

// Mostramos lo que contiene


Console.WriteLine( "Contenido actual:");
foreach (string frase in miLista)
Console.WriteLine( frase );

// Accedemos a una posicin


Console.WriteLine( "La segunda palabra es: {0}",
miLista[1] );

// Insertamos en la segunda posicion


miLista.Insert(1, "Como estas?");

// Mostramos de otra forma lo que contiene


Console.WriteLine( "Contenido tras insertar:");
for (int i=0; i<miLista.Count; i++)
Console.WriteLine( miLista[i] );

// Buscamos un elemento
Console.WriteLine( "La palabra \"yo\" est en la posicin {0}",
miLista.IndexOf("yo") );

// Ordenamos
miLista.Sort();

// Mostramos lo que contiene


Console.WriteLine( "Contenido tras ordenar");
foreach (string frase in miLista)
Console.WriteLine( frase );

// Buscamos con bsqueda binaria


Console.WriteLine( "Ahora \"yo\" est en la posicin {0}",
miLista.BinarySearch("yo") );

// Invertimos la lista
miLista.Reverse();

// Borramos el segundo dato y la palabra "yo"


miLista.RemoveAt(1);
miLista.Remove("yo");

// Mostramos nuevamente lo que contiene


Console.WriteLine( "Contenido dar la vuelta y tras eliminar dos:");
foreach (string frase in miLista)
Revisin 0.99zz Pgina 327
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine( frase );

// Ordenamos y vemos dnde ira un nuevo dato


miLista.Sort();
Console.WriteLine( "La frase \"Hasta Luego\"...");
int posicion = miLista.BinarySearch("Hasta Luego");
if (posicion >= 0)
Console.WriteLine( "Est en la posicin {0}", posicion );
else
Console.WriteLine( "No est. El dato inmediatamente mayor "+
"es el {0}: {1}",
~posicion, miLista[~posicion] );
}
}

De esta misma forma, podramos crear una lista de structs, o de objetos, o de


cualquier otro dato.

No slo tenemos listas. Por ejemplo, tambin 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 nmero entero como valor obtenido:

Dictionary<string, int> dict = new Dictionary<string, int>();

As, con un diccionario que tenga tanto claves string como valores string,
podramos crear una versin alternativa del ejemplo 11_05b. Los nicos cambios
seran una declaracin parecida a la anterior, el "using" correcto, y cambiar
Contains por ContainsKey:

// Ejemplo_11_08b.cs
// Ejemplo de Dictionary
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Collections.Generic;

public class Ejemplo_11_08b


{
public static void Main()
{
// Creamos e insertamos datos
Dictionary<string, string> miDiccio = new Dictionary<string,
string>();
miDiccio.Add("byte", "8 bits");
miDiccio.Add("pc", "personal computer");
miDiccio.Add("kilobyte", "1024 bytes");

// Mostramos algn dato


Console.WriteLine( "Cantidad de palabras en el diccionario: {0}",
miDiccio.Count );
Revisin 0.99zz Pgina 328
Introduccin a la programacin con C#, por Nacho Cabanes

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 versin 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 lnea a la
primera).

11.9. Los punteros en C#


11.9.1. Qu es un puntero?
La palabra "puntero" se usa para referirse a una direccin de memoria. Lo que
tiene de especial es que normalmente un puntero tendr un tipo de datos
asociado: por ejemplo, un "puntero a entero" ser una direccin de memoria en la
que habr almacenado (o podremos almacenar) un nmero entero.

El hecho de poder acceder directamente al contenido de ciertas posiciones de


memoria da una mayor versatilidad a un programa, porque permite hacer casi
cualquier cosa, pero a cambio de un riesgo de errores mucho mayor.

En lenguajes como C, es imprescindible utilizar punteros para poder crear


estructuras dinmicas, pero en C# podemos "esquivarlos", dado que tenemos
varias estructuras dinmicas ya creadas como parte de las bibliotecas auxiliares
que acompaan al lenguaje bsico. Aun as, veremos algn ejemplo que nos
muestre qu es un puntero y cmo se utiliza.

En primer lugar, comentemos la sintaxis bsica que utilizaremos:

int numero; /* "numero" es un nmero entero */


int* posicion; /* "posicion" es un "puntero a entero" (direccin de
memoria en la que podremos guardar un entero) */

Revisin 0.99zz Pgina 329


Introduccin a la programacin con C#, por Nacho Cabanes

Es decir, escribiremos un asterisco entre el tipo de datos y el nombre de la


variable. Ese asterisco puede ir junto a cualquiera de ambos, por lo que tambin
es correcto escribir

int *posicion;

El valor que guarda "posicion" es una direccin de memoria. Generalmente no


podremos hacer cosas como posicion=5; porque nada nos garantiza que la
posicin 5 de la memoria est disponible para que nosotros la usemos. Ser ms
habitual que tomemos una direccin de memoria que ya contiene otro dato, o
bien que le pidamos al compilador que nos reserve un espacio de memoria (ms
adelante veremos cmo).

Si queremos que "posicion" contenga la direccin de memoria que el compilador


haba reservado para la variable "numero", lo haramos usando el smbolo "&", as:

posicion = &numero;

11.9.2. Zonas "inseguras": unsafe


Como los punteros son "peligrosos" (es frecuente que den lugar a errores muy
difciles de encontrar), el compilador nos obligamos a que le digamos que
sabemos que esa zona de programa no es segura, usando la palabra "unsafe":

unsafe static void pruebaPunteros() { ...

Es ms, si intentamos compilar obtendremos un mensaje de error, que nos dice


que si nuestro cdigo no es seguro, deberemos compilarlo con la opcin "unsafe":

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 opcin /unsafe como forma de decir al
compilador "s, s que este programa tiene zonas no seguras, pero aun as quiero
compilarlo":

mcs unsafe1.cs /unsafe

Revisin 0.99zz Pgina 330


Introduccin a la programacin con C#, por Nacho Cabanes

Si usamos Visual Studio como herramienta de desarrollo, tendremos que ir a las


"propiedades del proyecto" (tpicamente estar al final del men "Proyecto") y
decir que queremos "Permitir cdigo no seguro":

11.9.3. Uso bsico de punteros


Veamos un ejemplo bsico de cmo dar valor a un puntero y de cmo guardar un
valor en l:

// Ejemplo_11_09_03a.cs
// Primer ejemplo de punteros
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_11_09_03a


{
private unsafe static void pruebaPunteros()
{
int* punteroAEntero;
int x;

// Damos un valor a x
x = 2;
// punteroAEntero ser la direccin de memoria de x
punteroAEntero = &x;
// Los dos estn en la misma direccin:
Console.WriteLine("x vale {0}", x);
Console.WriteLine("En punteroAEntero hay un {0}", *punteroAEntero);

// Ahora cambiamos el valor guardado en punteroAEntero


*punteroAEntero = 5;
// Y x se modifica tambin:
Console.WriteLine("x vale {0}", x);
Console.WriteLine("En punteroAEntero hay un {0}", *punteroAEntero);
}
Revisin 0.99zz Pgina 331
Introduccin a la programacin con C#, por Nacho Cabanes

public static void Main()


{
pruebaPunteros();
}
}

La salida de este programa es:

x vale 2
En punteroAEntero hay un 2
x vale 5
En punteroAEntero hay un 5

Es decir, cada cambio que hacemos en "x" se refleja en "punteroAEntero" y


viceversa.

Ejercicios propuestos:
(11.9.3.1) Crea un programa que intercambie la posicin 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.

11.9.4. Zonas inseguras


Tambin podemos hacer que slo una parte de un programa sea insegura,
indicando entre llaves una parte de una funcin:

// Ejemplo_11_09_04a.cs
// Segundo ejemplo de punteros
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_11_09_04a


{
public unsafe static void Incrementar(int* p)
{
//Incrementamos el entero "apuntado" por p
*p = *p + 1;
}

public static void Main()


{
int i = 1;

// sta es la parte insegura de "Main"


unsafe
{
// La funcin espera un puntero, as que le pasamos
// la direccin de memoria del entero:
Revisin 0.99zz Pgina 332
Introduccin a la programacin con C#, por Nacho Cabanes

Incrementar(&i);
}

// Y mostramos el resultado
Console.WriteLine (i);
}
}

11.9.5. Reservar espacio: stackalloc


Podemos reservar espacio para una variable dinmica usando "stackalloc". Por
ejemplo, una forma alternativa de crear un array de enteros sera sta:

int* datos = stackalloc int[5];

Un ejemplo completo podra ser:

// Ejemplo_11_09_05a.cs
// Tercer ejemplo de punteros: stackalloc
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_11_09_05a


{
public unsafe static void Main()
{
const int tamanyoArray = 5;
int* datos = stackalloc int[tamanyoArray];

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

Existen ciertas diferencias entre esta forma de trabajar y la que ya conocamos: la


memoria se reserva en la pila (stack), en vez de hacerlo en la zona de memoria
"habitual", conocida como "heap" o montn, pero es un detalle sobre el que no
vamos a profundizar.

Ejercicios propuestos:

Revisin 0.99zz Pgina 333


Introduccin a la programacin con C#, por Nacho Cabanes

(11.9.5.1) Crea una programa que pida al usuario 5 strings, los almacene usando
"stackalloc" y luego los muestre en orden inverso.

11.9.6. Aritmtica de punteros


Si aumentamos o disminuimos el valor de un puntero, cambiar la posicin que
representa... pero no cambiar de uno en uno, sino que saltar a la siguiente
posicin capaz de almacenar un dato como el que corresponde a su tipo base. Por
ejemplo, si un puntero a entero tiene como valor 40.000 y hacemos "puntero++",
su direccin pasar a ser 40.004 (porque cada entero ocupa 4 bytes). Vamos a
verlo con un ejemplo:

// Ejemplo_11_09_06a.cs
// Cuarto ejemplo de punteros: aritmtica de punteros
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_11_09_06a


{
public unsafe static void Main()
{
const int tamanyoArray = 5;
int* datos = stackalloc int[tamanyoArray];
int* posicion = datos;

Console.WriteLine("Posicion actual: {0}", (int) posicion);

// Ponemos un 0 en el primer dato


*datos = 0;

// Rellenamos los dems con 10,20,30...


for (int i = 1; i < tamanyoArray; i++)
{
posicion++;
Console.WriteLine("Posicion actual: {0}", (int) posicion);
*posicion = i * 10;
}

// Finalmente mostramos el array


Console.WriteLine("Contenido:");
for (int i = 0; i < tamanyoArray; i++)
{
Console.WriteLine(datos[i]);
}
}
}

El resultado sera algo parecido (porque las direcciones de memoria que


obtengamos no tienen por qu ser las mismas) a:

Posicion actual: 1242196


Revisin 0.99zz Pgina 334
Introduccin a la programacin con C#, por Nacho Cabanes

Posicion actual: 1242200


Posicion actual: 1242204
Posicion actual: 1242208
Posicion actual: 1242212
Contenido:
0
10
20
30
40

Ejercicios propuestos:
(11.9.6.1) Crea una programa que pida al usuario 4 nmeros reales, los almacene
usando "stackalloc" y luego los recorra incrementando el puntero a partir de la
posicin del primer elemento.

11.9.7. La palabra "fixed"


C# cuenta con un "recolector de basura", que se encarga de liberar el espacio
ocupado por variables que ya no se usan. Esto puede suponer algn problema
cuando usamos variables dinmicas, porque estemos accediendo a una posicin
de memoria que el entorno de ejecucin haya previsto que ya no necesitaramos
y haya borrado.

Por eso, en ciertas ocasiones el compilador puede avisarnos de que algunas


asignaciones son peligrosas y obligar a que usemos la palabra "fixed" para indicar
al compilador que esa zona "no debe limpiarse automticamente".

Esta palabra se usa antes de la declaracin y asignacin de la variable, y la zona de


programa que queremos "bloquear" se indica entre llaves:

// Ejemplo_11_09_07a.cs
// Quinto ejemplo de punteros: fixed
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_11_09_07a


{
public unsafe static void Main()
{
int[] datos={10,20,30};

Console.WriteLine("Leyendo el segundo dato...");


fixed (int* posicionDato = &datos[1])
{
Console.WriteLine("En posicionDato hay {0}",
Revisin 0.99zz Pgina 335
Introduccin a la programacin con C#, por Nacho Cabanes

*posicionDato);
}

Console.WriteLine("Leyendo el primer dato...");


fixed (int* posicionDato = datos)
{
Console.WriteLine("Ahora en posicionDato hay {0}",
*posicionDato);
}
}
}

Como se ve en el programa anterior, en una zona "fixed" no se puede modificar el


valor de esa variables; si lo intentamos recibiremos un mensaje de error que nos
avisa de que esa variable es de "slo lectura" (read-only). Por eso, para cambiarla,
tendremos que empezar otra nueva zona "fixed".

El resultado del ejemplo anterior sera:

Leyendo el segundo dato...


En posicionDato hay 20
Leyendo el primer dato...
Ahora en posicionDato hay 10

Revisin 0.99zz Pgina 336


Introduccin a la programacin con C#, por Nacho Cabanes

12. Algunas bibliotecas ad icionales de uso


frecuente
12.1. Fecha y hora. Temporizacin
Desde C#, tenemos la posibilidad de manejar fechas y horas con facilidad. Para
ello, tenemos el tipo de datos DateTime. Por ejemplo, podemos hallar la fecha (y
hora) actual con:

DateTime fecha = DateTime.Now;

Dentro de ese tipo de datos DateTime, tenemos las herramientas para saber el da
(Day), el mes (Month) o el ao (Year) de una fecha, entre otros. Tambin podemos
calcular otras fechas sumando a la actual una cierta cantidad de segundos
(AddSeconds), das (AddDays), etc. Un ejemplo bsico de su uso sera:

// Ejemplo_12_01a.cs
// Ejemplo bsico de manejo de fechas
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_12_01a


{
public static void Main()
{
DateTime fecha = DateTime.Now;
Console.WriteLine("Hoy es {0} del mes {1} de {2}",
fecha.Day, fecha.Month, fecha.Year);
DateTime manyana = fecha.AddDays(1);
Console.WriteLine("Maana ser {0}",
manyana.Day);
}
}

Algunos de las propiedades ms tiles de este tipo de datos son: Now (fecha y
hora actual de este equipo), Today (fecha actual); Day (da del mes), Month
(nmero de mes), Year (ao); Hour (hora), Minute (minutos), Second (segundos),
Millisecond (milisegundos); DayOfWeek (da de la semana: su nombre en ingls,
que se puede convertir en un nmero del 0-domingo- al 6-sbado- si se fuerza su
tipo a entero, anteponindole "(int)"); DayOfYear (da del ao).

Para calcular nuevas fechas, podemos usar mtodos que generan un nuevo
DateTime, como: AddDays (que aparece en el ejemplo anterior), AddHours,
AddMilliseconds, AddMinutes, AddMonths, AddSeconds, AddHours, o bien un Add
Revisin 0.99zz Pgina 337
Introduccin a la programacin con C#, por Nacho Cabanes

ms genrico (para sumar una fecha a otra) y un Subtract tambin genrico (para
restar una fecha de otra).

Cuando restamos dos fechas, obtenemos un dato de tipo "intervalo de tiempo"


(TimeSpan), del que podemos saber detalles como la cantidad de das (sin
decimales, Days, o con decimales, TotalDays), como se ve en este ejemplo:

// Ejemplo_12_01b.cs
// Diferencia entre dos fechas
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_12_01b


{
public static void Main()
{
DateTime fechaActual = DateTime.Now;
DateTime fechaNacimiento = new DateTime(1990, 9, 18);

TimeSpan diferencia =
fechaActual.Subtract(fechaNacimiento) ;

Console.WriteLine("Han pasado {0} das",


diferencia.Days);

Console.WriteLine("Si lo quieres con decimales: {0} das",


diferencia.TotalDays);

Console.WriteLine("Y si quieres ms detalles: {0}",


diferencia);
}
}

El resultado de este programa sera algo como

Han pasado 8886 das


Si lo quieres con decimales: 8886,41919806277 das
Y si quieres ms detalles: 8886.10:03:38.7126236

Tambin podemos hacer una pausa en la ejecucin: Si necesitamos que nuestro


programa se detenga una cierta cantidad de tiempo, no hace falta que usemos un
"while" que compruebe la hora continuamente, sino que podemos "bloquear"
(Sleep) el subproceso (o hilo, "Thread") que representa nuestro programa una
cierta cantidad de milsimas de segundo con: Thread.Sleep(5000);

Este mtodo pertenece a System.Threading, que deberamos incluir en nuestro


apartado de"using", o bien usar la llamada completa:

Revisin 0.99zz Pgina 338


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_12_01c.cs
// Pausas
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Threading;

public class Ejemplo_12_01c


{
public static void Main()
{
DateTime fecha = DateTime.Now;
Console.WriteLine("Son las {0}:{1}:{2}",
fecha.Hour, fecha.Minute, fecha.Second);
Console.WriteLine("Vamos a esperar 3 segundos...");
Thread.Sleep(3000);
fecha = DateTime.Now;
Console.WriteLine("Ahora son las {0}:{1}:{2}",
fecha.Hour, fecha.Minute, fecha.Second);
}
}

Ejercicios propuestos:
(12.1.1) Crea una versin mejorada del ejemplo 12_01a, que muestre el nombre
del mes (usa un array para almacenar los nombres y accede a la posicin
correspondiente), la hora, los minutos, los segundos y las dcimas de segundo (no
las milsimas). Si los minutos o los segundos son inferiores a 10, debern
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 aproximacin, el reloj se escribir con
"WriteLine", de modo que aparecer en la primera lnea de pantalla, luego en la
segunda, luego en la tercera y as sucesivamente (en el prximo apartado veremos
cmo 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 da de la semana se trataba.
(12.1.4) Crea un programa que muestre el calendario del mes actual (pista:
primero debers calcular qu da de la semana es el da 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

Revisin 0.99zz Pgina 339


Introduccin a la programacin con C#, por Nacho Cabanes

(12.1.5) Crea un programa que muestre el calendario correspondiente al mes y el


ao que se indiquen como parmetros en lnea de comandos.
(12.1.6) Crea un programa que vuelque a un fichero de texto el calendario del ao
que se indique como parmetro en lnea de comandos. Deben mostrarse tres
meses en anchura: el primer bloque contendr enero febrero y marzo, el segundo
tendr abril, mayo y junio y as sucesivamente.

12.2. Ms posibilidades de la "consola"


En "Console" hay mucho ms que ReadLine y WriteLine, aunque no todas las
posibilidades estn incluidas en las primeras versiones de la "plataforma punto
net". Vamos a ver algunas de las funcionalidades adicionales que nos pueden
resultar ms tiles:

Clear: borra la pantalla.


ForegroundColor: cambia el color de primer plano (para indicar los colores,
hay definidas constantes como "ConsoleColor.Black", que se detallan al
final de este apartado).
BackgroundColor: cambia el color de fondo (para el texto que se escriba a
partir de entonces; si se quiere borrar la pantalla con un cierto color, se
deber primero cambiar el color de fondo y despus usar "Clear").
SetCursorPosition(x, y): cambia la posicin del cursor ("x" se empieza a
contar desde el margen izquierdo, e "y" desde la parte superior de la
pantalla).
Readkey(interceptar): lee una tecla desde teclado. El parmetro
"interceptar" es un "bool", y es opcional. Indica si se debe capturar la tecla
sin permitir que se vea en pantalla ("true" para que no se vea, "false" para
que se pueda ver). Si no se indica este parmetro, la tecla se muestra en
pantalla.
KeyAvailable: indica si hay alguna tecla disponible para ser leda (es un
"bool")
Title: el ttulo que se va a mostrar en la consola (es un "string")

// Ejemplo_12_02a.cs
// Ms posibilidades de "System.Console"
// Introduccin a C#, por Nacho Cabanes

using System;

public class Ejemplo_12_02a


{
public static void Main()
Revisin 0.99zz Pgina 340
Introduccin a la programacin con C#, por Nacho Cabanes

{
int posX, posY;

Console.Title = "Ejemplo de consola";


Console.BackgroundColor = ConsoleColor.Green;
Console.ForegroundColor = ConsoleColor.Black;
Console.Clear();

posY = 10; // En la fila 10


Random r = new Random(DateTime.Now.Millisecond);
posX = r.Next(20, 40); // Columna al azar entre 20 y 40
Console.SetCursorPosition(posX, posY);
Console.WriteLine("Bienvenido");

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

int maxY = Console.WindowHeight;


int maxX = Console.WindowWidth;
Console.SetCursorPosition(maxX-50, maxY-1);
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("Pulsa una tecla para terminar... ");
Console.ReadKey(true);
}
}

(Nota: no todas las posibilidades que se aplican en este ejemplo estn disponibles
en la plataforma .Net 1.x, sino a partir de la versin 2).

Para comprobar el valor de una tecla, como se ve en el ejemplo anterior, tenemos


que usar una variable de tipo "ConsoleKeyInfo" (informacin de tecla de consola).
Un ConsoleKeyInfo tiene campos como:

KeyChar, que representa el carcter que se escribira al pulsar esa tecla. Por
ejemplo, podramos hacer if (tecla.KeyChar == '1') ...
Key, que se refiere a la tecla (porque hay teclas que no tienen un carcter
visualizable, como F1 o las teclas de cursor). Por ejemplo, para comprobar
la tecla ESC podramos hacer if (tecla.Key == ConsoleKey.Escape) ... .
Algunos de los cdigos de tecla disponibles son:
o Teclas de edicin 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))
Revisin 0.99zz Pgina 341
Introduccin a la programacin con C#, por Nacho Cabanes

o Teclas de movimiento del cursor, como: PageUp (Tecla RE PG),


PageDown (Tecla AV PG), End (Tecla FIN), Home (Tecla INICIO),
LeftArrow (Tecla FLECHA IZQUIERDA), UpArrow (Tecla FLECHA
ARRIBA), RightArrow (Tecla FLECHA DERECHA), DownArrow (Tecla
FLECHA ABAJO)
o Teclas alfabticas, como: A (Tecla A), B, C, D, E, F, G, H, I, J, K, L, M, N,
O, P, Q, R, S, T, U, V, W, X, Y, Z
o Teclas numricas, como: D0 (Tecla 0), D1, D2, D3, D4, D5, D6, D7, D8,
D9
o Teclado numrico adicional: NumPad0 (Tecla 0 del teclado
numrico), NumPad1, NumPad2, NumPad3, NumPad4, NumPad5,
NumPad6, NumPad7, NumPad8, NumPad9, Multiply (Tecla
Multiplicacin), Add (Tecla Suma), Separator (Tecla Separador),
Subtract (Tecla Resta), Decimal (Tecla Decimal), Divide (Tecla
Divisin)
o Sleep (Tecla Espera del equipo)
o Teclas de funcin: F1, F2 y sucesivas (hasta F24)
o Teclas especiales de Windows: LeftWindows (Tecla izquierda con el
logotipo de Windows), RightWindows (Tecla derecha con el logotipo
de Windows)
o Incluso teclas multimedia, si el teclado las incorpora, como:
VolumeMute (Tecla Silenciar el volumen, enMicrosoft Natural
Keyboard, bajo Windows 2000 o posterior), VolumeDown (Bajar el
volumen, dem), VolumeUp (Subir el volumen), MediaNext (Tecla
Siguiente pista de multimedia), etc.
o Modifiers, que permite comprobar si se han pulsado
simultneamente teclas modificadoras: Alt, Shift o Control. Un
ejemplo de su uso sera:

if ((tecla.Modifiers & ConsoleModifiers.Alt) != 0)


Console.Write("Has pulsado Alt");

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
smbolo a izquierda y derecha, pero muestra una animacin simple si no pulsamos
ninguna tecla:

// Ejemplo_12_02b.cs
// No bloquear el programa con Console.Readkey
// Introduccin a C#, por Nacho Cabanes
Revisin 0.99zz Pgina 342
Introduccin a la programacin con C#, por Nacho Cabanes

using System;
using System.Threading;

public class Ejemplo_12_02b


{
public static void Main()
{
int posX=40, posY=10;
string simbolos = "^>v<";
byte simboloActual = 0;
bool terminado = false;

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 pequea pausa entre
una comprobacin de teclas y la siguiente, con Thread.Sleep, tanto para que la
animacin no sea demasiado rpida 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 prpura), 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 rpido, haz una pausa
de 50 milisegundos entre un "fotograma" y otro.

Revisin 0.99zz Pgina 343


Introduccin a la programacin con C#, por Nacho Cabanes

(12.2.2) Crea una versin 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 opcin.
(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 versin del programa de "dibujar" en consola (12.2.3), que
permita escribir ms 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 versin mejorada del programa 12.1.2 (mostrar el reloj
actualizado en pantalla, que lo dibuje siempre en la esquina superior derecha de la
pantalla).

12.3. Lectura de directorios


Si queremos analizar el contenido de un directorio, podemos emplear las clases
Directory y DirectoryInfo.

La clase Directory contiene mtodos para crear un directorio (CreateDirectory),


borrarlo (Delete), moverlo (Move), comprobar si existe (Exists), etc. Por ejemplo,
podramos crear un directorio con:

// Ejemplo_12_03a.cs
// Crear un directorio
// Introduccin a C#, por Nacho Cabanes

using System.IO;

public class Ejemplo_12_03a


{
public static void Main()
{
string miDirectorio = @"d:\datos";
if (!Directory.Exists(miDirectorio))
Directory.CreateDirectory(miDirectorio);
}
}

(la clase Directory est declarada en el espacio de nombres System.IO, por lo que
deberemos aadirlo entre los "using" de nuestro programa).

Revisin 0.99zz Pgina 344


Introduccin a la programacin con C#, por Nacho Cabanes

Tambin tenemos un mtodo "GetFiles" que nos permite obtener la lista de


ficheros que contiene un directorio. As, podramos listar todo el contenido de un
directorio con:

// Ejemplo_12_03b.cs
// Lista de ficheros en un directorio
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_12_03b


{
public static void Main()
{
string miDirectorio = @"c:\";
string[] listaFicheros;

listaFicheros = Directory.GetFiles(miDirectorio);
foreach(string fichero in listaFicheros)
Console.WriteLine(fichero);
}
}

Se puede aadir un segundo parmetro a "GetFiles", que sera el patrn que


deben seguir los nombres de los ficheros que buscamos, como "*.txt". Por
ejemplo, podramos saber todos los fuentes de C# ("*.cs") de la carpeta actual (".")
con:

// Ejemplo_12_03c.cs
// Lista de ficheros en un directorio
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_12_03c


{
public static void Main()
{
string[] listaFicheros = Directory.GetFiles(".", "*.cs");
foreach(string fichero in listaFicheros)
Console.WriteLine(fichero);
}
}

Si necesitamos ms detalles, la clase DirectoryInfo permite obtener informacin


sobre fechas de creacin, modificacin y acceso, y, de forma anloga, FileInfo nos
permite conseguir informacin similar sobre un fichero. Podramos usar estas dos
clases para ampliar el ejemplo anterior, y que no slo muestre el nombre de cada
fichero, sino otros detalles adicionales como el tamao y la fecha de creacin:
Revisin 0.99zz Pgina 345
Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_12_03d.cs
// Lista detallada de ficheros en un directorio
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO;

public class Ejemplo_12_03d


{
public static void Main()
{
string miDirectorio = @"c:\";
DirectoryInfo dir = new DirectoryInfo(miDirectorio);
FileInfo[] infoFicheros = dir.GetFiles();
foreach (FileInfo infoUnFich in infoFicheros)
{
Console.WriteLine("{0}, de tamao {1}, creado {2}",
infoUnFich.Name,
infoUnFich.Length,
infoUnFich.CreationTime);
}

}
}

que escribira cosas como

hiberfil.sys, de tamao 6775930880, creado 15/07/2013 17:48:07

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 continuacin), 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).

Revisin 0.99zz Pgina 346


Introduccin a la programacin con C#, por Nacho Cabanes

(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 mostrarn en un color
distinto. Se podrn deseleccionar volviendo a pulsar "Espacio" o "Insert". Si se
pulsa F5, los ficheros seleccionados en la carpeta actual del panel actual se
copiarn a la carpeta del otro panel. Mientras se estn copiando, el programa
debe mostrar una "barra de progreso" de color amarillo, que indicar el porcentaje
de ficheros que ya se han copiado.

12.4. El entorno. Llamadas al sistema


Si hay algo que no sepamos o podamos hacer, pero que alguna utilidad del
sistema operativo s es capaz de hacer por nosotros, podemos hacer que ella
trabaje por nosotros. La forma de llamar a otras rdenes del sistema operativo
(incluso programas externos de casi cualquier tipo) es creando un nuevo proceso
con "Process.Start". Por ejemplo, podramos lanzar el bloc de notas de Windows
con:

Process proc = Process.Start("notepad.exe");

En los actuales sistemas operativos multitarea se da por sentado que no es


necesario esperar a que termine otra la tarea, sino que nuestro programa puede
proseguir. Si aun as, queremos esperar a que se complete la otra tarea, lo
conseguiramos con "WaitForExit", aadiendo esta segunda lnea:

proc.WaitForExit();

Un programa completo que lanzara el bloc de notas y que esperase a que se


cerrase podra ser:

// Ejemplo_12_04a.cs
// Lanzar otro proceso y esperar
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Diagnostics;

public class Ejemplo_12_04a


{
public static void Main()
{
Console.WriteLine("Lanzando el bloc de notas...");
Revisin 0.99zz Pgina 347
Introduccin a la programacin con C#, por Nacho Cabanes

Process proc = Process.Start("notepad.exe");


Console.WriteLine("Esperando a que se cierre...");
proc.WaitForExit();
Console.WriteLine("Terminado!");
}
}

Ejercicios propuestos:
(12.4.1) Mejora el ejercicio 12.3.3 (la versin bsica 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 versin 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 parmetro en la lnea de comandos.

12.5. Datos sobre "el entorno"


La clase "Environment" nos sirve para acceder a informacin sobre el sistema:
unidades de disco disponibles, directorio actual, versin del sistema operativo y de
la plataforma .Net, nombre de usuario y mquina, carcter o caracteres que se
usan para avanzar de lnea, etc:

// Ejemplo_12_05a.cs
// Informacin sobre el sistema
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Diagnostics;

public class Ejemplo_12_05a


{
public static void Main()
{
string avanceLinea = Environment.NewLine;
Console.WriteLine("Directorio actual: {0}",
Environment.CurrentDirectory);
Console.WriteLine("Nombre de la mquina: {0}",
Environment.MachineName);
Console.WriteLine("Nombre de usuario: {0}", Environment.UserName);
Console.WriteLine("Dominio: {0}", Environment.UserDomainName);
Console.WriteLine("Cdigo de salida del programa anterior: {0}",
Environment.ExitCode);
Console.WriteLine("Linea de comandos: {0}", Environment.CommandLine);
Console.WriteLine("Versin del S.O.: {0}",
System.Convert.ToString(Environment.OSVersion));
Console.WriteLine("Version de .Net: {0}",
Environment.Version.ToString());
String[] discos = Environment.GetLogicalDrives();
Revisin 0.99zz Pgina 348
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine("Unidades lgicas: {0}",


String.Join(", ", discos));
Console.WriteLine("Carpeta de sistema: {0}",
Environment.GetFolderPath(Environment.SpecialFolder.System));
}
}

Ejercicios propuestos:
(12.5.1) Mejora el ejercicio 12.4.2 (la versin ms 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 lgicas, 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.

12.6. Algunos servicios de red.


Muchos de los servicios que podemos obtener de Internet o de una red local son
accesibles de forma sencilla, tpicamente enmascarados como si leyramos de un
fichero o escribiramos en l.

(Nota: en este apartado se asumir que el lector entiende algunos conceptos


bsicos de redes, como qu es una direccin IP, qu significa "localhost" o qu
diferencias hay entre el protocolo TCP y el UDP).

Como primer ejemplo, vamos a ver cmo podramos recibir una pgina web (por
ejemplo, la pgina principal de "www.nachocabanes.com"), lnea a lnea como si se
tratara de un fichero de texto (StreamReader), y mostrar slo las lneas que
contengan un cierto texto (por ejemplo, la palabra "Pascal"):

// Ejemplo_12_06a.cs
// Ejemplo de descarga y anlisis de una web:
// Muestra las lneas que contienen "Pascal"
// Introduccin a C#, por Nacho Cabanes

using System;
using System.IO; // Para Stream
using System.Net; // Para System.Net.WebClient

public class DescargarWeb


{

public static void Main()


{
WebClient cliente = new WebClient();
Stream conexion = cliente.OpenRead("http://www.nachocabanes.com");
StreamReader lector = new StreamReader(conexion);
string linea;
Revisin 0.99zz Pgina 349
Introduccin a la programacin con C#, por Nacho Cabanes

int contador = 0;
while ( (linea=lector.ReadLine()) != null )
{
contador ++;
if (linea.IndexOf("Pascal") >= 0)
Console.WriteLine("{0}: {1}", contador, linea);
}
conexion.Close();
}
}

El resultado de este programa sera algo como:

28: <li><a href="pascal/index.php">Pascal/Delphi</a></li>


54: | <a href="pascal/index.php">Pascal / Delphi</a>
204: <li><a href="pascal/index.php">Pascal/Delphi</a></li>

Otra posibilidad que tampoco es complicada (aunque s algo ms que sta ltima)
es la de comunicar dos ordenadores, para enviar informacin 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 recepcin de frases a travs de la red
// Introduccin 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

public class ComunicacionRed


{
static string direccionPrueba = "localhost";
static int puertoPrueba = 2112;

private static void enviar(string direccion,


int puerto, string frase)
{
TcpClient cliente = new TcpClient(direccion, puerto);
NetworkStream conexion = cliente.GetStream();
byte[] secuenciaLetras = Encoding.ASCII.GetBytes( frase );

conexion.Write(secuenciaLetras, 0, secuenciaLetras.Length);

conexion.Close();
cliente.Close();
}

private static string esperar(string direccion, int puerto)


{
Revisin 0.99zz Pgina 350
Introduccin a la programacin con C#, por Nacho Cabanes

// Tratamos de hallar la primera IP que corresponde


// a una direccin como "localhost"
IPAddress direccionIP = Dns.GetHostEntry(direccion).AddressList[0];
// Comienza la espera de informacin
TcpListener listener = new TcpListener(direccionIP,puerto);
listener.Start();
TcpClient cliente = listener.AcceptTcpClient();
NetworkStream conexion = cliente.GetStream();
StreamReader lector = new StreamReader(conexion);

string frase = lector.ReadToEnd();

cliente.Close();
listener.Stop();

return frase;
}

public static void Main()


{
Console.WriteLine("Pulse 1 para recibir o 2 para enviar");
string respuesta = Console.ReadLine();

if (respuesta == "2") // Enviar


{
Console.Write("Enviando... ");
enviar( direccionPrueba, puertoPrueba, "Prueba de texto");
Console.WriteLine("Enviado");
}
else // Recibir
{
Console.WriteLine("Esperando... ");
Console.WriteLine( esperar(direccionPrueba, puertoPrueba) );
Console.WriteLine("Recibido");
}
}
}

Cuando lanzramos el programa, se nos preguntara si queremos enviar o recibir:

Pulse 1 para recibir o 2 para enviar

Lo razonable es lanzar primero el proceso que espera para recibir, pulsando 1:

1
Esperando...

Entonces lanzaramos otra sesin del mismo programa en el mismo ordenador


(porque estamos conectando a la direccin "localhost"), y en esta nueva sesin
escogeramos la opcin de Enviar (2):

Pulse 1 para recibir o 2 para enviar


2
Enviando...

Revisin 0.99zz Pgina 351


Introduccin a la programacin con C#, por Nacho Cabanes

Instantneamente, en la primera sesin recibiramos el texto que hemos enviado


desde la segunda, y se mostrara en pantalla:

Prueba de texto
Recibido

Y la segunda sesin confirmara que el envo ha sido correcto:

Enviando... Enviado

Esta misma idea se podra usar como base para programas ms elaborados, que
comunicaran diferentes equipos (en este caso, la direccin no sera "localhost",
sino la IP del otro equipo), como podra 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 ms 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 ms detalles de ninguno de ambos mtodos.

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 bsico 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 pgina 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
pgina que indique el usuario, analizando los enlaces que contiene y descargando
de forma recursiva las pginas que corresponden a dichos enlaces. Slo deber
procesar los enlaces internos (en el mismo sitio web), no las pginas 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 dnde 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 podrn colocar en
horizontal o en vertical sobre un tablero de 8x8, y sern: 4 de longitud 1, 3 de
longitud 2, 2 de longitud 3 y uno de longitud 4. Cada jugador ver el estado de su

Revisin 0.99zz Pgina 352


Introduccin a la programacin con C#, por Nacho Cabanes

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
ms detallado ("tocado" o "hundido", segn 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 sesin se reanude el juego en el punto en el que qued.

Revisin 0.99zz Pgina 353


Introduccin a la programacin con C#, por Nacho Cabanes

13. Otras caractersticas avanzad as de C#


13.1. Espacios de nombres
Desde nuestros primeros programas hemos estado usando cosas como
"System.Console" o bien "using System". Esa palabra "System" indica que las
funciones que estamos usando pertenecen a la estructura bsica de C# y de la
plataforma .Net.

La idea detrs 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 podra dar lugar a programas que no compilaran
correctamente, o, peor an, 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 nmeros 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.

De hecho, con entornos como SharpDevelop o Visual Studio, cuando creamos un


nuevo proyecto, el fuente "casi vaco" que se nos propone contendr un espacio
de nombres que se llamar igual que el proyecto. Esta es apariencia del fuente si
usamos VisualStudio 2008:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
}

Y esta es apariencia del fuente si usamos SharpDevelop 3:

Revisin 0.99zz Pgina 354


Introduccin a la programacin con C#, por Nacho Cabanes

using System;

namespace PruebaDeNamespaces
{
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");

// TODO: Implement Functionality Here

Console.Write("Press any key to continue . . . ");


Console.ReadKey(true);
}
}
}

Nosotros vamos a preparar un ejemplo algo ms avanzado, que contenga un


espacio de nombres, que cree una nueva clase Console y que utilice las dos (la
nuestra y la original, de System):

// Ejemplo_13_01a.cs
// Ejemplo de uso de "namespaces"
// Introduccin 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);
}
}
}

public class PruebaDeNamespaces


{
public static void Main()
{
System.Console.WriteLine("Hola");
ConsolaAmpliada.Console.WriteLine("Hola otra vez");
}
}

Como se puede ver, este ejemplo tiene dos clases Console, y ambas tienen un
mtodo WriteLine. Una es la original de C#, que invocaramos con
"System.Console". Otra es la que hemos creado para el ejemplo, que escribe un
texto modifica y en color (ayudndose de System.Console), y que llamaramos
mediante "ConsolaAmpliada.Console". El resultado es que podemos tener dos
Revisin 0.99zz Pgina 355
Introduccin a la programacin con C#, por Nacho Cabanes

clases Console accesibles desde el mismo programa, sin que existan conflictos
entre ellas. El resultado del programa sera:

Hola
Mensaje: Hola otra vez

13.2. Operaciones con bits


Podemos hacer desde C# operaciones entre bits de dos nmeros (AND, OR, XOR,
etc). Vamos primero a ver qu significa cada una de esas operaciones.

Operacin Resultado En Ejemplo


C#
Complemento (not) Cambiar 0 por 1 y ~ ~1100 = 0011
viceversa
Producto lgico (and) 1 slo si los 2 bits son 1 & 1101 & 1011 = 1001
Suma lgica (or) 1 slo si uno de los bits | 1101 | 1011 = 1111
es 1
Suma exclusiva (xor) 1 slo si los 2 bits son ^ 1101 ^ 1011 = 0110
distintos
Desplazamiento a la Desplaza y rellena con << 1101 << 2 = 110100
izquierda ceros
Desplazamiento a la Desplaza y rellena con >> 1101 >> 2 = 0011
derecha ceros

Ahora vamos a aplicarlo a un ejemplo completo en C#:

// Ejemplo_13_02a.cs
// Operaciones a nivel de bits
// Introduccin a C#, por Nacho Cabanes

using System;

public class Bits


{
public static void Main()
{
int a = 67;
int b = 33;

Console.WriteLine("La variable a vale {0}", a);


Console.WriteLine("y b vale {0}", b);
Console.WriteLine(" El complemento de a es: {0}", ~a);
Console.WriteLine(" El producto lgico de a y b es: {0}", a&b);
Console.WriteLine(" Su suma lgica es: {0}", a|b);
Console.WriteLine(" Su suma lgica exclusiva es: {0}", a^b);
Console.WriteLine(" Desplacemos a a la izquierda: {0}", a << 1);
Revisin 0.99zz Pgina 356
Introduccin a la programacin con C#, por Nacho Cabanes

Console.WriteLine(" Desplacemos a a la derecha: {0}", a >> 1);


}
}

El resultado es:

La variable a vale 67
y b vale 33
El complemento de a es: -68
El producto lgico de a y b es: 1
Su suma lgica es: 99
Su suma lgica 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
nmeros y seguir las operaciones paso a paso:

67 = 0100 0011
33 = 0010 0001

En primer lugar complementamos "a", cambiando los ceros por unos:

1011 1100 = -68

Despus hacemos el producto lgico de A y B, multiplicando cada bit, de modo


que 1*1 = 1, 1*0 = 0, 0*0 = 0

0000 0001 = 1

Despus hacemos su suma lgica, sumando cada bit, de modo que 1+1 = 1, 1+0 =
1, 0+0 = 0
0110 0011 = 99

La suma lgica 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 posicin a la izquierda es como multiplicar por dos:

1000 0110 = 134

Desplazar los bits una posicin a la derecha es como dividir entre dos:

0010 0001 = 33

Qu utilidades puede tener todo esto? Posiblemente, ms de las que parece a


primera vista. Por ejemplo: desplazar a la izquierda es una forma muy rpida de
multiplicar por potencias de dos; desplazar a la derecha es dividir por potencias de
Revisin 0.99zz Pgina 357
Introduccin a la programacin con C#, por Nacho Cabanes

dos; la suma lgica exclusiva (xor) es un mtodo rpido y sencillo de cifrar


mensajes; el producto lgico nos permite obligar a que ciertos bits sean 0 (algo
que se puede usar para comprobar mscaras de red); la suma lgica, por el
contrario, puede servir para obligar a que ciertos bits sean 1...

Un ltimo comentario: igual que hacamos operaciones abreviadas como

x += 2;

tambin podremos hacer cosas como

x <<= 2;
x &= 2;
x |= 2;
...

Ejercicios propuestos
(13.2.1) Crea un programa que lea un fichero de texto, encripte cada lnea
haciendo un XOR de los caracteres que la forman con un cierto dato prefijado (y
pequeo, como 3, por ejemplo) y vuelque el resultado a un nuevo fichero.

13.3. Enumeraciones
Cuando tenemos varias constantes, cuyos valores son nmeros enteros, hasta
ahora estamos dando los valores uno por uno, as:

const int LUNES = 0, MARTES = 1,


MIERCOLES = 2, JUEVES = 3,
VIERNES = 4, SABADO = 5,
DOMINGO = 6;

Hay una forma alternativa de hacerlo, especialmente til si son nmeros enteros
consecutivos. Se trata de enumerarlos:

enum diasSemana { LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO,


DOMINGO };

(Al igual que las constantes de cualquier otro tipo, se puede escribir en maysculas
para recordar "de un vistazo" que son constantes, no variables)

La primera constante valdr 0, y las dems irn aumentando de una en una, de


modo que en nuestro caso valen:

LUNES = 0, MARTES = 1, MIERCOLES = 2, JUEVES = 3, VIERNES = 4,


SABADO = 5, DOMINGO = 6

Revisin 0.99zz Pgina 358


Introduccin a la programacin con C#, por Nacho Cabanes

Si queremos que los valores no sean exactamente estos, podemos dar valor a
cualquiera de las contantes, y las siguientes irn aumentando de uno en uno. Por
ejemplo, si escribimos

enum diasSemana { LUNES=1, MARTES, MIERCOLES, JUEVES=6, VIERNES,


SABADO=10, DOMINGO };

Ahora sus valores son:

LUNES = 1, MARTES = 2, MIERCOLES = 3, JUEVES = 6, VIERNES = 7,


SABADO = 10, DOMINGO = 11

Un ejemplo bsico podra ser

// Ejemplo_13_03a.cs
// Ejemplo de enumeraciones
// Introduccin a C#, por Nacho Cabanes

using System;

public class Enumeraciones


{

enum diasSemana { LUNES, MARTES, MIERCOLES, JUEVES, VIERNES,


SABADO, DOMINGO };

public static void Main()


{
Console.Write("En la enumeracion, el mircoles tiene el valor: {0} ",
diasSemana.MIERCOLES);
Console.WriteLine("que equivale a: {0}",
(int) diasSemana.MIERCOLES);

const int LUNES = 0, MARTES = 1, MIERCOLES = 2, JUEVES = 3,


VIERNES = 4, SABADO = 5, DOMINGO = 6;

Console.WriteLine("En las constantes, el mircoles tiene el valor:


{0}",
MIERCOLES);
}
}

y su resultado ser:

En la enumeracion, el mircoles tiene el valor: MIERCOLES que equivale a: 2


En las constantes, el mircoles tiene el valor: 2

Nosotros hemos usado enumeraciones muchas veces hasta ahora, sin saber
realmente que lo estbamos haciendo. Por ejemplo, el modo de apertura de un
fichero (FileMode) es una enumeracin, por lo que escribimos FileMode.Open.
Tambin son enumeraciones los cdigos de color de la consola (como
ConsoleColor.Red) y las teclas de la consola (como ConsoleKey.Escape).

Revisin 0.99zz Pgina 359


Introduccin a la programacin con C#, por Nacho Cabanes

Nota: las enumeraciones existen tambin en otros lenguajes como C y C++, pero la
sintaxis es ligeramente distinta: en C# es necesario indicar el nombre de la
enumeracin cada vez que se usen sus valores (como en diasSemana.MIERCOLES),
mientras que en C se usa slo el valor (MIERCOLES).

Ejercicios propuestos
(13.3.1) Crea una versin de la base de datos de ficheros con colores (ejercicio
12.2.2) en la que las opciones sean parte de una enumeracin.

13.4. Propiedades
Hasta ahora estbamos siguiendo la poltica de que los atributos de una clase sean
privados, y se acceda a ellos a travs de mtodos "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 mtodos "get" y "set":

// Ejemplo_13_04a.cs
// Ejemplo de propiedades (1)
// Introduccin a C#, por Nacho Cabanes

using System;

public class EjemploPropiedades


{
// -------------------------

// Un atributo convencional, privado


private int altura = 0;

// Para ocultar detalles, leemos su valor con un "get"


public int GetAltura()
{
return altura;
}

// Y lo fijamos con un "set"


public void SetAltura(int nuevoValor)
{
altura = nuevoValor;
}

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

// Otro atributo convencional, privado


private int anchura = 0;

// Oculto mediante una "propiedad"


public int Anchura
Revisin 0.99zz Pgina 360
Introduccin a la programacin con C#, por Nacho Cabanes

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

Al igual que ocurra con las enumeraciones, ya hemos usado "propiedades"


anteriormente, sin saberlo: la longitud ("Length") de una cadena, el tamao
("Length") y la posicin actual ("Position") en un fichero, el ttulo ("Title") de una
ventana en consola, etc.

Una curiosidad: si una propiedad tiene un "get", pero no un "set", ser una
propiedad de slo lectura, no podremos hacer cosas como "Anchura = 4", porque
el programa no compilara. De igual modo, se podra crear una propiedad de slo
escritura, definiendo su "set" pero no su "get".

Ejercicios propuestos
(13.4.1) Crea una nueva versin del ejercicio de la clase Persona (6.7.1), en la que
el "nombre" sea una propiedad, con sus correspondientes "get" y "set".

13.5. Introduccin a las expresiones regulares.


Las "expresiones regulares" permiten hacer comparaciones mucho ms abstractas
que si se usa un simple "Contains". Por ejemplo, podemos comprobar con una
Revisin 0.99zz Pgina 361
Introduccin a la programacin con C#, por Nacho Cabanes

orden breve si todos los caracteres de una cadena son numricos, o si empieza
por mayscula y el resto son minsculas, etc.

Vamos a ver solamente un ejemplo con un caso habitual: comprobar si una


cadena es numrica, alfabtica o alfanumrica. Las ideas bsicas son:

Usaremos el tipo de datos "RegEx" (expresin regular).


Tenemos un mtodo IsMatch, que devuelve "true" si una cadena de texto
coincide con un cierto patrn.
Uno de los patrones ms habituales es indicar un rango de datos: [a-z]
quiere decir "un carcter entre la a y la z".
Podemos aadir modificadores: * para indicar "0 o ms veces", + para "1 o
ms veces", ? para "0 o una vez", como en [a-z]+, que quiere decir "uno o
ms caracteres entre la a y la z".
Aun as, esa expresin puede dar resultados inesperados: un secuencia
como [0-9]+ aceptara cualquier cadena que contuviera una secuencia de
nmeros aunque tuviera otros smbolos al principio y al final. Por eso, si
queremos que slo tenga cifras numricas, nuestra expresin regular
debera ser "inicio de cadena, cualquier secuencia de cifras, final de
cadena", que se representara como "\A[0-9]+\z". Una alternativa es
plantear la expresin regular al contrario: "no es vlido si contiene algo que
no sea del 0 al 9", que se podra conseguir devolviendo lo contrario de lo
que indique la expresin "[^0-9]".

El ejemplo podra ser:

// Ejemplo_13_05a.cs
// Ejemplo de expresiones regulares
// Introduccin 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);
}

public static bool EsNumeroEntero2(String cadena)


{
Regex patronNumerico = new Regex(@"\A[0-9]*\z");
return patronNumerico.IsMatch(cadena);
}
Revisin 0.99zz Pgina 362
Introduccin a la programacin con C#, por Nacho Cabanes

public static bool EsNumeroConDecimales(String cadena)


{
Regex patronNumericoConDecimales =
new Regex(@"\A[0-9]*,?[0-9]+\z");
return patronNumericoConDecimales.IsMatch(cadena);
}

public static bool EsAlfabetico(String cadena)


{
Regex patronAlfabetico = new Regex("[^a-zA-Z]");
return !patronAlfabetico.IsMatch(cadena);
}

public static bool EsAlfanumerico(String cadena)


{
Regex patronAlfanumerico = new Regex("[^a-zA-Z0-9]");
return !patronAlfanumerico.IsMatch(cadena);
}

static void Main(string[] args)


{
if (EsNumeroEntero("hola"))
Console.WriteLine("hola es nmero entero");
else
Console.WriteLine("hola NO es nmero entero");

if (EsNumeroEntero("1942"))
Console.WriteLine("1942 es un nmero entero");
else
Console.WriteLine("1942 NO es un nmero 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 nmero entero");
else
Console.WriteLine("23,45 NO es un nmero 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 nmero con decimales");
else
Console.WriteLine("23,45 NO es un nmero con decimales");

if (EsNumeroConDecimales("23,45,67"))
Console.WriteLine("23,45,67 es un nmero con decimales");
else
Console.WriteLine("23,45,67 NO es un nmero con decimales");

if (EsAlfabetico("hola"))
Revisin 0.99zz Pgina 363
Introduccin a la programacin 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:

hola NO es nmero entero


1942 es un nmero entero
1942 es entero (forma 2)
23,45 NO es un nmero entero
23,45 NO es entero (forma 2)
23,45 es un nmero con decimales
23,45,67 NO es un nmero con decimales
hola es alfabetico
hola1 es alfanumerico

Las expresiones regulares son algo complejo. Por una parte, su sintaxis puede
llegar a ser difcil de seguir. Por otra parte, el manejo en C# no se limita a buscar,
sino que tambin permite otras operaciones, como reemplazar unas expresiones
por otras. Como ver muchos ms detalles podra hacer el texto demasiado
extenso, puede ser recomendable ampliar informacin usando la pgina 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 funcin que valide cdigos postales espaoles (5 cifras
numricas): devolver true si la cadena recibida como parmetro es un cdigo
postal vlido.
(13.5.2) Crea una funcin que diga si una cadena que se le pase como parmetro
parece un correo electrnico vlido.

13.6. El operador coma


Cuando vimos la orden "for", siempre usbamos una nica variable como
contador, pero esto no tiene por qu ser siempre as. Vamos a verlo con un
ejemplo:

Revisin 0.99zz Pgina 364


Introduccin a la programacin con C#, por Nacho Cabanes

// Ejemplo_13_06a.cs
// Operador coma
// Introduccin a C#, por Nacho Cabanes

using System;

public class OperadorComa


{
public static void Main()
{
int i, j;

for (i=0, j=1; i<=5 && j<=30; i++, j+=2)


Console.WriteLine("i vale {0} y j vale {1}", i, j);
}
}

Vamos a ver qu hace este "for":

Los valores iniciales son i=0, j=1.


Se repetir mientras que i <= 5 y j <= 30.
Al final de cada paso, i aumenta en una unidad, y j en dos unidades.

El resultado de este programa es:

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

Nota: En el lenguaje C se puede "rizar el rizo" todava un poco ms: la condicin de


terminacin tambin podra tener una coma, y entonces no se sale del bucle "for"
hasta que se cumplen las dos condiciones (algo que no es vlido en C#, ya que la
condicin debe ser un "boolean", y la coma no es un operador vlido para
operaciones booleanas):

for (i=0, j=1; i<=5, j<=30; i++, j+=2)

13.7. Variables con tipo implicito


A partir de Visual C# 3.0, existe la posibilidad de no declarar de forma explcita el
tipo de una variable, sino que sea el propio compilador el que lo deduzca del
contexto. Para ello, se utiliza la palabra "var", como en este ejemplo:

// Ejemplo_13_07a.cs
// Ejemplo de uso de "var"
// Introduccin a C#, por Nacho Cabanes
Revisin 0.99zz Pgina 365
Introduccin a la programacin con C#, por Nacho Cabanes

using System;

public class UsoVar


{
public static void Main()
{
var n = 5;
Console.WriteLine("n vale {0} y es de tipo {1}",
n, n.GetType());

var condicion = 5 == 7;
Console.WriteLine("condicion vale {0} y es de tipo {1}",
condicion, condicion.GetType());

var letra = 'a';


Console.WriteLine("letra vale {0} y es de tipo {1}",
letra, letra.GetType());

var pi = 3.1416;
Console.WriteLine("pi vale {0} y es de tipo {1}",
pi, pi.GetType());

var texto = "Hola";


Console.WriteLine("texto vale {0} y es de tipo {1}",
texto, texto.GetType());
}
}

Como se ve en este ejemplo, si necesitramos saber de qu tipo es una variable (lo


que no es habitual, porque si se usa "var" es para despreocuparnos de esos
detalles), lo podramos conseguir con "GetType()".

Ejercicios propuestos
(13.7.1) Crea un programa que pida al usuario una cantidad de kilmetros y
muestre su equivalencia en millas. El valor de conversin debe estar en una
variable definida con "var".
(13.7.2) Crea un programa que muestre la primera lnea de un fichero de texto.
Tanto el fichero como la lnea se deben declarar con "var".

13.8. Contacto con LINQ


LINQ (Language-integrated Query, consulta integrada en el lenguaje) es una
posibilidad aadida en la plataforma .Net versin 3.5, y accesible a partir de Visual
Studio 2008, que nos permite hacer consultas a cualquier coleccin de datos
usando una sintaxis que recuerda a la de SQL (aunque se escribe "casi al revs":
primero el conjunto de datos, luego la condicin y luego la variable a devolver).

Revisin 0.99zz Pgina 366


Introduccin a la programacin con C#, por Nacho Cabanes

Por ejemplo, podemos obtener los nmeros enteros de un array cuyo valor es
mayor que 10 con:

// Ejemplo_13_08a.cs
// Ejemplo bsico de LINQ
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Linq;

public class EjemploLinq1


{
public static void Main()
{
int[] datos = { 20, 5, 7, 4, 25, 18, 5, 8, 21, 2 };

var result =
from n in datos
where n > 10
select n;

foreach(int i in result)
Console.Write("{0} ", i);
Console.WriteLine();
}
}

Tambin podemos recorrer la informacin de una lista de cadenas de texto y


obtenerla ordenada, as:

// Ejemplo_13_08b.cs
// Segundo ejemplo de LINQ
// Introduccin a C#, por Nacho Cabanes

using System;
using System.Linq;
using System.Collections.Generic;

public class EjemploLinq2


{
public static void Main()
{
List<string> nombres = new List<string>
{ "pan", "carne", "queso", "manzana", "natillas" };

var resultado =
from nombre in nombres
orderby nombre
select nombre;

foreach (string n in resultado)


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

Revisin 0.99zz Pgina 367


Introduccin a la programacin con C#, por Nacho Cabanes

Profundizar en LINQ es algo que queda fuera del propsito de este texto. Si
quieres saber ms, puedes acudir a la propia referencia oficial en lnea, en MSDN:

https://msdn.microsoft.com/es-es/library/bb397926.aspx

Ejercicios propuestos
(13.8.1) Crea un programa que pida al usuario varios nmeros 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.

13.9. Lo que no vamos a ver...


En C# (y la plataforma "punto net") hay ms que lo que hemos visto aqu.
Mencionaremos algunos, por si alguien quiere ampliar informacin por su cuenta
en MSDN o en cualquier otra fuente de informacin. Por ejemplo:

Delegados (delegate).
Funciones lambda.
El preprocesador.
Entornos grficos (Windows Forms, WPF).
Uso en servidores Web (ASP.Net).

Revisin 0.99zz Pgina 368


Introduccin a la programacin con C#, por Nacho Cabanes

14. Depuracin, prueba y documentacin de


programas
14.1. Conceptos bsicos sobre depuracin
La depuracin es el anlisis de un programa para descubrir fallos. El nombre en
ingls es "debug", porque esos fallos de programacin reciben el nombre de
"bugs" (bichos).

Para eliminar esos fallos que hacen que un programa no se comporte como
debera, se usan unas herramientas llamadas "depuradores". Estos nos permiten
avanzar paso a paso para ver cmo avanza realmente nuestro programa, y
tambin 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 deberan ser mnimas.

14.2. Depurando desde Visual Studio


Vamos a tomar como ejemplo la secuencia de operaciones que se propuso como
ejercicio al final del apartado 2.1:

a=5; b=a+2; b-=3; c=-3; c*=2; ++c; a*=b;

Esto se convertira en un programa como

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;
}
}
Revisin 0.99zz Pgina 369
Introduccin a la programacin con C#, por Nacho Cabanes

Ni siquiera necesitamos rdenes "WriteLine", porque no mostraremos nada en


pantalla, ser el propio entorno de desarrollo de Visual Studio el que nos muestre
los valores de las variables.

Para avanzar paso a paso y ver los valores de las variables, entramos al men
"Depurar". En l aparece la opcin "Paso a paso por instrucciones" (al que
corresponde la tecla F11):

Si escogemos esa opcin 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:

Revisin 0.99zz Pgina 370


Introduccin a la programacin con C#, por Nacho Cabanes

Cada vez que pulsemos nuevamente F11 (o vayamos al men, o al botn


correspondiente de la barra de herramientas), el depurador analiza una nueva
lnea de programa, muestra los valores de las variables correspondientes (el
cambio ms reciente se ver en color rojo), y se vuelve a quedar parado, realzando
con fondo amarillo la siguiente lnea que se analizar:

Revisin 0.99zz Pgina 371


Introduccin a la programacin con C#, por Nacho Cabanes

Aqu hemos avanzado desde el principio del programa, pero eso no es algo
totalmente habitual. Es ms frecuente que supongamos en qu zona del programa
se encuentra el error, y slo queramos depurar una zona de programa. La forma
de conseguirlo es escoger otra de las opciones del men de depuracin: "Alternar
puntos de ruptura" (tecla F9). Aparecer una marca granate en la lnea actual
como alternativa, podemos hacer clic con el ratn en el margen izquierdo del
programa, junto a esa lnea):

Si ahora iniciamos la depuracin 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.

14.3. Prueba de programas


Es frecuente que la correccin de ciertos errores introduzca a su vez errores
nuevos. Por eso, una forma habitual de garantizar la calidad de los programas es
creando una "batera de pruebas" que permita comprobar de forma automtica
que todo se comporta como debera. As, antes de dar por definitiva una versin
de un programa, se lanza la batera de pruebas y se verifica que los resultados son
los esperados.

Revisin 0.99zz Pgina 372


Introduccin a la programacin con C#, por Nacho Cabanes

Una forma sencilla de crear una batera de pruebas es comprobando los


resultados de operaciones conocidas. Vamos a ver un ejemplo, para un programa
que calcule las soluciones de una ecuacin de segundo grado.

La frmula que emplearemos es:

Un programa (incorrecto) para resolverlo podra ser:

public class SegundoGrado {

public static void Resolver(


float a, float b, float c,
out float x1, out float x2)
{

float discriminante;

discriminante = b*b - 4*a*c;

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 solucin no existe, se devuelve un valor falso, que no sea fcil
que se obtenga en un caso habitual, como -9999.

Y la batera de pruebas podra basarse en varios casos conocidos. Por ejemplo:

x2 -1 = 0 x1 = 1, x2 = -1
x2 = 0 x1 = 0, x2 = No existe (solucin nica)
Revisin 0.99zz Pgina 373
Introduccin a la programacin con C#, por Nacho Cabanes

x2 -3x = 0 x1 = 3, x2 = 0
2x2 -2 = 0 x1 = 1, x2 = -1

Estos casos de prueba se podran convertir en un programa como:

// Ejemplo_14_03a.cs
// Ejemplo de realizacin de pruebas
// Introduccin a C#, por Nacho Cabanes

using System;

public class SegundoGrado


{

public void Resolver(


float a, float b, float c,
out float x1, out float x2)
{

float discriminante;

discriminante = b*b - 4*a*c;

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

}
}

public class PruebaSegundoGrado


{
public static void Main()
{
float soluc1, soluc2;

Console.WriteLine("Probando ecuaciones de segundo grado");

SegundoGrado ecuacion = new SegundoGrado();

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");
Revisin 0.99zz Pgina 374
Introduccin a la programacin 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");

Console.Write("Probando x2 -3x = 0 ...");


ecuacion.Resolver((float)1, (float)-3, (float)0,
out soluc1, out soluc2);
if ((soluc1 == 3) && (soluc2 == 0))
Console.WriteLine("OK");
else
Console.WriteLine("Falla");

Console.Write("Probando 2x2 - 2 = 0 ...");


ecuacion.Resolver((float)2, (float)0, (float)-2,
out soluc1, out soluc2);
if ((soluc1 == 1) && (soluc2 == -1))
Console.WriteLine("OK");
else
Console.WriteLine("Falla");
}
}

El resultado de este programa es:

Probando ecuaciones de segundo grado


Probando x2 - 1 = 0 ...OK
Probando x2 = 0 ...OK
Probando x2 -3x = 0 ...OK
Probando 2x2 - 2 = 0 ...Falla

Vemos que en uno de los casos, la solucin no es correcta. Revisaramos los pasos
que da nuestro programa, corregiramos el fallo y volveramos a lanzar las pruebas
automatizadas. En este caso, el problema es que falta un parntesis, para dividir
entre (2*a), de modo que estamos diviendo entre 2 y luego multiplicando por a.

La mayora de lenguajes modernos tienen funcionalidades de creacin de pruebas


ya incorporadas como parte del lenguaje, normalmente en forma de una funcin
llamada "Assert". En C#, se trata de Debug.Assert", que es parte de
System.Diagnostics, de modo que el programa quedara as:

// Ejemplo_14_03b.cs
// Segundo ejemplo de realizacin de pruebas, con Assert
// Introduccin a C#, por Nacho Cabanes

Revisin 0.99zz Pgina 375


Introduccin a la programacin con C#, por Nacho Cabanes

using System;
using System.Diagnostics;

public class SegundoGrado


{

public void Resolver(


float a, float b, float c,
out float x1, out float x2)
{

float discriminante;

discriminante = b*b - 4*a*c;

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

}
}

public class PruebaSegundoGrado


{
public static void Main()
{
float soluc1, soluc2;

Console.WriteLine("Probando ecuaciones de segundo grado");

SegundoGrado ecuacion = new SegundoGrado();

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

Console.Write("Probando x2 -3x = 0 ...");


ecuacion.Resolver((float)1, (float)-3, (float)0,
out soluc1, out soluc2);
Debug.Assert((soluc1 == 3) && (soluc2 == 0));

Console.Write("Probando 2x2 - 2 = 0 ...");


Revisin 0.99zz Pgina 376
Introduccin a la programacin con C#, por Nacho Cabanes

ecuacion.Resolver((float)2, (float)0, (float)-2,


out soluc1, out soluc2);
Debug.Assert((soluc1 == 1) && (soluc2 == -1));
}
}

A lanzar este programa, se detendr la ejecucin y mostrar una ventana de aviso


con el texto "Error de asercin" al llegar a la lnea 67, con botones para Salir,
Depurar o Continuar ignorando el error.

La ventaja de crear bateras de pruebas es que es una forma muy rpida 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 slo que no falla en ciertos casos. Por ejemplo, si la
batera de pruebas anterior solo contuviera las tres primeras pruebas, no habra
descubierto el fallo del programa.

Por tanto, las pruebas slo 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.

Para crear las bateras de pruebas, lo que ms ayuda es la experiencia y el


conocimiento del problema. Algunos expertos recomiendan que, si es posible, las
pruebas las realice un equipo de desarrollo distinto al que ha realizado el proyecto
principal, para evitar que se omitan cosas que se "den por supuestas".

14.4. Documentacin bsica de programas


La mayor parte de la documentacin de un programa "serio" se debe realizar
antes de comenzar a teclear:

Especificaciones de requisitos, que reflejen lo que el programa debe hacer.


Diagramas de anlisis, como los diagramas de casos de uso UML, para
plasmar de una forma grfica lo que un usuario podr hacer con el sistema.
Diagramas de diseo, como los diagramas de clases UML que vimos en el
apartado 6.4, para mostrar qu tipos de objetos formarn realmente
nuestro programa y cmo interactuarn.

Revisin 0.99zz Pgina 377


Introduccin a la programacin con C#, por Nacho Cabanes

Casi todos esos diagramas caen fuera del alcance de este texto: en una
introduccin a la programacin se realizan programas de pequeo tamao, para
los que no es necesaria una gran planificacin.

Aun as, hay un tipo de documentacin 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 tambin veremos
como a partir de estos comentarios se puede generar documentacin adicional de
forma casi automtica.

14.4.1. Consejos para comentar el cdigo


Existen buenas recopilaciones de consejos en Internet. Yo voy a incluir (algo
resumida) una de Jos M. Aguilar, recopilada en su blog "Variable not found". El
artculo original se puede encontrar en:

http://www.variablenotfound.com/2007/12/13-consejos-para-comentar-tu-
cdigo.html

1. Comenta a varios niveles


Comenta los distintos bloques de los que se compone tu cdigo, aplicando un
criterio uniforme y distinto para cada nivel, por ejemplo:
En cada clase, incluir una breve descripcin, su autor y fecha de ltima
modificacin
Por cada mtodo, una descripcin de su objeto y funcionalidades, as como de los
parmetros y resultados obtenidos
(Lo importante es ceirse a unas normas y aplicarlas siempre).

2. Usa prrafos comentados


Adems, es recomendable dividir un bloque de cdigo extenso en "prrafos" que
realicen una tarea simple, e introducir un comentario al principio adems de una
lnea en blanco para separar bloques:

// Comprobamos si todos los datos


// son correctos
foreach (Record record in records)
{
...

Revisin 0.99zz Pgina 378


Introduccin a la programacin con C#, por Nacho Cabanes

3. Tabula por igual los comentarios de lneas consecutivas


Si tenemos un bloque de lneas de cdigo, cada una con un comentario, ser ms
legible si estn alineados:

const MAX_ITEMS = 10; // Nmero mximo de paquetes


const MASK = 0x1F; // Mscara de bits TCP

Ojo a las tabulaciones. Hay editores de texto que usan el carcter ASCII (9) y otros,
lo sustituyen por un nmero determinado de espacios, que suelen variar segn las
preferencias personales del desarrollador. Lo mejor es usar espacios simples o
asegurarse de que esto es lo que hace el IDE correspondiente.

4. No insultes la inteligencia del lector


Debemos evitar comentarios absurdos como:

if (a == 5) // Si a vale cinco, ...


contador = 0; // ... ponemos el contador a cero
...

5. S correcto
Evita comentarios del tipo "ahora compruebo que el estpido usuario no haya
introducido un nmero negativo", o "este parche corrige el efecto colateral
producido por la pattica implementacin del inepto desarrollador inicial".
Relacionado e igualmente importante: cuida la ortografa.

6. No pierdas el tiempo
No comentes si no es necesario, no escribas nada ms que lo que necesites para
transmitir la idea. Nada de diseos realizados a base de caracteres ASCII, ni
florituras, ni chistes, ni poesas, ni chascarrillos.

7. Utiliza un estilo consistente


Hay quien opina que los comentarios deberan ser escritos para que los
entendieran no programadores. Otros, en cambio, piensan que debe servir de
ayuda para desarrolladores exclusivamente. En cualquier caso, lo que importa es
que siempre sea de la misma forma, orientados al mismo destinatario.

8. Para los comentarios internos, usa marcas especiales

Revisin 0.99zz Pgina 379


Introduccin a la programacin con C#, por Nacho Cabanes

Y sobre todo cuando se trabaja en un equipo de programacin. El ejemplo tpico


es el comentario TODO (to-do, por hacer), que describe funciones pendientes de
implementar:

int calcula(int x, int y)


{
// TODO: implementar los clculos
return 0;
}

9. Comenta mientras programas


Ve introduciendo los comentarios conforme vas codificando. No lo dejes para el
final, puesto que entonces te costar ms del doble de tiempo, si es que llegas a
hacerlo. Olvida las posturas "no tengo tiempo de comentar, voy muy apurado", "el
proyecto va muy retrasado"... son simplemente excusas.

Hay incluso quien opina que los comentarios que describen un bloque deberan
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:

public void ProcesaPedido()


{
// Comprobar que hay material
// Comprobar que el cliente es vlido
// Enviar la orden a almacn
// Generar factura
}

10. Comenta como si fuera para t mismo. De hecho, lo es.


A la hora de comentar no pienses slo en mantenimiento posterior, ni creas que
es un regalo que dejas para la posteridad del que slo obtendr beneficios el
desarrollador que en el futuro sea designado para corregir o mantener tu cdigo.
En palabras del genial Phil Haack, "tan pronto como una lnea de cdigo sale de la
pantalla y volvemos a ella, estamos en modo mantenimiento de la misma"

11. Actualiza los comentarios a la vez que el cdigo


De nada sirve comentar correctamente una porcin de cdigo si en cuanto ste es
modificado no se actualizan tambin los comentarios. Ambos deben evolucionar

Revisin 0.99zz Pgina 380


Introduccin a la programacin con C#, por Nacho Cabanes

paralelamente, o estaremos haciendo ms difcil la vida del desarrollador que


tenga que mantener el software, al darle pistas incorrectas.

12. La regla de oro del cdigo legible


Es uno de los principios bsicos para muchos desarrolladores: deja que tu cdigo
hable por s mismo. Aunque se sospecha que este movimiento est liderado por
programadores a los que no les gusta comentar su cdigo ;-), es totalmente cierto
que una codificacin limpia puede hacer innecesaria la introduccin de textos
explicativos adicionales:

Console.WriteLine("Resultado: " +
new Calculator()
.Set(0)
.Add(10)
.Multiply(2)
.Substract(4)
.Get()
);

13. Difunde estas prcticas entre tus colegas


Aunque nosotros mismos nos beneficiamos inmediatamente de las bondades de
nuestro cdigo comentado, la generalizacin y racionalizacin de los comentarios
y la creacin cdigo inteligible nos favorecer a todos, sobre todo en contextos de
trabajo en equipo.

14.4.2. Generacin de documentacin a partir del cdigo


fuente.
Conocemos los comentarios de bloque (/* hasta */) y los comentarios hasta final
de lnea (a partir de una doble barra // ).

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 automtica cierta documentacin del programa.

Esta documentacin tpicamente ser una serie pginas HTML enlazadas, o bien
varios ficheros XML.

Por ejemplo, la herramienta (gratuita) Doxygen genera pginas como sta a partir
de un fuente en C#:

Revisin 0.99zz Pgina 381


Introduccin a la programacin con C#, por Nacho Cabanes

La forma de conseguirlo es empleando otros dos tipos de comentarios:


comentarios de documentacin en bloque (desde /** hasta */) y los comentarios
de documentacin hasta final de lnea (a partir de una triple barra /// ).

Lo habitual es que estos tipos de comentarios se utilicen al principio de cada clase


y de cada mtodo, as:

/**
* Personaje: uno de los tipos de elementos graficos del juego
*
* @see ElemGrafico Juego
* @author 1-DAI 2008/09
*/

public class Personaje : ElemGrafico


{
/// Mueve el personaje a la derecha, si es posible (sin obstculos)
public void MoverDerecha()
{
(...)
}

(...)

Revisin 0.99zz Pgina 382


Introduccin a la programacin con C#, por Nacho Cabanes

/// Animacin cuando el personaje muere: ambulancia, etc.


public void Morir()
{
(...)
}
}

Adems, es habitual que tengamos a nuestra disposicin ciertas "palabras


reservadas" para poder afinar la documentacin, como @returns, que da
informacin sobre el valor que devuelve una funcin, o como @see, para detallar
qu otras clases de nuestro programa estn relacionadas con la actual.

As, comparando el fuente anterior y la documentacin que se genera a partir de


l, podemos ver que:

El comentario de documentacin inicial, creado antes del comienzo de la


clase, aparece en el apartado "Descripcin detallada".
El comentario de documentacin del mtodo "Morir" se toma como
descripcin de dicha funcin miembro.
La funcin "MoverDerecha" tambin tiene un comentario que la precede,
pero est con el formato de un comentario "normal" (doble barra), por lo
que no se tiene en cuenta al generar la documentacin.
"@author" se usa para el apartado "Autor" de la documentacin.
"@see" se emplea en el apartado "Ver tambin", que incluye enlaces a la
documentacin de otros ficheros relacionados.

En el lenguaje Java, documentacin como esta se puede crear con la herramienta


JavaDoc, que es parte de la distribucin "oficial" del lenguaje. En cambio, en C#, la
herramienta estndar genera documentacin en formato XML, que puede resultar
menos legible, pero se pueden emplear alternativas gratuitas como Doxygen.

La forma que propone Microsoft de crear comentarios de documentacin en C# es


usar etiquetas XML, as:

/// <summary>Mueve el personaje a la derecha, si es posible</summary>


/// <seealso cref="MoverIzquierda"/>
public void MoverDerecha()
{
(...)
}

Revisin 0.99zz Pgina 383


Introduccin a la programacin con C#, por Nacho Cabanes

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 ms sobre el uso que se sugiere para cada una de ellas aqu:

https://msdn.microsoft.com/en-us/library/5ast78ax.aspx

Revisin 0.99zz Pgina 384


Introduccin a la programacin con C#, por Nacho Cabanes

Apnd ice 1. Unid ades de med ida y sistemas de


numeracin
Ap1.1. bytes, kilobytes, megabytes...
En informtica, la unidad bsica de informacin es el byte. En la prctica, podemos
pensar que un byte es el equivalente a una letra. Si un cierto texto est formado
por 2000 letras, podemos esperar que ocupe unos 2000 bytes de espacio en
nuestro disco.

Eso s, suele ocurrir que realmente un texto de 2000 letras que se guarde en el
ordenador ocupe ms de 2000 bytes, porque se suele incluir informacin adicional
sobre los tipos de letra que se han utilizado, cursivas, negritas, mrgenes y
formato de pgina, etc.

Un byte se queda corto a la hora de manejar textos o datos algo ms largos, con lo
que se recurre a un mltiplo suyo, el kilobyte, que se suele abreviar Kb o K.

En teora, el prefijo kilo querra decir "mil", luego un kilobyte debera 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 tpicas 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 pgina mecanografiada suele ocupar entre 2 K (cerca de 2000
letras) y 4 Kb.

Cuando se manejan datos ms extensos, necesitamos otro mltiplo, el megabyte


o Mb, que es 1000 Kb (en realidad 1024 Kb) o algo ms de un milln de bytes. Por
ejemplo, en un diskette caban 1.44 Mb, y en un Compact Disc para ordenador
(CD-ROM) se pueden almacenar hasta 700 Mb.

Para unidades de almacenamiento de gran capacidad, su tamao no se suele


medir en megabytes, sino en el mltiplo siguiente: en gigabytes, con la
correspondencia 1 Gb = 1024 Mb. As, es habitual que un equipo actual tenga una
memoria RAM entre 4 y 8 Gb, as como un disco duro de entre 500 y 2.000 Mb (2
Terabytes).

Revisin 0.99zz Pgina 385


Introduccin a la programacin con C#, por Nacho Cabanes

Todo esto se puede resumir as:

Unidad Equivalencia Valores posibles


Byte - 0 a 255 (para guardar 1 letra)
Kilobyte (K o Kb) 1024 bytes Aprox. media pgina mecanografiada
Megabyte (Mb) 1024 Kb -
Gigabyte (Gb) 1024 Mb -
Terabyte (Tb) 1024 Gb -

Ejercicios propuestos:
(Ap1.1.1) Cuntas letras se podran almacenar en una agenda electrnica que
tenga 32 Kb de capacidad?
(Ap1.1.2) Si suponemos que una cancin tpica en formato MP3 ocupa cerca de
3.500 Kb, cuntas se podran guardar en un reproductor MP3 que tenga 256 Mb
de capacidad?
(Ap1.1.3) Cuntos diskettes de 1,44 Mb haran 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, cuntos necesitaramos?
(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?

Ap1.2. Unidades de medida empleadas en informtica (2):


los bits
Dentro del ordenador, la informacin se debe almacenar realmente de alguna
forma que a l le resulte "cmoda" de manejar. Como la memoria del ordenador
se basa en componentes electrnicos, la unidad bsica de informacin ser que
una posicin de memoria est usada o no (totalmente llena o totalmente vaca), lo
que se representa como un 1 o un 0. Esta unidad recibe el nombre de bit.

Un bit es demasiado pequeo para un uso normal (recordemos: slo puede tener
dos valores: 0 1), por lo que se usa un conjunto de ellos, 8 bits, que forman un
byte. Las matemticas elementales (combinatoria) nos dicen que si agrupamos los
bits de 8 en 8, tenemos 256 posibilidades distintas (variaciones con repeticin de 2
elementos tomados de 8 en 8: VR2,8):

00000000
00000001
00000010
00000011
Revisin 0.99zz Pgina 386
Introduccin a la programacin con C#, por Nacho Cabanes

00000100
...
11111110
11111111

Por tanto, si en vez de tomar los bits de 1 en 1 (que resulta cmodo 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 ms
que suficientes para almacenar una letra, o un signo de puntuacin, o una cifra
numrica o algn otro smbolo.

Por ejemplo, se podra decir que cada vez que encontremos la secuencia 00000010
la interpretaremos como una letra A, y la combinacin 00000011 como una letra B,
y as sucesivamente (aunque existe un estndar distinto, que comentaremos un
poco ms adelante).

Tambin existe una correspondencia entre cada grupo de bits y un nmero del 0
al 255: si usamos el sistema binario de numeracin (que aprenderemos dentro de
muy poco), en vez del sistema decimal, tenemos que:

0000 0000 (binario) = 0 (decimal)


0000 0001 (binario) = 1 (decimal)
0000 0010 (binario) = 2 (decimal)
0000 0011 (binario) = 3 (decimal)
...
1111 1110 (binario) = 254 (decimal)
1111 1111 (binario) = 255 (decimal)

En la prctica, existe un cdigo estndar, el cdigo ASCII (American Standard Code


for Information Interchange, cdigo estndar americano para intercambio de
informacin), que relaciona cada letra, nmero o smbolo con una cifra del 0 al 255
(realmente, con una secuencia de 8 bits): la "a" es el nmero 97, la "b" el 98, la "A"
el 65, la "B", el 32, el "0" el 48, el "1" el 49, etc. As se tiene una forma muy cmoda
de almacenar la informacin en ordenadores, ya que cada letra ocupar
exactamente un byte (8 bits: 8 posiciones elementales de memoria). En el prximo
apndice tendrs un extracto de este cdigo.

Aun as, hay un inconveniente con el cdigo ASCII: slo los primeros 127 nmeros
son estndar. Eso quiere decir que si escribimos un texto en un ordenador y lo
llevamos a otro, las letras bsicas (A a la Z, 0 al 9 y algunos smbolos) no
cambiarn, pero las letras internacionales (como la o las vocales con acentos)
puede que no aparezcan correctamente, porque se les asignan nmeros que no
son estndar para todos los ordenadores. Por eso, existen estndares ms
modernos, como UTF-8, que comentaremos en el siguiente apartado.
Revisin 0.99zz Pgina 387
Introduccin a la programacin con C#, por Nacho Cabanes

Nota: Eso de que realmente el ordenador trabaja con ceros y unos, por lo que le
resulta ms fcil manejar los nmeros que son potencia de 2 que los nmeros 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 ms cercana: 210 =1024. Por eso, la equivalencia
exacta es 1 K = 1024 bytes.

Ejercicios propuestos:
(Ap1.2.1) Un nmero entero largo de 64 bits, cuntos bytes ocupa?
(Ap1.2.2) En una conexin de red de 100 Mbits/segundo, cunto tiempo tardara
en enviar 630 Mbytes de datos?

Revisin 0.99zz Pgina 388


Introduccin a la programacin con C#, por Nacho Cabanes

Apnd ice 2. El cdigo ASCII


El nombre del cdigo ASCII viene de "American Standard Code for Information
Interchange", que se podra traducir como Cdigo Estndar Americano para
Intercambio de Informacin.

La idea de este cdigo es que se pueda compartir informacin entre distintos


ordenadores o sistemas informticos. Para ello, se hace corresponder una letra o
carcter a cada secuencia de varios bits.

El cdigo ASCII estndar 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 cdigo ASCII, la secuencia de bits
1000001 (65 en decimal) corresponder a la letra "A" (a, en maysculas).

Los cdigos estndar "visibles" van del 32 al 127. Los cdigos del 0 al 31 son
cdigos de control: por ejemplo, el caracter 7 (BEL) hace sonar un pitido, el
caracter 13 (CR) avanza de lnea, el carcter 12 (FF) expulsa una pgina en la
impresora (y borra la pantalla en algunos ordenadores), etc.

Estos cdigos ASCII estndar son:

| 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 da, casi todos los ordenadores incluyen un cdigo ASCII extendido de 8
bits, que permite 256 smbolos (del 0 al 255), lo que permite que se puedan usar
tambin vocales acentuadas, ees, y otros smbolos para dibujar recuadros, por
ejemplo, aunque estos smbolos que van del nmero 128 al 255 no son estndar,

Revisin 0.99zz Pgina 389


Introduccin a la programacin con C#, por Nacho Cabanes

de modo que puede que otro ordenador no los interprete correctamente si el


sistema operativo que utiliza es distinto.

Una alternativa ms moderna es el estndar UTF-8, que permite usar caracteres


de ms de 8 bits (tpicamente 16 bits, que dara lugar a 65.536 smbolos distintos),
lo que permite que la transferencia de informacin entre distintos sistemas no
tenga problemas de distintas interpretaciones.

Ejercicios propuestos:
(Ap2.1) Crea un programa en C# que muestre una tabla ASCII bsica, desde el
carcter 32 hasta el 127, en filas de 16 caracteres cada una.

Revisin 0.99zz Pgina 390


Introduccin a la programacin con C#, por Nacho Cabanes

Apnd ice 3. Sistemas de numeracin.


Ap3.1. Sistema binario
Nosotros normalmente utilizamos el sistema decimal de numeracin: todos los
nmeros se expresan a partir de potencias de 10, pero normalmente lo hacemos
sin pensar.

Por ejemplo, el nmero 3.254 se podra desglosar como:

3.254 = 3 1000 + 2 100 + 5 10 + 4 1

o ms detallado todava:

254 = 3 103 + 2 102 + 5 101 + 4 100

Para los ordenadores no es cmodo contar hasta 10. Como partimos de "casillas
de memoria" que estn completamente vacas (0) o completamente llenas (1), slo
les es realmente cmodo contar con 2 cifras: 0 y 1.

Por eso, dentro del ordenador cualquier nmero se deber almacenar como ceros
y unos, y entonces los nmeros se debern desglosar en potencias de 2 (el
llamado "sistema binario"):

13 = 1 8 + 1 4 + 0 2 + 1 1

o, ms detallado:

3 2 1 0
13 = 1 2 + 1 2 + 0 2 + 1 2

de modo que el nmero decimal 13 se escribir en binario como 1101.

En general, convertir un nmero binario al sistema decimal es fcil: lo


expresamos como suma de potencias de 2 y sumamos:

0110 1101 (binario) =


7 6 5 4 3 2 1 0
0 2 + 1 2 + 1 2 + 0 2 + 1 2 + 1 2 + 0 2 + 1 2 =
0 128 + 1 64 + 1 32 + 0 16 + 1 8 + 1 4 + 0 2 + 1 1 =
109 (decimal)

Revisin 0.99zz Pgina 391


Introduccin a la programacin con C#, por Nacho Cabanes

Convertir un nmero 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:

109 / 128 = 0 (resto: 109)


109 / 64 = 1 (resto: 45)
45 / 32 = 1 (resto: 13)
13 / 16 = 0 (resto: 13)
13 / 8 = 1 (resto: 5)
5 / 4 = 1 (resto: 1)
1 / 2 = 0 (resto: 1)
1 / 1 = 1 (hemos terminado).

Si "juntamos" los cocientes que hemos obtenido, aparece el nmero binario que
buscbamos:

109 decimal = 0110 1101 binario

(Nota: es frecuente separar los nmeros binarios en grupos de 4 cifras -medio


byte- para mayor legibilidad, como yo he hecho en el ejemplo anterior; a un grupo
de 4 bits se le llama nibble).

Otra forma sencilla de convertir de decimal a binario es dividir consecutivamente


entre 2 y coger los restos que hemos obtenido, pero en orden inverso:

109 / 2 = 54, resto 1


54 / 2 = 27, resto 0
27 / 2 = 13, resto 1
13 /2 = 6, resto 1
6 / 2 = 3, resto 0
3 / 2 = 1, resto 1
1 / 2 = 0, resto 1
(y ya hemos terminado)

Si leemos esos restos de abajo a arriba, obtenemos el nmero binario: 1101101 (7


cifras, si queremos completarlo a 8 cifras rellenamos con ceros por la izquierda:
01101101).

Y se pueden hacer operaciones con nmeros binarios? S, casi igual que en


decimal:

00 = 0 01 = 0 10 = 0 11 = 1
0+0 = 0 0+1 = 1 1+0 = 1 1+1 = 10 (en decimal: 2)

Revisin 0.99zz Pgina 392


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicios propuestos:
(Ap3.1.1) Expresa en sistema binario los nmeros decimales 17, 101, 83, 45.
(Ap3.1.2) Expresa en sistema decimal los nmeros binarios de 8 bits: 01100110,
10110010, 11111111, 00101101
(Ap3.1.3) Suma los nmeros 01100110+10110010, 11111111+00101101.
Comprueba el resultado sumando los nmeros decimales obtenidos en el ejercicio
anterior.
(Ap3.1.4) Multiplica los nmeros binarios de 4 bits 01001011, 10010011.
Comprueba el resultado convirtindolos a decimal.
(Ap3.1.5) Crea un programa en C# que convierta a binario los nmeros (en base
10) que introduzca el usuario.
(Ap3.1.6) Crea un programa en C# que convierta a base 10 los nmeros binarios
que introduzca el usuario.

Ap3.2. Sistema octal


Hemos visto que el sistema de numeracin ms cercano a como se guarda la
informacin dentro del ordenador es el sistema binario. Pero los nmeros
expresados en este sistema de numeracin "ocupan mucho". Por ejemplo, el
nmero 254 se expresa en binario como 11111110 (8 cifras en vez de 3).

Por eso, se han buscado otros sistemas de numeracin que resulten ms


"compactos" que el sistema binario cuando haya que expresar cifras
medianamente grandes, pero que a la vez mantengan con ste una
correspondencia algo ms sencilla que el sistema decimal. Los ms usados son el
sistema octal y, sobre todo, el hexadecimal.

El sistema octal de numeracin trabaja en base 8. La forma de convertir de


decimal a binario ser, como siempre dividir entre las potencias de la base. Por
ejemplo:

254 (decimal) ->


254 / 64 = 3 (resto: 62)
62 / 8 = 7 (resto: 6)
6 / 1 = 6 (se termin)

de modo que

2 1 0
254 = 3 8 + 7 8 + 6 8

Revisin 0.99zz Pgina 393


Introduccin a la programacin con C#, por Nacho Cabanes

o bien

254 (decimal) = 376 (octal)

Hemos conseguido otra correspondencia que, si bien nos resulta a nosotros ms


incmoda que usar el sistema decimal, al menos es ms compacta: el nmero 254
ocupa 3 cifras en decimal, y tambin 3 cifras en octal, frente a las 8 cifras que
necesitaba en sistema binario.

Pero adems 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
rapidsimo

254 (decimal) = 011 111 110 (binario)

011 (binario ) = 3 (decimal y octal)


111 (binario ) = 7 (decimal y octal)
110 (binario ) = 6 (decimal y octal)

de modo que

254 (decimal) = 011 111 110 (binario) = 376 (octal)

El paso desde el octal al binario y al decimal tambin es sencillo. Por ejemplo, el


nmero 423 (octal) sera 423 (octal) = 100 010 011 (binario)

o bien

423 (octal) = 4 64 + 2 8 + 3 1 = 275 (decimal)

De cualquier modo, el sistema octal no es el que ms se utiliza en la prctica, sino


el hexadecimal...

Ejercicios propuestos:
(Ap3.2.1) Expresar en sistema octal los nmeros decimales 17, 101, 83, 45.
(Ap3.2.2) Expresar en sistema octal los nmeros binarios de 8 bits: 01100110,
10110010, 11111111, 00101101
(Ap3.2.3) Expresar en el sistema binario los nmeros octales 171, 243, 105, 45.
(Ap3.2.4) Expresar en el sistema decimal los nmeros octales 162, 76, 241, 102.
(Ap3.1.5) Crea un programa en C# que convierta a octal los nmeros (en base 10)
que introduzca el usuario.
(Ap3.1.6) Crea un programa en C# que convierta a base 10 los nmeros octales
que introduzca el usuario.
Revisin 0.99zz Pgina 394
Introduccin a la programacin con C#, por Nacho Cabanes

Ap3.3. Sistema hexadecimal


El sistema octal tiene un inconveniente: se agrupan los bits de 3 en 3, por lo que
convertir de binario a octal y viceversa es muy sencillo, pero un byte est formado
por 8 bits, que no es mltiplo de 3.

Sera ms cmodo poder agrupar de 4 en 4 bits, de modo que cada byte se


4
representara por 2 cifras. Este sistema de numeracin trabajar en base 16 (2
=16), y es lo que se conoce como sistema hexadecimal.

Pero hay una dificultad: estamos acostumbrados al sistema decimal, con nmeros
del 0 al 9, de modo que no tenemos cifras de un solo dgito para los nmeros 10,
11, 12, 13, 14 y 15, que utilizaremos en el sistema hexadecimal. Para representar
estas cifras usaremos las letras de la A a la F, as:

0 (decimal) = 0 (hexadecimal)
1 (decimal) = 1 (hexadecimal)
2 (decimal) = 2 (hexadecimal)
3 (decimal) = 3 (hexadecimal)
4 (decimal) = 4 (hexadecimal)
5 (decimal) = 5 (hexadecimal)
6 (decimal) = 6 (hexadecimal)
7 (decimal) = 7 (hexadecimal)
8 (decimal) = 8 (hexadecimal)
9 (decimal) = 9 (hexadecimal)
10 (decimal) = A (hexadecimal)
11 (decimal) = B (hexadecimal)
12 (decimal) = C (hexadecimal)
13 (decimal) = D (hexadecimal)
14 (decimal) = E (hexadecimal)
15 (decimal) = F (hexadecimal)

Con estas consideraciones, expresar nmeros en el sistema hexadecimal ya no es


difcil:

254 (decimal) ->


254 / 16 = 15 (resto: 14)
14 / 1 = 14 (se termin)

de modo que

1 0
254 = 15 16 + 14 16

o bien
Revisin 0.99zz Pgina 395
Introduccin a la programacin con C#, por Nacho Cabanes

254 (decimal) = FE (hexadecimal)

Vamos a repetirlo para un convertir de decimal a hexadecimal nmero ms


grande:

54331 (decimal) ->


54331 / 4096 = 13 (resto: 1083)
1083 / 256 = 4 (resto: 59)
59 / 16 = 3 (resto: 11)
11 / 1 = 11 (se termin)

de modo que

54331 = 13 4096 + 4 256 + 3 16 + 11 1

o bien

3 2 1 0
254 = 13 16 + 4 16 + 3 16 + 11 16

es decir

54331 (decimal) = D43B (hexadecimal)

Ahora vamos a dar el paso inverso: convertir de hexadecimal a decimal, por


ejemplo el nmero A2B5

3 2 1 0
A2B5 (hexadecimal) = 10 16 + 2 16 + 11 16 + 5 16 = 41653

El paso de hexadecimal a binario tambin es (relativamente) rpido, porque cada


dgito hexadecimal equivale a una secuencia de 4 bits:

0 (hexadecimal) = 0 (decimal) = 0000 (binario)


1 (hexadecimal) = 1 (decimal) = 0001 (binario)
2 (hexadecimal) = 2 (decimal) = 0010 (binario)
3 (hexadecimal) = 3 (decimal) = 0011 (binario)
4 (hexadecimal) = 4 (decimal) = 0100 (binario)
5 (hexadecimal) = 5 (decimal) = 0101 (binario)
6 (hexadecimal) = 6 (decimal) = 0110 (binario)
7 (hexadecimal) = 7 (decimal) = 0111 (binario)
8 (hexadecimal) = 8 (decimal) = 1000 (binario)
9 (hexadecimal) = 9 (decimal) = 1001 (binario)
A (hexadecimal) = 10 (decimal) = 1010 (binario)
B (hexadecimal) = 11 (decimal) = 1011 (binario)
C (hexadecimal) = 12 (decimal) = 1100 (binario)
D (hexadecimal) = 13 (decimal) = 1101 (binario)
E (hexadecimal) = 14 (decimal) = 1110 (binario)
Revisin 0.99zz Pgina 396
Introduccin a la programacin con C#, por Nacho Cabanes

F (hexadecimal) = 15 (decimal) = 1111 (binario)

de modo que A2B5 (hexadecimal) = 1010 0010 1011 0101 (binario)

y de igual modo, de binario a hexadecimal es dividir en grupos de 4 bits y hallar


el valor de cada uno de ellos:

110010100100100101010100111 =>
0110 0101 0010 0100 1010 1010 0111 = 6524AA7

Ejercicios propuestos:
(Ap3.3.1) Expresa en sistema hexadecimal los nmeros decimales 18, 131, 83, 245.
(Ap3.3.2) Expresa en sistema hexadecimal los nmeros binarios de 8 bits:
01100110, 10110010, 11111111, 00101101
(Ap3.3.3) Expresa en el sistema binario los nmeros hexadecimales 2F, 37, A0,
1A2.
(Ap3.3.4) Expresa en el sistema decimal los nmeros hexadecimales 1B2, 76, E1,
2A.
(Ap3.3.5) Crea un programa en C# que convierta a hexadecimal los nmeros (en
base 10) que introduzca el usuario.
(Ap3.3.6) Crea un programa en C# que convierta a base 10 los nmeros
hexadecimales que introduzca el usuario.
(Ap3.3.7) Crea un programa en C# que convierta a hexadecimal los nmeros
binarios que introduzca el usuario.

Ap3.4. Representacin interna de los enteros negativos


Para los nmeros enteros negativos, existen varias formas posibles de
representarlos. Las ms habituales son:

Signo y magnitud: el primer bit (el de ms a la izquierda) se pone a 1 si el nmero


es negativo y se deja a 0 si es positivo. Los dems bits se calculan como ya hemos
visto.

Por ejemplo, si usamos 4 bits, tendramos

3 (decimal) = 0011 -3 = 1011


6 (decimal) = 0110 -6 = 1110

Revisin 0.99zz Pgina 397


Introduccin a la programacin con C#, por Nacho Cabanes

Es un mtodo muy sencillo, pero que tiene el inconveniente de que las


operaciones en las que aparecen nmeros negativos no se comportan
correctamente. Vamos a ver un ejemplo, con nmeros de 8 bits:

13 (decimal) = 0000 1101 - 13 (decimal) = 1000 1101


34 (decimal) = 0010 0010 - 34 (decimal) = 1010 0010
13 + 34 = 0000 1101 + 0010 0010 = 0010 1111 = 47 (correcto)
(-13) + (-34) = 1000 1101 + 1010 0010 = 0010 1111 = 47 (INCORRECTO)
13 + (-34) = 0000 1101 + 1010 0010 = 1010 1111 = -47 (INCORRECTO)

Complemento a 1: se cambian los ceros por unos para expresar los nmeros
negativos.

Por ejemplo, con 4 bits

3 (decimal) = 0011 -3 = 1100


6 (decimal) = 0110 -6 = 1001

Tambin es un mtodo sencillo, en el que las operaciones con nmeros negativos


salen bien, y que slo tiene como inconveniente que hay dos formas de expresar
el nmero 0 (0000 0000 o 1111 1111), lo que complica algunos trabajos internos
del ordenador.

Ejercicio propuesto: convierte los nmeros decimales 13, 34, -13, -34 a sistema
binario, usando complemento a uno para expresar los nmeros negativos. Calcula
(en binario) el resultado de las operaciones 13+34, (-13)+(-34), 13+(-34) y
comprueba que los resultados que se obtienen son los correctos.

Complemento a 2: para los negativos, se cambian los ceros por unos y se suma
uno al resultado.

Por ejemplo, con 4 bits

3 (decimal) = 0011 -3 = 1101


6 (decimal) = 0110 -6 = 1010

Es un mtodo que parece algo ms complicado, pero que no es difcil de seguir,


con el que las operaciones con nmeros negativos siempre se realizan de forma
correcta, y no tiene problemas para expresar el nmero 0 (que siempre se
almacena como 00000000).

Revisin 0.99zz Pgina 398


Introduccin a la programacin con C#, por Nacho Cabanes

Ejercicio propuesto (Ap3.4.1): Convierte los nmeros decimales 13, 34, -13, -34 a
sistema binario, usando complemento a dos para expresar los nmeros negativos.
Calcula (en binario) el resultado de las operaciones 13+34, (-13)+(-34), 13+(-34) y
comprueba que los resultados que se obtienen son los correctos.

En general, todos los formatos que permiten guardar nmeros negativos usan el
primer bit para el signo. Por eso, si declaramos una variable como "unsigned", ese
primer bit se puede utilizar como parte de los datos, y podemos almacenar
nmeros ms grandes. Por ejemplo, un "ushort" (enterno corto sin signo) en C#
podra tomar valores entre 0 y 65.535, mientras que un "short" (entero corto con
signo) podra tomar valores entre +32.767 y 32.768.

Revisin 0.99zz Pgina 399


Introduccin a la programacin con C#, por Nacho Cabanes

Apnd ice 4. SDL


Ap4.1. Juegos con Tao.SDL
SDL es una conocida biblioteca para la realizacin de juegos, que est disponible
para diversos sistemas operativos y que permite tanto dibujar imgenes como
comprobar el teclado, el ratn o el joystick/gamepad, as como reproducir sonidos.

Tao.SDL es una adaptacin de esta librera, que permite emplearla desde C#. Las
primeras versiones se pueden descargar desde http://www.mono-project.com/Tao y
las versiones ms recientes desde http://sourceforge.net/projects/taoframework/

SDL no es una librera especialmente sencilla, y tampoco lo acaba de ser Tao.SDL,


as que los fuentes siguientes pueden resultar difciles de entender, a pesar de
realizar tareas muy bsicas. Por eso, muchas veces es preferible "ocultar" los
detalles de SDL creando nuestras propias clases "Hardware", "Imagen", etc., como
haremos un poco ms adelante.

Ap4.2. Mostrar una imagen esttica


Vamos a ver un primer ejemplo, bsico pero completo, que muestre cmo entrar a
modo grfico, cargar una imagen, dibujarla en pantalla, esperar cinco segundos y
volver al sistema operativo. Tras el fuente comentaremos las principales
funciones.

// sdl01.cs
// Primer acercamiento a SDL
// Introduccin a C#, por Nacho Cabanes

using Tao.Sdl;
using System; // Para IntPtr (puntero: imgenes, etc)

public class Sdl01


{

public static void Main()


{

short anchoPantalla = 800;


short altoPantalla = 600;
int bitsColor = 24;
int flags = Sdl.SDL_HWSURFACE|Sdl.SDL_DOUBLEBUF|Sdl.SDL_ANYFORMAT;
IntPtr pantallaOculta;

// Inicializamos SDL
Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING);
Revisin 0.99zz Pgina 400
Introduccin a la programacin con C#, por Nacho Cabanes

pantallaOculta = Sdl.SDL_SetVideoMode(
anchoPantalla,
altoPantalla,
bitsColor,
flags);

// Indicamos que se recorte lo que salga de la pantalla oculta


Sdl.SDL_Rect rect2 =
new Sdl.SDL_Rect(0,0, (short) anchoPantalla, (short) altoPantalla);
Sdl.SDL_SetClipRect(pantallaOculta, ref rect2);

// Cargamos una imagen


IntPtr imagen;
imagen = Sdl.SDL_LoadBMP("personaje.bmp");
if (imagen == IntPtr.Zero) {
System.Console.WriteLine("Imagen inexistente!");
Environment.Exit(4);
}

// Dibujamos la imagen
short x = 400;
short y = 300;
short anchoImagen = 50;
short altoImagen = 50;
Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0,0,anchoImagen,altoImagen);
Sdl.SDL_Rect dest = new Sdl.SDL_Rect(x,y,anchoImagen,altoImagen);
Sdl.SDL_BlitSurface(imagen, ref origen, pantallaOculta, ref dest);

// Mostramos la pantalla oculta


Sdl.SDL_Flip(pantallaOculta);

// Y esperamos 5 segundos
System.Threading.Thread.Sleep( 5000 );

// Finalmente, cerramos SDL


Sdl.SDL_Quit();

Algunas ideas bsicas:


SDL_Init es la rutina de inicializacin, que entra a modo grfico, con cierto
ancho y alto de pantalla, cierta cantidad de colores y ciertas opciones
adicionales.
El tipo SDL_Rect define un "rectngulo" a partir de su origen (x e y), su
ancho y su alto, y se usa en muchas operaciones.
SDL_SetClipRect indica la zona de recorte (clipping) del tamao de la
pantalla, para que no tengamos que preocuparnos por si dibujamos una
imagen parcialmente (o completamente) fuera de la pantalla visible.
SDL_LoadBMP carga una imagen en formato BMP (si slo usamos SDL
"puro", no podremos emplear otros tipos que permitan menores tamaos,
como el JPG, o transparencia, como el PNG). El tipo de dato que se obtiene

Revisin 0.99zz Pgina 401


Introduccin a la programacin con C#, por Nacho Cabanes

es un "IntPtr" (del que no daemos ms detalles), y la forma de comprobar si


realmente se ha podido cargar la imagen es mirando si el valor obtenido es
IntPtr.Zero (y en ese caso, no se habra podido cargar) u otro distinto (y
entonces la imagen se habra leido sin problemas).
SDL_BlitSurface vuelca un rectngulo (SDL_Rect) sobre otro, y lo usamos
para ir dibujando todas las imgenes en una pantalla oculta, y finalmente
volcar toda esa pantalla oculta a la pantalla visible, con lo que se evitan
parpadeos (es una tcnica que se conoce como "doble buffer").
SDL_Flip vuelca esa pantalla oculta a la pantalla visible (el paso final de ese
"doble buffer").
Para la pausa no hemos usado ninguna funcin de SDL, sino Thread.Sleep,
que ya habamos comentado en el apartado sobre temporizacin.
SDL_Quit libera los recursos (algo que generalmente haramos desde un
destructor).

Para compilar este ejemplo usando Mono, deberemos:

Teclear (o copiar y pegar) el fuente.


Copiar en la misma carpeta los ficheros DLL (Tao.Sdl.Dll y SDL.Dll) y las
imgenes (en este caso, "personaje.bmp").
Compilar con:

gmcs sdl01.cs /r:Tao.Sdl.dll /platform:x86

Y si empleamos Visual Studio o SharpDevelop, tendremos que:

Crear un proyecto de "aplicacin de consola".


Reemplazar nuestro programa principal por ste.
Copiar en la carpeta de ejecutables (tpicamente bin/debug) los ficheros DLL
(Tao.Sdl.Dll y SDL.Dll) y las imgenes (en este caso, "personaje.bmp").
Aadir el fichero Tao.Sdl.Dll a las referencias del proyecto (normalmente,
pulsando el botn derecho del ratn en el apartado "Referencias" de la
ventana del Explorador de soluciones y escogiendo la opcin "Agregar
referencia").
Compilar y probar.
Cuando todo funcione correctamente, podremos cambiar el "tipo de
proyecto" (desde el men "Proyecto", en la opcin "Propiedades") a
"Aplicacin de Windows", para que no aparezca la ventana negra de
consola cuando lanzamos nuestro programa.

Revisin 0.99zz Pgina 402


Introduccin a la programacin con C#, por Nacho Cabanes

No necesitaremos ninguna DLL adicional si no vamos a usar imgenes


comprimidas (PNG o JPG), ni tipos de letra TTF, ni sonidos en formato MP3, ni
funciones adicionales de dibujo (lneas, recuadros, crculos, etc).

Puedes descargar este fuente, incluyendo el proyecto de Visual Studio 2010, los
ficheros DLL necesarios y una imagen de ejemplo, desde:

http://www.nachocabanes.com/fich/descargar.php?nombre=SdlBasico.zip

Ejercicios propuestos
(Ap4.2.1) Crea una animacin con al menos 4 fotogramas, utilizando varias
imgenes que se mostrarn de forma repetitiva (por ejemplo, 10 veces, con medio
segundo de pausa entre una imagen y la siguiente).

Ap4.3. Una imagen que se mueve con el teclado


Un segundo ejemplo algo ms detallado podra permitirnos mover el personaje
con las flechas del teclado, a una velocidad de 50 fotogramas por segundo, as:

// sdl02.cs
// Segundo acercamiento a SDL
// Introduccin a C#, por Nacho Cabanes

using Tao.Sdl;
using System; // Para IntPtr (puntero: imgenes, etc)

public class Sdl02


{

public static void Main()


{

short anchoPantalla = 800;


short altoPantalla = 600;
int bitsColor = 24;
int flags = Sdl.SDL_HWSURFACE | Sdl.SDL_DOUBLEBUF | Sdl.SDL_ANYFORMAT
| Sdl.SDL_FULLSCREEN;
IntPtr pantallaOculta;

// Inicializamos SDL
Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING);
pantallaOculta = Sdl.SDL_SetVideoMode(
anchoPantalla,
altoPantalla,
bitsColor,
flags);

// Indicamos que se recorte lo que salga de la pantalla oculta


Sdl.SDL_Rect rect2 =
new Sdl.SDL_Rect(0,0, (short) anchoPantalla, (short) altoPantalla);
Revisin 0.99zz Pgina 403
Introduccin a la programacin con C#, por Nacho Cabanes

Sdl.SDL_SetClipRect(pantallaOculta, ref rect2);

// Cargamos una imagen


IntPtr imagen;
imagen = Sdl.SDL_LoadBMP("personaje.bmp");
if (imagen == IntPtr.Zero) {
System.Console.WriteLine("Imagen inexistente!");
Environment.Exit(4);
}

// Parte repetitiva
bool terminado = false;
short x = 400;
short y = 300;
short anchoImagen = 50;
short altoImagen = 50;
Sdl.SDL_Event suceso;
int numkeys;
byte[] teclas;

do
{
// Comprobamos sucesos
Sdl.SDL_PollEvent(out suceso);
teclas = Sdl.SDL_GetKeyState(out numkeys);

// Miramos si se ha pulsado alguna flecha del cursor


if (teclas[Sdl.SDLK_UP] == 1)
y -= 2;
if (teclas[Sdl.SDLK_DOWN] == 1)
y += 2;
if (teclas[Sdl.SDLK_LEFT] == 1)
x -= 2;
if (teclas[Sdl.SDLK_RIGHT] == 1)
x += 2;
if (teclas[Sdl.SDLK_ESCAPE] == 1)
terminado = true;

// Borramos pantalla
Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0,0,
anchoPantalla,altoPantalla);
Sdl.SDL_FillRect(pantallaOculta, ref origen, 0);
// Dibujamos en sus nuevas coordenadas
origen = new Sdl.SDL_Rect(0,0,anchoImagen,altoImagen);
Sdl.SDL_Rect dest = new Sdl.SDL_Rect(x,y,
anchoImagen,altoImagen);
Sdl.SDL_BlitSurface(imagen, ref origen,
pantallaOculta, ref dest);

// Mostramos la pantalla oculta


Sdl.SDL_Flip(pantallaOculta);

// Y esperamos 20 ms
System.Threading.Thread.Sleep( 20 );

} while (!terminado);

// Finalmente, cerramos SDL


Sdl.SDL_Quit();
Revisin 0.99zz Pgina 404
Introduccin a la programacin con C#, por Nacho Cabanes

}
}

Las diferencias de este fuente con el anterior son:


Al inicializar, aadimos una nueva opcin, Sdl.SDL_FULLSCREEN, para que el
"juego" se ejecute a pantalla completa, en vez de hacerlo en ventana.
Usamos un bucle "do...while" para repetir hasta que se pulse la tecla ESC.
SDL_Event es el tipo de datos que se usa para comprobar "sucesos", como
pulsaciones de teclas, o de ratn, o el uso del joystick.
Con SDL_PollEvent forzamos a que se mire qu sucesos hay pendientes de
analizar.
SDL_GetkeyState obtenemos un array que nos devuelve el estado actual de
cada tecla. Luego podemos mirar cada una de esas teclas accediendo a ese
array con el nombre de la tecla en ingls, as: teclas[Sdl.SDLK_RIGHT]
SDL_FillRect rellena un rectngulo con un cierto color. Es una forma sencilla
de borrar la pantalla o parte de sta.

Ejercicios propuestos
(Ap4.3.1) Ampla el ejemplo 2 de SDL (sdl02.cs) para que al moverse alterne entre
(al menos) dos imgenes, para dar una impresin de movimiento ms real.

Ap4.4. Simplificando con clases auxiliares


Podemos simplificar un poco la estructura del programa si creamos unas clases
auxiliares que nos permitan ocultar los detalles de SDL y hacer un Main ms
legible:

Por ejemplo, podemos buscar que el programa anterior quede simplemente as:

// sdl03.cs
// Tercer acercamiento a SDL: clases auxiliares
// Introduccin a C#, por Nacho Cabanes

public class Sdl03


{

public static void Main()


{
bool pantallaCompleta = true;
Hardware h = new Hardware(800, 600, 24, pantallaCompleta);
Imagen img = new Imagen("personaje.bmp", 50, 50);

// Parte repetitiva
bool terminado = false;
short x = 400;
short y = 300;
Revisin 0.99zz Pgina 405
Introduccin a la programacin 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 necesitaramos una clase "Hardware", que oculte los detalles del acceso a
la pantalla y al teclado. Podra ser as:

// hardware.cs
// Clases auxiliar Hardware para SDL
// Introduccin a C#, por Nacho Cabanes

using Tao.Sdl; // Para acceder a SDL


using System; // Para IntPtr
using System.Threading; // Para Pausa (Thread.Sleep)

public class Hardware


{
short anchoPantalla = 800;
short altoPantalla = 600;
int bitsColor = 24;

IntPtr pantallaOculta;

public Hardware(int ancho, int alto, int colores, bool pantallaCompleta)


{
int flags = Sdl.SDL_HWSURFACE | Sdl.SDL_DOUBLEBUF |
Sdl.SDL_ANYFORMAT;
if (pantallaCompleta)
flags = flags | Sdl.SDL_FULLSCREEN;

// Inicializamos SDL
Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING);
pantallaOculta = Sdl.SDL_SetVideoMode(
anchoPantalla,
Revisin 0.99zz Pgina 406
Introduccin a la programacin con C#, por Nacho Cabanes

altoPantalla,
bitsColor,
flags);

// Indicamos que se recorte lo que salga de la pantalla oculta


Sdl.SDL_Rect rect2 =
new Sdl.SDL_Rect(0, 0, (short)anchoPantalla, (short)altoPantalla);
Sdl.SDL_SetClipRect(pantallaOculta, ref rect2);
}

~Hardware()
{
Sdl.SDL_Quit();
}

public void BorrarPantallaOculta()


{
Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0, 0,
anchoPantalla, altoPantalla);
Sdl.SDL_FillRect(pantallaOculta, ref origen, 0);
}

public void DibujarImagenOculta(Imagen imagen)


{
Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0, 0,
anchoPantalla, altoPantalla);
Sdl.SDL_Rect dest = new Sdl.SDL_Rect(
imagen.GetX(), imagen.GetY(),
imagen.GetAncho(), imagen.GetAlto());
Sdl.SDL_BlitSurface(imagen.GetPuntero(), ref origen,
pantallaOculta, ref dest);
}

public void VisualizarOculta()


{
Sdl.SDL_Flip(pantallaOculta);
}

public bool TeclaPulsada(int c)


{
bool pulsada = false;
Sdl.SDL_PumpEvents();
Sdl.SDL_Event suceso;
Sdl.SDL_PollEvent(out suceso);
int numkeys;
byte[] teclas = Tao.Sdl.Sdl.SDL_GetKeyState(out numkeys);
if (teclas[c] == 1)
pulsada = true;
return pulsada;
}

public void Pausa(int milisegundos)


{
Thread.Sleep(milisegundos);
}

// 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;
Revisin 0.99zz Pgina 407
Introduccin a la programacin con C#, por Nacho Cabanes

public static int TECLA_DER = Sdl.SDLK_RIGHT;


public static int TECLA_IZQ = Sdl.SDLK_LEFT;
}

Y una clase "Imagen" podra ocultar ese "IntPtr", adems de aadirle datos
adicionales, como la posicin en la que se encuentra esa imagen, y otros que ms
adelante nos resultarn tiles, como el ancho y el alto (que nos servirn para
comprobar colisiones):

// imagen.cs
// Clases auxiliar Imagen para SDL
// Introduccin a C#, por Nacho Cabanes

using Tao.Sdl; // Para acceder a SDL


using System; // Para IntPtr

public class Imagen


{
IntPtr imagen;
int x, y;
int ancho, alto;

public Imagen(string nombreFichero, int ancho, int alto)


{
imagen = Sdl.SDL_LoadBMP(nombreFichero);
if (imagen == IntPtr.Zero)
{
System.Console.WriteLine("Imagen inexistente!");
Environment.Exit(1);
}
this.ancho = ancho;
this.alto = alto;
}

public void MoverA(int x, int y)


{
this.x = x;
this.y = y;
}

public IntPtr GetPuntero() { return imagen; }


public short GetX() { return (short)x; }
public short GetY() { return (short)y; }
public short GetAncho() { return (short)ancho; }
public short GetAlto() { return (short)alto; }
}

Al igual que antes, al crear el proyecto deberemos aadir el fichero Tao.Sdl.Dll a


sus referencias, y tendremos que copiar en la carpeta de ejecutables los ficheros
Tao.Sdl.Dll, SDL.Dll y la imagen "personaje.bmp".

Ejercicios propuestos
(Ap4.4.1) Descompn en clases el ejercicio Ap4.3.1.

Revisin 0.99zz Pgina 408


Introduccin a la programacin con C#, por Nacho Cabanes

Ap4.5. Un fuente ms modular: el "bucle de juego"


A medida que nuestro juego se va complicando, el fuente puede irse haciendo ms
difcil de leer, y ms an si no est estructurado, sino que tiene toda la lgica
dentro de "Main" y va creciendo arbitrariamente. Por eso, suele ser preferible
crear nuestras propias funciones que la oculten un poco: funciones como
"Inicializar", "CargarImagen", "ComprobarTeclas", "DibujarImagenes", etc.

Un "bucle de juego clsico" tendra una apariencia similar a esta:

Comprobar eventos (pulsaciones de teclas, clics o movimiento de ratn, uso


de joystick...)
Mover los elementos del juego (personaje, enemigos, fondos mviles)
Comprobar colisiones entre elementos del juego (que pueden suponer
perder vidas o energa, ganar puntos, etc)
Dibujar todos los elementos en pantalla en su estado actual
Hacer una pausa al final de cada "fotograma", para que la velocidad del
juego sea la misma en cualquier ordenador, y, de paso, para ayudar a la
multitarea del sistema operativo.

Podemos descomponer en funciones nuestro juego y, de paso, aadir algn otro


elemento, como un premio que recoger y un enemigo que se mueve:

// sdl04.cs
// Cuarto acercamiento a SDL: bucle de juego
// Introduccin a C#, por Nacho Cabanes

using Tao.Sdl;
using System; // Para IntPtr (puntero: imgenes, etc)

public class Juego


{
bool pantallaCompleta = true;
Hardware h;
Imagen img;
bool terminado = false;
short x, y;

void inicializar()
{
h = new Hardware(800, 600, 24, pantallaCompleta);
img = new Imagen("personaje.bmp", 50, 50);
x = 400;
y = 300;

Revisin 0.99zz Pgina 409


Introduccin a la programacin con C#, por Nacho Cabanes

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

private static void Main()


{
Juego j = new Juego();
j.inicializar();

// Bucle de juego
do
{
j.comprobarTeclas();
j.moverElementos();
Revisin 0.99zz Pgina 410
Introduccin a la programacin con C#, por Nacho Cabanes

j.comprobarColisiones();
j.dibujarElementos();
j.pausaFotograma();
} while (!j.partidaTerminada());
}
}

(Las clases Hardware e Imagen no cambian).

Ejercicios propuestos
(Ap4.5.1) Refina el ejercicio Ap4.4.1, para que tenga un fuente modular, siguiente
un bucle de juego clsico.

Ap4.6. Escribir texto


Si queremos escribir texto usando tipos de letra TrueType (los habituales en
Windows y Linux), los cambios no son grandes:

Debemos incluir el fichero DLL llamado SDL_ttf.DLL en la carpeta del ejecutable de


nuestro programa, as como el fichero TTF correspondiente a cada tipo de letra
que queramos usar.

Tambin tenemos que inicializar SdlTtf despus de la inicializacin bsica de SDL


(al final del mtodo Inicializar de la clase Hardware):

SdlTtf.TTF_Init();

El tipo de letra estar accesible a travs de un puntero genrico "IntPtr", igual que
ocurra con las imgenes, por lo que, igual que hicimos con stas, podemos crear
una clase auxiliar, llamada "Fuente":

// fuente.cs
// Clases auxiliar Fuente para SDL
// Introduccin a C#, por Nacho Cabanes

using Tao.Sdl; // Para acceder a SDL


using System; // Para IntPtr

public class Fuente


{
IntPtr tipoDeLetra;

public Fuente(string nombreFichero, short tamanyo)


{
Cargar(nombreFichero, tamanyo);
}

public void Cargar(string nombreFichero, short tamanyo)


{
Revisin 0.99zz Pgina 411
Introduccin a la programacin con C#, por Nacho Cabanes

tipoDeLetra = SdlTtf.TTF_OpenFont(nombreFichero, tamanyo);


if (tipoDeLetra == IntPtr.Zero)
{
System.Console.WriteLine("Tipo de letra inexistente!");
Environment.Exit(2);
}
}

public IntPtr GetPuntero()


{
return tipoDeLetra;
}
}

Y en la clase Hardware aadiremos un mtodo EscribirTextoOculta, que d por


nosotros lo pasos que son necesarios en SDL: crear una imagen a partir del texto,
incluirla dentro de un rectngulo y volcarlo a otro rectngulo en la pantalla oculta:

public void EscribirTextoOculta(string texto,


short x, short y, byte r, byte g, byte b, Fuente fuente)
{
Sdl.SDL_Color color = new Sdl.SDL_Color(r, g, b);
IntPtr textoComoImagen = SdlTtf.TTF_RenderText_Solid(
fuente.GetPuntero(), texto, color);
if (textoComoImagen == IntPtr.Zero)
Environment.Exit(5);

Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0, 0,


anchoPantalla, altoPantalla);
Sdl.SDL_Rect dest = new Sdl.SDL_Rect(x, y,
anchoPantalla, altoPantalla);

Sdl.SDL_BlitSurface(textoComoImagen, ref origen,


pantallaOculta, ref dest);
}

Y ya lo podemos usar desde nuestro juego. Por una parte, cargaremos el tipo de
letra (con el tamao 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 mtodo "dibujarElementos":

void dibujarElementos()
{
h.BorrarPantallaOculta();
Revisin 0.99zz Pgina 412
Introduccin a la programacin 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) Ampla el ejercicio Ap4.5.1, para que muestre una "puntuacin" en
pantalla (que, por ahora, siempre ser cero).

Ap4.7. Colisiones simples


En cualquier juego habr que comprobar colisiones: recoger premios, perder una
vida si nos toca el enemigo o un disparo, etc. No basta con comparar la X y la Y de
cada elemento, porque pueden solaparse aunque no estn exactamente en la
misma posicin.

Deberemos comprobar si el rectngulo que ocupa una imagen se superpone con


el que ocupa la otra: si el rectngulo 2 (verde) est ms a la derecha que el
rectngulo 1 (azul), entonces su posicin X (posX2) debe estar entre posX1 y
posX1+anchura1. Si est ms a la izquierda, estar entre posX1-anchura2 y posX1.
Lo mismo ocurrir con las coordenadas Y. Entonces, podemos crear un mtodo en
la clase Imagen que compruebe colisiones con otra imagen, as:

public bool ColisionCon(Imagen i)


{
if ((x + ancho > i.x)
&& (x < i.x + i.ancho)
&& (y + alto > i.y)
&& (y < i.y + i.alto))
return true;
else
return false;
}

Revisin 0.99zz Pgina 413


Introduccin a la programacin con C#, por Nacho Cabanes

Ahora podemos comprobar si dos imgenes se solapan. Por ejemplo, podemos


ver si un enemigo toca a nuestro personaje, para que en ese momento acabe la
partida:

if (imgPersonaje.ColisionCon(imgEnemigo))
{
terminado = true;
}

O bien, que al tocar un premio obtengamos puntos y ste se recoloque en una


posicin distinta de la pantalla:

if (imgPersonaje.ColisionCon(imgPremio))
{
puntos += 10;
xPremio = (short)generadorAzar.Next(750);
yPremio = (short)generadorAzar.Next(550);
imgPremio.MoverA(xPremio, yPremio);
}

Para que el enemigo se mueva "por s mismo", podemos indicar, adems de su


posicin, su velocidad, de modo que el mtodo "MoverElementos" vaya alterando
su posicin. Y podemos hacer que "rebote" en los extremos de la pantalla,
cambiando el signo de su velocidad (de positiva a negativa, o viceversa) cuando los
alcance:

xEnemigo += velocXEnemigo;
yEnemigo += velocYEnemigo;
imgEnemigo.MoverA(xEnemigo, yEnemigo);

if ((xEnemigo < 10) || (xEnemigo > 750))


velocXEnemigo = (short) (-velocXEnemigo);

if ((yEnemigo < 10) || (yEnemigo > 550))


velocYEnemigo = (short)(-velocYEnemigo);

El fuente completo podra ser as:

// sdl06.cs
// Sexto acercamiento a SDL: colisiones
// Introduccin a C#, por Nacho Cabanes

using Tao.Sdl;
using System; // Para IntPtr (puntero: imgenes, etc)

public class Juego


{
bool pantallaCompleta = false;
Hardware h;
Imagen imgPersonaje, imgEnemigo, imgPremio;
Revisin 0.99zz Pgina 414
Introduccin a la programacin con C#, por Nacho Cabanes

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

// Si toca el enemigo: acab la partida


if (imgPersonaje.ColisionCon(imgEnemigo))
{
terminado = true;
}
}

Revisin 0.99zz Pgina 415


Introduccin a la programacin con C#, por Nacho Cabanes

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

public static void Main()


{
Juego j = new Juego();
j.inicializar();

// Bucle de juego
do
{
j.comprobarTeclas();
j.moverElementos();
j.comprobarColisiones();
j.dibujarElementos();
j.pausaFotograma();
} while (!j.partidaTerminada());
}
}

Revisin 0.99zz Pgina 416


Introduccin a la programacin con C#, por Nacho Cabanes

Puedes descargar todo el proyecto, incluyendo fuentes, imgenes y ficheros DLL


desde

http://www.nachocabanes.com/fich/descargar.php?nombre=SdlClases04.zip

Ejercicios propuestos
(Ap4.7.1) Ampla 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
obtendrn 10 puntos. Al recoger todos los premios, acabar la partida.
(Ap4.7.2) Ampla el ejercicio Ap4.7.1, para que haya un "margen de la pantalla" y
una serie de obstculos, que "nos maten": si el personaje los toca, acabar la
partida.
(Ap4.7.3) Ampla el ejercicio Ap4.7.2, para que el movimiento sea continuo: el
jugador no se mover slo cuando se pulse una flecha, sino que continuar
movindose hasta que se pulse otra flecha (y entonces cambiar de direccin) o
choque con un obstculo (y entonces acabar la partida).
(Ap4.7.4) Ampla 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 ms 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 obstculos.
(Ap4.7.6) Aade dificultad creciente al ejercicio Ap4.7.5: cada cierto tiempo
aparecer un nuevo obstculo, y en cada nuevo nivel, el movimiento ser un poco
ms rpido (puedes conseguirlo reduciendo la pausa de final del bucle de juego).
(Ap4.7.7) Aade al ejercicio Ap4.7.6 un tabla de mejores puntuaciones, que se
guardar en fichero y se leer al principio de cada nueva partida.

Ap4.8. Imgenes PNG y JPG


Las imgenes BMP ocupan mucho espacio, y no permiten caractersticas
avanzadas, como la transparencia (aunque se podra imitar). Si queremos usar
imgenes en formatos ms modernos y ms optimizados, como JPG o PNG, slo
tenemos que incluir unos cuantos ficheros DLL ms y hacer un pequeo cambio
en el programa.

Los nuevos ficheros que necesitamos son: SDL_image.dll (el principal), libpng13.dll
(para imgenes PNG), zlib1.dll (auxiliar para el anterior) y jpeg.dll (si queremos usar
imgenes JPG).
Revisin 0.99zz Pgina 417
Introduccin a la programacin con C#, por Nacho Cabanes

En el fuente, slo cambiara la orden de cargar cada imagen, que no utilizara


Sdl.SDL_LoadBMP sino SdlImage.IMG_Load (y este cambio slo afectara al
constructor de la clase Imagen):

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 imgenes PNG con transparencia, a travs
de las que se pueda ver dicho fondo.

Ap4.9. Por dnde seguir?


Este texto no pretende ser un curso de programacin de videojuegos, de modo
que muchas cosas quedan en el tintero. Aun as, vamos a mencionar algunas, para
que el lector pueda intentar implementarlas o, al menos, saber por dnde podra
profundizar:

SDL, al igual que la mayora de bibliotecas de juegos, tiene soporte para el


uso del ratn y de joystick/gamepad. Para ver el estado del ratn se usara
Sdl.SDL_GetMouseState y para el joystick (o gamepad, o cualquier otro
dispositivos similar, como un volante) se usara Sdl.SDL_JoystickGetAxis
para ver si se ha inclinado en alguno de los ejes y Sdl.SDL_JoystickGetButton
para comprobar los botones.
Tambin permite reproduccin de sonidos. La biblioteca bsica soporta el
formato WAV, sin comprimir, pero si preferimos formatos ms compactos
como MP3 o MID, podemos aadir a nuestra carpeta el fichero
SDL_mixer.dll, que nos permitir usar SdlMixer.Mix_PlayMusic para
reproducir un sonido una vez, o varias, o de forma ininterrumpida.
Si queremos dibujar lneas, puntos, rectngulos, crculos o elipses, podemos
incluir tambin Sdl_Gfx.dll, con funciones como SdlGfx.rectangleRGBA.
Si queremos que una imagen sea "animada", es decir, formada por varios
fotogramas, podemos usar un array de imgenes. Para simplificar su
manejo, podemos crear una clase ElementoGrfico, de modo que sea una
extensin del concepto de imagen: un elemento grfico podr contener
una secuencia de imgenes, o incluso varias secuencias distintas, segn la
direccin en que se est moviendo el personaje. Entre sus mtodos, podra
existir un "SiguienteFotograma", que pase a mostrar la siguiente imagen de
la animacin.

Revisin 0.99zz Pgina 418


Introduccin a la programacin con C#, por Nacho Cabanes

Los fondos repetitivos que aparecen en la mayora de juegos de


plataformas clsicos se pueden realizar mediante un array de imgenes.
Para saber si es posible moverse a una cierta posicin de la pantalla o no,
comprobaramos antes si supondra colisionar con alguna de esas
imgenes.
Algunos movimientos repetitivos del personaje (caer, saltar) pueden
suponer que se siga moviendo aunque el usuario no pulse ninguna tecla.
Ese caso se tratara dentro del mtodo "MoverElementos" y sera
bsicamente una comprobacin de una o dos condiciones que dan lugar al
siguiente paso: if (cayendo && posibleBajar) y+=2;
A medida que el juego crece, puede ser interesante descomponerlo como
una serie de clases que se interrelacionan: juego, personaje, enemigo,
disparo, pantallaDeFondo, pantallaDeAyuda. As, cada clase tiene unas
responsabilidades ms delimitadas, y el programa resultante es ms fcil
de mantener.

Ejercicios propuestos
(Ap4.9.1) Crea una versin del juego de la serpiente en la que la lgica est
repartida entre varias clases que colaborar: JuegoSerpiente (que contendr el
Main), Presentacion, PantallaAyuda, PantallaCreditos, Nivel, Personaje, Premio.

Revisin 0.99zz Pgina 419


Introduccin a la programacin con C#, por Nacho Cabanes

Apnd ice 5. Contacto con los entornos grficos


Ap5.1. Creacin de formularios, botones y etiquetas
En C# podemos crear con una cierta facilidad programas en entornos grficos, con
mens botones, listas desplegables, etc.

La forma ms cmoda de conseguirlo es usando herramientas que incluyan un


editor visual, como Visual Studio o SharpDevelop.

SharpDevelop necesita un ordenador menos potente que Visual Studio y tiene un


manejo muy similar a ste. Aun as, dado que la versin Express de Visual Studio
es gratis para uso personal y se mueve con una soltura razonable en un ordenador
"moderno" (ms de 1 Gb de memoria RAM, procesador de un ncleo de 2 Ghz o
ms, o bien procesador de doble ncleo), vamos a centrarnos en su manejo.

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 "Aplicacin de Windows Forms",
y como nombre se nos propondr algo como "WindowsFormsApplication1", que
nosotros podramos cambiar por "AplicacionVentanas1" (como siempre, sin
espacios ni acentos, que suelen ser caracteres no vlidos en un identificador -
aunque Visual Studio s permitira los acentos-):
Revisin 0.99zz Pgina 420
Introduccin a la programacin con C#, por Nacho Cabanes

Nos aparecer un formulario ("una ventana") vaco:

Si nuestra pantalla est as de vaca, 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 aadir componentes visuales, como botones,
mens y listas desplegables. Ambos los podemos aadir desde el men "Ver":

Revisin 0.99zz Pgina 421


Introduccin a la programacin con C#, por Nacho Cabanes

De modo que ahora nuestra pantalla debera ser algo as:

Como primer ejemplo, vamos a aadir a nuestro formulario un botn (Button) y


una etiqueta de texto (Label), para hacer un primer programa que cambie el texto
de la etiqueta cuando pulsemos el botn.

Hacemos clic en el componente Button del Cuadro de Herramientas (panel


izquierdo) y luego clic dentro del formulario, para que aparezca un botn en esa

Revisin 0.99zz Pgina 422


Introduccin a la programacin con C#, por Nacho Cabanes

posicin. 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 debera estar quedando as:

Ahora vamos a hacer que nuestro programa responda a un clic del ratn sobre el
botn. Para eso, hacemos doble clic sobre dicho botn, y aparecer un esqueleto
de programa que podemos rellenar:

En ese hueco indicaremos lo que debe ocurrir cuando se pulse el botn


("button1_Click"). En nuestro caso, simplemente ser cambiar el texto del "label",
as:

private void button1_Click(object sender, EventArgs e)


{
label1.Text = "Hola";
}

Antes de ver con ms detalle por qu ocurre todo esto y mejorarlo, vamos a
comprobar que funciona. Pulsamos el botn de "Iniciar depuracin" en la barra de
herramientas:

Revisin 0.99zz Pgina 423


Introduccin a la programacin con C#, por Nacho Cabanes

Y nos aparecer nuestra ventana, lista para probar. Si


pulsamos el botn, cambiar el texto de la etiqueta:

Antes de seguir, deberamos grabar los cambios, porque


algunas versiones de Visual Studio permiten "compilar a
memoria", de modo que nuestro programa quiz todava
no est guardado.

Ahora vamos a crear un segundo proyecto, un poco ms elaborado y que nos


ayude a entender mejor el funcionamiento del entorno.

Ap5.2. Cambios de apariencia. Casillas de texto para


sumar dos nmeros
Hemos visto cmo colocar un botn y una casilla de texto, pero no hemos
cambiado el texto del botn, ni su tamao, ni el tipo de letra. Ahora vamos a hacer
un programa que calcule y muestre la suma de dos nmeros, y cuidaremos un
poco ms la apariencia.

Comenzamos por crear un nuevo proyecto llamado (por ejemplo)


"AplicacionVentanas2", que tambin ser una "Aplicacin
de Windows Forms".

En el formulario, aadiremos dos Label para incluir textos


explicativos, tres TextBox (casillas de introduccin de
texto) para los datos y el resultado, y tambin un botn
para que se calcule dicho resultado. El resultado todava
ser feo y, posiblemente, con los componentes
desalineados:

Vamos a comenzar por alinear los componentes. Si pinchamos y arrastramos uno


de ellos con el ratn, cuando lo movamos es fcil que aparezcan unas rayas para
avisarnos de que en ese momento estn alineados varios componentes:

Revisin 0.99zz Pgina 424


Introduccin a la programacin con C#, por Nacho Cabanes

Una vez que estn colocados, vamos a cambiar sus propiedades. Sabemos
cambiar el texto de un Label desde cdigo, pero tambin podemos hacerlo desde
el diseador visual. Basta con tener la ventana de "Propiedades", que podemos
obtener al pulsar el botn 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"):

La anchura de las casillas de texto y el tamao del botn los


podemos cambiar simplemente pinchando y arrastrando
con el ratn, pero tambin tenemos una propiedad "Size",
con componentes X e Y:
Revisin 0.99zz Pgina 425
Introduccin a la programacin con C#, por Nacho Cabanes

Algunas de las propiedades son:

Name, el nombre con el que se acceder desde el cdigo.


Text, el texto que muestra un elemento.
ForeColor, el color con el que se muestra el componente.
BackColor, el color de fondo.
TextAlign, para indicar la alineacin del texto (y poder centrarlo, por
ejemplo).
Enabled, para poder activar o desactivar un elemento.
Location, la posicin en que se encuentra (que podemos ajustar
inicialmente con el ratn).
Size, el tamao (ancho y alto, que tambin se puede ajustar inicialmente
con el ratn).
Font, el tipo de letra.
ReadOnly, para poder hacer que un TextBox sea slo de lectura y no se
pueda modificar su contenido.

En nuestro caso, vamos a hacer los siguientes cambios:


Las etiquetas de texto, en vez de label1 y label2, se llamarn lbNumero1 y
lbNumero2.
De igual modo, las casilla de texto, en vez de textBox1, textBox2, y textBox3
se llamarn tbNumero1, tbNumero2 y tbResultado.
El botn, en vez de Button1, pasar a llamarse btCalcular.
El formulario se llamar frmSumador.
La casilla del resultado usar el mismo tipo de letra, pero en negrita (Bold).
Adems, ser slo de lectura.

Nuevamente, para hacer que se calcule el resultado al pulsar el botn, hacemos


doble clic sobre l. En este caso, el texto de tbResultado ser la suma de los de los
otros TextBox. Eso s, como las casillas de texto contienen cadenas, habr que
convertir a int32 para poder sumar, y luego volver a convertir a string para
almacenar el resultado:

private void btCalcular_Click(object sender, EventArgs e)


{
tbResultado.Text = Convert.ToString( Convert.ToInt32(tbNumero1.Text)
+ Convert.ToInt32(tbNumero2.Text) );
}

Podemos volver a la vista de diseo en cualquier momento, haciendo clic en la


pestaa que hay en la parte superior:
Revisin 0.99zz Pgina 426
Introduccin a la programacin con C#, por Nacho Cabanes

La apariencia del programa, ya en funcionamiento, sera algo como:

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 estn vacas o contienen texto en
lugar de nmeros.
(Ap5.2.2) Crea una nueva versin del programa, que no solo muestre la suma, sino
tambin la resta, la multiplicacin, la divisin y el resto de la divisin.
(Ap5.2.3) Un programa que muestre una ventana con un recuadro de texto, un
botn y 3 etiquetas. En el recuadro de texto se escribir un nmero (en sistema
decimal). Cada vez que se pulse el botn, se mostrar en las 3 etiquetas de texto el
equivalente de ese nmero en binario, octal y hexadecimal.
(Ap5.2.4) Haz una variante del programa anterior, que no use un botn para
convertir el nmero a binario, octal y hexadecimal. En vez de eso, cada vez que en
el recuadro de texto se pulse una tecla, se mostrar automticamente en las 3
etiquetas de texto el equivalente del nmero 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.

Revisin 0.99zz Pgina 427


Introduccin a la programacin con C#, por Nacho Cabanes

Ap5.3. Usando ventanas predefinidas


En una aplicacin basada en ventanas, tpicamente tendremos que mostrar algn
mensaje de aviso, o pedir una confirmacin al usuario. Para ello podramos crear
un programa basado en mltiples ventanas, pero eso queda ms all de lo que
pretende este texto.

Una forma alternativa y sencilla de conseguirlo es usando "ventanas de mensaje".


stas se pueden crear llamando a "MessageBox.Show", que tiene varias sintaxis
posibles, segn el nmero de parmetros que queramos utilizar. Por ejemplo,
podemos mostrar un cierto texto de aviso en una ventana que tenga un ttulo
dado:

MessageBox.Show("Ese nmero no es vlido", "Aviso");

La segunda variante es indicar adems qu botones queremos mostrar, y qu


iconos de aviso:

MessageBox.Show("Ese nmero no es vlido", "Aviso",


MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

Y la tercera variante permite indicar adems el que ser el botn por defecto:

Revisin 0.99zz Pgina 428


Introduccin a la programacin con C#, por Nacho Cabanes

MessageBox.Show("Seguro que desea continuar?", "Confirma, por favor",


MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button1);

Como se ve en estos ejemplos, tenemos algunos valores predefinidos para indicar


qu botones o iconos queremos mostrar:

Los botones (MessageBoxButtons) pueden ser: OK (Aceptar), OKCancel


(Aceptar y Cancelar), AbortRetryIgnore (Anular, Reintentar y Omitir),
YesNoCancel (S, No y Cancelar), YesNo (S y No), RetryCancel (Reintentar y
Cancelar).
Los iconos (MessageBoxIcon) pueden ser: None (ninguno), Hand (X blanca
en un crculo con fondo rojo), Question (signo de interrogacin en un
crculo, no recomendado actualmente), Exclamation (signo de exclamacin
en un tringulo con fondo amarillo), Asterisk (letra 'i' minscula en un
crculo), Stop (X blanca en un crculo con fondo rojo), Error (X blanca en un
crculo con fondo rojo), Warning (signo de exclamacin en un tringulo con
fondo amarillo), Information (letra 'i' minscula en un crculo).
Los botones por defecto (MessageBoxDefaultButton) pueden ser: Button1
(el primero), Button2 (el segundo), Button3 (el tercero).

Si queremos que el usuario responda tecleando, no tenemos ninguna ventana


predefinida que nos lo permita (s existe un "InputBox" en otros entornos de
programacin, como Visual Basic), as que deberamos crear nosotros esa ventana
de introduccin de datos desde el editor visual o mediante cdigo, elemento por
elemento.

Ejercicios propuestos:
(Ap5.3.1) Mejora el ejercicio Ap5.2.1, para que muestre un mensaje de aviso si las
casillas estn vacas o contienen texto en lugar de nmeros.

Revisin 0.99zz Pgina 429


Introduccin a la programacin con C#, por Nacho Cabanes

Ap5.4. Una aplicacin con dos ventanas


Si queremos una ventana auxiliar, que permita al usuario introducir varios datos,
deberemos crear un segundo formulario, y llamarlo desde la ventana principal.
Vamos a ver los pasos necesarios.

En primer lugar, crearemos un nuevo proyecto, de tipo "Aplicacin de Windows


Forms", que llamaremos (por ejemplo) "DosVentanas" y entraremos a la vista de
diseo para crear la que ser la ventana principal de nuestra aplicacin.

Vamos a crear en ella una casilla de texto (TextBox) en la que no se podr escribir,
sino que slo se mostrarn resultados, y un botn que har que aparezca la
ventana secundaria, en la que s podremos introducir datos.

Comenzamos por crear el "TextBox" que mostrar el


texto, y el "Label" que aclarar qu es ese texto.
Cambiamos el "Text" de la etiqueta para que muestre
"Nombre y apellidos", y cambiamos la propiedad
"ReadOnly" de la casilla de texto para que sea "true"
(verdadero), de modo que no se pueda escribir en esa
casilla. Tambin podemos cambiar el color de la casilla,
para que sea ms evidente que "no es una casilla
normal". Visual Studio 2010 lo hace automticamente,
pero si un entorno ms antiguuo no lo hace por nosotros, podramos pedir que
fuera gris, como el resto de la ventana. Para eso, cambiamos su propiedad
BackColor (color de fondo). Es recomendable no usar colores prefijados, como el
"gris", sino colores de la paleta de Windows, de modo que los elementos cambien
correctamente si el usuario elige otra combinacin de colores para el sistema
operativo. Como el color de fondo de la ventana es "Control" (el color que tengan
los controles de Windows), para la casilla de texto, escogeramos tambin el color
"Control".

La apariencia de nuestra ventana debera ser parecida a sta:

Revisin 0.99zz Pgina 430


Introduccin a la programacin con C#, por Nacho Cabanes

Para crear el segundo formulario (la ventana auxiliar), usamos la opcin "Agregar
Windows Forms ", del men "Proyecto":

y le damos un nombre, por ejemplo "Introduccion.cs":

Nos aparecer una nueva ventana, en su vista de diseo. Aadimos dos casillas de
texto (TextBox), con sus etiquetas aclaratorias (Label), y el botn de Aceptar:

Revisin 0.99zz Pgina 431


Introduccin a la programacin con C#, por Nacho Cabanes

Llega el momento de aadir el cdigo a nuestro programa. Por una parte,


haremos que el botn "Aceptar" cierre la ventana. Lo podemos conseguir con
doble clic, para que nos aparezca la funcin que se lanzar con el suceso Click del
botn (cuando se pulse el ratn sobre l), y aadimos la orden "Close()" en el
cuerpo de esa funcin, as:

void Button1Click(object sender, EventArgs e)


{
Close();
}

Adems, para que desde la ventana principal se puedan leer los datos de sta,
podemos hacer que sus componentes sean pblicos, o, mejor, crear un mtodo
"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 string GetNombreApellido()


{
return tbNombre.Text + " " + tbApellidos.Text;
}

Ya slo falta que desde la ventana principal se muestre la ventana secundaria y se


lean los valores al terminar. Para eso, aadimos un atributo en la ventana
principal, que represente la ventana auxiliar:

public partial class MainForm : Form


{
Introduccion ventanaIntro;
...

Y la inicializamos al final del constructor:

public MainForm()
{
InitializeComponent();
ventanaIntro = new Introduccion();
}

Finalmente, en el suceso Click del botn hacemos que se muestre la ventana


secundaria usando ShowDialog, que espera a que se cierre sta antes de
permitirnos seguir trabajando en la ventana principal, y despus leemos el valor
que se haba tecleado en dicha ventana:

void Button1Click(object sender, EventArgs e)


{
ventanaIntro.ShowDialog();
tbNombreApellido.Text = ventanaIntro.GetNombreApellido();
Revisin 0.99zz Pgina 432
Introduccin a la programacin con C#, por Nacho Cabanes

El resultado sera una secuencia como esta:

Ejercicios propuestos
(Ap5.4.1) Crea un programa que descomponga un nmero 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 irn guardando en un fichero de
texto.
(Ap5.4.3) Crea una versin "con ventanas" de la "base de
datos de ficheros" (ejemplo 04_06a).

Ap5.5. Otros componentes visuales


Por supuesto, hay muchos ms controles visuales que
los que hemos visto, pero en esto texto no se pretende
dominar todos los componentes existentes, sino dar una
alternativa a la entrada y salida convencional a travs de
consola.

Entre los otros muchos componentes existentes, y que


quedan a la curiosidad del lector, cabe mencionar, por
ser frecuentes y razonablemente sencillos de utilizar:

CheckBox, para permitir al usuario escoger entre


varias opciones, y RadioButton, cuando slo se
puede elegir una de dichas opciones.
ListBox, para mostrar una lista de valores, y
ComboBox para permitir introducir un valor o
escoger de una lista desplegable.
PictureBox, para mostrar una imagen.
ProgressBar, para una barra de progreso.
Revisin 0.99zz Pgina 433
Introduccin a la programacin con C#, por Nacho Cabanes

TextBox tiene una propiedad "MultiLine", para indicar si es de mltiples


lneas. En ese caso, su contenido no se obtendra con "Text", sino
recorriendo sus "Lines".
Tambin, dentro de otras categoras, podemos crear paneles para agrupar
elementos, incluir mens y barras de herramientas, mostrar ventanas de
dilogo estndar (como la de abrir un fichero, la de guardarlo, la de navegar
por carpetas, la de elegir un color, la de elegir un tipo de letra), as como
realizar otras muchas tareas de mayor complejidad, como crear
componentes semiautomticos para acceder a bases de datos, mostrar
vistas previas de impresin, crear temporizadores, monitorizar cambios en
el sistema de ficheros, etc.

Ap5.6. Dibujando con Windows Forms


Windows es un entorno grfico, por lo que se podra suponer que deberamos
tener la posibilidad de trabajar en "modo grfico" desde dentro de Windows,
dibujando lneas, crculos y dems figuras bsicas. En efecto, podemos usar las
posibilidades de "System.Drawing" para crear una ventana grfica dentro de
nuestro formulario. Deberemos preparar tambin las "plumas" ("Pen", para los
contornos) y las "brochas" ("Brush", para los rellenos) que queramos usar. Un
ejemplo que dibujara una lnea roja y una elipse azul cuando pulsemos un botn
del formulario podra ser as:

void Button1Click(object sender, EventArgs e)


{
// Creamos la pluma, el relleno y la ventana grfica
System.Drawing.Pen contornoRojo = new System.Drawing.Pen(
System.Drawing.Color.Red);
System.Drawing.SolidBrush rellenoAzul = new System.Drawing.SolidBrush(
System.Drawing.Color.Blue);
System.Drawing.Graphics ventanaGrafica;
ventanaGrafica = this.CreateGraphics();

// Dibujamos
ventanaGrafica.DrawLine(contornoRojo, 200, 100, 300, 400);
ventanaGrafica.FillEllipse(rellenoAzul, new Rectangle(0, 0, 200, 300));

// Liberamos la memoria que habamos reservado


contornoRojo.Dispose();
rellenoAzul.Dispose();
ventanaGrafica.Dispose();
}

Revisin 0.99zz Pgina 434


Introduccin a la programacin con C#, por Nacho Cabanes

Los mtodos para dibujar lneas, rectngulos, elipses, curvas, etc. son parte de la
clase Graphics. Algunos de los mtodos que sta contiene y que pueden ser tiles
para realizar dibujos sencillos son:

DrawArc, para dibujar un arco.


DrawBezier, para una curva spline de Bzier definida por cuatro puntos
(estructuras Point).
DrawClosedCurve, para una curva spline cerrada, a partir de un array de
puntos.
DrawCurve, para una curva.
DrawEllipse, para dibujar una elipse, a partir del rectngulo que la contiene.
DrawIcon, para dibujar una imagen representada por un icono (Icon).
DrawImage, para mostrar una imagen (Image).
DrawLine, para una lnea.
DrawPolygon, para un polgono, a partir de un array de puntos.
DrawRectangle, para un rectngulo.
DrawString, para mostar una cadena de texto.
FillEllipse, para rellenar el interior de una elipse.
FillPolygon, para rellenar el interior de un polgono.
FillRectangle, para rellenar el interior de un rectngulo.

Por otra parte, un ejemplo de cmo mostrar una imagen predefinida podra ser:

void Button2Click(object sender, EventArgs e)


{
Graphics ventanaGrafica = this.CreateGraphics();
Image imagen = new Bitmap("MiImagen.png");
ventanaGrafica.DrawImage(imagen,20,20,100,90);
}

Esta imagen debera 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.

Se puede encontrar ms detalles sobre la clase Graphics en la referencia en lnea


(MSDN), por ejemplo en la pgina

http://msdn.microsoft.com/es-es/library/system.drawing.graphics_methods.aspx

Ejercicios propuestos
(Ap5.6.1) Crea una versin 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
patbulo".

Revisin 0.99zz Pgina 435


Introduccin a la programacin con C#, por Nacho Cabanes

Revisin 0.99zz Pgina 436


Introduccin a la programacin con C#, por Nacho Cabanes

Revisiones de este texto

0.99zz, de 22-ene-15. Nueva revisin, con la mayora de temas ligeramente


ampliados, con ms ejemplos y con ms ejercicios propuestos. Los
apartados sobre SDL y Windows Forms pasan a ser apndices.
0.99, de 30-dic-12. La mayora de temas estn ligeramente ampliados y con
ms ejercicios propuestos. Incluidos apartados sobre persistencia y sobre
acceso a bases de datos con SQLite. Muchos ms detalles sobre SDL y
algunos ms sobre Windows Forms.
0.98, de abr-11. Lanzamiento de excepciones. Resultado de un MessageBox.
Errata en "operaciones de bits" (suma lgica).
0.97, de 01-feb-11. Corregida alguna errata en referencias del tema 2, que
hablaban de ejemplos cuya numeracin haba cambiado. Incluido el
ejemplo 14b, sobre el uso de "switch". Ampliado el apartado 6, para hablar
de Get y Set, de constructores que se basan en otros, y de la palabra "this".
0.96, de 03-oct-10. Intercambiados los temas 2 y 3, para que no haya mucha
carga terica al principio. Ligeramente ampliado algn apartado de estos
temas. Aadidos 4 apartados sobre SDL (10.11.3 a 10.11.6). Completado el
apartado sobre expresiones regulares, al que faltaba un prrafo. La lista de
cambios entre versiones (este apartado) pasa al final del texto.
0.95, de 01-jun-10. Recolocados (en distinto orden) los temas 8, 9, 10, para
que el apartado sobre "otras bibliotecas" pase a ser el ltimo de esos tres.
Aadido el apartado 10.12 sobre servicios de red (cmo descargar una
pgina web y comunicar dos ordenadores). Ampliado el apartado 9.6 sobre
expresiones regulares e incluido un segundo ejemplo de acceso a bases de
datos con SQLite en 10.10. Ampliado el apartado 2.2.3 para ver cmo
convertir un nmero a binario o hexadecimal. Aadidos tres ejercicios
propuestos al apartado 4.1 y otros dos al 4.4.3
0.94, de 07-may-10. Algunas correcciones menores en algunos apartados
(por ejemplo, 6.3). Aadido el apartado 8.13 sobre Tao.SDL y el apndice 4
sobre la instalacin y uso bsico de Visual Studio (en sus versiones 2008
Express y 2010 Express).
0.93, de 10-mar-10. Algunas correcciones menores en algunos apartados
(por ejemplo, 2.1, 2.2, 3.1.2, 3.1.9, 4.1.4). Aadido el apartado 6.13 sobre
SharpDevelop y cmo crear programas a partir de varios fuentes.
0.92, de 22-nov-09. Ampliado el tema 4 con un apartado sobre
comparaciones de cadenas y otro sobre mtodos de ordenacin. Aadidos
ejercicios propuestos en los apartados 4.4.8, 5.5 (2), 5.7 (2), 5.9.1.
Revisin 0.99zz Pgina 437
Introduccin a la programacin con C#, por Nacho Cabanes

0.91, de 19-nov-09, que incluye unos 30 ejercicios propuestos adicionales


(en los apartados 1.2, 1.4, 1.8, 3.1.4, 3.1.5, 3.1.9, 3.2.1, 3.2.2, 3.2.3, 4.1.1,
4.1.2, 4.3.2, 4.5 y 5.10) y algunos apartados con su contenido ampliado
(como el 4.4.6 y el 8.1).
0.90, de 24-may-09, como texto de apoyo para los alumnos de
Programacin en Lenguajes Estructurados en el I.E.S. San Vicente. (Primera
versin completa; ha habido 4 versiones previas, incompletas, de
distribucin muy limitada).

Revisin 0.99zz Pgina 438


Introduccin a la programacin con C#, por Nacho Cabanes

ndice alfabtico

- @
-, 34 @, 109, 383

[
--, 94 [,] (arrays), 123
[] (arrays), 113
[Serializable], 275
!
!, 52 \
!=, 49
\, 109

#
^
#, 104
^, 356

%
{
%, 34
%=, 96 { y }, 17, 48

& |
&, 356 |, 356
& (direccin 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

Revisin 0.99zz Pgina 439


Introduccin a la programacin con C#, por Nacho Cabanes

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, nmeros, 170
C
Aleatorio, acceso, 253 C, 10
Aleatorios, nmeros, 170 C#, 10
algoritmo, 14 Cadena modificable, 138
alto nivel, 9 Cadenas de caracteres, 128
and, 356 Cadenas de texto, 110
Aadir a un array, 118 Cambio de base, 105
Aadir a un fichero, 247 Campo (bases de datos), 290
Apilar, 312 Carcter, 107
Aplicacin 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
Aritmtica 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
Asignacin de valores, 37
Close, 243
Asignacin en un "if", 54
cdigo mquina, 9
asignaciones mltiples, 96
Cdigos de formato, 103
Asin, 172
Cola, 314
Atan, 172
Colisiones entre imgenes, 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 documentacin, 381
base, 230 Comillas (escribir), 109
Base numrica, 105 Comparacin 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
Revisin 0.99zz Pgina 440
Introduccin a la programacin con C#, por Nacho Cabanes

Convertir a hexadecimal, 105 Enumeradores, 322


Cos, 172 Environment, 348
Coseno, 172 Environment.Exit, 178
Coseno hiperblico, 172 ERRORLEVEL, 178
Cosh, 172 Escritura indentada, 49
Create, 267 Espacios de nombres, 354
CreateDirectory, 344 Estructuras, 125
CreateNew, 271 Estructuras alternativas, 47
CreateText, 242 Estructuras anidadas, 127
Current (SeekOrigin), 259 Estructuras dinmicas, 311
Estructuras repetitivas, 63
Euclides, 177
D Excepciones, 88, 250
DataGridView, 310 EXE, 12
DateTime, 337 Exists (ficheros), 249
Day, 337 Exit, 178
debug, 369 Exp, 172
decimal, 99 Exponencial, 172
Decimal (sistema de numeracin), 391 Expresiones regulares, 361
Declarar una variable, 36
Decremento, 94 F
default, 60
Depuracin, 369 factorial, 175
Dequeue, 314 false, 111, 121
Desapilar, 312 falso, 111, 121
Desbordamiento, 36 Fecha y hora, 337
Descomposicin modular, 156 Fibonacci, 176
Desplazamiento a la derecha, 356 Fichero fsico, 253
Desplazamiento a la izquierda, 356 Fichero lgico, 253
destructor, 214 Ficheros, 242
Diagramas de Chapin, 83 Ficheros binarios, 253
Diagramas de flujo, 55 Ficheros de texto, 242
Dibujo con Windows Forms, 434 Ficheros en directorio, 345
Diccionario, 319 FIFO, 314
Dinmica, memoria, 311 File.Exists, 249
Direccin de memoria, 329 FileAccess.ReadWrite, 271
Directorios, 344 FileStream, 258
DirectoryInfo, 344 fin de fichero, 245
Diseo modular de programas, 156 fixed, 335
Distinto de, 49 float, 99
Divisin, 34 for, 67
do ... while, 65 foreach, 139
Doble precisin, 97, 98 ForegroundColor, 340
Documentacin, 377 Formatear nmeros, 102, 103
Documentacin desde comentarios, 381 fuente, 16
Dos dimensiones (array), 122 Funcin de dispersin, 320
Dos ventanas (Windows Forms), 430 Funciones, 156
Dot Net Framework, 13 Funciones matemticas, 171
double, 99 Funciones virtuales, 225
Doxygen, 381
DrawLine, 435
G
E Generics, 326
get, 360
E, 172 Get, 189
ejecutable, 12 GetEnumerator, 322
elevado a, 172 GetFiles, 345
else, 50 GetKey, 319
Encolar, 314 gigabyte, 385
End (SeekOrigin), 259 Global (variable), 163
Enqueue, 314 gmcs, 31
ensamblador, 10 goto, 82
ensambladores, 11 goto case, 60
enteros negativos, 397
Entorno, 347, 348
enum, 358
H
Enumeraciones, 358 Hash, 320
Revisin 0.99zz Pgina 441
Introduccin a la programacin con C#, por Nacho Cabanes

Hexadecimal, 105, 395 M


Hora, 337
Main, 17
mquina virtual, 12
I matemticas, funciones, 171
Identificadores, 40 matriz, 113
if, 47 Mayor que, 49
Igual a, 49 maysculas, 133
Imgenes con SDL, 400 Maysculas y minsculas, 40
Incremento, 94 mcs, 31
IndexOf, 132 megabyte, 385
Inseguro (bloque "unsafe"), 330 Memoria dinmica, 311
Insercin directa, 146 Menor que, 49
Insert, 133, 316 MessageBox, 428
Insertar en un array, 118 mtodos, 219
int, 36, 37, 93 Mientras (condicin repetitiva), 63
Internet, 349 Modificar parmetros, 167
intrprete, 12 Mdulo (resto de divisin), 34
Interrumpir un programa, 178 Mono, 16, 19
Introduccin de datos, 43 MonoDevelop, 204
IOException, 271 Month, 337
iterativa, 176 Mostrar el valor de una variable, 38
MoveNext, 323
Multiplicacin, 34
J
JavaDoc, 383 N
JPG, 417
Juegos, 400 n, 108
namespace, 354
Negacin, 34
K Negativos (nmeros, representacin), 397
Key, 341 Net, 13
KeyAvailable, 340 NetworkStream, 350
KeyChar, 341 new (objetos), 187
kilobyte, 385 new (redefinir mtodos), 206
Next (nmeros al azar), 170
nibble, 392
L No, 52
not, 356
Label, 424
Notepad++, 33
LastIndexOf, 132
Now, 337
Lectura y escritura en un mismo fichero, 271
null (fin de fichero), 246
Length (cadenas), 130
Nmeros aleatorios, 170
Length (fichero), 260
nmeros enteros, 34
lenguaje C, 10
Nmeros reales, 97
lenguaje mquina, 9
LIFO, 312
Lnea de comandos, 177 O
Lneas, dibujar, 434
List, 326 O, 52
lista, 316 Objetos, 180
octal, 393
Octal, 105
Ll Ocultacin de datos, 189
OpenOrCreate, 271
llaves, 17
OpenText, 244
OpenWrite, 267
L Operaciones abreviadas, 96
Operaciones aritmticas, 34
Local (variable), 163 Operaciones con bits, 356
Log, 172 operador coma, 364
Log10, 172 Operador condicional, 58
Logaritmo, 172 Operadores, 34
long, 93 Operadores lgicos, 52
Longitud de una cadena, 130 Operadores relacionales, 49
Operator (sobrecarga de operadores), 237
or, 356
Ordenaciones simples, 145
Revisin 0.99zz Pgina 442
Introduccin a la programacin con C#, por Nacho Cabanes

Overflow, 36 Referencia (paso de parmetros), 168


override, 225 RegEx, 362
Registro, 253
Registros, 125
P Remove, 133
Palabra reservada, 40 Repetirmientras, 65
Parmetros de Main, 177 Replace, 133
Parmetros de una funcin, 158 Reserva de espacio, 333
parmetros por referencia, 168 Resta, 34
parmetros por valor, 167 Resto de la divisin, 34
Pascal, 9 Retorno de Main, 177
Paso a paso (depuracin), 370 return, 161
Pausa (Sleep), 338 Round, 172
Peek, 313
Persistencia de objetos, 274 S
Pi, 172
Pila, 312 Sangrado, 49
PNG, 417 sbyte, 93
Polimorfismo, 215 SDL, 400
POO, 180 Secuencial, acceso, 253
pop, 312 Secuencias de escape, 108
Posicin del cursor, 340 Seek, 259
Posicin en el fichero, 259 SeekOrigin, 259
postdecremento, 95 Seleccin directa, 145
postincremento, 95 Seno, 172
Potencia, 172 Sentencias compuestas, 48
Pow, 172 Serializable, 275
Precedencia de los operadores, 35 set, 360
predecremento, 95 Set, 189
preincremento, 95 SetByIndex, 320
Prioridad de los operadores, 35 SetCursorPosition, 340
private, 209 SharpDevelop, 420
Process.Start, 347 short, 93
Producto lgico, 356 Si no, 50
programa, 9 Signo y magnitud, 397
Programacin orientada a objetos, 180 Simple precisin, 98
Propiedades, 360 Sin, 172
protected, 208 Sistema (informacin), 348
Prueba de programas, 372 Sistema (llamadas), 347
Pseudocdigo, 14 Sistema binario, 391
public, 18 Sistema de numeracin, 105
public (struct), 125 Sistema decimal, 391
Punteros, 311, 329 Sleep, 338
Punto Net, 13 SOAP, 283
Puntos de ruptura, 372 Sobrecarga, 215
push, 312 Sobrecarga de operadores, 237, 239
Python, 10 Sort, 316
SortedList, 319
Split, 135
Q SQL, 289
Queue, 314 SQLite, 292
Sqrt, 172
Stack, 312
R stackalloc, 333
Raz cuadrada, 172 static, 219
Random, 170 StreamReader, 244
Rango de valores (enteros), 93 StreamWriter, 242
Read (FileStream), 258 string, 62, 110
Readkey, 340 String.Compare, 137
ReadLine, 43, 44, 45, 46 StringBuilder, 138
ReadLine (fichero), 244 struct, 125
real (tipo de datos), 97 struct anidados, 127
recolector de basura, 335 Subcadena, 131
Recursividad, 174 Subcadenas, 135
Red local, 349 Substring, 131
Redondear un nmero, 172 Suma, 34
ref, 168 Suma exclusiva, 356
Revisin 0.99zz Pgina 443
Introduccin a la programacin con C#, por Nacho Cabanes

Suma lgica, 356 ulong, 93


switch, 60 UML, 185
System, 17 Unicode, 390
System.Drawing, 434 Unidades de disco, 348
Unidades de medida, 385
unsafe, 330
T ushort, 93
tabla, 113 using, 44
Tabla (bases de datos), 289
Tablas bidimensionales, 122 V
Tablas hash, 320
Tan, 172 Valor absoluto, 172
Tangente, 172 Valor devuelto por una funcin, 160
Tanh, 172 Variables, 36
Tao.SDL, 400 Variables globales, 163
TcpCliente, 350 Variables locales, 163
Teclado, 340 vector, 113
Teclado con SDL, 403 Ver resultado de un programa, 33
Teclas en consola, 341 verdadero, 111
Temporizacin, 337 virtual, 225
TextBox, 424 Visibilidad (POO), 191, 208
Texto con SDL, 411 Visual Studio, 420
this, 232 VisualStudio, 200
Thread, 338 void, 157
Thread.Sleep, 338
Tipo de datos carcter, 107
Tipos de datos enteros, 92
W
Title, 340 WaitForExit, 347
ToByte, 93 while, 63, 65
ToDecimal, 100 Windows Forms, 420
ToInt16, 93 Write (BinaryWriter), 269
ToInt32, 43, 93 Write (FileStream), 267
ToInt64, 93 WriteByte, 267
ToLower, 133 WriteLine, 17
ToSbyte, 93 WriteLine (ficheros), 243
ToSingle, 100
ToString, 103
ToUInt16, 93 X
ToUpper, 133 XML, 283
true, 111 xor, 356
Truncate, 271
try, 88
Y
U Y, 52
Year, 337
uint, 93

Revisin 0.99zz Pgina 444

También podría gustarte