Visual C Sharp
Visual C Sharp
Contenido
0. Conceptos bsicos sobre programacin _______________________________________________ 6
0.1. Lenguajes de alto nivel y de bajo nivel. _____________________________________________________ 6 0.2. Ensambladores, compiladores e intrpretes _________________________________________________ 8 0.3. Pseudocdigo __________________________________________________________________________ 9
1.6. Identificadores ________________________________________________________________________ 22 1.7. Comentarios__________________________________________________________________________ 23 1.8. Datos por el usuario: ReadLine __________________________________________________________ 24
2. Estructuras de control_____________________________________________________________ 26
2.1. Estructuras alternativas ________________________________________________________________ 2.1.1. if________________________________________________________________________________ 2.1.2. if y sentencias compuestas ____________________________________________________________ 2.1.3. Operadores relacionales: <, <=, >, >=, ==, !=_____________________________________________ 2.1.4. if-else ____________________________________________________________________________ 2.1.5. Operadores lgicos: &&, ||, ! __________________________________________________________ 2.1.6. El peligro de la asignacin en un "if"____________________________________________________ 2.1.7. Introduccin a los diagramas de flujo ___________________________________________________ 2.1.8. Operador condicional: ?______________________________________________________________ 2.1.10. switch___________________________________________________________________________ 2.2. Estructuras repetitivas _________________________________________________________________ 2.2.1. while ____________________________________________________________________________ 2.2.2. do ... while ________________________________________________________________________ 2.2.3. for ______________________________________________________________________________ 26 26 27 28 29 31 31 32 34 35 39 39 40 42
2.3. Sentencia break: termina el bucle ________________________________________________________ 46 2.4. Sentencia continue: fuerza la siguiente iteracin ____________________________________________ 47 2.5. Sentencia goto ________________________________________________________________________ 49 2.6. Ms sobre diagramas de flujo. Diagramas de Chapin.________________________________________ 50 2.7. El caso de "foreach" ___________________________________________________________________ 52 2.8. Recomendacin de uso para los distintos tipos de bucle ______________________________________ 52
3.3. Tipo de datos carcter__________________________________________________________________ 60 3.3.1. Secuencias de escape: \n y otras. _______________________________________________________ 61 3.4. Toma de contacto con las cadenas de texto _________________________________________________ 63 3.5. Los valores "booleanos" ________________________________________________________________ 64
4.2. Tablas bidimensionales _________________________________________________________________ 70 4.3. Estructuras o registros _________________________________________________________________ 4.3.1. Definicin y acceso a los datos ________________________________________________________ 4.3.2. Arrays de estructuras ________________________________________________________________ 4.3.3. Estructuras anidadas ________________________________________________________________ 4.4. Cadenas de caracteres__________________________________________________________________ 4.4.1. Definicin. Lectura desde teclado ______________________________________________________ 4.4.2. Cmo acceder a las letras que forman una cadena__________________________________________ 4.4.3. Longitud de la cadena. _______________________________________________________________ 4.4.4. Extraer una subcadena _______________________________________________________________ 4.4.5. Buscar en una cadena________________________________________________________________ 4.4.6. Otras manipulaciones de cadenas ______________________________________________________ 4.4.7. Comparacin de cadenas _____________________________________________________________ 4.4.8. Una cadena modificable: StringBuilder __________________________________________________ 4.4.9. Recorriendo con "foreach"____________________________________________________________ 73 73 74 75 76 76 77 77 78 78 78 81 82 83
5.10. Recursividad _______________________________________________________________________ 106 5.11. Parmetros y valor de retorno de "Main" _______________________________________________ 108
6.8. Orden de llamada de los constructores ___________________________________________________ 126 6.9. Arrays de objetos_____________________________________________________________________ 128 6.10. Funciones virtuales. La palabra "override" ______________________________________________ 132 6.11. Llamando a un mtodo de la clase "padre" ______________________________________________ 136 6.12. La palabra "this": el objeto actual _____________________________________________________ 138 6.13. Sobrecarga de operadores ____________________________________________________________ 139 6.14. Proyectos a partir de varios fuentes_____________________________________________________ 139
9.6. Introduccin a las expresiones regulares. _________________________________________________ 196 9.7. El operador coma ____________________________________________________________________ 199 9.8. Lo que no vamos a ver... _______________________________________________________________ 200
Apndice 2. El cdigo ASCII ________________________________________________________ 250 Apndice 3. Sistemas de numeracin. _________________________________________________ 251
Ap3.1. Sistema binario ____________________________________________________________________ 251 Ap3.2. Sistema octal ______________________________________________________________________ 252 Ap3.3. Sistema hexadecimal _______________________________________________________________ 254 Ap3.4. Representacin interna de los enteros negativos _________________________________________ 255
Otros lenguajes, como Pascal, nos obligan a ser algo ms estrictos, pero a cambio hacen ms fcil descubrir errores (ya veremos por qu):
program Saludo; begin write('Hola'); end.
En C# hay que dar todava ms pasos para conseguir lo mismo: public class Ejemplo01 { public static void Main() { System.Console.WriteLine("Hola"); } } 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 el sistema operativo MsDos y con un procesador de la familia Intel x86 sera algo como
dosseg .model small .stack 100h .data hello_message db 'Hola',0dh,0ah,'$' .code main proc mov mov mov mov int mov int endp main
main end
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 errores).
Revisin 0.97 Pgina 7
0.3. Pseudocdigo
A pesar de que los lenguajes de alto nivel se acercan al lenguaje natural, que nosotros empleamos, es habitual no usar ningn lenguaje de programacin concreto cuando queremos plantear los pasos necesarios para resolver un problema, sino emplear un lenguaje de programacin ficticio, no tan estricto, muchas veces escrito 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 hay alguna condicin ms, por ejemplo, debe ser un nmero finito de pasos). Por tanto, un programa de ordenador es un algoritmo expresado en 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
Ejercicios propuestos 1. Localizar en Internet el intrprete de Basic llamado Bywater Basic, en su versin para el sistema operativo que se est utilizando y probar el primer programa de ejemplo que se ha visto en el apartado 0.1. 2. Localizar en Internet el compilador de Pascal llamado Free Pascal, en su versin para el sistema operativo que se est utilizando, instalarlo y probar el segundo programa de ejemplo que se ha visto en el apartado 0.1. 3. Localizar un compilador de C para el sistema operativo que se est utilizando (si es Linux o alguna otra versin de Unix, es fcil que se encuentre ya instalado) y probar el tercer programa de ejemplo que se ha visto en el apartado 0.1.
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"); porque WriteLine es una orden de manejo de la "consola" (la pantalla "negra" en modo texto del sistema operativo). System.Console.WriteLine("Hola"); porque las rdenes relacionadas con el manejo de consola (Console) pertenecen a la 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. 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". public class Ejemplo01 - de momento pensaremos que "Ejemplo01" es el nombre de nuestro programa. Una lnea como esa deber existir tambin siempre en nuestros programas, 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: Crea un programa en C# que te salude por tu nombre (ej: "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 dnde termina una orden y donde empieza la siguiente son los puntos y coma. Por ese motivo, el programa anterior se podra haber escrito tambin as (aunque no es aconsejable, porque puede resultar menos legible): public class Ejemplo01 { public static void Main() { System.Console.WriteLine("Hola"); }
De hecho, hay dos formas especialmente frecuentes de colocar la llave de comienzo, y yo usar ambas indistintamente. Una es como hemos hecho en el primer ejemplo: situar la llave de apertura en una 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:
Revisin 0.97 Pgina 11
public class Ejemplo01 { public static void Main(){ System.Console.WriteLine("Hola"); } } (esta es la forma que yo emplear preferentemente en este texto cuando estemos trabajando con fuentes de mayor tamao, para que ocupe un poco menos de espacio). 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.
En la parte superior derecha aparece el enlace para descargar ("download now"), 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 2.6.7 en el momento de escribir este texto). Se trata de un fichero de algo ms de 70 Mb. Cuando termina la descarga, hacemos doble clic en el fichero recibido y comienza la instalacin, en la que primero se nos muestra el mensaje de bienvenida:
Revisin 0.97 Pgina 12
Despus se nos muestra una ventana de informacin, en la que se nos avisa de que se va a instalar Mono 1.9.1, junto con las libreras Gtk# para creacin de interfaces de usuario y XSP (eXtensible Server Pages, un servidor web).
A continuacin se nos pregunta en qu carpeta queremos instalar. Como es habitual, se nos propone que sea dentro de "Archivos de programa":
Revisin 0.97 Pgina 13
Yo no soy partidario de instalar todo en "Archivos de Programa". Mis herramientas de programacin suelen estar en otra unidad de disco (D:), as que prefiero cambiar esa opcin por defecto:
Nuevamente, soy partidario de no 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 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:
El siguiente paso es indicar en qu carpeta del men de Inicio queremos que quede accesible:
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:
Si todo es correcto, 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 1.9.1 for Windows", y dentro de ella un acceso a "Mono-1.9.1 Command Prompt":
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:
Revisin 0.97 Pgina 16
Primero debemos teclear nuestro 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. Respondemos que s y podemos empezar a teclear el ejemplo que habamos visto anteriormente:
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. 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
Si en nuestro ordenador est instalado el "Dot Net Framework" (algo que debera ser cierto en las ltimas versiones de Windows), no debera hacer falta decir que queremos que sea Mono quien lance nuestro programa, y podremos ejecutarlo directamente con su nombre:
ejemplo01
Nota: 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. Y 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 el apartado 6.13 hablaremos de SharpDevelop. Si quieres saber cosas sobre el entorno "oficial", llamado Visual Studio, los tienes en el Apndice 4.
Ejercicio propuesto: crea un programa que diga el resultado de sumar 118 y 56.
Ejercicios propuestos: Hacer Hacer Hacer Hacer un un un un programa programa programa programa que que que que calcule calcule calcule calcule el la el el producto de los nmeros 12 y 13. diferencia (resta) entre 321 y 213. resultado de dividir 301 entre 3. resto de la divisin de 301 entre 3.
Despus ya podemos hacer operaciones con las variables, igual que las hacamos con los nmeros: suma = primerNumero + segundoNumero;
(Nos saltamos todava los detalles de qu quieren decir "public", "class", "static" y "void"). Main() indica donde comienza el cuerpo del programa, que se delimita entre llaves { y } int primerNumero; reserva espacio para guardar un 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).
Ejercicio propuesto: Hacer un programa que calcule el producto de los nmeros 121 y 132, usando variables.
1.6. Identificadores
Estos nombres de variable (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 entre medias, y hay que recordar que las vocales acentuadas y la ee son problemticas, porque no son letras "estndar" en todos los idiomas. 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) 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). De momento, intentaremos usar nombres de variables que a nosotros nos resulten claros, y que no parezca que puedan ser alguna orden de C#. Hay que recordar que en C# las maysculas y minsculas se consideran diferentes, de modo que si intentamos hacer PrimerNumero = 0; primernumero = 0;
o cualquier variacin similar, el compilador protestar y nos dir que no conoce esa variable, porque la habamos declarado como
int primerNumero;
1.7. Comentarios
Podemos escribir comentarios, que el compilador ignora, pero que pueden servir para aclararnos cosas a nosotros. Se escriben entre /* y */:
int suma; /* Porque guardar el valor 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: /* ---- Ejemplo en C#: sumar dos nmeros prefijados ---- */ public class Ejemplo02b { 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); } }
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 al estilo de C++": // Este es un comentario "al estilo C++"
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); } }
Ejercicios propuestos: 1. Multiplicar dos nmeros tecleados por usuario. 2. El usuario teclear dos nmeros (x e y), y el programa deber calcular cual es el resultado de su divisin y el resto de esa divisin. 3. 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. 4. Sumar tres nmeros tecleados por usuario. 5. Pedir al usuario un nmero y mostrar su tabla de multiplicar. Por ejemplo, si el nmero es el 3, debera escribirse algo como 3x 3x 3x 3x 0=0 1=3 2=6 10 = 30
2. Estructuras de control
2.1. Estructuras alternativas
2.1.1. if
Vamos a ver cmo podemos comprobar si se cumplen condiciones. La primera construccin que usaremos ser "si ... entonces ...". El formato es
if (condicin) sentencia;
Vamos a verlo con un ejemplo: /*---------------------------*/ /* Ejemplo en C# n 5: */ /* ejemplo05.cs */ /* */ /* Condiciones con if */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo05 { 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. 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.");
Revisin 0.97 Pgina 26
Ejercicios propuestos: Crear 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) ). Crear un programa que pida al usuario dos nmeros enteros y diga cual es el mayor de ellos. Crear 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).
En este caso, si el nmero es positivo, se hacen dos cosas: escribir un texto y luego... escribir otro! (Claramente, en este ejemplo, esos dos "WriteLine" podran ser uno solo, en el que los
dos textos estuvieran separados por un "\n"; 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 algo ms a la derecha que la orden "if". Este "sangrado" del texto se suele llamar "escritura indentada". Un tamao habitual para el sangrado es de 4 espacios, aunque en este texto muchas veces usaremos slo dos espacios, para no llegar al margen derecho del papel con demasiada facilidad.
As, un ejemplo, que diga si un nmero NO ES cero sera: /*---------------------------*/ /* Ejemplo en C# n 7: */ /* ejemplo07.cs */ /* */ /* Condiciones con if (3) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo07 { 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: Crear 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. Crear un programa que pida al usuario dos nmeros reales. 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 en C# n 8: */ /* ejemplo08.cs */ /* */ /* Condiciones con if (4) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo08 { 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 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 en C# n 9: */ /* ejemplo09.cs */ /* */ /* Condiciones con if (5) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System;
Revisin 0.97 Pgina 29
public class Ejemplo09 { 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 8) 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 9), 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 en C# n 10: */ /* ejemplo10.cs */ /* */ /* Condiciones con if (6) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo10 { 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."); } }
Revisin 0.97 Pgina 30
Ejercicio propuesto: Mejorar la solucin a los dos ejercicios del apartado anterior, usando "else".
...
Ejercicios propuestos: Crear un programa que pida una letra al usuario y diga si se trata de una vocal. Crear un programa que pida al usuario dos 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. Crear un programa que pida al usuario tres nmeros reales y muestre cul es el mayor de los tres. Crear un programa que pida al usuario dos nmeros enteros cortos y diga si son iguales o, en caso contrario, cul es el mayor de ellos. Crear un programa que pida al usuario su nombre, y le diga "Hola" si se llama "Juan", o bien le diga "No te conozco" si teclea otro nombre.
/* */ /* Condiciones con if (7) */ /* comparacion incorrecta */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo11 { public static void Main() { int numero; Console.WriteLine("Introduce un nmero"); numero = Convert.ToInt32(Console.ReadLine()); if (numero = 0) Console.WriteLine("El nmero es cero."); else if (numero < 0) Console.WriteLine("El nmero es negativo."); else Console.WriteLine("El nmero es positivo."); } }
El inicio o el final del programa se indica dentro de un crculo. 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
Revisin 0.97 Pgina 32
inferior horizontales, pero no tenga verticales los otros dos. Las decisiones se indican dentro de un rombo. Vamos a aplicarlo al ejemplo de un programa que pida un nmero al usuario y diga si es positivo, negativo o cero:
El paso de aqu al correspondiente programa en lenguaje C# (el que vimos en el ejemplo 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". Ejercicios propuestos: Crear 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.
Crear 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. Crear el diagrama de flujo para el programa que pide tres nmeros al usuario y dice cul es el mayor de los tres.
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;
Aplicado a un programa sencillo, podra ser /*---------------------------*/ /* Ejemplo en C# n 12: */ /* ejemplo12.cs */ /* */ /* El operador condicional */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo12 { public static void Main() { 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;
(La orden Console.Write, empleada en el ejemplo anterior, escribe un texto sin avanzar a la lnea siguiente, de modo que el prximo texto que escribamos o introduzcamos- quedar a continuacin de ste).
Un segundo ejemplo, que sume o reste dos nmeros segn la opcin que se escoja, sera: /*---------------------------*/ /* Ejemplo en C# n 13: */ /* ejemplo13.cs */ /* */ /* Operador condicional - 2 */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo13 { 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}.\n", resultado); } } Ejercicios propuestos: Crear 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. Usar el operador condicional para calcular el menor de dos nmeros.
2.1.10. switch
Si queremos ver varios posibles valores, sera muy pesado tener que hacerlo con muchos "if" seguidos o encadenados. La alternativa es la orden "switch", cuya sintaxis es switch (expresin) { case valor1: sentencia1; break; case valor2: sentencia2;
Revisin 0.97 Pgina 35
sentencia2b; break; ... case valorN: sentenciaN; break; default: otraSentencia; break; } Es decir, se escribe tras "switch" 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 se trata de ese 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 con 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 en C# n 14: */ /* ejemplo14.cs */ /* */ /* La orden "switch" (1) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo14 { public static void Main() { char letra; Console.WriteLine("Introduce una letra"); letra = Convert.ToChar( Console.ReadLine() ); switch (letra) { case ' ': Console.WriteLine("Espacio."); break; case '1': goto case '0'; case '2': goto case '0'; case '3': goto case '0'; case '4': goto case '0'; case '5': goto case '0'; case '6': goto case '0'; case '7': goto case '0'; case '8': goto case '0';
Revisin 0.97 Pgina 36
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, con la nica excepcin de que un caso no haga absolutamente nada que no sea dejar pasar el control al siguiente caso, y en ese caso se puede dejar totalmente vaco: /*---------------------------*/ /* Ejemplo en C# n 14b: */ /* ejemplo14b.cs */ /* */ /* La orden "switch" (1b) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo14b { 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': case '6': case '7': case '8': case '9': case '0': Console.WriteLine("Dgito."); break; default: Console.WriteLine("Ni espacio ni dgito."); break; } } }
Revisin 0.97 Pgina 37
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 en C# n 15: */ /* ejemplo15.cs */ /* */ /* La orden "switch" (2) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo15 { public static void Main() { string nombre; Console.WriteLine("Introduce tu nombre"); nombre = Console.ReadLine(); switch (nombre) { case "Juan": Console.WriteLine("Bienvenido, Juan."); break; case "Pedro": Console.WriteLine("Que tal estas, Pedro."); break; default: Console.WriteLine("Procede con cautela, desconocido."); break; } } }
Ejercicios propuestos: Crear un programa que lea una letra tecleada por el usuario y diga si se trata de una vocal, una cifra numrica o una consonante (pista: habr que usar un dato de tipo "char"). Crear un programa que lea una letra tecleada por el usuario y diga si se trata de un signo de puntuacin, una cifra numrica o algn otro carcter. Repetir los dos programas anteriores, empleando "if" en lugar de "switch".
2.2.1. 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. 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 { y }. Un ejemplo que nos diga si cada nmero que tecleemos es positivo o negativo, y que pare cuando tecleemos el nmero 0, podra ser: /*---------------------------*/ /* Ejemplo en C# n 16: */ /* ejemplo16.cs */ /* */ /* La orden "while" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo16 { 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()); } }
Revisin 0.97 Pgina 39
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: Crear un programa que pida al usuario su contrasea. Deber terminar cuando introduzca como contrasea la palabra "clave", pero volvrsela a pedir tantas veces como sea necesario. Crea un programa que escriba en pantalla los nmeros del 1 al 10, usando "while". Crea un programa que escriba en pantalla los nmeros pares del 26 al 10 (descendiendo), usando "while". Crear un programa calcule cuantas cifras tiene un nmero entero positivo (pista: se puede hacer dividiendo varias veces entre 10).
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 en C# n 17: */ /* ejemplo17.cs */ /* */ /* La orden "do..while" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo17 { public static void Main() { int valida = 711; int clave; do {
Revisin 0.97 Pgina 40
Console.Write("Introduzca su clave numrica: "); clave = Convert.ToInt32(Console.ReadLine()); if (clave != valida) Console.WriteLine("No vlida!\n"); } while (clave != valida); Console.WriteLine("Aceptada.\n"); } } 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. Si preferimos que la clave sea un texto en vez de un nmero, los cambios al programa son mnimos, basta con usar "string": /*---------------------------*/ /* Ejemplo en C# n 18: */ /* ejemplo18.cs */ /* */ /* La orden "do..while" (2) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo18 { public static void Main() { string valida = "secreto"; string clave; do { Console.Write("Introduzca su clave: "); clave = Console.ReadLine(); if (clave != valida) Console.WriteLine("No vlida!\n"); } while (clave != valida); Console.WriteLine("Aceptada.\n"); }
Revisin 0.97 Pgina 41
Ejercicios propuestos: Crear un programa que pida nmeros positivos al usuario, y vaya calculando la suma de todos ellos (terminar cuando se teclea un nmero negativo o cero). Crea un programa que escriba en pantalla los nmeros del 1 al 10, usando "do..while". Crea un programa que escriba en pantalla los nmeros pares del 26 al 10 (descendiendo), usando "do..while". Crea un programa que pida al usuario su nombre 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": 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. 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 en C# n 19: */ /* ejemplo19.cs */ /* */ /* Uso bsico de "for" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo19 { public static void Main() { int contador;
Revisin 0.97 Pgina 42
Ejercicios propuestos: Crear 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--). Crear un programa que muestre los primeros ocho nmeros pares (pista: en cada pasada habr que aumentar de 2 en 2, o bien mostrar el doble del valor que hace de contador).
En un "for", realmente, 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 sera la siguiente orden:
for ( ; ; )
Los bucles "for" se pueden anidar (incluir uno dentro de otro), de modo que podramos escribir las tablas de multiplicar del 1 al 5 con: /*---------------------------*/ /* Ejemplo en C# n 20: */ /* ejemplo20.cs */ /* */ /* "for" anidados */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo20 { public static void Main() {
Revisin 0.97 Pgina 43
int tabla, numero; for (tabla=1; tabla<=5; tabla++) for (numero=1; numero<=10; numero++) Console.WriteLine("{0} por {1} es {2}", tabla, numero, tabla*numero); } }
En estos ejemplos que hemos visto, 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 en C# n 21: */ /* ejemplo21.cs */ /* */ /* "for" anidados (2) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo21 { public static void Main() { int tabla, numero; for (tabla=1; tabla<=5; tabla++) { for (numero=1; numero<=10; numero++) Console.WriteLine("{0} por {1} es {2}", tabla, numero, tabla*numero); Console.WriteLine(); } } }
Para "contar" no necesariamente hay que usar nmeros. Por ejemplo, podemos contar con letras as:
Revisin 0.97 Pgina 44
/*---------------------------*/ /* Ejemplo en C# n 22: */ /* ejemplo22.cs */ /* */ /* "for" que usa "char" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo22 { public static void Main() { char letra; for (letra='a'; letra<='z'; letra++) Console.Write("{0} ", letra); } }
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: /*---------------------------*/ /* Ejemplo en C# n 23: */ /* ejemplo23.cs */ /* */ /* "for" que descuenta */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo23 { public static void Main() { char letra; for (letra='z'; letra>='a'; letra--) Console.Write("{0} ", letra); } }
Revisin 0.97 Pgina 45
Ejercicios propuestos: Crear un programa que muestre las letras de la Z (mayscula) a la A (mayscula, descendiendo). Crear un programa que escriba en pantalla la tabla de multiplicar del 5. Crear 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).
(en cuanto se llega al valor 5, se interrumpe el "for", por lo que no se alcanza el valor 10).
Respuesta: los nmeros del 1 al 3 (se empieza en 1 y se repite mientras sea menor que 4).
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, "i" ya tiene el valor 5.
Respuesta: escribe "1" continuamente, porque no aumentamos el valor de "i", luego nunca se llegar a cumplir la condicin de salida.
Respuesta: escribe nmeros crecientes continuamente, comenzando en uno y aumentando una unidad en cada pasada, pero sin terminar.
Respuesta: escribe los nmeros del 0 al 4, porque la condicin del "continue" nunca se llega a dar.
Respuesta: escribe 5, porque no hay llaves tras el "for", luego slo se repite la orden "if".
como en el siguiente ejemplo: /*---------------------------*/ /* Ejemplo en C# n 26: */ /* ejemplo26.cs */ /* */ /* "for" y "goto" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo26 { public static void Main() { int i, j; for (i=0; i<=5; i++)
Revisin 0.97 Pgina 49
for (j=0; j<=20; j+=2) { if ((i==1) && (j>=7)) goto salida; Console.WriteLine("i vale {0} y j vale {1}.", i, j); } salida: Console.Write("Fin del programa"); } }
Aun as, existen otras notaciones ms modernas y que pueden resultar ms cmodas. Slo comentaremos una: los diagramas de Chapin. En ellos se representa cada orden dentro de una caja: Pedir primer nmero Pedir segundo nmero Mostrar primer num+segundo num Las condiciones se indican dividiendo las cajas en dos:
Revisin 0.97 Pgina 51
Pedir nmero n1 Pedir nmero n2 n1>n2? si Decir "n1 es mayor" Decir "n2 es mayor" no
Y las condiciones repetitivas se indican dejando una barra a la izquierda, que marca qu es lo 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
Ejercicios propuestos: 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. Crear un programa que descomponga un nmero (que teclee el usuario) como producto de su factores primos. Por ejemplo, 60 = 2 2 3 5
Rango de valores -128 a 127 0 a 255 -32768 a 32767 0 a 65535 -2147483648 a 2147483647 0 a 4294967295 -9223372036854775808 a 9223372036854775807 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".
"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). Ejercicios propuestos: Preguntar al usuario su edad, que se guardar en un "byte". A continuacin, se deber le deber decir que no aparenta tantos aos (por ejemplo, "No aparentas 20 aos"). Pedir al usuario dos nmeros de dos cifras ("byte"), calcular su multiplicacin, que se deber guardar en un "ushort", y mostrar el resultado en pantalla. Pedir al usuario dos nmeros enteros largos ("long") y mostrar cuanto es su suma, su resta y su producto.
Pero esto tiene ms misterio todava del que puede parecer en un primer vistazo: podemos distinguir entre "preincremento" y "postincremento". En C# es posible hacer asignaciones como b = a++; As, si "a" 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: Crear 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.
Revisin 0.97 Pgina 55
Cul sera el resultado de las siguientes operaciones? a=5; b=++a; c=a++; b=b*5; a=a*2;
Y ya que estamos hablando de las asignaciones, hay que comentar que en C# es posible hacer asignaciones mltiples: a = b = c = 1;
Ejercicios propuestos: Crear un programa que use tres variables x,y,z. Sus valores iniciales sern 15, -10, 214. Se deber 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. Cul sera el resultado de las siguientes operaciones? a=5; b=a+2; b-=3; c=-3; c*=2; ++c; a*=b;
double 64 5,010
-324
1,710308 15-16
o bien, si queremos dar un valor inicial en el momento de definirlos (recordando que para las cifras decimales no debemos usar una coma, sino un punto):
float x = 12.56;
segundoNumero = Convert.ToSingle(Console.ReadLine()); suma = primerNumero + segundoNumero; Console.WriteLine("La suma de {0} y {1} es {2}", primerNumero, segundoNumero, suma); } } Cuidado al probar este programa: aunque en el fuente debemos escribir los decimales usando un punto, como 123.456, al poner el ejecutable en marcha parte del trabajo se le encarga al sistema operativo, de modo que si ste sabe que en nuestro pas se usa la coma para los decimales, considere la coma el separador correcto y no el punto, como ocurre si introducimos estos datos en la versin espaola de Windows XP: ejemplo05 Introduce el primer nmero 23,6 Introduce el segundo nmero 34.2 La suma de 23,6 y 342 es 365,6
Vamos a probarlos en un ejemplo: /*---------------------------*/ /* Ejemplo en C# n 28: */ /* ejemplo28.cs */ /* */ /* Formato de nms. reales */ /* */ /* Introduccion a C#, */
Revisin 0.97 Pgina 58
/* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo28 { public static void Main() { double numero = 12.34; Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( } } 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. numero.ToString("N1") ); numero.ToString("N3") ); numero.ToString("0.0") ); numero.ToString("0.000") ); numero.ToString("#.#") ); numero.ToString("#.###") );
Ejercicios propuestos: 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. Crear un programa que use tres variables x,y,z. Las tres sern nmeros reales, y nos bastar con dos cifras decimales. Deber pedir al usuario los valores para las tres variables y mostrar en pantalla el valor de x2 + y - z (con exactamente dos cifras decimales).
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
Revisin 0.97 Pgina 59
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 en C# n 28b: */ /* ejemplo28b.cs */ /* */ /* Hexadecimal y binario */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo28b { public static void Main() { int numero = 247; Console.WriteLine( Convert.ToString(numero, 16) ); Console.WriteLine( Convert.ToString(numero, 2) ); } }
Su resultado sera: f7 11110111 (Si quieres saber ms sobre el sistema hexadecimal, mira los apndices al final de este texto)
/*---------------------------*/ /* Ejemplo en C# n 29: */ /* ejemplo29.cs */ /* */ /* Tipo de datos "char" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo29 { public static void Main() { char letra; letra = 'a'; Console.WriteLine("La letra es {0}", letra); Console.WriteLine("Introduce una nueva letra"); letra = Convert.ToChar(Console.ReadLine()); Console.WriteLine("Ahora la letra es {0}", letra); } }
Significado Emite un pitido Retroceso (permite borrar el ltimo carcter) Avance de pgina (expulsa una hoja en la impresora) Avanza de lnea (salta a la lnea siguiente) Retorno de carro (va al principio de la lnea) Salto de tabulacin horizontal Salto de tabulacin vertical Muestra una comilla simple Muestra una comilla doble Muestra una barra invertida Carcter nulo (NULL)
Vamos a ver un ejemplo que use los ms habituales: /*---------------------------*/ /* Ejemplo en C# n 30: */ /* ejemplo30.cs */ /* */ /* Secuencias de escape */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo30 { 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 \\"); } }
En algunas ocasiones puede ser incmodo manipular estas secuencias de escape. Por ejemplo, cuando usemos estructuras de directorios: c:\\datos\\ejemplos\\curso\\ejemplo1. En este caso, se puede usar una arroba (@) antes del texto sin las barras invertidas: ruta = @"c:\datos\ejemplos\curso\ejemplo1" En este caso, el problema est si aparecen comillas en medio de la cadena. Para solucionarlo, se duplican las comillas, as: orden = "copy ""documento de ejemplo"" f:" Ejercicio propuesto: Crear un programa que pida al usuario que teclee cuatro letras y las muestre en pantalla juntas, pero en orden inverso, y entre comillas dobles. Por ejemplo si las letras que se teclean son a, l, o, h, escribira "hola".
As, un ejemplo que diera un valor a un "string", lo mostrara (entre comillas, para practicar las secuencias de escape que hemos visto en el apartado anterior) y leyera un valor tecleado por el usuario podra ser: /*---------------------------*/ /* Ejemplo en C# n 31: */ /* ejemplo31.cs */ /* */ /* Uso basico de "string" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo31 { public static void Main() { string frase; frase = "Hola, como estas?"; Console.WriteLine("La frase es \"{0}\"", frase);
Revisin 0.97 Pgina 63
Console.WriteLine("Introduce una nueva frase"); frase = Console.ReadLine(); Console.WriteLine("Ahora la frase es \"{0}\"", frase); } } 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, cuando conozcamos las estructuras de control bsicas.
Lo emplearemos a partir de ahora en los fuentes que usen condiciones un poco complejas. Un ejemplo que pida una letra y diga si es una vocal, una cifra numrica u otro smbolo, usando variables "bool" podra ser: /*---------------------------*/ /* Ejemplo en C# n 32: */ /* ejemplo32.cs */ /* */ /* Condiciones con if (8) */ /* Variables bool */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo32 { public static void Main() {
Revisin 0.97 Pgina 64
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 nmero."); } } Ejercicios propuestos: Crear 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.
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]. Como ejemplo, vamos a definir un grupo de 5 nmeros enteros y hallar su suma: /*---------------------------*/ /* Ejemplo en C# n 33: */ /* ejemplo33.cs */ /* */ /* Primer ejemplo de tablas */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo33 { public static void Main() { int[] numero = new int[5]; /* Un array de 5 nmeros enteros */ int suma; /* Un entero que ser la suma */ numero[0] = 200; /* Les damos valores */ numero[1] = 150; numero[2] = 100; numero[3] = -50; numero[4] = 300; suma = numero[0] + /* Y hallamos la suma */ numero[1] + numero[2] + numero[3] + numero[4]; Console.WriteLine("Su suma es {0}", suma); /* Nota: esta es la forma ms ineficiente e incmoda */ /* Ya lo iremos mejorando */ }
Revisin 0.97 Pgina 66
Ejercicios propuestos: Un programa que pida al usuario 4 nmeros, los memorice (utilizando una tabla), calcule su media aritmtica y despus muestre en pantalla la media y los datos tecleados. Un programa que pida al usuario 5 nmeros reales y luego los muestre en el orden contrario al que se introdujeron. Un programa que pida al usuario 10 nmeros enteros y calcule (y muestre) cul es el mayor de ellos.
Ejercicios propuestos:
Revisin 0.97 Pgina 67
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. Un programa que almacene en una tabla el nmero de das que tiene cada mes (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).
El "truco" consistir en emplear cualquiera de las estructuras repetitivas que ya hemos visto (while, do..while, for), por ejemplo as: suma = 0; /* Valor inicial de la suma */ for (i=0; i<=4; i++) /* Y hallamos la suma repetitiva */ suma += numero[i];
El fuente completo podra ser as: /*---------------------------*/ /* Ejemplo en C# n 35: */ /* ejemplo35.cs */ /* */ /* Tercer ejemplo de */ /* tablas */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo35 { public static void Main() { int[] numero = /* Un array de 5 nmeros enteros */ {200, 150, 100, -50, 300}; int suma; /* Un entero que ser la suma */ int i; /* Para recorrer los elementos */ suma = 0; /* Valor inicial de la suma */ for (i=0; i<=4; i++) /* Y hallamos la suma repetitiva */ suma += numero[i]; Console.WriteLine("Su suma es {0}", suma); }
Revisin 0.97 Pgina 68
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 est clara.
Ejercicios propuestos: A partir del programa propuesto en 4.1.2, que almacenaba en una tabla el nmero de das que tiene cada mes, crear otro que pida al usuario que le indique la fecha, detallando el da (1 al 31) y el mes (1=enero, 12=diciembre), como respuesta muestre en pantalla el nmero de das que quedan hasta final de ao.
Revisin 0.97 Pgina 69
Crear un programa que pida al usuario 10 nmeros y luego los muestre en orden inverso (del ltimo que se ha introducido al primero que se introdujo). Un programa que pida al usuario 10 nmeros y luego calcule y muestre cual es el mayor de todos ellos. Un programa que pida al usuario 10 nmeros, calcule su media y luego muestre los que estn por encima de la media. 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". Un programa que prepare espacio para 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. Un programa que prepare espacio para un mximo de 10 nombres. Deber mostrar al usuario un men que le permita realizar las siguientes operaciones: o Aadir un dato al final de los ya existentes. o Insertar un dato en una cierta posicin (lo 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". o Borrar el dato que hay en una cierta posicin (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" o Mostrar los datos que contiene el array. o Salir del programa.
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.
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 del uso con arrays de la forma [2,20], lo que podramos llamar el "estilo Pascal", en el usemos tanto arrays con valores prefijados, como arrays para los que reservemos espacio con "new" y a los que demos valores ms tarde: /*---------------------------*/ /* Ejemplo en C# n 37: */ /* ejemplo37.cs */ /* */ /* Array de dos dimensiones */ /* al estilo Pascal */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo37 { public static void Main() { int[,] notas1 notas1[0,0] = notas1[0,1] = notas1[1,0] = notas1[1,1] = = new int[2,2]; // 2 bloques de 2 datos 1; 2; 3; 4;
int[,] notas2 = // 2 bloques de 10 datos { {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {11, 12, 13, 14, 15, 16, 17, 18, 19, 20} }; Console.WriteLine("La nota1 del segundo alumno del grupo 1 es {0}", notas1[0,1]); Console.WriteLine("La nota2 del tercer alumno del grupo 1 es {0}", notas2[0,2]); } }
Este tipo de tablas de varias dimensiones son las que se usan tambin para guardar matrices, cuando se trata de resolver problemas matemticos ms complejos que los que hemos visto hasta ahora. La otra forma de tener arrays multidimensionales son los "arrays de arrays", que, como ya hemos comentado, y como veremos en este ejemplo, pueden tener elementos de distinto tamao. En ese caso nos puede interesar saber su longitud, para lo que podemos usar "a.Length": /*---------------------------*/
Revisin 0.97 Pgina 71
/* Ejemplo en C# n 38: */ /* ejemplo38.cs */ /* */ /* Array de dos dimensiones */ /* al estilo C... o casi */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo38 { public static void Main() { int[][] notas; // Array de dos dimensiones notas = new int[3][]; // Seran 3 bloques de datos notas[0] = new int[10]; // 10 notas en un grupo notas[1] = new int[15]; // 15 notas en otro grupo notas[2] = new int[12]; // 12 notas en el ultimo // Damos valores de ejemplo for (int i=0;i<notas.Length;i++) { for (int j=0;j<notas[i].Length;j++) { notas[i][j] = i + j; } } // Y mostramos esos valores for (int i=0;i<notas.Length;i++) { for (int j=0;j<notas[i].Length;j++) { Console.Write(" {0}", notas[i][j]); } Console.WriteLine(); } } } La salida de este programa 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: Un programa que 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.
Revisin 0.97 Pgina 72
Un programa que 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.
Console.WriteLine("La edad de {0} es {1}", persona.nombre, persona.edad); } } Nota: La notacin 7.5f se usa para detallar que se trata de un nmero real de simple precisin (un "float"), porque de lo contrario, 7.5 se considerara un nmero de doble precisin, y al tratar de compilar obtendramos un mensaje de error, diciendo que no se puede convertir de "double" a "float" sin prdida de precisin. Al aadir la "f" al final, estamos diciendo "quiero que ste nmero se tome como un float; s que habr una prdida de precisin pero es aceptable para m. Ejercicios propuestos: 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.
Console.WriteLine("La edad de {0} es {1}", persona[0].nombre, persona[0].edad); persona[1].nombre = "Pedro"; Console.WriteLine("La edad de {0} es {1}", persona[1].nombre, persona[1].edad); } } La inicial de la primera persona sera "persona[0].inicial", y la edad del ltimo sera "persona[99].edad". Al probar este programa obtenemos La edad de Juan es 20 La edad de Pedro es 0 Porque cuando reservamos espacio para los elementos de un "array" usando "new", sus valores se dejan "vacos" (0 para los nmeros, cadenas vacas para las cadenas de texto). Ejercicios propuestos: Ampliar el programa del apartado 4.3.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). 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.
using System; public class Ejemplo41 { struct fechaNacimiento { public int dia; public int mes; public int anyo; }
struct tipoPersona { public string nombre; public char inicial; public fechaNacimiento diaDeNacimiento; public float nota; } public static void Main() { tipoPersona persona; persona.nombre = "Juan"; persona.inicial = 'J'; persona.diaDeNacimiento.dia = 15; persona.diaDeNacimiento.mes = 9; persona.nota = 7.5f; Console.WriteLine("{0} naci en el mes {1}", persona.nombre, persona.diaDeNacimiento.mes); } } Ejercicios propuestos: Ampliar el programa del primer apartado de 4.3.2, para que el campo "duracin" se almacene como minutos y segundos, usando un "struct" anidado que contenga a su vez estos dos campos.
/* Ejemplo en C# n 42: */ /* ejemplo42.cs */ /* */ /* Cadenas de texto (1) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo42 { public static void Main() { string saludo = "Hola"; string segundoSaludo; string nombre, despedida; segundoSaludo = "Que tal?"; Console.WriteLine("Dime tu nombre... "); nombre = Console.ReadLine(); Console.WriteLine("{0} {1}", saludo, nombre); Console.WriteLine(segundoSaludo); if (nombre == "Alberto") Console.WriteLine("Dices que eres Alberto?"); else Console.WriteLine("As que no eres Alberto?"); despedida = "Adios " + nombre + "!"; Console.WriteLine(despedida); } }
Ejercicios propuestos: Un programa que te pida tu nombre y lo muestre en pantalla separando cada letra la siguiente con un espacio. Por ejemplo, si tu nombre es "Juan", debera aparecer pantalla "J u a n". Un programa capaz de sumar dos nmeros enteros muy grandes (por ejemplo, de cifras), que se debern pedir como cadena de texto y analizar letra a letra. Un programa capaz de multiplicar dos nmeros enteros muy grandes (por ejemplo, 30 cifras), que se debern pedir como cadena de texto y analizar letra a letra.
de en 30 de
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");
Un programa que probara todas estas posibilidades podra ser as: /*---------------------------*/ /* Ejemplo en C# n 43: */ /* ejemplo43.cs */ /* */ /* Cadenas de texto (2) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo43 { public static void Main() { string ejemplo = "Hola, que tal estas"; Console.WriteLine("El texto es: {0}", ejemplo); Console.WriteLine("La primera letra es: {0}", ejemplo[0]); Console.WriteLine("Las tres primeras letras son: {0}", ejemplo.Substring(0,3)); Console.WriteLine("La longitud del texto es: {0}", ejemplo.Length); Console.WriteLine("La posicion de \"que\" es: {0}", ejemplo.IndexOf("que")); Console.WriteLine("La ultima A esta en la posicion: {0}", ejemplo.LastIndexOf("a")); Console.WriteLine("En maysculas: {0}", ejemplo.ToUpper()); Console.WriteLine("En minsculas: {0}", ejemplo.ToLower()); Console.WriteLine("Si insertamos \", tio\": {0}", ejemplo.Insert(4,", tio"));
Revisin 0.97 Pgina 79
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 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
Otra posibilidad interesante, aunque un poco ms avanzada, es la de descomponer una cadena en trozos, que estn separados por una serie de delimitadores (por ejemplo, espacios o comas). Para ello se puede usar Split, que crea un array a partir de los fragmentos de la cadena, as: /*---------------------------*/ /* Ejemplo en C# n 43b: */ /* ejemplo43b.cs */ /* */ /* Cadenas de texto (2b) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo43b { public static void Main() { string ejemplo = "uno,dos.tres,cuatro"; char [] delimitadores = {',', '.'}; int i; string [] ejemploPartido = ejemplo.Split(delimitadores);
for (i=0; i<ejemploPartido.Length; i++) Console.WriteLine("Fragmento {0}= {1}", i, ejemploPartido[i]); } } Que mostrara en pantalla lo siguiente: Fragmento Fragmento Fragmento Fragmento 0= 1= 2= 3= uno dos tres cuatro
Un programa completo de prueba podra ser as: /*---------------------------*/ /* Ejemplo en C# n 43c: */ /* ejemplo43c.cs */ /* */ /* Cadenas de texto (2c) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo43c { public static void Main()
Revisin 0.97 Pgina 81
{ string frase; Console.WriteLine("Escriba una palabra"); frase = Console.ReadLine(); // Compruebo si es exactamente hola if (frase == "hola") Console.WriteLine("Ha escrito hola"); // Compruebo si es mayor o menor if (frase.CompareTo("hola") > 0) Console.WriteLine("Es mayor que hola"); else if (frase.CompareTo("hola") < 0) Console.WriteLine("Es menor que hola"); // Comparo sin distinguir 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 coindice 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)
ciertas ocasiones con los Arrays), y se pueden convertir a una cadena "convencional" usando "ToString": /*---------------------------*/ /* Ejemplo en C# n 44: */ /* ejemplo44.cs */ /* */ /* Cadenas modificables */ /* con "StringBuilder" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Text; // Usaremos un System.Text.StringBuilder public class Ejemplo44 { public static void Main() { StringBuilder cadenaModificable = new StringBuilder("Hola"); cadenaModificable[0] = 'M'; Console.WriteLine("Cadena modificada: {0}", cadenaModificable); string cadenaNormal; cadenaNormal = cadenaModificable.ToString(); Console.WriteLine("Cadena normal a partir de ella: {0}", cadenaNormal); } }
Ejercicios propuestos: 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". Crear un juego del ahorcado, en el que un primer usuario introduzca la palabra a adivinar, se muestre esta programa oculta con guiones (-----) y el programa acepte las letras que introduzca el segundo usuario, cambiando los guiones por letras correctas cada vez que acierte (por ejemplo, a---a-t-). La partida terminar cuando se acierte la palabra por completo o el usuario agote sus 8 intentos.
/*---------------------------*/ /* Ejemplo en C# n 45: */ /* ejemplo45.cs */ /* */ /* Ejemplo de "foreach" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo45 { public static void Main() { int[] diasMes = {31, 28, 21}; foreach(int dias in diasMes) { Console.WriteLine("Dias del mes: {0}", dias); } string[] nombres = {"Alberto", "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(); } }
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. Una opcin que podemos a tomar para resolver este problema es la de contar el nmero de fichas que tenemos almacenadas, y as podremos aadir de una en una. 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, recorreremos desde la posicin 0 hasta la n-1, haciendo algo como
for (i=0; i<=n-1; i++) { ... ms rdenes ...}
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 en C# n 46: */ /* ejemplo46.cs */ /* */ /* Tabla con muchos struct */ /* y menu para manejarla */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo46 { struct tipoFicha { public string nombreFich; public long tamanyo; };
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 */
Revisin 0.97 Pgina 85
do { /* Menu principal */ 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 que sean de mas 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 que te muestre?"); tamanyoBuscar = Convert.ToInt64( Console.ReadLine() ); for (i=0; i<numeroFichas; i++) if (fichas[i].tamanyo >= tamanyoBuscar) Console.WriteLine("Nombre: {0}; Tamao: {1} Kb", fichas[i].nombreFich, fichas[i].tamanyo); break; case 4: /* Ver todos los datos (pocos) de un fichero */ Console.WriteLine("De qu fichero quieres ver todos los datos?"); textoBuscar = Console.ReadLine(); for (i=0; i<numeroFichas; i++) if ( fichas[i].nombreFich == textoBuscar ) Console.WriteLine("Nombre: {0}; 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 */ } }
Revisin 0.97 Pgina 86
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 supuesto, 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: 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. Un programa que pida datos de 8 personas: nombre, dia de nacimiento, mes de nacimiento, y ao de nacimiento (que se deben almacenar en una tabla 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. 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: o Mostrar la lista de todos los nombres. o Mostrar las personas de una cierta edad. o Mostrar las personas cuya inicial sea la que el usuario indique. o Salir del programa (lgicamente, este men debe repetirse hasta que se escoja la opcin de "salir"). Mejorar la base de datos de ficheros (ejemplo 46) para que no permita introducir tamaos incorrectos (nmeros negativos) ni nombres de fichero vacos. Ampliar la base de datos de ficheros (ejemplo 46) 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 "IndexOf"). Esta bsqueda no debe distinguir maysculas y minsculas (con la ayuda de ToUpper o ToLower). Ampliar el ejercicio anterior (el que permite bsqueda parcial) 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"). Ampliar la base de datos de ficheros (ejemplo 46) 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). Mejorar la base de datos de ficheros (ejemplo 46) 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. Ampliar la base de datos de ficheros (ejemplo 46) 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.
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 en C# */ /* ordenar.cs */ /* */ /* Ordenaciones simples */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ordenar { public static void Main() { int[] datos = {5, 3, 14, 20, 8, 9, 1}; int i,j,datoTemporal; int n=7; // Numero de datos
// BURBUJA // (Intercambiar cada pareja consecutiva que no est ordenada) // Para i=1 hasta n-1 // Para j=i+1 hasta n // Si A[i] > A[j] // Intercambiar ( A[i], A[j]) Console.WriteLine("Ordenando mediante burbuja... "); for(i=0; i < n-1; i++) { foreach (int dato in datos) // Muestro datos Console.Write("{0} ",dato);
Revisin 0.97 Pgina 89
Console.WriteLine(); for(j=i+1; j < n; j++) { if (datos[i] > datos[j]) { datoTemporal = datos[i]; datos[i] = datos[j]; datos[j] = datoTemporal; } } } Console.Write("Ordenado:"); foreach (int dato in datos) // Muestro datos finales Console.Write("{0} ",dato); Console.WriteLine();
// 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("\nOrdenando 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).
Revisin 0.97 Pgina 90
// Para i=2 hasta n // j=i-1 // mientras (j>=1) y (A[j] > A[j+1]) // Intercambiar ( A[j], A[j+1]) // j = j - 1 Console.WriteLine("\nOrdenando mediante insercin directa... "); int[] datos3 = {5, 3, 14, 20, 8, 9, 1}; for(i=1; i < n; i++) { foreach (int dato in datos3) // Muestro datos Console.Write("{0} ",dato); Console.WriteLine(); j = i-1; while ((j>=0) && (datos3[j] > datos3[j+1])) { datoTemporal = datos3[j]; datos3[j] = datos3[j+1]; datos3[j+1] = datoTemporal; j--; } } Console.Write("Ordenado:"); foreach (int dato in datos3) // Muestro datos finales Console.Write("{0} ",dato); Console.WriteLine(); } } Y su resultado 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
Revisin 0.97 Pgina 91
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
Esos "trozos" de programa son lo que suele llamar "subrutinas", "procedimientos" o "funciones". En el lenguaje C y sus derivados, el nombre que ms se usa es el de funciones.
{ 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 en C# n 47: */ /* ejemplo47.cs */ /* */ /* Funcion "saludar" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo47 { public static void Saludar() { Console.WriteLine("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 podra ser simplemente: leerDatosDeFichero(); do { mostrarMenu(); pedirOpcion(); switch( opcion ) { case 1: buscarDatos(); break; case 2: modificarDatos(); break; case 3: anadirDatos(); break;
} Estos datos adicionales que indicamos a la funcin es lo que llamaremos sus "parmetros". Como se ve en el ejemplo, tenemos que indicar un nombre para cada parmetro (puede haber varios) y el tipo de datos que corresponde a ese parmetro. Si hay ms de un parmetro, deberemos indicar el tipo y el nombre para cada uno de ellos, y separarlos entre comas: public static void sumar ( int x, int y ) { ... }
public static void Main() { int numero; int resultado; numero= 5; resultado = cuadrado(numero); Console.WriteLine("El cuadrado del numero {0} es {1}", numero, resultado); Console.WriteLine(" y el de 3 es {0}", cuadrado(3)); } }
Podemos hacer una funcin que nos diga cual es el mayor de dos nmeros reales as: public static float mayor ( float n1, float n2 ) { if (n1 > n2) return n1; else return n2; } Ejercicios propuestos: Crear una funcin que borre la pantalla dibujando 25 lneas en blanco. No debe devolver ningn valor. Crear una funcin que calcule el cubo de un nmero real (float). El resultado deber ser otro nmero real. Probar esta funcin para calcular el cubo de 3.2 y el de 5. Crear una funcin que calcule cual es el menor de dos nmeros enteros. El resultado ser otro nmero entero. Crear 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. Crear una funcin que devuelva la primera letra de una cadena de texto. Probar esta funcin para calcular la primera letra de la frase "Hola". Crear una funcin que devuelva la ltima letra de una cadena de texto. Probar esta funcin para calcular la ltima letra de la frase "Hola". Crear una funcin que reciba un nmero y muestre en pantalla el permetro y la superficie de un cuadrado que tenga como lado el nmero que se ha indicado como parmetro.
(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 en C# n 50: */ /* ejemplo50.cs */ /* */ /* Ejemplo de funcin con */ /* variables locales */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo50 { public static int potencia(int nBase, int nExponente) { int temporal = 1; /* Valor que voy hallando */ int i; /* Para bucles */ for(i=1; i<=nExponente; i++) temporal *= nBase; return temporal; } /* Multiplico "n" veces */ /* Y calculo el valor temporal */ /* Tras las multiplicaciones, */ /* obtengo el valor que buscaba */
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". 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 es usando los parmetros de cada funcin, como en el anterior ejemplo. Ejercicios propuestos: Crear 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. Probarlo con un programa que pida al usuario un ao entre 1800 y 2100. Crear 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). Crear una funcin "esPrimo", que reciba un nmero y devuelva el valor booleano "true" si es un nmero primo o "false" en caso contrario. Crear una funcin 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 (aparece 2 veces). Crear una funcin 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. Crear una funcin 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 **** *** ** *
public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); cambiaN(); Console.WriteLine("Ahora n vale {0}", n); } } El resultado de este programa es:
n vale 5 Ahora n vale 5
Por qu? Sencillo: tenemos una variable local dentro de "duplica" y otra dentro de "main". El hecho de que las dos tengan el mismo nombre no afecta al funcionamiento del programa, siguen siendo distintas. Si la variable es "global", declarada fuera de estas funciones, s ser accesible por todas ellas: /*---------------------------*/ /* Ejemplo en C# n 52: */ /* ejemplo52.cs */ /* */ /* Una variable global */
Revisin 0.97 Pgina 100
/* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo52 { static int n = 7; public static void cambiaN() { n ++; }
public static void Main() { Console.WriteLine("n vale {0}", n); cambiaN(); Console.WriteLine("Ahora n vale {0}", n); } } Dentro de poco, hablaremos de por qu cada uno de los bloques de nuestro programa, e incluso las "variables globales", tienen delante la palabra "static". Ser cuando tratemos la "Programacin Orientada a Objetos", en el prximo tema.
public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); duplica(n); Console.WriteLine("Ahora n vale {0}", n); } } El resultado de este programa ser: n vale 5 El valor recibido vale 5 y ahora vale 10 Ahora n vale 5 Vemos que al salir de la funcin, los cambios que hagamos a esa variable que se ha recibido como parmetro no se conservan. 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. Si queremos que los cambios 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 en C# n 54: */ /* ejemplo54.cs */ /* */ /* Modificar una variable */ /* recibida como parmetro */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo54 { public static void duplica(ref int x) { Console.WriteLine(" El valor recibido vale {0}", x); x = x * 2; Console.WriteLine(" y ahora vale {0}", x); } public static void Main() { int n = 5;
Revisin 0.97 Pgina 102
Console.WriteLine("n vale {0}", n); duplica(ref n); Console.WriteLine("Ahora n vale {0}", n); } } En este caso s se modifica la variable n: n vale 5 El valor recibido vale 5 y ahora vale 10 Ahora n vale 10 El hecho de poder modificar valores que se reciban como 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". Las veremos ms adelante. Ejercicios propuestos: Crear una funcin "intercambia", que intercambie el valor de los dos nmeros enteros que se le indiquen como parmetro. Crear 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.
public class Ejemplo55 { public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); duplica(ref n); Console.WriteLine("Ahora n vale {0}", n); } public static void duplica(ref int x) { Console.WriteLine(" El valor recibido vale {0}", x); x = x * 2; Console.WriteLine(" y ahora vale {0}", x); } }
Vamos a ver un ejemplo, que muestre en pantalla un nmero al azar entre 1 y 10: /*---------------------------*/ /* Ejemplo en C# n 56: */ /* ejemplo56.cs */ /* */ /* Nmeros al azar */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo56 { public static void Main() { Random r = new Random(DateTime.Now.Millisecond); int aleatorio = r.Next(1, 10); Console.WriteLine("Un nmero entre 1 y 10: {0}", aleatorio); } }
Ejercicios propuestos: Crear un programa que genere un nmero al azar entre 1 y 100. El usuario tendr 6 oportunidades para acertarlo. Mejorar el programa del ahorcado propuesto en el apartado 4.4.8, para que la palabra a adivinar no sea tecleado por un segundo usuario, sino que se escoja al azar de un "array" de palabras prefijadas (por ejemplo, nombres de ciudades).
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") y 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: 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); Ejercicios propuestos: Crear un programa que halle cualquier raz 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. Crear un programa que resuelva ecuaciones de segundo grado, del tipo ax2 + bx + c = 0 El usuario deber introducir los valores de a, b y c. Se deber crear una funcin "raicesSegundoGrado", que recibir como parmetros los coeficientes a, b y c, 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.10. Recursividad
Una funcin recursiva es aquella que se define a partir de ella misma. Dentro de las matemticas tenemos varios ejemplos. Uno clsico es el "factorial de un nmero":
n! = n (n-1) (n-2) ... 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, programando, se hara: /*---------------------------*/ /* Ejemplo en C# n 57: */ /* ejemplo57.cs */ /* */ /* Funciones recursivas: */ /* factorial */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo57 { public static long fact(int n) { if (n==1) // Aseguramos que termine return 1; return n * fact (n-1); // Si no es 1, sigue la 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}", fact(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". 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? Pues 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. Ms adelante veremos algn otro ejemplo.
Revisin 0.97 Pgina 107
Ejercicios propuestos: Crear una funcin que calcule el valor de elevar un nmero entero a otro nmero entero (por ejemplo, 5 elevado a 3 = 53 = 5 5 5 = 125). Esta funcin se debe crear de forma recursiva. Como alternativa, crear 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". Crear 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). Crear un programa que emplee recursividad para calcular la suma de los elementos de un vector. Crear un programa que emplee recursividad para calcular el mayor de los elementos de un vector. Crear un programa que emplee recursividad para dar la vuelta a una cadena de caracteres (por ejemplo, a partir de "Hola" devolvera "aloH"). Crear, 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. 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).
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 lee con "IF ERRORLEVEL", as:
IF ERRORLEVEL 1 ECHO Ha habido un error en el programa
Una forma alternativa de que "Main" indique errores al sistema operativo es no declarndolo como "void", sino como "int", y empleando entonces la orden "return" cuando nos interese: public static int Main(string[] args) { ... return 1; } Un ejemplo que pusiera todo esto en prueba podra ser: /*---------------------------*/ /* Ejemplo en C# n 58: */ /* ejemplo58.cs */ /* */ /* Parmetros y valor de */ /* retorno de "Main" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo58 { public static int Main(string[] args)
Revisin 0.97 Pgina 109
{ 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("Escriba algn parmetro!"); Environment.Exit(1); } return 0; } }
Ejercicios propuestos: Crear un programa llamado "suma", que calcule (y muestre) la suma de dos nmeros que se le indiquen como parmetro. Por ejemplo, si se teclea "suma 2 3" deber responder "5", y si se teclea "suma 2" deber responder "no hay suficientes datos y devolver un cdigo de error 1. Crear 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".
Otro concepto importante es el de "clase": Una clase es un conjunto de objetos que tienen caractersticas comunes. Por ejemplo, tanto mi puerta como la de mi vecino son puertas, es decir, ambas son objetos que pertenecen a la clase "puerta". De igual modo, tanto un Ford Focus como un Honda Civic o un Toyota Corolla son objetos concretos que pertenecen a la clase "coche".
public void Abrir() { abierta = true; } public void Cerrar() { abierta = false; } public void MostrarEstado() { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
Como se puede observar, los objetos de la clase "Puerta" 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"):
Revisin 0.97 Pgina 112
/*---------------------------*/ /* Ejemplo en C# n 59: */ /* ejemplo59.cs */ /* */ /* Primer ejemplo de clases */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Puerta { int ancho; int alto; int color; bool abierta; // // // // Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public void Abrir() { abierta = true; } public void Cerrar() { abierta = false; } public void MostrarEstado() { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
public class Ejemplo59 { public static void Main() { Puerta p = new Puerta(); Console.WriteLine("Valores iniciales..."); p.MostrarEstado(); Console.WriteLine("\nVamos a abrir..."); p.Abrir(); p.MostrarEstado(); } }
Revisin 0.97 Pgina 113
Este fuente ya no contiene una nica clase (class), como todos nuestros ejemplos anteriores, sino dos clases distintas: La clase "Puerta", que son los nuevos objetos con los que vamos a practicar. La clase "Ejemplo59", que representa a nuestra aplicacin.
El resultado de ese programa es el siguiente: Valores iniciales... Ancho: 0 Alto: 0 Color: 0 Abierta: False Vamos a abrir... Ancho: 0 Alto: 0 Color: 0 Abierta: True Se puede ver que en C#, al contrario que en otros lenguajes, las variables que forman parte de una clase (los "atributos") tienen un valor inicial predefinido: 0 para los nmeros, una cadena vaca para las cadenas de texto, o "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", 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, desde Ejemplo59). 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.
Normalmente, como forma de ocultar datos, 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) {
Revisin 0.97 Pgina 114
ancho = nuevoValor; }
Tambin puede desconcertar que en "Main" aparezca la palabra "static", mientras que no lo hace en los mtodos de la clase "Puerta". Veremos este detalle un poco ms adelante. Ejercicio propuesto: Crear 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. Crear tambin una clase llamada PruebaPersona. Esta clase deber contener slo la funcin Main, que crear dos objetos de tipo Persona, les asignar un nombre y les pedir que saluden.
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). Precisamente por eso, es interesante (pero 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". Esta es una regla que no seguiremos en algunos de los ejemplos del texto, por respetar la numeracin consecutiva de los ejemplos, pero que s se debera seguir en un proyecto de mayor tamao, formado por varias clases.
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
Vamos a dividir en dos fuentes el ltimo ejemplo y a ver cmo se compilara. La primera clase podra ser sta: /*---------------------------*/ /* Ejemplo en C# n 59b: */ /* ejemplo59b.cs */ /* */ /* Dos clases en dos */ /* ficheros (fichero 1) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/
Revisin 0.97 Pgina 115
using System; public class Puerta { int ancho; int alto; int color; bool abierta; // // // // Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public void Abrir() { abierta = true; } public void Cerrar() { abierta = false; } public void MostrarEstado() { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
Y la segunda clase podra ser: /*---------------------------*/ /* Ejemplo en C# n 59c: */ /* ejemplo59c.cs */ /* */ /* Dos clases en dos */ /* ficheros (fichero 2) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ public class Ejemplo59c { public static void Main() { Puerta p = new Puerta(); Console.WriteLine("Valores iniciales..."); p.MostrarEstado(); Console.WriteLine("\nVamos a abrir..."); p.Abrir(); p.MostrarEstado();
Revisin 0.97 Pgina 116
Aun as, para estos proyectos formados por varias clases, lo ideal es usar algn entorno ms avanzado, como SharpDevelop o VisualStudio, que 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... por eso, al final de este tema tendrs un apartado con una introduccin al uso de SharpDevelop.
Ejercicio propuesto: Modificar el fuente del ejercicio anterior, para dividirlo en dos ficheros: Crear 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. Crear 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.
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. No tenemos por qu heredar todo; 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: public new void MostrarEstado() { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); Console.WriteLine("Bloqueada: {0}", bloqueada); } Aun as, esto todava no funciona: los atributos de una Puerta, como el "ancho" y el "alto" estaban declarados como "privados" (es lo que se considera si no decimos los contrario), por lo que no son accesibles desde ninguna otra clase, ni siquiera desde Porton. La solucin ms razonable no es declararlos como "public", porque no queremos que sean accesibles desde cualquier sitio. 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 "protected" ser accesible por las clases derivadas de la actual, pero por nadie ms: public class Puerta { protected protected protected protected int ancho; int alto; int color; bool abierta; // // // // Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
(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"). Un 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 en C# n 60: */ /* ejemplo60.cs */ /* */
Revisin 0.97 Pgina 118
/* Segundo ejemplo de */ /* clases: herencia */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; // ------------------------------public class Puerta { protected protected protected protected int ancho; int alto; int color; bool abierta; // // // // Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public void Abrir() { abierta = true; } public void Cerrar() { abierta = false; } public void MostrarEstado() { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
// ------------------------------public class Porton: Puerta { bool bloqueada; public void Bloquear() { bloqueada = true; } public void Desbloquear() { bloqueada = false; } public new void MostrarEstado() { Console.WriteLine("Ancho: {0}", ancho);
Revisin 0.97 Pgina 119
Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); Console.WriteLine("Bloqueada: {0}", bloqueada); } } // Final de la clase Porton // ------------------------------public class Ejemplo60 { public static void Main() { Porton p = new Porton(); Console.WriteLine("Valores iniciales..."); p.MostrarEstado(); Console.WriteLine("\nVamos a bloquear..."); p.Bloquear(); p.MostrarEstado(); Console.WriteLine("\nVamos a desbloquear y a abrir..."); p.Desbloquear(); p.Abrir(); p.MostrarEstado(); } }
Ejercicios propuestos: Ampliar las clases del primer ejercicio propuesto, creando un nuevo proyecto con las siguientes caractersticas: 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. Ampliar las clases del segundo ejercicio propuesto, 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". Esta clase deber heredar las caractersticas de la clase "Persona", pero redefinir el mtodo "Saludar", para que escriba en pantalla "Ciao". Crear tambin una clase llamada PruebaPersona3, en el fichero " pruebaPersona3.cs". Esta clase deber contener slo la funcin Main, que 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.
Este diagrama es una forma ms simple de ver las clases existentes y las relaciones entre ellas. Si generamos las clases a partir del diagrama, tendremos parte del trabajo hecho: ya "slo" nos quedar rellenar los detalles de mtodos como "Abrir", pero el esqueleto de todas las clases ya estar "escrito" para nosotros.
que desde dentro de "Main" (incluso perteneciente a otra clase) se usara con el nombre de la clase delante: public class Juego { ... public ComienzoPartida() { Hardware.BorrarPantalla (); ...
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 desde "Main". Declarar un objeto de la clase correspondiente, y entonces s podremos acceder a sus mtodos desde "Main": public class Ejemplo { ... public LanzarJuego () Juego j = new Juego(); j.ComienzoPartida (); ... {
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; } 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 en C# n 61: */ /* ejemplo61.cs */ /* */ /* Tercer ejemplo de clases */ /* Constructores */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Puerta { int ancho; int alto; int color; bool abierta; // // // // Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public Puerta() { ancho = 100; alto = 200; color = 0xFFFFFF; abierta = false; } public Puerta(int an, int al) { ancho = an; alto = al; color = 0xFFFFFF;
Revisin 0.97 Pgina 124
abierta = false; } public void Abrir() { abierta = true; } public void Cerrar() { abierta = false; } public void MostrarEstado() { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
public class Ejemplo61 { public static void Main() { Puerta p = new Puerta(); Puerta p2 = new Puerta(90,220); Console.WriteLine("Valores iniciales..."); p.MostrarEstado(); Console.WriteLine("\nVamos a abrir..."); p.Abrir(); p.MostrarEstado(); Console.WriteLine("Para la segunda puerta..."); p2.MostrarEstado(); } } Nota: al igual que existen los "constructores", tambin podemos indicar 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). Un "destructor" se llama igual que la clase, pero precedido por el smbolo "~", no tiene tipo de retorno, y no necesita ser "public", como ocurre en este ejemplo: ~Puerta() { // Liberar memoria
Revisin 0.97 Pgina 125
// Cerrar ficheros }
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"); } } // -----------------public class Ejemplo62 { public static void Main() { Animal a1 = new Animal(); GatoSiames a2 = new GatoSiames(); Perro a3 = new Perro(); Gato a4 = new Gato(); } } El resultado de este programa es: Ha nacido un animal Ha nacido un animal
Revisin 0.97 Pgina 127
Ha Ha Ha Ha Ha Ha
un un un un un un
Ejercicio propuesto: Crear un nico fuente que contenga las siguientes clases: o Una clase Trabajador, cuyo constructor escriba en pantalla "Soy un trabajador". o Una clase Programador, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy programador". o Una clase Analista, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy analista". o Una clase Ingeniero, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy ingeniero". o Una clase IngenieroInformatico, que derive de Ingeniero, cuyo constructor escriba en pantalla "Soy ingeniero informtico". o Una clase "PruebaDeTrabajadores", que cree un objeto perteneciente a cada una de esas clases.
Un fuente completo de ejemplo podra ser /*---------------------------*/ /* Ejemplo en C# n 63: */ /* ejemplo63.cs */ /* */ /* Quinto ejemplo de clases */ /* Array de objetos */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Animal
Revisin 0.97 Pgina 128
{ public Animal() { Console.WriteLine("Ha nacido un animal"); } } // -----------------public class Perro: Animal { public Perro() { Console.WriteLine("Ha nacido un perro"); } } // -----------------public class Ejemplo63 { public static void Main() { Perro[] misPerros = new Perro[5]; for (byte i = 0; i < 5; i ++) misPerros[i] = new Perro(); } } y su salida en pantalla, parecida a la del ejemplo anterior, sera Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido un un un un un un un un un un animal perro animal perro animal perro animal perro animal perro
Ejercicio propuesto: Crea una versin ampliada del anterior ejercicio propuesto, en la que no se cree un nico objeto de cada clase, sino un array de tres objetos.
Adems, existe una peculiaridad curiosa: podemos crear un array de "Animales", pero luego indicar que unos de ellos son perros, otros gatos, etc.,
Revisin 0.97 Pgina 129
Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new GatoSiames();
Un ejemplo ms detallado: /*---------------------------*/ /* Ejemplo en C# n 64: */ /* ejemplo64.cs */ /* */ /* Ejemplo de clases */ /* Array de objetos de */ /* varias subclases */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Animal { public Animal() { Console.WriteLine("Ha nacido un animal"); } } // -----------------public class Perro: Animal { public Perro() { Console.WriteLine("Ha nacido un perro"); } } // -----------------public class Gato: Animal { public Gato() { Console.WriteLine("Ha nacido un gato"); } }
public class GatoSiames: Gato { public GatoSiames() { Console.WriteLine("Ha nacido un gato siams"); } } // -----------------public class Ejemplo64 { 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(); } } La salida de este programa sera: Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido un un un un un un un un un un un un un un un un animal perro animal gato animal gato gato siams animal perro animal perro animal perro animal perro animal
// -----------------public class Ejemplo65 { 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(); } } 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.97 Pgina 133
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: 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", el primer elemento hace lo que corresponde a un Animal genrico (decir "Estoy comunicndome"), a pesar de que hayamos dicho que se trata de un Perro.
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 contuviera animales de distintos tipos. Para conseguir este comportamiento, debemos indicar a nuestro compilador que el mtodo "Hablar" que se usa en la clase Animal puede que sea redefinido por otras clases hijas, y que en ese caso debe prevalecer lo que indiquen las clases hijas. La forma de hacerlo es declarando ese mtodo "Hablar" como "virtual", y empleando en las clases hijas la palabra "override" en vez de "new", as: /*---------------------------*/ /* Ejemplo en C# n 66: */ /* ejemplo66.cs */ /* */ /* Ejemplo de clases */ /* Array de objetos de */ /* varias subclases con */ /* metodos virtuales */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Animal { public virtual void Hablar() { Console.WriteLine("Estoy comunicndome..."); } }
Revisin 0.97 Pgina 134
// -----------------public class Perro: Animal { public override void Hablar() { Console.WriteLine("Guau!"); } } // -----------------public class Gato: Animal { public override void Hablar() { Console.WriteLine("Miauuu"); } } // -----------------public class Ejemplo66 { 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(); } } 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:
Revisin 0.97 Pgina 135
Este podra ser un fuente completo: /*---------------------------*/ /* Ejemplo en C# n 67: */ /* ejemplo67.cs */ /* */ /* Ejemplo de clases */ /* Llamar a la superclase */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Animal { } // -----------------public class Gato: Animal { public void Hablar() { Console.WriteLine("Miauuu"); } }
Revisin 0.97 Pgina 136
// -----------------public class GatoSiames: Gato { public new void Hablar() { base.Hablar(); Console.WriteLine("Pfff"); } } // -----------------public class Ejemplo67 { public static void Main() { Gato miGato = new Gato(); GatoSiames miGato2 = new GatoSiames(); miGato.Hablar(); Console.WriteLine(); miGato2.Hablar(); } } 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).
// Linea en blanco
Ejercicios propuestos: 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.
como nosotros puede necesitar. Una alternativa muy similar, pero algo ms sencilla (lo que supone que funcione ms rpido en ordenadores no demasiado potentes) es SharpDevelop. Vamos a ver las pautas bsicas de manejo de SharpDevelop (que se aplicaran con muy pocos cambios al caso de Visual Studio): Comenzamos por descargar el fichero de instalacin del entorno, desde su pgina oficial (http://www.icsharpcode.net/opensource/sd/). La versin 3.1, para las versiones 2.0 y 3.5 de la plataforma .Net, ocupa unos 15 Mb. La instalacin comenzar simplemente con hacer doble clic. 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:
La instalacin debera ser muy sencilla en Windows Vista y superiores, pero en Windows XP quiz necesite que instalemos la versin 3.5 de la plataforma .Net (se puede hacer gratuitamente desde su pgina oficial).
Cuando lanzamos 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", y 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" (en C#, claro). Deberemos escribir tambin el nombre., y aparecer un esqueleto de aplicacin que nosotros slo tendramos que completar:
Cuando hayamos terminado de realizar nuestros cambios, podemos compilar el programa con el botn:
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 / Nuevo Elemento:
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.
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.
Ejercicio propuesto: Crear un proyecto que contenga las siguientes clases (cada una en un fichero distinto): o Una clase Trabajador, cuyo constructor escriba en pantalla "Soy un trabajador". o Una clase Programador, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy programador". o Una clase Analista, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy analista". o Una clase Ingeniero, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy ingeniero".
Revisin 0.97 Pgina 143
o o
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.
7. Manejo de ficheros
7.1. Escritura en un fichero de texto
Para manejar ficheros, siempre deberemos realizar tres operaciones bsicas: Abrir el fichero. Leer datos de l o escribir datos en l. Cerrar el fichero.
Eso s, no siempre podremos 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 queramos 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 en C# n 70 */ /* ejemplo70.cs */ /* */ /* Escritura en un fichero */ /* de texto */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO;
// Para StreamWriter
public class Ejemplo70 { 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 en el que podemos escribir. El fichero de texto lo creamos con el mtodo CreateText, que pertenece a la clase File. Para escribir en el fichero, empleamos Write y WriteLine, al igual que en la consola.
Revisin 0.97 Pgina 145
Finalmente, debemos cerrar el fichero con Close, o podra quedar algn dato sin guardar.
Ejercicios propuestos: 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).
// Para StreamReader
public class Ejemplo71 { 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.
Ejercicios propuestos: Crea un programa que lea las tres primeras lneas del fichero creado en el apartado anterior y las muestre en pantalla.
// Para StreamReader
public class Ejemplo72 { public static void Main() { StreamReader fichero; string linea; fichero = File.OpenText("prueba.txt"); do { linea = fichero.ReadLine(); if (linea != null) Console.WriteLine( linea ); } while (linea != null); fichero.Close(); } }
Ejercicios propuestos: Un programa que pida al usuario que teclee frases, y las almacene en el fichero "frases.txt". Acabar cuando el usuario pulse Intro sin teclear nada. Despus deber mostrar el contenido del fichero. Un programa que pregunte un nombre de fichero y muestre en pantalla el contenido de ese fichero, haciendo una pausa despus de cada 25 lneas, para que d tiempo a leerlo. Cuando el usuario pulse intro, se mostrarn las siguientes 25 lneas, y as hasta que termine el fichero.
// Para StreamWriter
public class Ejemplo73 { 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(); } } Ejercicios propuestos: Un programa que pida al usuario que teclee frases, y las almacene en el fichero "registro.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.
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: Crear 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 que se ejecute el programa, deber aadir los nuevos resultados a continuacin de los resultados de las ejecuciones anteriores.
{ public static void Main() { StreamReader fichero; string nombre; while (true) { Console.Write( "Dime el 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: Mejorar el segundo ejercicio del apartado 7.3 (el que muestra un fichero, haciendo una pausa cada 25 lneas) para que compruebe antes si el fichero existe, y muestre un mensaje de aviso en caso de que no sea 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 en C# n 75: */ /* ejemplo75.cs */ /* */ /* Excepciones y ficheros */ /* (1) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo75 { 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 ); } 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 Introduzca el nombre del fichero prueba Ha habido un error: No se pudo encontrar el archivo 'C:\Fuentes\nacho\prueba'.
Revisin 0.97 Pgina 151
Pero en general, 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, de forma que el mensaje de aviso sea ms concreto, o que podamos dar pasos ms adecuados para solucionar el problema: /*---------------------------*/ /* Ejemplo en C# n 76: */ /* ejemplo76.cs */ /* */ /* Excepciones y ficheros */ /* (2) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo76 { public static void Main() { StreamWriter fichero; string nombre; 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) {
Revisin 0.97 Pgina 152
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 convertir a Int32, pero el usuario ha tecleado slo texto).
Ejercicios propuestos: 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. 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. 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 posibles errores, como que los datos no sean numricos, la divisin entre cero, o que el fichero no se haya podido crear.
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 casete). 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.
} catch (Exception exp) { Console.WriteLine(exp.Message); return; } } } La forma de abrir un BinaryReader es algo ms incmoda que con los ficheros de texto, porque nos obliga a llamar a un constructor y a indicarle ciertas opciones sobre el modo de fichero (la habitual ser simplemente "abrirlo", con "FileMode.Open"), pero a cambio podemos leer cualquier tipo de dato, no slo texto: ReadByte lee un dato "byte", ReadInt32 lee un "int", ReadSingle lee un "float", ReadString lee un "string", etc.
Ejercicios propuestos: Abrir 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.
{ public static void Main() { FileStream fichero; string nombre; byte[] datos; int cantidadLeida; Console.WriteLine("Introduzca el nombre del fichero"); nombre = Console.ReadLine(); try { fichero = File.OpenRead(nombre); datos = new byte[10]; int posicion = 0; int cantidadALeer = 10; 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]); } fichero.Close(); } catch (Exception exp) { Console.WriteLine(exp.Message); return; } } } Ejercicios propuestos: Abrir 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. Se deben leer ambos bytes a la vez, usando un array.
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). De igual modo, podemos saber en qu posicin del fichero nos encontramos, consultando la propiedad "Position", as como la longitud del fichero, mirando su propiedad "Length", como en este ejemplo: /*---------------------------*/ /* Ejemplo en C# n 79: */ /* ejemplo79.cs */ /* */ /* Ficheros binarios (3) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo79 { public static void Main() { FileStream fichero; string nombre; byte[] datos; int cantidadLeida; Console.WriteLine("Introduzca el nombre del fichero"); nombre = Console.ReadLine(); try { fichero = File.OpenRead(nombre); datos = new byte[10]; int posicion = 0; int cantidadALeer = 10; 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}",
Revisin 0.97 Pgina 157
fichero.Length); } fichero.Close(); } catch (Exception exp) { Console.WriteLine(exp.Message); return; } } } (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).
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.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 exp) { Console.WriteLine(exp.Message); return; } } }
(Nota: existe una propiedad "CanWrite" que nos permite saber si se puede escribir en el fichero). 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 en C# n 81: */ /* ejemplo81.cs */ /* */ /* Ficheros binarios (5) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo81 { 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();
Revisin 0.97 Pgina 160
} catch (Exception exp) { Console.WriteLine(exp.Message); return; } // Ahora vamos a volver a leerlos Console.WriteLine("Leyendo fichero..."); 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 exp) { Console.WriteLine(exp.Message); return; } } } Como se puede ver en este ejemplo, 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: ficheroEntrada.BaseStream.Seek(1, SeekOrigin.Begin); 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
Revisin 0.97 Pgina 161
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: 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 realiza una bsqueda hasta el final del mismo, o crea un archivo nuevo si no existe.
Compresin (0=no comprimido) Tamao de la imagen Resolucin horizontal Resolucin vertical Tamao de la tabla de color Contador de colores importantes
Con esta informacin nos basta para nuestro propsito: la compresin se indica en la posicin 30 del fichero, es un entero de 4 bytes (lo mismo que un "int" en los sistemas operativos de 32 bits), y si es un 0 querr decir que la imagen no est comprimida. Como el bit menos significativo se almacena en primer lugar, nos podra bastar con leer slo el byte de la posicin 30, para ver si vale 0, y despreciar los 3 bytes siguientes. Entonces, lo podramos comprobar as: /*---------------------------*/ /* Ejemplo en C# n 82: */ /* ejemplo82.cs */ /* */ /* Ficheros binarios (6): */ /* Ver si un BMP est */ /* comprimido */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo82 { public static void Main() { FileStream fichero; string nombre; int compresion; Console.WriteLine("Comprobador de imgenes BMP\n"); Console.WriteLine("Dime el nombre del fichero: "); nombre = Console.ReadLine(); if (! File.Exists( nombre) ) { Console.WriteLine("No encontrado!"); } else { fichero = File.OpenRead(nombre); fichero.Seek(30, SeekOrigin.Begin); compresion = fichero.ReadByte(); fichero.Close(); if (compresion == 0)
Revisin 0.97 Pgina 163
Ya que estamos, podemos mejorarlo un poco para que adems nos muestre el ancho y el alto de la imagen, y que compruebe antes si realmente se trata de un fichero BMP: /*---------------------------*/ /* Ejemplo en C# n 83: */ /* ejemplo83.cs */ /* */ /* Ficheros binarios (7): */ /* Informacin de un */ /* fichero BMP */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo83 { public static void Main() { FileStream fichero; string nombre; int compresion, ancho, alto; char marca1, marca2; byte[] datosTemp = new byte[4];
Console.WriteLine("Comprobador de imgenes BMP\n"); Console.WriteLine("Dime el nombre del fichero: "); nombre = Console.ReadLine(); if (! File.Exists( nombre) ) { Console.WriteLine("No encontrado!"); } else { fichero = File.OpenRead(nombre); // Leo los dos primeros bytes marca1 = Convert.ToChar( fichero.ReadByte() ); marca2 = Convert.ToChar( fichero.ReadByte() ); if ((marca1 =='B') && (marca2 =='M')) { // Si son BM Console.WriteLine("Marca del fichero: {0}{1}", marca1, marca2); fichero.Seek(18, SeekOrigin.Begin); // Posicin 18: ancho
Revisin 0.97 Pgina 164
fichero.Read(datosTemp, 0, 4); ancho = datosTemp[0] + // Convierto 4 bytes a Int32 datosTemp[1] * 256 + datosTemp[2] * 256 * 256 + datosTemp[3] * 256 * 256 * 256; Console.WriteLine("Ancho: {0}", ancho); fichero.Read(datosTemp, 0, 4); alto = datosTemp[0] + // Convierto 4 bytes a Int32 datosTemp[1] * 256 + datosTemp[2] * 256 * 256 + datosTemp[3] * 256 * 256 * 256; Console.WriteLine("Alto: {0}", alto); fichero.Seek(4, SeekOrigin.Current); // 4 bytes despus: compresin compresion = fichero.ReadByte(); fichero.Close(); 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; } } else Console.WriteLine("No parece un fichero BMP\n"); // Si la marca no es BM } } } Tambin podemos hacer lo mismo empleando un "BinaryReader", en lugar de un "FileStream". En ese caso, se simplifica la lectura de datos de 32 bits, a cambio de complicarse ligeramente la apertura y los "Seek", como se ve en este ejemplo: /*---------------------------*/ /* Ejemplo en C# n 84: */ /* ejemplo84.cs */ /* */ /* Ficheros binarios (8): */ /* Informacin de un BMP */ /* con BinaryReader */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo84 { public static void Main() { BinaryReader fichero; string nombre; int compresion, ancho, alto; char marca1, marca2;
Console.WriteLine("Comprobador de imgenes BMP\n"); Console.WriteLine("Dime el nombre del fichero: "); nombre = Console.ReadLine(); if (! File.Exists( nombre) ) { Console.WriteLine("No encontrado!"); } else { fichero = new BinaryReader( File.Open(nombre, FileMode.Open)); // Leo los dos primeros bytes marca1 = Convert.ToChar( fichero.ReadByte() ); marca2 = Convert.ToChar( fichero.ReadByte() ); if ((marca1 =='B') && (marca2 =='M')) { // Si son BM Console.WriteLine("Marca del fichero: {0}{1}", marca1, marca2); fichero.BaseStream.Seek(18, SeekOrigin.Begin); ancho = fichero.ReadInt32(); Console.WriteLine("Ancho: {0}", ancho); alto = fichero.ReadInt32(); Console.WriteLine("Alto: {0}", alto); fichero.BaseStream.Seek(4, SeekOrigin.Current); // 4 bytes despus: compresin compresion = fichero.ReadInt32(); fichero.Close(); 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; } } else Console.WriteLine("No parece un fichero BMP\n"); // Si la marca no es BM } } } // Posicin 18: ancho
Ejercicios propuestos: 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. 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.
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 vieron en el apartado 7.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 en C# n 85: */ /* ejemplo85.cs */ /* */ /* Ficheros binarios (9): */ /* Lectura y Escritura */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.IO; public class Ejemplo85 { 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);
Revisin 0.97 Pgina 167
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 exp) { Console.WriteLine(exp.Message); return; } } } Ejercicios propuestos: 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. 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 frases 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). 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. Un programa que duplique un fichero, copiando todo su contenido a un nuevo fichero. El nombre de ambos ficheros se debe leer de la lnea de comandos. 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).
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 prefijado. Veremos ejemplos de cmo crear estructuras dinmicas de estos tipos en C#, y despus comentaremos los pasos para crear una estructura dinmica de forma "artesanal".
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:
Revisin 0.97 Pgina 170
yo soy Hola, La implementacin de una pila en C# es algo ms avanzada: permite 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.
Queue miCola = new Queue(); miCola.Enqueue("Hola,"); miCola.Enqueue("soy"); miCola.Enqueue("yo"); for (byte i=0; i<3; i++) { palabra = (string) miCola.Dequeue();
Revisin 0.97 Pgina 171
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.
8.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 bien ordenar toda la lista con "Sort". Vamos a ver un ejemplo de la mayora de sus posibilidades: /*---------------------------*/ /* Ejemplo en C# */ /* arrayList1.cs */ /* */ /* Ejemplo de ArrayList */
Revisin 0.97 Pgina 172
/* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Collections; public class ejemploArrayList1 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:"); 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 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
Revisin 0.97 Pgina 174
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 10, que estar dedicado a otras caractersticas avanzadas de C#.
8.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 (sabiendo en qu posicin se encuentra una clave con "IndexOfKey"), como en este ejemplo: /*---------------------------*/ /* Ejemplo en C# */ /* sortedList1.cs */ /* */ /* Ejemplo de SortedList: */ /* Diccionario esp-ing */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Collections; public class ejemploSortedList 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") )); } }
Su resultado sera Cantidad de palabras en el diccionario: 3 Lista de palabras y su significado: adis = good bye 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.
{ {
// Creamos e insertamos datos Hashtable miDiccio = new Hashtable(); miDiccio.Add("byte", "8 bits"); miDiccio.Add("pc", "personal computer");
Revisin 0.97 Pgina 176
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 e) { 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 en C# */ /* HashTable2.cs */ /* */ /* Ejemplo de HashTable 2: */ /* Diccionario de inform. */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Collections; public class ejemploHashTable2 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")) Console.WriteLine( "El significado de PC es: {0}", miDiccio["pc"]); else
Revisin 0.97 Pgina 177
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);
// Creamos e insertamos datos Hashtable miDiccio = new Hashtable(); miDiccio.Add("byte", "8 bits"); 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); }
Revisin 0.97 Pgina 178
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 en C# */ /* pila2.cs */ /* */ /* Ejemplo de clase "Stack" */ /* y enumerador */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Collections; public class ejemploPila1 { {
Stack miPila = new Stack(); miPila.Push("Hola,"); miPila.Push("soy"); miPila.Push("yo"); // Mostramos todos los datos Console.WriteLine("Contenido:"); IEnumerator miEnumerador = miPila.GetEnumerator(); while ( miEnumerador.MoveNext() ) Console.WriteLine("{0}", miEnumerador.Current); } } que escribira
Revisin 0.97 Pgina 179
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#
El fuente podra ser as: /*---------------------------*/ /* Ejemplo en C# */ /* pilaEstatica.cs */ /* */ /* Ejemplo de clase "Pila" */ /* basada en un array */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; using System.Collections; public class PilaString {
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 ); } } // 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; } } // Fin de la clase
Ejercicios propuestos: Usando esta misma estructura de programa, crear una clase "Cola", que permita introducir datos 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". 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").
Es decir, pondremos un asterisco entre el tipo de datos y el nombre de la variable. Ese asterisco puede ir junto a cualquiera de ambos, 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 = №
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
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); } public static void Main() pruebaPunteros(); } } La salida de este programa es:
Revisin 0.97 Pgina 183
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.
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: Incrementar(&i); } // Y mostramos el resultado Console.WriteLine (i); } }
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.
/*---------------------------*/ /* Ejemplo en C# */ /* unsafe4.cs */ /* */ /* Ejemplo de punteros (4) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class EjemploUnsafe4 {
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: Posicion actual: Posicion actual: Posicion actual: Posicion actual: Contenido: 0 1242196 1242200 1242204 1242208 1242212
10 20 30 40
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}", *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
namespace ConsoleApplication1 { class Program { static void Main(string[] args) { } } } Y esta es apariencia del fuente si usamos SharpDevelop 3: using System; namespace PruebaDeNamespaces { class Program { public static void Main(string[] args) { Console.WriteLine("Hello World!"); // TODO: Implement Functionality Here
Vamos a 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 en C# */ /* namespaces.cs */ /* */ /* Ejemplo de espacios de */ /* nombres */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; namespace ConsolaDeNacho { 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"); ConsolaDeNacho.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 "ConsolaDeNacho.Console". El resultado es que podemos tener dos clases Console accesibles desde el mismo programa, sin que existan conflictos entre ellas. El resultado del programa sera: Hola Mensaje: Hola otra vez
Resultado
Cambiar 0 por 1 y viceversa 1 slo si los 2 bits son 1 1 slo si uno de los bits es 1 1 slo si los 2 bits son distintos Desplaza y rellena con ceros
En C# Ejemplo ~ & | ^ << >> ~1100 = 0011 1101 & 1011 = 1001 1101 | 1011 = 1001 1101 ^ 1011 = 0110 1101 << 2 = 110100 1101 >> 2 = 0011
Ahora vamos a aplicarlo a un ejemplo completo en C#: /*---------------------------*/ /* Ejemplo en C# */ /* bits.cs */ /* */ /* Operaciones entre bits */ /* */ /* Introduccion a C#, */ /* 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); 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
Revisin 0.97 Pgina 191
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
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 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;
...
9.3. Enumeraciones
Cuando tenemos varias constantes, cuyos valores son nmeros enteros, hasta ahora estamos dando los valores uno por uno, as:
Revisin 0.97 Pgina 192
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 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 en C# */ /* enum.cs */ /* */ /* Ejemplo de enumeraciones */ /* */ /* Introduccion a C#, */ /* 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} ",
Revisin 0.97 Pgina 193
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). 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).
9.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 en C# */ /* propiedades.cs */ /* */ /* Ejemplo de propiedades */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class EjemploPropiedades { // ------------------------// Un atributo convencional, privado
Revisin 0.97 Pgina 194
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 { 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".
El ejemplo podra ser: 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); } 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)");
Revisin 0.97 Pgina 197
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")) 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
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 i i i i i vale vale vale vale vale vale 0 1 2 3 4 5 y y y y y y j j j j j j vale vale vale vale vale vale 0. 1. 2. 3. 4. 5.
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 "Bolean", y la coma no es un operador vlido para operaciones booleanas): for (i=0, j=1; i<=5, j<=30; i++, j+=2)
/*---------------------------*/ /* Ejemplo en C# */ /* consola.cs */ /* */ /* Ms posibilidades de */ /* "System.Console" */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class consola { public static void Main() { int posX, posY; Console.Title = "Ejemplo de consola"; Console.BackgroundColor = ConsoleColor.Green; Console.ForegroundColor = ConsoleColor.Black; Console.Clear(); posY = 10; // En la fila 10
Revisin 0.97 Pgina 201
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: si se prueba este fuente desde Mono, habr que compilar con "gmcs" en vez de con "mcs", para compilar usando la versin 2.x de la plataforma .Net, no la 1.x, que no tena estas posibilidades). 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)) 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
Teclado numrico adicional: NumPad0 (Tecla 0 del teclado numrico), NumPad1, NumPad2, NumPad3, NumPad4, NumPad5, NumPad6, NumPad7, NumPad8, NumPad9, Multiply (Tecla Multiplicacin), Add (Tecla Agregar), 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. 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"); o
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).
Debajo de la ventana de cdigo hay una pestaa llamada "Diseo", que nos permite acceder al diseador visual:
Para poder incluir botones y otros elementos visuales, debemos escoger la ventana de "Herramientas" en la parte inferior de la pantalla, y luego el panel "Windows Forms" en esta ventana:
Para incluir un botn, podemos hacer clic en el elemento "Button" del panel izquierdo, y luego hacer un clic en la parte de la ventana en la que queremos que aparezca. De igual modo, podramos aadir otros elementos. Por ejemplo, vamos a aadir 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. Las propiedades de cada uno de estos elementos aparecen en la parte derecha, en una nueva ventana. Estas propiedades las podremos cambiar directamente en ese panel, o bien desde cdigo. Algunas de esas 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. 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).
Si queremos que al pulsar el botn cambie el texto, tendremos que modificar el cdigo que corresponde al "evento" de pulsacin del botn. Lo conseguimos simplemente haciendo doble clic en el botn, y aparece este texto: void Button1Click(object sender, EventArgs e) {
Revisin 0.97 Pgina 206
} Dentro de ese mtodo escribiremos lo que queremos que ocurra al hacer clic en el botn. Por ejemplo, para que el texto de la etiqueta "label1" pase a ser "Hola", haramos: void Button1Click(object sender, EventArgs e) { label1.Text = "Hola"; }
Ejercicios propuestos: Un programa que muestre una ventana con 3 recuadros de texto (TextBox) y un botn "Calcular suma". Cuando se pulse el botn, en el tercer recuadro deber aparecer la suma de los nmeros introducidos en los dos primeros recuadros. Un programa que muestre una ventana con un recuadro de texto y 3 etiquetas. En el recuadro de texto se escribir un nmero (en sistema decimal). Cada vez que en el recuadro de texto se pulse una tecla, se mostrar en las 3 etiquetas de texto el equivalente de ese nmero en binario, octal y hexadecimal. Pista: en la ventana de propiedades, se deber escoger "Eventos", para ver las posibles acciones relacionadas con el TextBox.
La segunda variante es indicar adems qu botones queremos mostrar, y qu iconos de aviso: MessageBox.Show("Ese nombre no es vlido", "Aviso", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
Revisin 0.97 Pgina 207
Y la tercera variante permite indicar adems cual ser el botn por defecto: 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 (MessageBoxDefaultButton) por defecto 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.
Vamos a crear una ventana principal que tenga 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" (verdad), 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". Por ejemplo, podemos hacer que sea 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", as:
Para crear el segundo formulario (la ventana auxiliar), usamos la opcin "Agregar / Nuevo Elemento", del men "Proyecto":
En esta nueva ventana, entramos a la vista de diseo y aadimos dos casillas de texto (TextBox), con sus etiquetas aclaratorias (Label), y el botn de Aceptar:
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 GetNombre() { return textBox1.Text + " " + textBox2.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(); }
Revisin 0.97 Pgina 211
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(); textBox1.Text = ventanaIntro.GetNombre(); } El resultado sera una secuencia como esta:
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.
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 en la referencia en lnea (MSDN), por ejemplo en la pgina http://msdn.microsoft.com/es-es/library/system.drawing.graphics_methods.aspx
/*---------------------------*/ /* Ejemplo en C# */ /* fechas.cs */ /* */ /* Ejemplo bsico de */ /* manejo de fechas */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; class ejemploFecha { 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); } } Algunas de las propiedades ms tiles 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); DayOfYear (da del ao). Y para calcular nuevas fechas, podemos usar mtodos que generan un nuevo objeto DateTime, como: AddDays , AddHours, AddMilliseconds, AddMinutes, AddMonths, AddSeconds, AddHours, o bien un Add 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 en C# */ /* restarfechas.cs */ /* */ /* Ejemplo ampliado de */ /* manejo de fechas */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System;
Revisin 0.97 Pgina 214
class ejemploFecha { 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 7170 das Si lo quieres con decimales: 7170,69165165654 das Y si quieres ms detalles: 7170.16:35:58.7031250
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 "using", o bien usar la llamada completa: System.Threading.Thread.Sleep(100);
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: string miDirectorio = @"c:\"; string[] listaFicheros; listaFicheros = Directory.GetFiles(miDirectorio); foreach(string fich in listaFicheros) Console.WriteLine(fich); (la clase Directory est declarada en el espacio de nombres System.IO, por lo que deberemos aadirlo entre los "using" de nuestro programa).
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: 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 1005113344, creado 15/12/2008 12:00:09
proc.WaitForExit();
//Utilizamos la DLL
Revisin 0.97 Pgina 217
public class pruebaSQLite { 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=prueba.sqlite;Version=3;New=False;Compress=True;"); conexion.Open(); // Lanzamos la consulta y preparamos la estructura para leer datos string consulta = "select * from gente"; 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[1]); // Y los mostramos System.Console.WriteLine("Nombre: {0}, edad: {1}", nombre, edad); } // Finalmente, cerramos la conexion conexion.Close(); } } Deberemos compilar con la versin 2 (o superior) de la plataforma .Net. En Mono, esto supone emplear el compilador "gmcs" en vez de "mcs": gmcs pruebaSQLite.cs /r:System.Data.SQLite.dll Y para usarlo necesitaremos la versin 2 (o superior) de la plataforma .Net, o bien cargarlo a travs de Mono: mono pruebaSQLite.exe La 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. Otra alternativa es utilizar un fuente similar, que creara una base de datos y que introdujera en ella algn dato, lo que se podra hacer as: using System; using System.Data.SQLite; //Utilizamos la DLL public class pruebaSQLite2
Revisin 0.97 Pgina 218
{ 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=prueba.sqlite;Version=3;New=True;Compress=True;"); conexion.Open(); // Creamos la tabla string creacion = "CREATE TABLE gente " +"(codigo INT PRIMARY KEY, nombre VARCHAR(40), edad INT);"; SQLiteCommand cmd = new SQLiteCommand(creacion, conexion); cmd.ExecuteNonQuery(); // E insertamos dos datos string insercion = "INSERT INTO gente VALUES (1,'Juan',20);"; cmd = new SQLiteCommand(insercion, conexion); int cantidad = cmd.ExecuteNonQuery(); if (cantidad < 1) Console.WriteLine("No se ha podido insertar"); insercion = "INSERT INTO gente VALUES (2,'Pedro',19);"; 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."); } }
Nota: el primer ejemplo daba por sentando que en la tabla "gente" existan dos campos (o ms), de los cuales el primero era el "nombre" y el segundo era la "edad"; en este segundo ejemplo, estos son el segundo y el tercer dato, respectivamente, porque el primero es un "cdigo", incluido a modo de ejemplo de cmo se puede crear una clave primaria.
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 vers al final de este apartado.
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 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).
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: mcs sdl01.cs /r:Tao.Sdl.dll
Y si empleamos Visual Studio o SharpDevelop, tendremos que: Crear un proyecto de "aplicacin de consola". Reemplazar nuestro programa principal por ste. Copiar el fichero Tao.Sdl.Dll a la carpeta de fuentes, y aadirlo a las referencias del proyecto (normalmente, pulsando el botn derecho del ratn en la vista de clases del proyecto y escogiendo la opcin "Agregar referencia"). 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"). Compilar y probar.
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). No necesitaremos ninguna DLL adicional.
| 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); 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
Revisin 0.97 Pgina 223
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);
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.
Luego preparamos el tipo de letra que queremos usar, indicando a partir de qu fichero TTF y en qu tamao: // Un tipo de letra, en tamano 18 tipoDeLetra = SdlTtf.TTF_OpenFont("FreeSansBold.ttf", 18); if (tipoDeLetra == IntPtr.Zero) { System.Console.WriteLine("Tipo de letra inexistente: FreeSansBold.ttf!"); Environment.Exit(5); } A continuacin, podemos crear una imagen a partir de una frase: // Y creamos una imagen a partir de ese texto Sdl.SDL_Color colorAzulIntenso = new Sdl.SDL_Color(50, 50, 255); string texto = "Texto de ejemplo"; IntPtr textoComoImagen = SdlTtf.TTF_RenderText_Solid( tipoDeLetra, texto, colorAzulIntenso); if (textoComoImagen == IntPtr.Zero) { System.Console.WriteLine("No se puedo renderizar el texto"); Environment.Exit(6); } Y podramos dibujar esa imagen en cualquier parte de la pantalla, como cualquier otra imagen: // Escribimos el texto, como imagen origen = new Sdl.SDL_Rect(0,0,anchoPantalla,altoPantalla); dest = new Sdl.SDL_Rect(200,100,anchoPantalla,altoPantalla); Sdl.SDL_BlitSurface(textoComoImagen, ref origen, pantallaOculta, ref dest); Como esta forma de trabajar puede resultar engorrosa, dentro de poco lo mejoraremos, creando una clase "Fuente" que nos oculte todos estos detalles y nos permita escribir texto de forma sencilla.
Por tanto, crearemos esas funciones, junto con otras auxiliares para inicializar el sistema, dibujar una imagen en pantalla oculta, escribir un texto en pantalla oculta El resultado podra ser ste: /*---------------------------*/ /* Ejemplo en C# */ /* sdl05.cs */ /* */ /* Quinto acercamiento */ /* a SDL */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using Tao.Sdl; using System; // Para IntPtr (puntero: imgenes, etc) public class Juego { short anchoPantalla = 800; short altoPantalla = 600; bool terminado = false; short x = 400; short y = 300; short anchoImagen = 50; short altoImagen = 50; Sdl.SDL_Event suceso; int numkeys; byte[] teclas; IntPtr pantallaOculta; IntPtr tipoDeLetra; IntPtr imagen; void inicializar()
Revisin 0.97 Pgina 226
{ int bitsColor = 24; int flags = Sdl.SDL_HWSURFACE | Sdl.SDL_DOUBLEBUF | Sdl.SDL_ANYFORMAT | Sdl.SDL_FULLSCREEN; // Inicializamos SDL Sdl.SDL_Init(Sdl.SDL_INIT_EVERYTHING); pantallaOculta = Sdl.SDL_SetVideoMode( anchoPantalla, altoPantalla, bitsColor, flags); // Y SdlTTF, para escribir texto SdlTtf.TTF_Init(); // 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 imagen = SdlImage.IMG_Load("personaje.png"); if (imagen == IntPtr.Zero) { System.Console.WriteLine("Imagen inexistente: personaje.png!"); Environment.Exit(4); } // Y un tipo de letra, en tamano 18 tipoDeLetra = SdlTtf.TTF_OpenFont("FreeSansBold.ttf", 18); if (tipoDeLetra == IntPtr.Zero) { System.Console.WriteLine("Tipo de letra inexistente: FreeSansBold.ttf!"); Environment.Exit(5); } } void comprobarTeclas() { // 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; }
void comprobarColisiones() {
Revisin 0.97 Pgina 227
void moverElementos() { // Todavia no hay ningun elemento que se mueva solo } void dibujarElementos() { // Borramos pantalla Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0,0, anchoPantalla,altoPantalla); Sdl.SDL_FillRect(pantallaOculta, ref origen, 0); // Dibujamos la imagen en sus nuevas coordenadas dibujarImagenOculta(imagen,x,y, anchoImagen, altoImagen); // Escribimos el texto, como imagen escribirTextoOculta("Texto de ejemplo", /* Coordenadas */ 200,100, /* Colores */ 50, 50, 255, tipoDeLetra ); // Mostramos la pantalla oculta Sdl.SDL_Flip(pantallaOculta); }
void dibujarImagenOculta(IntPtr imagen, short x, short y, short ancho, short alto) { Sdl.SDL_Rect origen = new Sdl.SDL_Rect(0, 0, ancho, alto); Sdl.SDL_Rect dest = new Sdl.SDL_Rect(x, y, ancho, alto); Sdl.SDL_BlitSurface(imagen, ref origen, pantallaOculta, ref dest); }
void escribirTextoOculta(string texto, short x, short y, byte r, byte g, byte b, IntPtr fuente) { // Creamos una imagen a partir de ese texto Sdl.SDL_Color color = new Sdl.SDL_Color(r, g, b); IntPtr textoComoImagen = SdlTtf.TTF_RenderText_Solid( fuente, texto, color); if (textoComoImagen == IntPtr.Zero){ System.Console.WriteLine("No se puedo renderizar el texto"); Environment.Exit(6);
Revisin 0.97 Pgina 228
private 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() ); // Finalmente, cerramos SDL Sdl.SDL_Quit(); } }
Fuente, para representar un tipo de letra. Sonido, para permitir reproducir efectos sonoros y melodas, ya sea una vez o de forma continua.
Y esto podra simplificar el cuerpo del juego para que acabara siendo algo as: /*---------------------------*/ /* Ejemplo en C# */ /* sdl06.cs */ /* */ /* Sexto acercamiento */ /* a SDL */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using Tao.Sdl; public class Juego { short anchoPantalla = 800; short altoPantalla = 600; bool terminado = false; short x = 400; short y = 300; Fuente tipoDeLetra; ElemGrafico personaje; void inicializar() { bool pantallaCompleta = false; Hardware.Inicializar(800, 600, 24, pantallaCompleta); personaje = new ElemGrafico("personaje.png"); tipoDeLetra = new Fuente("FreeSansBold.ttf", 18); }
) ) ) ) )
// ni con obstaculos }
void dibujarElementos() { // Borramos pantalla Hardware.BorrarPantallaOculta(0,0,0); // Dibujamos el personaje y el texto personaje.DibujarOculta(x, y); Hardware.EscribirTextoOculta("Texto de ejemplo", /* Coordenadas */ 200,100, /* Colores */ 50, 50, 255, tipoDeLetra ); // Finalmente, mostramos la pantalla oculta Hardware.VisualizarOculta(); }
private 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() ); // Finalmente, cerramos SDL Sdl.SDL_Quit(); }
Revisin 0.97 Pgina 231
(Y cmo podran ser esas clases Hardware, Imagen, etc? Tienes detalles en un apndice al final de este texto).
(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 de descarga y anlisis de una web // Muestra las lneas que contienen "Pascal" 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; 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>
Revisin 0.97 Pgina 232
54: 204:
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 de envio y recepcin de frases a travs de la red using using using using using System; System.IO; // Para Stream System.Text; // Para Encoding System.Net; // Para Dns, IPAddress System.Net.Sockets; // Para NetworkStream
public class DescargarWeb { 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) { // Tratamos de hallar la primera IP que corresponde // a una direccin como "localhost" IPAddress direccionIP = Dns.Resolve(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... 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.
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:
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, y se vuelve a quedar parado, realzando con fondo amarillo la siguiente lnea que se analizar:
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:
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.
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); } }
Revisin 0.97 Pgina 239
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 x2 = 0 x1 x2 -3x = 0 2x2 -2 = 0 x1 = 1, x2 = -1 = 0, x2 = No existe (solucin nica) x1 = 3, x2 = 0 x1 = 1, x2 = -1
Estos casos de prueba se podran convertir en un programa como: using System; 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 ..."); SegundoGrado.Resolver((float)1, (float) 0, (float) -1, out soluc1, out soluc2); if ((soluc1 == 1) && (soluc2 == -1)) Console.WriteLine("OK"); else Console.WriteLine("Falla");
Console.Write("Probando x2 = 0 ..."); SegundoGrado.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 ..."); SegundoGrado.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 ..."); SegundoGrado.Resolver((float)2, (float)0, (float)-2, out soluc1, out soluc2);
Revisin 0.97 Pgina 240
if ((soluc1 == 1) && (soluc2 == -1)) Console.WriteLine("OK"); else Console.WriteLine("Falla"); } } El resultado de este programa es: Probando Probando Probando Probando Probando ecuaciones de segundo grado x2 - 1 = 0 ...OK x2 = 0 ...OK x2 -3x = 0 ...OK 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 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".
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.
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 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 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.
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#:
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
Revisin 0.97 Pgina 245
public class Personaje : ElemGrafico { // Mueve el personaje a la derecha, si es posible (sin obstculos) public void MoverDerecha() { (...) } (...) /// 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.
Ejercicios propuestos:
Revisin 0.97 Pgina 247
Cuntas letras se podran almacenar en una agenda electrnica que tenga 32 Kb de capacidad? 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? 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? 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?
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. 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 0000 0000 0000 0001 0010 0011 (binario) (binario) (binario) (binario) = = = = 0 1 2 3 (decimal) (decimal) (decimal) (decimal)
Revisin 0.97 Pgina 248
... 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). 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.
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.
000 010 020 030 040 050 060 070 080 090 100 110 120
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, 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 Unicode, 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.
En general, convertir un nmero binario al sistema decimal es fcil: lo expresamos como suma de potencias de 2 y sumamos: 0110 1101 (binario) = 0 2 7 + 1 2 6 + 1 2 5 + 0 2 4 + 1 2 3 + 1 2 2 + 0 2 1 + 1 2 0 = = 0 128 + 1 64 + 1 32 + 0 16 + 1 8 + 1 4 + 0 2 + 1 1 = 109 (decimal) 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)
Revisin 0.97 Pgina 251
1 / 2 = 0 (resto: 1) 1 / 1 = 1 (se termin). 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 0+0 = 0 01 = 0 0+1 = 1 10 = 0 1+0 = 1 11 = 1 1+1 = 10 (en decimal: 2)
Ejercicios propuestos: 1. Expresar en sistema binario los nmeros decimales 17, 101, 83, 45. 2. Expresar en sistema decimal los nmeros binarios de 8 bits: 01100110, 10110010, 11111111, 00101101 3. Sumar los nmeros 01100110+10110010, 11111111+00101101. Comprobar el resultado sumando los nmeros decimales obtenidos en el ejercicio anterior. 4. Multiplicar los nmeros binarios de 4 bits 01001011, 10010011. Comprobar el resultado convirtindolos a decimal.
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 254 = 3 8 2 + 7 8 1 + 6 8 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...
0
Ejercicios propuestos: 1. Expresar en sistema octal los nmeros decimales 17, 101, 83, 45. 2. Expresar en sistema octal los nmeros binarios de 8 bits: 01100110, 10110010, 11111111, 00101101 3. Expresar en el sistema binario los nmeros octales 171, 243, 105, 45. 4. Expresar en el sistema decimal los nmeros octales 162, 76, 241, 102.
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 254 = 15 16 1 + 14 16 o bien 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
Revisin 0.97 Pgina 254
0
54331 = 13 4096 + 4 256 + 3 16 + 11 1 o bien 254 = 13 16 3 + 4 16 2 + 3 16 1 + 11 16 es decir 54331 (decimal) = D43B (hexadecimal)
0
Ahora vamos a dar el paso inverso: convertir de hexadecimal a decimal, por ejemplo el nmero A2B5 A2B5 (hexadecimal) = 10 16 3 + 2 16 2 + 11 16 1 + 5 16 0 = 41653 El paso de hexadecimal a binario tambin es (relativamente) rpido, porque cada dgito hexadecimal equivale a una secuencia de 4 bits:
0 1 2 3 4 5 6 7 8 9 A B C D E F
(hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal) (hexadecimal)
= = = = = = = = = = = = = = = =
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
(decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal) (decimal)
= = = = = = = = = = = = = = = =
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
(binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (binario) (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: 1. Expresar en sistema hexadecimal los nmeros decimales 18, 131, 83, 245. 2. Expresar en sistema hexadecimal los nmeros binarios de 8 bits: 01100110, 10110010, 11111111, 00101101 3. Expresar en el sistema binario los nmeros hexadecimales 2F, 37, A0, 1A2. 4. Expresar en el sistema decimal los nmeros hexadecimales 1B2, 76, E1, 2A.
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 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 6 (decimal) = 0110
-3 = 1100 -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: convertir los nmeros decimales 13, 34, -13, -34 a sistema binario, usando complemento a uno para expresar los nmeros negativos. Calcular (en binario) el resultado de las operaciones 13+34, (-13)+(-34), 13+(-34) y comprobar 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 6 (decimal) = 0110
-3 = 1101 -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 salen bien, y no tiene problemas para expresar el nmero 0 (00000000). Ejercicio propuesto: convertir los nmeros decimales 13, 34, -13, -34 a sistema binario, usando complemento a dos para expresar los nmeros negativos. Calcular (en
binario) el resultado de las operaciones 13+34, (-13)+(-34), 13+(-34) y comprobar 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 "unsigned int" en MsDos podra tomar valores entre 0 y 65.535, mientras que un "int" (con signo) podra tomar valores entre +32.767 y 32.768.
Nosotros usaremos Visual Studio 2008 en su versin Express, que es la que se puede usar libremente ("gratis"), y, por tanto, es la ms razonablemente para principiantes, adems de que necesita un ordenador menos potente qu la versin profesional. Se puede descargar de: http://www.microsoft.com/express/downloads/#Visual_Studio_2008_Express_Downloads En concreto, en vez de descargar el paquete que llaman "Visual C#, y que es apenas un "descargador" para instalar con conexin a Internet, puede ser preferible descargar directamente la imagen ISO en espaol -Spanish- del DVD -900 Mb-, para poder instalar en cualquier ordenador y en cualquier momento, sin necesidad de estar conectado a Internet: http://www.microsoft.com/express/downloads/#2008-All Esa imagen ISO se deber grabar en DVD usando cualquer programa de grabacin de CD y DVD. Al introducir y ejecutar el DVD deber aparecer un men como ste:
E indicar si queremos algn componente adicional, como Silverlight (una alternativa al Flash de Adobe) o el gestor de bases de datos SQL Server, en su versin Express (para un uso de principiante, yo recomendara no instalar ninguno de los dos):
Y comenzar la instalacin en s:
Cuando lo arrancamos, posiblemente nos aparecer la pgina de inicio, con novedades propuestas por Microsoft:
Y para empezar un proyecto nuevo, deberamos ir al men Archivo y escoger la opcin "Nuevo Proyecto":
Revisin 0.97 Pgina 261
La mayora de nuestros proyectos de principiante seran aplicaciones de consola. Los programas con "ventanitas" como la que hicimos en el apartado 8.2 seran "aplicaciones de Windows Forms":
En la parte derecha de la pantalla aparecern las clases que forman nuestro proyecto (por ahora, slo "Program.cs", que es el esqueleto bsico, "Form1.cs", que representa nuestra primera ventana), y en la parte izquierda aparecer nuestra ventana vaca.
El "cuadro de herramientas", que nos permite colocar componentes visuales, como botones y casillas de texto, aparece "escondido" a la izquierda. Si hacemos un clic se despliega, y podemos fijarlo con el "pincho" que aparece en la esquina superior dercha, si no queremos que se vuelva a "esconder" automticamente.
Revisin 0.97 Pgina 262
A partir de ah, el manejo sera muy similar a lo que vimos de SharpDevelop en el apartado 8.2, con la diferencia de que no es necesario compilar para ver los errores, sino que se nos van indicando "al vuelo", a medida que tecleamos:
Y esta sera la ventana de trabajo, con nuestro formulario, la vista de clases a la derecha (solution explorer), el cuadro de herramientas a la izquierda (toolbox), y las propiedades de cada componente (properties) en la parte inferior derecha:
Revisiones de este texto hasta la fecha: 0.97, de 01-feb-10. 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. 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).
ndice alfabtico
r
-, 19 @, 63, 246
@ [
--, 55
!
!, 31 !=, 28 \, 62
\ ^
^, 191
#
#, 58
%
%, 19 %=, 56 { y }, 11, 27
{ |
&
&, 191 & (direccin de una variable, 182 &&, 31
|, 191 ||, 31
~
~, 125, 191
*
*, 19 * (punteros), 182 *=, 56
+
+, 19 ++, 42, 55 +=, 56
,
,, 199
< .
<, 28 <<, 191
.Net, 8
= /
/, 19 /*, 23 /**, 245 //, 23 ///, 245 /=, 56 =, 20 -=, 56 ==, 28
>
>, 28 >>, 191
:
: (goto), 49
A
Abs, 105 Acceso aleatorio, 154 Acceso secuencial, 154 Acos, 105 Add (ArrayList), 172 al azar, nmeros, 104
Revisin 0.97 Pgina 267
?
?, 34
Aleatorio, acceso, 154 aleatorios, nmeros, 104 algoritmo, 9 alto nivel, 6 and, 191 Aadir a un fichero, 148 apilar, 170 Aplicacin Windows, 203 Append, 162 AppendText, 148 Arco coseno, 105 Arco seno, 105 Arco tangente, 105 args, 109 Argumentos de un programa, 108 Aritmtica de punteros, 185 array, 66 Array de objetos, 128 ArrayList, 172 Arrays bidimensionales, 70 Arrays de struct, 74 arreglo, 66 ASCII, 250 Asignacin de valores, 20 Asignacin en un "if", 31 asignaciones mltiples, 56 Asin, 105 Atan, 105 Atan2, 105 atributo, 122 azar, 104
Ch
char, 36, 60
C
cifrar mensajes, 192 Cifras significativas, 57 clase, 112 class, 11, 112 Clear, 201 Close, 146 cdigo mquina, 6 Cdigos de formato, 58 cola, 171 Color de texto, 201 Coma fija, 56 Coma flotante, 56 Comentarios recomendaciones, 242 Comillas (escribir), 62 CommandLine, 217 Comparacin de cadenas, 81 CompareTo, 81 compiladores, 8 Compilar con mono, 17 Complemento, 191 Complemento a 1, 256 Complemento a 2, 256 Consola, 201 Console, 11, 201 ConsoleKeyInfo, 202 constantes, 192 constructor, 123 Contains, 171, 176, 177 ContainsKey, 177 ContainsValue, 176 continue, 47 Convert, 54 Convert.ToInt32, 24 Convertir a binario, 59 Convertir a hexadecimal, 59 Cos, 105 Coseno, 105 Coseno hiperblico, 105 Cosh, 105 Create, 158 CreateDirectory, 215 CreateNew, 162 CreateText, 145 Current, 156
B
BackgroundColor, 201 bajo nivel, 7 base, 136 Base numrica, 59 Bases de datos, 217 BaseStream, 161 BASIC, 6 Begin, 156 Binario, 59, 251, 258, 264 BinaryReader, 154 BinaryWriter, 160 Bloque de programa, 44 BMP, 162 bool, 64 Booleanos, 64 borrar la pantalla, 201 break, 36, 46 bucle de juego, 226 bucle sin fin, 43 Bucles, 39 Bucles anidados, 43 bug, 236 burbuja, 88 byte, 54, 247
D
DateTime, 104, 213 Day, 213 debug, 236 decimal, 57 Decimal (sistema de numeracin), 251 Declarar una variable, 20 Decremento, 55 default, 36
Revisin 0.97 Pgina 2
C
C, 6 C#, 7 Cadena modificable, 82 Cadenas de caracteres, 76 Cadenas de texto, 63 Carcter, 60
Depuracin, 236 Dequeue, 171 Desbordamiento, 19 Descomposicin modular, 93 Desplazamiento a la derecha, 191 Desplazamiento a la izquierda, 191 destructor, 125 diagrama de clases, 121 Diagramas de Chapin, 50 Diagramas de flujo, 32 Dibujo con Windows Forms, 212 Dinmica, memoria, 169 direccin de memoria, 182 Directorios, 215 DirectoryInfo, 215 Diseo modular de programas, 93 Distinto de, 28 Divisin, 19 do ... while, 40 Doble precisin, 57 Documentacin, 241 Dot Net Framework, 8 double, 57 Doxygen, 245 DrawLine, 213
Fecha y hora, 213 Fibonacci, 108 Fichero fsico, 153 Fichero lgico, 153 Ficheros, 145 Ficheros binarios, 154 Ficheros de texto, 145 Ficheros en directorio, 216 FIFO, 171 File.Exists, 149 FileAccess.ReadWrite, 167 FileMode.Open, 155 FileStream, 155 fin de fichero, 147 fixed, 187 float, 57 for, 42 foreach, 83 ForegroundColor, 201 Formatear nmeros, 58 fuente, 10 funcin de dispersin, 176 Funciones, 93 Funciones matemticas, 105 Funciones virtuales, 132
E
E, 106 ejecutable, 8 elevado a, 105 else, 29 Encapsulacin, 111 End, 157 Enqueue, 171 ensamblador, 7 ensambladores, 8 entero, 54 enteros negativos, 255 Entorno, 216 enum, 192 Enumeraciones, 192 enumeradores, 178 Environment, 217 Environment.Exit, 109 ERRORLEVEL, 109 escritura indentada, 28 Espacios de nombres, 189 Estructuras, 73 Estructuras alternativas, 26 Estructuras anidadas, 75 Estructuras de control, 26 Estructuras dinmicas, 169 Estructuras repetitivas, 39 Euclides, 108 Excepciones, 150 EXE, 8 Exists (ficheros), 149 Exp, 105 Exponencial, 105 Expresiones regulares, 196 get, 194 Get, 114 GetEnumerator, 178 GetFiles, 216 GetKey, 175 gigabyte, 247 gmcs, 202 goto, 49 goto case, 36
H
Herencia, 111 Hexadecimal, 59 Hora, 213
I
Identificadores, 22 if, 26 Igual a, 28 Incremento, 55 IndexOf, 78 IndexOfKey, 175 inseguro (bloque "unsafe"), 182 Insercin directa, 89 Insert, 79, 172 instante actual:, 104 int, 20, 54 intrprete, 8 interrumpir un programa, 109 Introduccin de datos, 24 IOException, 162 iterativa, 108
F
factorial, 106 false, 64 falso, 64
J
JavaDoc, 246
Revisin 0.97 Pgina 3
Juegos, 219
K
Key, 202 KeyAvailable, 201 KeyChar, 202 kilobyte, 247
nibble, 252 No, 31 not, 191 Notepad++, 18 Now, 213 null (fin de fichero), 147 Nmeros aleatorios, 104 nmeros enteros, 18 Nmeros reales, 56
L
LastIndexOf, 78 Lectura y escritura en un mismo fichero, 166 Length (cadenas), 77 Length (fichero), 157 lenguaje C, 6 lenguaje mquina, 6 LIFO, 170 lnea de comandos, 108 Lneas, dibujar, 212 lista, 172
O
O, 31 Objetos, 111 octal, 252 ocultacin de datos, 114 OpenOrCreate, 162 OpenRead, 155 OpenText, 146 OpenWrite, 158 Operaciones abreviadas, 56 Operaciones aritmticas, 19 Operaciones con bits, 191 operador coma, 199 Operador condicional, 34 Operadores lgicos, 31 Operadores relacionales, 28 or, 191 Ordenaciones simples, 88 OSVersion, 217 out, 196 override, 132
Ll
llaves, 11
L
Log, 105 Log10, 105 Logaritmo, 105 long, 54 Longitud de una cadena, 77
P M
Main, 11 mquina virtual, 8 matemticas, funciones, 105 Mayor que, 28 maysculas, 78 mcs, 17 megabyte, 247 Memoria dinmica, 169 Menor que, 28 MessageBox, 207 mtodos, 122 Microsoft, 258 Mientras (condicin repetitiva), 39 Millisecond, 104 Modificar parmetros, 101 Mdulo (resto de divisin), 19 Mono, 10, 12 Month, 213 Mostrar el valor de una variable, 21 MoveNext, 179 Multiplicacin, 19 Parmetros de Main, 108 Parmetros de salida, 196 Parmetros de una funcin, 95 parmetros por referencia, 102 parmetros por valor, 102 Pascal, 6 Paso a paso (depuracin), 236 pausa, 215 Peek, 171 Pi, 106 pila, 170 Polimorfismo, 111, 126 POO, 111 pop, 170 Posicin del cursor, 201 Posicin en el fichero, 156 postdecremento, 55 postincremento, 55 Potencia, 105 Pow, 105 Precedencia de los operadores, 19 predecremento, 55 preincremento, 55 Prioridad de los operadores, 19 private, 118 Process.Start, 216 Producto lgico, 191 programa, 6 Programacin orientada a objetos, 111 Propiedades, 194 protected, 118 Prueba de programas, 239
Revisin 0.97 Pgina 4
N
n, 61 namespace, 189 Negacin, 19 Net, 8 new (arrays), 66 new (redefinir mtodos), 118
Pseudocdigo, 9 public, 11, 117 public (struct), 73 Punteros, 169, 182 Punto Net, 8 Puntos de ruptura, 238 push, 170
Q
Queue, 171
R
Raz cuadrada, 106 Random, 104 Rango de valores (enteros), 54 Read (FileStream), 155 ReadByte, 155, 156 Readkey, 201 ReadLine, 24 ReadLine (fichero), 146 ReadString, 155 real (tipo de datos), 56 recolector de basura, 187 Recursividad, 106 Redondear un nmero, 105 ref, 102, 196 Registro, 153 Registros, 73 Remove, 79 Replace, 79 Reserva de espacio, 185 Resta, 19 Resto de la divisin, 19 return, 96 Round, 105 rutas, 149
Sobrecarga de operadores, 139 Sort, 172 SortedList, 175 Split, 80 SQLite, 217 Sqrt, 106 Stack, 170 stackalloc, 185 static, 122 StreamReader, 146 StreamWriter, 145 string, 38, 63 String.Compare, 81 StringBuilder, 81, 82 struct, 73 struct anidados, 75 Subcadena, 78 Substring, 78 Suma, 19 Suma exclusiva, 191 Suma lgica, 191 switch, 35 System, 11 System.Drawing, 212
T
tabla, 66 Tablas bidimensionales, 70 tablas hash, 176 Tan, 106 Tangente, 106 Tanh, 106 Teclado, 201 Temporizacin, 213 terabyte, 247 this, 138 Thread, 215 Thread.Sleep, 215 Tipo de datos carcter, 60 Tipos de datos enteros, 54 Title, 201 ToInt32, 24, 54 ToLower, 78 ToString, 58 ToUpper, 78 true, 64 Truncate, 162 try, 150
S
sangrado, 28 sbyte, 54 SDL, 219 secuencial, 154 Secuencias de escape, 61 Seek, 156 SeekOrigin, 156 Seleccin directa, 88 Seno, 106 Sentencias compuestas, 27 set, 194 Set, 114 SetByIndex, 176 SetCursorPosition, 201 SharpDevelop, 140, 203 short, 54 si no, 29 Signo y magnitud, 256 Simple precisin, 57 Sin, 106 Sistema binario, 251, 258, 264 Sistema de numeracin, 59 Sistema decimal, 251 Sleep, 215 Sobrecarga, 126
U
uint, 54 ulong, 54 UML, 121 Unicode, 250 unsafe, 182 ushort, 54 using, 24
V
Valor absoluto, 105 Valor devuelto por una funcin, 96 Variables, 20 Variables globales, 98
Revisin 0.97 Pgina 5
Variables locales, 98 vector, 66 verdadero, 64 virtual, 132 Visibilidad (POO), 117, 138 Visual C#, 258 Visual Studio, 258 void, 93
Write (BinaryWriter), 160 Write (FileStream), 158 WriteByte, 158 WriteLine, 11 WriteLine (ficheros), 145
X
xor, 191
W
WaitForExit, 216 while, 39 Windows Forms, 203 Write, 35
Y
Y, 31 Year, 213