Programacion c sharp_04

590 views

Published on

Programación básica c sharp

Published in: Education
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
590
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
37
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Programacion c sharp_04

  1. 1. Capítulo 4 ELEMENTOS DE UNA CLASE C#La clase como elemento fundamental de la programación orientada a objetos requieretoda la atención posible por parte del programador. El correcto manejo de suselementos y el buen aprovechamiento de los mismos es lo que permitirá sacar elmáximo provecho de esta metodología de programación, y sobretodo hacer más fácil yefectiva la tarea de programar una aplicación de software. La teoría general de laprogramación orientada a objetos define unos elementos básicos que conforman unaclase, pero cada uno de los lenguajes de programación ha realizado sus propios aportesa estos elementos, especialmente ampliando su funcionalidad o representándolosmediante elementos propios del lenguaje, con el objetivo de volverlos más potentes yfáciles de manejar.El lenguaje C# ha dotado a las clases de una serie de elementos que en aparienciaamplían el conjunto de elementos definidos en la teoría general, pero más que eso, enrealidad lo que se busca es poner a disposición del programador toda una gama derecursos que le permitan construir componentes de software que cumplan todos losrequerimientos de la programación orientada a objetos y permitan expresar loselementos generales en la forma más efectiva y eficiente posible.Estos son los elementos básicos que constituyen una clase en C#: Constructores Destructores Constantes Campos Métodos Propiedades Indizadores Operadores Eventos Delegados EstructurasConstructoresUn constructor es un método de una clase que se ejecuta automáticamente cada vezque se crea una instancia de la misma. Aunque no se especifique, como ha sucedido entodas las clases que hasta ahora hemos implementado, el compilador de C# siempreestablece internamente un método constructor que no hace absolutamente nada.Además, siempre que vamos a crear un objeto definido por una clase, hacemos unllamado a su constructor en el momento que creamos una nueva instancia con eloperador new. Por ejemplo, retomando nuestra clase de los números complejos,definida en el anterior capítulo, en las siguientes dos líneas de código, la primera líneadefine un objeto de tipo Complejo y la segunda se encarga de llamar al constructor deesa clase.
  2. 2. 86 CAPITULO 4 PROGRAMACION CON C# Complejo z; z = new Complejo(); El trabajo del constructor es iniciar el objeto que se ha definido mediante la clase. Dentro del constructor pueden implementarse aquellas acciones que se necesita ejecutar inicialmente y en forma automática para dar una determinada configuración a un objeto, o incluso para hacerlo funcional. Un método constructor lleva el mismo nombre de la clase que lo contiene y se debe declarar con nivel de accesibilidad pública (public), aunque también es admitido el nivel interno (internal). El nivel de accesibilidad pública permite que el constructor pueda ser ejecutado en cualquier instancia, ya sea dentro del proyecto que implementó la clase o por fuera de él, en cambio si el método es internal, significa que solo podrá ejecutarse dentro del proyecto que contiene a la clase. En general, la sintaxis para implementar un constructor es la siguiente: [public | internal] NombreClase() { // Implementar el constructor o dejar esto vació } Un constructor es el primer elemento de la clase sobre el cual puede aplicarse el polimorfismo, aquí identificado como sobrecarga. Una clase puede implementar varios constructores, los cuales deben diferenciarse ya sea en el tipo o la cantidad de parámetros que manejan. En el siguiente fragmento de código, la clase Complejo implementa tres constructores, public class Complejo { public Complejo() { } public Complejo(string NumeroComplejo) { // Código para procesar el parámetro } public Complejo(double Real, double Imaginario) { // Código para procesar los parámetros } } En el momento de la ejecución, de acuerdo a los parámetros que reciba el constructor, el sistema decidirá a cual de estos constructores llamar. Si la declaración de un objeto Complejo se hiciera mediante, Complejo z = new Complejo("5 + 3i") se pondrá en marcha el segundo constructor, ya que este es el único que recibe como parámetro un valor tipo cadena de texto. Destructor Un destructor es un método que se ejecuta automáticamente justo antes de que un objeto sea destruido. A diferencias del constructor, un método destructor no puede sobrecargarse ni tampoco heredarse. Además, no puede invocarse explícitamente. El lenguaje C# cuenta con una herramienta llamada recolector de basura que se encarga de destruir aquellos objetos que ya no se estén utilizando y aún siganwww.pedrov.phpnet.us
  3. 3. CAPITULO 4 ELEMENTOS DE UN CLASE C# 87ocupando espacio en memoria. Como ya se había mencionada anteriormente, eloperador new, aplicado a la creación de un objeto, se encarga de asignar la memorianecesaria que este necesita. En el momento que el objeto deja de ser referenciado, elrecolector de basura se encarga de liberar la memoria que ocupaba. Un objeto se dejade referenciar cuando la ejecución sale del ámbito que lo definió. En los ejemplos quehemos analizado hasta el momento, la mayoría de objetos deja de ser referenciadocuando se finaliza la ejecución del método Main.La sintaxis para implementar el destructor es la siguiente: ~NombreClase() { // Acciones antes de destruir un objeto }En la práctica un método destructor es utilizado para realizar tareas que se necesitanantes de destruir el objeto, como puede ser: guardar valores de datos, limpiar lamemoria manualmente o fijar alguna configuración especial. Ejemplo 4.1 Un constructor y un destructorEn el siguiente ejemplo vamos a programar un constructor y un destructor paradeterminar el instante en que se ejecutan cada uno de ellos. Supongamos que tenemosuna clase Estudiante que permite procesar algunos datos de estudiantes, y presenta dossobrecargas para su constructor. La primera sobrecarga no hace prácticamente nada yla segunda exige el código del estudiante. /* Archivo: Ejemplo41.cs */ using System; using System.Windows.Forms; public class Estudiante { string código; // Constructor por defecto public Estudiante() { } // Constructor con código del estudiante public Estudiante(string CodigoEstudiante) { código = CodigoEstudiante; MessageBox.Show("Código: " + código, "Construyendo..."); } // Propiedad public string Codigo { set { codigo = value; } get { return codigo; } } // Destructor ~Estudiante() { MessageBox.Show("Ejecutando el destructor...", "Destruyendo..."); } } pedrov.cs@hotmail.com
  4. 4. 88 CAPITULO 4 PROGRAMACION CON C#public class Programa{ static void Main() { Estudiante alumno = new Estudiante(); //Estudiante escolar = new Estudiante("01"); }} Compile el archivo con la instrucción, > csc ejemplo41.cs Al ejecutar el programa se crea un objeto de tipo Estudiante pero, dado que la ejecución llega al final del método Main, inmediatamente se inicia el proceso de destrucción del objeto. Observe que, sin necesidad de hacer nada, la caja de mensajes del destructor permanece un instante y luego desaparece. En realidad, al llegar al final de la ejecución e iniciar el proceso de destrucción, también se activa la recogida de basura y por lo tanto esta se encarga de destruir cualquier dato que exista en memoria, incluida la caja de mensajes. Desactive la primera línea del programa y active la segunda. Vuelva a compilar el programa y analice la ejecución. Observará que aparece la caja de mensajes del constructor y esta se mantiene hasta hacer clic en el botón aceptar. La caja de mensajes se mantiene por que el programa está en plena ejecución. Pero no ocurre lo mismo cuando pasa al proceso de destrucción. Métodos Hasta este punto ya hemos trabajado con muchos métodos. Sabemos que un método es lo que otros lenguajes de programación, sobre todo estructurados, se denominan procedimientos o funciones. Además, se sabe que el principal método que dirige la ejecución de un programa C# es el método Main, que es el punto por donde se inicia la ejecución y la carga en memoria por parte del sistema operativo. Los métodos le permiten al programador realizar acciones sobre los atributos internos de un objeto, o incluso actuar sobre elementos externos que se relacionan con dicho objeto. Aunque, un método existe en la medida que exista para un programador que hace uso de una determinada clase, es decir un método publico, en la práctica se puede hablar también de métodos privados, queriendo significar que son acciones internas que se realizan con los elementos de un objeto. En general, un método se define con una instrucción que tiene la siguiente sintaxis: public tipo NombreMétodo(Argumentos) { // Implementación del método return valor } Todo método que debe devolver un valor, el cual debe ser del mismo tipo que el método. Los métodos que ejecutan acciones que no requieren la devolución de un valor, se deben definir como void. C# para permitirle al programador compartir métodos genéricos que se ejecutan sin necesidad de hacer referencia a ningún objeto en particular, permite la definición de métodos estáticos. Esto métodos, que se definen con el modificador static, sewww.pedrov.phpnet.us
  5. 5. CAPITULO 4 ELEMENTOS DE UN CLASE C# 89comportan como las funciones o procedimientos de acceso publico que se utilizan en laprogramación estructurada, o en C++. Esta es la forma que implementa C# parapermitirle al programador contar con funciones que son accesibles en el mismo niveldonde actúa la clase en la cual se han incorporado.PropiedadesLas propiedades tienen un aspecto similar a un método, pero no admiten argumentos.Se utilizan para establecer o asignar valores a los atributos de un objeto. Aunque,también pueden utilizarse para procesar valores internos del objeto y retornar un valorsin la necesidad de que exista un atributo directamente relacionado.En general, una propiedad se implementa con la siguiente sintaxis: public tipo NombrePropiedad { get { // Devuelve con return un valor } set { // Asigna el valor de value a un atributo } }La sección get se encarga de retornar un valor, ya sea de un atributo o como resultadode un proceso de datos. La sección set, en cambio, si debe actuar directamente sobreun atributo, ya que de otra forma no tendría sentido, y su objetivo es asignar un valor.En ambas secciones puede incluirse todo un conjunto de instrucciones, antes deretornar o asignar un valor.En el caso que no se requieren procesar parámetros, pueden presentarse por parte delprogramador dudas sobre lo que debe utilizarse: un método o una propiedad. Noexisten reglas definidas sobre cual de los dos utilizar, y bajo que condiciones. Tododepende del diseño o claridad que se desee darle al código de programación. Porejemplo, si se tiene una clase Persona, y se desea procesar a través de ella la edad deuna persona, con base en su fecha de nacimiento, que se encuentra incluida en algúnatributo, bien podría implementarse un método o una propiedad, y todo sería correcto.Propiedades de solo lecturaUna propiedad de solo lectura es aquella que únicamente permite leer un datocontenido en algún atributo de un objeto, o como resultado de un proceso interno. Estetipo de propiedades no debe permitir ingresar un dato al objeto. Es decir no debeincluir la sección set.La implementación de una propiedad de solo lectura se logra incluyendo en el cuerpode la propiedad únicamente una sección get, con lo cual se limita su funcionalidadsolo a retornar un valor hacia el cliente de la clase.La siguiente es la sintaxis para implementar una propiedad de solo lectura: public tipo NombrePropiedad { get { // Devuelve con return un valor } } pedrov.cs@hotmail.com
  6. 6. 90 CAPITULO 4 PROGRAMACION CON C# Propiedades de solo escritura Las propiedades de solo escritura solo permiten asignar valores a un atributo del objeto que las implementa. Esto significa que en la implementación solo debe incluirse la sección set. La siguiente es la sintaxis para implementar una propiedad de solo escritura: public tipo NombrePropiedad { set { // Asigna el valor de value a un atributo } } El uso adecuado de esta clasificación de las propiedades es lo que permite establecer un buen nivel de encapsulamiento en las clases que se diseñen. Sobrecarga de métodos Sobrecargar un método significa implementar varios métodos de una clase con el mismo nombre pero con diferentes parámetros, ya sea en cantidad o en tipo. C#, también acepta diferencias en los valores devueltos por un método sobrecargado, pero siempre y cuando esta no sea la única diferencia. Un método podría tener una versión que devuelva un entero (int) y contar con tres parámetros, todos de tipo cadena (string). Si otra versión del método devuelve un double, y tiene tres parámetros, no pueden ser todos del tipo string, al menos uno de ellos debe ser de otro tipo, de lo contrario el compilador devolverá un error. El siguiente es un ejemplo de una sobrecarga correcta: public int Matricular(string codigo, string curso) { // Registrar datos de un estudiante } public bool Matricular(string codigo) { // Registrar datos de un estudiante } Supongamos que una clase implementa los métodos, public int Matricular(string codigo, string curso) { // Registrar datos de un estudiante } public bool Matricular(string codigo, string curso) { // Registrar datos de un estudiante } En este caso se generará un error en el momento de la compilación, dado que C# tratará de identificar cada método a través de la cantidad de argumentos y sus tipos. Como no encuentra diferencias que permitan una clara identificación, no se puede terminar el proceso de compilación.www.pedrov.phpnet.us
  7. 7. CAPITULO 4 ELEMENTOS DE UN CLASE C# 91 Ejemplo 4.2 Autómata que procesa direcciones webEn este ejemplo vamos a construir una clase con dos métodos para identificar si unacadena de texto corresponde a una dirección web bien escrita, a un correo electrónico ono corresponde a ninguno de los dos formatos. Los programas que se encargan derealizar este tipo de análisis suelen denominarse autómatas.Una de las tareas más complejas a las que se puede enfrentar un programador es la deprocesar cadenas de texto para determinar si están escritas de acuerdo a un formato osintaxis preestablecidos. En la mayoría de los casos esta tarea requiere la revisión, enforma repetitiva, carácter por carácter, en busca de los patrones que han sidoestudiados y establecidos con anticipación. Una solución, dada por las Ciencias de laComputación, para facilitar el estudio y análisis de las condiciones que se debenestablecer en el procesamiento de textos, es la teoría de los autómatas y los lenguajesregulares, y más específicamente las llamadas expresiones regulares, un campo depermanente investigación que ha permitido alcanzar los logros que hoy en día tienen ala Computación y a la Informática en el sitial en que se encuentran.Las expresiones regulares, como tal, son un lenguaje que permite simbolizar conjuntosde cadenas de texto formadas por concatenación de otras cadenas. La definición exactade expresión regular se ha establecido a través de un intrincado conjunto de axiomas detipo matemático, que por ahora no entraremos a detallar. Tan solo vamos a dar unanoción breve del concepto de expresión regular en la misma forma como lo hace laayuda incluida en el kit de desarrollo de .NET, donde se la asemeja con los patronesque solemos utilizar para realizar búsquedas de archivos en el disco duro de nuestrocomputador. Por ejemplo, si queremos que el sistema nos muestre todos los archivosfuentes de C# podemos hacerlo a través del patrón “*.cs”. Esta es una forma de decirleal sistema operativo que muestre solo los archivos cuyo nombre termine en loscaracteres “.cs”. Podríamos decir que para el sistema operativo la cadena *.cs es unaexpresión regular.A partir de aquí, y en lo que resta de esta práctica, para aquellos lectores que no estánfamiliarizados con las expresiones regulares, se les sugiere olvidar todo lo que sabensobre el significado de algunos caracteres especiales, tales como * y +, y manejarúnicamente el significado que aquí se describa.Para comenzar, y a manera de ejemplo explicativo, veamos algunas expresionesregulares básicas que se manejan en la teoría general de la computación. Supongamosque tenemos un carácter a, entonces se representa: Expresión regular Conjunto representado ε Representa a la cadena vacía a Representa a la cadena formada por a Todas las cadenas formadas por la concatenación de a, tales como a, aa, a+ aaa, aaa, … etc. Todas las cadenas formadas por la concatenación de a, incluyendo a la a* cadena vacía. Es decir, a* = (a+) ∪ {ε }Entonces, la expresión regular 01*, representa a todas las cadenas que empiezan por elcarácter 0 (cero) seguido de ninguno o cualquier cantidad de unos. Aquí, estánrepresentadas cadenas como 0, 01, 011, 0111, etc. La expresión regular (ab)+c,representa todas las cadenas que repiten la cadena ab, una o más veces, y terminan en pedrov.cs@hotmail.com
  8. 8. 92 CAPITULO 4 PROGRAMACION CON C# el carácter c, tales como abc, ababc, abababc, etc. En este último ejemplo no se incluye la cadena abcab, ni tampoco la cadena c. El framework de .NET pone a disposición del programador el espacio de nombres System.Text.RegularExpressions, conformado por un conjunto de clases que se encargan de trabajar con expresiones regulares. Vamos a utilizar esta teoría y las clases que .NET pone disposición del programador para implementar un método que permita analizar cadenas de texto y determinar si una cadena de texto corresponde a una dirección web, a un correo electrónico o a ninguno de ellos. .NET dispone de una mayor cantidad de elementos, o caracteres especiales, que aquellos manejados en la teoría general computación para la construcción de las expresiones regulares. Vamos a describir brevemente algunos de ellos, para utilizarlos en nuestro ejemplo: Caracteres especiales Descripción [ ] Permiten determinar una lista de caracteres, de los cuales se escogerá uno. Por ejemplo, [0123] pone a disposición cualquiera de estos dígitos para hacerlo coincidir con la cadena analizada. ( ) Permiten establecer alguna subcadena que se hará coincidir con la cadena analizada. Por ejemplo, (01)* representa a todas las cadenas que son una repetición de la subcadena 01, tales como 01, 0101, 010101. A Establece que la coincidencia debe cumplirse desde el principio de la cadena Z Establece que la coincidencia debe establecerse hasta el final de la cadena w Representa a cualquier carácter que sea un carácter alfanumérico o el carácter de subrayado. También se representa como [a-zA- Z0-9] {N} Establece que el elemento que le antecede debe repetirse exactamente N veces. Por ejemplo, [w]{3} representa a la cadena www. Para no complicar mucho las cosas vamos a crear una expresión regular que permita identificar las direcciones web que tienen el formato, www.nombredominio.tipodominio donde nombredominio es un nombre formado por una cadena de caracteres alfanumericos y tipodominio corresponde a alguno de los posibles tipos de dominio que pueden existir, tales como com, net, info u org. Para nuestro caso, toda dirección web debe empezar por la repetición del carácter w, tres veces. Esto podemos expresarlo como, [w]{3} A continuación viene un punto. Este símbolo corresponde a un carácter especial de las expresiones regular de .NET, por lo cual debemos escribirlo en la forma (.) El nombre del dominio, como ya se dijo, es una cadena de caracteres alfanuméricos, donde no cabe la posibilidad de que sea una cadena vacía. Vamos a suponer que solo se aceptan caracteres en minúsculas, por lo cual su representación puede hacerse como, [a-z0-9]+www.pedrov.phpnet.us
  9. 9. CAPITULO 4 ELEMENTOS DE UN CLASE C# 93El tipo de dominio puede corresponder a una de las siguientes posibilidades: com, net,info u org. En este caso existe una disyunción de la cual se debe escoger solo unaopción y se expresa como, (com|net|info|org)Finalmente es necesario que la cadena a analizar coincida exactamente desde su iniciohasta su final, por lo cual es necesario incluir los limites A y Z al principio y al finalde la expresión regular, respectivamente.En definitiva la expresión regular que nos permitirá validar una dirección web es lasiguiente: expresion = @"A[w]{3}(.)[a-z0-9]+(.)(com|net|info|org)Z";El símbolo @ al inicio de la asignación le informa al compilador de C# que noidentifique en la cadena de texto las secuencias estándar de escape.De la misma forma podemos crear una expresión regular que identifique a todas lasdirecciones de correo electrónico que cumplan con el formato, nombreusuario@nombredominio.tipodominioLa siguiente es la expresión regular que identifica a este tipo de cadenas: expresion = @"A(w+.?w*@w+.)(com)Z";En esta expresión se ha incluido la secuencia .? que admite la existencia de un punto,o ninguno, en el nombre del usuario.La clase que se encarga del procesamiento de las expresiones regulares se llama Regexy se encuentra en el espacio de nombres System.Text.RegularExpressions. Con estaclase se pueden definir objetos que reciben una expresión regular y se encargan deprocesar una cadena de texto para identificar todas las subcadenas que hacen parte deella. La clase Regex implementa el método IsMatch que recibe como argumento lacadena que se va a analizar y retorna un valor booleano, true o false, de acuerdo a si seencontró cadenas que se identifiquen con la expresión regular o no. La siguiente líneadefine un objeto tipo Regex que procesa una expresión regular como las definidas eneste análisis: Regex automata = new Regex(expresion);La clase Regex exige que la expresión regular sea asignada en el momento de lacreación del objeto.Con base en el anterior análisis vamos a programar un ensamblado, tipo libreríadinámica, que se encargará de recibir una cadena de texto y validarla de acuerdo a loantes requerido. Este ensamblado estará constituido básicamente por una clase,llamada AutomataWEB, conformada por dos métodos estáticos, EsWeb y EsCorreo,que servirán para validar las direcciones web y las direcciones de correo electrónico,respectivamente./* Archivo: AutomataWEB.cs */using System;using System.Text.RegularExpressions;public class AutomataWEB{ pedrov.cs@hotmail.com
  10. 10. 94 CAPITULO 4 PROGRAMACION CON C# public AutomataWEB() { } // Método para identificar direcciones web public static bool EsWeb(string cadena) { string expresion; expresion = @"A[w]{3}(.)[a-z0-9]+(.)(com|net|info|org)Z"; Regex automata = new Regex(expresion); return automata.IsMatch(cadena); } // Método para identificar direcciones de correo electrónico public static bool EsCorreo(string cadena) { string expresion; expresion = @"A(w+.?w*@w+.)(com)Z"; Regex automata = new Regex(expresion); return automata.IsMatch(cadena); }} Compile este archivo con la instrucción, > csc /t:library AutomataWEB.cs Los métodos estáticos no pueden hacer parte de ningún objeto definido a partir de la clase que lo contiene. En consecuencia para referirse a ellos se debe hacer mediante el nombre de la clase. En este caso, para validar una dirección web, se debe hacer siguiendo la sintaxis, AutomataWEB.EsWeb(cadena) Ahora vamos a crear un programa que se encargará de recibir una cadena de texto y realizar la correspondiente verificación con ayuda del autómata que hemos creado./* Archivo: Ejemplo42.cs */using System;public class ValidacionWEB{ public static void Main() { string cadena; Console.Write("Escriba una cadena de texto: "); cadena = Console.ReadLine(); cadena = cadena.ToLower(); // convierte a minúsculas if (AutomataWEB.EsWeb(cadena)) Console.Write("La cadena es una dirección web."); else if (AutomataWEB.EsCorreo(cadena)) Console.Write("La cadena es una dirección de email."); else Console.Write("La cadena no es una dirección válida."); }}www.pedrov.phpnet.us
  11. 11. CAPITULO 4 ELEMENTOS DE UN CLASE C# 95Compile el programa con la instrucción, > csc /r:AutomataWEB.dll ejemplo42.csEjecute el programa resultante y pruebe su funcionamiento ingresando cadenas quecorrespondan a direcciones web, correos electrónicos y otras que no cumplan conninguno de los dos formatos.El estudio de las expresiones regulares requiere un análisis más profundo y detallado,por lo cual este ejemplo se constituye solo en una breve introducción a laspotencialidades que ofrece este tema en las aplicaciones de software modernas. Unadescripción más detallada se realizará en un capítulo posterior dedicadoexclusivamente a este tema. Como ejercicio, el lector debería modificar los métodosprogramados aquí para validar cualquier dirección web, incluyendo todos los tipos dedominio y las abreviaturas de los países a los que pertenecen, y de esta forma irganando terreno en el manejo de las expresiones regulares con .NET. Ejemplo 4.3 Complejos de la forma a + biEn este ejemplo vamos a continuar en la construcción de la clase Complejo que se hatomado como modelo de las descripciones de este capítulo. Sabemos que, todocomplejo tiene la forma a + bi, y como tal tiene su validez dentro de las matemáticas.Siempre que se necesite trabajar con un complejo, las cosas serían más fáciles si tantola asignación como el valor retornado por una variable de este tipo se hacen entérminos de la forma matemática de un número complejo. Sería ideal poder realizaruna asignación a una variable compleja en la forma, z = 2 + 3i;Pero en vista de que el compilador de C# no reconoce la sintaxis a + bi como un valornumérico válido, por ahora no es posible en el nivel de programación lograr estapretensión. Sin embargo, si podemos hacer una aproximación de este tipo deasignación mediante asignaciones en forma de cadena de texto, tal como z.Valor = "2 + 3i";de tal manera que, para el usuario de un programa que utilice esta clase, el manejo delos números complejos sea totalmente transparente.En este punto, nos enfrentamos a una situación bastante particular. Debemosprogramar una validación que se encargue de reconocer un número complejo en unacadena de texto, interpretándola adecuadamente, para determinar las partes real eimaginaria del mismo. Para ello, es necesario, antes que nada, garantizar que la cadenade texto realmente incida con el formato de un número complejo válido.Existen diversas formas para expresar un número complejo. Se sabe que un mismonúmero complejo se puede escribir en formas equivalentes, tales como 2 + 3i, 2 + i3, 3i+ 2, i3 + 2, y cualquiera de ellas es válida. Además existen complejos cuya forma,debido a las propiedades matemáticas, puede ser equivalente a otra. Así, por ejemplo,tenemos que 5 + -2i es lo mismo que 5 – 2i, o que 4 + 1i es igual a 4 + i, o también que0 + 4i es igual a 4i. Incluso, cualquier número real puede considerarse como uncomplejo de parte imaginaria igual a cero.La clase Complejo debe poseer la capacidad suficiente de recibir un número complejoen cualquiera de sus formatos válidos y procesarlo adecuadamente. En general, y parafacilitar la comprensión de las descripciones algoritmicas vamos a dividir en cuatrogrupos los formatos en que puede estar escrito cualquier número complejo: pedrov.cs@hotmail.com
  12. 12. 96 CAPITULO 4 PROGRAMACION CON C# Grupo Formato 1 a, a + i, a + bi 2 i, i + a, bi, bi + a 3 ib, a + ib 4 ib + a En estas descripciones sintácticas no se han incluido los signos, positivo o negativo, que pueden adoptar la parte real y la parte imaginaria de un complejo. Más adelante, en cada caso particular entraremos a considerar este aspecto. Tampoco, el agrupamiento realizado obedece a ninguna regla específica relacionada con estos números, sino únicamente se busca agruparlos de acuerdo a algunas características de forma que pueden convenientemente facilitar la generalización de cada uno de ellos. El primer grupo es el formato más usual entre los matemáticos, la parte real va en primera instancia y la parte imaginara después. El tercer formato suele encontrárselo con mucha frecuencia en libros de ingeniería. El segundo y cuarto formatos son un equivalente de los anteriores. Como ya se vio en el ejemplo anterior, .NET dispone de un recurso muy poderoso para identificar cadenas de texto que guardan una relación de semejanza entre si. Estas son las clases del espacio de nombres System.Text.RegularExpressions, que permiten procesar expresiones regulares. Vamos a utilizar este recurso para procesar cadenas de texto a través de expresiones regulares que representen a los grupos de la tabla anterior, para identificar si corresponden o no a números complejos bien construidos. Abra en un editor de texto el archivo que contiene a la clase Complejo que se viene construyendo en los ejemplos anteriores. Una ventaja de .NET es que permite modificar un ensamblado, y siempre y cuando no se modifiquen los identificadores de sus miembros existentes, mantienen la compatibilidad con las aplicaciones que utilizaban la versión antigua del componente. Lo primero que se debe hacer es incluir dos nuevas líneas de código con los espacios de nombres que se necesitan para los nuevos procesamientos que vamos a incluir en la clase. using System.Globalization; using System.Text.RegularExpressions; En vista de que los procesos que se van a realizar requieren conversiones de tipos numéricos a cadenas de texto, y viceversa, es posible que algunos elementos de estos se vuelvan incompatibles con los tipos. Por ejemplo, si se tiene la cadena de texto “3.52”, donde el punto corresponde al separador decimal del número representado, al convertirla a tipo double se puede presentar una inconsistencia de interpretación si en la configuración del sistema operativo se identifica al separador decimal con una coma, (,) La primera directiva posibilita acceder a las clases que nos van a permitir determinar el formato que se está utilizando en la máquina actual para mostrar números con parte entera y decimal. Para evitar inconsistencias se adecuaran los números al formato que maneje la máquina donde se esté trabajando. En la siguiente línea de código, la propiedad CurrencyDecimalSeparator devuelve una cadena tipo string con el separador decimal que utiliza el sistema operativo en la máquina actual: sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; El punto central de este ejemplo, es construir un método que le permita a las variables instanciadas de la clase Complejo recibir una cadena de texto y validarla para determinar si corresponde a un número complejo. Para ello vamos a determinar los patrones que pueden determinarlos.www.pedrov.phpnet.us
  13. 13. CAPITULO 4 ELEMENTOS DE UN CLASE C# 97Sabemos que, un número es una cadena de dígitos decimales en la cual puede o noaparecer un separador decimal. Si el separador decimal fuera un punto, que en ellenguaje de expresiones regulares de .NET se representa como (.), un número tendríael formato, numero = @"(d+(.)?d*)";El cuantificador ? especifica que el elemento que le antecede puede aparecer una, oninguna vez, en la cadena analizada. Como en este caso vamos a utilizar como válidoel separador decimal definido por el sistema, sd, entonces introducimos unaconcatenación con este, así: numero = @"(d+(" + sd + @")?d*)";Todo número complejo, exceptuando aquellos que no posean parte imaginaria nula,incluyen un literal que representa a la raíz cuadrada de -1. Este generalmente sesimboliza con la letra minúscula i. Definimos este símbolo en la siguiente forma: i = @"(i)";El signo que puede anteceder a un número puede ser positivo, (+), o negativo, (-).Sabemos que para expresar opción en la escogencia de uno u otro símbolo se utilizanlos corchetes. Por lo tanto el signo de un número, en términos de expresión regular de.NET, quedaría expresado por, signo = @"([+-])";Con los anteriores elementos podemos expresar la parte real e imaginaria de uncomplejo en la siguiente forma: real = signo + numero; imaginario = signo + numero + i;Ahora veamos el primer grupo de complejos que es posible encontrar: Grupo Formato 1 a, a + i, a + biEn el caso más general, un complejo puede estar formado por una parte real y una parteimaginaria, a + bi, que queda incluida en una expresión regular como la siguiente: expresion1 = real + imaginario;Pero, para incluir en la expresión regular los otros dos casos (complejos con parteimaginaria 0 o 1) es necesario tratar en forma independiente esta parte. En el primercaso, la parte imaginaria no existe, por lo tanto se debe dejar como opcional esta parte,incluyendo a su signo. Así: imaginario = "(" + signo + numero + i + ")?";A su vez, para el segundo caso, donde la parte imaginaria solo la forma el literal i, sepuede obtener dejando como opcional el número que la acompaña. imaginario = "(" + signo + "(" + numero + ")?" + i + ")?"; pedrov.cs@hotmail.com
  14. 14. 98 CAPITULO 4 PROGRAMACION CON C# Con estas modificaciones, e indicándole al motor de procesamiento de expresiones regulares que debe validar la coincidencia en toda la cadena, desde el principio (A) hasta el final, (Z), la expresión regular para el grupo 1 de posibles complejos que pueden pasarse a la clase queda así: string expresion1 = @"A" + real + imaginario + @"Z"; Para este primer grupo de complejos, es necesario definir un objeto del tipo Regex quien se encargará de procesar las cadenas entrantes para determinar si constituyen un número complejo. La siguiente línea define el objeto complejo1 con la expresión regular antes analizada: Regex complejo1 = new Regex(expresion1); La comprobación de la cadena entrante, puede realizarse mediante el método booleano IsMatch del objeto complejo1. Así, if (complejo1.IsMatch(cadena)) return true; De la misma forma como se realizó la expresión regular para el primer grupo de complejos, puede definirse las expresiones regulares para los otros casos de posibles formatos de números complejos. En definitiva, con estos elementos construimos el método booleano EsComplejo, en la siguiente forma: // Método para válidar un número complejo private bool EsComplejo(string cadena) { cadena = QuitarEspacios(cadena); if (cadena.Length == 0) return false; string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace(., Char.Parse(sd)); // Elementos básicos de un complejo string numero = @"(d+(" + sd + @")?d*)"; string i = @"(i)"; string signo = @"([+-])"; // Validación para a, a + i, a + bi string real = signo + "?" + numero; string imaginario = "("+signo+"("+numero+")?"+i+")?"; string expresion1 = @"A" + real + imaginario + @"Z"; Regex complejo1 = new Regex(expresion1); if (complejo1.IsMatch(cadena)) return true; // Validación para i, i + a, bi, bi + a imaginario = signo + "?" + numero + "?" + i; real = "(" + signo + numero + ")?"; string expresion2 = @"A" + imaginario + real + @"Z"; Regex complejo2 = new Regex(expresion2); if (complejo2.IsMatch(cadena)) return true; // Validación para ib, ib + a imaginario = signo + "?" + i + numero; real = "(" + signo + numero + ")?"; string expresion3 = @"A" + imaginario + real + @"Z"; Regex complejo3 = new Regex(expresion3); if (complejo3.IsMatch(cadena)) return true; // Validación para a + ib real = signo + "?" + numero; imaginario = signo + i + numero; string expresion4 = @"A" + real + imaginario + @"Z";www.pedrov.phpnet.us
  15. 15. CAPITULO 4 ELEMENTOS DE UN CLASE C# 99 Regex complejo4 = new Regex(expresion4); return complejo4.IsMatch(cadena); }Se ha incluido una llamada a un método denominado QuitarEspacios, que se encargade eliminar todos los espacios en blanco que puedan existir en la cadena de texto.Aunque la inclusión de los espacios pudo haberse considerado en las expresionesregulares de cada uno de los casos, esto haría un tanto más compleja su estructuración,por lo que se ha optado por el camino más fácil, ¡quitarlos! Este método recibe unacadena de texto, y a través de una expresión regular apropiada, busca uno o másespacios y los reemplaza con una cadena vacía. private string QuitarEspacios(string cadena) { Regex espacio = new Regex(@"s+"); cadena = espacio.Replace(cadena, ""); return cadena; }Una vez que se ha determinado la validez de una cadena de texto como númerocomplejo, es necesario separar sus partes real e imaginaria para asignarlas a susrespectivos atributos. El método PartesComplejo se basa de un razonamiento muysimple: se busca la parte imaginaria del complejo, se lee su valor y luego se elimina,dejando de esta forma únicamente la parte real. // Método para separar la parte real y la parte imaginaria private void PartesComplejo(string cadena) { string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace(., Char.Parse(sd)); string parteReal = ""; string parteImag = ""; string signo = @"([+-])"; string numero = @"(d+(" + sd + @")?d*)"; string i = @"(i)"; string imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Regex imaginario1 = new Regex(imaginaria); if (imaginario1.IsMatch(cadena)) { // Cargar en mc las cadenas encontrada MatchCollection mc = imaginario1.Matches(cadena); // Recuperar la cadena encontrada foreach(Match m in mc) { parteImag = m.ToString(); } // Analizar algunos casos especiales if (parteImag == "+i" || parteImag == "i") parteImag = "1"; else if (parteImag == "-i") parteImag = "-1"; else parteImag = parteImag.Replace("i", ""); // Eliminar la parte imaginaria parteReal = imaginario1.Replace(cadena, ""); } else { parteReal = cadena; pedrov.cs@hotmail.com
  16. 16. 100 CAPITULO 4 PROGRAMACION CON C# parteImag = "0"; } // Convierte las cadenas de texto a double // y las asigna a sus atributos respectivos real = Double.Parse(parteReal); imaginario = Double.Parse(parteImag); } En la expresión regular que se utiliza en este método existen dos particularidades. La primera es que no se busca un sola coincidencia en toda la cadena (aunque, bien podría haberse hecho), por que se supone que la cadena analizada ya está comprobado que corresponde a un número complejo y por lo tanto solo existirá una, o ninguna, parte imaginaria. La otra particularidad, es que se han establecido todas las formas de parte imaginaria en una sola expresión regular. La razón, en este punto ya se sabe que la parte imaginaria está bien escrita y por lo tanto todo lo que se encuentre será válido. imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Cuando se analiza una cadena a través de una expresión regular, el motor de análisis busca todas las subcadenas que hagan parte de esa familia y las va guardando en un objeto de tipo MatchCollection. Para recuperar la colección de cadenas objetivo encontradas existe el método Matches que hace parte de los objetos de tipo Regex. En la siguiente línea se recupera todas las cadenas encontradas y se asignan al objeto mc: MatchCollection mc = imaginario1.Matches(cadena); En este caso particular, estamos seguros que si la cadena objetivo existe, es única, y en el peor de los casos no existe. Con este método, y los anteriores, estamos listos para ampliar y mejorar las capacidades de nuestra clase Complejo. Se pondrá a disposición del usuario de la clase, tres constructores sobrecargados. El primero no pide ningún dato de entrada. El segundo método da la posibilidad de ingresar los valores real e imaginario del complejo y el tercer método permite inicializar la variable con un complejo ingresado en forma de cadena de texto. Estos son los tres constructores: // Constructores public Complejo() { } public Complejo(double parteReal, double parteImaginaria) { real = parteReal; imaginario = parteImaginaria; } public Complejo(string valorComplejo) { if (EsComplejo(valorComplejo)) PartesComplejo(valorComplejo); else { real = 0; imaginario = 0; } } La salida devuelta por un objeto de tipo Complejo debe ser acorde a los valores de su parte real e imaginaria y al formato manejado para representar este tipo de números. El siguiente método privado se encarga de preparar la salida de un complejo en forma de cadena de texto, con el formato a + bi.www.pedrov.phpnet.us
  17. 17. CAPITULO 4 ELEMENTOS DE UN CLASE C# 101 private string FormatoSalida() { if (real == 0) return String.Format("{0}i", imaginario); else if (imaginario > 0) return String.Format("{0} + {1}i", real, imaginario); else if (imaginario < 0) return String.Format("{0} - {1}i", real, imaginario); else return real.ToString(); }Con base en lo anterior se agregará la propiedad Valor, que se encarga de devolver elvalor de un complejo o recibir su valor desde el exterior, validando su correctaescritura. Esta es su implementación: public string Valor { get { return FormatoSalida(); } set { if (EsComplejo(value)) PartesComplejo(value); else { real = 0; imaginario = 0; } } }Finalmente, es importante que los objetos tipo Complejo se puedan imprimir sinnecesidad de recurrir a ninguna propiedad en especial, en la misma forma como loshacen los valores numéricos de otros tipos. Es decir, si el programador tiene Console.WriteLine(z);debe mostrarse en pantalla el valor del complejo, que hace parte del argumento delmétodo WriteLine, en el formato adecuado. Esto mejora el nivel de abstracción de laclase Complejo y le asegura a sus objetos un comportamiento más cercano a losvalores numéricos, facilitando su manejo por parte de cualquier programador. Paralograr esto es necesario sobrescribir el método ToString que hace parte de toda clasedefinida en .NET.La clase Complejo al igual que todas las clases de .NET, en realidad son heredadas deuna clase genérica que forma parte de la raíz del framework, llamada Object. Aunqueesta herencia no se necesita determinar en forma explicita, el compilador de C# lointerpreta así con todas las clases definidas como superclases. Un método que sehereda de Object para todas las clases es ToString el que se ejecuta por defectocuando se intenta imprimir un objeto cualquiera. En la mayoría de los casos cuando seimprime un objeto, sin especificar ninguna propiedad, este método devuelve el nombrecompleto del objeto. En nuestro caso vamos a sobrescribir el método para obligarloescribir el valor del número complejo. Así: // Sobrecarga del método ToString pedrov.cs@hotmail.com
  18. 18. 102 CAPITULO 4 PROGRAMACION CON C# public override string ToString() { return FormatoSalida(); } En definitiva la clase complejo, ya casi lista, queda como sigue:/* Archivo: Complejo.cs */using System;using System.Globalization;using System.Text.RegularExpressions;public class Complejo{ // Atributos private double real; private double imaginario; // Constructores public Complejo() { } public Complejo(double parteReal, double parteImaginaria) { real = parteReal; imaginario = parteImaginaria; } public Complejo(string valorComplejo) { if (EsComplejo(valorComplejo)) PartesComplejo(valorComplejo); else { real = 0; imaginario = 0; } } // Propiedades public double Real { get { return real; } set { real = value; } } public double Imaginario { get { return imaginario; } set { imaginario = value; } } public double Modulo { get { return Tamano(); } } public double Argumento { get { return Angulo(); } }www.pedrov.phpnet.us
  19. 19. CAPITULO 4 ELEMENTOS DE UN CLASE C# 103public string Valor{ get { return FormatoSalida(); } set { if (EsComplejo(value)) PartesComplejo(value); else { real = 0; imaginario = 0; } }}// Sobrecarga del método ToStringpublic override string ToString(){ return FormatoSalida();}// Métodos privadosprivate double Tamano(){ double c; c = Math.Sqrt(real * real + imaginario * imaginario); return c;}private double Angulo(){ double alfa; if (real > 0) alfa = Math.Atan(imaginario / real); else if (real < 0) if (imaginario > 0) alfa = Math.PI + Math.Atan(imaginario / real); else alfa = - Math.PI + Math.Atan(imaginario / real); else if (imaginario > 0) alfa = Math.PI / 2; else if (imaginario < 0) alfa = - Math.PI / 2; else alfa = 0; return alfa;}// Método para válidar un número complejoprivate bool EsComplejo(string cadena){ cadena = QuitarEspacios(cadena); if (cadena.Length == 0) return false; string sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace(., Char.Parse(sd)); // Elementos básicos de un complejo string numero = @"(d+(" + sd + @")?d*)"; string i = @"(i)"; pedrov.cs@hotmail.com
  20. 20. 104 CAPITULO 4 PROGRAMACION CON C# string signo = @"([+-])"; // Validación para a, a + i, a + bi string real = signo + "?" + numero; string imaginario = "(" + signo + "(" + numero + ")?" + i + ")?"; string expresion1 = @"A" + real + imaginario + @"Z"; Regex complejo1 = new Regex(expresion1); if (complejo1.IsMatch(cadena)) return true; // Validación para i, i + a, bi, bi + a imaginario = signo + "?" + numero + "?" + i; real = "(" + signo + numero + ")?"; string expresion2 = @"A" + imaginario + real + @"Z"; Regex complejo2 = new Regex(expresion2); if (complejo2.IsMatch(cadena)) return true; // Validación para ib, ib + a imaginario = signo + "?" + i + numero; real = "(" + signo + numero + ")?"; string expresion3 = @"A" + imaginario + real + @"Z"; Regex complejo3 = new Regex(expresion3); if (complejo3.IsMatch(cadena)) return true; // Validación para a + ib real = signo + "?" + numero; imaginario = signo + i + numero; string expresion4 = @"A" + real + imaginario + @"Z"; Regex complejo4 = new Regex(expresion4); return complejo4.IsMatch(cadena); } // Método para separar la parte real y la parte imaginaria private void PartesComplejo(string cadena) { string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = QuitarEspacios(cadena); cadena = cadena.Replace(., Char.Parse(sd)); string parteReal = ""; string parteImag = ""; string signo = @"([+-])"; string numero = @"(d+(" + sd + @")?d*)"; string i = @"(i)"; string imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Regex imaginario1 = new Regex(imaginaria); if (imaginario1.IsMatch(cadena)) { // Cargar en mc las cadenas encontrada MatchCollection mc = imaginario1.Matches(cadena); // Recuperar la cadena encontrada foreach(Match m in mc) { parteImag = m.ToString(); } // Analizar algunos casos especiales if (parteImag == "+i" || parteImag == "i") parteImag = "1"; else if (parteImag == "-i") parteImag = "-1"; elsewww.pedrov.phpnet.us
  21. 21. CAPITULO 4 ELEMENTOS DE UN CLASE C# 105 parteImag = parteImag.Replace("i", ""); // Eliminar la parte imaginaria parteReal = imaginario1.Replace(cadena, ""); } else { parteReal = cadena; parteImag = "0"; } // Verificar la cadenas de texto vacías if (parteReal.Length == 0) parteReal = "0"; if (parteImag.Length == 0) parteImag = "0"; // Convierte las cadenas de texto a double // y las asigna a sus atributos respectivos real = Double.Parse(parteReal); imaginario = Double.Parse(parteImag); } private string QuitarEspacios(string cadena) { Regex espacio = new Regex(@"s+"); cadena = espacio.Replace(cadena, ""); return cadena; } private string FormatoSalida() { if (real == 0) return String.Format("{0}i", imaginario); else if (imaginario > 0) return String.Format("{0} + {1}i", real, imaginario); else if (imaginario < 0) return String.Format("{0} - {1}i", real, - imaginario); else return real.ToString(); }}Compile este archivo en un ensamblado tipo librería dinámica, con la instrucción, > csc /t:library Complejo.csEl siguiente programa hace uso de la clase Complejo y muestra el funcionamiento delos cambios realizados:/* Archivo: Ejemplo43.cs */using System;public class NumerosComplejos{ static void Main() { Complejo z = new Complejo(); Console.Write("Ingrese un número complejo: "); z.Valor = Console.ReadLine(); Console.Write("z = {0}n", z); Console.Write("a = {0}; b = {1}n", z.Real, z.Imaginario); Console.Write("Módulo: {0}n", z.Modulo); pedrov.cs@hotmail.com
  22. 22. 106 CAPITULO 4 PROGRAMACION CON C# Console.Write("Argumento: {0}", z.Argumento); // Uso de una sobrecarga del constructor de Complejo Complejo w = new Complejo("-3i + 4"); Console.WriteLine(w); Console.Write("a = {0}; b = {1}n", w.Real, w.Imaginario); Console.Write("Módulo: {0}n", w.Modulo); Console.Write("Argumento: {0}", w.Argumento); }} Compile este archivo con la instrucción de línea de comandos, > csc /r:Complejo.dll ejemplo43.cs Ejecute el programa resultante y analice el comportamiento de cada línea que lo compone. Un detalle importante a tener en cuenta es que el ensamblado Complejo.dll, a pesar de haber sufrido cambios, sigue siendo compatible con los programas que utilizaban la versión desarrollada en los anteriores ejemplos. Esta es una característica de los ensamblados de .NET, mientras no se modifique o elimine el nombre de alguno de los miembros que lo componen, cada componente puede seguir editándose y aumentando sus elementos y mantener la compatibilidad hacia versiones anteriores. Sobrecarga de operadores El concepto de sobrecarga también es aplicable a los operadores de C# y consiste en hacer que estos se comporten de acuerdo a los objetos que los utilizan. El ejemplo más conocido es el operador sobrecargado es +, quién tiene una versión para valores numéricos y otra para valores tipo cadena de texto. Cuando el operador se aplica a dos valores que representan cantidades numéricas, realiza una suma matemática, pero cuando se aplica a dos cadenas de texto, produce como resultado una cadena que es la concatenación de las dos primeras. Las siguientes líneas de código muestran un ejemplo típico: int a = 5 + 7; string c = "Hola" + "Mundo"; En la variable entera a se almacena el valor numérico 12, mientras que en la variable tipo cadena c se almacena el valor "HolaMundo". En cada caso el operador + tiene un comportamiento acorde a los tipos de datos sobre los que se aplica. La sobrecarga de operadores le da al lenguaje de programación la claridad y naturalidad suficientes para hacer de las operaciones con objetos un trabajo fácil de entender y aplicar por parte del programador. Sin embargo, no se debe abusar de este recurso, por que un mal uso del mismo puede volver al lenguaje incomprensible y confuso al momento de aplicar los operadores a algunos objetos. Por ejemplo, perfectamente se podría hacer una sobrecarga para el operador +, de tal manera que al aplicarse a un cierto tipo de datos numéricos produjera una multiplicación, algo que haría perder innecesariamente la lógica del lenguaje de programación. Todo operador, al momento de sobrecargarse, debe ejecutarse acorde a la función que realiza en otros objetos ya establecidos o en el mundo real del programador. No debemos perder de vista que la esencia de un lenguaje de programación es actuar como intermediario en el proceso de comunicación entre la máquina y el ser humano, y por lo tanto debe buscar ser lo más claro posible.www.pedrov.phpnet.us
  23. 23. CAPITULO 4 ELEMENTOS DE UN CLASE C# 107Para sobrecargar un operador se utiliza un método estático que debe hacer parte deltipo o clase que lo va a utilizar. La siguiente es la sintaxis general que se utiliza parasobrecargar un operador unario:public static TipoDevuelto operator operador (Tipo operando){ // Implementación}En forma similar se sobrecarga un operador binario:public static TipoDevuelto operator operador (Tipo1 operando1, Tipo2 operando2){ // Implementación}Aunque C# no permite la sobrecarga de todo los operadores que maneja, si lo hacepara la mayoría de operadores relacionados con las matemáticas y los bits. En lasiguiente lista se muestran todos los operadores que admiten sobrecarga: Operadores unarios +, -, !, ~, ++, --, true, false Operadores binarios +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, <= Ejemplo 4.4 Operaciones con complejosEn este ejemplo vamos a sobrecargar los operadores matemáticos para la claseComplejo. Hasta ahora no hemos definido la forma de realizar operaciones concomplejos. Si un programador deseara obtener una suma de complejos debería recurrira la definición matemática y aplicar el proceso con las partes de los complejos que sevayan a operar.Comencemos con la suma de números complejos. Las matemáticas la definen de lasiguiente forma: si se tienen dos complejos z1 y z2 , la suma de ellos es un númerocomplejo cuya parte real es la suma de las partes reales de los dos complejos, y deigual forma la parte imaginaria es igual a la suma de las partes imaginarias. Ennotación matemática: Si z1 = a1 + b1i y z2 = a2 + b2 i , entonces z1 + z2 = (a1 + a2 ) + (b1 + b2 )iUna posible solución al problema de programar la suma de complejos, podría ser ladefinición de un método estático que se encargue de recibir como parámetros doscomplejos, realizar la suma utilizando la definición matemática y devolver el resultadoen términos de una variable compleja. Este método, implementado por la claseComplejo, bien podría ser el siguiente: public static Complejo Suma(Complejo z1, Complejo z2) { double a = z1.Real + z2.Real; double b = z1.Imaginario + z2.Imaginario; Complejo z = new Complejo(a, b); return z; } pedrov.cs@hotmail.com
  24. 24. 108 CAPITULO 4 PROGRAMACION CON C# La solución es buena, y de hecho funciona muy bien. En las siguientes líneas de código se muestra como debería utilizarse el método Suma: Complejo z1 = new Complejo("5 + 3i"); Complejo z2 = new Complejo("8 - 2i"); Complejo z = new Complejo(); z = Complejo.Suma(z1, z2); Esta codificación de operaciones matemáticas, basada en llamadas a métodos, puede resultar molesta en situaciones donde se van a realizar operaciones de uso muy común. Un programador que haga uso de los complejos, talvez preferiría codificar una suma en forma más natural, o por lo menos como está acostumbrado a hacerlo con los demás números que maneja el lenguaje de programación, así suma = z1 + z2; Para lograr esto es necesario sobrecargar el operador +, indicándole cual debe ser el proceso a seguir cuando se aplique a números complejos. El siguiente método sobrecarga el operador + para la clase Complejo: public static Complejo operator +(Complejo z1,Complejo z2) { Complejo suma = new Complejo(); suma.real = z1.real + z2.real; suma.imaginario = z1.imaginario + z2.imaginario; return suma; } Como puede observarse, el método de sobrecarga hace exactamente lo que debería hacer el programador usuario de la clase. La ventaja es que solo se programa aquí, y en adelante bastará con aplicar una operación de suma común y corriente, como si de otro número cualquiera se tratará. Antes de sobrecargar las demás operaciones vamos sobrecargar el operador inverso aditivo, o signo negativo, (-), el cual invierte el signo de las partes que conforman un complejo. Matemáticamente establece: Si z = a + bi entonces − z = − a − bi Entonces la sobrecarga del operador inverso aditivo, o signo negativo, queda como sigue: public static Complejo operator -(Complejo z) { Complejo inverso = new Complejo(); inverso.real = - z.real; inverso.imaginario = - z.imaginario; return inverso; } A su vez, la resta de complejos puede definirse a través de la suma, así: z1 − z2 = z1 + ( − z2 ) Con lo que la sobrecarga del operador resta sigue la misma noción. public static Complejo operator -(Complejo z1,Complejo z2) {www.pedrov.phpnet.us
  25. 25. CAPITULO 4 ELEMENTOS DE UN CLASE C# 109 Complejo resta = z1 + (- z2); return resta; }En las dos últimas sobrecargas aparentemente se ha modificado el mismo operador. Enrealidad no es así. El compilador de C# distingue claramente a cada operador por elnúmero de operandos sobre los cuales actúa. En el primer caso, al existir un solooperando entiende que se trata del operador inverso aditivo, mientras que el segundocaso queda claro que se trata del operador resta.La multiplicación de complejos se obtiene realizando una multiplicación polinomial delos dos operandos. En general esta operación se simplifica en el siguiente resultado: Si z1 = a1 + b1i y z2 = a2 + b2 i , entonces z1 ⋅ z2 = (a1a2 − b1b2 ) + (a1b2 + a2 b1 )iCon base en esta definición, la sobrecarga del operador multiplicación, *, queda así: public static Complejo operator *(Complejo z1,Complejo z2) { Complejo producto = new Complejo(); double a1 = z1.real, b1 = z1.imaginario; double a2 = z2.real, b2 = z2.imaginario; producto.real = a1 * a2 - b1 * b2; producto.imaginario = a1 * b2 + a2 * b1; return producto; }Pero la multiplicación no solo puede darse entre números complejos, también puedemultiplicarse un numero real por un complejo. Para lograr esto es necesario aplicar dossobrecargas más al operador *. Puede multiplicarse un complejo por la izquierda o porla derecha. Ambas situaciones deben quedar bien claras para el compilador de C#. Ladefinición matemática de esta multiplicación establece:Si c ∈ ℝ y z = a + bi , entonces c ⋅ z = ca + cbiEn consecuencia la sobrecarga de * para este caso queda como sigue: public static Complejo operator *(double c, Complejo z) { Complejo z1 = new Complejo(); z1.Real = c * z.Real; z1.Imaginario = c * z.Imaginario; return z1; }A su vez, la sobrecarga para la multiplicación por la derecha se puede implementar conbase en la anterior, así: public static Complejo operator *(Complejo z, double c) { return c * z; }Existe una operación propia de los complejos, que no esta definida para ningún otrotipo numérico. Es el conjugado de un complejo. Esta operación, lo único que hace esinvertir la parte imaginaria del número complejo al cual se aplica. Si z = a + bi , el conjugado de z se define como z = a − bi pedrov.cs@hotmail.com
  26. 26. 110 CAPITULO 4 PROGRAMACION CON C# En C# no existe un operador cuya funcionalidad tenga alguna relación con el conjugado de un complejo. En vista de esto vamos a sobrecargar el operador ! (negación lógica). La sobrecarga queda como sigue: public static Complejo operator !(Complejo z) { Complejo conjugado = new Complejo(); conjugado.Real = z.Real; conjugado.Imaginario = - z.Imaginario; return conjugado; } La división de números complejos se puede definir, con base en el conjugado y el módulo del divisor, en la siguiente forma: z1 1 = z1 ⋅ z2 z2 z2 2 Por lo tanto, el método que sobrecarga el operador división, /, se puede implementar como sigue: public static Complejo operator /(Complejo z1,Complejo z2) { Complejo cociente; cociente = 1 / Math.Pow(z2.Modulo, 2) * (z1 * !z2); return cociente; } Teniendo en cuenta estos cambios, nuestra clase Complejo queda como sigue:/* Archivo: Complejo.cs */using System;using System.Text.RegularExpressions;using System.Globalization;public class Complejo{ // Atributos private double real; private double imaginario; // Constructores public Complejo() { } public Complejo(double parteReal, double parteImaginaria) { real = parteReal; imaginario = parteImaginaria; } public Complejo(string valorComplejo) { if (EsComplejo(valorComplejo)) PartesComplejo(valorComplejo); else { real = 0; imaginario = 0;www.pedrov.phpnet.us
  27. 27. CAPITULO 4 ELEMENTOS DE UN CLASE C# 111 }}// Propiedadespublic double Real{ get { return real; } set { real = value; }}public double Imaginario{ get { return imaginario; } set { imaginario = value; }}public double Modulo{ get { return Tamano(); }}public double Argumento{ get { return Angulo(); }}public string Valor{ get { return FormatoSalida(); } set { if (EsComplejo(value)) PartesComplejo(value); else { real = 0; imaginario = 0; } }}// Sobrecarga del método ToStringpublic override string ToString(){ return FormatoSalida();}// Sobrecarga del operador +public static Complejo operator +(Complejo z1, Complejo z2){ Complejo suma = new Complejo(); suma.Real = z1.Real + z2.Real; suma.Imaginario = z1.Imaginario + z2.Imaginario; return suma;}// Sobrecarga del operador - (negativo)public static Complejo operator -(Complejo z){ Complejo inverso = new Complejo(); pedrov.cs@hotmail.com
  28. 28. 112 CAPITULO 4 PROGRAMACION CON C# inverso.Real = - z.Real; inverso.Imaginario = - z.Imaginario; return inverso; } // Sobrecarga del operador - public static Complejo operator -(Complejo z1, Complejo z2) { Complejo resta = z1 + (- z2); return resta; } // Sobrecarga del operador * public static Complejo operator *(Complejo z1, Complejo z2) { Complejo producto = new Complejo(); double a1 = z1.Real, b1 = z1.Imaginario; double a2 = z2.Real, b2 = z2.Imaginario; producto.Real = a1 * a2 - b1 * b2; producto.Imaginario = a1 * b2 + a2 * b1; return producto; } public static Complejo operator *(double c, Complejo z) { Complejo z1 = new Complejo(); z1.Real = c * z.Real; z1.Imaginario = c * z.Imaginario; return z1; } public static Complejo operator *(Complejo z, double c) { return c * z; } // Sobrecarga del operador ! para el conjugado public static Complejo operator !(Complejo z) { Complejo conjugado = new Complejo(); conjugado.Real = z.Real; conjugado.Imaginario = - z.Imaginario; return conjugado; } // Sobrecarga del operador / public static Complejo operator /(Complejo z1, Complejo z2) { Complejo cociente; cociente = 1 / Math.Pow(z2.Modulo, 2) * (z1 * !z2); return cociente; } // Métodos privados private double Tamano() { double c; c = Math.Sqrt(real * real + imaginario * imaginario); return c; }www.pedrov.phpnet.us
  29. 29. CAPITULO 4 ELEMENTOS DE UN CLASE C# 113// Calcular el ángulo del complejoprivate double Angulo(){ double alfa; if (real > 0) alfa = Math.Atan(imaginario / real); else if (real < 0) if (imaginario > 0) alfa = Math.PI + Math.Atan(imaginario / real); else alfa = - Math.PI + Math.Atan(imaginario / real); else if (imaginario > 0) alfa = Math.PI / 2; else if (imaginario < 0) alfa = - Math.PI / 2; else alfa = 0; return alfa;}// Método para válidar un número complejoprivate bool EsComplejo(string cadena){ cadena = QuitarEspacios(cadena); if (cadena.Length == 0) return false; string sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace(., Char.Parse(sd)); // Elementos básicos de un complejo string numero = @"(d+(" + sd + @")?d*)"; string i = @"(i)"; string signo = @"([+-])"; // Validación para a, a + i, a + bi string real = signo + "?" + numero; string imaginario = "(" + signo + "(" + numero + ")?" + i + ")?"; string expresion1 = @"A" + real + imaginario + @"Z"; Regex complejo1 = new Regex(expresion1); if (complejo1.IsMatch(cadena)) return true; // Validación para i, i + a, bi, bi + a imaginario = signo + "?" + numero + "?" + i; real = "(" + signo + numero + ")?"; string expresion2 = @"A" + imaginario + real + @"Z"; Regex complejo2 = new Regex(expresion2); if (complejo2.IsMatch(cadena)) return true; // Validación para ib, ib + a imaginario = signo + "?" + i + numero; real = "(" + signo + numero + ")?"; string expresion3 = @"A" + imaginario + real + @"Z"; Regex complejo3 = new Regex(expresion3); if (complejo3.IsMatch(cadena)) return true; // Validación para a + ib real = signo + "?" + numero; imaginario = signo + i + numero; string expresion4 = @"A" + real + imaginario + @"Z"; Regex complejo4 = new Regex(expresion4); pedrov.cs@hotmail.com
  30. 30. 114 CAPITULO 4 PROGRAMACION CON C# return complejo4.IsMatch(cadena); } // Método para separar la parte real y la parte imaginaria private void PartesComplejo(string cadena) { string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = QuitarEspacios(cadena); cadena = cadena.Replace(., Char.Parse(sd)); string parteReal = ""; string parteImag = ""; string signo = @"([+-])"; string numero = @"(d+(" + sd + @")?d*)"; string i = @"(i)"; string imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Regex imaginario1 = new Regex(imaginaria); if (imaginario1.IsMatch(cadena)) { // Cargar en mc las cadenas encontrada MatchCollection mc = imaginario1.Matches(cadena); // Recuperar la cadena encontrada foreach(Match m in mc) { parteImag = m.ToString(); } // Analizar algunos casos especiales if (parteImag == "+i" || parteImag == "i") parteImag = "1"; else if (parteImag == "-i") parteImag = "-1"; else parteImag = parteImag.Replace("i", ""); // Eliminar la parte imaginaria parteReal = imaginario1.Replace(cadena, ""); } else { parteReal = cadena; parteImag = "0"; } // Verificar la cadenas de texto vacías if (parteReal.Length == 0) parteReal = "0"; if (parteImag.Length == 0) parteImag = "0"; // Convierte las cadenas de texto a double // y las asigna a sus atributos respectivos real = Double.Parse(parteReal); imaginario = Double.Parse(parteImag); } // Elimina los espacios de una cadena de texto private string QuitarEspacios(string cadena) { Regex espacio = new Regex(@"s+"); cadena = espacio.Replace(cadena, ""); return cadena; }www.pedrov.phpnet.us
  31. 31. CAPITULO 4 ELEMENTOS DE UN CLASE C# 115 // Da formato a la cadena de texto de salida private string FormatoSalida() { if (real == 0) return String.Format("{0}i", imaginario); else if (imaginario > 0) return String.Format("{0} + {1}i", real, imaginario); else if (imaginario < 0) return String.Format("{0} - {1}i", real, - imaginario); else return real.ToString(); }}Compile el archivo con la instrucción, > csc /t:library Complejo.csCon los cambios realizados ya contamos con una clase Complejo capaz de definirobjetos cuyo comportamiento se asemeja bastante a los números que maneja C#. Lasobrecarga de los operadores aritméticos nos permitirá codificar las operaciones de estetipo en la misma forma como se hace con cualquier otro tipo numérico. Aunque talvezno es el mejor, esta clase es un buen ejemplo de abstracción y encapsulamiento, lo cualpermite contar con tipos complejos con un buen nivel de autonomía para resolver lamayoría de problemas propios de su naturaleza.El siguiente programa hace uso de la clase Complejo y realiza algunas operacionescon números complejos:/* Archivo: Ejemplo44.cs */using System;public class OperacionesComplejos{ static void Main() { Complejo w = new Complejo(); Complejo z = new Complejo(); Console.Write("w = "); w.Valor = Console.ReadLine(); Console.Write("z = "); z.Valor = Console.ReadLine(); Console.Write("-w = {0}n", -w); Console.Write("w + z = {0}n", w + z); Console.Write("w - z = {0}n", w - z); Console.Write("w * z = {0}n", w * z); Console.Write("w / z = {0}n", w / z); Console.Write("!w = {0}n", !w); Console.Write("5w = {0}n", 5 * w); }}Guarde el archivo con el nombre ejemplo44.cs y compílelo con la instrucción, > csc /r:Complejo.dll ejemplo44.cs pedrov.cs@hotmail.com
  32. 32. 116 CAPITULO 4 PROGRAMACION CON C# El lector podrá comprobar que la clase Complejo define objetos que en forma autónoma se encargan de realizar la mayoría de térreas que les impone su naturaleza, incluyendo su operatoria y el formato para la salida de los resultados, sin necesidad de que el programador tenga que preocuparse de esos detalles. Aunque se ha logrado un buen nivel de abstracción y encapsulamiento, no podemos decir que todo está terminado. Por ejemplo, cuando se asigna a un objeto Complejo una cadena que no corresponde a la forma de un número complejo, la clase no posee un mecanismo para informar directamente sobre esa situación anómala y en vez de eso asume un valor nulo sin que el usuario se entere de tal situación. Se podría implementar un mecanismo de mensajes para informar al usuario que existe un error en la asignación de un valor, pero esto podría afectar la generalidad del componente y limitarlo a un único entorno de ejecución. El objetivo es crear un componente de software útil en cualquier entorno, consola, sistema gráfico de Windows o web. En las siguientes secciones se describirán elementos de la programación con C# que permiten dar mayor robustez a los componentes de software, y con ellos podremos resolver en forma técnica las deficiencias de nuestra clase Complejo. Eventos Un evento es una acción que produce un componente y a la que otro componente puede responder o puede controlar mediante código. Los eventos más conocidos son aquellos que se producen por acción del usuario, por ejemplo, al hacer clic con el botón principal del ratón sobre un botón de una ventana se produce un evento que a su vez ejecuta un código de programación. Sin embargo, esta última asociación didáctica para intentar explicar el concepto de evento, más que ayudar, puede distorsionar la noción que sobre el mismo impone la programación orientada a objetos. En la práctica un evento es una especie de procedimiento que ejecuta un objeto, pero que se implementa fuera de su clase. Mejor, podemos ver a un evento como una llamada a un procedimiento (o método) que hace un objeto, el cual es programado en la misma clase donde este existe. Los eventos le sirven a una clase para proporcionar notificaciones cuando sucede algo de interés. Una noción muy general de cómo funciona un evento la podemos visualizar en el siguiente esquema. Supongamos que tenemos una clase ClaseA, class ClaseA { Miembro { Llamar a MiEvento; } } en la cual uno de sus miembros ejecuta un procedimiento especial al que hemos llamado MiEvento. Si este procedimiento se define como evento, su implementación se puede hacer para cada objeto derivado de ClaseA y en el espacio donde estos se definan. Si con la clase ClaseA se definen los objetos a1 y a2, en una clase ClaseB, esta clase puede implementar métodos que se ejecuten cuando cada uno de estos objetos, internamente, hace el llamado al procedimiento especial que hemos denominado MiEvento. class ClaseB {www.pedrov.phpnet.us

×