C Sharp
C Sharp
! " #
$ %&
%" '
' (
) * + #
, - " .
! "
# $
- /
0 %
1 .2 )
%
& '
# () (*) +) +*) **) ,* -
.
" //) 00) , #
% 1 " " 2 2 #
' " 3 #
- 4 #
5 6 #
#
56 #
56
#
3 4. )
) 3 ./ 5 " )0
, 3 )#
'+ / 1 %6 ,
0 7/ 67 ,
2 " ,
8 + ,)
# 7
# 8
# # &
# 9* %
8 ,
# : & '
# $ '
# # ; -
8 +
## : < & %
) 8 $
, 7 7 )
! "# " $
) % +
&& %%
= & %'
# > %-
%
) 8 0
) 0
# & '#
# ! & '
## 1 '
)) % 0
? '%
8 @ ''
# ? " ''
1A '-
B '-
% '-
' 8 -
- C : "B -
> 2 62 -#
), )
) *
% & '
, 1 9 .1 " #
, % + / #
, ! + / " #,
,) : / " 7 7 #
,, : #
, /
,0 ' / +
,
,# ; / ( )
D
; E
# F 6 & 6 E G %
, 2
, ! + 7' 7
( )
<! = >
* %&
6 : 0
) <%" 9 >
, 7 7
%
0 ! /
Revisión 0.97 – Página 3
Introducción a la programación con C#, por Nacho Cabanes
*
# ;
? 7 7
7 7
7 6 7.
3 #
) ! / #
* + ) & ,
0 / 6 $ ),
0 / 6 $ )
0 6 / / 6 )0
0 ) ;9 / 6 $ )
0, ? 6 )#
0 3 / 6 $ )#
0 0 '+ . $ ,
0 % + / 6 ,
0# + / 6 ,)
0 = / 6 ,,
0 " / 6 ,
0 / 6 ,
0 . / " / 6 @'!
0 ) / 6
- ( " '
<! = + > #
A %& 0
A %& 0
) 0
- ! &? '
- : ? '
, 7 6 67 0
7 7 0
0 %" 7 7 7 7
%&
- - HI J 4 -
-- K 2 " 2 -
--#C E -#
-- K " -
-- > L -
-- ! J -
--%? 2 A 2 -'
' . / 0 1 -'
# #
# * #
# #
#) ! #)
#, ! + B C #
Revisión 0.97 – Página 4
Introducción a la programación con C#, por Nacho Cabanes
# - " $ #
#0 ##
# =
! &
'+ 7 7
D + +/
A / 0
) A " #
, 1 E F ?
? 6 6 8 5 "
0 ,
# 1 7 7 0
; 3G 0
H 8 31 #
$ " E
C " @
# 1 A
E" FDM & NFM
C E 2 3 " 2 %
% = A
;
2 # "
% + "
1 :3 $
! #
) 1 " + )
8 3 "
M "
! 3 4 " *
; 4 )0
; A / + B C. )
! 3 !5 %%
! 3 5
; 3 ,
; 3 ,
; 3 6 $ ,)
; ) 2 " ,,
! 3 % 6 5 -
; ) : 3 $ ,
; ) : 3 $ )
7 & 3 *
El problema es que los lenguajes que realmente entienden los ordenadores resultan difíciles
para nosotros, porque son muy distintos de los que nosotros empleamos habitualmente para
hablar. Escribir programas en el lenguaje que utiliza internamente el ordenador (llamado
" " o "código máquina") es un trabajo duro, tanto a la hora de crear el
programa como (especialmente) en el momento de corregir algún fallo o mejorar lo que se
hizo.
Por ejemplo, un programa que simplemente guardara un valor "2" en la posición de memoria 1
de un ordenador sencillo, con una arquitectura propia de los años 80, basada en el procesador
Z80 de 8 bits, sería así en código máquina:
Prácticamente ilegible. Por eso, en la práctica se emplean lenguajes más parecidos al lenguaje
humano, llamados "lenguajes de ". Normalmente, estos son muy parecidos al idioma
inglés, aunque siguen unas reglas mucho más estrictas.
Vamos a ver en primer lugar algún ejemplo de lenguaje de alto nivel, para después comparar
con lenguajes de bajo nivel, que son los más cercanos al ordenador.
Uno de los lenguajes de alto nivel más sencillos es el lenguaje BASIC. En este lenguaje, escribir
el texto Hola en pantalla, sería tan sencillo como usar la orden
Otros lenguajes, como Pascal, nos obligan a ser algo más estrictos, pero a cambio hacen más
fácil descubrir errores (ya veremos por qué):
"# $%
& '
( ) * +
Por el contrario, los lenguajes de son más cercanos al ordenador que a los
lenguajes humanos. Eso hace que sean más difíciles de aprender y también que los fallos sean
más difíciles de descubrir y corregir, a cambio de que podemos optimizar al máximo la
velocidad (si sabemos cómo), e incluso llegar a un nivel de control del ordenador que a veces
no se puede alcanzar con otros lenguajes. Por ejemplo, escribir Hola en lenguaje ensamblador
de un ordenador equipado con el sistema operativo MsDos y con un procesador de la familia
Intel x86 sería algo como
- %$$
. , / $ $ 0
2
!! . ,
3%
4)$$
3%
Resulta bastante más difícil de seguir. Pero eso todavía no es lo que el ordenador entiende,
aunque tiene una equivalencia casi directa. Lo que el ordenador realmente es capaz de
comprender son secuencias de ceros y unos. Por ejemplo, las órdenes "mov ds, ax" y "mov ah,
9" (en cuyo significado no vamos a entrar) se convertirían a lo siguiente:
(Nota: los colores de los ejemplos anteriores son una ayuda que nos dan algunos entornos de
programación, para que nos sea más fácil descubrir errores).
Revisión 0.97 – Página 7
Introducción a la programación con C#, por Nacho Cabanes
Está claro entonces que las órdenes que nosotros hemos escrito (lo que se conoce como
"programa ") deben convertirse a lo que el ordenador comprende (obteniendo el
"programa ").
Si elegimos un lenguaje de bajo nivel, como el ensamblador (en inglés Assembly, abreviado
como Asm), la traducción es sencilla, y de hacer esa traducción se encargan unas herramientas
llamadas (en inglés Assembler).
Cuando el lenguaje que hemos empleado es de alto nivel, la traducción es más complicada, y a
veces implicará también recopilar varios fuentes distintos o incluir posibilidades que se encuenD
tran en otras bibliotecas que no hemos preparado nosotros. Las herramientas encargadas de
realizar todo esto son los .
Por ejemplo, en el caso de Windows (y de MsDos), y del programa que nos saluda en lenguaje
Pascal, tendríamos un fichero fuente llamado SALUDO.PAS. Este fichero no serviría de nada en
un ordenador que no tuviera un compilador de Pascal. En cambio, después de compilarlo
obtendríamos un fichero SALUDO.EXE, capaz de funcionar en cualquier otro ordenador que
tuviera el mismo sistema operativo, aunque no tenga un compilador de Pascal instalado.
Hoy en día existe algo que parece intermedio entre un compilador y un intérprete. Existen
lenguajes que no se compilan a un ejecutable para un ordenador concreto, sino a un ejecutable
"genérico", que es capaz de funcionar en distintos tipos de ordenadores, a condición de que en
ese ordenador exista una " " capaz de entender esos ejecutables genéricos.
Esta es la idea que se aplica en Java: los fuentes son ficheros de texto, con extensión ".java",
que se compilan a ficheros ".class". Estos ficheros ".class" se podrían llevar a cualquier
ordenador que tenga instalada una "máquina virtual Java" (las hay para la mayoría de sistemas
operativos). Esta misma idea se sigue en el lenguaje C#, que se apoya en una máquina virtual
llamada "Dot Net Framework" (algo así como "armazón punto net").
A pesar de que los lenguajes de alto nivel se acercan al lenguaje natural, que nosotros
empleamos, es habitual no usar ningún lenguaje de programación concreto cuando queremos
plantear los pasos necesarios para resolver un problema, sino emplear un lenguaje de
programación ficticio, no tan estricto, muchas veces escrito incluso en español. Este lenguaje
recibe el nombre de
Por ejemplo, un algoritmo que controlase los pagos que se realizan en una tienda con tarjeta de
crédito, escrito en pseudocódigo, podría ser:
+ / , 5 #
) /
( 6 7 ( # 8
7 ,
) / . 6
. 6 9 .
: &
;
<
<
<
Se trata de un lenguaje creado por Microsoft para crear programas para su plataforma .NET,
pero estandarizado posteriormente por ECMA y por ISO, y del que existe una implementación
alternativa de "código abierto", el "proyecto Mono", que está disponible para Windows, Linux,
Mac OS X y otros sistemas operativos.
Nosotros comenzaremos por usar Mono como plataforma de desarrollo durante los primeros
temas. Cuando los conceptos básicos estén asentados, pasaremos a emplear Visual C#, de
Microsoft, que requiere un ordenador más potente pero a cambio incluye un entorno de
desarrollo muy avanzado, y está disponible también en una versión gratuita (Visual Studio
Express Edition).
La mayoría de los compiladores actuales permiten dar todos estos pasos desde un único
, en el que escribimos nuestros programas, los compilamos, y los depuramos en caso
de que exista algún fallo.
!"
Vamos con un primer ejemplo de programa en C#, posiblemente el más sencillo de los que
"hacen algo útil". Se trata de escribir un texto en pantalla. La apariencia de este programa la
vimos en el tema anterior. Vamos a verlo ahora con más detalle:
"# $%
& '
( ) * +
Esto escribe "Hola" en la pantalla. Pero hay mucho alrededor de ese "Hola", y vamos a
comentarlo antes de proseguir, aunque muchos de los detalles se irán aclarando más adelante.
En este primer análisis, iremos de dentro hacia fuera:
Como se puede ver, mucha parte de este programa todavía es casi un "acto de fe" para
nosotros. Debemos creernos que "se debe hacer así". Poco a poco iremos detallando el por qué
de "public", de "static", de "void", de "class"... Por ahora nos limitaremos a "rellenar" el cuerpo
del programa para entender los conceptos básicos de programación.
"# $%
& ' ( ) * +
"# $%
& '
( ) * +
! #
Como ya hemos comentado, usaremos Mono como plataforma de desarrollo para nuestros
primeros programas. Por eso, vamos a comenzar por ver dónde encontrar esta herramienta,
cómo instalarla y cómo utilizarla.
8==>>> ? # =
En la parte superior derecha aparece el enlace para descargar ("download now"), que nos lleva
a una nueva página en la que debemos elegir la plataforma para la que queremos nuestro
Mono. Nosotros descargaremos la versión más reciente para Windows (la 2.6.7 en el momento
de escribir este texto).
Se trata de un fichero de algo más de 70 Mb. Cuando termina la descarga, hacemos doble clic
en el fichero recibido y comienza la instalación, en la que primero se nos muestra el mensaje de
bienvenida:
Revisión 0.97 – Página 12
Introducción a la programación con C#, por Nacho Cabanes
Después se nos muestra una ventana de información, en la que se nos avisa de que se va a
instalar Mono 1.9.1, junto con las librerías Gtk# para creación de interfaces de usuario y XSP
(eXtensible Server Pages, un servidor web).
A continuación se nos pregunta en qué carpeta queremos instalar. Como es habitual, se nos
propone que sea dentro de "Archivos de programa":
El siguiente paso es elegir qué componentes queremos instalar (Mono, Gtk#, XSP):
El siguiente paso es indicar en qué carpeta del menú de Inicio queremos que quede accesible:
Mono está listo para usar. En nuestro menú de Inicio deberíamos tener una nueva carpeta
llamada "Mono 1.9.1 for Windows", y dentro de ella un acceso a "MonoD1.9.1 Command
Prompt":
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:
# $%
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 habíamos visto anteriormente:
Guardamos los cambios, salimos del "Bloc de notas" y nos volvemos a encontrar en la pantalla
negra del símbolo del sistema. Nuestro fuente ya está escrito. El siguiente paso es compilarlo.
Para eso, tecleamos
# $%
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, podríamos lanzar el programa tecleando
# $%
Si en nuestro ordenador está instalado el "Dot Net Framework" (algo que debería ser cierto en
las últimas versiones de Windows), no debería hacer falta decir que queremos que sea Mono
quien lance nuestro programa, y podremos ejecutarlo directamente con su nombre:
# $%
! Si quieres un editor más potente que el Bloc de notas de Windows, puedes probar
Notepad++, que es gratuito (realmente más que eso: es de "código abierto") y podrás localizar
fácilmente en Internet. Y si prefieres un entorno desde el que puedas teclear, compilar y probar
tus programas, incluso los de gran tamaño que estén 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 Apéndice 4.
# $
Cuando queremos escribir un texto "tal cual", como en el ejemplo anterior, lo encerramos entre
comillas. Pero no siempre querremos escribir textos prefijados. En muchos casos, se tratará de
algo que habrá que calcular.
"# $%
& '
( ) * + @ 4
% & '
Está claro que el símbolo de la suma será un +, y podemos esperar cual será el de la resta,
pero alguna de las operaciones matemáticas habituales tiene símbolos menos intuitivos.
Veamos cuales son los más importantes:
A
? B , 7
C ' 7
= ; & 7
D & 7 7
Sencillo:
En primer lugar se realizarán las operaciones indicadas entre paréntesis.
Luego la negación.
Después las multiplicaciones, divisiones y el resto de la división.
Finalmente, las sumas y las restas.
En caso de tener igual prioridad, se analizan de izquierda a derecha.
El espacio del que disponemos para almacenar los números es limitado. Si el resultado de una
operación es un número "demasiado grande", obtendremos un mensaje de error o un resultado
erróneo. Por eso en los primeros ejemplos usaremos números pequeños. Más adelante veremos
a qué se debe realmente este problema y cómo evitarlo. Como anticipo, el siguiente programa
ni siquiera compila, porque el compilador sabe que el resultado va a ser "demasiado grande":
"# $%
& '
( ) * + %$$$$$$$!%$$$$$$$
( ) *
Las son algo que no contiene un valor predeterminado, un espacio de memoria al
que nosotros asignamos un nombre y en el que podremos almacenar datos.
El primer ejemplo nos permitía escribir "Hola". El segundo nos permitía sumar dos números que
habíamos prefijado en nuestro programa. Pero esto tampoco es "lo habitual", sino que esos
números dependerán de valores que haya tecleado el usuario o de cálculos anteriores.
Por eso necesitaremos usar variables, zonas de memoria en las que guardemos los datos con
los que vamos a trabajar y también los resultados temporales. Como primer ejemplo, vamos a
ver lo que haríamos para sumar dos números enteros que fijásemos en el programa.
! " #
Para usar una cierta variable primero hay que : indicar su nombre y el tipo de datos
que querremos guardar.
El primer tipo de datos que usaremos serán números enteros (sin decimales), que se indican
con "int" (abreviatura del inglés "integer"). Después de esta palabra se indica el nombre que
tendrá la variable:
Esa orden reserva espacio para almacenar un número entero, que podrá tomar distintos
valores, y al que nos referiremos con el nombre "primerNumero".
$ !
Podemos darle un valor a esa variable durante el programa haciendo
" 3@4
O también podemos darles un valor inicial ("inicializarlas") antes de que empiece el programa,
en el mismo momento en que las definimos:
" 3@4
(esta línea reserva espacio para dos variables, que usaremos para almacenar números enteros;
una de ellas se llama primerNumero y tiene como valor inicial 234 y la otra se llama
segundoNumero y tiene como valor inicial 567).
Después ya podemos hacer operaciones con las variables, igual que las hacíamos con los
números:
" ,
% & ! !
Una vez que sabemos cómo mostrar un número en pantalla, es sencillo mostrar el valor de una
variable. Para un número hacíamos cosas como
( ) * + @ 4
( ) * +
O bien, si queremos mostrar un texto además del valor de la variable, podemos indicar el texto
entre comillas, detallando con {0} en qué parte del texto queremos que aparezca el valor de la
variable, de la siguiente forma:
( ) * + + H$I
Si se trata de más de una variable, indicaremos todas ellas tras el texto, y detallaremos dónde
debe aparecer cada una de ellas, usando {0}, {1} y así sucesivamente:
Ya sabemos todo lo suficiente para crear nuestro programa que sume dos números usando
variables:
"# $3
& '
" 3@4
, " EFG
" ,
(Nos saltamos todavía los detalles de qué quieren decir "public", "class", "static" y
"void").
Main() indica donde comienza el cuerpo del programa, que se delimita entre llaves { y }
int primerNumero; reserva espacio para guardar un número entero, al que llamaremos
primerNumero.
int segundoNumero; reserva espacio para guardar otro número entero, al que
llamaremos segundoNumero.
int suma; reserva espacio para guardar un tercer número entero, al que llamaremos
suma.
primerNumero = 234; da el valor del primer número que queremos sumar
segundoNumero = 567; da el valor del segundo número que queremos sumar
suma = primerNumero + segundoNumero; halla la suma de esos dos números y la
guarda en otra variable, en vez de mostrarla directamente en pantalla.
System.Console.WriteLine("La suma de {0} y {1} es {2}", primerNumero,
segundoNumero, suma); muestra en pantalla el texto y los valores de las tres variables
(los dos números iniciales y su suma).
+ ) ,
Estos nombres de variable (lo que se conoce como " ") pueden estar formados
por letras, números o el símbolo de subrayado (_) y deben comenzar por letra o subrayado. No
deben tener espacios entre medias, y hay que recordar que las vocales acentuadas y la eñe son
problemáticas, porque no son letras "estándar" en todos los idiomas.
:L % L
'M ; &
De momento, intentaremos usar nombres de variables que a nosotros nos resulten claros, y
que no parezca que puedan ser alguna orden de C#.
9 $
9 $
o cualquier variación similar, el compilador protestará y nos dirá que no conoce esa variable,
porque la habíamos declarado como
- !
Podemos escribir comentarios, que el compilador ignora, pero que pueden servir para
aclararnos cosas a nosotros. Se escriben entre /* y */:
=C N , 5 & M C=
Es conveniente escribir comentarios que aclaren la misión de las partes de nuestros programas
que puedan resultar menos claras a simple vista. Incluso suele ser aconsejable que el
programa comience con un comentario, que nos recuerde qué hace el programa sin que
necesitemos mirarlo de arriba a abajo. Un ejemplo casi exagerado:
"# $3/
& '
" 3@4
, " EFG
=C O 5 & M C=
=C C=
" ,
=C P 5 & C=
( ) * + + H$I ( H%I H3I
,
=C "
N
M Q
C=
También es posible declarar otro tipo de comentarios, que comienzan con doble barra y
terminan cuando se acaba la línea (estos comentarios, claramente, no podrán ocupar más de
una línea). Son los "comentarios al estilo de C++":
== " )AA
. / *0
Si queremos que sea el usuario de nuestro programa quien teclee los valores, necesitamos una
nueva orden, que nos permita leer desde teclado. Pues bien, al igual que tenemos
System.Console.WriteLine ("escribir línea), también existe System.Console.ReadLine ("leer
línea"). Para leer textos, haríamos
6 " ( ) +
pero eso ocurrirá en el próximo tema, cuando veamos cómo manejar textos. De momento,
nosotros sólo sabemos manipular números enteros, así que deberemos convertir ese dato a un
número entero, usando Convert.ToInt32:
" ( ) & @3 ( ) +
Un ejemplo de programa que sume dos números tecleados por el usuario sería:
"# $@
& '
( ) * + K
" ( ) & @3
( ) +
( ) * + , K
, " ( ) & @3
( ) +
" ,
Va siendo hora de hacer una pequeña mejora: no es necesario repetir "System." al principio de
la mayoría de las órdenes que tienen que ver con el sistema (por ahora, las de consola y las de
conversión), si al principio del programa utilizamos "using System":
"# $4
& '
) * + K
" ) & @3 ) +
Revisión 0.97 – Página 24
Introducción a la programación con C#, por Nacho Cabanes
) * + , K
, " ) & @3 ) +
" ,
:
1. Multiplicar dos números tecleados por usuario.
2. El usuario tecleará dos números (x e y), y el programa deberá calcular cual es el
resultado de su división y el resto de esa división.
3. El usuario tecleará dos números (a y b), y el programa mostrará el resultado de la
operación (a+b)*(aDb) y el resultado de la operación a2Db2.
4. Sumar tres números tecleados por usuario.
5. Pedir al usuario un número y mostrar su tabla de multiplicar. Por ejemplo, si el número
es el 3, debería escribirse algo como
3x 0=0
3x 1=3
3x 2=6
…
3x 10 = 30
'
! 7
=C???????????????????????????C=
=C "# ) R E8 C=
=C # $E C=
=C C=
=C ) ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# $E
& '
) * + K
" ) & @3 ) +
# $$ ) * + " K &
Este programa pide un número al usuario. Si es positivo (mayor que 0), escribe en pantalla "El
número es positivo."; si es negativo o cero, no hace nada.
Como se ve en el ejemplo, para comprobar si un valor numérico es mayor que otro, usamos el
símbolo ">". Para ver si dos valores son iguales, usaremos dos símbolos de "igual": !
99$ . Las demás posibilidades las veremos algo más adelante. En todos los casos, la
condición que queremos comprobar deberá indicarse entre paréntesis.
Este programa comienza por un comentario que nos recuerda de qué se trata. Como nuestros
fuentes irán siendo cada vez más complejos, a partir de ahora incluiremos comentarios que nos
permitan recordar de un vistazo qué pretendíamos hacer.
Si la orden "if" es larga, se puede partir en dos líneas para que resulte más legible:
# $$
) * + " K &
Revisión 0.97 – Página 26
Introducción a la programación con C#, por Nacho Cabanes
:
Crear un programa que pida al usuario un número entero y diga si es par (pista: habrá
que comprobar si el resto que se obtiene al dividir entre dos es cero: if (x % 2 == 0)
…).
Crear un programa que pida al usuario dos números enteros y diga cual es el mayor de
ellos.
Crear un programa que pida al usuario dos números enteros y diga si el primero es
múltiplo del segundo (pista: igual que antes, habrá que ver si el resto de la división es
cero: a % b == 0).
(
Habíamos dicho que el formato básico de "if" es ! 7 Esa "sentencia"
que se ejecuta si se cumple la condición puede ser una sentencia simple o una compuesta. Las
sentencias se forman agrupando varias sentencias simples entre llaves ( { y } ),
como en este ejemplo:
=C???????????????????????????C=
=C "# ) R F8 C=
=C # $F C=
=C C=
=C ) ! 3 C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# $F
& '
) * + K
" ) & @3 ) +
# $ $
) * + " K &
) * + N / 5 , &
=C :N Q / ! C=
=C :N Q / ' C=
=C :N Q / "# $F C=
En este caso, si el número es positivo, se hacen dos cosas: escribir un texto y luego... ¡escribir
otro! (Claramente, en este ejemplo, esos dos "WriteLine" podrían ser uno solo, en el que los
dos textos estuvieran separados por un "\n"; más adelante iremos encontrando casos en lo que
necesitemos hacer cosas "más serias" dentro de una sentencia compuesta).
Como se ve en este ejemplo, cada nuevo "bloque" se suele escribir un poco más a la derecha
que los anteriores, para que sea fácil ver dónde comienza y termina cada sección de un
programa. Por ejemplo, el contenido de "Ejemplo06" está un poco más a la derecha que la
cabecera "public class Ejemplo06", y el contenido de "Main" algo más a la derecha, y la
sentencia compuesta que se debe realizar si se cumple la condición del "if" está algo más a la
derecha que la orden "if". Este " " del texto se suele llamar " ".
Un tamaño habitual para el sangrado es de 4 espacios, aunque en este texto muchas veces
usaremos sólo dos espacios, para no llegar al margen derecho del papel con demasiada
facilidad.
' N
' ( N
9 ' , N
9 ' ( , N
99 ,
S9 ,
=C???????????????????????????C=
=C "# ) R G8 C=
=C # $G C=
=C C=
=C ) ! @ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# $G
& '
) * + K
" ) & @3 ) +
# %" $
) * + " K
:
Crear un programa que multiplique dos números enteros de la siguiente forma: pedirá
al usuario un primer número entero. Si el número que se que teclee es 0, escribirá en
pantalla "El producto de 0 por cualquier número es 0". Si se ha tecleado un número
distinto de cero, se pedirá al usuario un segundo número y se mostrará el producto de
ambos.
Crear un programa que pida al usuario dos números reales. Si el segundo no es cero,
mostrará el resultado de dividir entre el primero y el segundo. Por el contrario, si el
segundo número es cero, escribirá “Error: No se puede dividir entre cero”.
.
Podemos indicar lo que queremos que ocurra en caso de que no se cumpla la condición, usando
la orden "else" (en caso contrario), así:
=C???????????????????????????C=
=C "# ) R T8 C=
=C # $T C=
=C C=
=C ) ! 4 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# $T
& '
) * + K
" ) & @3 ) +
# $ $
) * + " K &
) * + " K , &
Podríamos intentar evitar el uso de "else" si utilizamos un "if" a continuación de otro, así:
=C???????????????????????????C=
=C "# ) R 28 C=
=C # $2 C=
=C C=
=C ) ! E C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
Revisión 0.97 – Página 29
Introducción a la programación con C#, por Nacho Cabanes
"# $2
& '
) * + K
" ) & @3 ) +
# $ $
) * + " K &
# &" $
) * + " K , &
Podemos enlazar varios "if" usando "else", para decir "si no se cumple esta condición, mira a
ver si se cumple esta otra":
=C???????????????????????????C=
=C "# ) R %$8 C=
=C # %$ C=
=C C=
=C ) ! F C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %$
& '
) * + K
" ) & @3 ) +
# $ $
) * + " K &
# & $
) * + " K , &
) * + " K
:
• Mejorar la solución a los dos ejercicios del apartado anterior, usando "else".
' #
UU P
VV W
S
# ""% (( ""3
# ""% )) ""@
# % "" ) )) """ )
:
Crear un programa que pida una letra al usuario y diga si se trata de una vocal.
Crear un programa que pida al usuario dos números enteros y diga "Uno de los
números es positivo", "Los dos números son positivos" o bien "Ninguno de los números
es positivo", según corresponda.
Crear un programa que pida al usuario tres números reales y muestre cuál es el mayor
de los tres.
Crear un programa que pida al usuario dos números enteros cortos y diga si son iguales
o, en caso contrario, cuál es el mayor de ellos.
Crear un programa que pida al usuario su nombre, y le diga "Hola" si se llama "Juan", o
bien le diga "No te conozco" si teclea otro nombre.
1 ' 2 2
Cuidado con el operador de : hay que recordar que el formato es if (a==b) ... Si no
nos acordamos y escribimos if (a=b), estamos intentando asignar a "a" el valor de "b".
En algunos compiladores de lenguaje C, esto podría ser un problema serio, porque se considera
válido hacer una asignación dentro de un "if" (aunque la mayoría de compiladores modernos
nos avisarían de que quizá estemos asignando un valor sin pretenderlo, pero no es un "error"
sino un "aviso", lo que permite que se genere un ejecutable, y podríamos pasar por alto el
aviso, dando lugar a un funcionamiento incorrecto de nuestro programa).
En el caso del lenguaje C#, este riesgo no existe, porque la "condición" debe ser algo cuyo
resultado "verdadero" o "falso" (un dato de tipo "bool"), de modo que obtendríamos un error de
compilación "Cannot implicitly convert type 'int' to 'bool'" (no puedo convertir un "int" a "bool").
Es el caso del siguiente programa:
=C???????????????????????????C=
=C "# ) R %%8 C=
=C # %% C=
Revisión 0.97 – Página 31
Introducción a la programación con C#, por Nacho Cabanes
=C C=
=C ) ! G C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %%
& '
) * + K
" ) & @3 ) +
# " $
) * + " K
# & $
) * + " K , &
) * + " K &
3 4
A veces puede resultar difícil ver claro donde usar un "else" o qué instrucciones de las que
siguen a un "if" deben ir entre llaves y cuales no. Generalmente la dificultad está en el hecho de
intentar teclear directamente un programa en C#, en vez de pensar en el problema que se
pretende resolver.
Para ayudarnos a centrarnos en el problema, existen notaciones gráficas, como los diagramas
de flujo, que nos permiten ver mejor qué se debe hacer y cuando.
En primer lugar, vamos a ver los 4 elementos básicos de un diagrama de flujo, y luego los
aplicaremos a un caso concreto.
El inicio o el final del programa se indica dentro de un círculo. Los procesos internos, como
realizar operaciones, se encuadran en un rectángulo. Las entradas y salidas (escrituras en
pantalla y lecturas de teclado) se indican con un paralelogramo que tenga su lados superior e
inferior horizontales, pero no tenga verticales los otros dos. Las decisiones se indican dentro de
un rombo.
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 serán un "if", que si se cumple ejecutará la sentencia que aparece en su salida "si" y
si no se cumple ("else") ejecutará lo que aparezca en su salida "no".
!
Crear el diagrama de flujo y la versión en C# de un programa que dé al usuario tres
oportunidades para adivinar un número del 1 al 10.
Crear el diagrama de flujo para el programa que pide al usuario dos números y dice si
uno de ellos es positivo, si lo son los dos o si no lo es ninguno.
Crear el diagrama de flujo para el programa que pide tres números al usuario y dice
cuál es el mayor de los tres.
5 "6
En C# hay otra forma de asignar un valor según se cumpla una condición o no. Es el
" " " : que se usa
y equivale a decir "si se cumple la condición, toma el valor valor1; si no, toma el valor valor2".
Un ejemplo de cómo podríamos usarlo sería para calcular el mayor de dos números:
' ( " $/ * + /
# $ /
' ( "
' ( " /
=C???????????????????????????C=
=C "# ) R %38 C=
=C # %3 C=
=C C=
=C " C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %3
& '
/ (
) * " / K 8
" ) & @3 ) +
) * " / 8
/ " ) & @3 ) +
( " $/ * + /
) * + " ( K H$I (
(La orden Console.Write, empleada en el ejemplo anterior, escribe un texto sin avanzar a la
línea siguiente, de modo que el próximo texto que escribamos –o introduzcamosD quedará a
continuación de éste).
Un segundo ejemplo, que sume o reste dos números según la opción que se escoja, sería:
=C???????????????????????????C=
=C "# ) R %@8 C=
=C # %@ C=
=C C=
=C W ? 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %@
& '
) * " / K 8
" ) & @3 ) +
) * " / 8
/ " ) & @3 ) +
) * " / 7 % 9 9 8
" ) & @3 ) +
" "" % * / + /
) * + " H$I Y
!
• Crear un programa que use el operador condicional para mostrar un el valor absoluto
de un número de la siguiente forma: si el número es positivo, se mostrará tal cual; si es
negativo, se mostrará cambiado de signo.
• Usar el operador condicional para calcular el menor de dos números.
7 8
Si queremos ver , sería muy pesado tener que hacerlo con muchos
"if" seguidos o encadenados. La alternativa es la orden "switch", cuya sintaxis es
6 7
& %+ %
,
& 3+ 3
Revisión 0.97 – Página 35
Introducción a la programación con C#, por Nacho Cabanes
3/
,
& +
,
# +
Es decir, se escribe tras "switch" la expresión a analizar, entre paréntesis. Después, tras varias
órdenes "case" se indica cada uno de los valores posibles. Los pasos (porque pueden ser
varios) que se deben dar si se trata de ese valor se indican a continuación, terminando con
"break". Si hay que hacer algo en caso de que no se cumpla ninguna de las condiciones, se
detalla después de la palabra "default". Si dos casos tienen que hacer lo mismo, se añade "goto
case" a uno de ellos para indicarlo.
Vamos con un ejemplo, que diga si el símbolo que introduce el usuario es una cifra numérica,
un espacio u otro símbolo. Para ello usaremos un dato de tipo "char" (carácter), que veremos
con más detalle en el próximo tema. De momento nos basta que deberemos usar
Convert.ToChar si lo leemos desde teclado con ReadLine, y que le podemos dar un valor (o
compararlo) usando comillas simples:
=C???????????????????????????C=
=C "# ) R %48 C=
=C # %4 C=
=C C=
=C + > % C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %4
& '
) * +
" ) & ) ) +
+ ) * + "
,
% + $
3 + $
@ + $
4 + $
E + $
F + $
G + $
T + $
Revisión 0.97 – Página 36
Introducción a la programación con C#, por Nacho Cabanes
2 + $
$ + ) * + ;Q,
,
# + ) * + Q,
,
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 excepción 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 vacío:
=C???????????????????????????C=
=C "# ) R %4/8 C=
=C # %4/ C=
=C C=
=C + > %/ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %4/
'
) * +
" ) & ) ) +
+ ) * + "
,
% +
3 +
@ +
4 +
E +
F +
G +
T +
2 +
$ + ) * + ;Q,
,
# + ) * + Q,
,
En el lenguaje C, que es más antiguo, sólo se podía usar "switch" para comprobar valores de
variables "simples" (numéricas y caracteres); en C#, que es un lenguaje más evolucionado, se
puede usar también para comprobar valores de cadenas de texto ("strings").
Una cadena de texto, como veremos con más detalle en el próximo tema, se declara con la
palabra "string", se puede leer de teclado con ReadLine (sin necesidad de convertir) y se le
puede dar un valor desde programa si se indica entre comillas dobles. Por ejemplo, un
programa que nos salude de forma personalizada si somos "Juan" o "Pedro" podría ser:
=C???????????????????????????C=
=C "# ) R %E8 C=
=C # %E C=
=C C=
=C + > 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %E
& '
, /
) * + /
/ " ) +
Z + ) * + [ & B Z
,
+ ) * + \ B
,
# + ) * + B
,
!
Crear un programa que lea una letra tecleada por el usuario y diga si se trata de una
vocal, una cifra numérica o una consonante (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 puntuación, una cifra numérica o algún otro carácter.
Repetir los dos programas anteriores, empleando "if" en lugar de "switch".
Hemos visto cómo comprobar condiciones, pero no cómo hacer que una cierta parte de un
programa se repita un cierto número de veces o mientras se cumpla una condición (lo que
llamaremos un " "). En C# tenemos varias formas de conseguirlo.
78
Si queremos hacer que una sección de nuestro programa se repita mientras se cumpla una
cierta condición, usaremos la orden "while". Esta orden tiene dos formatos distintos, según
comprobemos la condición al principio o al final.
Un ejemplo que nos diga si cada número que tecleemos es positivo o negativo, y que pare
cuando tecleemos el número 0, podría ser:
=C???????????????????????????C=
=C "# ) R %F8 C=
=C # %F C=
=C C=
=C + > C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %F
& '
) * K $ 8
" ) & @3 ) +
%" $
# $ $ ) * + " &
) * + " , &
) * + K $ 8
" ) & @3 ) +
!
• Crear un programa que pida al usuario su contraseña. Deberá terminar cuando
introduzca como contraseña la palabra "clave", pero volvérsela a pedir tantas veces
como sea necesario.
• Crea un programa que escriba en pantalla los números del 1 al 10, usando "while".
• Crea un programa que escriba en pantalla los números pares del 26 al 10 (descenD
diendo), usando "while".
• Crear un programa calcule cuantas cifras tiene un número entero positivo (pista: se
puede hacer dividiendo varias veces entre 10).
78
Este es el otro formato que puede tener la orden "while": la condición se comprueba .
El punto en que comienza a repetirse se indica con la orden "do", así:
Al igual que en el caso anterior, si queremos que se repitan varias órdenes (es lo habitual),
deberemos encerrarlas entre llaves.
Como ejemplo, vamos a ver cómo sería el típico programa que nos pide una clave de acceso y
no nos deja entrar hasta que tecleemos la clave correcta:
=C???????????????????????????C=
=C "# ) R %G8 C=
=C # %G C=
=C C=
=C + > C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %G
& '
) * J & 5 8
& " ) & @3 ) +
) * + : Y
En este caso, se comprueba la condición al final, de modo que se nos preguntará la clave al
menos una vez. Mientras que la respuesta que demos no sea la correcta, se nos vuelve a
preguntar. Finalmente, cuando tecleamos la clave correcta, el ordenador escribe "Aceptada" y
termina el programa.
Si preferimos que la clave sea un texto en vez de un número, los cambios al programa son
mínimos, basta con usar "string":
=C???????????????????????????C=
=C "# ) R %T8 C=
=C # %T C=
=C C=
=C + > 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %T
& '
, & "
, &
) * J & 8
& " ) +
) * + : Y
!
• Crear un programa que pida números positivos al usuario, y vaya calculando la suma
de todos ellos (terminará cuando se teclea un número negativo o cero).
• Crea un programa que escriba en pantalla los números del 1 al 10, usando "do..while".
• Crea un programa que escriba en pantalla los números pares del 26 al 10 (descenD
diendo), usando "do..while".
• Crea un programa que pida al usuario su nombre y su contraseña, y no le permita
seguir hasta que introduzca como nombre "Pedro" y como contraseña "Peter".
%
Ésta es la orden que usaremos habitualmente para crear partes del programa que
un cierto número de veces. El formato de "for" es
# & ) 7 7
Así, para # #$, tendríamos 1 como valor inicial, <=10 como condición de
repetición, y el incremento sería de 1 en 1. Es muy habitual usar la letra "i" como contador,
cuando se trata de tareas muy sencillas, así que el valor inicial sería "i=1", la condición de
repetición sería "i<=10" y el incremento sería "i=i+1":
La orden para incrementar el valor de una variable ("i = i+1") se puede escribir de la forma
abreviada "i++", como veremos con más detalle en el próximo tema.
En general, será preferible usar nombres de variable más descriptivos que "i". Así, un programa
que escribiera los números del 1 al 10 podría ser:
=C???????????????????????????C=
=C "# ) R %28 C=
=C # %2 C=
=C C=
=C ] /M ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# %2
& '
# "% &"%$
) * H$I
!
Crear un programa que muestre los números del 15 al 5, descendiendo (pista: en cada
pasada habrá que descontar 1, por ejemplo haciendo 9 ?%, que se puede abreviar ??).
Crear un programa que muestre los primeros ocho números pares (pista: en cada
pasada habrá que aumentar de 2 en 2, o bien mostrar el doble del valor que hace de
contador).
En un "for", realmente, la parte que hemos llamado "Incremento" no tiene por qué incrementar
la variable, aunque ése es su uso más habitual. Es simplemente una orden que se ejecuta
cuando se termine la "Sentencia" y antes de volver a comprobar si todavía se cumple la
condición de repetición.
! 9% 9%$
Un caso todavía más exagerado de algo a lo que se entra y de lo que no se sale sería la
siguiente orden:
Los bucles "for" se pueden (incluir uno dentro de otro), de modo que podríamos escribir
las tablas de multiplicar del 1 al 5 con:
=C???????????????????????????C=
=C "# ) R 3$8 C=
=C # 3$ C=
=C C=
=C ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 3$
& '
# / "% / &"E /
# "% &"%$
En estos ejemplos que hemos visto, después de "for" había una única sentencia. Si queremos
que se hagan varias cosas, basta definirlas como un (una sentencia compuesta)
encerrándolas entre llaves. Por ejemplo, si queremos mejorar el ejemplo anterior haciendo que
deje una línea en blanco entre tabla y tabla, sería:
=C???????????????????????????C=
=C "# ) R 3%8 C=
=C # 3% C=
=C C=
=C ! 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 3%
& '
# / "% / &"E /
# "% &"%$
) * +
Para "contar" no necesariamente hay que usar . Por ejemplo, podemos contar con
letras así:
Revisión 0.97 – Página 44
Introducción a la programación con C#, por Nacho Cabanes
=C???????????????????????????C=
=C "# ) R 338 C=
=C # 33 C=
=C C=
=C ! N C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 33
& '
# " &" J
) * H$I
=C???????????????????????????C=
=C "# ) R 3@8 C=
=C # 3@ C=
=C C=
=C ! N C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 3@
& '
# " J $"
) * H$I
!
Crear un programa que muestre las letras de la Z (mayúscula) a la A (mayúscula,
descendiendo).
Crear un programa que escriba en pantalla la tabla de multiplicar del 5.
Crear un programa que escriba en pantalla los números del 1 al 50 que sean múltiplos
de 3 (pista: habrá que recorrer todos esos números y ver si el resto de la división entre
3 resulta 0).
1 2*
Podemos salir de un bucle "for" antes de tiempo con la orden " %":
=C???????????????????????????C=
=C "# ) R 348 C=
=C # 34 C=
=C C=
=C ! C=
=C / - C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 34
& '
# "% &"%$
# ""E
,
) * H$I
% 3 @ 4
(en cuanto se llega al valor 5, se interrumpe el "for", por lo que no se alcanza el valor 10).
% 1 *, 3
Podemos saltar alguna repetición de un bucle con la orden " ":
=C???????????????????????????C=
=C "# ) R 3E8 C=
=C # 3E C=
=C C=
=C ! C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 3E
& '
# "% &"%$
# ""E
) * H$I
% 3 @ 4 F G T 2 %$
:
¿Qué escribiría en pantalla este fragmento de código?
! 9% 4 AA ) * H$I B
Respuesta: los números del 1 al 3 (se empieza en 1 y se repite mientras sea menor que
4).
! 9% 4 AA ) * H$I B
Revisión 0.97 – Página 47
Introducción a la programación con C#, por Nacho Cabanes
! 9% 94 AA ) * H$I B
Respuesta: escribe un 5, porque hay un punto y coma después del "for", de modo que
repite cuatro veces una orden vacía, y cuando termina, "i" ya tiene el valor 5.
! 9% 4 ) * H$I B
! 9% AA ) * H$I B
! 9 $ 9 4 AA H
! 99 3
) * H$I B
I
! 9 $ 9 4 AA H
! 99 3 / -
) * H$I B
I
! 9 $ 9 4 AA H
! 99 %$
) * H$I B
Respuesta: escribe los números del 0 al 4, porque la condición del "continue" nunca se
llega a dar.
! 9 $ 9 4 AA
! 99 3
) * H$I B
Respuesta: escribe 5, porque no hay llaves tras el "for", luego sólo se repite la orden
"if".
( 1
El lenguaje C# también permite la orden " ", para hacer saltos incondicionales. Su uso
indisciplinado está muy mal visto, porque puede ayudar a hacer programas llenos de saltos,
difíciles de seguir. Pero en casos concretos puede ser muy útil, por ejemplo, para salir de un
bucle muy anidado (un "for" dentro de otro "for" que a su vez está dentro de otro "for": en este
caso, "break" sólo saldría del "for" más interno).
El formato de "goto" es
=C???????????????????????????C=
=C "# ) R 3F8 C=
=C # 3F C=
=C C=
=C ! ( , C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 3F
& '
# "$ &"E
Revisión 0.97 – Página 49
Introducción a la programación con C#, por Nacho Cabanes
# ""% (( #$"G
+
) * < ,
& $ ( # & $
& $ ( # & 3
& $ ( # & 4
& $ ( # & F
& $ ( # & T
& $ ( # & %$
& $ ( # & %3
& $ ( # & %4
& $ ( # & %F
& $ ( # & %T
& $ ( # & 3$
& % ( # & $
& % ( # & 3
& % ( # & 4
& % ( # & F
< ,
+ #' , / !4
Cuando comenzamos el tema, vimos cómo ayudarnos de los diagramas de flujo para plantear lo
que un programa debe hacer. Si entendemos esta herramienta, el paso a C (o a casi cualquier
otro lenguaje de programación es sencillo). Pero este tipo de diagramas es antiguo, no tiene en
cuenta todas las posibilidades del lenguaje C (y de muchos otros lenguajes actuales). Por
ejemplo, no existe una forma clara de representar una orden "switch", que equivaldría a varias
condiciones encadenadas.
Por su parte, un bucle "while" se vería como una condición que hace que algo se repita (una
flecha que vuelve hacia atrás, al punto en el que se comprobaba la condición):
Aun así, existen otras notaciones más modernas y que pueden resultar más cómodas. Sólo
comentaremos una: los diagramas de Chapin. En ellos se representa cada orden dentro de una
caja:
Pedir número n1
Pedir número n2
n1>n2?
si no
Decir "n1 es Decir "n2 es
mayor" mayor"
Y las condiciones repetitivas se indican dejando una barra a la izquierda, que marca qué es lo
que se repite, tanto si la condición se comprueba al final (do..while):
Abrir fichero
Mientras haya datos en fichero
Leer dato
Mostrar dato
Cerrar fichero
- 5, 45
Nos queda por ver otra orden que permite hacer cosas repetitivas: "foreach" (se traduciría
"para cada"). La veremos más adelante, cuando manejemos estructuras de datos más
complejas, que es en las que la nos resultará útil.
. 0
En general, nos interesará usar "& " cuando puede que la parte repetitiva no se
llegue a repetir nunca (por ejemplo: cuando leemos un fichero, si el fichero está vacío,
no habrá datos que leer).
De igual modo, " & " será lo adecuado cuando debamos repetir al menos una
vez (por ejemplo, para pedir una clave de acceso, se le debe preguntar al menos una
vez al usuario, o quizá más veces, si la teclea correctamente).
En cuanto a " ", es equivalente a un "while", pero la sintaxis habitual de la oren "for"
hace que sea especialmente útil cuando sabemos exactamente cuantas veces queremos
que se repita (por ejemplo: 10 veces sería "! 9% 9%$ AA ").
%
6 '
Hemos hablado de números enteros, de cómo realizar operaciones sencillas y de cómo usar
variables para reservar espacio y poder trabajar con datos cuyo valor no sabemos de
antemano.
Empieza a ser el momento de refinar, de dar más detalles. El primer "matiz" importante que
nos hemos saltado es el tamaño de los números que podemos emplear, así como su signo
(positivo o negativo). Por ejemplo, un dato de tipo "int" puede guardar números de hasta unas
nueve cifras, tanto positivos como negativos, y ocupa 4 bytes en memoria.
Pero no es la única opción. Por ejemplo, si queremos guardar la edad de una persona, no
necesitamos usar números negativos, y nos bastaría con 3 cifras, así que es de suponer que
existirá algún tipo de datos más adecuado, que desperdicie menos memoria. También existe el
caso contrario: un banco puede necesitar manejar números con más de 9 cifras, así que un
dato "int" se les quedaría corto. Siendo estrictos, si hablamos de valores monetarios,
necesitaríamos usar decimales, pero eso lo dejamos para el siguiente apartado.
% #
Los tipos de datos enteros que podemos usar en C#, junto con el espacio que ocupan en
memoria y el rango de valores que os permiten almacenar son:
Nombre Tamaño
Del Tipo (bytes) Rango de valores
sbyte 1 D128 a 127
byte 1 0 a 255
short 2 D32768 a 32767
ushort 2 0 a 65535
int 4 D2147483648 a 2147483647
uint 4 0 a 4294967295
long 8 D9223372036854775808 a 9223372036854775807
ulong 8 0 a 18446744073709551615
Como se puede observar en esta tabla, el tipo de dato más razonable para guardar edades
sería "byte", que permite valores entre 0 y 255, y ocupa 3 bytes menos que un "int".
% !
Si queremos obtener estos datos a partir de una cadena de texto, no siempre nos servirá
Convert.ToInt32, porque no todos los datos son enteros de 32 bits (4 bytes). Para datos de tipo
Revisión 0.97 – Página 54
Introducción a la programación con C#, por Nacho Cabanes
"byte" usaríamos Convert.ToByte (sin signo) y ToSByte (con signo), para datos de 2 bytes
tenemos ToInt16 (con signo) y ToUInt16 (sin signo), y para los de 8 bytes existen ToInt64 (con
signo) y ToUInt64 (sin signo).
:
• Preguntar al usuario su edad, que se guardará en un "byte". A continuación, se deberá
le deberá decir que no aparenta tantos años (por ejemplo, "No aparentas 20 años").
• Pedir al usuario dos números de dos cifras ("byte"), calcular su multiplicación, que se
deberá guardar en un "ushort", y mostrar el resultado en pantalla.
• Pedir al usuario dos números enteros largos ("long") y mostrar cuanto es su suma, su
resta y su producto.
% % (
Conocemos la forma de realizar las operaciones aritméticas más habituales. Peor también existe
una operación que es muy frecuente cuando se crean programas, y que no tiene un símbolo
específico para representarla en matemáticas: incrementar el valor de una variable en una
unidad:
9 A %
Pues bien, en C#, existe una notación más compacta para esta operación, y para la opuesta (el
decremento):
AA N 9 A%
?? N 9 ?%
Pero esto tiene más misterio todavía del que puede parecer en un primer vistazo: podemos
distinguir entre "preincremento" y "postincremento". En C# es posible hacer asignaciones como
/ 9 AA
Así, si "a" valía 2, lo que esta instrucción hace es dar a "b" el valor de "a" y aumentar el valor
de "a". Por tanto, al final tenemos que b=2 y a=3 ( : se incrementa "a" tras
asignar su valor).
En cambio, si escribimos
/ 9 AA
y "a" valía 2, primero aumentamos "a" y luego los asignamos a "b" ( ), de modo
que a=3 y b=3.
:
Crear un programa que use tres variables x,y,z. Sus valores iniciales serán 15, D10,
2.147.483.647. Se deberá incrementar el valor de estas variables. ¿Qué valores esperas
que se obtengan? Contrástalo con el resultado obtenido por el programa.
¿Cuál sería el resultado de las siguientes operaciones? a=5; b=++a; c=a++; b=b*5;
a=a*2;
Y ya que estamos hablando de las asignaciones, hay que comentar que en C# es posible hacer
:
9 / 9 9 %
% ! " 9+
Pero aún hay más. Tenemos incluso formas reducidas de escribir cosas como "a = a+5". Allá
van
A9 / N 9 A/
?9 / N 9 ?/
C9 / N 9 C/
=9 / N 9 =/
D9 / N 9 D/
:
Crear un programa que use tres variables x,y,z. Sus valores iniciales serán 15, D10, 214.
Se deberá incrementar el valor de estas variables en 12, usando el formato abreviado.
¿Qué valores esperas que se obtengan? Contrástalo con el resultado obtenido por el
programa.
¿Cuál sería el resultado de las siguientes operaciones? a=5; b=a+2; bD=3; c=D3;
c*=2; ++c; a*=b;
6
Cuando queremos almacenar datos con decimales, no nos sirve el tipo de datos "int".
Necesitamos otro tipo de datos que sí esté preparado para guardar números "reales" (con
decimales). En el mundo de la informática hay dos formas de trabajar con números reales:
% : (
Tenemos tres tamaños para elegir, según si queremos guardar números con mayor cantidad de
cifras o con menos. Para números con pocas cifras significativas (un máximo de 7, lo que se
conoce como "un dato real de simple precisión") existe el tipo "float" y para números que
necesiten más precisión (unas 15 cifras, "doble precisión") tenemos el tipo "double". En C#
existe un tercer tipo de números reales, con mayor precisión todavía, que es el tipo "decimal":
# 6
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):
# 6 " %3 EF
% & #
Al igual que hacíamos con los enteros, podemos leer como cadena de texto, y convertir cuando
vayamos a realizar operaciones aritméticas. Ahora usaremos Convert.ToDouble cuando se trate
de un dato de doble precisión, Convert.ToSingle cuando sea un dato de simple precisión (float)
y Convert.ToDecimal para un dato de precisión extra (decimal):
=C???????????????????????????C=
=C "# ) R 3G8 C=
=C # 3G C=
=C C=
=C K % C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 3G
& '
!
! ,
!
) * + K
" ) & , ) +
) * + , K
Revisión 0.97 – Página 57
Introducción a la programación con C#, por Nacho Cabanes
, " ) & , ) +
" ,
' al probar este programa: aunque en el fuente debemos escribir los decimales usando
un punto, como 123.456, al poner el ejecutable en marcha parte del trabajo se le encarga al
sistema operativo, de modo que si éste sabe que en nuestro país se usa la coma para los
decimales, considere la coma el separador correcto y no el punto, como ocurre si introducimos
estos datos en la versión española de Windows XP:
# $E
K
3@BF
, K
@4 3
+ 3@BF ( @43 @FEBF
% % ; #
En más de una ocasión nos interesará formatear los números para mostrar una cierta cantidad
de decimales: por ejemplo, nos puede interesar que una cifra que corresponde a dinero se
muestre siempre con dos cifras decimales, o que una nota se muestre redondeada, sin
decimales.
Una forma de conseguirlo es crear una cadena de texto a partir del número, usando "ToString".
A esta orden se le puede indicar un dato adicional, que es el formato numérico que queremos
usar, por ejemplo: suma.ToString("0.00")
• Un cero (0) indica una posición en la que debe aparecer un número, y se mostrará un 0
si no hay ninguno.
• Una almohadilla (#) indica una posición en la que puede aparecer un número, y no se
escribirá nada si no hay número.
• Un punto (.) indica la posición en la que deberá aparecer la coma decimal.
• Alternativamente, se pueden usar otros formatos abreviados: por ejemplo, N2 quiere
decir "con dos cifras decimales" y N5 es "con cinco cifras decimales"
=C???????????????????????????C=
=C "# ) R 3T8 C=
=C # 3T C=
=C C=
=C < K C=
=C C=
=C ) B C=
Revisión 0.97 – Página 58
Introducción a la programación con C#, por Nacho Cabanes
=C ) / C=
=C???????????????????????????C=
"# 3T
& '
/ " %3 @4
) * + , %
) * + , @
) * + , $ $
) * + , $ $$$
) * + ,
) * + ,
%3B@
%3B@4$
%3B@
%3B@4$
%3B@
%3B@4
=C???????????????????????????C=
=C "# ) R 3T/8 C=
=C # 3T/ C=
=C C=
=C 6 ( / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 3T/
& '
" 34G
) * + ) & , %F
) * + ) & , 3
Su resultado sería:
!G
%%%%$%%%
(Si quieres saber más sobre el sistema hexadecimal, mira los apéndices al final de este texto)
6 '
También tenemos un tipo de datos que nos permite almacenar una única letra, el tipo "char":
"
Para leer valores desde teclado, lo podemos hacer de forma similar a los casos anteriores:
leemos toda una frase con ReadLine y convertimos a tipo "char" usando Convert.ToChar:
" ) & ) ) +
Así, un programa que de un valor inicial a una letra, la muestre, lea una nueva letra tecleada
por el usuario, y la muestre, podría ser:
=C???????????????????????????C=
=C "# ) R 328 C=
=C # 32 C=
=C C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 32
& '
"
) * + + H$I
) * + &
" ) & ) ) +
) * + : H$I
%% : "< (
Como hemos visto, los textos que aparecen en pantalla se escriben con WriteLine, indicados
entre paréntesis y entre comillas dobles. Entonces surge una dificultad: ¿cómo escribimos una
comilla doble en pantalla? La forma de conseguirlo es usando ciertos caracteres especiales, lo
que se conoce como "secuencias de escape". Existen ciertos caracteres especiales que se
pueden escribir después de una barra invertida (\) y que nos permiten conseguir escribir esas
comillas dobles y algún otro carácter poco habitual. Por ejemplo, con \" se escribirán unas
comillas dobles, y con \' unas comillas simples, o con \n se avanzará a la línea siguiente de
pantalla.
( (
\a Emite un pitido
\b Retroceso (permite borrar el último carácter)
\f Avance de página (expulsa una hoja en la impresora)
\n Avanza de línea (salta a la línea siguiente)
\r Retorno de carro (va al principio de la línea)
\t Salto de tabulación horizontal
\v Salto de tabulación vertical
\' Muestra una comilla simple
\" Muestra una comilla doble
\\ Muestra una barra invertida
\0 Carácter nulo (NULL)
=C???????????????????????????C=
=C "# ) R @$8 C=
=C # @$ C=
=C C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @$
& '
) * + " !
) * +
) * +
) * + ( B
) * + Y Y Z , 8Y Y
) * + ) / 8 Y ( Y B ( / YY
" !
( B
Z , 8
) / 8 ( B ( / Y
En algunas ocasiones puede ser incómodo manipular estas secuencias de escape. Por ejemplo,
cuando usemos estructuras de directorios: c:\\datos\\ejemplos\\curso\\ejemplo1. En este caso,
se puede usar una arroba (@) antes del texto sin las barras invertidas:
9 1 8Y Y # Y Y # %
En este caso, el problema está si aparecen comillas en medio de la cadena. Para solucionarlo,
se duplican las comillas, así:
9 ( # !8
: Crear un programa que pida al usuario que teclee cuatro letras y las
muestre en pantalla juntas, pero en orden inverso, y entre comillas dobles. Por ejemplo si las
letras que se teclean son a, l, o, h, escribiría "hola".
% 6
Las cadenas de texto son tan fáciles de manejar como los demás tipos de datos que hemos
visto, con apenas tres diferencias:
Así, un ejemplo que diera un valor a un "string", lo mostrara (entre comillas, para practicar las
secuencias de escape que hemos visto en el apartado anterior) y leyera un valor tecleado por el
usuario podría ser:
=C???????????????????????????C=
=C "# ) R @%8 C=
=C # @% C=
=C C=
=C ] / , C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @%
& '
, !
! " B ^
) * + + ! Y H$IY !
) * + & !
! " ) +
) * + : ! Y H$IY !
Se pueden hacer muchas más operaciones sobre cadenas de texto: convertir a mayúsculas o a
minúsculas, eliminar espacios, cambiar una subcadena por otra, dividir en trozos, etc. Pero ya
volveremos a las cadenas más adelante, cuando conozcamos las estructuras de control básicas.
( 5 5
En C# tenemos también un tipo de datos llamado "booleano" ("bool"), que puede tomar dos
valores: verdadero ("trae") o falso ("false"):
"
Este tipo de datos hará que podamos escribir de forma sencilla algunas condiciones que podrían
resultar complejas. Así podemos hacer que ciertos fragmentos de nuestro programa no sean "if
((vidas==0) || (tiempo == 0) || ((enemigos ==0) && (nivel == ultimoNivel)))" sino
simplemente "if (partidaTerminada) ..."
A las variables "bool" también se le puede dar como valor el resultado de una comparación:
" #
" , ""$ (( & "" &
# & "" $ "
Lo emplearemos a partir de ahora en los fuentes que usen condiciones un poco complejas. Un
ejemplo que pida una letra y diga si es una vocal, una cifra numérica u otro símbolo, usando
variables "bool" podría ser:
=C???????????????????????????C=
=C "# ) R @38 C=
=C # @3 C=
=C C=
=C ) ! T C=
=C X / / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @3
& '
/ X ) !
) * +
" ) & ) ) +
# ) !
) * + " ! 5
# X
) * + " &
) * + " K
!
• Crear un programa que use el operador condicional para dar a una variable llamada
"iguales" (booleana) el valor "true" si los dos números que ha tecleado el usuario son
iguales, o "false" si son distintos.
$ ( * ( =
% ! '
((
Una tabla, vector, matriz o (que algunos autores traducen por " ") es un conjunto
de elementos, todos los cuales son del mismo tipo. Estos elementos tendrán todos el mismo
nombre, y ocuparán un espacio contiguo en la memoria.
Por ejemplo, si queremos definir un grupo de números enteros, el tipo de datos que usaremos
para declararlo será "int[]". Si sabemos desde el principio cuantos datos tenemos (por ejemplo
4), les reservaremos espacio con "= new int[4]", así
_` # 9 > _4`
Podemos acceder a cada uno de los valores individuales indicando su nombre (ejemplo) y el
número de elemento que nos interesa, pero con una precaución: se empieza a numerar desde
0, así que en el caso anterior tendríamos 4 elementos, que serían ejemplo[0], ejemplo[1],
ejemplo[2], ejemplo[3].
=C???????????????????????????C=
=C "# ) R @@8 C=
=C # @@ C=
=C C=
=C # / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @@
& '
-. " -E. =C ] ( E K C=
=C ] N M C=
-$. 3$$
" =C + & C=
-%. %E$
"
-3. %$$
"
-@. " E$
-4. @$$
"
" -$. =C P C=
-%. -3. -@. -4.
) * + H$I
=C 8 ! M ! 7 C=
=C P # C=
!
• Un programa que pida al usuario 4 números, los memorice (utilizando una tabla),
calcule su media aritmética y después muestre en pantalla la media y los datos
tecleados.
• Un programa que pida al usuario 5 números reales y luego los muestre en el orden
contrario al que se introdujeron.
• Un programa que pida al usuario 10 números enteros y calcule (y muestre) cuál es el
mayor de ellos.
> (
Al igual que ocurría con las variables "normales", podemos dar valor a los elementos de una
tabla al principio del programa. Será más cómodo que dar los valores uno por uno, como
hemos hecho antes, pero sólo se podrá hacer si conocemos todos los valores. En este caso, los
indicaremos todos entre llaves, separados por comas:
=C???????????????????????????C=
=C "# ) R @48 C=
=C # @4 C=
=C C=
=C , # C=
=C / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @4
& '
-. " =C ] ( E K C=
3$$ %E$ %$$ E$ @$$
=C ] N M C=
" -$. =C P C=
-%. -3. -@. -4.
) * + H$I
=C 8 ! , , B &Q C=
=C M / + , # C=
!
Revisión 0.97 – Página 67
Introducción a la programación con C#, por Nacho Cabanes
• Un programa que almacene en una tabla el número de días que tiene cada mes (suD
pondremos que es un año no bisiesto), pida al usuario que le indique un mes (1=enero,
12=diciembre) y muestre en pantalla el número de días que tiene ese mes.
• Un programa que almacene en una tabla el número de días que tiene cada mes (año
no bisiesto), pida al usuario que le indique un mes (ej. 2 para febrero) y un día (ej. el
día 15) y diga qué número de día es dentro del año (por ejemplo, el 15 de febrero sería
el día número 46, el 31 de diciembre sería el día 365).
% ?
Es de esperar que exista una forma más cómoda de acceder a varios elementos de un array,
sin tener siempre que repetirlos todos, como hemos hecho en
El "truco" consistirá en emplear cualquiera de las estructuras repetitivas que ya hemos visto
(while, do..while, for), por ejemplo así:
" $ =C X C=
# "$ &"4 =C P & C=
" - .
=C???????????????????????????C=
=C "# ) R @E8 C=
=C # @E C=
=C C=
=C # C=
=C / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @E
& '
-. " =C ] ( E K C=
3$$ %E$ %$$ E$ @$$
=C ] N M C=
=C C=
" $ =C X C=
# "$ &"4 =C P & C=
" - .
) * + H$I
En este caso, que sólo sumábamos 5 números, no hemos escrito mucho menos, pero si
trabajásemos con 100, 500 o 1000 números, la ganancia en comodidad sí que está clara.
!
Si queremos que sea el usuario el que introduzca datos a un array, usaríamos otra estructura
repetitiva ("for", por ejemplo) para pedírselos:
=C???????????????????????????C=
=C "# ) R @F8 C=
=C # @F C=
=C C=
=C ) # C=
=C / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @F
& '
-. " -E. =C ] ( E K C=
=C ] N M C=
=C C=
# "$ &"4 =C C=
) * H$I8 %
- . " ) & @3 ) +
" $ =C X C=
# "$ &"4 =C P & C=
" - .
) * + H$I
!
• A partir del programa propuesto en 4.1.2, que almacenaba en una tabla el número de
días que tiene cada mes, crear otro que pida al usuario que le indique la fecha,
detallando el día (1 al 31) y el mes (1=enero, 12=diciembre), como respuesta muestre
en pantalla el número de días que quedan hasta final de año.
• Crear un programa que pida al usuario 10 números y luego los muestre en orden
inverso (del último que se ha introducido al primero que se introdujo).
• Un programa que pida al usuario 10 números y luego calcule y muestre cual es el
mayor de todos ellos.
• Un programa que pida al usuario 10 números, calcule su media y luego muestre los que
están por encima de la media.
• Un programa que pida 10 nombres y los memorice (pista: esta vez se trata de un array
de "string"). Después deberá pedir que se teclee un nombre y dirá si se encuentra o no
entre los 10 que se han tecleado antes. Volverá a pedir otro nombre y a decir si se
encuentra entre ellos, y así sucesivamente hasta que se teclee "fin".
• Un programa que prepare espacio para un máximo de 100 nombres. El usuario deberá
ir introduciendo un nombre cada vez, hasta que se pulse Intro sin teclear nada,
momento en el que dejarán de pedirse más nombres y se mostrará en pantalla la lista
de los nombres que se han introducido.
• Un programa que prepare espacio para un máximo de 10 nombres. Deberá mostrar al
usuario un menú que le permita realizar las siguientes operaciones:
Añadir un dato al final de los ya existentes.
Insertar un dato en una cierta posición (lo que quedén detrás deberán
desplazarse "a la derecha" para dejarle hueco; por ejemplo, si el array contiene
"hola", "adios" y se pide insertar "bien" en la segunda posición, el array pasará
a contener "hola", "bien", "adios".
Borrar el dato que hay en una cierta posición (lo que estaban detrás deberán
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 posición,
el array pasará a contener "hola", "adios"
Mostrar los datos que contiene el array.
Salir del programa.
% 6
Podemos declarar tablas de . Por ejemplo, si queremos guardar datos
de dos grupos de alumnos, cada uno de los cuales tiene 20 alumnos, tenemos dos opciones:
En cualquier caso, si queremos indicar valores iniciales, lo haremos entre llaves, igual que si
fuera una tabla de una única dimensión.
Vamos a ver un primer ejemplo del uso con arrays de la forma [2,20], lo que podríamos llamar
el "estilo Pascal", en el usemos tanto arrays con valores prefijados, como arrays para los que
reservemos espacio con "new" y a los que demos valores más tarde:
=C???????????????????????????C=
=C "# ) R @G8 C=
=C # @G C=
=C C=
=C : ( C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @G
& '
- . % " -3 3. == 3 / N 3
%-$ $. " %
%-$ %. " 3
%-% $. " @
%-% %. " 4
- . 3 " == 3 / N %$
% 3 @ 4 E F G T 2 %$
%% %3 %@ %4 %E %F %G %T %2 3$
) * + + % , , % H$I
%-$ %.
) * + + 3 , % H$I
3-$ 3.
Este tipo de tablas de varias dimensiones son las que se usan también para guardar matrices,
cuando se trata de resolver problemas matemáticos más complejos que los que hemos visto
hasta ahora.
La otra forma de tener arrays multidimensionales son los "arrays de arrays", que, como ya
hemos comentado, y como veremos en este ejemplo, pueden tener elementos de distinto
tamaño. En ese caso nos puede interesar saber su longitud, para lo que podemos usar
"a.Length":
=C???????????????????????????C=
Revisión 0.97 – Página 71
Introducción a la programación con C#, por Nacho Cabanes
=C "# ) R @T8 C=
=C # @T C=
=C C=
=C : ( C=
=C ) C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @T
& '
-.-. == : (
" [email protected]. == @ / N
-$. " -%$. == %$ ,
-%. " -%E. == %E ,
-3. " -%3. == %3
== ; & #
# "$ & + ,
# #"$ #& - . + , #
- .-#. " #
== P &
# "$ & + ,
# #"$ #& - . + , #
) * H$I - .-#.
) * +
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
!
• Un programa que al usuario dos bloques de 10 números enteros (usando un array de
dos dimensiones). Después deberá mostrar el mayor dato que se ha introducido en
cada uno de ellos.
Revisión 0.97 – Página 72
Introducción a la programación con C#, por Nacho Cabanes
%
% (
Un es una agrupación de datos, los cuales no necesariamente son del mismo tipo. Se
definen con la palabra " ". La serie de datos que van a formar
En C# (al contrario que en C), primero deberemos declarar cual va a ser la estructura de
nuestro registro, lo que no se puede hacer dentro de "Main". Más adelante, ya dentro de
"Main", podremos declarar variables de ese nuevo tipo.
Los datos que forman un "struct" pueden ser públicos o privados. Por ahora, a nosotros nos
interesará que sean accesibles desde el resto de nuestro programa, por lo que les añadiremos
delante la palabra "public" para indicar que queremos que sean públicos.
Ya desde el cuerpo del programa, para acceder a cada uno de los datos que forman el registro,
tanto si queremos leer su valor como si queremos cambiarlo, se debe indicar el nombre de la
variable y el del dato (o campo) separados por un punto:
=C???????????????????????????C=
=C "# ) R @28 C=
=C # @2 C=
=C C=
=C , C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# @2
, /
& '
/ " Z
" Z
" 3$
" G E!
Revisión 0.97 – Página 73
Introducción a la programación con C#, por Nacho Cabanes
) * + + H$I H%I
/
Nota: La notación 7.5f se usa para detallar que se trata de un número real de simple precisión
(un "float"), porque de lo contrario, 7.5 se consideraría un número de doble precisión, y al
tratar de compilar obtendríamos un mensaje de error, diciendo que no se puede convertir de
"double" a "float" sin pérdida de precisión. Al añadir la "f" al final, estamos diciendo "quiero que
éste número se tome como un float; sé que habrá una pérdida de precisión pero es aceptable
para mí”.
!
• Un "struct" que almacene datos de una canción en formato MP3: Artista, Título,
Duración (en segundos), Tamaño del fichero (en KB). Un programa debe pedir los
datos de una canción al usuario, almacenarlos en dicho "struct" y después mostrarlos
en pantalla.
% $ (
Hemos guardado varios datos de una persona. Se pueden almacenar los de
si combinamos el uso de los "struct" con las tablas (arrays) que vimos anteriormente. Por
ejemplo, si queremos guardar los datos de 100 personas podríamos hacer:
=C???????????????????????????C=
=C "# ) R 4$8 C=
=C # 4$ C=
=C C=
=C : ( C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4$
, /
& '
-. " -%$$.
-$. / " Z
-$. " Z
-$. " 3$
-$. " G E!
Revisión 0.97 – Página 74
Introducción a la programación con C#, por Nacho Cabanes
) * + + H$I H%I
-$. / -$.
-%. / "
) * + + H$I H%I
-%. / -%.
+ Z 3$
+ $
Porque cuando reservamos espacio para los elementos de un "array" usando "new", sus valores
se dejan "vacíos" (0 para los números, cadenas vacías para las cadenas de texto).
!
• Ampliar el programa del apartado 4.3.1, para que almacene datos de hasta 100
canciones. Deberá tener un menú que permita las opciones: añadir una nueva canción,
mostrar el título de todas las canciones, buscar la canción que contenga un cierto texto
(en el artista o en el título).
• Un programa que permita guardar datos de "imágenes" (ficheros de ordenador que
contengan fotografías o cualquier otro tipo de información gráfica). De cada imagen se
debe guardar: nombre (texto), ancho en píxeles (por ejemplo 2000), alto en píxeles
(por ejemplo, 3000), tamaño en Kb (por ejemplo 145,6). El programa debe ser capaz
de almacenar hasta 700 imágenes (deberá avisar cuando su capacidad esté llena).
Debe permitir las opciones: añadir una ficha nueva, ver todas las fichas (número y
nombre de cada imagen), buscar la ficha que tenga un cierto nombre.
%% '
Podemos encontrarnos con un registro que tenga varios datos, y que a su vez ocurra que uno
de esos datos esté formado por varios datos más sencillos. Por ejemplo, una fecha de
nacimiento podría estar formada por día, mes y año. Para hacerlo desde C#, incluiríamos un
"struct" dentro de otro, así:
=C???????????????????????????C=
=C "# ) R 4%8 C=
=C # 4% C=
=C C=
=C , C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
Revisión 0.97 – Página 75
Introducción a la programación con C#, por Nacho Cabanes
"# 4%
, /
! ;
!
& '
/ " Z
" Z
; " %E
; " 2
" G E!
) * + H$I 7 H%I
/ ;
!
• Ampliar el programa del primer apartado de 4.3.2, para que el campo "duración" se
almacene como minutos y segundos, usando un "struct" anidado que contenga a su vez
estos dos campos.
%% !
@
Hemos visto cómo leer cadenas de caracteres (Console.ReadLine) y cómo mostrarlas en
pantalla (Console.Write), así como la forma de darles un valor(=). También podemos comparar
cual es su valor, usando ==, o formar una cadena a partir de otras si las unimos con el símbolo
de la suma (+):
Así, un ejemplo que nos pidiese nuestro nombre y nos saludase usando todas estas
posibilidades podría ser:
=C???????????????????????????C=
Revisión 0.97 – Página 76
Introducción a la programación con C#, por Nacho Cabanes
=C "# ) R 438 C=
=C # 43 C=
=C C=
=C ) 6 % C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 43
& '
, "
, ,
, /
, " \ ^
) * + ; /
/ " ) +
) * + H$I H%I /
) * + ,
# / "" : /
) * + ; N : / ^
) * + : Q N : / ^
" : / S
) * +
A
Podemos leer (o modificar) una de las letras de una cadena de igual forma que leemos los
elementos de cualquier array: si la cadena se llama "texto", el primer elemento será texto[0], el
segundo será texto[1] y así sucesivamente.
Eso sí, las cadenas en C# no se pueden modificar letra a letra: no podemos hacer texto[0]=’a’.
Para eso habrá que usar una construcción auxiliar, que veremos más adelante.
% @
Podemos saber cuantas letras forman una cadena con "cadena.Length". Esto permite que
podamos recorrer la cadena letra por letra, usando construcciones como "for".
!
• Un programa que te pida tu nombre y lo muestre en pantalla separando cada letra de
la siguiente con un espacio. Por ejemplo, si tu nombre es "Juan", debería aparecer en
pantalla "J u a n".
• Un programa capaz de sumar dos números enteros muy grandes (por ejemplo, de 30
cifras), que se deberán pedir como cadena de texto y analizar letra a letra.
• Un programa capaz de multiplicar dos números enteros muy grandes (por ejemplo, de
30 cifras), que se deberán pedir como cadena de texto y analizar letra a letra.
'=
Podemos extraer parte del contenido de una cadena con "Substring", que recibe dos
parámetros: la posición a partir de la que queremos empezar y la cantidad de caracteres que
queremos obtener. El resultado será otra cadena:
" ! / , $ 4
Podemos omitir el segundo número, y entonces se extraerá desde la posición indicada hasta el
final de la cadena.
B
Para ver si una cadena contiene un cierto texto, podemos usar IndexOf ("posición de"), que nos
dice en qué posición se encuentra (o devuelve el valor D1 si no aparece):
Podemos añadir un segundo parámetro opcional, que es la posición a partir de la que queremos
buscar:
# / 6W! Z E $" $
La búsqueda termina al final de la cadena, salvo que indiquemos que termine antes con un
tercer parámetro opcional:
# / 6W! Z E %E $" $
De forma similar, LastIndexOf ("última posición de") indica la última aparición (es decir, busca
de derecha a izquierda).
1
Ya hemos comentado que las cadenas en C# son inmutables, no se pueden modificar. Pero sí
podemos realizar ciertas operaciones sobre ellas para obtener una nueva cadena. Por ejemplo:
• Insert(int posición, string subcadena): Insertar una subcadena en una cierta posición
de la cadena inicial: nombreFormal = nombre.Insert(0,"Don");
• Remove(int posición, int cantidad): Elimina una cantidad de caracteres en cierta
posición: apellidos = nombreCompleto.Remove(0,6);
• Replace(string textoASustituir, string cadenaSustituta): Sustituye una cadena (todas las
veces que aparezca) por otra: nombreCorregido = nombre.Replace("Pepe", "Jose");
=C???????????????????????????C=
=C "# ) R 4@8 C=
=C # 4@ C=
=C C=
=C ) 6 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4@
& '
, # " B N
) * + " 6 8 H$I
#
) * + + 8 H$I
# -$.
) * + + 8 H$I
# / , $ @
) * + + , 6 8 H$I
# + ,
) * + + Y N Y 8 H$I
# 6W! N
) * + + : 8 H$I
# + 6W!
) * + " (K 8 H$I
# ]
) * + " K 8 H$I
# + >
) * + Y B Y 8 H$I
# 4 B
Revisión 0.97 – Página 79
Introducción a la programación con C#, por Nacho Cabanes
) * + / F 8 H$I
# & $ F
Y su resultado sería
" 6 8 B N
+ 8
+ 8
+ , 6 8 %2
+ N 8 F
+ : 8 %G
" (K 8 W+:B \]" :+ " :
" K 8 B N
B 8 B B N
/ F 8 N
/ " : " :'W 8 B N
Otra posibilidad interesante, aunque un poco más avanzada, es la de descomponer una cadena
en trozos, que estén separados por una serie de delimitadores (por ejemplo, espacios o comas).
Para ello se puede usar Split, que crea un array a partir de los fragmentos de la cadena, así:
=C???????????????????????????C=
=C "# ) R 4@/8 C=
=C # 4@/ C=
=C C=
=C ) 6 3/ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4@/
& '
, # " B B
-. " B
, -. # " #
# "$ & # + ,
) * + < , H$I9 H%I
# - .
< , $9
< , %9
< , 39
< , @9
3
Sabemos comprobar si una cadena tiene exactamente un cierto valor, con el operador de
igualdad (==), pero no sabemos comparar qué cadena es "mayor" que otra, algo que es
necesario si queremos ordenar textos. El operador "mayor que" (>) que usamos con los
números no se puede aplicar directamente a las cadenas. En su lugar, debemos usar
"CompareTo", que devolverá un número mayor que 0 si la nuestra cadena es mayor que la que
indicamos como parámetro (o un número negativo si nuestra cadena es menor, o 0 si son
iguales):
# ! ) $ $
) * + " ( N
# , ) ! $ $
) * + " ( N (
=C???????????????????????????C=
=C "# ) R 4@ 8 C=
=C # 4@ C=
=C C=
=C ) 6 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4@
& '
Revisión 0.97 – Página 81
Introducción a la programación con C#, por Nacho Cabanes
, !
) * + " / /
! " ) +
== ) / 6
# ! ""
) * +
== ) / (
# ! ) $ $
) * + " ( N
# ! ) & $
) * + " N
== ) , (K K
/ , ' ( "
# , ) ! , ' ( $ $
) * + " ( N (
# , ) ! , ' ( & $
) * + " N (
) * + " (
Si tecleamos una palabra como "gol", que comienza por G, que alfabéticamente está antes de
la H de "hola", se nos dirá que esa palabra es menor:
" / /
,
" N
" N (
Si escribimos "hOLa", que coindice con "hola" salvo por las mayúsculas, una comparación
normal nos dirá que es mayor (las mayúsculas se consideran "mayores" que las minúsculas), y
una comparación sin considerar mayúsculas o minúsculas nos dirá que coinciden:
" / /
W+
" ( N
" (
5 C ": B
Si tenemos la necesidad de poder modificar una cadena letra a letra, no podemos usar un
"string" convencional, deberemos recurrir a un "StringBuilder", que sí lo permiten pero son algo
más complejos de manejar: hay de reservarles espacio con "new" (igual que hacíamos en
ciertas ocasiones con los Arrays), y se pueden convertir a una cadena "convencional" usando
"ToString":
=C???????????????????????????C=
=C "# ) R 448 C=
=C # 44 C=
=C C=
=C ) ! / C=
=C ,[ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( 6 == ] ( 6 ,[
"# 44
& '
,[ ' ! / " ,[
' ! / -$. " '
) * + ) ! 8 H$I
' ! /
,
" ' ! / ,
) * + ) 8 H$I
!
• Un programa que pida tu nombre, tu día de nacimiento y tu mes de nacimiento y lo
junte todo en una cadena, separando el nombre de la fecha por una coma y el día del
mes por una barra inclinada, así: "Juan, nacido el 31/12".
• Crear un juego del ahorcado, en el que un primer usuario introduzca la palabra a
adivinar, se muestre esta programa oculta con guiones (DDDDD) y el programa acepte las
letras que introduzca el segundo usuario, cambiando los guiones por letras correctas
cada vez que acierte (por ejemplo, aDDDaDtD). La partida terminará cuando se acierte la
palabra por completo o el usuario agote sus 8 intentos.
D ? 2 82
Existe una construcción parecida a "for", pensada para recorrer ciertas estructuras de datos,
como los arrays (y otras que veremos más adelante).
=C???????????????????????????C=
=C "# ) R 4E8 C=
=C # 4E C=
=C C=
=C "# ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4E
& '
-. ' " @% 3T 3%
# '
) * + ; 8 H$I
,-. / " : / : 5 :
# , / /
) * H$I /
) * +
, "
#
) * H$I?
) * +
%(
Vamos a hacer un ejemplo completo que use tablas ("arrays"), registros ("struct") y que
además manipule cadenas.
La idea va a ser la siguiente: Crearemos un programa que pueda almacenar datos de hasta
1000 ficheros (archivos de ordenador). Para cada fichero, debe guardar los siguientes datos:
Nombre del fichero, Tamaño (en KB, un número de 0 a 8.000.000.000). El programa mostrará
un menú que permita al usuario las siguientes operaciones:
No debería resultar difícil. Vamos a ver directamente una de las formas en que se podría
plantear y luego comentaremos alguna de las mejoras que se podría (incluso se debería) hacer.
Una opción que podemos a tomar para resolver este problema es la de contar el número de
fichas que tenemos almacenadas, y así podremos añadir de una en una. Si tenemos 0 fichas,
deberemos almacenar la siguiente (la primera) en la posición 0; si tenemos dos fichas, serán la
0 y la 1, luego añadiremos en la posición 2; en general, si tenemos "n" fichas, añadiremos cada
nueva ficha en la posición "n". Por otra parte, para revisar todas las fichas, recorreremos desde
la posición 0 hasta la nD1, haciendo algo como
El resto del programa no es difícil: sabemos leer y comparar textos y números, comprobar
varias opciones con "switch", etc. Aun así, haremos una última consideración: hemos limitado el
número de fichas a 1000, así que, si nos piden añadir, deberíamos asegurarnos antes de que
todavía tenemos hueco disponible.
=C???????????????????????????C=
=C "# ) R 4F8 C=
=C # 4F C=
=C C=
=C / C=
=C ( # C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4F
<
, / < =C / ! C=
, ( =C " L /( C=
& '
< -. ! =C + C=
" < -%$$$.
< "$ =C K ! N ( C=
=C / C=
=C + N # C=
, 6 [ =C , C=
, ( [ =C / L C=
=C ' C=
) * +
) * + " # 7 8
) * + % ? :L & !
) * + 3 ? ' / !
) * + @ ? ' ! N L
) * + 4 ? X !
) * + E ?
" ) & @3 ) +
=C ,K 7 , C=
%+ =C :L & C=
# < & %$$$ =C N C=
) * + / ! 8
! - < . / < " ) +
) * + L a[8
! - < . ( " ) & @3 ) +
=C P ( ! M C=
<
=C ( M ! B & C=
) * + 'M6 ! J %$$$ S
,
3+ =C ' C=
# "$ & <
) * + / 8 H$I L 8 H%I a/
! - . / < ! - . (
,
@+ =C ' ,K L C=
) * + b: N L N N ^
( [ " ) & F4 ) +
# "$ & <
# ! - . ( $" ( [
) * + / 8 H$I L 8 H%I a/
! - . / < ! - . (
,
4+ =C X ! C=
) * + b; N 5 ! N & ^
6 [ " ) +
# "$ & <
# ! - . / < "" 6 [
) * + / 8 H$I L 8 H%I a/
! - . / < ! - . (
,
E+ =C 8 & N C=
) * + < ,
,
# + =C W 8 &M C=
) * + W 7 S
,
%" E =C EB C=
Funciona, y hace todo lo que tiene que hacer, pero es mejorable. Por supuesto, en un caso real
es habitual que cada ficha tenga que guardar más información que sólo esos dos apartados de
ejemplo que hemos previsto esta vez. Si nos muestra todos los datos en pantalla y se trata de
muchos datos, puede ocurrir que aparezcan en pantalla tan rápido que no nos dé tiempo a
leerlos, así que sería deseable que parase cuando se llenase la pantalla de información (por
ejemplo, una pausa tras mostrar cada 25 datos). Por supuesto, se nos pueden ocurrir muchas
más preguntas que hacerle sobre nuestros datos. Y además, cuando salgamos del programa se
borrarán todos los datos que habíamos tecleado, pero eso es lo único "casi inevitable", porque
aún no sabemos manejar ficheros.
!
• Un programa que pida el nombre, el apellido y la edad de una persona, los almacene
en un "struct" y luego muestre los tres datos en una misma línea, separados por
comas.
• Un programa que pida datos de 8 personas: nombre, dia de nacimiento, mes de
nacimiento, y año de nacimiento (que se deben almacenar en una tabla de structs).
Después deberá repetir lo siguiente: preguntar un número de mes y mostrar en
pantalla los datos de las personas que cumplan los años durante ese mes. Terminará
de repetirse cuando se teclee 0 como número de mes.
• Un programa que sea capaz de almacenar los datos de 50 personas: nombre, dirección,
teléfono, edad (usando una tabla de structs). Deberá ir pidiendo los datos uno por uno,
hasta que un nombre se introduzca vacío (se pulse Intro sin teclear nada). Entonces
deberá aparecer un menú que permita:
Mostrar la lista de todos los nombres.
Mostrar las personas de una cierta edad.
Mostrar las personas cuya inicial sea la que el usuario indique.
Salir del programa
(lógicamente, este menú debe repetirse hasta que se escoja la opción de "salir").
• Mejorar la base de datos de ficheros (ejemplo 46) para que no permita introducir
tamaños incorrectos (números negativos) ni nombres de fichero vacíos.
• Ampliar la base de datos de ficheros (ejemplo 46) para que incluya una opción de
búsqueda parcial, en la que el usuario indique parte del nombre y se muestre todos los
ficheros que contienen ese fragmento (usando "IndexOf"). Esta búsqueda no debe
distinguir mayúsculas y minúsculas (con la ayuda de ToUpper o ToLower).
• Ampliar el ejercicio anterior (el que permite búsqueda parcial) para que la búsqueda
sea incremental: el usuario irá indicando letra a letra el texto que quiere buscar, y se
mostrará todos los datos que lo contienen (por ejemplo, primero los que contienen "j",
luego "ju", después "jua" y finalmente "juan").
• Ampliar la base de datos de ficheros (ejemplo 46) para que se pueda borrar un cierto
dato (habrá que "mover hacia atrás" todos los datos que había después de ese, y
disminuir el contador de la cantidad de datos que tenemos).
• Mejorar la base de datos de ficheros (ejemplo 46) para que se pueda modificar un
cierto dato a partir de su número (por ejemplo, el dato número 3). En esa modificación,
se deberá permitir al usuario pulsar Intro sin teclear nada, para indicar que no desea
modificar un cierto dato, en vez de reemplazarlo por una cadena vacía.
• Ampliar la base de datos de ficheros (ejemplo 46) para que se permita ordenar los
datos por nombre. Para ello, deberás buscar información sobre algún método de
ordenación sencillo, como el "método de burbuja" (en el siguiente apartado tienes
algunos), y aplicarlo a este caso concreto.
%+&
Es muy frecuente querer ordenar datos que tenemos en un array. Para conseguirlo, existen
varios algoritmos sencillos, que no son especialmente eficientes, pero son fáciles de programar.
La falta de eficiencia se refiere a que la mayoría de ellos se basan en dos bucles "for" anidados,
de modo que en cada pasada quede ordenado un dato, y se dan tantas pasadas como datos
existen, de modo que para un array con 1.000 datos, podrían llegar a tener que hacerse un
millón de comparaciones.
Existen ligeras mejoras (por ejemplo, cambiar uno de los "for" por un "while", para no repasar
todos los datos si ya estaban parcialmente ordenados), así como métodos claramente más
efectivos, pero más difíciles de programar, alguno de los cuales veremos más adelante.
Veremos tres de estos métodos simples de ordenación, primero mirando la apariencia que tiene
el algoritmo, y luego juntando los tres en un ejemplo que los pruebe:
)
(Intercambiar cada pareja consecutiva que no esté ordenada)
(Nota: algunos autores hacen el bucle exterior creciente y otros decreciente, así:)
(
(En cada pasada busca el menor, y lo intercambia al final de la pasada)
Nota: el símbolo "<>" se suele usar en pseudocódigo para indicar que un dato es distinto de
otro, de modo que equivale al "!=" de C#. La penúltima línea en C# saldría a ser algo como "if
(menor !=i)"
*
(Comparar cada elemento con los anteriores Dque ya están ordenadosD y desplazarlo hasta su
posición correcta).
(Es mejorable, no intercambiando el dato que se mueve con cada elemento, sino sólo al final de
cada pasada, pero no entraremos en más detalles).
=C???????????????????????????C=
=C "# ) C=
=C C=
=C C=
=C W C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
& '
-. " E @ %4 3$ T 2 %
#
"G ==
== [] []Z:
== / # & N 5
== 9% ?%
== #9 A%
== :_ ` :_#`
== / :_ `B :_#`
) * + W / / #
# "$ & %
# == '
) * H$I
Revisión 0.97 – Página 89
Introducción a la programación con C#, por Nacho Cabanes
) * +
# #" % # & #
# - . $ -#.
" - .
- . " -#.
-#. "
) * W 8
# == ' !
) * H$I
) * +
== "+")) c ; ") :8
== " / B ( / !
== 9% ?%
== 9
== #9 A%
== :_#` :_ `
== 9 #
==
== / :_ `B :_ `
) * + Y W 7
-. 3 " E @ %4 3$ T 2 %
# "$ & %
# 3 == '
) * H$I
) * +
"
# #" % # & #
# 3-#. & 3- .
" #
# %"
" 3- .
3- . " 3- .
3- . "
) * W 8
# 3 == ' !
) * H$I
) * +
== " ) W ; ") :8
== ) ?N ( M ?
== ( J 7
== 93
== #9 ?%
== # 9% ( :_#` :_#A%`
== / :_#`B :_#A%`
== # 9 # ? %
) * + Y W 7
-. @ " E @ %4 3$ T 2 %
# "% &
# @ == '
) * H$I
) * +
# " %
#$"$ (( @-#. $ @-# %.
" @-#.
@-#. " @-# %.
@-# %. "
#
) * W 8
# @ == ' !
) * H$I
) * +
Y su resultado sería:
W / / #
E @ %4 3$ T 2 %
% E %4 3$ T 2 @
% @ %4 3$ T 2 E
% @ E 3$ %4 2 T
% @ E T 3$ %4 2
% @ E T 2 3$ %4
W 8% @ E T 2 %4 3$
W 7
E @ %4 3$ T 2 %
% @ %4 3$ T 2 E
% @ %4 3$ T 2 E
% @ E 3$ T 2 %4
% @ E T 3$ 2 %4
% @ E T 2 3$ %4
W 8% @ E T 2 %4 3$
W 7
E @ %4 3$ T 2 %
Revisión 0.97 – Página 91
Introducción a la programación con C#, por Nacho Cabanes
@ E %4 3$ T 2 %
@ E %4 3$ T 2 %
@ E %4 3$ T 2 %
@ E T %4 3$ 2 %
@ E T 2 %4 3$ %
W 8% @ E T 2 %4 3$
( / 7 */
Hasta ahora hemos estado pensando los pasos que deberíamos dar para resolver un cierto
problema, y hemos creado programas a partir de cada uno de esos pasos. Esto es razonable
cuando los problemas son sencillos, pero puede no ser la mejor forma de actuar cuando se
trata de algo más complicado.
A partir de ahora vamos a empezar a intentar descomponer los problemas en trozos más
pequeños, que sean más fáciles de resolver. Esto nos puede suponer varias ventajas:
• Cada "trozo de programa" independiente será más fácil de programar, al realizar una
función breve y concreta.
• El "programa principal" será más fácil de leer, porque no necesitará contener todos los
detalles de cómo se hace cada cosa.
• Podremos repartir el trabajo, para que cada persona se encargue de realizar un "trozo
de programa", y finalmente se integrará el trabajo individual de cada persona.
( ! ' ,
En C#, al igual que en C y los demás lenguajes derivados de él, todos los "trozos de programa"
son funciones, incluyendo el propio cuerpo de programa, ) . De hecho, la forma básica de
una función será indicando su nombre seguido de unos paréntesis vacíos, como
hacíamos con "Main", y precediéndolo por ciertas palabras reservadas, como "public static
void", cuyo significado iremos viendo muy pronto. Después, entre llaves indicaremos todos los
pasos que queremos que dé ese "trozo de programa".
Por ejemplo, podríamos crear una función llamada "saludar", que escribiera varios mensajes en
la pantalla:
&
) * + [ & ,
) * + #
) * + " N 5 /
Ahora desde dentro del cuerpo de nuestro programa, podríamos " " a esa función:
& '
Así conseguimos que nuestro programa principal sea más fácil de leer.
Un detalle importante: tanto la función habitual "Main" como la nueva función "Saludar" serían
parte de nuestra "class", es decir, el fuente completo sería así:
=C???????????????????????????C=
=C "# ) R 4G8 C=
=C # 4G C=
=C C=
=C < C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4G
&
) * + [ & ,
) * + #
) * + " N 5 /
& '
) * + M (
Como ejemplo más detallado, la parte principal de una agenda podría ser simplemente:
; ; <
'
W
%+ / ; ,
3+ ! ; ,
@+ ; ,
d
( ' ,
Es muy frecuente que nos interese además indicarle a nuestra función ciertos datos especiales
con los que queremos que trabaje. Por ejemplo, si escribimos en pantalla números reales con
frecuencia, nos puede resultar útil que nos los muestre con el formato que nos interese. Lo
podríamos hacer así:
& / !
) * + ,
/ 3 @!
(recordemos que el sufijo "f" es para indicar al compilador que trate ese número como un
"float", porque de lo contrario, al ver que tiene cifras decimales, lo tomaría como "double", que
permite mayor precisión... pero a cambio nosotros tendríamos un mensaje de error en nuestro
programa, diciendo que estamos dando un dato "double" a una función que espera un "float").
=C???????????????????????????C=
=C "# ) R 4T8 C=
=C # 4T C=
=C C=
=C < C=
=C / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 4T
& / !
) * + ,
& '
! 6
6" E %!
) * + " 8
/ 6
) * + ( 8
/ 3 @!
Estos datos adicionales que indicamos a la función es lo que llamaremos sus "parámetros".
Como se ve en el ejemplo, tenemos que indicar un nombre para cada parámetro (puede haber
varios) y el tipo de datos que corresponde a ese parámetro. Si hay más de un parámetro,
deberemos indicar el tipo y el nombre para cada uno de ellos, y separarlos entre comas:
& 6 (
(% 8 , 5 5
Cuando queremos dejar claro que una función no tiene que devolver ningún valor, podemos
hacerlo indicando al principio que el tipo de datos va a ser "void" (nulo), como hacíamos hasta
ahora con "Main" y como hicimos con nuestra función "saludar".
Pero eso no es lo que ocurre con las funciones matemáticas que estamos acostumbrados a
manejar: sí devuelven un valor, el resultado de una operación.
De igual modo, para nosotros también será habitual que queramos que nuestra función realice
una serie de cálculos y nos "devuelva" (return, en inglés) el resultado de esos cálculos, para
poderlo usar desde cualquier otra parte de nuestro programa. Por ejemplo, podríamos crear
una función para elevar un número entero al cuadrado así:
" E
=C???????????????????????????C=
=C "# ) R 428 C=
=C # 42 C=
=C C=
=C < C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# 42
& '
" E
"
) * + " H$I H%I
) * + ( @ H$I @
Podemos hacer una función que nos diga cual es el mayor de dos números reales así:
! ( ! % ! 3
# % $ 3
%
• Crear una función que borre la pantalla dibujando 25 líneas en blanco. No debe
devolver ningún valor.
• Crear una función que calcule el cubo de un número real (float). El resultado deberá ser
otro número real. Probar esta función para calcular el cubo de 3.2 y el de 5.
• Crear una función que calcule cual es el menor de dos números enteros. El resultado
será otro número entero.
• Crear una función llamada "signo", que reciba un número real, y devuelva un número
entero con el valor: D1 si el número es negativo, 1 si es positivo o 0 si es cero.
• Crear una función que devuelva la primera letra de una cadena de texto. Probar esta
función para calcular la primera letra de la frase "Hola".
• Crear una función que devuelva la última letra de una cadena de texto. Probar esta
función para calcular la última letra de la frase "Hola".
• Crear una función que reciba un número y muestre en pantalla el perímetro y la
superficie de un cuadrado que tenga como lado el número que se ha indicado como
parámetro.
(( 8
Hasta ahora, hemos declarado las variables dentro de "Main". Ahora nuestros programas tienen
varios "bloques", así que se comportarán de forma distinta según donde declaremos las
variables.
Las variables se pueden declarar dentro de un bloque (una función), y entonces sólo ese bloque
las conocerá, no se podrán usar desde ningún otro bloque del programa. Es lo que llamaremos
"variables ".
Por el contrario, si declaramos una variable al comienzo del programa, fuera de todos los
"bloques" de programa, será una " ", a la que se podrá acceder desde cualquier
parte.
Vamos a verlo con un ejemplo. Crearemos una función que calcule la potencia de un número
entero (un número elevado a otro), y el cuerpo del programa que la use.
@ & E 9 @ e @ e @ e @ e @
(multiplicamos 5 veces el 3 por sí mismo). En general, como nos pueden pedir cosas como "6
elevado a 100" (o en general números que pueden ser grandes), usaremos la orden "for" para
multiplicar tantas veces como haga falta:
=C???????????????????????????C=
=C "# ) R E$8 C=
=C # E$ C=
=C C=
=C "# ! 7 C=
=C & / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# E$
[ "6
" % =C X N & ( C=
=C / C=
=C B C=
=C / , & N / / C=
& '
% 3
) * + J / 8
% " ) & @3 ) +
) * + J 6 8
3 " ) & @3 ) +
En este caso, las variables "temporal" e "i" son locales a la función "potencia": para "Main" no
existen. Si en "Main" intentáramos hacer i=5; obtendríamos un mensaje de error.
De igual modo, "num1" y "num2" son locales para "main": desde la función "potencia" no
podemos acceder a su valor (ni para leerlo ni para modificarlo), sólo desde "main".
En general, deberemos intentar que la mayor cantidad de variables posible sean locales (lo
ideal sería que todas lo fueran). Así hacemos que cada parte del programa trabaje con sus
propios datos, y ayudamos a evitar que un error en un trozo de programa pueda afectar al
resto. La forma correcta de pasar datos entre distintos trozos de programa es usando los
parámetros de cada función, como en el anterior ejemplo.
:
• Crear una función "pedirEntero", que reciba como parámetros el texto que se debe
mostrar en pantalla, el valor mínimo aceptable y el valor máximo aceptable. Deberá
pedir al usuario que introduzca el valor tantas veces como sea necesario, volvérselo a
pedir en caso de error, y devolver un valor correcto. Probarlo con un programa que
pida al usuario un año entre 1800 y 2100.
• Crear una función "escribirTablaMultiplicar", que reciba como parámetro un número
entero, y escriba la tabla de multiplicar de ese número (por ejemplo, para el 3 deberá
llegar desde 3x0=0 hasta 3x10=30).
• Crear una función "esPrimo", que reciba un número y devuelva el valor booleano "true"
si es un número primo o "false" en caso contrario.
• Crear una función que reciba una cadena y una letra, y devuelva la cantidad de veces
que dicha letra aparece en la cadena. Por ejemplo, si la cadena es "Barcelona" y la letra
es 'a', debería devolver 2 (aparece 2 veces).
• Crear una función que reciba un numero cualquiera y que devuelva como resultado la
suma de sus dígitos. Por ejemplo, si el número fuera 123 la suma sería 6.
• Crear una función que reciba una letra y un número, y escriba un "triángulo" formado
por esa letra, que tenga como anchura inicial la que se ha indicado. Por ejemplo, si la
letra es * y la anchura es 4, debería escribir
****
***
**
*
(+ ,
¿Qué ocurre si damos el mismo nombre a dos variables locales? Vamos a comprobarlo con un
ejemplo:
=C???????????????????????????C=
=C "# ) R E%8 C=
=C # E% C=
=C C=
=C ; & / C=
=C / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# E%
& /
" G
& '
" E
) * + & H$I
/
) * + : & H$I
& E
: & E
¿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:
=C???????????????????????????C=
=C "# ) R E38 C=
=C # E3 C=
=C C=
=C ] & / , / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# E3
" G
& /
& '
) * + & H$I
/
) * + : & H$I
Dentro de poco, hablaremos de por qué cada uno de los bloques de nuestro programa, e
incluso las "variables globales", tienen delante la palabra "static". Será cuando tratemos la
"Programación Orientada a Objetos", en el próximo tema.
(- # , '
Podemos modificar el valor de un dato que recibamos como parámetro, pero posiblemente el
resultado no será el que esperamos. Vamos a verlo con un ejemplo:
=C???????????????????????????C=
=C "# ) R E@8 C=
=C # E@ C=
=C C=
=C ' ! & / C=
=C / M C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# E@
& 6
) * + " & / & H$I 6
6 " 6 ! 3
) * + ( & H$I 6
& '
" E
) * + & H$I
) * + : & H$I
& E
" & / & E
( & %$
: & E
Vemos que al salir de la función, los cambios que hagamos a esa variable que se ha recibido
como parámetro no se conservan.
Esto se debe a que, si no indicamos otra cosa, los parámetros " ", es decir,
la función no recibe los datos originales, sino una copia de ellos. Si modificamos algo, estamos
cambiando una copia de los datos originales, no dichos datos.
Si queremos que los cambios se conserven, basta con hacer un pequeño cambio: indicar que la
variable se va a pasar " ", lo que se indica usando la palabra "ref", tanto en la
declaración de la función como en la llamada, así:
=C???????????????????????????C=
=C "# ) R E48 C=
=C # E4 C=
=C C=
=C ' ! & / C=
=C / M C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# E4
& # 6
) * + " & / & H$I 6
6 " 6 ! 3
) * + ( & H$I 6
& '
" E
Revisión 0.97 – Página 102
Introducción a la programación con C#, por Nacho Cabanes
) * + & H$I
#
) * + : & H$I
& E
" & / & E
( & %$
: & %$
El hecho de poder modificar valores que se reciban como parámetros abre una posibilidad que
no se podría conseguir de otra forma: con "return" sólo se puede devolver un valor de una
función, pero con parámetros pasados por referencia podríamos devolver más de un dato. Por
ejemplo, podríamos crear una función que intercambiara los valores de dos variables:
& / # 6 # (
La posibilidad de pasar parámetros por valor y por referencia existe en la mayoría de lenguajes
de programación. En el caso de C# existe alguna posibilidad adicional que no existe en otros
lenguajes, como los "parámetros de salida". Las veremos más adelante.
:
• Crear una función "intercambia", que intercambie el valor de los dos números enteros
que se le indiquen como parámetro.
• Crear una función "iniciales", que reciba una cadena como "Nacho Cabanes" y devuelva
las letras N y C (primera letra, y letra situada tras el primer espacio), usando
parámetros por referencia.
(.
En algunos lenguajes, una función debe estar declarada antes de usarse. Esto no es necesario
en C#. Por ejemplo, podríamos rescribir el fuente anterior, de modo que "Main" aparezca en
primer lugar y "duplica" aparezca después, y seguiría compilando y funcionando igual:
=C???????????????????????????C=
=C "# ) R EE8 C=
=C # EE C=
=C C=
=C < 7 ' C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
Revisión 0.97 – Página 103
Introducción a la programación con C#, por Nacho Cabanes
"# EE
& '
" E
) * + & H$I
#
) * + : & H$I
& # 6
) * + " & / & H$I 6
6 " 6 ! 3
) * + ( & H$I 6
(9 : , $
D E#
En un programa de gestión o una utilidad que nos ayuda a administrar un sistema, no es
habitual que podamos permitir que las cosas ocurran al azar. Pero los juegos se encuentran
muchas veces entre los ejercicios de programación más completos, y para un juego sí suele ser
conveniente que haya algo de azar, para que una partida no sea exactamente igual a la
anterior.
== ) /#
"
== O K &
" 6 % %$$
Podemos hacer que sea realmente un poco más aleatorio si en la primera orden le indicamos
que tome como semilla el instante actual:
De hecho, una forma muy simple de obtener un número "casi al azar" entre 0 y 999 es tomar
las milésimas de segundo de la hora actual:
Vamos a ver un ejemplo, que muestre en pantalla un número al azar entre 1 y 10:
=C???????????????????????????C=
=C "# ) R EF8 C=
=C # EF C=
=C C=
=C K J C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# EF
& '
!
• Crear un programa que genere un número al azar entre 1 y 100. El usuario tendrá 6
oportunidades para acertarlo.
• Mejorar el programa del ahorcado propuesto en el apartado 4.4.8, para que la palabra
a adivinar no sea tecleado por un segundo usuario, sino que se escoja al azar de un
"array" de palabras prefijadas (por ejemplo, nombres de ciudades).
D ;
En C# tenemos muchas funciones matemáticas predefinidas, como:
• Sin(x): Seno
• Sinh(x): Seno hiperbólico
• Sqrt(x): Raíz cuadrada
• Tan(x): Tangente
• Tanh(x): Tangente hiperbólica
!
• Crear un programa que halle cualquier raíz de un número. El usuario deberá indicar el
número (por ejemplo, 2) y el índice de la raíz (por ejemplo, 3 para la raíz cúbica). Pista:
hallar la raíz cúbica de 2 es lo mismo que elevar 2 a 1/3.
• Crear un programa que resuelva ecuaciones de segundo grado, del tipo ax2 + bx + c =
0 El usuario deberá introducir los valores de a, b y c. Se deberá crear una función
"raicesSegundoGrado", que recibirá como parámetros 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 solución no existe, se devolverá como valor 100.000 para
esa solución. Pista: la solución se calcula con
x = Db ± raíz (b2 – 4·a·c) / 2·a
D% F 8 ( 8 G
Pero en C# hay muchas más funciones de lo que parece. De hecho, salvo algunas palabras
reservadas (int, float, string, if, switch, for, do, while...), gran parte de lo que hasta ahora
hemos llamado "órdenes", son realmente "funciones", como Console.ReadLine o
Console.WriteLine. Nos iremos encontrando con otras funciones a medida que avancemos.
( 0
Una función recursiva es aquella que se define a partir de ella misma. Dentro de las
matemáticas tenemos varios ejemplos. Uno clásico es el "factorial de un número":
S 9 e ?% e ?3 e e @ e 3 e %
?% S 9 ?% e ?3 e ?@ e e @ e 3 e %
Entonces podemos escribir el factorial de un número a partir del factorial del siguiente número:
S 9 e ?% S
Esta es la definición recursiva del factorial, ni más ni menos. Esto, programando, se haría:
=C???????????????????????????C=
=C "# ) R EG8 C=
=C # EG C=
=C C=
=C < & 8 C=
=C ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# EG
, !
# ""% == : , N
%
! ! % == %B , 7
& '
) * + J K 8
" ) & @3 ( ) +
) * + ! 8 H$I !
¿Qué utilidad tiene esto? Pues más de la que parece: muchos problemas complicados se
pueden expresar a partir de otro más sencillo. En muchos de esos casos, ese problema se
podrá expresar de forma recursiva. Más adelante veremos algún otro ejemplo.
!
• Crear una función que calcule el valor de elevar un número entero a otro número
entero (por ejemplo, 5 elevado a 3 = 53 = 5 ·5 ·5 = 125). Esta función se debe crear
de forma recursiva.
• Como alternativa, crear una función que calcule el valor de elevar un número entero a
otro número entero de forma NO recursiva (lo que llamaremos "de forma iterativa"),
usando la orden "for".
• Crear un programa que emplee recursividad para calcular un número de la serie
Fibonacci (en la que los dos primeros elementos valen 1, y para los restantes, cada
elemento es la suma de los dos anteriores).
• Crear un programa que emplee recursividad para calcular la suma de los elementos de
un vector.
• Crear un programa que emplee recursividad para calcular el mayor de los elementos de
un vector.
• Crear un programa que emplee recursividad para dar la vuelta a una cadena de
caracteres (por ejemplo, a partir de "Hola" devolvería "aloH").
• Crear, tanto de forma recursiva como de forma iterativa, una función diga si una
cadena de caracteres es simétrica (un palíndromo). Por ejemplo,
"DABALEARROZALAZORRAELABAD" es un palíndromo.
• Crear un programa que encuentre el máximo común divisor de dos números usando el
algoritmo de Euclides: Dados dos números enteros positivos m y n, tal que m > n, para
encontrar su máximo común divisor, es decir, el mayor entero positivo que divide a
ambos: D Dividir m por n para obtener el resto r (0 ≤ r < n) ; D Si r = 0, el MCD es n.; D
Si no, el máximo común divisor es MCD(n,r).
( ' 5# 5
Es muy frecuente que un programa que usamos desde la "línea de comandos" tenga ciertas
opciones que le indicamos como argumentos. Por ejemplo, bajo Linux o cualquier otro sistema
operativo de la familia Unix, podemos ver la lista detallada de ficheros que terminan en .c
haciendo
f C
En este caso, la orden sería "ls", y las dos opciones (argumentos o parámetros) que le
indicamos son "Dl" y "*.c".
Pues bien, estas opciones que se le pasan al programa se pueden leer desde C#. Se hace
indicando un parámetro especial en Main, un array de strings:
Para conocer esos parámetros lo haríamos de la misma forma que se recorre habitualmente un
array cuyo tamaño no conocemos: con un "for" que termine en la longitud ("Length") del array:
# " $ & , + ,
Es decir, entre paréntesis indicamos un cierto código, que suele ser (por convenio) un 0 si no
ha habido ningún error, u otro código distinto en caso de que sí exista algún error.
Este valor se podría comprobar desde el sistema operativo. Por ejemplo, en MsDos y Windows
se lee con "IF ERRORLEVEL", así:
Una forma alternativa de que "Main" indique errores al sistema operativo es no declarándolo
como "void", sino como "int", y empleando entonces la orden "return" cuando nos interese:
' ,-. ,
=C???????????????????????????C=
=C "# ) R ET8 C=
=C # ET C=
=C C=
=C M ( & C=
=C ' C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# ET
' ,-. ,
Revisión 0.97 – Página 109
Introducción a la programación con C#, por Nacho Cabanes
) * + M 8 H$I , + ,
# " $ & , + ,
# , + , "" $
) * + " / ,K M S
" & "6 %
:
• Crear un programa llamado "suma", que calcule (y muestre) la suma de dos números
que se le indiquen como parámetro. Por ejemplo, si se teclea "suma 2 3" deberá
responder "5", y si se teclea "suma 2" deberá responder "no hay suficientes datos y
devolver un código de error 1.
• Crear una calculadora básica, llamada "calcula", que deberá sumar, restar, multiplicar o
dividir los dos números que se le indiquen como parámetros. Ejemplos de su uso sería
"calcula 2 + 3" o "calcula 5 * 60".
1 F 4
+ ; < =
Hasta ahora hemos estado "cuadriculando" todo para obtener algoritmos: tratábamos de
convertir cualquier cosa en una función que pudiéramos emplear en nuestros programas.
Cuando teníamos claros los pasos que había que dar, buscábamos las variables necesarias para
dar esos pasos.
Pero no todo lo que nos rodea es tan fácil de cuadricular. Supongamos por ejemplo que
tenemos que introducir datos sobre una puerta en nuestro programa. ¿Nos limitamos a
programar los procedimientos AbrirPuerta y CerrarPuerta? Al menos, deberíamos ir a la zona
de declaración de variables, y allí guardaríamos otras datos como su tamaño, color, etc.
No está mal, pero es "antinatural": una puerta es un conjunto: no podemos separar su color de
su tamaño, o de la forma en que debemos abrirla o cerrarla. Sus características son tanto las
físicas (lo que hasta ahora llamábamos variables) como sus comportamientos en distintas
circunstancias (lo que para nosotros eran las funciones). Todo ello va unido, formando un
" ".
Por otra parte, si tenemos que explicar a alguien lo que es el portón de un garaje, y ese alguien
no lo ha visto nunca, pero conoce cómo es la puerta de su casa, le podemos decir "se parece a
una puerta de una casa, pero es más grande para que quepan los coches, está hecha de metal
en vez de madera...". Es decir, podemos describir unos objetos a partir de lo que conocemos de
otros.
Finalmente, conviene recordar que "abrir" no se refiere sólo a una puerta. También podemos
hablar de abrir una ventana o un libro, por ejemplo.
Con esto, hemos comentado casi sin saberlo las tres características más importantes de la
Programación Orientada a Objetos (OOP):
• + : Unos objetos pueden heredar métodos y datos de otros. Esto hace más
fácil definir objetos nuevos a partir de otros que ya teníamos anteriormente (como
ocurría con el portón y la puerta) y facilitará la reescritura de los programas, pudiendo
aprovechar buena parte de los anteriores... si están bien diseñados.
+ & !"
Vamos con los detalles. Las '. se definen de forma parecida a los registros (struct),
sólo que ahora también incluirán funciones. Así, la clase "Puerta" que mencionábamos antes se
podría declarar así:
== :
== :
== ) ! O[
/ / == :/
& :/
/ "
& )
/ " #
) * + : 8 H$I
) * + : 8 H$I
) * + ) 8 H$I
) * + :/ 8 H$I /
== <
Como se puede observar, los objetos de la clase "Puerta" tendrán un ancho, un alto, un color, y
un estado (abierta o no abierta), y además se podrán abrir o cerrar (y además, nos pueden
"mostrar su estado, para comprobar que todo funciona correctamente).
Para declarar estos objetos que pertenecen a la clase "Puerta", usaremos la palabra "new",
igual que hacíamos con los "arrays":
"
:/
' "
Vamos a completar un programa de prueba que use un objeto de esta clase (una "Puerta"):
=C???????????????????????????C=
=C "# ) R E28 C=
=C # E2 C=
=C C=
=C # C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
== :
== :
== ) ! O[
/ / == :/
& :/
/ "
& )
/ " #
) * + : 8 H$I
) * + : 8 H$I
) * + ) 8 H$I
) * + :/ 8 H$I /
== <
"# E2
& '
"
) * + X
' "
) * + Y X /
:/
' "
Este fuente ya no contiene una única clase (class), como todos nuestros ejemplos anteriores,
sino dos clases distintas:
• La clase "Puerta", que son los nuevos objetos con los que vamos a practicar.
• La clase "Ejemplo59", que representa a nuestra aplicación.
X
: 8 $
: 8 $
) 8 $
:/ 8 <
X /
: 8 $
: 8 $
) 8 $
:/ 8
Se puede ver que en C#, al contrario que en otros lenguajes, las variables que forman parte de
una clase (los "atributos") tienen un valor inicial predefinido: 0 para los números, una cadena
vacía para las cadenas de texto, o "False" para los datos booleanos.
Vemos también que se accede a los métodos y a los datos precediendo el nombre de cada uno
por el nombre de la variable y por un punto, como hacíamos con los registros (struct). Aun así,
en nuestro caso no podemos hacer directamente "p.abierta = true", por dos motivos:
• El atributo "abierta" no tiene delante la palabra "public"; por lo que no es público, sino
privado, y no será accesible desde otras clases (en nuestro caso, desde Ejemplo59).
• Los puristas de la Programación Orientada a Objetos recomiendan que no se acceda
directamente a los atributos, sino que siempre se modifiquen usando métodos
auxiliares (por ejemplo, nuestro "Abrir"), y que se lea su valor también usando una
función. Esto es lo que se conoce como " ". 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 más adelante):
O :
: & X
" & X
También puede desconcertar que en "Main" aparezca la palabra "static", mientras que no lo
hace en los métodos de la clase "Puerta". Veremos este detalle un poco más adelante.
:
• Crear una clase llamada Persona, en el fichero "persona.cs". Esta clase deberá tener un
atributo "nombre", de tipo string. También deberá tener un método "SetNombre", de
tipo void y con un parámetro string, que permita cambiar el valor del nombre.
Finalmente, también tendrá un método "Saludar", que escribirá en pantalla "Hola, soy "
seguido de su nombre. Crear también una clase llamada PruebaPersona. Esta clase
deberá contener sólo la función Main, que creará dos objetos de tipo Persona, les
asignará un nombre y les pedirá que saluden.
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 numeración consecutiva de los ejemplos, pero que sí se
debería seguir en un proyecto de mayor tamaño, formado por varias clases.
! % ! 3 ! @
En ese caso, el ejecutable obtenido tendía el nombre del primero de los fuentes (fuente1.exe).
Podemos cambiar el nombre del ejecutable con la opción "Dout" de Mono:
! % ! 3 ! @ ? 8 # 6
Vamos a dividir en dos fuentes el último ejemplo y a ver cómo se compilaría. La primera clase
podría ser ésta:
=C???????????????????????????C=
=C "# ) R E2/8 C=
=C # E2/ C=
=C C=
=C ; C=
=C ! ! % C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
Revisión 0.97 – Página 115
Introducción a la programación con C#, por Nacho Cabanes
== :
== :
== ) ! O[
/ / == :/
& :/
/ "
& )
/ " #
) * + : 8 H$I
) * + : 8 H$I
) * + ) 8 H$I
) * + :/ 8 H$I /
== <
=C???????????????????????????C=
=C "# ) R E2 8 C=
=C # E2 C=
=C C=
=C ; C=
=C ! ! 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# E2
& '
"
) * + X
' "
) * + Y X /
:/
' "
Revisión 0.97 – Página 116
Introducción a la programación con C#, por Nacho Cabanes
Y lo compilaríamos con:
# E2/ # E2 ? 8 # E2/( 6
Aun así, para estos proyectos formados por varias clases, lo ideal es usar algún entorno más
avanzado, como SharpDevelop o VisualStudio, que permitan crear todas las clases con
comodidad, saltar de una clase a clase otra rápidamente, que marquen dentro del propio editor
la línea en la que están los errores... por eso, al final de este tema tendrás un apartado con una
introducción al uso de SharpDevelop.
:
• 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. También deberá tener un método "SetNombre", de tipo void y
con un parámetro string, que permita cambiar el valor del nombre. Finalmente, también
tendrá un método "Saludar", que escribirá en pantalla "Hola, soy " seguido de su
nombre. Crear también una clase llamada PruebaPersona, en el fichero
"pruebaPersona.cs". Esta clase deberá contener sólo la función Main, que creará dos
objetos de tipo Persona, les asignará un nombre y les pedirá que saluden.
+ 4 8
Vamos a ver ahora cómo definir una nueva clase de objetos a partir de otra ya existente. Por
ejemplo, vamos a crear una clase "Porton" a partir de la clase "Puerta". Un portón tendrá las
mismas características que una puerta (ancho, alto, color, abierto o no), pero además se podrá
bloquear, lo que supondrá un nuevo atributo y nuevos métodos para bloquear y desbloquear:
/ / N
& [ N
/ N "
& ; / N
/ N " #
Con "public class Porton: Puerta" indicamos que Porton debe "heredar" todo lo que ya
habíamos definido para Puerta. Por eso, no hace falta indicar nuevamente que un Portón tendrá
un cierto ancho, o un color, o que se puede abrir: todo eso lo tiene por ser un "descendiente"
de Puerta.
No tenemos por qué heredar todo; también podemos "redefinir" algo que ya existía. Por
ejemplo, nos puede interesar que "MostrarEstado" ahora nos diga también si la puerta está
bloqueada. Para eso, basta con volverlo a declarar y añadir la palabra " &" para indicar al
compilador de C# que sabemos que ya existe ese método y que sabemos seguro que lo
queremos redefinir:
) * + : 8 H$I
) * + : 8 H$I
) * + ) 8 H$I
) * + :/ 8 H$I /
) * + [ N 8 H$I / N
Aun así, esto todavía no funciona: los atributos de una Puerta, como el "ancho" y el "alto"
estaban declarados como "privados" (es lo que se considera si no decimos los contrario), por lo
que no son accesibles desde ninguna otra clase, ni siquiera desde Porton.
La solución más razonable no es declararlos como "public", porque no queremos que sean
accesibles desde cualquier sitio. Sólo querríamos que esos datos estuvieran disponibles para
todos los tipos de Puerta, incluyendo sus "descendientes", como un Porton. Esto se puede
conseguir usando otro método de acceso: " ". Todo lo que declaremos como
"protected" será accesible por las clases derivadas de la actual, pero por nadie más:
== :
== :
== ) ! O[
/ / == :/
& :/
(Si quisiéramos dejar claro que algún elemento de una clase debe ser totalmente privado,
podemos usar la palabra " ", en vez de "public" o "protected").
Un fuente completo que declarase la clase Puerta, la clase Porton a partir de ella, y que además
contuviese un pequeño "Main" de prueba podría ser:
=C???????????????????????????C=
=C "# ) R F$8 C=
=C # F$ C=
=C C=
Revisión 0.97 – Página 118
Introducción a la programación con C#, por Nacho Cabanes
=C , # C=
=C 8 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
== ???????????????????????????????
== :
== :
== ) ! O[
/ / == :/
& :/
/ "
& )
/ " #
) * + : 8 H$I
) * + : 8 H$I
) * + ) 8 H$I
) * + :/ 8 H$I /
== <
== ???????????????????????????????
+
/ / N
& [ N
/ N "
& ; / N
/ N " #
) * + : 8 H$I
) * + : 8 H$I
) * + ) 8 H$I
) * + :/ 8 H$I /
) * + [ N 8 H$I / N
== <
== ???????????????????????????????
"# F$
& '
"
) * + X
' "
) * + Y X / N
[ N
' "
) * + Y X / N ( /
; / N
:/
' "
:
• Ampliar las clases del primer ejercicio propuesto, creando un nuevo proyecto con las
siguientes características: La clase Persona no cambia. Se creará una nueva clase
PersonaInglesa, en el fichero "personaInglesa.cs". Esta clase deberá heredar las
características de la clase "Persona", y añadir un método "TomarTe", de tipo void, que
escribirá en pantalla "Estoy tomando té". Crear también una clase llamada
PruebaPersona2, en el fichero "pruebaPersona2.cs". Esta clase deberá contener sólo la
función Main, que creará dos objetos de tipo Persona y uno de tipo PersonaInglesa, les
asignará un nombre, les pedirá que saluden y pedirá a la persona inglesa que tome té.
• Ampliar las clases del segundo ejercicio propuesto, creando un nuevo proyecto con las
siguientes características: La clase Persona no cambia. La clase PersonaInglesa se
modificará para que redefina el método "Saludar", para que escriba en pantalla "Hi, I
am " seguido de su nombre. Se creará una nueva clase PersonaItaliana, en el fichero
"personaItaliana.cs". Esta clase deberá heredar las características de la clase
"Persona", pero redefinir el método "Saludar", para que escriba en pantalla "Ciao".
Crear también una clase llamada PruebaPersona3, en el fichero " pruebaPersona3.cs".
Esta clase deberá contener sólo la función 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é.
+ % ;! 7 =
En estos primeros ejemplos, hemos "pensado" qué objetos necesitaríamos, y hemos empezado
a teclear directamente para implementarlos. Esto no es lo habitual. Normalmente, se usan
que nos ayuden a visualizar las clases y las relaciones que existen
entre ellas. También se puede dibujar directamente en papel para aclararnos las ideas, pero el
empleo de herramientas informáticas tiene una ventaja adicional: algunas de ellas nos permiten
generar automáticamente un esqueleto del programa.
La metodología más extendida actualmente para diseñar estos objetos y sus interacciones
(además de otras muchas cosas) se conoce como -)/ (Unified Modelling Language, lenguaje
de modelado unificado). El estándar UML propone distintos tipos de diagramas para representar
los posibles "casos de uso" de una aplicación, la secuencia de acciones que se debe seguir, las
clases que la van a integrar (es lo que a nosotros nos interesa en este momento), etc.
Vamos a ver la apariencia que tendría un " ". En concreto, vamos a ver un
ejemplo usando 0 -)/, que es una herramienta gratuita de modelado UML, que está
creada en Java, por lo que se puede utilizar desde multitud de sistemas operativos.
Ampliando el ejemplo anterior, vamos a suponer que queremos hacer un sistema "domótico",
para automatizar ciertas funciones en una casa: apertura y cierre de ventanas y puertas,
encendido de calefacción, etc.
Este diagrama es una forma más simple de ver las clases existentes y las relaciones entre ellas.
Si generamos las clases a partir del diagrama, tendremos parte del trabajo hecho: ya "sólo" nos
quedará rellenar los detalles de métodos como "Abrir", pero el esqueleto de todas las clases ya
estará "escrito" para nosotros.
+( 5 5
Desde un principio, nos hemos encontrado con que "Main" siempre iba acompañado de la
palabra "static". En cambio, los métodos (funciones) que pertenecen a nuestros objetos no los
estamos declarando como "static". Vamos a ver el motivo:
La palabra "static" delante de un atributo (una variable) de una clase, indica que es una
"variable de clase", es decir, que su valor es el mismo para todos los objetos de la clase. Por
ejemplo, si hablamos de coches convencionales, podríamos suponer que el atributo
"numeroDeRuedas" va a valer 4 para cualquier objeto que pertenezca a esa clase (cualquier
coches). Por eso, se podría declarar como "static".
De igual modo, si un método (una función) está precedido por la palabra "static", indica que es
un "método de clase", es decir, un método que se podría usar sin necesidad de declarar ningún
objeto de la clase. Por ejemplo, si queremos que se pueda usar la función "BorrarPantalla" de
una clase "Hardware" sin necesidad de crear primero un objeto perteneciente a esa clase, lo
podríamos conseguir así:
>
& [
que desde dentro de "Main" (incluso perteneciente a otra clase) se usaría con el nombre de la
clase delante:
Z ,
) J
> [
Desde una función "static" no se puede llamar a otras funciones que no lo sean. Por eso, como
nuestro "Main" debe ser static, deberemos siempre elegir entre:
• Que todas las demás funciones de nuestro fuente también estén declaradas como
"static", por lo que podrán ser utilizadas desde "Main".
• Declarar un objeto de la clase correspondiente, y entonces sí podremos acceder a sus
métodos desde "Main":
"#
+ J Z ,
Z , # " Z ,
# ) J
++ !
Hemos visto que al declarar una clase, se dan valores por defecto para los atributos. Por
ejemplo, para un número entero, se le da el valor 0. Pero puede ocurrir que nosotros deseemos
dar valores iniciales que no sean cero. Esto se puede conseguir declarando un " "
para la clase.
Un constructor es una función especial, que se pone en marcha cuando se crea un objeto de
una clase, y se suele usar para dar esos valores iniciales, para reservar memoria si fuera
necesario, etc.
Se declara usando el mismo nombre que el de la clase, y sin ningún tipo de retorno. Por
ejemplo, un "constructor" para la clase Puerta que le diera los valores iniciales de 100 para el
ancho, 200 para el alto, etc., podría ser así:
" %$$
" 3$$
" $6<<<<<<
/ " #
Revisión 0.97 – Página 123
Introducción a la programación con C#, por Nacho Cabanes
Podemos tener más de un constructor, cada uno con distintos parámetros. Por ejemplo, puede
haber otro constructor que nos permita indicar el ancho y el alto:
"
"
" $6<<<<<<
/ " #
Ahora, si declaramos un objeto de la clase puerta con "Puerta p = new Puerta();" tendrá de
ancho 100 y de alto 200, mientras que si lo declaramos con "Puerta p2 = new Puerta(90,220);"
tendrá 90 como ancho y 220 como alto.
Un programa de ejemplo que usara estos dos constructores para crear dos puertas con
características iniciales distintas podría ser:
=C???????????????????????????C=
=C "# ) R F%8 C=
=C # F% C=
=C C=
=C # C=
=C ) C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
== :
== :
== ) ! O[
/ / == :/
" %$$
" 3$$
" $6<<<<<<
/ " #
"
"
" $6<<<<<<
/ " #
& :/
/ "
& )
/ " #
) * + : 8 H$I
) * + : 8 H$I
) * + ) 8 H$I
) * + :/ 8 H$I /
== <
"# F%
& '
"
3 " 2$ 33$
) * + X
' "
) * + Y X /
:/
' "
) * + ,
3 ' "
Nota: al igual que existen los "constructores", también podemos indicar un " " para
una clase, que se encargue de liberar la memoria que pudiéramos haber reservado en nuestra
clase (no es nuestro caso, porque aún no sabemos manejar memoria dinámica) o para cerrar
ficheros abiertos (que tampoco sabemos).
Un "destructor" se llama igual que la clase, pero precedido por el símbolo "~", no tiene tipo de
retorno, y no necesita ser "public", como ocurre en este ejemplo:
== + /
Revisión 0.97 – Página 125
Introducción a la programación con C#, por Nacho Cabanes
== ) !
+- ,
Esos dos constructores "Puerta()" y "Puerta(int ancho, int alto)", que se llaman igual pero
reciben distintos parámetros, y se comportan de forma que puede ser distinta, son ejemplos de
" " (funciones que tienen el mismo nombre, pero distintos parámetros, y que
quizá no se comporten de igual forma).
Un concepto muy relacionado con el polimorfismo es el de " ": dos funciones están
sobrecargadas cuando se llaman igual, reciben el mismo número de parámetros, pero se
aplican a objetos distintos, así:
:/
/ :/
En este caso, la función "Abrir" está sobrecargada: se usa tanto para referirnos a abrir un libro
como para abrir una puerta. Se trata de dos acciones que no son exactamente iguales, que se
aplican a objetos distintos, pero que se llaman igual.
+. &
Cuando creamos objetos de una clase derivada, antes de llamar a su constructor se llama a los
constructores de las clases base, empezando por la más general y terminando por la más
específica. Por ejemplo, si creamos una clase "GatoSiamés", que deriva de una clase "Gato",
que a su vez procede de una clase "Animal", el orden de ejecución de los constructores sería:
Animal, Gato, GatoSiames, como se ve en este ejemplo:
=C???????????????????????????C=
=C "# ) R F38 C=
=C # F3 C=
=C C=
=C ) # C=
=C ) ( C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
) * +
== ??????????????????
+ :
) * +
== ??????????????????
O + :
) * + ,
== ??????????????????
O + O
) * + , 5
== ??????????????????
"# F3
& '
: % " :
O 3 " O
@ "
O 4 " O
,
, 5
:
• Crear un único fuente que contenga las siguientes clases:
Una clase Trabajador, cuyo constructor escriba en pantalla "Soy un trabajador".
Una clase Programador, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy programador".
Una clase Analista, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy analista".
Una clase Ingeniero, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy ingeniero".
Una clase IngenieroInformatico, que derive de Ingeniero, cuyo constructor
escriba en pantalla "Soy ingeniero informático".
Una clase "PruebaDeTrabajadores", que cree un objeto perteneciente a cada
una de esas clases.
+9 :
Es muy frecuente que no nos baste con tener un objeto de cada clase, sino que necesitemos
manipular varios objetos pertenecientes a la misma clase.
En ese caso, deberemos reservar memoria primero para el array, y luego para cada uno de los
elementos. Por ejemplo, podríamos tener un array de 5 perros, que crearíamos de esta forma:
-. " -E.
# /( " $ & E
- . "
=C???????????????????????????C=
=C "# ) R F@8 C=
=C # F@ C=
=C C=
=C \ # C=
=C : ( /# C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
) * +
== ??????????????????
+ :
) * +
== ??????????????????
"# F@
& '
-. " -E.
# /( " $ & E
- . "
:
• Crea una versión ampliada del anterior ejercicio propuesto, en la que no se cree un
único objeto de cada clase, sino un array de tres objetos.
Además, existe una peculiaridad curiosa: podemos crear un array de "Animales", pero luego
indicar que unos de ellos son perros, otros gatos, etc.,
Revisión 0.97 – Página 129
Introducción a la programación con C#, por Nacho Cabanes
: -. : " : -@.
: -$. "
: -%. " O
: -3. " O
=C???????????????????????????C=
=C "# ) R F48 C=
=C # F4 C=
=C C=
=C "# C=
=C : ( /# C=
=C & / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
) * +
== ??????????????????
+ :
) * +
== ??????????????????
O + :
) * + ,
== ??????????????????
O + O
) * + , 5
== ??????????????????
"# F4
& '
: -. : " : -T.
: -$. "
: -%. " O
: -3. " O
# /( "@ &G
: - . "
: -G. " :
,
, 5
+ > 5 5
En el ejemplo anterior hemos visto cómo crear un array de objetos, usando sólo la clase base,
pero insertando realmente objetos de cada una de las clases derivadas que nos interesaba, y
hemos visto que los constructores se llaman correctamente... pero con los métodos puede
haber problemas.
Vamos a verlo con un ejemplo, que en vez de tener constructores va a tener un único método
"Hablar", que se redefine en cada una de las clases hijas, y después comentaremos qué ocurre
al ejecutarlo:
=C???????????????????????????C=
=C "# ) R FE8 C=
=C # FE C=
=C C=
=C "# C=
=C : ( /# C=
=C & / C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
& /
) * + " ( M
== ??????????????????
+ :
& /
) * + O S
== ??????????????????
O + :
& /
) * + '
== ??????????????????
"# FE
& '
==
"
O O " O
: : " :
/
O /
: /
== + / B , /
) * +
== : (
: -. : " : -@.
: -$. "
: -%. " O
: -3. " :
: -$. /
: -%. /
: -3. /
O S
'
" ( M
" ( M
" ( M
" ( M
La primera parte era de esperar: si creamos un perro, debería decir "Guau", un gato debería
decir "Miau" y un animal genérico debería comunicarse. Eso es lo que se consigue con este
fragmento:
"
O O " O
: : " :
/
O /
: /
Revisión 0.97 – Página 133
Introducción a la programación con C#, por Nacho Cabanes
: -. : " : -@.
: -$. "
: -%. " O
: -3. " :
: -$. /
: -%. /
: -3. /
Es decir, como la clase base es "Animal", el primer elemento hace lo que corresponde a un
Animal genérico (decir "Estoy comunicándome"), a pesar de que hayamos dicho que se trata de
un Perro.
Generalmente, no será esto lo que queramos. Sería 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 método
"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 método "Hablar" como "virtual", y empleando en las
clases hijas la palabra "override" en vez de "new", así:
=C???????????????????????????C=
=C "# ) R FF8 C=
=C # FF C=
=C C=
=C "# C=
=C : ( /# C=
=C & / C=
=C & C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
& /
) * + " ( M
== ??????????????????
+ :
& /
) * + O S
== ??????????????????
O + :
& /
) * + '
== ??????????????????
"# FF
& '
==
"
O O " O
: : " :
/
O /
: /
== + / B , /
) * +
== : (
: -. : " : -@.
: -$. "
: -%. " O
: -3. " :
: -$. /
: -%. /
: -3. /
O S
'
" ( M
O S
'
" ( M
+ 5 5
Puede ocurrir que en un método de una clase hija no nos interese redefinir por completo las
posibilidades del método equivalente, sino ampliarlas. En ese caso, no hace falta que volvamos
a teclear todo lo que hacía el método de la clase base, sino que podemos llamarlo directaD
mente, precediéndolo de la palabra "base". Por ejemplo, podemos hacer que un Gato Siamés
hable igual que un Gato normal, pero diciendo "Pfff" después, así:
& /
/
) * + !!!
=C???????????????????????????C=
=C "# ) R FG8 C=
=C # FG C=
=C C=
=C "# C=
=C + C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
== ??????????????????
O + :
& /
) * + '
== ??????????????????
O + O
& /
/
) * + !!!
== ??????????????????
"# FG
& '
O O " O
O O 3 " O
O /
) * + == + /
O 3 /
Su resultado sería
'
'
!!!
, 6 (
+ 6 (
==
== N ,
+ 5 4 5*
Podemos hacer referencia al objeto que estamos usando, con "this":
' & : 6 (
6 " 6
( " (
En muchos casos, podemos evitarlo. Por ejemplo, normalmente es preferible usar otro nombre
en los parámetros:
6 " & g
( " & P
6 " & g
( " & P
Pero "this" tiene también otros usos. Por ejemplo, podemos crear un a partir de
otro que tenga distintos parámetros:
, 6 (
+ 6
! " (
==
" , " O ! ,
De modo que el personaje de un juego le podría indicar al adversario que él es su enemigo con
: & " ,
Es habitual usarlo para que una clase sepa a cual pertenece (por ejemplo, a qué casa pertenece
una puerta), porque de lo contrario, cada clase "conoce" a los objetos que contiene (la casa
"conoce" a sus puertas), pero no al contrario.
+ 1
Los "operadores" son los símbolos que se emplean para indicar ciertas operaciones. Por
ejemplo, el operador "+" se usa para indicar que queremos sumar dos números.
# 6"$ 6 & ( 6
# ("$ ( & ( (
& ' J-6 (. " %-6 (. 3-6 (.
& ' J
Desde "Main", calcularíamos una matriz como suma de otras dos haciendo simplemente
' J J@ " J% J3
!
• Desarrolla una clase "Matriz", que represente a una matriz de 3x3, con métodos para
indicar el valor que hay en una posición, leer el valor de una posición, escribir la matriz
en pantalla y sumar dos matrices.
+ % ,
En los programas de gran tamaño, lo habitual es que no se plantee como un único fichero
fuente de gran tamaño, sino como una serie de objetos que colaboran entre ellos, lo que
supone descomponer nuestro fuente en varias clases (y, por tanto, en varios ficheros).
En estos casos, un editor como el Bloc de Notas, o incluso como Notepad++, se queda corto.
Sería preferible un entorno que nos permitiera editar nuestros fuentes, compilarlos sin
necesidad de salir de él, que nos destacara las líneas que contienen errores, que nos mostrara
ayuda sobre la sintaxis de cada función, que nos permitiera depurar nuestros programas
avanzando paso a paso…
Existen entornos que nos permiten hacer todo eso, y además hacerlo gratis. El más conocido es
el Visual Studio de Microsoft, que en su versión Express incluye todo lo un programador novel
como nosotros puede necesitar. Una alternativa muy similar, pero algo más sencilla (lo que
supone que funcione más rápido en ordenadores no demasiado potentes) es SharpDevelop.
Vamos a ver las pautas básicas de manejo de ( 1 (que se aplicarían con muy pocos
cambios al caso de Visual Studio):
Comenzamos por descargar el fichero de instalación del entorno, desde su página oficial
(http://www.icsharpcode.net/opensource/sd/). La versión 3.1, para las versiones 2.0 y 3.5 de la
plataforma .Net, ocupa unos 15 Mb.
La instalación comenzará simplemente con hacer doble clic. Deberíamos ver una ventana
parecida a ésta:
Como es habitual, el siguiente paso será aceptar el contrato de licencia, después 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 instalación debería ser muy sencilla en Windows Vista y superiores, pero en Windows XP
quizá necesite que instalemos la versión 3.5 de la plataforma .Net (se puede hacer
gratuitamente desde su página 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 solución", y se nos mostrará los tipos de
proyectos para los que se nos podría crear un esqueleto vacío que después iríamos rellenando:
De estos tipos, el único que conocemos es una "Aplicación de Consola" (en C#, claro).
Deberemos escribir también el nombre., y aparecerá un esqueleto de aplicación que nosotros
sólo tendríamos que completar:
Cuando hayamos terminado de realizar nuestros cambios, podemos compilar el programa con el
botón:
(Si la ventana de nuestro programa se cierra tan rápido que no tenemos tiempo de leerla, nos
puede interesar añadir provisionalmente una línea ReadLine() al final del fuente, para que éste
se detenga hasta que pulsemos la tecla Intro)
Así prepararíamos y lanzaríamos un programa formado por un solo fuente. Si se trata de varios
fuentes, basta con ir añadiendo nuevas clases al proyecto. Lo conseguimos pulsando el botón
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 obtendríamos un nuevo esqueleto vacío (esta vez sin "Main"), que deberíamos completar.
Nuestro programa, que ahora estaría formado por dos clases, se compilaría y se ejecutaría de la
misma forma que cuando estaba integrado por una única clase.
:
• Crear un proyecto que contenga las siguientes clases (cada una en un fichero distinto):
Una clase Trabajador, cuyo constructor escriba en pantalla "Soy un trabajador".
Una clase Programador, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy programador".
Una clase Analista, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy analista".
Una clase Ingeniero, que derive de Trabajador, cuyo constructor escriba en
pantalla "Soy ingeniero".
3 & 4 8
- , 4
Para manejar ficheros, siempre deberemos realizar tres operaciones básicas:
• 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 además 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 sólo de lectura.
Vamos a ver un ejemplo, que cree un fichero de texto y escriba algo en él:
=C???????????????????????????C=
=C "# ) R G$ C=
=C # G$ C=
=C C=
=C " ! C=
=C 6 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W == *
"# G$
& '
* !
! " < ) 6 / 6
! * + " Q
! * "
! * + ( 7
! )
• Finalmente, debemos cerrar el fichero con Close, o podría quedar algún dato sin
guardar.
!
• 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).
- , 4
La estructura de un programa que leyera de un fichero de texto sería parecida:
=C???????????????????????????C=
=C "# ) R G%8 C=
=C # G% C=
=C C=
=C + ! C=
=C 6 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W ==
"# G%
& '
!
,
! " < W 6 / 6
" ! +
) * +
) * + ! +
! )
!
• Crea un programa que lea las tres primeras líneas del fichero creado en el apartado
anterior y las muestre en pantalla.
- 4 , , 4
Normalmente no querremos leer sólo una frase del fichero, sino procesar todo su contenido, de
principio a fin
En un fichero de texto, la forma de saber si hemos llegado al final es intentar leer una línea, y
comprobar si el resultado ha sido "null", lo que nos indicaría que no se ha podido leer y que,
por tanto estamos en el final del fichero.
Normalmente, si queremos procesar todo un fichero, esta lectura y comprobación debería ser
repetitiva, así:
=C???????????????????????????C=
=C "# ) R G38 C=
=C # G3 C=
=C C=
=C + ! C=
=C 6 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W ==
"# G3
& '
!
,
! " < W 6 / 6
" ! +
# %"
) * +
%"
! )
!
• 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. Después 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 después de cada 25 líneas, para que dé tiempo a
leerlo. Cuando el usuario pulse intro, se mostrarán las siguientes 25 líneas, y así hasta
que termine el fichero.
- % :7 , 4
La idea es sencilla: en vez de abrirlo con CreateText ("crear texto"), usaremos AppendText
("añadir texto"). Por ejemplo, podríamos crear un fichero, guardar información, cerrarlo, y
luego volverlo a abrir para añadir datos, de la siguiente forma:
=C???????????????????????????C=
=C "# ) R G@ C=
=C # G@ C=
=C C=
=C :L ! C=
=C 6 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W == *
"# G@
& '
* !
! " < ) 6 / 3 6
! * + Q
! )
! " < : 6 / 3 6
! * + , Q
! )
!
• 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 añadir al
final de su contenido). Cada sesión acabará cuando el usuario pulse Intro sin teclear
nada.
•
-( > 4
Si un fichero al que queremos acceder se encuentra en otra carpeta, basta con que indiquemos
la ruta hasta ella, recordando que, como la barra invertida que se usa en sistemas Windows
para separar los nombres de los directorios, coincide con el carácter de control que se usa en
las cadenas de C y los lenguajes que derivan de él, deberemos escribir dichas barras invertidas
repetidas, así:
Como esta sintaxis puede llegar a resultar incómoda, en C# existe una alternativa: podemos
indicar una arroba (@) justo antes de abrir las comillas, y entonces no será necesario delimitar
los caracteres de control:
, / < " 1 8Y # Y # % 6
!
• Crear un programa que pida al usuario pares de números enteros y escriba su suma
(con el formato "20 + 3 = 23") en pantalla y en un fichero llamado "sumas.txt", que se
encontrará en un subdirectorio llamado "resultados". Cada vez que se ejecute el
programa, deberá añadir los nuevos resultados a continuación de los resultados de las
ejecuciones anteriores.
-+ 1 , 4
Hasta ahora, estamos intentando abrir ficheros para lectura, pero sin comprobar realmente si el
fichero existe o no, lo que puede suponer que nuestro programa falle en caso de que el fichero
no se encuentre donde nosotros esperamos.
Una primera solución es usar "File.Exists(nombre)", para comprobar si está, antes de intentar
abrirlo:
=C???????????????????????????C=
=C "# ) R G48 C=
=C # G4 C=
=C C=
=C + ! C=
=C 6 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# G4
Revisión 0.97 – Página 149
Introducción a la programación con C#, por Nacho Cabanes
& '
!
, /
) * ; / ! Y ! Y 8
/ " ) +
# / "" !
,
# < "6 /
! " < W 6 /
) * + 8 H$I
! +
! )
) * + 6 S
!
• Mejorar el segundo ejercicio del apartado 7.3 (el que muestra un fichero, haciendo una
pausa cada 25 líneas) para que compruebe antes si el fichero existe, y muestre un
mensaje de aviso en caso de que no sea así.
- - #' *
El uso de "File.Exists" nos permite saber si el fichero existe, pero ese no es el único problema
que podemos tener al acceder a un fichero. Puede ocurrir que no tengamos permiso para
acceder al fichero, a pesar de que exista, o que intentemos escribir en un dispositivo que sea
sólo de lectura (como un CDDROM, por ejemplo).
Una forma más eficaz de comprobar si ha existido algún tipo de error es utilizar el manejo de
"excepciones" que permiten los lenguajes modernos, como C#.
La idea es la siguiente: "intentaremos" dar una serie de pasos, y al final de todos ellos indicaD
remos qué pasos hay que dar en caso de que alguno no se consiga completar. Esto permite
que el programa sea más legible que la alternativa "convencional", que consistía en: intentar un
paso y comprobar errores; si todo era correcto, intentar otro paso y volver a comprobar
errores; si todo seguía siendo correcto, intentar otro nuevo paso, y así sucesivamente.
La forma de conseguirlo es dividir en dos bloques las partes de programa que puedan dar lugar
a error:
Un primer ejemplo, que mostrara todo el contenido de un fichero de texto, y que en caso de
error, se limitara a mostrar un mensaje de error y a abandonar el programa, podría ser:
=C???????????????????????????C=
=C "# ) R GE8 C=
=C # GE C=
=C C=
=C "6 ( ! C=
=C % C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# GE
& '
!
, /
,
) * + J / !
/ " ) +
! " < W 6 /
" ! +
# %"
) * +
%"
! )
"6 6
) * + / 8 H$I 6 ' ,
J / !
/
/ 8 & )8Y< Y Y /
Revisión 0.97 – Página 151
Introducción a la programación con C#, por Nacho Cabanes
Pero en general, lo razonable no es interceptar "todas las excepciones a la vez", sino crear un
análisis 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 sólo lectura (se lanzará una excepción del tipo "IOException").
• La ruta del fichero es demasiado larga (excepción de tipo "PathTooLongException").
• El disco puede estar lleno (IOException).
Así, dentro de cada bloque "catch" podríamos indicar una excepción más concreta, de forma
que el mensaje de aviso sea más concreto, o que podamos dar pasos más adecuados para
solucionar el problema:
=C???????????????????????????C=
=C "# ) R GF8 C=
=C # GF C=
=C C=
=C "6 ( ! C=
=C 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# GF
& '
* !
, /
,
) * J / ! 8
/ " ) +
) * J ! , 8
" ) +
! " < ) 6 /
! * +
! )
+ ,"6
) * + / , S
W"6
) * + / S
) * + " 6 8 H$I ' ,
Como la consola se comporta como un fichero de texto (realmente, como un fichero de entrada
y otro de salida), se puede usar "try…catch" para comprobar ciertos errores relacionados con la
entrada de datos, como cuando no se puede convertir un dato a un cierto tipo (por ejemplo, si
queremos convertir a Int32, pero el usuario ha tecleado sólo texto).
!
• Un programa que pida al usuario el nombre de un fichero de origen y el de un fichero
de destino, y que vuelque al segundo fichero el contenido del primero, convertido a
mayúsculas. Se debe controlar los posibles errores, como que el fichero de origen no
exista, o que el fichero de destino no se pueda crear.
• Un programa que pida al usuario un número, una operación (+, D, *, /) y un segundo
número, y muestre el resultado de la correspondiente operación. Si se teclea un dato
no numérico, el programa deberá mostrar un aviso y volver a pedirlo, en vez de
interrumpir la ejecución.
• Un programa que pida al usuario repetidamente pares de números y la operación a
realizar con ellos (+, D, *, /) y guarde en un fichero "calculadora.txt" el resultado de
dichos cálculos (con la forma "15 * 6 = 90"). Debe controlar los posibles errores, como
que los datos no sean numéricos, la división entre cero, o que el fichero no se haya
podido crear.
-. ! ' , 4
Llega el momento de ver algunos conceptos que hemos pasado por encima, y que es necesario
conocer:
• Fichero físico. Es un fichero que existe realmente en el disco, como "agenda.txt".
• Fichero lógico. Es un identificador que aparece dentro de nuestro programa, y que
permite acceder a un fichero físico. Por ejemplo, si declaramos "StreamReader
fichero1", esa variable "fichero1" representa un fichero lógico.
• Equivalencia entre fichero lógico y fichero físico. No necesariamente tiene por qué
existir una correspondencia "1 a 1": puede que accedamos a un fichero físico mediante
dos o más ficheros lógicos (por ejemplo, un StreamReader para leer de él y un
StreamWriter para escribir en él), y también podemos usar un único fichero lógico para
acceder a distintos ficheros físicos (por ejemplo, los mapas de los niveles de un juego,
que estén guardados en distintos ficheros físicos, pero los abramos y los leamos
utilizando siempre una misma variable).
• Registro: Un bloque de datos que forma un "todo", como el conjunto de los datos de
una persona: nombre, dirección, eDmail, teléfono, etc. Cada uno de esos "apartados"
de un registro se conoce como "campo".
-9 ' , 4
Los ficheros de texto son habituales, pero es aún más frecuente encontrarnos con ficheros en
los que la información está estructurada como una secuencia de bytes, más o menos ordenada.
Esto ocurre en las imágenes, los ficheros de sonido, de video, etc.
Como primer acercamiento, vamos a ver cómo abrir un fichero (no necesariamente de texto) y
leer el primer byte que contiene. Usaremos una clase específicamente diseñada para leer datos
de los tipos básicos existentes en C# (byte, int, float, etc.), la clase "BinaryReader":
=C???????????????????????????C=
=C "# ) R GG8 C=
=C # GG C=
=C C=
=C < / % C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# GG
& '
[ ( !
, /
/( ;
) * + J / !
/ " ) +
! " [ (
< W / < ' W
; " ! [(
) * + " /( H$I
;
! )
Revisión 0.97 – Página 154
Introducción a la programación con C#, por Nacho Cabanes
"6 6
) * + 6 ' ,
La forma de abrir un BinaryReader es algo más incómoda 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 sólo texto: ReadByte lee un dato "byte", ReadInt32 lee un "int",
ReadSingle lee un "float", ReadString lee un "string", etc.
!
• Abrir un fichero con extensión EXE y comprobar si realmente se trata de un ejecutable,
mirando si los dos primeros bytes del fichero corresponden a una letra "M" y una letra
"Z", respectivamente.
- < , 4
Leer byte a byte (o float a float) puede ser cómodo, pero también es lento. Por eso, en la
práctica es más habitual leer grandes bloques de datos. Típicamente, esos datos se guardarán
como un array de bytes.
Para eso, usaremos la clase "FileStream", más genérica. Esta clase tiene método "Read", que
nos permite leer una cierta cantidad de datos desde el fichero. Le indicaremos en qué array
guardar esos datos, a partir de qué posición del array debe introducir los datos, y qué cantidad
de datos se deben leer. Nos devuelve un valor, que es la cantidad de datos que se han podido
leer realmente (porque puede ser menos de lo que le hemos pedido, si hay un error o estamos
al final del fichero).
=C???????????????????????????C=
=C "# ) R GT8 C=
=C # GT C=
=C C=
=C < / 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# GT
Revisión 0.97 – Página 155
Introducción a la programación con C#, por Nacho Cabanes
& '
< !
, /
/( -.
+
) * + J / !
/ " ) +
! " < W /
" /( -%$.
" $
:+ " %$
+ " ! :+
# + & %$
) * + S
! )
"6 6
) * + 6 ' ,
!
• Abrir un fichero con extensión EXE y comprobar si realmente se trata de un ejecutable,
mirando si los dos primeros bytes del fichero corresponden a una letra "M" y una letra
"Z", respectivamente. Se deben leer ambos bytes a la vez, usando un array.
- , 4
La clase FileStream tiene también un método ReadByte, que permite leer un único byte. En ese
caso (y también a veces en el caso de leer todo un bloque), habitualmente nos interesará
situarnos antes en la posición exacta en la que se encuentra el dato que nos interesa leer, y
esto podemos conseguirlo mediante acceso aleatorio, sin necesidad de leer todos los bytes
anteriores.
Para ello, tenemos el método "Seek". A este método se le indican dos parámetros: la posición a
la que queremos saltar, y el punto desde el que queremos que se cuente esa posición (desde el
comienzo del fichero –SeekOrigin.BeginD, desde la posición actual –SeekOrigin.CurrentD o desde
el final del fichero –SeekOrigin.EndD). La posición es un Int64, porque puede ser un número
muy grande e incluso un número negativo (si miramos desde el final del fichero, porque desde
él habrá que retroceder).
De igual modo, podemos saber en qué posición del fichero nos encontramos, consultando la
propiedad "Position", así como la longitud del fichero, mirando su propiedad "Length", como en
este ejemplo:
=C???????????????????????????C=
=C "# ) R G28 C=
=C # G2 C=
=C C=
=C < / @ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# G2
& '
< !
, /
/( -.
+
) * + J / !
/ " ) +
! " < W /
" /( -%$.
" $
:+ " %$
+ " ! :+
# + & %$
) * + S
# ! + , $ @$
! - %2 -W , [ ,
& ; " ! [(
) * + " /( 3$ H$I & ;
) * + + 7 H$I
!
) * + P L ! H$I
Revisión 0.97 – Página 157
Introducción a la programación con C#, por Nacho Cabanes
! + ,
! )
"6 6
) * + 6 ' ,
(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).
- , 4
Para escribir en un FileStream, usaremos la misma estructura que para leer de él:
• Un método Write, para escribir un bloque de información (desde cierta posición de un
array, y con cierto tamaño).
• Un método WriteByte, para escribir sólo un byte.
Vamos a ver un ejemplo que junte todo ello: crearemos un fichero, guardaremos datos, lo
leeremos para comprobar que todo es correcto, añadiremos al final, y volveremos a leer:
=C???????????????????????????C=
=C "# ) R T$8 C=
=C # T$ C=
=C C=
=C < / 4 8 C=
=C " C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# T$
& '
< !
, /
/( -.
/ "
" /( - :': PW.[]<<" .
== ; & (
# /( "$ & :': PW.[]<<"
- . " /( %$
" $
== ! B ,
! " < ) /
! * :': PW.[]<<"
! )
== :
! " < W /
) * + " L H$I ! + ,
! - 3 -W , [ ,
& ; " ! [(
) * + " /( H$I & ;
! - 3 -W , "
& ; " ! [(
) * + " /( H$I & ;
! )
== : L %$ M B !
! " < W * /
! - $ -W , "
! * $ :': PW.[]<<"
== ( ! /(
! - 3 -W , [ ,
! * [( 22
! )
== X & ,
! " < W /
) * + " L H$I ! + ,
! - 3 -W , [ ,
& ; " ! [(
) * + " /( H$I & ;
! )
"6 6
) * + 6 ' ,
(Nota: existe una propiedad "CanWrite" que nos permite saber si se puede escribir en el
fichero).
Si queremos que escribir datos básicos de C# (float, int, etc.) en vez de un array de bytes,
podemos usar un "BinaryWriter", que se maneja de forma similar a un "BinaryReader", con la
diferencia de que no tenemos métodos WriteByte, WriteString y similares, sino un único método
"Write", que se encarga de escribir el dato que le indiquemos, sea del tipo que sea:
=C???????????????????????????C=
=C "# ) R T%8 C=
=C # T% C=
=C C=
=C < / E C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# T%
& '
[ (* !
[ ( ! "
, /
== + N & , =
/( ; [(
;
! ; <
/ ; ; /
, ; ,
) * J / ! 8
/ " ) +
) * + ) !
== & , /
0
! " [ (*
< W / < ' )
; [( " %
; " 3
; < " @ $!
; ; / " 4 $
; , "
! * ; [(
! * ;
! * ; <
! * ; ; /
! * ; ,
! )
"6 6
) * + 6 ' ,
! " " [ (
< W / < ' W
; [( " ! " [(
) * + " /( H$I
; [(
; " ! " @3
) * + " H$I
;
; < " ! " ,
) * + " ! H$I
; <
; ; / " ! " ; /
) * + " / H$I
; ;
/
; , " ! " ,
) * + " , Y H$IY
; ,
) * + X &
! " [ - % -W , [ ,
; " ! " @3
) * + " H$I
;
! " )
"6 6
) * + 6 ' ,
Como se puede ver en este ejemplo, también podemos usar "Seek" para movernos a un punto
u otro de un fichero si usamos un "BinaryReader", pero está un poco más escondido: no se lo
pedimos directamente a nuestro fichero, sino al "Stream" (flujo de datos) que hay por debajo:
! " [ - % -W , [ ,
J / ! 8 %3@4
) !
+ ( !
" /( %
" 3
" ! @
" / 4
" ,
X &
" 3
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 BinaryD
Reader o en un BinaryWriter son los siguientes:
- * , , 4 ?#
Ahora vamos a ver un ejemplo un poco más sofisticado y un poco más real: vamos a abrir un
fichero que sea una imagen en formato BMP y a mostrar en pantalla si está comprimido o no.
Para eso necesitamos antes saber cómo se guarda la información en un fichero BMP, pero esto
es algo fácil de localizar en Internet:
Un fichero BMP está compuesto por las siguientes partes: una cabecera de
fichero, una cabecera del bitmap, una tabla de colores y los bytes que
definirán la imagen.
Con esta información nos basta para nuestro propósito: la compresión se indica en la posición
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 podría bastar con leer sólo el
byte de la posición 30, para ver si vale 0, y despreciar los 3 bytes siguientes. Entonces, lo
podríamos comprobar así:
=C???????????????????????????C=
=C "# ) R T38 C=
=C # T3 C=
=C C=
=C < / F 8 C=
=C X [' M C=
=C C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# T3
& '
< !
, /
) * + ) / M, [' Y
) * + ; / ! 8
/ " ) +
# % < "6 /
) * + S
! " < W /
! - @$ -W , [ ,
" ! [(
! )
# "" $
) * + 7
) * + [' )
Ya que estamos, podemos mejorarlo un poco para que además nos muestre el ancho y el alto
de la imagen, y que compruebe antes si realmente se trata de un fichero BMP:
=C???????????????????????????C=
=C "# ) R T@8 C=
=C # T@ C=
=C C=
=C < / G 8 C=
=C ! 7 C=
=C ! [' C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# T@
& '
< !
, /
% 3
/( -. " /( -4.
) * + ) / M, [' Y
) * + ; / ! 8
/ " ) +
# % < "6 /
) * + S
! " < W /
== + /(
% " ) & ) ! [(
3 " ) & ) ! [(
! - %T -W , [ , == 7 %T8
! $ 4
" -$. == ) & 4 /( @3
-%. ! 3EF -3. ! 3EF ! 3EF
-@. ! 3EF ! 3EF ! 3EF
) * + : 8 H$I
! $ 4
" -$. == ) & 4 /( @3
-%. ! 3EF -3. ! 3EF ! 3EF
-@. ! 3EF ! 3EF ! 3EF
) * + : 8 H$I
! - 4 -W , ) == 4 /( 5 8 7
" ! [(
! )
$+ ) * + 7 ,
%+ ) * + ) 7 +" T / ,
3+ ) * + ) 7 +" 4 / ,
) * + ! [' Y == ['
=C???????????????????????????C=
=C "# ) R T48 C=
=C # T4 C=
=C C=
=C < / T 8 C=
=C ! 7 [' C=
=C [ ( C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# T4
& '
[ ( !
, /
% 3
) * + ) / M, [' Y
) * + ; / ! 8
/ " ) +
# % < "6 /
) * + S
! " [ (
< W / < ' W
== + /(
% " ) & ) ! [(
3 " ) & ) ! [(
! [ - %T -W , [ , == 7 %T8
" ! @3
) * + : 8 H$I
" ! @3
) * + : 8 H$I
! [ - 4 -W , ) == 4 /( 5 8 7
" ! @3
! )
$+ ) * + 7 ,
%+ ) * + ) 7 +" T / ,
3+ ) * + ) 7 +" 4 / ,
) * + ! [' Y == ['
!
• Localiza en Internet información sobre el formato de imágenes PCX. Crea un programa
que diga el ancho, alto y número de colores de una imagen PCX.
• Localiza en Internet información sobre el formato de imágenes GIF. Crea un programa
que diga el subformato, ancho, alto y número de colores de una imagen GIF.
- % , 4
También es posible que nos interese leer y escribir en un mismo fichero (por ejemplo, para
poder modificar algún dato erróneo, o para poder crear un editor hexadecimal). Podemos
conseguirlo abriendo (en modo de lectura o de escritura) o cerrando el fichero cada vez, pero
también tenemos la alternativa de usar un "FileStream", que también tiene un método 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":
=C???????????????????????????C=
=C "# ) R TE8 C=
=C # TE C=
=C C=
=C < / 2 8 C=
=C + ( " C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( W
"# TE
& '
< !
, /
/( -.
/ "
" /( - :': PW.[]<<" .
== ; & (
# /( "$ & :': PW.[]<<"
- . " /( %$
" $
== ! B ,
! " < ) /
! * :': PW.[]<<"
! )
== :
! " < W / < ' W < : *
! - 3 -W , [ ,
& ; " ! [(
) * + " /( H$I & ;
! - 3 -W , [ ,
! * [( 4
! - 3 -W , [ ,
& ; " ! [(
) * + : /( H$I & ;
! )
"6 6
) * + 6 ' ,
!
• Un programa que vuelque todo el contenido de un fichero de texto a otro, convirtiendo
cada frase a mayúsculas. Los nombres de ambos ficheros se deben indican como
parámetros en la línea 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 número de línea (la primera línea 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 línea de comandos.
• Un programa que muestre el nombre del autor de un fichero de música en formato MP3
(tendrás que localizar en Internet la información sobre dicho formato).
5 F (
. ; < ' =
Hasta ahora teníamos una serie de variables que declaramos al principio del programa o de
cada función. Estas variables, que reciben el nombre de (676*'0(, tienen un tamaño
asignado desde el momento en que se crea el programa.
Este tipo de variables son sencillas de usar y rápidas... si sólo vamos a manejar estructuras de
datos que no cambien, pero resultan poco eficientes si tenemos estructuras cuyo tamaño no
sea siempre el mismo.
Es el caso de una agenda: tenemos una serie de fichas, e iremos añadiendo más. Si reservamos
espacio para 10, no podremos llegar a añadir la número 11, estamos limitando el máximo. Una
solución sería la de trabajar siempre en el disco: no tenemos límite en cuanto a número de
fichas, pero es muchísimo más lento.
Lo ideal sería aprovechar mejor la memoria que tenemos en el ordenador, para guardar en ella
todas las fichas o al menos todas aquellas que quepan en memoria.
Una solución "típica" (pero mala) es sobredimensionar: preparar una agenda contando con
1000 fichas, aunque supongamos que no vamos a pasar de 200. Esto tiene varios
inconvenientes: se desperdicia memoria, obliga a conocer bien los datos con los que vamos a
trabajar, sigue pudiendo verse sobrepasado, etc.
La solución suele ser crear estructuras 1* 7)*'0(, que puedan ir creciendo o disminuyendo
según nos interese. En los lenguajes de programación "clásicos", como C y Pascal, este tipo de
estructuras se tienen que crear de forma básicamente artesanal, mientras que en lenguajes
modernos como C#, Java o las últimas versiones de C++, existen esqueletos ya creados que
podemos utilizar con facilidad.
Las . Como una pila de libros: vamos apilando cosas en la cima, o cogiendo de la
cima. Se supone que no se puede tomar elementos de otro sitio que no sea la cima, ni
dejarlos en otro sitio distinto. De igual modo, se supone que la pila no tiene un tamaño
máximo definido, sino que puede crecer arbitrariamente.
Las . Como las del cine (en teoría): la gente llega por un sitio (la cola) y sale por
el opuesto (la cabeza). Al igual que antes, supondremos que un elemento no puede
entrar a la cola ni salir de ella en posiciones intermedias y que la cola puede crecer
hasta un tamaño indefinido.
Las , en las que se puede añadir elementos en cualquier posición, y borrarlos de
cualquier posición.
Y la cosa se va complicando: en los cada elemento puede tener varios sucesores (se
parte de un elemento "raíz", y la estructura se va ramificando), etc.
Veremos ejemplos de cómo crear estructuras dinámicas de estos tipos en C#, y después
comentaremos los pasos para crear una estructura dinámica de forma "artesanal".
. @ !"
Para crear una pila tenemos preparada la clase Stack. Los métodos habituales que debería
permitir una pila son introducir un nuevo elemento en la cima ("apilar", en inglés "push"), y
quitar el elemento que hay en la cima ("desapilar", en inglés "pop"). Este tipo de estructuras se
suele llamar también con las siglas "LIFO" (Last In First Out: lo último en entrar es lo primero
en salir). Para utilizar la clase "Stack" y la mayoría de las que veremos en este tema,
necesitamos incluir en nuestro programa una referencia a "System.Collections".
Así, un ejemplo básico que creara una pila, introdujera tres palabras y luego las volviera a
mostrar sería:
=C???????????????????????????C=
=C "# ) C=
=C % C=
=C C=
=C "# - C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# %
& '
, /
- " -
B
(
(
# /( "$ &@
/ " ,
) * + /
(
(
B
La implementación de una pila en C# es algo más avanzada: permite también métodos 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 algún detalle más adelante.
• También tenemos una propiedad "Count", que nos indica cuántos elementos contiene.
. @ !"
Podemos crear colas si nos apoyamos en la clase Queue. En una cola podremos introducir
elementos por la cabeza ("Enqueue", encolar) y extraerlos por el extremo opuesto, el final de la
cola ("Dequeue", desencolar). Este tipo de estructuras se nombran a veces también por las
siglas FIFO (First In First Out, lo primero en entrar es lo primero en salir). Un ejemplo básico
similar al anterior, que creara una cola, introdujera tres palabras y luego las volviera a mostrar
sería:
=C???????????????????????????C=
=C "# ) C=
=C % C=
=C C=
=C "# \ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# ) %
& '
, /
\ ) " \
) " N B
) " N (
) " N (
# /( "$ &@
/ " , ) ; N
) * + /
que mostraría:
B
(
(
Al igual que ocurría con la pila, la implementación de una cola que incluye C# es más avanzada
que eso, con métodos similares a los de antes:
• "Peek", que mira el valor que hay en la cabeza de la cola, pero sin extraerlo.
• "Clear", que borra todo el contenido de la cola.
• "Contains", que indica si un cierto elemento está en la cola.
• "GetType", para saber de qué tipo son los elementos almacenados en la cola.
• "ToString", que devuelve el elemento actual convertido a un string.
• "ToArray", que devuelve toda la pila convertida a un array.
• "GetEnumerator", que permite usar "enumeradores" para recorrer la cola, una
funcionalidad que veremos con algún detalle más adelante.
• Al igual que en la pila, también tenemos una propiedad "Count", que nos indica cuántos
elementos contiene.
.%
Una lista es una estructura dinámica en la que se puede añadir elementos sin tantas
restricciones. Es habitual que se puedan introducir nuevos datos en ambos extremos, así como
entre dos elementos existentes, o bien incluso de forma ordenada, de modo que cada nuevo
dato se introduzca automáticamente en la posición adecuada para que todos ellos queden en
orden.
En el caso de C#, no tenemos ninguna clase "List" que represente una lista genérica, pero sí
dos variantes especialmente útiles: una lista ordenada ("SortedList") y una lista a cuyos
elementos se puede acceder como a los de un array ("ArrayList").
5 $ (@
En un ArrayList, podemos añadir datos en la última posición con "Add", insertar en cualquier
otra con "Insert", recuperar cualquier elemento usando corchetes, o bien ordenar toda la lista
con "Sort". Vamos a ver un ejemplo de la mayoría de sus posibilidades:
=C???????????????????????????C=
=C "# ) C=
=C (+ % C=
=C C=
=C "# : (+ C=
Revisión 0.97 – Página 172
Introducción a la programación con C#, por Nacho Cabanes
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# : (+ %
& '
: (+ + " : (+
== :L
+ : B
+ : (
+ : (
== ' N
) * + ) 8
# , ! +
) * + !
== : 7
) * + + , / 8 H$I
+ -%.
== ,
+ % ) ^
== ' ! N
) * + ) 8
# "$ & + )
) * + + - .
== [
) * + + / Y ( Y M 7 H$I
+ 6W! (
== W
+
== ' N
) * + )
# , ! +
) * + !
== [ /K N /
) * + : Y ( Y M 7 H$I
+ [ ( (
== &
+ &
== [ , ( / (
+ & : %
+ & (
== ' & N
) * + ) & ( 8
# , ! +
) * + !
== W ( & 7 Q &
+
) * + + ! Y + , Y
" + [ ( + ,
# $" $
) * + " M 7 H$I
) 8
B
(
(
+ , / 8 (
) 8
B
) ^
(
(
+ / ( M 7 @
)
) ^
B
(
(
: ( M 7 @
) & ( 8
B
) ^
+ ! + ,
M " ( %8 B
Casi todo debería resultar fácil de entender, salvo quizá el símbolo ~. Esto se debe a que
BinarySearch devuelve un número negativo cuando el texto que buscamos no aparece, pero
ese número negativo tiene un significado: es el "valor complementario" de la posición del dato
inmediatamente mayor (es decir, el dato cambiando los bits 0 por 1 y viceversa). En el ejemplo
anterior, "posición" vale D2, lo que quiere decir que el dato no existe, y que el dato
inmediatamente mayor está en la posición 1 (que es el "complemento a 2" del número D2, que
es lo que indica la expresión "~posición"). En el apéndice 3 de este texto hablaremos de cómo
se representan internamente los números enteros, tanto positivos como negativos, y entonces
se verá con detalle en qué consiste el "complemento a 2".
A efectos prácticos, lo que nos interesa es que si quisiéramos insertar la frase "Hasta Luego",
su posición correcta para que todo el ArrayList permaneciera ordenado sería la 1, que viene
indicada por "~posicion".
Veremos los operadores a nivel de bits, como ~, en el tema 10, que estará dedicado a otras
características avanzadas de C#.
5 : @
En un SortedList, los elementos están formados por una pareja: una clave y un valor (como en
un diccionario: la palabra y su definición). Se puede añadir elementos con "Add", o acceder a
los elementos mediante su índice numérico (con "GetKey") o mediante su clave (sabiendo en
qué posición se encuentra una clave con "IndexOfKey"), como en este ejemplo:
=C???????????????????????????C=
=C "# ) C=
=C + % C=
=C C=
=C "# + 8 C=
=C ; ? , C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# +
& '
== )
+ ; " +
; :
; : 7 , /(
; : , (
== '
) * + ) / 8 H$I
; )
) * + + / ( , ! 8
# "$ & ; )
) * + H$I 9 H%I
; O a ( ; O [( 6
) * + 7 Y Y 8 H$I
; O [( 6 ; 6W!a (
Su resultado sería
) / 8 @
+ / ( , ! 8
7 9 , /(
, 9 (
9
7 8
.( 5 4 45
En una "tabla hash", los elementos están formados por una pareja: una clave y un valor, como
en un SortedList, pero la diferencia está en la forma en que se manejan internamente estos
datos: la "tabla hash" usa una "función de dispersión" para colocar los elementos, de forma que
no se pueden recorrer secuencialmente, pero a cambio el acceso a partir de la clave es muy
rápido, más que si hacemos una búsqueda secuencial (como en un array) o binaria (como en
un ArrayList ordenado).
Un ejemplo de diccionario, parecido al anterior (que es más rápido de consultar para un dato
concreto, pero que no se puede recorrer en orden), podría ser:
=C???????????????????????????C=
=C "# ) C=
=C / % C=
=C C=
=C "# / 8 C=
=C ; ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# /
& '
== )
/ ; " /
; : /( T /
; :
Revisión 0.97 – Página 176
Introducción a la programación con C#, por Nacho Cabanes
; : - /( %$34 /(
== ' ,K
) * + ) / 8 H$I
; )
0
) * + " , ! ) 8 H$I
; - .
"6
) * + 6 / S
) / 8 @
" , ! ) 8
Si un elemento que se busca no existe, se lanzaría una excepción, por lo que deberíamos
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 método
"Contains" (o su sinónimo "ContainsKey"), como en este ejemplo:
=C???????????????????????????C=
=C "# ) C=
=C / 3 C=
=C C=
=C "# / 38 C=
=C ; ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# / 3
& '
== )
/ ; " /
; : /( T /
; :
; : - /( %$34 /(
== ' ,K
) * + ) / 8 H$I
; )
# ; )
) * + " , ! ) 8 H$I
; - .
) * + 6 / )
Otras posibilidades son: borrar un elemento ("Remove"), vaciar toda la tabla ("Clear"), o ver si
contiene un cierto valor ("ContainsValue", mucho más lento que buscar entre las claves con
"Contains").
Una tabla hash tiene una cierta capacidad inicial, que se amplía automáticamente cuando es
necesario. Como la tabla hash es mucho más rápida cuando está bastante vacía que cuando
está casi llena, podemos usar un constructor alternativo, en el que se le indica la capacidad
inicial que queremos, si tenemos una idea aproximada de cuántos datos vamos a guardar:
/ ; " / E$$
.+ 5 5
Un enumerador es una estructura auxiliar que permite recorrer las estructuras dinámicas de
forma secuencial. Casi todas ellas contienen un método GetEnumerator, que permite obtener
un enumerador para recorrer todos sus elementos. Por ejemplo, en una tabla hash podríamos
hacer:
=C???????????????????????????C=
=C "# ) C=
=C / @ C=
=C C=
=C "# / C=
=C ( C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# / @
& '
== )
/ ; " /
; : /( T /
; :
; : - /( %$34 /(
== '
) * + ) 8
; (" " " ; O "
" ' & 6
) * + H$I 9 H%I
" a ( " X
cuyo resultado es
) 8
9
/( 9 T /
- /( 9 %$34 /(
Como se puede ver, los enumeradores tendrán un método "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.
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:
=C???????????????????????????C=
=C "# ) C=
=C 3 C=
=C C=
=C "# - C=
=C ( C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
# %
& '
- " -
B
(
(
== '
) * + ) 8
" " " O "
" ' & 6
) * + H$I " )
que escribiría
Revisión 0.97 – Página 179
Introducción a la programación con C#, por Nacho Cabanes
) 8
(
(
B
Nota: los "enumeradores" existen también en otras plataformas, como Java, aunque allí reciben
el nombre de "iteradores".
Se puede saber más sobre las estructuras dinámicas que hay disponibles en la plataforma .Net
consultando la referencia en línea de MSDN (mucha de la cual está sin traducir al español):
http://msdn.microsoft.com/esDes/library/system.collections(enDus,VS.71).aspx#
.- ! 5 5 5 5
Las estructuras dinámicas se pueden imitar usando estructuras estáticas sobredimensionadas, y
esto puede ser un ejercicio de programación interesante. Por ejemplo, podríamos imitar una
pila dando los siguientes pasos:
• Utilizamos internamente un array más grande que la cantidad de datos que esperemos
que vaya a almacenar la pila.
• Creamos una función "Apilar", que añade en la primera posición libre del array
(inicialmente la 0) y después incrementa esa posición, para que el siguiente dato se
introduzca a continuación.
• Creamos también una función "Desapilar", que devuelve el dato que hay en la última
posición, y que disminuye el contador que indica la posición, de modo que el siguiente
dato que se obtuviera sería el que se introdujo con anterioridad a éste.
=C???????????????????????????C=
=C "# ) C=
=C " C=
=C C=
=C "# C=
=C / ( C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
(
( )
,-.
& '
, /
, " ,
: B
: (
: (
# /( "$ &@
/ " , ;
) * + /
== )
,
" $
" ,-':g +:.
== :L 8 :
& : , & ;
# "" ':g +:
) * + S
- . " & ;
== "6 8 ;
, ;
# & $
) * + & S
- .
== <
!
• 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 método "Encolar" y otro "Desencolar".
• Crear una clase "ListaOrdenada", que almacene un único dato (no un par claveDvalor
como los SortedList). Debe contener un método "Insertar", que añadirá un nuevo dato
en orden en el array, y un "Extraer(n)", que obtenga un elemento de la lista (el número
"n").
.. !"
55 HI J 6
La palabra "puntero" se usa para referirse a una . Lo que tiene de
especial es que normalmente un puntero tendrá un tipo de datos asociado: por ejemplo, un
"puntero a entero" será una dirección de memoria en la que habrá almacenado (o podremos
almacenar) un número entero.
=C K C=
! =C 7
N , C=
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, también es correcto escribir
El valor que guarda "posicion" es una dirección de memoria. Generalmente no podremos hacer
cosas como "E porque nada nos garantiza que la posición 5 de la memoria esté
disponible para que nosotros la usemos. Será más habitual que tomemos una dirección de
memoria que ya contiene otro dato, o bien que le pidamos al compilador que nos reserve un
espacio de memoria (más adelante veremos cómo).
" (
55 K 2 2"
Como los punteros son "peligrosos" (es frecuente que den lugar a errores muy difíciles de
encontrar), el compilador nos obligamos a que le digamos que sabemos que esa zona de
programa no es segura, usando la palabra "unsafe":
# & /
Es más, si intentamos compilar obtendremos un mensaje de error, que nos dice que si nuestro
código no es seguro, deberemos compilarlo con la opción "unsafe":
Revisión 0.97 – Página 182
Introducción a la programación con C#, por Nacho Cabanes
! %
! % %EB@% 8 ) $33G8 ] ! N h !
/ !
) ! 8 % B $ > ,
Por tanto, deberemos compilar con la opción /unsafe como forma de decir al compilador "sí, sé
que este programa tiene zonas no seguras, pero aun así quiero compilarlo":
! % = !
55%C
Veamos un ejemplo básico de cómo dar valor a un puntero y de cómo guardar un valor en él:
=C???????????????????????????C=
=C "# ) C=
=C ! % C=
=C C=
=C "# % C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
# ] ! %
# & /
! :"
6
== ; & 6
6 " 3
== :" M 7 6
:" " (6
== + M 7 8
) * + 6 & H$I 6
) * + " :" ( H$I ! :"
== : / & , :"
! :" " E
== P 6 ! / 5 8
) * + 6 & H$I 6
) * + " :" ( H$I ! :"
& '
/
6 & 3
" :" ( 3
6 & E
" :" ( E
55 K
También podemos hacer que sólo una parte de un programa sea insegura, indicando entre
llaves una parte de una función:
=C???????????????????????????C=
=C "# ) C=
=C ! 3 C=
=C C=
=C "# 3 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
# ] ! 3
# & !
==
! " ! %
& '
" %
== i , '
#
== + ! 7 B Q N
== 7 8
(
== P
) * +
55 ? ! " L
Podemos reservar espacio para una variable dinámica usando "stackalloc". Por ejemplo, una
forma alternativa de crear un array de enteros sería ésta:
! " , -E.
=C???????????????????????????C=
=C "# ) C=
=C ! @ C=
=C C=
=C "# @ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# ] ! @
# & '
( : ( " E
! " , - ( : (.
== (
# " $ & ( : (
- . " !%$
== ' (
# " $ & ( : (
) * + - .
Existen ciertas diferencias entre esta forma de trabajar y la que ya conocíamos: la memoria se
reserva en la pila (stack), en vez de hacerlo en la zona de memoria "habitual", conocida como
"heap" o montón, pero es un detalle sobre el que no vamos a profundizar.
55 $ J
Si aumentamos o disminuimos el valor de un puntero, cambiará la posición que representa...
pero no cambiará de uno en uno, sino que saltará a la siguiente posición capaz de almacenar
un dato como el que corresponde a su tipo base. Por ejemplo, si un puntero a entero tiene
como valor 40.000 y hacemos "puntero++", su dirección pasará a ser 40.004 (porque cada
entero ocupa 4 bytes). Vamos a verlo con un ejemplo:
=C???????????????????????????C=
=C "# ) C=
=C ! 4 C=
=C C=
=C "# 4 C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# ] ! 4
# & '
( : ( " E
! " , - ( : (.
! "
) * + 8 H$I
== $
! " $
== M %$B3$B@$
# " % & ( : (
) * + 8 H$I
! " ! %$
== < (
) * + ) 8
# " $ & ( : (
) * + - .
El resultado sería algo parecido (porque las direcciones de memoria que obtengamos no tienen
por qué ser las mismas) a:
8 %343%2F
8 %3433$$
8 %3433$4
8 %3433$T
8 %3433%3
) 8
$
%$
3$
@$
4$
551@ 2 = 2
C# cuenta con un "recolector de basura", que se encarga de liberar el espacio ocupado por
variables que ya no se usan. Esto puede suponer algún problema cuando usamos variables
dinámicas, porque estemos accediendo a una posición de memoria que el entorno de ejecución
haya previsto que ya no necesitaríamos… y haya borrado.
Por eso, en ciertas ocasiones el compilador puede avisarnos de que algunas asignaciones son
peligrosas y obligar a que usemos la palabra "fixed" para indicar al compilador que esa zona
"no debe limpiarse automáticamente".
=C???????????????????????????C=
=C "# ) C=
=C ! E C=
=C C=
=C "# E C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"# ] ! E
# & '
-. " %$ 3$ @$
) * + + ( ,
# ! ; " ( -%.
) * + " ; ( H$I ! ;
) * + + (
# ! ; "
) * + : ; ( H$I ! ;
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 "sólo lectura" (readDonly). Por eso, para cambiarla, tendremos que empezar otra nueva
zona "fixed".
+ ( ,
" ; ( 3$
+ (
: ; ( %$
D M ! N
9
Desde nuestros primeros programas hemos estado usando cosas como "System.Console" o bien
"using System". Esa palabra "System" indica que las funciones que estamos usando pertenecen
a la estructura básica de C# y de la plataforma .Net.
La idea detrás de ese "using" es que puede ocurrir que distintos programadores en distintos
puntos del mundo creen funciones o clases que se llamen igual, y, si se mezclan fuentes de
distintas procedencias, esto podría dar lugar a programas que no compilaran correctamente, o,
peor aún, que compilaran pero no funcionaran de la forma esperada.
Por eso, se recomienda usar "espacios de nombres", que permitan distinguir unos de otros. Por
ejemplo, si yo quisiera crear mi propia clase "Console" para el acceso a la consola, o mi propia
clase "Random" para manejo de números aleatorios, lo razonable es crear un nuevo espacio de
nombres.
De hecho, con entornos como SharpDevelop o Visual Studio, cuando creamos un nuevo
proyecto, el fuente "casi vacío" que se nos propone contendrá un espacio de nombres que se
llamará igual que el proyecto. Esta es apariencia del fuente si usamos VisualStudio 2008:
(
( ) O
( + N
( 6
) : %
/ ;
) * + * S
== W;W8 < (
) * ( - (
) a (
Vamos a un ejemplo algo más 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):
=C???????????????????????????C=
=C "# ) C=
=C C=
=C C=
=C "# C=
=C / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
) ;
)
& * + , 6
( ) < , ) " ) ) [
( ) * + ' # 8 6
/ ;
& '
( ) * +
) ; ) * + & J
Como se puede ver, este ejemplo tiene dos clases Console, y ambas tienen un método
WriteLine. Una es la original de C#, que invocaríamos con "System.Console". Otra es la que
hemos creado para el ejemplo, que escribe un texto modifica y en color (ayudándose de
System.Console), y que llamaríamos mediante "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 sería:
' # 8 & J
9 &
Podemos hacer desde C# operaciones entre bits de dos números (AND, OR, XOR, etc). Vamos
primero a ver qué significa cada una de esas operaciones.
=C???????????????????????????C=
=C "# ) C=
=C / C=
=C C=
=C W / C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
& '
" FG
/ " @@
El resultado es:
+ & / & FG
( / & @@
" 8 ?FT
" 7, ( / 8 %
7, 8 22
7, 6 & 8 2T
; JN 8 %@4
; 8 @@
Para comprobar que es correcto, podemos convertir al sistema binario esos dos números y
seguir las operaciones paso a paso:
FG 9 $%$$ $$%%
@@ 9 $$%$ $$$%
Después hacemos el producto lógico de A y B, multiplicando cada bit, de modo que 1*1 = 1,
1*0 = 0, 0*0 = 0
$$$$ $$$% 9 %
Después hacemos su suma lógica, sumando cada bit, de modo que 1+1 = 1, 1+0 = 1, 0+0 =
0
$%%$ $$%% 9 22
La suma lógica exclusiva devuelve un 1 cuando los dos bits son distintos: 1^1 = 0, 1^0 = 1,
0^0 = 0
$%%$ $$%$ 9 2T
Desplazar los bits una posición a la izquierda es como multiplicar por dos:
Desplazar los bits una posición a la derecha es como dividir entre dos:
$$%$ $$$% 9 @@
¿Qué utilidades puede tener todo esto? Posiblemente, más de las que parece a primera vista.
Por ejemplo: desplazar a la izquierda es una forma muy rápida de multiplicar por potencias de
dos; desplazar a la derecha es dividir por potencias de dos; la suma lógica exclusiva (xor) es un
método rápido y sencillo de cifrar mensajes; el producto lógico nos permite obligar a que
ciertos bits sean 0 (algo que se puede usar para comprobar máscaras de red); la suma lógica,
por el contrario, puede servir para obligar a que ciertos bits sean 1...
6 A9 3
6 9 3
6 U9 3
6 V9 3
...
9
Cuando tenemos varias constantes, cuyos valores son números enteros, hasta ahora estamos
dando los valores uno por uno, así:
Hay una forma alternativa de hacerlo, especialmente útil si son números enteros consecutivos.
Se trata de :
+] " ': " ' " )W+" Z]"X" X " " :[:;W
;W' OW
(Al igual que las constantes de cualquier otro tipo, se puede escribir en mayúsculas para
recordar "de un vistazo" que son constantes, no variables)
La primera constante valdrá 0, y las demás irán aumentando de una en una, de modo que en
nuestro caso valen:
Si queremos que los valores no sean exactamente estos, podemos dar valor a cualquiera de las
contantes, y las siguientes irán aumentando de uno en uno. Por ejemplo, si escribimos
+] " "% ': " ' " )W+" Z]"X" "F X " "
:[:;W"%$ ;W' OW
=C???????????????????????????C=
=C "# ) C=
=C C=
=C C=
=C "# C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
+] " ': " ' " )W+" Z]"X" X " " :[:;W
;W' OW
& '
+] " " $ ': " " % ' " )W+" " 3 Z]"X" " @
X " " " 4 :[:;W " E ;W' OW " F
y su resultado será:
Nosotros hemos usado enumeraciones muchas veces hasta ahora, sin saber realmente que lo
estábamos haciendo. Por ejemplo, el modo de apertura de un fichero (FileMode) es una
enumeración, por lo que escribimos FileMode.Open. También son enumeraciones los códigos de
color de la consola (como ConsoleColor.Red) y las teclas de la consola (como
ConsoleKey.Escape).
Nota: las enumeraciones existen también en otros lenguajes como C y C++, pero la sintaxis es
ligeramente distinta: en C# es necesario indicar el nombre de la enumeración cada vez que se
usen sus valores (como en diasSemana.MIERCOLES), mientras que en C se usa sólo el valor
(MIERCOLES).
9%
Hasta ahora estábamos siguiendo la política de que los atributos de una clase sean privados, y
se acceda a ellos a través de métodos "get" (para leer su valor) y "set" (para cambiarlo). En el
caso de C#, existe una forma alternativa de conseguir el mismo efecto, empleando las llamadas
"propiedades", que tienen una forma abreviada de escribir sus métodos "get" y "set":
=C???????????????????????????C=
=C "# ) C=
=C C=
=C C=
=C "# C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
"#
== ?????????????????????????
== ] / & B &
" $
== B & ,
O :
== P ! #
& : & X
" & X
== ?????????????????????????
== W / & B &
" $
== W
:
" &
== ?????????????????????????
== " ' /
& '
"# #
" "#
# : E
) * + + H$I
# O :
# : " F
) * + + H$I
# :
Al igual que ocurría con las enumeraciones, ya hemos usado "propiedades" anteriormente, sin
saberlo: la longitud ("Length") de una cadena, el tamaño ("Length") y la posición actual
("Position") en un fichero, el título ("Title") de una ventana en consola, etc.
Una curiosidad: si una propiedad tiene un "get", pero no un "set", será una propiedad de sólo
lectura, no podremos hacer cosas como "Anchura = 4", porque el programa no compilaría. De
igual modo, se podría crear una propiedad de sólo escritura, definiendo su "set" pero no su
"get".
9( ' A B
Hemos hablado de dos tipos de parámetros de una función: parámetros por valor (que no se
pueden modificar) y parámetros por referencia ("ref", que sí se pueden modificar). Un uso
habitual de los parámetros por referencia es devolver más de un valor a la salida de una
función. Para ese uso, en C# existe otra alternativa: los parámetros de salida. Se indican con la
palabra "out" en vez de "ref", y no exigen que las variables tengan un valor inicial:
9+ )
Las "expresiones regulares" permiten hacer comparaciones mucho más abstractas que si se usa
un simple "IndexOf". Por ejemplo, podemos comprobar con una orden breve si todos los
caracteres de una cadena son numéricos, o si empieza por mayúscula y el resto son
minúsculas, etc.
Vamos a ver solamente un ejemplo con un caso habitual: comprobar si una cadena es
numérica, alfabética o alfanumérica. Las ideas básicas son:
(
( 6 , "6
/ "6 ,
/ " " ,
, 6 " , 6 _j$?2`
% '
/ " " 3 ,
, 6 " , 6 1 Y:_$?2`CYJ
'
/ " ) ; ,
, 6 ) ; "
, 6 1 Y:_$?2`CB^_$?2`AYJ
) ; '
/ " : ! / ,
, 6 : ! / " , 6 _j ?J:?k`
% : ! / '
/ " : ! ,
, 6 : ! " , 6 _j ?J:?k$?2`
% : ! '
# " "
) * + K
) * + W K
) * + %243 W K
) * + %243 W ! 3
) * + 3@B4E W K
) * + 3@B4E W ! 3
# " ) ; 3@B4E
) * + 3@B4E K
) * + 3@B4E W K
# " ) ; 3@B4EBFG
) * + 3@B4EBFG K
) * + 3@B4EBFG W K
# " : ! /
) * + ! /
) * + W ! /
# " : ! %
) * + % !
) * + % W !
Su salida es:
W K
%243 K
%243 ! 3
3@B4E W K
3@B4E W ! 3
3@B4E K
3@B4EBFG W K
! /
% !
Las expresiones regulares son algo complejo. Por una parte, su sintaxis puede llegar a ser difícil
de seguir. Por otra parte, el manejo en C# no se limita a buscar, sino que también permite
otras operaciones, como reemplazar unas expresiones por otras. Como ver muchos más
detalles podría hacer el texto demasiado extenso, puede ser recomendable ampliar información
usando la página web de MSDN (Microsoft Developer Network):
http://msdn.microsoft.com/esDes/library/system.text.regularexpressions.regex(VS.80).aspx
9-
Cuando vimos la orden "for", siempre usábamos una única variable como contador, pero esto
no tiene por qué ser siempre así. Vamos a verlo con un ejemplo:
=C???????????????????????????C=
=C "# ) C=
=C C=
=C C=
=C W C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
& '
& $ ( # & $
& % ( # & %
& 3 ( # & 3
& @ ( # & @
& 4 ( # & 4
& E ( # & E
Nota: En el lenguaje C se puede "rizar el rizo" todavía un poco más: la condición de terminación
también podría tener una coma, y entonces no se sale del bucle "for" hasta que se cumplen las
dos condiciones (algo que no es válido en C#, ya que la condición debe ser un "Bolean", y la
coma no es un operador válido para operaciones booleanas):
9. <
En C# hay más que lo que hemos visto aquí. Mencionaremos algunos, por si alguien quiere
ampliar información por su cuenta en MSDN o en cualquier otra fuente de información. Por
ejemplo:
• Delegados (delegate).
• Indexadores.
• Colecciones genéricas.
• …
#' 5 5
En "Console" hay mucho más que ReadLine y WriteLine, aunque quizá no todas las
posibilidades estén contempladas en implementaciones "alternativas", como las primeras
versiones de Mono. Vamos a ver algunas de las posibilidades que nos pueden resultar más
útiles:
=C???????????????????????????C=
=C "# ) C=
=C C=
=C C=
=C 'M / C=
=C ( ) C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
& '
g P
) " "#
) [ -, ) " ) ) O
) < , ) " ) ) [ -
) )
P " %$ == " ! %$
Revisión 0.97 – Página 201
Introducción a la programación con C#, por Nacho Cabanes
) < , ) " ) ) [
) ) %$ %E
) * % 38
) a ( !
" ) a ( #
a () %" % (( a () %" 3
6P " ) * > ,
6g " ) * >*
) ) 6g E$ 6P %
) < , ) " ) )
) *
) a (
(Nota: si se prueba este fuente desde Mono, habrá que compilar con "gmcs" en vez de con
"mcs", para compilar usando la versión 2.x de la plataforma .Net, no la 1.x, que no tenía estas
posibilidades).
Para comprobar el valor de una tecla, como se ve en el ejemplo anterior, tenemos que usar
una variable de tipo "ConsoleKeyInfo" (información de tecla de consola). Un ConsoleKeyInfo
tiene campos como:
• KeyChar, que representa el carácter que se escribiría al pulsar esa tecla. Por ejemplo,
podríamos hacer # a () "" %
• Key, que se refiere a la tecla (porque hay teclas que no tienen un carácter visualizable,
como F1 o las teclas de cursor). Por ejemplo, para comprobar la tecla ESC podríamos
hacer # a ( "" ) a ( " . Algunos de los códigos de tecla
disponibles son:
Teclas de edición y control como, como: Backspace (Tecla RETROCESO), Tab
(Tecla TAB), Clear (Tecla BORRAR), Enter (Tecla ENTRAR), Pause (Tecla
PAUSA), Escape (Tecla ESC (ESCAPE)), Spacebar (Tecla BARRA
ESPACIADORA), PrintScreen (Tecla IMPR PANT), Insert (Tecla INS (INSERT)),
Delete (Tecla SUPR (SUPRIMIR))
Teclas de movimiento del cursor, como: PageUp (Tecla RE PÁG), PageDown
(Tecla AV PÁG), End (Tecla FIN), Home (Tecla INICIO), LeftArrow (Tecla
FLECHA IZQUIERDA), UpArrow (Tecla FLECHA ARRIBA), RightArrow (Tecla
FLECHA DERECHA), DownArrow (Tecla FLECHA ABAJO)
Teclas alfabéticas, 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
Teclas numéricas, como: D0 (Tecla 0), D1, D2, D3, D4, D5, D6, D7, D8, D9
Los colores que tenemos disponibles (y que se deben escribir precedidos con "ConsoleColor")
son: Black (negro), DarkBlue (azul marino), DarkGreen (verde oscuro) DarkCyan (verde
azulado oscuro), DarkRed (rojo oscuro), DarkMagenta (fucsia oscuro o púrpura), DarkYellow
(amarillo oscuro u ocre), Gray (gris), DarkGray (gris oscuro), Blue (azul), Green (verde), Cyan
(aguamarina o verde azulado claro), Red (rojo), Magenta (fucsia), Yellow (amarillo), White
(blanco).
C ' ',
En C# podemos crear con una cierta facilidad programas en entornos gráficos, con menús
botones, listas desplegables, etc.
La forma más cómoda de conseguirlo es usando herramientas que incluyan un editor visual,
como Visual Studio o SharpDevelop.
A pesar de que existen versiones gratuitas de Visual Studio, vamos a ver el caso de
SharpDevelop , que necesita un ordenador menos potente y tiene un manejo muy similar (en el
Apéndice 4 tienes los cambios para Visual Studio).
Cuando entramos a SharpDevelop, diríamos que queremos crear una "nueva solución", y en el
menú escogeríamos "Aplicación Windows":
Debajo de la ventana de código hay una pestaña llamada "Diseño", que nos permite acceder al
diseñador 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 botón, 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,
podríamos añadir otros elementos.
Por ejemplo, vamos a añadir un botón (Button) y una etiqueta de texto (Label), para hacer un
primer programa que cambie el texto de la etiqueta cuando pulsemos el botón.
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
código. Algunas de esas propiedades son:
Si queremos que al pulsar el botón cambie el texto, tendremos que modificar el código que
corresponde al "evento" de pulsación del botón. Lo conseguimos simplemente haciendo doble
clic en el botón, y aparece este texto:
& [ %) - 2 "& : ,
Dentro de ese método escribiremos lo que queremos que ocurra al hacer clic en el botón. Por
ejemplo, para que el texto de la etiqueta "label1" pase a ser "Hola", haríamos:
& [ %) - 2 "& : ,
/ % 6 "
!
• Un programa que muestre una ventana con 3 recuadros de texto (TextBox) y un botón
"Calcular suma". Cuando se pulse el botón, en el tercer recuadro deberá aparecer la
suma de los números 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 número (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 número en binario, octal y hexadecimal. Pista: en la ventana de
propiedades, se deberá escoger "Eventos", para ver las posibles acciones relacionadas
con el TextBox.
@ ,
En una aplicación basada en ventanas, típicamente tendremos que mostrar algún mensaje de
aviso, o pedir una confirmación al usuario. Para ello podríamos crear un programa basado en
múltiples ventanas, pero eso queda más allá de lo que pretende este texto.
La segunda variante es indicar además qué botones queremos mostrar, y qué iconos de aviso:
Y la tercera variante permite indicar además cual será el botón por defecto:
Como se ve en estos ejemplos, tenemos algunos valores predefinidos para indicar qué botones
o iconos queremos mostrar:
Si queremos que el usuario responda tecleando, no tenemos ninguna ventana predefinida que
nos lo permita (sí existe un "InputBox" en otros entornos de programación, como Visual Basic),
así que deberíamos crear nosotros esa ventana de introducción de datos desde el editor visual
o mediante código, elemento por elemento.
% @
Si queremos una ventana auxiliar, que permita al usuario introducir más de un dato, deberemos
crear un segundo formulario, y llamarlo desde la ventana principal. Vamos a ver los pasos con
SharpDevelop (como siempre, serían muy similares en Visual Studio).
Vamos a crear una ventana principal que tenga una casilla de texto (TextBox) en la que no se
podrá escribir, sino que sólo se mostrarán resultados, y un botón que hará que aparezca la
ventana secundaria, en la que sí podremos introducir datos.
Para crear el segundo formulario (la ventana auxiliar), usamos la opción "Agregar / Nuevo
Elemento", del menú "Proyecto":
En esta nueva ventana, entramos a la vista de diseño y añadimos dos casillas de texto
(TextBox), con sus etiquetas aclaratorias (Label), y el botón de Aceptar:
Llega el momento de añadir el código a nuestro programa. Por una parte, haremos que el
botón "Aceptar" cierre la ventana. Lo podemos conseguir con doble clic, para que nos aparezca
la función que se lanzará con el suceso Click del botón (cuando se pulse el ratón sobre él), y
añadimos la orden "Close()" en el cuerpo de esa función, así:
& [ %) - 2 "& : ,
Además, para que desde la ventana principal se puedan leer los datos de ésta, podemos hacer
que sus componentes sean públicos, o, mejor, crear un método "Get" que devuelva el
contenido de estos componentes. Por ejemplo, podemos devolver el nombre y el apellido como
parte de una única cadena de texto, así:
, O /
6 [ 6% 6 6 [ 63 6
Ya sólo falta que desde la ventana principal se muestre la ventana secundaria y se lean los
valores al terminar. Para eso, añadimos un atributo en la ventana principal, que represente la
ventana auxiliar:
&
' <
J )
& "
Finalmente, en el suceso Click del botón 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 después leemos el valor que se había tecleado en dicha ventana:
& [ %) - 2 "& : ,
& >; ,
6 [ 6% 6 " & O /
( / D E >
Windows es un entorno gráfico, por lo que se podría suponer que deberíamos tener la
posibilidad de trabajar en "modo gráfico" desde dentro de Windows, dibujando líneas, círculos y
demás figuras básicas. En efecto, podemos usar las posibilidades de "System.Drawing" para
crear una ventana gráfica dentro de nuestro formulario (ventana de programa). Deberemos
preparar también las "plumas" ("Pen", para los contornos) y las "brochas" ("Brush", para los
rellenos) que queramos usar. Un ejemplo que dibujara una línea roja y una elipse azul cuando
pulsemos un botón del formulario podría ser así:
& [ %) - 2 "& : ,
== ) B ( & , M!
( ; > , # " ( ; > ,
( ; > , )
( ; > , [ :J " ( ; > , [
( ; > , ) [
( ; > , O & O !
& O ! " ) O
== ; / #
& O ! ; >+ # 3$$ %$$ @$$ 4$$
& O ! < " :J , $ $ 3$$ @$$
== + / N /Q &
# ;
:J ;
& O ! ;
Los métodos para dibujar líneas, rectángulos, elipses, curvas, etc. son parte de la clase
Graphics. Algunos de los métodos que ésta contiene y que pueden ser útiles para realizar
dibujos sencillos son:
& [ 3) - 2 "& : ,
O & O ! " ) O
, , " [ ' , ,
& O ! ; > , , 3$ 3$ %$$ 2$
Esta imagen debería estar en la carpeta del programa ejecutable (que quizá no sea la misma
que el fuente), y puede estar en formato BMP, GIF, PNG, JPG o TIFF.
Se puede encontrar más detalles en la referencia en línea (MSDN), por ejemplo en la página
8== ! = ? = / (= ( > , , . 6
+ > 4 4 6 3
Desde C#, tenemos la posibilidad de manejar con facilidad. Para ello, tenemos
el tipo de datos DateTime. Por ejemplo, podemos hallar la fecha (y hora) actual con:
; ! " ; >
Dentro de ese tipo de datos DateTime, tenemos las herramientas para saber el día (Day), el
mes (Month) o el año (Year) de una fecha, entre otros. También podemos calcular otras fechas
sumando a la actual una cierta cantidad de segundos (AddSeconds), días (AddDays), etc. Un
ejemplo básico de su uso sería:
=C???????????????????????????C=
=C "# ) C=
=C ! C=
=C C=
=C "# /M C=
=C # ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
# <
& '
; ! " ; >
) * + ( H$I H%I H3I
! ; ( ! ' ! P
; ( " ! : ; ( %
) * + ' L M H$I
( ; (
Algunas de las propiedades más útiles son: Now (fecha y hora actual de este equipo), Today
(fecha actual); Day (día del mes), Month (número de mes), Year (año); Hour (hora), Minute
(minutos), Second (segundos), Millisecond (milisegundos); DayOfWeek (día de la semana: su
nombre en inglés); DayOfYear (día del año).
Y para calcular nuevas fechas, podemos usar métodos que generan un nuevo objeto DateTime,
como: AddDays , AddHours, AddMilliseconds, AddMinutes, AddMonths, AddSeconds, AddHours,
o bien un Add más genérico (para sumar una fecha a otra) y un Subtract también genérico
(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 días (sin decimales, Days, o con decimales,
TotalDays), como se ve en este ejemplo:
=C???????????????????????????C=
=C "# ) C=
=C ! C=
=C C=
=C "# C=
=C # ! C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
# <
& '
; ! : " ; >
; ! " ; %22$ 2 %T
! "
! : / !
) * + H$I Q
! ; (
) * + N 8 H$I Q
! ; (
) * + P N M 8 H$I
!
G%G$ Q
N 8 G%G$BF2%FE%FEFE4 Q
P N M 8 G%G$ %F8@E8ET G$@%3E$
-
Si queremos analizar el contenido de un directorio, podemos emplear las clases Directory y
DirectoryInfo.
, ; " 1 8Y # %Y # 3
# %; ( "6 ;
; ( ) ; ( ;
También tenemos un método "GetFiles" que nos permite obtener la lista de ficheros que
contiene un directorio. Así, podríamos listar todo el contenido de un directorio con:
, ; " 1 8Y
,-. <
(la clase Directory está declarada en el espacio de nombres System.IO, por lo que deberemos
añadirlo entre los "using" de nuestro programa).
; ( ! " ; ( ! ;
< ! -. ! < " O <
# < ! ! ] < ! <
.
Si hay algo que no sepamos o podamos hacer, pero que alguna utilidad del sistema operativo sí
es capaz de hacer por nosotros, podemos hacer que ella trabaje por nosotros. La forma de
llamar a otras órdenes del sistema operativo (incluso programas externos de casi cualquier tipo)
es creando un nuevo proceso con "Process.Start". Por ejemplo, podríamos lanzar el bloc de
notas de Windows con:
" 6
En los actuales sistemas operativos multitarea se da por sentado que no es necesario esperar a
que termine otra la tarea, sino que nuestro programa puede proseguir. Si aun así, queremos
esperar a que se complete la otra tarea, lo conseguiríamos con "WaitForExit", añadiendo esta
segunda línea:
* < "6
9 / 5 5
La clase "Environment" nos sirve para acceder a información sobre el sistema: unidades de
disco disponibles, directorio actual, versión del sistema operativo y de la plataforma .Net,
nombre de usuario y máquina, carácter o caracteres que se usan para avanzar de línea, etc:
: 1F
SQLite es un gestor de bases de datos de pequeño tamaño, que emplea SQL para las consultas,
y del que existe una versión que se distribuye como un fichero DLL que acompañará al
ejecutable de nuestro programa, o bien como un fichero en C que se podría integrar
directamente en dicho ejecutable (si usamos lenguaje C en nuestro proyecto).
Para acceder a SQLite desde C#, tenemos disponible alguna adaptación de la biblioteca
original. Una de ellas de es System.Data.SQLite, disponible en http://sqlite.phxsoftware.com/
Con ella, los pasos a seguir para leer información desde una base de datos serían:
(
( ; \+ ==] J ;++
/ \+
& '
== ) 6 [;
== " ; & [;
\+ ) 6 "
\+ )
; 9 / N X 9@ >9< ) 9
6 W
== + J (
, " C ! ,
\+ ) " \+ ) 6
\+ ; " "6
== + ! &
== < B 6
6 )
Deberemos compilar con la versión 2 (o superior) de la plataforma .Net. En Mono, esto supone
emplear el compilador "gmcs" en vez de "mcs":
, / \+ = 8 ( ; \+
/ \+ 6
La base de datos de prueba se podría haber creado previamente desde el propio entorno de
SQLite, o con algún gestor auxiliar, como la extensión 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 algún dato, lo que se podría hacer así:
(
( ; \+ ==] J ;++
/ \+ 3
& '
) * + ) /
== ) 6 [;
== " ; & [;
\+ ) 6 "
\+ )
; 9 / N X 9@ >9 ) 9
6 W
== ) /
, " ) ": " :[+" ,
, ': P a"PB / X: ) : 4$ B
\+ ) " \+ ) 6
"6 \ (
== "
, " " W , X:+]" %B Z B3$
" \+ ) 6
" "6 \ (
# & %
) * +
== < B 6
6 )
) * + )
Nota: el primer ejemplo daba por sentando que en la tabla "gente" existían dos campos (o
más), 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
"código", incluido a modo de ejemplo de cómo se puede crear una clave primaria.
G 6 1/
SDL es una conocida biblioteca para la realización de juegos, que está disponible para diversos
sistemas operativos y que permite tanto dibujar imágenes como comprobar el teclado, el ratón
o el joystick, así como reproducir sonidos.
Tao.SDL es una adaptación de esta librería, que permite emplearla desde C#. Se puede
descargar desde http://www.monoDproject.com/Tao
SDL no es una librería especialmente sencilla, y tampoco lo acaba de ser Tao.SDL, así que los
fuentes siguientes pueden resultar difíciles de entender, a pesar de realizar tareas muy básicas.
Por eso, muchas veces es preferible "ocultar" los detalles de SDL creando nuestras propias
clases "Hardware", "Imagen", etc., como verás al final de este apartado.
&
Vamos a ver un primer ejemplo, básico pero completo, que muestre cómo entrar a modo
gráfico, cargar una imagen, dibujarla en pantalla, esperar cinco segundos y volver al sistema
operativo. Tras el fuente comentaremos las principales funciones.
=C???????????????????????????C=
=C "# ) C=
=C $% C=
=C C=
=C C=
=C ;+ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
( == 8 M, B
$%
& '
" T$$
" F$$
/ ) " 34
! , " ;+. * ] <:)") ;+.;W][+"[]<) ;+.: P<W ':
W
== J ;+
;+. ;+. ."X" P O
W " ;+. X '
/ )
! ,
== N N ,
;+. 3 "
;+. $ $
;+. ) W # 3
== ) , ,
,
, " ;+.+ [' # /
# , "" k
( ) * + , 6 S
Revisión 0.97 – Página 220
Introducción a la programación con C#, por Nacho Cabanes
== ; / # ,
6 " 4$$
( " @$$
, " E$
, " E$
;+. , " ;+. $ $ , ,
;+. " ;+. 6 ( , ,
;+.[ ! , # , W #
== '
;+.< W
== P E ,
( , E$$$
== < B ;+
;+.\
• SDL_Init es la rutina de inicialización, que entra a modo gráfico, con cierto ancho y alto
de pantalla, cierta cantidad de colores y ciertas opciones adicionales.
• El tipo SDL_Rect define un "rectángulo" 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 tamaño 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 sólo usamos SDL "puro", no
podremos emplear otros tipos que permitan menores tamaños, como el JPG, o
transparencia, como el PNG). El tipo de dato que se obtiene es un "IntPtr" (del que no
daemos más 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 habría
podido cargar) u otro distinto (y entonces la imagen se habría leido sin problemas).
• SDL_BlitSurface vuelca un rectángulo (SDL_Rect) sobre otro, y lo usamos para ir
dibujando todas las imágenes en una pantalla oculta, y finalmente volcar toda esa
pantalla oculta a la pantalla visible, con lo que se evitan parpadeos (es una técnica 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 función de SDL, sino Thread.Sleep, que ya
habíamos comentado en el apartado sobre temporización.
• SDL_Quit libera los recursos (algo que generalmente haríamos desde un destructor).
Si no vamos a usar imágenes comprimidas (PNG o JPG), ni tipos de letra TTF, ni sonidos en
formato MP3, ni funciones adicionales de dibujo (líneas, recuadros, círculos, etc). No
necesitaremos ninguna DLL adicional.
C A !
Un segundo ejemplo algo más detallado podría permitirnos mover el personaje con las flechas
del teclado, a una velocidad de 50 fotogramas por segundo, así:
=C???????????????????????????C=
=C "# ) C=
=C $3 C=
=C C=
=C , C=
=C ;+ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
( == 8 M, B
$3
& '
" T$$
" F$$
/ ) " 34
! , " ;+. * ] <:)" ) ;+.;W][+"[]< ) ;+.: P<W ':
) ;+.<]++ ) ""
W
== J ;+
;+. ;+. ."X" P O
W " ;+. X '
/ )
! ,
== N N ,
;+. 3 "
;+. $ $
;+. ) W # 3
== ) , ,
,
, " ;+.+ [' # /
# , "" k
( ) * + , 6 S
" & "6 4
== &
/ " #
6 " 4$$
( " @$$
, " E$
, " E$
;+."&
- (
/( -.
== ) /
;+. "&
" ;+.O a ( - (
== ' , !
# - ;+a.] . "" %
( " 3
# - ;+a.;W* . "" %
( " 3
# - ;+a.+"< . "" %
6 " 3
# - ;+a. O . "" %
6 " 3
# - ;+a." ): ". "" %
"
== [
;+. , " ;+. $ $
;+.< W # , $
== ; / # &
Revisión 0.97 – Página 223
Introducción a la programación con C#, por Nacho Cabanes
, " ;+. $ $ , ,
;+. " ;+. 6 (
, ,
;+.[ ! , # ,
W #
== '
;+.< W
== P 3$
( , 3$
== < B ;+
;+.\
% ' =
Si queremos escribir texto usando tipos de letra TrueType (los habituales en Windows y Linux),
los cambios no son grandes:
Debemos incluir el fichero DLL llamado SDL_ttf.DLL en la carpeta del ejecutable de nuestro
programa, así como el fichero TTF correspondiente a cada tipo de letra que queramos usar.
Tenemos que declarar un nuevo dato que será nuestro tipo de letra, del tipo genérico "IntPtr":
; +
! <.
Luego preparamos el tipo de letra que queremos usar, indicando a partir de qué fichero TTF y
en qué tamaño:
== ] B %T
; + " ! <.W < < [ ! %T
# ; + "" k
( ) * + 6 8 < [ !S
" & "6 E
== P , 6
;+.) :J " ;+.) E$ E$ 3EE
, 6 " 6 #
6 ) , " ! <. 6 .
; + 6 :J
# 6 ) , "" k
( ) * + J 6
" & "6 F
Y podríamos dibujar esa imagen en cualquier parte de la pantalla, como cualquier otra imagen:
== " / 6 B ,
, " ;+. $ $
" ;+. 3$$ %$$
;+.[ ! 6 ) , # ,
W #
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.
FEO ( PFO
Las imágenes BMP ocupan mucho espacio, y no permiten características avanzadas, como la
transparencia (aunque se podría imitar). Si queremos usar imágenes en formatos más
modernos, como JPG o PNG, sólo tenemos que incluir unos cuantos ficheros DLL más y hacer
un pequeño cambio en el programa.
Los nuevos ficheros que necesitamos son: SDL_image.dll (el principal), libpng13.dll (para
imágenes PNG), zlib1.dll (auxiliar para el anterior) y jpeg.dll (si queremos usar imágenes JPG).
En el fuente, sólo cambiaría la orden de cargar cada imagen, que no utilizaría Sdl.SDL_LoadBMP
sino SdlImage.IMG_Load:
, " , 'O.+ # ,
C " 2 4 2
Un fuente con SDL puede resultar difícil de leer, y más aún si no está estructurado, sino que
tiene toda la lógica dentro de "Main" y va creciendo arbitrariamente. Por eso, suele ser
preferible crear nuestras propias funciones que la oculten un poco: funciones como "Inicializar",
"CargarImagen", "ComprobarTeclas", "DibujarImagenes", etc.
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 podría
ser éste:
=C???????????????????????????C=
=C "# ) C=
=C $E C=
=C C=
=C \ C=
=C ;+ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
( == 8 M, B
Z ,
" T$$
" F$$
/ " #
6 " 4$$
( " @$$
, " E$
, " E$
;+."&
- (
/( -.
W
; +
,
& J
/ ) " 34
! , " ;+. * ] <:)" ) ;+.;W][+"[]< ) ;+.: P<W ':
) ;+.<]++ ) ""
== J ;+
;+. ;+. ."X" P O
W " ;+. X '
/ )
! ,
== P <B / 6
! <.
== N N ,
;+. 3 "
;+. $ $
;+. ) W # 3
== ) , ,
, " , 'O.+ # ,
# , "" k
( ) * + , 6 8 # ,S
" & "6 4
== P B %T
; + " ! <.W < < [ ! %T
# ; + "" k
( ) * + 6 8 < [ !S
" & "6 E
& /
== ) /
;+. "&
" ;+.O a ( - (
== ' , !
# - ;+a.] . "" %
( " 3
# - ;+a.;W* . "" %
( " 3
# - ;+a.+"< . "" %
6 " 3
# - ;+a. O . "" %
6 " 3
# - ;+a." ): ". "" %
"
& / )
== & / ,
== /
== & ( , N &
& / # "
== [
;+. , " ;+. $ $
;+.< W # , $
== ; / # , &
/ # , W , 6 ( , ,
== " / B 6 ,
/ 6 W 6 #
=C ) C= 3$$ %$$
=C ) C= E$ E$ 3EE
; +
== '
;+.< W
& < ,
== " 3$
( , 3$
& / # , W , 6 (
& / 6 W , 6
6 ( /( /( , /( / !
== ) , 6
;+.) " ;+.) , /
6 ) , " ! <. 6 .
! 6
# 6 ) , "" k
( ) * + J 6
" & "6 F
/ # , W 6 ) , 6 ( T$$ 6 F$$ (
& '
Z , # " Z ,
# J
== [ # ,
# /
# & "
# / )
# / # "
# < ,
% #
== < B ;+
;+.\
1 > =
Cuando el proyecto es grande, no basta con que esté desglosado en funciones. En esos casos,
es muy interesante analizar qué objetos interaccionan y plantear el programa como una serie
de clases que interactúan. Eso permite que el proyecto esté formado por varios fuentes de
menor tamaño, cada uno con una responsabilidad clara, y a su vez esto facilita repartir el
trabajo entre varios programadores.
Si aplicamos este planteamiento a nuestro esqueleto de juego, podríamos crear clases auxiliares
como:
Y esto podría simplificar el cuerpo del juego para que acabara siendo algo así:
=C???????????????????????????C=
=C "# ) C=
=C $F C=
=C C=
=C 6 C=
=C ;+ C=
=C C=
=C ) B C=
=C ) / C=
=C???????????????????????????C=
Z ,
" T$$
" F$$
/ " #
6 " 4$$
( " @$$
< ; +
" O ! #
& J
/ ) " #
> J T$$ F$$ 34 )
# " " O ! # ,
; + " < < [ ! %T
& /
& / )
== & / ,
Revisión 0.97 – Página 230
Introducción a la programación con C#, por Nacho Cabanes
== /
== & ( , N &
& / # "
== [
> [ W $ $ $
== ; / # # ( 6
# ; / # W 6 (
> " / 6 W 6 #
=C ) C= 3$$ %$$
=C ) C= E$ E$ 3EE
; +
== < B
> X J W
& < ,
== " 3$
> 3$
& '
Z , # " Z ,
# J
== [ # ,
# /
# & "
# / )
# / # "
# < ,
% #
== < B ;+
;+.\
(¿Y cómo podrían ser esas clases Hardware, Imagen, etc? Tienes detalles en un apéndice al
final de este texto).
:
Muchos de los servicios que podemos obtener de Internet o de una red local son accesibles de
forma sencilla, típicamente enmascarados como si leyéramos de un fichero o escribiéramos en
él.
(Nota: en este apartado se asumirá que el lector entiende algunos conceptos básicos de redes,
como qué es una dirección IP, qué significa "localhost" o qué diferencias hay entre el protocolo
TCP y el UDP).
Como primer ejemplo, vamos a ver cómo podríamos recibir una página web (por ejemplo, la
página principal de "www.nachocabanes.com"), línea a línea como si se tratara de un fichero de
texto (StreamReader), y mostrar sólo las líneas que contengan un cierto texto (por ejemplo, la
palabra "Pascal"):
== "# , ( M > /
== ' Q N
(
( W ==
( == ( * /)
; , * /
& '
* /) " * /)
6 " W 8==>>> /
" 6
,
" $
" + %"
# 6W! $" $
) * + H$I8 H%I
6 )
3T8 !9 = 6 =; = =
E48 V !9 = 6 = ; =
3$48 !9 = 6 =; = =
Otra posibilidad que tampoco es complicada (aunque sí algo más que ésta última) es la de
comunicar dos ordenadores, para enviar información desde uno y recibirla desde el otro. Se
puede hacer de varias formas. Una de ellas es usando directamente el protocolo TCP:
emplearemos un TcpClient para enviar y un TcpListener para recibir, y en ambos casos
trataremos los datos como un tipo especial de fichero binario, un NetworkStream:
(
( W ==
( 6 == " ,
( == ; B :
( - == > -
; , * /
, / "
/ " 3%%3
& & , , !
) " )
> - 6 " O
/( -. + " " , : ) O [( !
6 * + $ + + ,
6 )
)
, ,
== N
== 7
: " ; & : + -$.
== ) J ! 7
+ " +
) " : )
> - 6 " O
" 6
, ! " "
& '
) * + % / 3 &
, " ) +
) * " &
& / / / 6
) * + " &
== /
) * + "
) * + / /
) * + /
% / 3 &
%
"
Entonces lanzaríamos otra sesión del mismo programa en el mismo ordenador (porque estamos
conectando a la dirección "localhost"), y en esta nueva sesión escogeríamos la opción de Enviar
(2):
% / 3 &
3
" &
/ 6
/
Esta misma idea se podría usar como base para programas más elaborados, que comunicaran
diferentes equipos (en este caso, la dirección no sería "localhost", sino la IP del otro equipo),
como podría ser un chat o cualquier juego multijugador en el que hubiera que avisar a otros
jugadores de los cambios realizados por cada uno de ellos.
Esto se puede conseguir a un nivel algo más alto, usando los llamados "Sockets" en vez de los
TcpClient, o de un modo "no fiable", usando el protocolo UDP en vez de TCP, pero nosotros no
veremos más detalles de ninguno de ambos métodos.
* (
! '
La depuración es el análisis de un programa para descubrir fallos. El nombre en inglés es
"debug", porque esos fallos de programación reciben el nombre de "bugs" (bichos).
Para eliminar esos fallos que hacen que un programa no se comporte como debería, se usan
unas herramientas llamadas "depuradores". Estos nos permiten avanzar paso a paso para ver
cómo avanza realmente nuestro programa, y también nos dejan ver los valores de las variables.
/ 81 .
Vamos a tomar como ejemplo la secuencia de operaciones que se propuso como ejercicio al
final del apartado 2.1:
) : %
" E
/ " 3
/ " @
" @
!" 3
!" /
Para avanzar paso a paso y ver los valores de las variables, entramos al menú "Depurar". En él
aparece la opción "Paso a paso por instrucciones" (al que corresponde la tecla F11):
Si escogemos esa opción del menú o pulsamos F11, aparece una ventana inferior con la lista de
variables, y un nuevo cursor amarillo, que se queda al principio del programa:
Cada vez que pulsemos nuevamente F11 (o vayamos al menú, o al botón correspondiente de la
barra de herramientas), el depurador analiza una nueva línea de programa, muestra los valores
de las variables correspondientes, y se vuelve a quedar parado, realzando con fondo amarillo la
siguiente línea que se analizará:
Aquí hemos avanzado desde el principio del programa, pero eso no es algo totalmente habitual.
Es más frecuente que supongamos en qué zona del programa se encuentra el error, y sólo
queramos depurar una zona de programa. La forma de conseguirlo es escoger otra de las
opciones del menú de depuración: "Alternar puntos de ruptura" (tecla F9). Aparecerá una
marca granate en la línea actual:
Si ahora iniciamos la depuración del programa, saltará sin detenerse hasta ese punto, y será
entonces cuando se interrumpa. A partir de ahí, podemos seguir depurando paso a paso como
antes.
Es frecuente que la corrección de ciertos errores introduzca a su vez errores nuevos. Por eso,
una forma habitual de garantizar la calidad de los programas es creando una "batería de
pruebas" que permita comprobar de forma automática que todo se comporta como debería.
Así, antes de dar por definitiva una versión de un programa, se lanza la batería de pruebas y se
verifica que los resultados son los esperados.
Una forma sencilla de crear una batería de pruebas es comprobando los resultados de
operaciones conocidas. Vamos a ver un ejemplo, para un programa que calcule las soluciones
de una ecuación de segundo grado.
, O
& &
! ! / !
! 6% ! 63
" /!/ 4! !
# & $
6% " 2222
63 " 2222
# "" $
6% " / 3 3!
63 " 2222
6% " ! / ' N 3 3!
63 " ! / ' N 3 3!
Es decir, si alguna solución no existe, se devuelve un valor falso, que no sea fácil que se
obtenga en un caso habitual, como D9999.
x2 D1 = 0 x1 = 1, x2 = D1
x2 = 0 x1 = 0, x2 = No existe (solución única)
x2 D3x = 0 x1 = 3, x2 = 0
2x2 D2 = 0 x1 = 1, x2 = D1
/ , O
& '
! % 3
) * + / , ,
, O " " , O
) * / 63 ? % 9 $
, O & ! % ! $ ! %
% 3
# % "" % (( 3 "" %
) * + Wa
) * + <
) * / 63 9 $
, O & ! % ! $ ! $
% 3
# % "" $ (( 3 "" 2222
) * + Wa
) * + <
) * / 63 ?@6 9 $
, O & ! % ! @ ! $
% 3
# % "" @ (( 3 "" $
) * + Wa
) * + <
) * / 363 ? 3 9 $
, O & ! 3 ! $ ! 3
% 3
Revisión 0.97 – Página 240
Introducción a la programación con C#, por Nacho Cabanes
# % "" % (( 3 "" %
) * + Wa
) * + <
/ , ,
/ 63 ? % 9 $ Wa
/ 63 9 $ Wa
/ 63 ?@6 9 $ Wa
/ 363 ? 3 9 $ <
Vemos que en uno de los casos, la solución no es correcta. Revisaríamos los pasos que da
nuestro programa, corregiríamos el fallo y volveríamos a lanzar las pruebas automatizadas. En
este caso, el problema es que falta un paréntesis, para dividir entre (2*a), de modo que
estamos diviendo entre 2 y luego multiplicando por a.
La ventaja de crear baterías de pruebas es que es una forma muy rápida de probar un
programa, por lo que se puede aplicar tras cada pocos cambios para comprobar que todo es
correcto. El inconveniente es que 4 8030 6*90 que el programa sea correcto, sino sólo que
no falla en ciertos casos. Por ejemplo, si la batería de pruebas anterior solo contuviera las tres
primeras pruebas, no habría descubierto el fallo del programa.
Por tanto, las pruebas sólo permiten asegurar que el programa falla en caso de encontrar
problemas, pero no permiten asegurar nada en caso de que no se encuentren problemas:
puede que aun así exista un fallo que no hayamos detectado.
Para crear las baterías de pruebas, lo que más 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".
% / '
La mayor parte de la documentación de un programa "serio" se debe realizar antes de
comenzar a teclear:
Casi todos esos diagramas caen fuera del alcance de este texto: en una introducción a la
programación se realizan programas de pequeño tamaño, para los que no es necesaria una
gran planificación.
Aun así, hay un tipo de documentación que sí debe estar presente en cualquier problema: los
que aclaren todo aquello que no sea obvio.
Por eso, este apartado se va a centrar en algunas de las pautas que los expertos suelen
recomendar para los comentarios en los programas, y también veremos como a partir de estos
comentarios se puede generar documentación adicional de forma casi automática.
4
Existen buenas recopilaciones de consejos en Internet. Yo voy a incluir (algo resumida) una de
José M. Aguilar, recopilada en su blog "Variable not found". El artículo original se puede
encontrar en:
http://www.variablenotfound.com/2007/12/13DconsejosDparaDcomentarDtuDcdigo.html
# '
Comenta los distintos bloques de los que se compone tu código, aplicando un criterio uniforme
y distinto para cada nivel, por ejemplo:
• En cada clase, incluir una breve descripción, su autor y fecha de última modificación
• Por cada método, una descripción de su objeto y funcionalidades, así como de los
parámetros y resultados obtenidos
(Lo importante es ceñirse a unas normas y aplicarlas siempre).
: -
Además, es recomendable dividir un bloque de código extenso en "párrafos" que realicen una
tarea simple, e introducir un comentario al principio además de una línea en blanco para
separar bloques:
== ) /
==
!
H
; 6 <
Si tenemos un bloque de líneas de código, cada una con un comentario, será más legible si
están alineados:
':g. "' 9 %$ == K M6 N
': a 9 $6%< == 'M / )
Ojo a las tabulaciones. Hay editores de texto que usan el carácter ASCII (9) y otros, lo
sustituyen por un número determinado de espacios, que suelen variar según las preferencias
personales del desarrollador. Lo mejor es usar espacios simples o asegurarse de que esto es lo
que hace el IDE correspondiente.
=
Debemos evitar comentarios absurdos como:
! 99 E == & B
9 $ ==
> (
Evita comentarios del tipo "ahora compruebo que el estúpido usuario no haya introducido un
número negativo", o "este parche corrige el efecto colateral producido por la patética
implementación del inepto desarrollador inicial".
Relacionado e igualmente importante: cuida la ortografía.
?
No comentes si no es necesario, no escribas nada más que lo que necesites para transmitir la
idea. Nada de diseños realizados a base de caracteres ASCII, ni florituras, ni chistes, ni poesías,
ni chascarrillos.
@ - A
Hay quien opina que los comentarios deberían 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.
B ,
Y sobre todo cuando se trabaja en un equipo de programación. El ejemplo típico es el
comentario TODO (toDdo, por hacer), que describe funciones pendientes de implementar:
6B (
H
== W;W8 M
$
I
C '
Ve introduciendo los comentarios conforme vas codificando. No lo dejes para el final, puesto
que entonces te costará más 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 deberían escribirse antes
de codificarlo, de forma que, en primer lugar, sirvan como referencia para saber qué es lo que
hay que hacer y, segundo, que una vez codificado éstos queden como comentarios para la
posteridad. Un ejemplo:
/ &
H
== ) / N (
== ) / N &M
== " & 5
== O !
I
#$ ' < 1
A la hora de comentar no pienses sólo en mantenimiento posterior, ni creas que es un regalo
que dejas para la posteridad del que sólo obtendrá beneficios el desarrollador que en el futuro
sea designado para corregir o mantener tu código. En palabras del genial Phil Haack, "tan
pronto como una línea de código sale de la pantalla y volvemos a ella, estamos en modo
mantenimiento de la misma"
## 0 A A
De nada sirve comentar correctamente una porción de código si en cuanto éste es modificado
no se actualizan también los comentarios. Ambos deben evolucionar paralelamente, o
estaremos haciendo más difícil la vida del desarrollador que tenga que mantener el software, al
darle pistas incorrectas.
#: /
Es uno de los principios básicos para muchos desarrolladores: deja que tu código hable por sí
mismo. Aunque se sospecha que este movimiento está liderado por programadores a los que
no les gusta comentar su código ;D), es totalmente cierto que una codificación limpia puede
hacer innecesaria la introducción de textos explicativos adicionales:
Console.WriteLine("Resultado: " +
new Calculator()
.Set(0)
.Add(10)
.Multiply(2)
.Substract(4)
.Get()
);
#; 1
Aunque nosotros mismos nos beneficiamos inmediatamente de las bondades de nuestro código
comentado, la generalización y racionalización de los comentarios y la creación código
inteligible nos favorecerá a todos, sobre todo en contextos de trabajo en equipo.
O
Conocemos los comentarios de bloque (/* hasta */) y los comentarios hasta final de línea (a
partir de una doble barra // ).
Pero en algunos lenguajes modernos, como Java y C#, existe una posibilidad adicional que
puede resultar muy útil: usar comentarios que nos ayuden a crear de forma automática cierta
documentación del programa.
Esta documentación típicamente será una serie páginas HTML enlazadas, o bien varios ficheros
XML.
Por ejemplo, la herramienta (gratuita) Doxygen genera páginas como ésta a partir de un fuente
en C#:
Lo habitual es que estos tipos de comentarios se utilicen al principio de cada clase y de cada
método, así:
=CC
C # 8 , ! # ,
C
C 4 " O ! Z ,
C 4 %?;: 3$$T=$2
C=
Revisión 0.97 – Página 245
Introducción a la programación con C#, por Nacho Cabanes
# + " O !
== ' & # B / / M
& ' & ;
=== : 7 # 8 / B
& '
Además, es habitual que tengamos a nuestra disposición ciertas "palabras reservadas" para
poder afinar la documentación, como @returns, que da información sobre el valor que devuelve
una función, o como @see, para detallar qué otras clases de nuestro programa están
relacionadas con la actual.
Así, comparando el fuente anterior y la documentación que se genera a partir de él, podemos
ver que:
En el lenguaje Java, documentación como esta se puede crear con la herramienta JavaDoc, que
es parte de la distribución "oficial" del lenguaje. En cambio, en C#, la herramienta estándar
genera documentación en formato XML, que puede resultar menos legible, pero se pueden
emplear alternativas gratuitas como Doxygen.
$ J C (
: 2
En informática, la unidad básica de información es el . En la práctica, podemos pensar que
un byte es el equivalente a una . Si un cierto texto está formado por 2000 letras, podemos
esperar que ocupe unos 2000 bytes de espacio en nuestro disco.
Eso sí, suele ocurrir que realmente un texto de 2000 letras que se guarde en el ordenador
ocupe más de 2000 bytes, porque se suele incluir información adicional sobre los tipos de letra
que se han utilizado, cursivas, negritas, márgenes y formato de página, etc.
Un byte se queda corto a la hora de manejar textos o datos algo más largos, con lo que se
recurre a un múltiplo suyo, el % , que se suele abreviar D o K.
En teoría, el prefijo kilo querría decir "mil", luego un kilobyte debería ser 1000 bytes, pero en
los ordenadores conviene buscar por comodidad una potencia de 2 (pronto veremos por qué),
por lo que se usa 210 =1024. Así, la equivalencia exacta es 1 K = 1024 bytes.
Los K eran unidades típicas para medir la memoria de ordenadores: 640 K ha sido mucho
tiempo la memoria habitual en los IBM PC y similares. Por otra parte, una página
mecanografiada suele ocupar entre 2 K (cerca de 2000 letras) y 4 K.
Cuando se manejan datos realmente extensos, se pasa a otro múltiplo, el o Mb, que
es 1000 K (en realidad 1024 K) o algo más de un millón de bytes. Por ejemplo, en un diskette
"normal" caben 1.44 Mb, y en un Compact Disc para ordenador (CdDRom) se pueden almacenar
hasta 700 Mb. La memoria principal (RAM) de un ordenador actual suele andar por encima de
los 512 Mb, y un disco duro actual puede tener una capacidad superior a los 80.000 Mb.
Para estas unidades de gran capacidad, su tamaño no se suele medir en megabytes, sino en el
múltiplo siguiente: en , con la correspondencia 1 Gb = 1024 Mb. Así, son cada vez
más frecuentes los discos duros con una capacidad de 120, 200 o más gigabytes.
Y todavía hay unidades mayores, pero que aún se utilizan muy poco. Por ejemplo, un
son 1024 gigabytes.
:
Revisión 0.97 – Página 247
Introducción a la programación con C#, por Nacho Cabanes
: @ , ' A B*
Dentro del ordenador, la información se debe almacenar realmente de alguna forma que a él le
resulte "cómoda" de manejar. Como la memoria del ordenador se basa en componentes
electrónicos, la unidad básica de información será que una posición de memoria esté usada o
no (totalmente llena o totalmente vacía), lo que se representa como un 1 o un 0. Esta unidad
recibe el nombre de .
Un bit es demasiado pequeño para un uso normal (recordemos: sólo puede tener dos valores: 0
ó 1), por lo que se usa un conjunto de ellos, 8 bits, que forman un . Las matemáticas
elementales (combinatoria) nos dicen que si agrupamos los bits de 8 en 8, tenemos 256
posibilidades distintas (variaciones con repetición de 2 elementos tomados de 8 en 8: VR2,8):
$$$$$$$$
$$$$$$$%
$$$$$$%$
$$$$$$%%
$$$$$%$$
%%%%%%%$
%%%%%%%%
Por tanto, si en vez de tomar los bits de 1 en 1 (que resulta cómodo para el ordenador, pero no
para nosotros) los utilizamos en grupos de 8 (lo que se conoce como un byte), nos
encontramos con 256 posibilidades distintas, que ya son más que suficientes para almacenar
una letra, o un signo de puntuación, o una cifra numérica o algún otro símbolo.
Por ejemplo, se podría decir que cada vez que encontremos la secuencia 00000010 la
interpretaremos como una letra A, y la combinación 00000011 como una letra B, y así
sucesivamente.
También existe una correspondencia entre cada grupo de bits y un número del 0 al 255: si
usamos el sistema binario de numeración (que aprenderemos dentro de muy poco), en vez del
sistema decimal, tenemos que:
$$$$ $$$$ / 9 $
$$$$ $$$% / 9 %
$$$$ $$%$ / 9 3
$$$$ $$%% / 9 @
Aun así, hay un inconveniente con el código ASCII: sólo los primeros 127 números son
estándar. Eso quiere decir que si escribimos un texto en un ordenador y lo llevamos a otro, las
letras básicas (A a la Z, 0 al 9 y algunos símbolos) no cambiarán, pero las letras internacionales
(como la Ñ o las vocales con acentos) puede que no aparezcan correctamente, porque se les
asignan números que no son estándar para todos los ordenadores.
! Eso de que realmente el ordenador trabaja con ceros y unos, por lo que le resulta más
fácil manejar los números que son potencia de 2 que los números que no lo son, es lo que
explica que el prefijo kilo no quiera decir "exactamente mil", sino que se usa la potencia de 2
más cercana: 210 =1024. Por eso, la equivalencia exacta es # D E #$:= .
$ J ' $:
El nombre del código ASCII viene de "American Standard Code for Information Interchange",
que se podría traducir como Código Estándar Americano para Intercambio de Información.
La idea de este código es que se pueda compartir información entre distintos ordenadores o
sistemas informáticos. Para ello, se hace corresponder una letra o carácter a cada secuencia de
varios bits.
El código ASCII estándar es de 7 bits, lo que hace que cada grupo de bits desde el 0000000
hasta el 1111111 (0 a 127 en decimal) corresponda siempre a la misma letra.
Por ejemplo, en cualquier ordenador que use código ASCII, la secuencia de bits 1000001 (65 en
decimal) corresponderá a la letra "A" (a, en mayúsculas).
Los códigos estándar "visibles" van del 32 al 127. Los códigos del 0 al 31 son códigos de
control: por ejemplo, el caracter 7 (BEL) hace sonar un pitido, el caracter 13 (CR) avanza de
línea, el carácter 12 (FF) expulsa una página en la impresora (y borra la pantalla en algunos
ordenadores), etc.
V $ % 3 @ 4 E F G T 2 V
V $$$ ]+ W g " g "W " \ :)a ["+ [ V
V $%$ +< X << ) W ;+" ;)% ;)3 ;)@ V
V $3$ ;)4 :a P " [ ): "' ][ " ) < O V
V $@$ ] S 0 D U V
V $4$ C A B ? = $ % V
V $E$ 3 @ 4 E F G T 2 8 V
V $F$ 9 ^ 1 : [ ) ; " V
V $G$ < O Z a + ' W V
V $T$ \ ] X * g P V
V $2$ k _ Y ` j . h / V
V %$$ ! , # - V
V %%$ N & > V
V %3$ 6 ( J H V I l m V
Hoy en día, casi todos los ordenadores incluyen un código ASCII extendido de 8 bits, que
permite 256 símbolos (del 0 al 255), lo que permite que se puedan usar también vocales
acentuadas, eñes, y otros símbolos para dibujar recuadros, por ejemplo, aunque estos símbolos
que van del número 128 al 255 no son estándar, de modo que puede que otro ordenador no los
interprete correctamente si el sistema operativo que utiliza es distinto.
Una alternativa más moderna es el estándar Unicode, que permite usar caracteres de más de 8
bits (típicamente 16 bits, que daría lugar a 65.536 símbolos distintos), lo que permite que la
transferencia de información entre distintos sistemas no tenga problemas de distintas
interpretaciones.
$ J % :
: 1
Nosotros normalmente utilizamos el de numeración: todos los números se
expresan a partir de potencias de 10, pero normalmente lo hacemos sin pensar.
(aunque realmente nosotros lo hacemos automáticamente: no nos paramos a pensar este tipo
de cosas cuando sumamos o multiplicamos dos números).
Para los ordenadores no es cómodo contar hasta 10. Como partimos de "casillas de memoria"
que están completamente vacías (0) o completamente llenas (1), sólo les es realmente cómodo
contar con 2 cifras: 0 y 1.
Por eso, dentro del ordenador cualquier número se deberá almacenar como ceros y unos, y
entonces los números se deberán desglosar en potencias de 2 (el llamado " "):
13 = 1 · 8 + 1 · 4 + 0 · 2 + 1 · 1
13 = 1 · 2 3 + 1 · 2 2 + 0 · 2 1 + 1 · 2 0
Convertir un número de decimal a binario resulta algo menos intuitivo. Una forma sencilla es ir
dividiendo entre las potencias de 2, y coger todos los cocientes de las divisiones:
1 / 2 = 0 (resto: 1)
1 / 1 = 1 (se terminó).
Si "juntamos" los cocientes que hemos obtenido, aparece el número binario que buscábamos:
109 decimal = 0110 1101 binario
(Nota: es frecuente separar los números binarios en grupos de 4 cifras Dmedio byteD para
mayor legibilidad, como yo he hecho en el ejemplo anterior; a un grupo de 4 bits se le llama
).
Si leemos esos restos de abajo a arriba, obtenemos el número binario: 1101101 (7 cifras, si
queremos completarlo a 8 cifras rellenamos con ceros por la izquierda: 01101101).
¿Y se pueden hacer operaciones con números binarios? Sí, casi igual que en decimal:
:
1. Expresar en sistema binario los números decimales 17, 101, 83, 45.
2. Expresar en sistema decimal los números binarios de 8 bits: 01100110, 10110010,
11111111, 00101101
3. Sumar los números 01100110+10110010, 11111111+00101101. Comprobar el
resultado sumando los números decimales obtenidos en el ejercicio anterior.
4. Multiplicar los números binarios de 4 bits 0100·1011, 1001·0011. Comprobar el
resultado convirtiéndolos a decimal.
: 1
Hemos visto que el sistema de numeración más cercano a como se guarda la información
dentro del ordenador es el sistema binario. Pero los números expresados en este sistema de
numeración "ocupan mucho". Por ejemplo, el número 254 se expresa en binario como
11111110 (8 cifras en vez de 3).
Por eso, se han buscado otros sistemas de numeración que resulten más "compactos" que el
sistema binario cuando haya que expresar cifras medianamente grandes, pero que a la vez
mantengan con éste una correspondencia algo más sencilla que el sistema decimal. Los más
usados son el sistema octal y, sobre todo, el hexadecimal.
de modo que
254 = 3 · 8 2 + 7 · 8 1 + 6 · 8 0
o bien
Hemos conseguido otra correspondencia que, si bien nos resulta a nosotros más incómoda que
usar el sistema decimal, al menos es más compacta: el número 254 ocupa 3 cifras en decimal,
y también 3 cifras en octal, frente a las 8 cifras que necesitaba en sistema binario.
Pero además existe una correspondencia muy sencilla entre el sistema octal y el sistema
binario: si agrupamos los bits de 3 en 3, el paso de es rapidísimo
de modo que
o bien
:
1. Expresar en sistema octal los números decimales 17, 101, 83, 45.
2. Expresar en sistema octal los números binarios de 8 bits: 01100110, 10110010,
11111111, 00101101
3. Expresar en el sistema binario los números octales 171, 243, 105, 45.
4. Expresar en el sistema decimal los números octales 162, 76, 241, 102.
: 1 4
El sistema octal tiene un inconveniente: se agrupan los bits de 3 en 3, por lo que convertir de
binario a octal y viceversa es muy sencillo, pero un byte está formado por 8 bits, que no es
múltiplo de 3.
Sería más cómodo poder agrupar de 4 en 4 bits, de modo que cada byte se representaría por 2
cifras. Este sistema de numeración trabajará en base 16 (2 4 =16), y es lo que se conoce como
sistema hexadecimal.
Pero hay una dificultad: estamos acostumbrados al sistema decimal, con números del 0 al 9, de
modo que no tenemos cifras de un solo dígito para los números 10, 11, 12, 13, 14 y 15, que
utilizaremos en el sistema hexadecimal. Para representar estas cifras usaremos las letras de la
A a la F, así:
$ 9 $ 6
% 9 % 6
3 9 3 6
@ 9 @ 6
4 9 4 6
E 9 E 6
F 9 F 6
G 9 G 6
T 9 T 6
2 9 2 6
%$ 9 : 6
%% 9 [ 6
%3 9 ) 6
%@ 9 ; 6
%4 9 " 6
%E 9 < 6
de modo que
254 = 15 · 16 1 + 14 · 16 0
o bien
de modo que
o bien
254 = 13 · 16 3 + 4 · 16 2 + 3 · 16 1 + 11 · 16 0
es decir
$ 6 9 $ 9 $$$$ /
% 6 9 % 9 $$$% /
3 6 9 3 9 $$%$ /
@ 6 9 @ 9 $$%% /
4 6 9 4 9 $%$$ /
E 6 9 E 9 $%$% /
F 6 9 F 9 $%%$ /
G 6 9 G 9 $%%% /
T 6 9 T 9 %$$$ /
2 6 9 2 9 %$$% /
: 6 9 %$ 9 %$%$ /
[ 6 9 %% 9 %$%% /
) 6 9 %3 9 %%$$ /
; 6 9 %@ 9 %%$% /
" 6 9 %4 9 %%%$ /
< 6 9 %E 9 %%%% /
110010100100100101010100111 =>
0110 0101 0010 0100 1010 1010 0111 = 6524AA7
:
1. Expresar en sistema hexadecimal los números decimales 18, 131, 83, 245.
2. Expresar en sistema hexadecimal los números binarios de 8 bits: 01100110, 10110010,
11111111, 00101101
3. Expresar en el sistema binario los números hexadecimales 2F, 37, A0, 1A2.
4. Expresar en el sistema decimal los números hexadecimales 1B2, 76, E1, 2A.
: % 0
Para los , existen varias formas posibles de representarlos. Las
más habituales son:
Revisión 0.97 – Página 255
Introducción a la programación con C#, por Nacho Cabanes
Es un método muy sencillo, pero que tiene el inconveniente de que las operaciones en
las que aparecen números negativos no se comportan correctamente. Vamos a ver un
ejemplo, con números de 8 bits:
' #: se cambian los ceros por unos para expresar los números
negativos.
También es un método sencillo, en el que las operaciones con números negativos salen
bien, y que sólo tiene como inconveniente que hay dos formas de expresar el número 0
(0000 0000 o 1111 1111), lo que complica algunos trabajos internos del ordenador.
' :: para los negativos, se cambian los ceros por unos y se suma uno
al resultado.
Es un método que parece algo más complicado, pero que no es difícil de seguir, con el
que las operaciones con números negativos salen bien, y no tiene problemas para
expresar el número 0 (00000000).
En general, todos los formatos que permiten guardar números 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 números más grandes. Por ejemplo, un
"unsigned int" en MsDos podría tomar valores entre 0 y 65.535, mientras que un "int" (con
signo) podría tomar valores entre +32.767 y –32.768.
$ J > :
Visual Studio es la herramienta "oficial" desarrollada por Microsoft para permitirnos crear
programas en lenguajes Visual Basic, C++ y, recientemente, C# (entre otros). Existe una
versión gratuita, llamada Visual Studio Express, que puede ser una herramienta muy útil para el
que está aprendiendo a programar y no puede permitirse adquirir la versión profesional. Por
eso veremos algunos detalles sobre su instalación y su uso básico.
: % 8 1 .
Visual Studio 2008 no es ya la última versión de Visual Studio, pero tiene dos ventajas que aun
así lo pueden hacer resultar atractivo:
Nosotros usaremos Visual Studio 2008 en su versión Express, que es la que se puede usar
libremente ("gratis"), y, por tanto, es la más razonablemente para principiantes, además de que
necesita un ordenador menos potente qu la versión profesional.
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 conexión a Internet, puede ser preferible descargar
directamente la imagen ISO en español DSpanishD del DVD D900 MbD, para poder instalar en
cualquier ordenador y en cualquier momento, sin necesidad de estar conectado a Internet:
http://www.microsoft.com/express/downloads/#2008DAll
Esa imagen ISO se deberá grabar en DVD usando cualquer programa de grabación de CD y
DVD. Al introducir y ejecutar el DVD deberá aparecer un menú como éste:
E indicar si queremos algún componente adicional, como Silverlight (una alternativa al Flash de
Adobe) o el gestor de bases de datos SQL Server, en su versión Express (para un uso de
principiante, yo recomendaría no instalar ninguno de los dos):
Y para empezar un proyecto nuevo, deberíamos ir al menú Archivo y escoger la opción "Nuevo
Proyecto":
En la parte derecha de la pantalla aparecerán las clases que forman nuestro proyecto (por
ahora, sólo "Program.cs", que es el esqueleto básico, "Form1.cs", que representa nuestra
primera ventana), y en la parte izquierda aparecerá nuestra ventana vacía.
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" automáticamente.
Podemos desplegar la categoría "componentes comunes" para acceder a los más habituales:
A partir de ahí, el manejo sería 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:
: % 8 1
También está ya disponible Visual Studio 2010 (por ahora sólo en inglés), que se puede
descargar de:
http://www.microsoft.com/express/downloads/#2010DAll
La instalación y el manejo son muy similares a los de la versión 2008, salvo por el detalle de
que está en inglés y de que la nueva estética juega con tonos blancos y azules. Esta es la
pantalla de instalación:
Y esta sería 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:
0.97, de 01DfebD10. Corregida alguna errata en referencias del tema 2, que hablaban de ejemplos
cuya numeración había 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 03DoctD10. Intercambiados los temas 2 y 3, para que no haya mucha carga teórica al
principio. Ligeramente ampliado algún apartado de estos temas. Añadidos 4 apartados sobre SDL
(10.11.3 a 10.11.6). Completado el apartado sobre expresiones regulares, al que faltaba un
párrafo. La lista de cambios entre versiones (este apartado) pasa al final del texto.
0.95, de 01DjunD10. Recolocados (en distinto orden) los temas 8, 9, 10, para que el apartado
sobre "otras bibliotecas" pase a ser el último de esos tres. Añadido el apartado 10.12 sobre
servicios de red (cómo descargar una página 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 cómo convertir un número a
binario o hexadecimal. Añadidos tres ejercicios propuestos al apartado 4.1 y otros dos al 4.4.3
0.94, de 07DmayD10. Algunas correcciones menores en algunos apartados (por ejemplo, 6.3).
Añadido el apartado 8.13 sobre Tao.SDL y el apéndice 4 sobre la instalación y uso básico de
Visual Studio (en sus versiones 2008 Express y 2010 Express).
0.93, de 10DmarD10. Algunas correcciones menores en algunos apartados (por ejemplo, 2.1, 2.2,
3.1.2, 3.1.9, 4.1.4). Añadido el apartado 6.13 sobre SharpDevelop y cómo crear programas a
partir de varios fuentes.
0.92, de 22DnovD09. Ampliado el tema 4 con un apartado sobre comparaciones de cadenas y otro
sobre métodos de ordenación. Añadidos ejercicios propuestos en los apartados 4.4.8, 5.5 (2), 5.7
(2), 5.9.1.
0.91, de 19DnovD09, 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 24DmayD09, como texto de apoyo para los alumnos de Programación en Lenguajes
Estructurados en el I.E.S. San Vicente. (Primera versión completa; ha habido 4 versiones previas,
incompletas, de distribución muy limitada).
Q J
N
?) T) %#) %
O
??) U)V P & S) '
UV P & S) %%
I
P
S) #
S9) - <) %
& Q
O) - W)
J R
D) X & Y) ) '
D9) %
S
K 0)
/) VV) #
/P ) -
UU) # T
Z) )
L
C) U
C ) -
C9) % A)
99) )
A9) %
)) V
) -
(()
D )-
W
M ")
=) ?9) %
=C) # 99) -
QRR)
==) # X
QQQ)
=9) % ) -
++)
.
;
P" S)
! )
! )
> ! )
4) # ! )
! P! &? S) '
[ ) )
! ) ) )
) ) ) #%
" ) )
)% 6)
)
!\ 6 ) -
) '
%6
! ] 5) # 6 ) #%) %
! ) %
! 7 A) -
! ) %
! ) 3 )
! " ) 8 " ) '
") )
!" " ) - ) )
! J ) - 8 )
&) %% 8 ) %
! & 3 ) - " E@ )%
! &? ) ' 8 " ) -
! & )' ) '
! & )' 8 A )
" ) %% 8 3 ) %
!:8 ) 8 ) %
! " ) 8
! " 2 2) # )
" ) % 8 P S) %
! ) 8 ? ) '
! ) 8 )-
! ) 8 7 )-
) )-
[ ) 8 ) '
8 )
@ 8 ) %
8 ) %
B L" 8 ) 8 )
)' ) ) )
) #% 8 ^ & )
B J ) )
B ) ' ) #
B : ) % 8 ) ' ) '%) ''
B!: 8) % 8 ^ &) ''
B " ) % 8 = ) '%
B ) ) ) -) % ) '
B &> ) 8 )
B &] ) % 8 7 # )
B @ " ) 8 )
B$F) % 8 6 A )
)% 8 )
B )% 8 )
) 8 6 )
L) #%) % 8 6)
3 " ) % 8 ) -
) # 8 &)
B )# 8 D 5) %
B ) # 8 7 A)
") #% 8 ) %
3 ) --
& ) ) '
1
% 7 ) ) #
&) #
8) % ") #%
8O) ' ) '
8 )- P S)
8 ) '% )
8 A ) %# )
8 E )% ) #%
Revisión 0.97 – Página 2
Introducción a la programación con C#, por Nacho Cabanes
) #% ; 6 &6 # )
@ ) ' ; - )
) ; 6 ` ) #
) # ; 6 " ) #
[ 6 ) ; 6 )
[ [@ ) ; 6 )
) ; 6 A )
" ) ; 6 ) %
" 86 ) ; ; ) '
" 3 )# ; 1A )
3 ] 5 ; ) ; ! > ] ) %'
E ) ) % ; $ )
) - ; : )
) 6 ) '
& ) A ) -'
\ " ) # ) '
; ) - )
; & 7 ) 6) -#
56 ) ; " 8 )
) ' ; ) -
) )
D ; 5 L) - ) '%
) ' ; ) #
A&" ) ; E )
5? ) # ; ) #
Y
1) % " )
3 )- M )
) M 1 ) '-
) M ; ) %
1 ) M ^ &) '
1 ) ' "" & ) '
1 @ ) ' " )
)' " )
)- " ) #%
)
" ) Z
1 ) %
) a )
1 ) a A )
) '- a ) #
1 ) '
1 1A )
1>> >?1=1?) -
) - )
1 ) - ) %
1 ) '# , ) -
1 ) % )
1 )' A ) '-
1 ) % A ^ &) '
1 E ) % " P @ 2 2S) -
1 )# )-
1 ) - )' ) '
1A ) )
1_1) - ) )
1A P 6 S) J )-
1A ) " )
1A ) )
1A " ) % 1A ) %
) -
?
) % H
)% N ) %
)%
Revisión 0.97 – Página 3
Introducción a la programación con C#, por Nacho Cabanes
N " ) )
)#
)
[ D 99) -
^ &) D 5) #
^ &! ) P 6 S) '
^ &86 ) D )
L & ) ' ) -
D ) %
*
? A ) '-
? & 6 ) %% W) #
? "6P S) '' 3 )
? " 6 P 6 S) ' )
" 3 8) % )
" 3 E@ )% 8 ) %
?; ) ' > )
` ) - 7 A) %
?` ) 3 ) ] ) -
) ' ) %
J )
)
)
) )#
" )#
) -
)
? ") ) --
? " ) := ) '
? " ) ) %
") ) #
? " ) ''
!
' F E $ ) -
$ ) F E ) %
E@ )- F E )
E ) ) E )
E )
' ( N ) -
F )%
& ) '-
F P S) #%
) '
)
" & ) '
F L) '
$ E ) %
F) %
' N ) - ) '
$ " B A) ' F ) ) %
J ) F )
$ ) - ) '
$ P S) # F )
$ ) F 6 ) %
$ E ) )
'7 & 7 ) )
$ ) ) F )
$ 6) # F 5)
$ ) F )
$ D A) ' )
' 7 ) )
F )
D ) -
F : ) %
)% F " )
) - " )%
, 7 ) F " 3 )
D )- F )
5P & S) %% ) -
5P J S) - F " ) #
Revisión 0.97 – Página 4
Introducción a la programación con C#, por Nacho Cabanes
F " ) : " ) #
) )
' : ) '
P S) '# : ? ) '
F ) % ) - : )-
F D )- :I? ) '
F ) #- :@ ) %
6) ' : L) '
L ) -
)
G : > ) %
I ) ' : ] )
") #-) %#
: "8 )-
2 : "B )- )-
> `[ ) % ) '#
> ) )'
> " P S) : ) '-
> P; : S) : ") '-
> B& ) ) % )
> L &) : A )
> ? ) : " )
> ? P 6 S) % 5 6) #
> : ") ( )
P S) % :& 5 ")
) -'
> ) % 8
> )
) ) % ) %%
> " ) # 7 )'
> " ) '# 6 6) '%
> )' 7 ) %
> )' 7 " ) %
> ) - 7 6) %
) 7 )
& 7 ) 7 [ ) #
) % & ) '
> ) 6 ) #-
) 76 )
76 : )
7 E )%
3 7 )
" ) - 7 )
& ) 7 # ) )
: ?) 7 ? 5 ) '-
) 7 : ") -
: )% 7 C ) '-
: L) % )%
: L " ) % 7 ) %
: ) -- &)
: ) %
: ) ' A
)
: ) )
: B& A) '% ")
: 8 F ) C$?)
:6 ) ) # C )
6 ) ) -
) 6 )
:" & " ) % ")
: ) '
: ) % :
: ) ) -) %
: ) = )
: ) = ) %
: ) = )
: " ) % = " ) -
= ) - ] PB &] S) %
) %% ] P; : S) -
)% ] B& ) -
) # * + )
= PF S) ') #- ] ? P 6 S)
= 8O) -
= : ) -
) # \
A )
E
] ; 1A ) % ]
56 ) # P) #
] 5 ; ) # b ) #
] )#