Programación Orientada a Objetos
Contenidos Artículos Programación orientada a objetos
1
Paradigma de programación
6
Objeto (programación)
7
Herencia (informática)
13
Abstracción (informática)
16
Polimorfismo (informática)
17
Encapsulamiento (informática)
22
Método (informática)
23
Programación estructurada
24
Simula
30
C+ +
31
C (lenguaje de programación)
51
Interfaz gráfica de usuario
60
Programación dirigida por eventos
64
Ada (lenguaje de programación)
68
Lisp
71
Pascal (lenguaje de programación)
78
Sobrecarga
82
Object Pascal
84
Referencias Fuentes y contribuyentes del artículo
85
Fuentes de imagen, Licencias y contribuyentes
87
Licencias de artículos Licencia
88
Contenidos Artículos Programación orientada a objetos
1
Paradigma de programación
6
Objeto (programación)
7
Herencia (informática)
13
Abstracción (informática)
16
Polimorfismo (informática)
17
Encapsulamiento (informática)
22
Método (informática)
23
Programación estructurada
24
Simula
30
C+ +
31
C (lenguaje de programación)
51
Interfaz gráfica de usuario
60
Programación dirigida por eventos
64
Ada (lenguaje de programación)
68
Lisp
71
Pascal (lenguaje de programación)
78
Sobrecarga
82
Object Pascal
84
Referencias Fuentes y contribuyentes del artículo
85
Fuentes de imagen, Licencias y contribuyentes
87
Licencias de artículos Licencia
88
Programación orientada a objetos
Programación orientada a objetos La programación orientada a objetos o POO (OOP según sus siglas en inglés) es un paradigma de programación que usa objetos y sus interacciones, para diseñar aplicaciones y programas informáticos. Está basado en varias técnicas, incluyendo herencia, abstracción, polimorfismo polimorfismo y encapsulamiento. encapsulamiento. Su uso se popularizó a principios de la década de los años 1990. En la actualidad, existe variedad de lenguajes de programación que soportan la orientación a objetos.
Introducción Los objetos son entidades que tienen un determinado estado, comportamiento (método) e identidad : • El estado está compuesto de datos o informaciones , será uno o varios atributos a los que se habrán asignado unos valores concretos (datos). • El comportamiento está definido por los métodos o mensajes a los que sabe responder dicho objeto, es decir, qué operaciones se pueden realizar con él. • La identidad es una propiedad de un objeto que lo diferencia del resto, dicho dicho con otras palabras, es su identificador identificador (concepto análogo al de identificador identificador de una variable o una constante). Un objeto contiene toda la información que permite definirlo e identificarlo frente a otros objetos pertenecientes a otras clases e incluso frente a objetos de una misma clase, al poder tener valores bien diferenciados en sus atributos. A su vez, los objetos disponen de mecanismos de interacción llamados métodos, que favorecen la comunicación entre ellos. Esta comunicación favorece a su vez el cambio de estado en los propios objetos. Esta característica característica lleva a tratarlos como unidades indivisibles, en las que no se separa el estado y el comportamiento. Los métodos (comportamiento) y atributos (estado) están estrechamente relacionados por la propiedad de conjunto. Esta propiedad destaca que una clase requiere de métodos para poder tratar los atributos con los que cuenta. El programador debe pensar indistintamente en ambos conceptos, sin separar ni darle mayor importancia a alguno de ellos. Hacerlo podría producir el hábito erróneo de crear clases contenedoras de información por un lado y clases con métodos que manejen a las primeras por el otro. De esta manera se estaría realizando una programación estructurada camuflada en un lenguaje de programación orientado a objetos. La POO difiere de la programación estructurada tradicional, tradicional, en la que los datos y los procedimientos están separados y sin relación, ya que lo único que se busca es el procesamiento de unos datos de entrada para obtener otros de salida. La programación estructurada anima al programador a pensar sobre todo en términos de procedimientos o funciones, y en segundo lugar en las estructuras de datos que esos procedimientos manejan. En la programación estructurada solo se escriben funciones que procesan datos. Los programadores que emplean POO, en cambio, primero definen objetos para luego enviarles mensajes solicitándoles solicitándoles que realicen sus métodos por sí mismos.
Origen Los conceptos de la programación orien tada a objetos tienen origen en Simula 67, un lenguaje diseñado para hacer simulaciones, creado por Ole-Johan Dahl y Kristen Nygaard del Centro de Cómputo Noruego en Oslo. En este centro, se trabajaba en simulaciones de naves, que fueron confundidas por la explosión combinatoria de cómo las diversas cualidades de diferentes naves podían afectar unas a las otras. La idea surgió al agrupar los diversos tipos de naves en diversas clases de objetos, siendo responsable cada clase de objetos de definir sus propios datos y comportamientos. Fueron refinados más tarde en Smalltalk, desarrollado en Simula en Xerox PARC (cuya primera versión fue escrita sobre Basic) pero diseñado para ser un sistema completamente completamente dinámico en el cual los objetos se podrían crear y modificar "sobre la marcha" (en tiempo de ejecución) en lugar de tener un sistema basado en programas estáticos.
1
Programación orientada a objetos La programación orientada a objetos se fue convirtiendo en el estilo de programación dominante a mediados de los años ochenta, en gran parte debido a la influencia de C++, una extensión del lenguaje de programación C. Su dominación fue consolidada gracias al auge de las Interfaces gráficas de usuario, para las cuales la programación orientada a objetos está particularmente bien adaptada. En este caso, se habla también de programación dirigida por eventos. Las características de orientación a objetos fueron agregadas a muchos lenguajes existentes durante ese tiempo, incluyendo Ada, BASIC, Lisp, Pascal, entre otros. La adición de estas características a los lenguajes que no fueron diseñados inicialmente para ellas condujo a menudo a problemas de compatibilidad y en la capacidad de mantenimiento del código. Los lenguajes orientados a objetos "puros", por su parte, carecían de las características de las cuales muchos programadores habían venido a depender. Para saltar este obstáculo, se hicieron muchas tentativas para crear nuevos lenguajes basados en métodos orientados a objetos, pero permitiendo algunas características imperativas de maneras "seguras". El Eiffel de Bertrand Meyer fue un temprano y moderadamente acertado lenguaje con esos objetivos pero ahora ha sido esencialmente reemplazado por Java, en gran parte debido a la aparición de Internet, y a la implementación de la máquina virtual de Java en la mayoría de navegadores. PHP en su versión 5 se ha modificado, soporta una orientación completa a objetos, cumpliendo todas las características propias de la orientación a objetos.
Conceptos fundamentales La programación orientada a objetos es una forma de programar que trata de encontrar una solución a estos problemas. Introduce nuevos conceptos, que superan y amplían conceptos antiguos ya conocidos. Entre ellos destacan los siguientes: • Clase: definiciones de las propiedades y comportamiento de un tipo de objeto concreto. La instanciación es la lectura de estas definiciones y la creación de un objeto a partir de ellas. • Herencia: (por ejemplo, herencia de la clase C a la clase D) Es la facilidad mediante la cual la clase D hereda en ella cada uno de los atributos y operaciones de C, como si esos atributos y operaciones hubiesen sido definidos por la misma D. Por lo tanto, puede usar los mismos métodos y variables publicas declaradas en C. Los componentes registrados como "privados" (private) también se heredan, pero como no pertenecen a la clase, se mantienen escondidos al programador y sólo pueden ser accedidos a través de otros métodos públicos. Esto es así para mantener hegemónico el ideal de OOP. • Objeto: entidad provista de un conjunto de propiedades o atributos (datos) y de comportamiento o funcionalidad (métodos) los mismos que consecuentemente reaccionan a eventos. Se corresponde con los objetos reales del mundo que nos rodea, o a objetos internos del sistema (del programa). Es una instancia a una clase. • Método: Algoritmo asociado a un objeto (o a una clase de objetos), cuya ejecución se desencadena tras la recepción de un "mensaje". Desde el punto de vista del comportamiento, es lo que el objeto puede hacer. Un método puede producir un cambio en las propiedades del objeto, o la generación de un "evento" con un nuevo mensaje para otro objeto del sistema. • Evento: Es un suceso en el sistema (tal como una interacción del usuario con la máquina, o un mensaje enviado por un objeto). El sistema maneja el evento enviando el mensaje adecuado al objeto pertinente. También se puede definir como evento, a la reacción que puede desencadenar un objeto, es decir la acción que genera. • Mensaje: una comunicación dirigida a un objeto, que le ordena que ejecute uno de sus métodos con ciertos parámetros asociados al evento que lo generó. • Propiedad o atributo: contenedor de un tipo de datos asociados a un objeto (o a una clase de objetos), que hace los datos visibles desde fuera del objeto y esto se define como sus características predeterminadas, y cuyo valor puede ser alterado por la ejecución de algún método. • Estado interno: es una variable que se declara privada, que puede ser únicamente accedida y alterada por un método del objeto, y que se utiliza para indicar distintas situaciones posibles para el objeto (o clase de objetos).
2
Programación orientada a objetos No es visible al programador que maneja una instancia de la clase. • Componentes de un objeto: atributos, identidad, relaciones y métodos. • Identificación de un objeto: un objeto se representa por medio de una tabla o entidad que esté compuesta por sus atributos y funciones correspondientes. En comparación con un lenguaje imperativo, una "variable", no es más que un contenedor interno del atributo del objeto o de un estado interno, así como la "función" es un procedimiento interno del método del objeto.
Características de la POO Existe un acuerdo acerca de qué características contempla la "orientación a objetos", las características siguientes son las más importantes: • Abstracción: denota las características esenciales de un objeto, donde se capturan sus comportamientos. Cada objeto en el sistema sirve como modelo de un "agente" abstracto que puede realizar trabajo, informar y cambiar su estado, y "comunicarse" con otros objetos en el sistema sin revelar cómo se implementan estas características. Los procesos, las funciones o los métodos pueden también ser abstraídos y cuando lo están, una variedad de técnicas son requeridas para ampliar una abstracción.El proceso de abstracción permite seleccionar las características relevantes dentro de un conjunto e identificar comportamientos comunes para definir nuevos tipos de entidades en el mundo real. La abstracción es clave en el proceso de análisis y diseño orientado a objetos, ya que mediante ella podemos llegar a armar un conjunto de clases que permitan modelar la realidad o el problema que se quiere atacar. • Encapsulamiento: Significa reunir a todos los elementos que pueden considerarse pertenecientes a una misma entidad, al mismo nivel de abstracción. Esto permite aumentar la cohesión de los componentes del sistema. Algunos autores confunden este concepto con el principio de ocultación, principalmente porque se suelen emplear conjuntamente. • Modularidad: Se denomina Modularidad a la propiedad que permite subdividir una aplicación en partes más pequeñas (llamadas módulos), cada una de las cuales debe ser tan independiente como sea posible de la aplicación en sí y de las restantes partes. Estos módulos se pueden compilar por separado, pero tienen conexiones con otros módulos. Al igual que la encapsulación, los lenguajes soportan la Modularidad de diversas formas. • Principio de ocultación: Cada objeto está aislado del exterior, es un módulo natural, y cada tipo de objeto expone una interfaz a otros objetos que especifica cómo pueden interactuar con los objetos de la clase. El aislamiento protege a las propiedades de un objeto contra su modificación por quien no tenga derecho a acceder a ellas, solamente los propios métodos internos del objeto pueden acceder a su estado. Esto asegura que otros objetos no pueden cambiar el estado interno de un objeto de maneras inesperadas, eliminando efectos secundarios e interacciones inesperadas. Algunos lenguajes relajan esto, permitiendo un acceso directo a los datos internos del objeto de una manera controlada y limitando el grado de abstracción. La aplicación entera se reduce a un agregado o rompecabezas de objetos. • Polimorfismo: comportamientos diferentes, asociados a objetos distintos, pueden compartir el mismo nombre, al llamarlos por ese nombre se utilizará el comportamiento correspondiente al objeto que se esté usando. O dicho de otro modo, las referencias y las colecciones de objetos pueden contener objetos de diferentes tipos, y la invocación de un comportamiento en una referencia producirá el comportamiento correcto para el tipo real del objeto referenciado. Cuando esto ocurre en "tiempo de ejecución", esta última característica se llama asignación tardía o asignación dinámica. Algunos lenguajes proporcionan medios más estáticos (en "tiempo de compilación") de polimorfismo, tales como las plantillas y la sobrecarga de operadores de C++. • Herencia: las clases no están aisladas, sino que se relacionan entre sí, formando una jerarquía de clasificación. Los objetos heredan las propiedades y el comportamiento de todas las clases a las que pertenecen. La herencia organiza y facilita el polimorfismo y el encapsulamiento permitiendo a los objetos ser definidos y creados como tipos especializados de objetos preexistentes. Estos pueden compartir (y extender) su comportamiento sin tener
3
Programación orientada a objetos que volver a implementarlo. Esto suele hacerse habitualmente agrupando los objetos en clases y estas en árboles o enrejados que reflejan un comportamiento común. Cuando un objeto hereda de más de una clase se dice que hay herencia múltiple. • Recolección de basura: la recolección de basura o garbage collector es la técnica por la cual el entorno de objetos se encarga de destruir automáticamente, y por tanto desvincular la memoria asociada, los objetos que hayan quedado sin ninguna referencia a ellos. Esto significa que el programador no debe preocuparse por la asignación o liberación de memoria, ya que el entorno la asignará al crear un nuevo objeto y la liberará cuando nadie lo esté usando. En la mayoría de los lenguajes híbridos que se extendieron para soportar el Paradigma de Programación Orientada a Objetos como C++ u Object Pascal, esta característica no existe y la memoria debe desasignarse manualmente.
Resumen La programación orientada a objetos es un paradigma que utiliza objetos como elementos fundamentales en la construcción de la solución. Surge en los años 70. Un objeto es una abstracción de algún hecho o ente del mundo real que tiene atributos que representan sus características o propiedades y métodos que representan su comportamiento o acciones que realizan. Todas las propiedades y métodos comunes a los objetos se encapsulan o se agrupan en clases. Una clase es una plantilla o un prototipo para crear objetos, por eso se dice que los objetos son instancias de clases.
Lenguajes orientados a objetos Simula (1967) es aceptado como el primer lenguaje que posee las características principales de un lenguaje orientado a objetos. Fue creado para hacer programas de simulación, en donde los "objetos" son la representación de la información más importante. Smalltalk (1972 a 1980) es posiblemente el ejemplo canónico, y con el que gran parte de la teoría de la programación orientada a objetos se ha desarrollado. Entre los lenguajes orientados a objetos se destacan los siguientes: • • • • • • • • • • • • • • • • • • • • • •
ABAP -> SAP Lenguaje orientado a eventos ABL Lenguaje de programación de OpenEdge de Progress Software ActionScript ActionScript 3 Ada C++ C# Clarion Clipper (lenguaje de programación) (Versión 5.x con librería de objetos Class(y)) D Object Pascal (Embarcadero Delphi) Gambas Harbour Eiffel Fortran 90/95 Java JavaScript (la herencia se realiza por medio de la programación basada en prototipos) Lexico (en castellano) Objective-C Ocaml Oz R
4
Programación orientada a objetos • Perl (soporta herencia múltiple. La resolución se realiza en preorden, pero puede modificarse al algoritmo linearization C3 por medio del módulo Class::C3 [1] en CPAN) • PHP (a partir de su versión 5) • PowerBuilder • Python • Ruby • Smalltalk (Entorno de objetos puro) • Magik (SmallWorld) • Vala • VB.NET • Visual FoxPro (en su versión 6) • Visual Basic 6.0 • Visual DataFlex • Visual Objects • XBase++ • Lenguaje DRP • Lenguaje de programación Scala (lenguaje usado por Twitter) http:/ / www.scala-lang.org/ page. jsp Muchos de estos lenguajes de programación no son puramente orientados a objetos, sino que son híbridos que combinan la POO con otros paradigmas. Al igual que C++ otros lenguajes, como OOCOBOL, OOLISP, OOPROLOG y Object REXX, han sido creados añadiendo extensiones orientadas a objetos a un lenguaje de programación clásico. Un nuevo paso en la abstracción de paradigmas de programación es la Programación Orientada a Aspectos (POA). Aunque es todavía una metodología en estado de maduración, cada vez atrae a más investigadores e incluso proyectos comerciales en todo el mundo.
Enlaces externos • Qué es la programacion orientada a objetos [2]
Referencias [1] http:/ / search.cpan.org/ perldoc?Class::C3 [2] http:/ / www.desarrolloweb.com/ articulos/ 499.php
5
Paradigma de programación
Paradigma de programación Un paradigma de programación es una propuesta tecnológica que es adoptada por una comunidad de programadores cuyo núcleo central es incuestionable en cuanto a que unívocamente trata de resolver uno o varios problemas claramente delimitados. La resolución de estos problemas debe suponer consecuentemente un avance significativo en al menos un parámetro que afecte a la ingeniería de software. Tiene una estrecha relación con la formalización de determinados lenguajes en su momento de definición. Un paradigma de programación está delimitado en el tiempo en cuanto a aceptación y uso ya que nuevos paradigmas aportan nuevas o mejores soluciones que la sustituyen parcial o totalmente. Ejemplo : Probablemente el paradigma de programación que actualmente es el más usado a todos los niveles es la orientación a objeto. El núcleo central de este paradigma es la unión de datos y procesamiento en una entidad llamada "objeto", relacionable a su vez con otras entidades "objeto". Tradicionalmente datos y procesamiento se han separado en áreas diferente del diseño y la implementación de software. Esto provocó que grandes desarrollos tuvieran problemas de fiabilidad, mantenimiento, adaptación a los cambios y escalabilidad. Con la orientación a objetos y características como el encapsulado, polimorfismo o la herencia se permitió un avance significativo en el desarrollo de software a cualquier escala de producción. La orientación a objeto parece estar ligado en sus orígenes con lenguajes como L isp y Simula aunque el primero que acuño el título de programación orientada a objetos fue Smaltalk
Tipos de paradigmas de programación más comunes • Imperativo o por procedimientos: es considerado el más común y está representado, por ejemplo, por C, BASIC o Pascal • Funcional: está representado por Scheme o Haskell. Este es un caso del paradigma declarativo. • Lógico: está representado por Prolog. Este es otro caso del paradigma declarativo. • Declarativo: por ejemplo la programación funcional, la programación lógica, o la combinación lógico-funcional. • Orientado a objetos: está representado por Smalltalk, un lenguaje completamente orientado a objetos. Si bien puede seleccionarse la forma pura de estos paradigmas al momento de programar, en la práctica es habitual que se mezclen, dando lugar a la programación multiparadigma. Actualmente el paradigma de programación más usado es el de la programación orientada a objetos.
6
Objeto (programación)
Objeto (programación) En el paradigma de programación orientada a objetos (POO, o bien OOP en inglés), un objeto se define como la unidad que en tiempo de ejecución realiza las tareas de un programa. También a un nivel más básico se define como la instancia de una clase. Estos objetos interactúan unos con otros, en contraposición a la visión tradicional en la cual un programa es una colección de subrutinas (funciones o procedimientos), o simplemente una lista de instrucciones para el computador. Cada objeto es capaz de recibir mensajes, procesar datos y enviar mensajes a otros objetos de manera similar a un servicio. En el mundo de la programación orientada a objetos (POO), un objeto es el resultado de la instanciación de una clase. Una clase es el anteproyecto que ofrece la funcionalidad en ella definida, pero ésta queda implementada sólo al crear una instancia de la clase, en la forma de un objeto. Por ejemplo: dado un plano para construir sillas (una clase de nombre clase_silla), entonces una silla concreta, en la que podemos sentarnos, construida a partir de este plano, sería un objeto de clase_silla. Es posible crear (construir) múltiples objetos (sillas) utilizando la definición de la clase (plano) anterior. Los conceptos de clase y objetos son análogos a los de tipo de datos y variable, es decir, definida una clase podemos crear objetos de esa clase, igual que disponiendo de un determinado tipo de dato (por ejemplo el tipo entero), podemos definir variables de dicho tipo: int a,b;
( 'int' es un tipo de dato y 'a' y 'b' son variables de tipo entero con las que podemos operar ) Para utilizar la funcionalidad definida en una clase en particular (salvo en las clases abstractas), primeramente es necesario crear un objeto de esa clase. De la misma manera para una persona que desea sentarse, las especificaciones para construir una silla serán de poca utilidad; lo que se necesita es una silla real construida a partir de esas especificaciones. Siguiendo con la analogía anterior, también se puede decir que para hacer operaciones aritméticas, de nada sirve por sí solo el tipo entero ( int); para ello necesitamos variables (o constantes) con las que operar.
Definición de Objeto En filosofía un objeto es aquello que puede ser observado, estudiado y aprendido, en contraposición a la representación abstracta de ese objeto que se crea en la mente a través del proceso de generalización. Un objeto en POO representa alguna entidad de la vida real, es decir, alguno de los objetos que pertenecen al negocio con que estamos trabajando o al problema con el que nos estamos enfrentando, y con los que podemos interactuar. A través del estudio de ellos se adquiere el conocimiento necesario para, mediante la abstracción y la generalización, agruparlos según sus características en conjuntos, estos conjuntos determinan las clases de objetos con las que estamos trabajando. Primero existen los objetos, luego aparecen las clases en función de la solución que estemos buscando. Ésta es la forma más común de adquirir conocimiento aunque no es la única. En ocasiones cuando el observador es un experto del negocio (o del problema), el proceso puede ser a la inversa y comenzar el análisis en una base teórica abstracta, sustentada por el conocimiento previo que da lugar primeramente a clases de objetos que satisfagan las necesidades de la solución. Estos conceptos son parte de la base teórica de la idea de objeto y clase utilizados en la POO. Los objetos tienen características fundamentales que nos permiten conocerlos mediante la observación, identificación y el estudio posterior de su comportamiento; estas características son: • Identidad • Comportamiento • Estado En las ramas de las ciencias de la computación más estrictamente matemáticas, el término objeto es usado en sentido puramente matemático para referirse a cualquier "cosa". Esta interpretación resulta útil para discutir sobre teorías
7
Objeto (programación) abstractas, pero no es suficientemente concreta para servir como definición de un tipo primitivo en discusiones de ramas más específicas como en la programación, que está más cerca de cálculos reales y el procesamiento de información.
Identidad La identidad es la propiedad que permite a un objeto diferenciarse de otros. Generalmente esta propiedad es tal, que da nombre al objeto. Tomemos por ejemplo el "verde" como un objeto concreto de una clase color ; la propiedad que da identidad única a este objeto es precisamente su "color" verde. Tanto es así que para nosotros no tiene sentido usar otro nombre para el objeto que no sea el valor de la propiedad que lo identifica. En programación la identidad de los objetos sirve para comparar si dos objetos son iguales o no. No es raro encontrar que en muchos lenguajes de programación la identidad de un objeto esté determinada por la dirección de memoria de la computadora en la que se encuentra el objeto, pero este comportamiento puede ser variado redefiniendo la identidad del objeto a otra propiedad.
Comportamiento El comportamiento de un objeto está directamente relacionado con su funcionalidad y determina las operaciones que este puede realizar o a las que puede responder ante mensajes enviados por otros objetos. La funcionalidad de un objeto está determinada, primariamente, por su responsabilidad. Una de las ventajas fundamentales de la POO es la reusabilidad del código; un objeto es más fácil de reutilizarse en tanto su responsabilidad sea mejor definida y más concreta. Una tarea fundamental a la hora de diseñar una aplicación informática es definir el comportamiento que tendrán los objetos de las clases involucradas en la aplicación, asociando la funcionalidad requerida por la aplicación a las clases adecuadas.
Estado El estado de un objeto se refiere al conjunto de los valores de sus atributos en un instante de tiempo dado. El comportamiento de un objeto puede modificar el estado de este. Cuando una operación de un objeto modifica su estado se dice que esta tiene "efecto colateral". Esto tiene especial importancia en aplicaciones que crean varios hilos de ejecución. Si un objeto es compartido por varios hilos y en el transcurso de sus operaciones estas modifican el estado del objeto, es posible que se deriven errores del hecho de que alguno de los hilos asuma que el estado del objeto no cambiará (Véase Condición de carrera).
Representación en las computadoras Los objetos aunque son entidades conceptuales, dado el diseño de las computadoras, se corresponde directamente con bloques de memoria de tamaño y localización específicos. Esto ocurre porque los cálculos y el procesamiento de la información en última instancia requieren de una representación en la memoria de la computadora. En este sentido, los objetos son primitivas fundamentales necesarias para definir de forma precisa conceptos como referencias, variables y vinculación de nombres. En ciencias de la computación se utiliza cotidianamente la interpretación más concreta de objeto en lugar de las más abstractas sin que esto sea considerado un error. Es preciso hacer notar que aunque un bloque de memoria puede aparecer contiguo en un nivel de abstracción y no contiguo en otro, lo importante es que este aparece contiguo para el programa, quien lo trata como un objeto. Por este motivo, los detalles de implementación privados de un modelo de objetos, no deben ser expuestos al cliente del objeto, y estos pueden ser cambiados sin que se requieran cambios al código cliente.
8
Objeto (programación) Los objetos en la computadora existen entonces, sólo dentro de contextos capaces de reconocerlos; un espacio de memoria sólo contiene un objeto si un programa lo trata como tal (por ejemplo, reservándolo para uso exclusivo de un procedimiento específico y/o asociándole un tipo de dato). Así, el tiempo de vida de un objeto es el tiempo durante el cual este es tratado como un objeto. Es por esto que los objetos son entidades conceptuales, a pesar de su presencia física en la memoria de la computadora. En otras palabras, los conceptos abstractos que no ocupen espacio de memoria en tiempo de ejecución, no son, de acuerdo con esta definición, objetos. Ejemplos de estos conceptos son: patrones de diseño exhibidos por un conjunto de clases y tipos de datos en lenguajes de programación que utilizan tipos estáticos. Se llama objeto fantasma a un objeto que no es referenciado en un programa, y que por tanto no sirve a ningún propósito. En un lenguaje que posea un recolector de basura, este marcará la memoria ocupada por el objeto como libre, aunque ésta todavía contendrá los datos del objeto hasta el momento que sea reescrita.
Objetos en la programación orientada a objetos En programación orientada a objetos (POO), una instancia de programa (por ejemplo un programa ejecutándose en una computadora) es tratado como un conjunto dinámico de objetos interactuando entre sí. Los objetos en la POO extienden la noción más general descrita en secciones anteriores para modelar un tipo muy específico que está definido fundamentalmente por: 1. atributos que representan los datos asociados al objeto, o lo que es lo mismo sus propiedades o características. Los atributos y sus valores en un momento dado, determinan el estado de un objeto. 2. métodos que acceden a los atributos de una manera predefinida e implementan el comportamiento del objeto. Los atributos y métodos de un objetos están definidos por su clase, aunque (en un lenguaje dinámico como Python o Ruby) una instancia puede poseer atributos que no fueron definidos en su clase. Algo similar ocurre con los métodos, una instancia puede contener métodos que no estén definidos en su clase de la misma manera una clase puede declarar ciertos métodos como "métodos de clase", y estos (en dependencia del lenguaje) podrán estar o no presentes en la instancia. En el caso de la mayoría de los objetos, los atributos solo pueden ser accedidos a través de los métodos, de esta manera es más fácil garantizar que los datos permanecerán siempre en un estado bien definido (Invariante de Clase). En un lenguaje en el que cada objeto es creado a partir de una clase, un objeto es llamado una instancia de esa clase. Cada objeto pertenece a un tipo y dos objetos que pertenezcan a la misma clase tendrán el mismo tipo de dato. Crear una instancia de una clase es entonces referido como instanciar la clase. En casi todos los lenguajes de programación orientados a objeto, el operador "punto" (.) es usado para referirse o "llamar" a un método particular de un objeto. Un ejemplo de lenguaje que no siempre usa este operador el C++ ya que para referirse a los métodos de un objeto a través de un puntero al objeto se utiliza el operador (->). Consideremos como ejemplo una clase aritmética llamada Aritmética. Esta clase contiene métodos como "sumar", "restar", "multiplicar", "dividir", etc. que calculan el resultado de realizar estas operaciones sobre dos números. Un objeto de esta clase puede ser utilizado para calcular el producto de dos números, pero primeramente sería necesario definir dicha clase y crear un objeto. En las secciones a continuación se muestra como hacer esto utilizando dos lenguajes de programación: C++ y Python.
9
Objeto (programación)
10
Declaración de una clase Esta clase podría ser definida de la siguiente manera en C++: class Aritmetica
{ public: inline int sumar (int a, int b) const
{ return a + b;
} inline int restar (int a, int b) const
{ return a - b;
} inline float multiplicar (int a, int b) const
{ return a * b;
} inline float dividir (int a, int b) const
{ return a / b;
} };
o como sigue en Python: class Aritmetica: def sumar(self, a, b): return a + b def restar(self, a, b): return a - b def multiplicar(self, a, b): return a * b def dividir(self, a, b): return a / b
Objeto (programación)
Instanciación de una clase en un objeto Para crear un objeto de tipo 'Aritmetica' ( instanciar a Aritmetica) en C++ se haría de la siguiente forma: Aritmetica calculador = Aritmetica(); #Otra manera usando punteros Aritmetica* calculador1 = new Aritmetica();
la misma operación usando python sería así: calculador = Aritmetica()
Operando con un objeto Una vez que tenemos un objeto de 'Aritmetica', podemos usarlo para realizar cálculos sobre dos números. En C++ contamos con dos objetos de ejemplo: "calculador" y "calculador1", en esta última variable en realidad hemos almacenado la dirección de memoria del objeto creado. En este lenguaje esto sienta diferencias a la hora de utilizar el objeto. Para calcular la suma entre 78 y 69 usando un objeto "calculador" necesitaríamos un código como el siguiente en C++: int resultado = 0; resultado = calculador.sumar(78, 69); #Otra manera usando punteros resultado = calculador1->sumar(78, 69);
ahora usando Python para sumar dos números con el objeto calculador: resultado = calculador.sumar(78, 69)
Otro ejemplo del mundo real de un objeto podría ser "mi perro", el cual es una instancia de un tipo (una clase) llamada "perro", la que es una subclase de la clase "animal". En el caso de un objeto polimórfico, algunos detalles de su tipo pueden ser ignorados, por ejemplo el objeto "mi perro" puede ser usado en un método que espera recibir un "animal". También podría usarse un objeto "gato", puesto que esta también pertenece a la clase "animal". Pero mientras es accedido como un "animal", algunos atributos de un "perro" o un "gato" permanecerán no disponibles, como la "cola", porque no todos los animales tienen colas. Atributos dinámicos en objetos Python y C++ son lenguajes con características muy diferentes. Python utiliza un sistema de tipos dinámico y C++ en cambio, uno estático o estricto. El sistema de tipos usado en Python permite al programador agregar atributos a una instancia que no han sido definidos en la clase que le dio origen, cosa que no es posible hacer en un lenguaje como C++, por ejemplo: La clase siguiente en Python no define ningún atributo: class Prueba(object): pass
pero es posible hacer lo siguiente: 1: p = Prueba() 2: p.unNumero = 3 3: print("Atributo unNumero de p = %s" % p.unNumero) 4: Atributo unNumero de p = 3
11
Objeto (programación) A la instancia de p creada en la línea 1, le es asignado en la línea 2 el valor "3", lo cual crea un atributo de nombre unNumero en p de tipo "int" para almacenar el número 3.
Relaciones entre objetos Como ya se ha dicho antes, un sistema orientado a objetos está caracterizado por objetos que interactúan entre si. Estas interacciones supone ciertos tipos de relaciones entre los objetos del sistema. La semántica que expresa un objeto en el sistema está determinado en primer lugar, por las relaciones que éste establece con otros objetos o conjunto de objetos. Tomemos como ejemplo un objeto fecha, del que sin establecer ningún tipo de relación, podría decirse que significa un día del año particular. Pero si relacionamos ese objeto fecha con un objeto Persona de manera que represente la fecha en que esa persona nació, en ese contexto dado, el mismo objeto fecha adoptaría un significado diferente, el de un cumpleaños, aunque sigue siendo una fecha, ahora tiene otra idea asociada. Las relaciones entre objetos no solo están definidas por los objetos que participan y la circunstancia que los relaciona, sino también por la cantidad de objetos (cardinalidad de la relación) y la dirección de la misma. Una relación puede tener cardinalidad: • uno a uno, ejemplo: un auto tiene un motor • uno a muchos, ejemplo: un auto tiene muchas ruedas • muchos a muchos, ejemplo: un auto se puede servir en muchas gasolineras y una gasolinera puede servir a muchos autos. y direccionalidad: • unidireccional, ejemplo: un auto tiene cuatro ruedas. • bidireccional Las relaciones entre objetos más generales son las siguientes:
Composición La composición (también conocida como relación asociativa) es un tipo de relación que se establece entre dos objetos que tienen comunicación persistente. Se utiliza para expresar que un par de objetos tienen una relación de dependencia para llevar a cabo su función, de modo que uno de los objetos involucrados está compuesto por el otro. De manera práctica, es posible reconocer asociatividad entre dos objetos A y B si la proposición "A tiene un B" (o viceversa) es verdadera. Por ejemplo: "una computador tiene un disco duro" es verdadero, por tanto un objeto computador tiene una relación de composición con al menos un objeto disco duro.
Uso Un objeto usa (conoce) a otro cuando puede enviarle mensajes, por ejemplo, para requerir de este algún servicio. La composición puede verse como un caso particular de esta relación.
Delegación En ocasiones para lograr flexibilidad de diseño, un objeto es implementado de forma tal que este delegue parte de su funcionalidad en otro objeto. Esto es muy común en aplicaciones que hacen uso de interfaces gráficas de usuario, en las que los controles gráficos generales delegan la acción que se ejecutará ante determinado estímulo en otro objeto.
12
Objeto (programación)
13
Objetos especializados Algunos términos para tipos especializados de objetos son: • Singleton: Un objeto del que solo puede existir una única instancia de su clase durante el tiempo de vida del programa. • Functor : un objeto que puede ser utilizado como una función. • Objeto inmutable: un objeto creado con un estado fijo y que no puede variar en el tiempo de vida del mismo. • Objeto de primera clase: un objeto que puede ser utilizado sin restricciones. • Contenedor: un objeto que contiene a otros objetos. • Fábrica de objetos: un objeto cuyo propósito es crear otros objetos. • Metaobjeto: un objeto a partir del cual se pueden crear otros objetos (comparable con una clase, la que no necesariamente es un objeto) • Prototipo: un metaobjeto especializado a partir del cual se pueden crear otros objetos copiándolo. • Objeto todopoderoso: un objeto que sabe mucho o hace mucho. Este es un ejemplo de antipatrón de diseño. • Antiobjeto: una metáfora computacional útil para conceptualizar y solucionar problemas complejos, usualmente con aproximaciones paralelas.
Herencia (informática) En orientación a objetos la herencia es, después de la agregación o composición, el mecanismo más utilizado para alcanzar algunos de los objetivos más preciados en el desarrollo de software como lo son la reutilización y la extensibilidad. A través de ella los diseñadores pueden crear nuevas clases partiendo de una clase o de una jerarquía de clases preexistente (ya comprobadas y verificadas) evitando con ello el rediseño, la modificación y verificación de la parte ya implementada. La herencia facilita la creación de objetos a partir de otros ya existentes e implica que una subclase obtiene todo el comportamiento (métodos) y eventualmente los atributos (variables) de su superclase. Es la relación entre una clase general y otra clase más especifica. Por ejemplo: Si declaramos una clase párrafo derivada de una clase texto, todos los métodos y variables asociadas con la clase texto, son automáticamente heredados por la subclase párrafo. La herencia es uno de los mecanismos de los lenguajes de programación orientada a objetos basados en clases, por medio del cual una clase se deriva de otra de manera que extiende su funcionalidad. La clase de la que se hereda se suele denominar clase base, clase padre, superclase, clase ancestro (el vocabulario que se utiliza suele depender en gran medida del lenguaje de programación). En los lenguajes que cuentan con un sistema de tipos fuerte y estrictamente restrictivo con el tipo de datos de las variables, la herencia suele ser un requisito fundamental para poder emplear el Polimorfismo, al igual que un mecanismo que permita decidir en tiempo de ejecución qué método debe invocarse en respuesta a la recepción de un mensaje, conocido como enlace tardío (late binding) o enlace dinámico (dynamic binding).
Ejemplo en Java public class Mamifero{ private int patas; private String nombre; public void imprimirPatas(){
JOptionPane.showMessageDialog(null," Tiene " + patas + "patas\n","Mamifero", JOptionPane.INFORMATION_MESSAGE); }
Herencia (informática)
14
public Mamifero(String nombre, int patas){ this.nombre = nombre; this.patas = patas;
} } public class Perro extends Mamifero { public Perro(String nombre){ super(nombre, 4);
} } public class Gato extends Mamifero { public Gato(String nombre){ super(nombre, 4);
} } public class CrearPerro { public static void main(String [] args) {
Perro perrito = new Perro("Pantaleon"); perrito.imprimirPatas();
/*Está en la clase mamífero*/
} }
Se declaran las clases mamíferos, gato y perro, haciendo que gato y perro sean unos mamíferos (derivados de esta clase), y se ve como a través de ellos se nombra al animal pero así también se accede a patas dándole el valor por defecto para esa especie. Es importante destacar tres cosas. La primera, es que la herencia no es un mecanismo esencial en el paradigma de programación orientada a objetos; en la mayoría de los lenguajes orientados a objetos basados en prototipos las clases no existen, en consecuencia tampoco existe la herencia y el polimorfismo se logra por otros medios. La segunda, es que el medio preferido para lograr los objetivos de extensibilidad y reutilización es la agregación o composición. La tercera, es que en lenguajes con un sistema de tipos débiles, el polimorfismo se puede lograr sin utilizar la herencia. Por otra parte y aunque la herencia no es un concepto indispensable en el paradigma de programación orientada a objetos, es mucho más que un mecanismo de los lenguajes basados en clases, porque implica una forma de razonar sobre cómo diseñar ciertas partes de un programa. Es decir, no sólo es un mecanismo que permite implementar un diseño, sino que establece un marco conceptual que permite razonar sobre cómo crear ese diseño.
Clase Abstracta La herencia permite que existan clases que nunca serán instanciadas directamente. En el ejemplo anterior, una clase "perro" heredaría los atributos y métodos de la clase "mamífero", así como también "gato", "delfín" o cualquier otra subclase; pero, en ejecución, no habrá ningún objeto "mamífero" que no pertenezca a alguna de las subclases. En ese caso, a una clase así se la conocería como Clase Abstracta. La ausencia de instancias específicas es su única particularidad, para todo lo demás es como cualquier otra clase.
Herencia (informática)
Herencia y ocultación de información En ciertos lenguajes, el diseñador puede definir qué variables de instancia y métodos de los objetos de una clase son visibles. En C++ y java esto se consigue con las especificaciones private, protected y public. Sólo las variables y métodos definidos como públicos en un objeto serán visibles por todos los objetos. En otros lenguajes como Smalltalk, todas las variables de instancia son privadas y todos los métodos son públicos. Dependiendo del lenguaje que se utilice, el diseñador también puede controlar qué miembros de las superclases son visibles en las subclases. En el caso de java y C++ los especificadores de acceso (private, protected, public) de los miembros de la superclase afectan también a la herencia: • Private: ningún miembro privado de la superclase es visible en la subclase. • Protected: los miembros protegidos de la superclase son visibles en la subclase, pero no visibles para el exterior. • Public: los miembros públicos de la superclase siguen siendo públicos en la subclase.
Redefinición de métodos En la clase derivada se puede redefinir algún método existente en la clase base, con el objeto de proveer una implementación diferente. Para redefinir un método en la subclase, basta con declararlo nuevamente con la misma signatura (nombre y parámetros). Si se invoca un cierto método de un objeto que no está definido en su propia clase, se dispara la búsqueda hacia arriba en la jerarquía a la que dicha clase pertenece. Sin embargo, si existieran dos métodos con la misma signatura, uno en la clase y otro en una superclase, se ejecutaría el de la clase, no el de la superclase. Cuando se redefine un método en una clase es posible acceder explícitamente al método original de su superclase, mediante una sintaxis específica que depende del lenguaje de programación empleado (en muchos lenguajes se trata de la palabra clave super ).
Ventajas • Ayuda a los programadores a ahorrar código y tiempo, ya que la clase padre ha sido implementada y verificada con anterioridad, restando solo referenciar desde la clase derivada a la clase base (que suele ser extends, inherits, subclass u otras palabras clave similares, dependiendo del lenguaje). • Los objetos pueden ser construidos a partir de otros similares. Para ello es necesario que exista una clase base (que incluso puede formar parte de una jerarquía de clases más amplia). • La clase derivada hereda el comportamiento y los atributos de la clase base, y es común que se le añada su propio comportamiento o que modifique lo heredado. • Toda clase pueden servir como clase base para crear otras.
Estereotipos de herencia • Herencia simple: Una clase sólo puede heredar de una clase base y de ninguna otra. • Herencia múltiple: Una clase puede heredar las características de varias clases base, es decir, puede tener varios padres. En este aspecto hay discrepancias entre los diseñadores de lenguajes. Algunos de ellos han preferido no admitir la herencia múltiple debido a que los potenciales conflictos entre métodos y variables con igual nombre, y eventualmente con comportamientos diferentes crea un desajuste cognitivo que va en contra de los principio de la programación orientada a objetos. Por ello, la mayoría de los lenguajes orientados a objetos admite herencia simple. En contraste, algunos pocos lenguajes admiten herencia múltiple, entre ellos: C++, Python, PHP, Eiffel, mientras que Smalltalk, Java, Ada y C# sólo permiten herencia simple.
15
Abstracción (informática)
Abstracción (informática) La abstracción consiste en aislar un elemento de su contexto o del resto de los elementos que lo acompañan. En programación, el término se refiere al énfasis en el "¿qué hace?" más que en el "¿cómo lo hace?" (característica de caja negra). El común denominador en la evolución de los lenguajes de programación, desde los clásicos o imperativos hasta los orientados a objetos, ha sido el nivel de abstracción del que cada uno de ellos hace uso. Los lenguajes de programación son las herramientas mediante las cuales los diseñadores de lenguajes pueden implementar los modelos abstractos. La abstracción ofrecida por los lenguajes de programación se puede dividir en dos categorías: abstracción de datos (pertenecientes a los datos) y abstracción de control (perteneciente a las estructuras de control). Los diferentes paradigmas de programación han aumentado su nivel de abstracción, comenzando desde los lenguajes de máquina, lo más próximo al ordenador y más lejano a la comprensión humana; pasando por los lenguajes de comandos, los imperativos, la orientación a objetos (OO), la Programación Orientada a Aspectos (POA); u otros paradigmas como la programación declarativa, etc. La abstracción encarada desde el punto de vista de la programación orientada a objetos expresa las características esenciales de un objeto, las cuales distinguen al objeto de los demás. Además de distinguir entre los objetos provee límites conceptuales. Entonces se puede decir que la encapsulación separa las características esenciales de las no esenciales dentro de un objeto. Si un objeto tiene más características de las necesarias los mismos resultarán difíciles de usar, modificar, construir y comprender. La misma genera una ilusión de simplicidad dado a que minimiza la cantidad de características que definen a un objeto. Durante años, los programadores se han dedicado a construir aplicaciones muy parecidas que resolvían una y otra vez los mismos problemas. Para conseguir que sus esfuerzos pudiesen ser utilizados por otras personas se creó la POO que consiste en una serie de normas para garantizar la interoperabilidad entre usuarios de manera que el código se pueda reutilizar.
Uso A grandes rasgos, la abstracción, permite que dispongamos de las características de un objeto que necesitemos. Si necesitamos el objeto Persona, podríamos poner nombre, edad, dirección, estado civil, etc. Si lo necesitamos en un sistema administrativo, pero, si lo requerimos para el área de biología, dentro de sus atributos quizá tengamos, ADN, RND, Gen x1, Gen x2, etc. Y los atributos antes mencionados no sean requeridos. En general, podemos decir que Persona cuenta con todos los atributos mencionados aquí, pero, por el proceso de abstracción excluimos todos aquellos, que no tiene cabida en nuestro sistema. Se define como un metodo por el cual rescata los datos relevantes e ignora los datos irrelevantes.
Ejemplo Pensar en términos de objetos es muy parecido a cómo lo haríamos en la vida real. Una analogía sería modelizar un coche en un esquema de POO. Diríamos que el coche es el elemento principal que tiene una serie de características, como podrían ser el color, el modelo o la marca. Además tiene una serie de funcionalidades asociadas, como pueden ser ponerse en marcha, parar o aparcar. En un esquema POO el coche sería el objeto, las propiedades serían las características como el color o el modelo y los métodos serían las funcionalidades asociadas como ponerse en marcha o parar. Por poner otro ejemplo vamos a ver cómo modelizaríamos en un esquema POO una fracción, es decir, esa estructura matemática que tiene un numerador y un denominador que divide al numerador, por ejemplo 3/2. La fracción será el
16
Abstracción (informática) objeto y tendrá dos propiedades, el numerador y el denominador. Luego podría tener varios métodos como simplificarse, sumarse con otra fracción o número, restarse con otra fracción, etc. Estos objetos son utilizables en los programas, por ejemplo en un programa de matemáticas se puede hacer uso de objetos fracción y en un programa que gestione un taller de coches, objetos coche. Los programas orientados a objetos utilizan muchos objetos para realizar las acciones que se desean realizar y ellos mismos también son objetos. Es decir, el taller de coches será un objeto que utilizará objetos coche, herramienta, mecánico, recambios, etc.
Polimorfismo (informática) En programación orientada a objetos el polimorfismo se refiere a la posibilidad de enviar un mensaje a un grupo de objetos cuya naturaleza puede ser heterogénea. El único requisito que deben cumplir los objetos que se utilizan de manera polimórfica es saber responder al mensaje que se les envía. La apariencia del código puede ser muy diferente dependiendo del lenguaje que se utilice, más allá de las obvias diferencias sintácticas. Por ejemplo, en un lenguaje de programación que cuenta con un sistema de tipos dinámico (en los que las variables pueden contener datos de cualquier tipo u objetos de cualquier clase) como Smalltalk no se requiere que los objetos que se utilizan de modo polimórfico sean parte de una jerarquía de clases. En lenguajes basados en clases y con un sistema de tipos de datos fuerte (independientemente si la verificación se realiza en tiempo de compilación o de ejecución), es posible que el único modo de poder utilizar objetos de manera polimórfica sea que compartan una raíz común, es decir, una jerarquía de clases, ya que esto proporciona la compatibilidad de tipos de datos necesaria para que sea posible utilizar una misma variable de referencia (que podrá apuntar a objetos de diversas subclases de dicha jerarquía) para enviar el mismo mensaje (o un grupo de mensajes) al grupo de objetos que se tratan de manera polimórfica. En Java, es frecuente y profusamente aconsejada la utilización de interfaces (que es un mecanismo del lenguaje que se emplea por medio de la palabra clave Interface) para proveer la necesaria concordancia de tipos para hacer posible el polimorfismo, también como un contrato que debe cumplir cualquier clase que implemente una cierta interfaz y como una forma de documentación para los desarrolladores. A veces, en la literatura que refiere específicamente a Java se hace mención a "herencia y polimorfismo de interfaces", lo que no concuerda con los conceptos de la programación orientada a objetos porque una clase que implementa una interfaz sólo obtiene su tipo de datos y la obligación de implementar sus métodos, no obtiene comportamiento ni de atributos. Esto muchas veces resulta paradójico porque en Java frecuentemente se utiliza la mal llamada "herencia de interfaces" para dotar a una clase con un tipo adicional (o varios) para que su uso en combinación con la agregación (colaboración o composición) permita evitar la necesidad de la herencia múltiple y favorezca una utilización más amplia del polimorfismo. No obstante, el uso de una jerarquía de clases como paso previo, es muy habitual incluso en aquellos lenguajes en los que es posible prescindir de tal jerarquía, ya que, desde una perspectiva conceptual, se puede decir que al pertenecer los "objetos polimórficos" a subclases de una misma jerarquía, se asegura la equivalencia semántica de los mensajes que se invocarán de modo polimórfico. Por esto, en programación orientada a objetos a veces se denomina al polimorfismo como "polimorfismo de subclase (o de subtipo)". En resumen, en la programación orientada a objetos, la esencia del polimorfismo no atañe a la clase o prototipo de la que provienen los objetos. Aún así, en los lenguajes basados en clases, es habitual (y en algunos tal vez sea el único modo) que dichos objetos pertenezcan a subclases pertenecientes a una misma jerarquía. Entonces, el polimorfismo debe verse como una forma flexible de usar un grupo de objetos (como si fueran sólo uno). Podría decirse que el polimorfismo en esencia refiere al comportamiento de los objetos, no a su pertenencia a una jerarquía de clases (o a sus tipos de datos).
17
Polimorfismo (informática) Lo anterior se hace aún más evidente en lenguajes de programación orientada a objetos basados en prototipos, como Self, en los que las clases no existen. Además, es importante remarcar que si un cierto grupo de objetos pueden utilizarse de manera polimórfica es porque, en última instancia, todos ellos saben responder a un cierto mensaje (o a varios), pero dado que esos mismos objetos generalmente contendrán otros métodos (que otros objetos en dicho grupo no contienen), difícilmente se pueda decir lisa y llanamente que los objetos son polimórficos; lo correcto es decir que esos objetos se pueden utilizar de modo polimórfico para un cierto conjunto de mensajes. Un ejemplo. Podemos crear dos clases distintas: Pez y Ave que heredan de la superclase Animal. La clase Animal tiene el método abstracto mover que se implementa de forma distinta en cada una de las subclases (peces y aves se mueven de forma distinta). Entonces, un tercer objeto puede enviar el mensaje mover a un grupo de objetos Pez y Ave por medio de una variable de referencia de clase Animal, haciendo así un uso polimórfico de dichos objetos respecto del mensaje mover . El concepto de polimorfismo, desde una perspectiva más general, se puede aplicar tanto a funciones como a tipos de datos. Así nacen los conceptos de funciones polimórficas y tipos polimórficos. Las primeras son aquellas funciones que pueden evaluarse o ser aplicadas a diferentes tipos de datos de forma indistinta; los tipos polimórficos, por su parte, son aquellos tipos de datos que contienen al menos un elemento cuyo tipo no está especificado.
Clasificación Se puede clasificar el polimorfismo en dos grandes clases: • Polimorfismo dinámico (o polimorfismo paramétrico) es aquél en el que el código no incluye ningún tipo de especificación sobre el tipo de datos sobre el que se trabaja. Así, puede ser utilizado a todo tipo de datos compatible. • Polimorfismo estático (o polimorfismo ad hoc ) es aquél en el que los tipos a los que se aplica el polimorfismo deben ser explicitados y declarados uno por uno antes de poder ser utilizados. El polimorfismo dinámico unido a la herencia es lo que en ocasiones se conoce como programación genérica. También se clasifica en herencia por redefinición de métodos abstractos y por método sobrecargado. El segundo hace referencia al mismo método con diferentes parámetros. Otra clasificación agrupa los polimorfismo en dos tipos: Ad-Hoc que incluye a su vez sobrecarga de operadores y coerción, Universal (inclusión o controlado por la herencia, paramétrico o genericidad).
Ejemplo de polimorfismo En el siguiente ejemplo hacemos uso del lenguaje C++ para ilustrar el polimorfismo. Se observa a la vez el uso de las funciones virtuales puras, como se les conoce en C++, estas funciones constituyen una interfaz más consistente cuando se trabaja con una jerarquía de clases, puesto que hacen posible el enlace durante la ejecución. Sin embargo como se verá, para que el polimorfismo funcione no es una condición obligatoria que todas las funciones en la clase base sean declaradas como virtuales.
18
Polimorfismo (informática)
Diagrama de clases UML, que describe gráficamente la relación entre la clase base Figura y sus posibles clases derivadas, y la entidad que utiliza esta estructura: la Aplicación, también identificado como objeto Cliente. #include
using namespace std;
class Figura { private: float base; float altura; public: void captura(); virtual unsigned float perimetro()=0; virtual unsigned float area()=0;
};
class Rectangulo: public Figura { public: void imprime(); unsigned float perimetro(){ return 2*(base+altura);} unsigned float area(){return base*altura;} };
class Triangulo: public Figura { public: void muestra(); unsigned float perimetro(){ return 2*altura+base} unsigned float area(){return (base*altura)/2;} };
void Figura::captura() { cout << "CALCULO DEL AREA Y PERIMETRO DE UN TRIANGULO ISÓSCELES Y UN RECTANGULO:" << endl;
19
Polimorfismo (informática) cout << "escribe la altura: "; cin
>> altura;
cout << "escribe la base: "; cin
>> base;
cout << "EL PERIMETRO ES: " << perimetro(); cout << "EL AREA ES: " << area(); }
Polimorfismo desde una interfaz Aunque el polimorfismo es el mismo se aplique donde se aplique, el modo en que se aplica desde una interfaz puede resultar un poco más oscuro y difícil de entender. Se expone un sencillo ejemplo (en VB-NET) comentado para entender como funciona aplicado desde una interfaz, primero se escribe el código y luego se comenta el funcionamiento. Nota: para no enturbiar el código en exceso, todo lo que no se declara privado se sobreentiende público. ' Declaramos una interfaz llamada IOperar y declaramos una función llamada Operar ' que implementarán las clases deseadas: Interface IOperar Function Operar(valor1 as integer, valor2 as integer) as long End Interface
' Declaramos una clase que trabaja más alejada del usuario y que contendría funciones comunes ' para las siguiente clase, si no fueran idénticas irían en la interfaz, ' pero al caso de mostrar el polimorfismo se suponen idénticas: Class Operacion Function Calcular(clasellamante as Object) as Long
' aquí iría el código común a todas las operaciones.... que llaman a esa función ' por ejemplo recoger los 2 valores de la operación, chequear que están en el rango deseado, etc. ' se supone que la función inputValor recoge un valor de algún sitio valor1 as integer = inputValor() valor2 as integer = inputValor() op as New IOperar = clasellamante Return op.Operar(valor1,valor2) 'AQUÍ es donde se utiliza el
polimorfismo. End Function End Class
20
Polimorfismo (informática) ' Declaramos 2 clases: Sumar y Restar que implementan la interfaz y que llaman a la clase Operacion: Class Sumar Implements IOperar Private Function Operar(valor1 as Integer, valor2 as Integer) as
Long Implements IOperar.Operar Return valor1 + valor2 End Function Function Calcular() as Long
op as New operacion Return op.Calcular( Me) ' se está llamando a la función
'Calcular' de la clase 'Operación' End Function End Class
' segunda clase.... Class Restar Implements IOperar Private Function Operar(valor1 as Integer, valor2 as Integer) as
Long Implements IOperar.Operar Return valor1 - valor2 End Function Function Calcular() as Long
op as New operacion Return op.Calcular( Me) ' se está llamando a la función
'Calcular' de la clase 'Operación' End Function End Class
Analicemos ahora el código para entender el polimorfismo expuesto en la interfaz: La interfaz expone un método que puede ser implementado por las diferentes clases, normalmente relacionadas entre si. Las clases Sumar y Restar implementan la interfaz pero el método de la interfaz lo declaramos privado para evitar ser accedido libremente y además tienen un método llamado Calcular que llama a la clase Operacion donde tenemos otro método con el mismo nombre. Es esta clase última la que realiza el polimorfismo y debe fijarse como es a través de una instancia de la interfaz que llama al método operar. La interfaz sabe a qué método de qué clase llamar desde el momento que asignamos un valor a la variable OP en el método Calcular de la clase Operacion, que a su vez recibió la referencia del método Calcular desde la clase que la llama, sea ésta cual sea, se identifica a sí misma, mediante la referencia Me ó This según el lenguaje empleado. Debe notarse que la instancia de la interfaz accede a sus métodos aunque en sus clases se hayan declarado privadas.
21
Polimorfismo (informática)
Diferencias entre polimorfismo y sobrecarga El polimorfismo como se muestra en el ejemplo anterior, suele ser bastante ventajoso aplicado desde las interfaces, ya que permite crear nuevos tipos sin necesidad de tocar las clases ya existentes (imaginemos que deseamos añadir una clase Multiplicar), basta con recompilar todo el código que incluye los nuevos tipos añadidos. Si se hubiera recurrido a la sobrecarga durante el diseño exigiría retocar la clase anteriormente creada al añadir la nueva operación Multiplicar, lo que además podría suponer revisar todo el código donde se instancia a la clase. • La sobrecarga se da siempre dentro de una sola clase, mientras que el polimorfismo se da entre clases distintas. • Un método está sobrecargado si dentro de una clase existen dos o más declaraciones de dicho método con el mismo nombre pero con parámetros distintos, por lo que no hay que confundirlo con polimorfismo. • En definitiva: La sobrecarga se resuelve en tiempo de compilación utilizando los nombres de los métodos y los tipos de sus parámetros; el polimorfismo se resuelve en tiempo de ejecución del programa, esto es, mientras se ejecuta, en función de la clase a la que pertenece el objeto.
Encapsulamiento (informática) En Programación modular, y más específicamente en programación orientada a objetos, se denomina encapsulamiento al ocultamiento del estado, es decir, de los datos miembro, de un objeto de manera que sólo se puede cambiar mediante las operaciones definidas para ese objeto. Cada objeto está aislado del exterior, es un módulo natural, y la aplicación entera se reduce a un agregado o rompecabezas de objetos. El aislamiento protege a los datos asociados a un objeto contra su modificación por quien no tenga derecho a acceder a ellos, eliminando efectos secundarios e interacciones. De esta forma el usuario de la clase puede obviar la implementación de los métodos y propiedades para concentrarse sólo en cómo usarlos. Por otro lado se evita que el usuario pueda cambiar su estado de maneras imprevistas e incontroladas.
Encapsulamiento Como se puede observar de los diagramas, las variables del objeto se localizan en el centro o núcleo del objeto. Los métodos rodean y esconden el núcleo del objeto de otros objetos en el programa. 'Al empaquetamiento de las variables de un objeto con la protección de sus métodos se le llama encapsulamiento . Típicamente, el encapsulamiento es utilizado para esconder detalles de la puesta en práctica no importantes de otros objetos. Entonces, los detalles de la puesta en práctica pueden cambiar en cualquier tiempo sin afectar otras partes del programa. El encapsulamiento de variables y métodos en un componente de software ordenado es, todavía, una simple idea poderosa que provee dos principales beneficios a los desarrolladores de software: El encapsulamiento consiste en unir en la Clase las características y comportamientos, esto es, las variables y métodos. Es tener todo esto en una sola entidad. En los lenguajes estructurados esto era imposible. Es evidente que el encapsulamiento se logra gracias a la abstracción y el ocultamiento que veremos a continuación. La utilidad del encapsulamiento va por la facilidad para manejar la complejidad, ya que tendremos a las Clases como cajas negras donde sólo se conoce el comportamiento pero no los detalles internos, y esto es conveniente porque lo que nos interesará será conocer qué hace la Clase pero no será necesario saber cómo lo hace. La encapsulación da lugar a que las clases se dividan en dos partes: 1. Interfaz: Captura la visión externa de una clase, abarcando la abstracción del comportamiento común a los ejemplos de esa clase.
22
Encapsulamiento (informática) 2. Implementación: Comprende la representación de la abstracción, así como los mecanismos que conducen al comportamiento deseado.
Formas de encapsular 1. Estándar (Predeterminado) 2. Abierto : Hace que el miembro de la clase pueda ser accedido desde el exterior de la Clase y cualquier parte del programa. 3. Protegido : Solo es accesible desde la Clase y las clases que heredan (a cualquier nivel). 4. Semi cerrado : Solo es accesible desde la clase heredada 5. Cerrado : Solo es accesible desde la Clase. En el encapsulamiento hay analizadores que pueden ser semánticos y sintácticos.
Método (informática) En la programación orientada a objetos, un método es una subrutina asociada exclusivamente a una clase (llamados métodos de clase o métodos estáticos) o a un objeto (llamados métodos de instancia). Análogamente a los procedimientos en los lenguajes imperativos, un método consiste generalmente de una serie de sentencias para llevar a cabo una acción, un juego de parámetros de entrada que regularán dicha acción y o, posiblemente, un valor de salida (o valor de retorno) de algún tipo. Algunos lenguajes de programación asumen que un método debe de mantener el invariante del objeto al que está asociado asumiendo también que éste es válido cuando el método es invocado. En lenguajes compilados dinámicamente, los métodos pueden ser objetos de primera clase, y en este caso se puede compilar un método sin asociarse a ninguna clase en particular, y luego asociar el vínculo o contrato entre el objeto y el método en tiempo de ejecución. En cambio en lenguajes no compilados dinámicamente o tipados estáticamente, se acude a precondiciones para regular los parámetros del método y postcondiciones para regular su salida (en caso de tenerla). Si alguna de las precondiciones o postcondiciones es falsa el método genera una excepción. Si el estado del objeto no satisface la invariante de su clase al comenzar o finalizar un método, se considera que el programa tiene un error de programación. La diferencia entre un procedimiento (generalmente llamado función si devuelve un valor) y un método es que éste último, al estar asociado con un objeto o clase en particular, puede acceder y modificar los datos privados del objeto correspondiente de forma tal que sea consistente con el comportamiento deseado para el mismo. Así, es recomendable entender a un método no como una secuencia de instrucciones sino como la forma en que el objeto es útil (el método para hacer su trabajo). Por lo tanto, podemos considerar al método como el pedido a un objeto para que realice una tarea determinada o como la vía para enviar un mensaje al objeto y que éste reaccione acorde a dicho mensaje.
Tipos de métodos Como ya se mencionó, los métodos de instancia están relacionados con un objeto en particular, mientras que los métodos estáticos o de clase (también denominados métodos compartidos) están asociados a una clase en particular. En una implementación típica, a los métodos de instancia se les pasa una referencia oculta al objeto al que pertenecen, comúnmente denominada this o self (referencias a sí mismo por sus significados en inglés), para que puedan acceder a los datos asociados con el mismo. Un ejemplo típico de un método de clase sería uno que mantuviera la cuenta de la cantidad de objetos creados dentro de esa clase.
23
Método (informática) Los llamados métodos obtener y métodos establecer (en inglés get y set ) proveen un mecanismo para leer y modificar (respectivamente) los datos privados que se encuentran almacenados en un objeto o clase. Algunos lenguajes de programación requieren la definición de constructores, siendo estos métodos de instancia especiales llamados automáticamente cuando se crea una instancia de alguna clase. En Java y C ++ se distinguen por tener el mismo nombre de la clases a la que están asociados. Lenguajes como Smalltalk no requieren constructores ni destructores. Los métodos de acceso son un tipo de método normalmente pequeño y simple que se limita a proveer información acerca del estado de un objeto. Aunque introduce una nueva dependencia, la utilización de métodos es preferida a acceder directamente a la información para proveer de una nueva capa de abstracción (programación orientada a objetos). Por ejemplo, si una clase que modela una cuenta bancaria provee de un método de acceso "obtenerBalance()" en versiones posteriores de la clase se podría cambiar el código de dicho método substancialmente sin que el código dependiente de la clase tuviese que ser modificado (un cambio sería necesario siempre que el tipo de dato devuelto por el método cambie). Los métodos de acceso que pueden cambiar el estado de un objeto son llamados, frecuentemente, métodos de actualización ó métodos de mutación; a su vez, los objetos que proveen de dichos métodos son denominados objetos mutables.
Programación estructurada La programación estructurada es una técnica para escribir programas (programación de computadora) de manera clara. Para ello se utilizan únicamente tres estructuras: secuencia, selección e iteración; siendo innecesario el uso de la instrucción o instrucciones de transferencia incondicional (GOTO, EXIT FUNCTION, EXIT SUB o múltiples RETURN). Hoy en día las aplicaciones informáticas son mucho más ambiciosas que las necesidades de programación existentes en los años 1960, principalmente debido a las aplicaciones gráficas, por lo que las técnicas de programación estructurada no son suficientes. Ello ha llevado al desarrollo de nuevas técnicas, tales como la programación orientada a objetos y el desarrollo de entornos de programación que facilitan la programación de grandes aplicaciones.
Orígenes de la programación estructurada A finales de los años 1970 surgió una nueva forma de programar que no solamente daba lugar a programas fiables y eficientes, sino que además estaban escritos de manera que facilitaba su comprensión posterior. El teorema del programa estructurado, propuesto por Böhm-Jacopini, demuestra que todo programa puede escribirse utilizando únicamente las tres instrucciones de control siguientes: • Secuencia • Instrucción condicional. • Iteración (bucle de instrucciones) con condición al principio. Solamente con estas tres estructuras se pueden escribir todos los programas y aplicaciones posibles. Si bien los lenguajes de programación tienen un mayor repertorio de estructuras de control, éstas pueden ser construidas mediante las tres básicas citadas.
24
Programación estructurada
Estructura secuencial Una estructura de programa es secuencial si las instrucciones se ejecutan una tras otra, a modo de secuencia lineal, es decir que una instrucción no se ejecuta hasta que finaliza la anterior, ni se bifurca el flujo del programa. Ejemplo: INPUT x INPUT y auxiliar= x x= y y= auxiliar PRINT x PRINT y
Esta secuencia de instrucciones permuta los valores de x e y, con ayuda de una variable auxiliar, intermedia. • 1º Se guarda una copia del valor de x en auxiliar. • 2º Se guarda el valor de y en x, perdiendo su valor anterior, pero se mantiene una copia del contenido en auxiliar. • 3º Se copia a y el valor de auxiliar, que es el valor inicial de x. • El resultado es el intercambio de los valores entre x e y, en tres operaciones secuenciales.
Estructura selectiva o de selección La estructura selectiva permite que la ejecución del programa se bifurque a una instrucción (o conjunto) u otra/s, según un criterio o condición lógica establecida, sólo uno de los caminos en la bifurcación será el tomado para ejecutarse. Ejemplo: IF a > b THEN PRINT a ; " es mayor que " ; b ELSE PRINT a ; " no es mayor que " ; b END IF
La instrucción selectiva anterior puede presentar uno de dos mensajes: a es mayor que b o a no es mayor que b, según el resultado de la comparación entre a y b; si el resultado de a > b es verdadero, se presenta el primer mensaje, si es falso se exterioriza el segundo. Las palabras clave IF, THEN, ELSE, y END IF; constituyen la propia estructura de la instrucción condicional (palabra reservadas), proporcionada por el lenguaje, el usuario no debe utilizar sus nombres salvo para este fin. El caso ejemplo se ha codificado en BASIC. • IF señala el comienzo de la instrucción condicional, y se espera que después siga la condición lógica de control de la instrucción. • THEN señala el fin de la condición, y después estará la instrucción a ejecutar si la condición es verdadera. • ELSE es opcional, le sigue la instrucción que se ejecutará si la condición es falsa. • END IF indica el final de la estructura, luego de ésta el programa seguirá su curso. Ampliando un poco el ejemplo anterior, con estructuras anidadas: IF a > b THEN PRINT a ; " es mayor que " ; b ELSEIF a < b THEN PRINT a ; " es menor que " ; b ELSE
25
Programación estructurada PRINT a ; " es igual que " ; b END IF
Este ejemplo permite considerar situaciones en las que se tiene más de dos alternativas. En este caso se ha considerado tres, pero hay situaciones en las que deben considerarse más casos y para ellos se puede repetir las veces que sea necesario la opcional ELSEIF.
Estructura iterativa Un bucle iterativo o iteración de una secuencia de instrucciones, hace que se repita su ejecución mientras se cumpla una condición, el número de iteraciones normalmente está determinado por el cambio en la condición dentro del mismo bucle, aunque puede ser forzado o explícito por otra condición. Ejemplo: a= 0 b= 7 DO WHILE b > a PRINT a a= a + 1 LOOP
Esta instrucción tiene tres palabras reservadas WHILE, DO y LOOP. • DO WHILE: señala el comienzo del bucle ("haga mientras") y después de estas palabras se espera la condición lógica de repetición, si la condición es verdadera pasa el control al cuerpo del bucle, en caso contrario el flujo salta directamente al final de la estructura, saliendo de la misma. • LOOP: señala el final del cuerpo de la estructura de bucle. El bucle mientras, se repite mientras la condición sea verdadera, esta condición se comprueba o chequea antes de ingresar al cuerpo del bucle, por lo que el mismo puede que no se ejecute nunca (cuando la condición es falsa desde un principio) o bien que se repita tantas veces como resulte y mientras la condición sea cierta. En el ejemplo se tienen definidas dos variables a y b, que al iniciarse el bucle contienen los valores a=0 y b=7. La condición del bucle es b > a. Si a=0 y b=7. la condición es verdadera, en el cuerpo del bucle se escribe el valor de a en pantalla y luego se incrementa esa variable en una unidad. Entonces pasa a ser a=1 y b=7. ... (se repite la secuencia) ... Cuando a=6 y b=7. la condición sigue siendo verdadera, se escribe el valor de a en pantalla y se incrementa en una unidad. Cuando se llega a que a=7 y b=7. Entonces la condición ya resulta falsa y la instrucción WHILE finaliza, saliendo por LOOP. La salida por pantalla de este ejemplo es 0 1 2 3 4 5 6, y se iteró 7 veces. El lenguaje utilizado en el ejemplo (BASIC), además de tener otras del tipo iterativas, permite utilizar la misma estructura indicada, pero de la siguiente forma: a= 0 b= 7 WHILE b > a PRINT a a= a + 1
26
Programación estructurada WEND
Que es absolutamente análoga, en éste formato la palabra reservada WEND marca el fin del bucle y no se utiliza ni DO ni LOOP.
Anidamiento El cuerpo de cualquier estructura puede ser instrucciones simples u otras estructuras, que a su vez pueden contener a otras. Ejemplo: CLS INPUT "Valor entero para a:"; a INPUT "Valor entero para b:"; b IF a > b THEN REM hacer intercambio de variables auxiliar = a a = b b = auxiliar REM imprimir diferencia en escala de uno en uno DO WHILE auxiliar > a auxiliar = auxiliar - 1 PRINT auxiliar LOOP ELSE REM no hacer nada END IF PRINT PRINT a; b
En el ejemplo la sentencia o instrucción CLS sólo tiene el efecto de "limpiar" la pantalla al inicio de la ejecución del programa. Las instrucciones INPUT permiten que el operador ingrese, desde teclado y con un mensaje previo acorde, los valores deseados para las variables a y b. La instrucción no ejecutable REM permite la inserción de comentarios en cualquier parte del programa, donde el programador lo considere necesario; constituye una buena práctica de programación comentariar adecuadamente todos los programas, de este modo se clarifica notablemente su lectura, durante su desarrollo y posterior para modificación o mantenimiento. En la anterior estructura IF bien se puede omitir la porción ELSE, ya que no hay cuerpo allí, de modo que lo siguiente es completamente equivalente: CLS INPUT "Valor entero para a:"; a INPUT "Valor entero para b:"; b IF a > b THEN REM hacer intercambio de variables auxiliar = a a = b b = auxiliar REM imprimir diferencia en escala de uno en uno DO WHILE auxiliar > a auxiliar = auxiliar - 1
27
Programación estructurada PRINT auxiliar LOOP END IF PRINT PRINT a; b
Ventajas de la programación estructurada 1. Los programas son más fáciles de entender, pueden ser leídos de forma secuencial, no hay necesidad de hacer engorrosos seguimientos en saltos de línea (GOTO) dentro de los bloques de código para intentar entender la lógica. 2. La estructura de los programas es clara, puesto que las instrucciones están más ligadas o relacionadas entre sí. 3. Reducción del esfuerzo en las pruebas y depuración. El seguimiento de los fallos o errores del programa ("debugging") se facilita debido a su estructura más sencilla y comprensible, por lo que los errores se pueden detectar y corregir más fácilmente. 4. Reducción de los costos de mantenimiento. Análogamente a la depuración, durante la fase de mantenimiento, modificar o extender los programas resulta más fácil. 5. Programas son más sencillos y más rápidos de confeccionar (y se facilita su optimización). 6. Los bloques de código son casi auto-explicativos, lo que reduce y facilita la documentación. 7. Las instrucciones de salto, GOTO, quedan reservadas para construir las instrucciones básicas, si fuera realmente imprescindible. Aunque no se usan de forma directa, por estar prohibida su utilización, están incluidas implícitamente en las instrucciones de selección e iteración. 8. Un programa escrito de acuerdo a los principios de programación estructurada no solamente tendrá una mejor estructura sino también una excelente presentación. 9. Se incrementa el rendimiento de los programadores, comparada con la forma tradicional que utiliza GOTO. La programación estructurada ofrece estos beneficios, pero no se la debe considerar como una panacea ya que el desarrollo de programas es, esencialmente, una tarea de dedicación, esfuerzo y creatividad; programar es casi un arte.
Inconvenientes de la programación estructurada El principal inconveniente de este método de programación es que se obtiene un único bloque de programa, que cuando se hace demasiado grande puede resultar problemático el manejo de su código fuente; esto se resuelve empleando conjuntamente la programación modular, es decir, si es necesario, se definen módulos independientes, programados y compilados por separado (en realidad esto no es necesario, pero sí es recomendable para su mejor mantenimiento y depuración). En realidad, cuando se programa hoy en día (inicios del siglo XXI) se utilizan normalmente, tanto las técnicas de programación estructurada como las de programación modular, de forma conjunta y por lo tanto es muy común que cuando se hace referencia a la programación estructurada muchos entiendan que ella incluye también las técnicas modulares, estrictamente no es así. Un método un poco más sofisticado es la programación por capas, en la que los módulos tienen una estructura jerárquica en la que se pueden definir funciones dentro de funciones o de procedimientos. Si bien las metodologías en cuestión ya son de antigua data ("en plazos informáticos"), aun en la actualidad la conjunción "Programación estructurada" y "programación modular" es una de la más utilizadas, juntamente con un más moderno paradigma, en pleno auge, completamente distinto, llamado programación orientada a objetos.
28
Programación estructurada
Bibliografía 1. García-Bermejo Giner, José Rafael (2 de 2008) (en español). Programación estructurada en C (1 edición). Pearson Prentice Hall. ISBN 978-84-8322-423-6 . 2. Valls Ferrán, José María; Camacho Fernández, David (9 de 2004) (en español). Programación estructurada y algoritmos en Pascal (1 edición). Pearson Alhambra. ISBN 978-84-205-4246-1 . 3. (en español) Programación estructurada II (1 ed. 4 imp. edición). Enseñanza Técnica y Sistemas, S.A.. 6 de 2000. ISBN 978-84-85838-90-5 . 4. (en español) Pseudocódigos y programación estructurada (1 edición). Centro Técnico Europeo de Enseñanzas Profesionales. 2 de 1997. ISBN 978-84-8199-065-2 . 5. Sánchez Andrés, María Ángeles (5 de 1996) (en español). Programación estructurada y fundamentos de programación (1 edición). McGraw-Hill / Interamericana de España, S.A.. ISBN 978-84-481-0557-0 .
Enlaces externos • • • •
INTRO.pdf: Programación Estructurada [1] Tutoriales: Programación Estructurada [2] Lenguajes de programacion.com: Programación Estructurada [3] Monografias.com: Programación Estructurada [4]
Referencias [1] [2] [3] [4]
http:/ / delfosis.uam.mx/ ~sgb/ docs/ INTRO.pdf http:/ / sistemas.itlp.edu.mx/ tutoriales/ pascal/ u1_1_4.html http:/ / www.lenguajes-de-programacion. com/ programacion-estructurada. shtml http:/ / www.monografias.com/ trabajos/ progestructu/ progestructu.shtml
29
Simula
30
Simula Simula es un lenguaje de programación orientada a objetos (OOP). Fue el primero de los lenguajes orientado a objetos. Varios años después de su desarrollo, casi todos los lenguajes modernos comenzaron a utilizar sus principios de orientación a objetos. Así fue como se popularizaron términos como clases, objetos, instancias, herencia, polimorfismo, etc. Simula 67 fue lanzado oficialmente por sus autores Ole Johan Dahl y Kristen Nygaard en mayo de 1967, en la Conferencia de Trabajo en Lenguajes de Simulación IFIO TC 2, en L ysebu cerca de Oslo Hoy en día, los creadores de Simula han desarrollado un nuevo lenguaje de programación, llamado Beta, que generaliza todas las construcciones del lenguaje en una única idea denominada patrón.
¡Hola Mundo! Éste es el famoso programa "Hola Mundo" en Simula 67: ! esto es un comentario ; Begin comment aquí comienza el programa ; OutText("¡Hola Mundo!"); OutImage; End of program;
Clases y objetos Simula es un lenguaje orientado a objetos. Esto significa que el ejemplo de 'Hola Mundo' también se puede escribir instanciando una clase que se encarga de escribir el saludo. ! todo programa empieza con un begin y termina con un end ; Begin Class Saludos; Begin OutText("¡Hola Mundo!"); OutImage; End of class saludos; REF(Saludos) objeto;
objeto :- New Saludos; End of module program ;
Este programa también muestra "¡Hola Mundo!" . El mensaje está codificado en el bloque de código de la clase Saludos. Este bloque de código se ejecuta solamente cuando existe una instancia o variable de tipo Saludos; lo que ocurre efectivamente al crear una instancia por medio de la instrucción New. En Simula, los objetos siempre son manejados por medio de referencias. Existe un recolector de basura que se encarga de eliminar de la memoria los objetos que se han quedado sin referencias a ellos. Una de estas referencias la vemos con variable objeto. Utilizamos el operador :- para asignar referencias. A diferencia de muchos lenguajes modernos, Simula entiende de dos tipos de objetos.
Simula
31
Activos son aquellos objetos que aún no han completado su bloque asociado begin/end. begin/end. Inactivos por otra parte, han completado su bloque de instrucciones. Tanto de unos como de otros, es posible ejecutar los procedimientos miembro y consultar los atributos en cualquier momento. Dado que Simula 67 es un lenguaje ya un poco añejo, los conceptos que maneja son un poco distintos a los actualmente utilizados por la comunidad de programación orientada a objetos. Las instancias a las que estamos habituados corresponden a los objetos inactivos. En tanto que el bloque de instrucciones constituye una serie de constructores. En cuanto a los objetos activos, estos existen debido a una funcionalidad de pseudo-paralelismo encontrada en Simula y ausente en casi todos los lenguajes modernos. Esta funcionalidad recibe el nombre de co-rutina y es controlada directamente directamente por el lenguaje por medio de un grupo de palabras clave. Un grupo de objetos activos pueden coexistir en un mismo programa Simula, y transferir el control de unos a otros en cualquier momento. Esta funcionalidad es la base de las características de simulación que dan nombre al lenguaje.
Enlaces externos • Simula. Simula. Explicació Explicación, n, paso por paso, paso, de las caracterís característica ticass de este lenguaje. lenguaje. [1]
Referencias [1] [1] http http:/ :/ / staff.um. staff. um.edu. edu.mt/ mt/ jskl1/ talk.html talk. html
C++ C++
Desarrollador(es) Bjarne Stroustrup, Bell Labs Información general Extensiones comunes
.h .hh .hpp .hxx .h++ .cc .cpp .cxx .c++
Paradigma
multiparadigma: orientado a objetos, imperativo, programación genérica.
Apareció en
1983
Diseñado por
Bjarne Stroustrup
Última versión estable
ISO/IEC 14882:2011 (2011)
Última versión en pruebas
C++11
Tipo de dato
fuerte, estático, inseguro, nominativo
Implementaciones
C++ Builder, clang, Comeau C/C++, GCC, Intel C++ Compiler, Microsoft Visual C++, Sun Studio, Code::Blocks, Zinjai
Dialectos
ISO/IEC C++ 1998, ISO/IEC C++ 2003, ISO/IEC C++ 2011
Influido por
C, Simula, Ada 83, ALGOL 68, CLU, ML
[1]
C++
32
Ha influido a
Perl, LPC, Lua, Pike, Ada 95, Java, PHP, D, C99, C#, Falcon
Sistema operativo
Multiplataforma
C++ es un lenguaje de programación diseñado a mediados de los años 1980 por Bjarne Stroustrup. La intención de su creación fue el extender al exitoso lenguaje de programación C con mecanismos que permitan la manipulación de objetos. En ese sentido, desde el punto de vista de los lenguajes orientados a objetos, el C++ es un lenguaje híbrido. Posteriormente se añadieron facilidades de programación genérica, que se sumó a los otros dos paradigmas que ya estaban admitidos (programación estructurada y la programación orientada a objetos). Por esto se suele decir que el C++ es un lenguaje de programación multiparadigma. Actualmente existe un estándar, denominado ISO C++, al que se han adherido la mayoría de los fabricantes de compiladores más modernos. Existen también algunos intérpretes, tales como ROOT. Una particularidad del C++ es la posibilidad de redefinir los operadores, y de poder crear nuevos tipos que se comporten como tipos fundamentales. El nombre C++ fue propuesto por Rick Mascitti en el año 1983, cuando el lenguaje fue utilizado por primera vez fuera de un laboratorio científico. Antes se había usado el nombre "C con clases". En C++, la expresión "C++" significa "incremento "incremento de C" y se refiere a que C++ es una extensión de C.
Un ejemplo de programa en C++, el clásico Hola mundo A continuación se cita un programa de ejemplo Hola mundo escrito en C++: /* Esta cabecera permite usar los objetos que encapsulan los descriptores stdout y stdin: cout(<<) y cin(>>)*/ #include using namespace std;
int main() { cout << "Hola mundo" << endl; return 0;
}
Al usar la directiva #include estamos diciéndole al compilador que busque e interprete todos los elementos definidos en el archivo que acompaña la directiva (en este caso, iostream). Para evitar sobrescribir los elementos ya definidos al ponerles igual nombre, se crearon los espacios de nombres o namespace del singular en inglés. En este caso hay un espacio de nombres llamado std , que es donde se incluyen las definiciones de todas las funciones y clases que conforman la biblioteca estándar de C++. Al incluir la sentencia using namespace std le estamos diciendo al compilador que usaremos el espacio de nombres std por lo que no tendremos que incluirlo cuando usemos elementos de este espacio de nombres, como pueden ser los objetos cout y cin , que representan el flujo de salida estándar (típicamente la pantalla o una ventana de texto) y el flujo de entrada estándar (típicamente el teclado). La definición de funciones es igual que en C, salvo por la característica de que si main no va a recoger argumentos, no tenemos por qué ponérselos, a diferencia de C, donde había que ponerlos explícitamente, aunque no se fueran a usar. Queda solo comentar que el símbolo << se conoce como operador de inserción, y grosso modo está enviando a cout lo que queremos mostrar por pantalla para que lo pinte, en este caso la cadena "Hola mundo". El mismo operador << se puede usar varias veces en la misma sentencia, de forma que gracias a esta característica podremos concatenar el objeto endl al final, cuyo resultado será imprimir un retorno de línea.
C++
33 Por último tomaremos una secuencia de caracteres del teclado hasta el retorno de línea (presionando ENTER), llamando al método get del objeto cin .
Tipos de datos C++ tiene los siguientes tipos fundamentales: • • • • •
Car Caracter cteres es:: char (también es un entero), wchar_t Enteros: short, int , long, long long Núme Número ross en com comaa flot flotan ante te:: float, double, long double Boo Booleanos: bool Vacío: void
El modificador unsigned se puede aplicar a enteros para obtener números sin signo (por omisión los enteros contienen signo), con lo que se consigue un rango mayor de números naturales.
Tamaños asociados Tamaños de tipos primitivos bajo i386 (GCC) T ip o
Número de Bits
char
8
short
16
int
32
float
32
double 64
Según la máquina y el compilador que se utilice los tipos primitivos pueden ocupar un determinado tamaño en memoria. La siguiente lista ilustra el número de bits que ocupan los distintos tipos primitivos en la arquitectura x86. Otras arquitecturas pueden requerir distintos tamaños de tipos de datos primitivos. C++ no dice nada acerca de cuál es el número de bits en un byte, ni del tamaño de estos tipos; más bien, ofrece solamente las siguientes "garantías de tipos": • Un tipo char tiene el tamaño mínimo en bytes asignable por la máquina, y todos los bits de este espacio deben ser "accesibles". • El tam tamañ añoo rec recon onoc ocid idoo de char es de 1. Es decir, sizeof(char) siempre devuelve 1. • Un tipo short tiene al menos el mismo tamaño que un tipo char. • Un tipo long tiene al menos el doble tamaño en bytes que un tipo short. • Un tipo int tiene un tamaño entre el de short y el de long, ambos inclusive, preferentemente el tamaño de un apuntador de memoria de la máquina. • Un tipo unsigned tiene el mismo tamaño que su versión signed.
Wchar_t Para la versión del estándar que se publicó en 1998, se decidió añadir el tipo de dato wchar_t, que permite el uso de caracteres UNICODE, a diferencia del tradicional char, que contempla simplemente al código de caracteres ASCII extendido. A su vez, se ha definido para la mayoría de las funciones y clases, tanto de C como de C++, una versión para trabajar con wchar_t, donde usualmente se prefija el carácter w al nombre de la función (en ocasiones el carácter es un infijo). Por ejemplo: • strcpy - wstrcpy
C++
34 • std::string - std::wstring • std::cout - std::wcout Cabe resaltar que en C se define wchar_t como: wchar_t; ; typedef unsigned short wchar_t
Mientras que en C++ es en sí mismo un tipo de dato.
La palabra reservada "void" La palabra reservada void define en C++ el concepto de no existencia o no atribución de un tipo en una variable o declaración. Como tal, puede ser usada para destacar que una función no recibe parámetros, como en: int funcion (void (void); );
Aunque la tendencia actual es la de no colocar la palabra "void". Además se utiliza para determinar que una función no retorna un valor, como en: void funcion (int (int parametro);
Cabe destacar que void no es un tipo. tipo. Una función como la declarada anteriormente no puede retornar un valor por medio de return: la palabra clave va sola. No es posible una declaración del tipo: void t; //Está mal
En este sentido, void se comporta de forma ligeramente diferente a como lo hace en C, especialmente en cuanto a su significado en declaraciones y prototipos de funciones. Sin embargo, la forma especial void * indica que el tipo de datos es un puntero. Por ejemplo: void *memoria;
Indica que memoria es un puntero a alguna parte, donde se guarda información de algún tipo. El programador es responsable de definir estos "algún", eliminando toda ambigüedad. Una ventaja de la declaración " void *" es que puede representar a la vez varios tipos de datos, dependiendo de la operación de cast escogida. La memoria que hemos apuntado en alguna parte, en el ejemplo anterior, bien podría almacenar un entero, un flotante, una cadena de texto o un programa, o combinaciones de éstos. Es responsabilidad responsabilidad del programador recordar qué tipo de datos hay y garantizar el acceso adecuado.
Principios Todo programa en C++ debe tener la función main() (a no ser que se especifique en tiempo de compilación otro punto de entrada, que en realidad es la función que tiene el main()) int main() {}
La función main debe tener uno de los siguientes prototipos: int main() int main(int argc, char** argv) int main(int argc, char** argv, char** env)
La primera es la forma por omisión de un programa que no recibe parámetros ni argumentos. La segunda forma tiene dos parámetros: argc, un número que describe el número de argumentos del programa (incluyendo el nombre del programa mismo), y argv, un puntero a un array de punteros, de argc elementos, donde el elemento argv[i] representa el i-ésimo argumento entregado al programa. En el tercer caso se añade la posibilidad de poder acceder a
C++
35 las variables de entorno de ejecución de la misma forma que se accede a los argumentos del programa, pero reflejados sobre la variable env. El tipo de retorno de main es int. int. Al finalizar la función main, debe incluirse el valor de retorno (por ejemplo, return 0;, aunque el estándar prevé solamente dos posibles valores de retorno: EXIT_SUCCESS y EXIT_ERROR, definidas en el archivo cstddef), o salir por medio de la función exit. Alternativamente puede dejarse en blanco, en cuyo caso el compilador es responsable de agregar la salida adecuada.
El concepto de clase (informática) Véase también: Clase (informática) Los objetos en C++ son abstraídos mediante una clase. Según el paradigma de la programación orientada a objetos un objeto consta de: 1. Identidad, que lo diferencía diferencía de otros objetos objetos (Nombre que llevara llevara la clase a la que pertenece dicho objeto). 2. Método Métodoss o funcion funciones es miemb miembro ro 3. Atributos Atributos o variab variables les miembro miembro Un ejemplo de clase que podemos tomar es la clase perro. Cada perro comparte unas características (atributos). Su número de patas, el color de su pelaje o su tamaño son algunos de sus atributos. Las funciones que lo hagan ladrar, cambiar su comportamiento... esas son las funciones de la clase. Este es otro ejemplo de una clase: class Punto
{ //por omisión los miembros son 'private' para que sólo se puedan modificar desde la propia clase. private:
// Variable miembro privada int id; protected :
// Variables miembro protegidas int x; int y; public:
// Constructor Punto(); // Destructor ~Punto(); // Funciones miembro o métodos int ObtenerX(); int ObtenerY(); };
C++
36
Constructores Véase también: Constructor (informática)
Son unos métodos especiales que se ejecutan automáticamente al crear un objeto de la clase. En su declaración no se especifica el tipo de dato que devuelven, y poseen el mismo nombre que la clase a la que pertenecen. Al igual que otros métodos, puede haber varios constructores sobrecargados, aunque no pueden existir constructores virtuales. Como característica especial a la hora de implementar un constructor, justo después de la declaración de los parámetros, se encuentra lo que se llama "lista de inicializadores". Su objetivo es llamar a los constructores de los atributos que conforman el objeto a construir. Cabe destacar que no es necesario declarar un constructor al igual que un destructor, pues el compilador lo puede hacer, aunque no es la mejor forma de programar. Tomando el ejemplo de la Clase Punto, si deseamos que cada vez que se cree un objeto de esta clase las coordenadas del punto sean igual a cero podemos agregar un constructor como se muestra a continuación: class Punto
{ public:
float x;
// Coordenadas del punto
float y;
// Constructor
Punto() : x(0), y(0){ // Inicializamos las variables "x" e "y" } };
// Main para demostrar el funcionamiento de la clase
# include // Esto nos permite utilizar "cout"
using namespace std;
int main () { Punto MiPunto;
// creamos un elemento de la clase Punto
llamado MiPunto
cout << "Coordenada X: " << MiPunto.x << endl;
// mostramos el valor acumulado en la variable x
cout << "Coordenada Y: " << MiPunto.y << endl;
// mostramos el valor acumulado en la variable y
getchar(); // le indicamos al programa que espere al buffer de entrada (detenerse) return 0;
}
Si compilamos y ejecutamos el anterior programa, obtenemos una salida que debe ser similar a la siguiente: Coordenada X: 0 Coordenada Y: 0 Existen varios tipos de constructores en C++: 1. Constructor predeterminado. Es el constructor que no recibe ningún parámetro en la función. Si no se definiera ningún constructor, el sistema proporcionaría uno predeterminado. Es necesario para la construcción de
C++
37 estructuras y contenedores de la STL. 2. Constructor de copia. Es un constructor que recibe un objeto de la misma clase, y realiza una copia de los atributos del mismo. Al igual que el predeterminado, si no se define, el sistema proporciona uno. 3. Constructor de conversión. Este constructor, recibe como único parámetro, un objeto o variable de otro tipo distinto al suyo propio. Es decir, convierte un objeto de un tipo determinado a otro objeto del tipo que estamos generando. Constructores + Memoria heap Un objeto creado de la forma que se vio hasta ahora, es un objeto que vive dentro del scope(las llaves { }) en el que fue creado. Para que un objeto pueda seguir viviendo cuando se saque de el scope en el que se creó, se lo debe crear en memoria heap. Para esto, se utiliza el operador new, el cual asigna memoria para almacenar al objeto creado, y además llama a su constructor(por lo que se le pueden enviar parámetros). El operador new se utiliza de la siguiente manera: int main() { Punto *unPunto = new Punto(); //esto llama al contructor que se describe más arriba delete unPunto;
//no hay que olvidarse de liberar la
memoria ocupada por el objeto(ver la sección destructores, más abajo) return 0;
}
Además, con el operador new[] se pueden crear arreglo de tamaño dinámico: Punto *asignar(int cuantos) { return new Punto[cuantos]; //asigna un arreglo de 'cuantos' puntos(se
llama el constructor que se muestra más arriba), y se retorna. }
Destructores Véase también: Destructor (informática)
Los destructores son funciones miembro especiales llamadas automáticamente en la ejecución del programa, y por tanto no tienen por qué ser llamadas explícitamente por el programador. Sus principales cometidos son: • Liberar los recursos computacionales que el objeto de dicha clase haya adquirido en tiempo de ejecución al expirar éste. • Quitar los vínculos que pudiesen tener otros recursos u objetos con éste. Los destructores son invocados automáticamente al alcanzar el flujo del programa el fin del ámbito en el que está declarado el objeto. El único caso en el que se debe invocar explícitamente al destructor de un objeto, es cuando éste fue creado mediante el operador new, es decir, que éste vive en memoria heap, y no en la pila de ejecución del programa. La invocación del destructor de un objeto que vive en heap se realiza a través del operador delete o delete[] para arreglos. Ejemplo: int main() { int *unEntero = new int(12);
//asignamos un entero en memoria
heap con el valor 12 int *arregloDeEnteros = new int[25]; //asignamos memoria para 25 enteros(no estan inicializados) delete unEntero;
//liberamos la memoria que
ocupaba unEntero delete[] arregloDeEnteros;
//liberamos la memoria ocupada
C++
38 por arregloDeEnteros return 0;
}
Si no se utilizara el operador delete y delete[] en ese caso, la memoria ocupada por unEntero y arregloDeEnteros respectivamente, quedaría ocupada sin sentido. Cuando una porción de memoria queda ocupada por una variable que ya no se utiliza, y no hay forma de acceder a ella, se denomina un 'memory leak'. En aplicaciones grandes, si ocurren muchos memory leaks, el programa puede terminar ocupando bastante más memoria RAM de la que debería, lo que no es para nada conveniente. Es por esto, que el manejo de memoria heap debe usarse conscientemente. Existen dos tipos de destructores pueden ser públicos o privados, según si se declaran: • Si es público se llama desde cualquier parte del programa para destruir el objeto. • Si es privado no se permite la destrucción del objeto por el usuario. El uso de destructores es clave en el concepto de Adquirir Recursos es Inicializar.
Funciones miembro Función miembro es aquella que está declarada en ámbito de clase. Son similares a las funciones habituales, con la salvedad de que el compilador realizara el proceso de Decoración de nombre ( Name Mangling en inglés): Cambiara el nombre de la función añadiendo un identificador de la clase en la que está declarada, pudiendo incluir caracteres especiales o identificadores numéricos. Además, las funciones miembro reciben implícitamente un parámetro adicional: El puntero this, que referencia al objeto que ejecuta la función. Las funciones miembro se invocan accediendo primero al objeto al cual refieren, con la sintaxis: myobject.mymemberfunction() , esto es un claro ejemplo de una función miembro.
Plantillas Las plantillas son el mecanismo de C++ para implantar el paradigma de la programación genérica. Permiten que una clase o función trabaje con tipos de datos abstractos, especificándose más adelante cuales son los que se quieren usar. Por ejemplo, es posible construir un vector genérico que pueda contener cualquier tipo de estructura de datos. De esta forma se pueden declarar objetos de la clase de este vector que contengan enteros, flotantes, polígonos, figuras, fichas de personal, etc. La declaración de una plantilla se realiza anteponiendo la declaración template a la declaración de la estructura (clase, estructura o función) deseado. Por ejemplo: template
T max(const T &x, const T &y) { return (x > y) ? x : y; //si x > y, retorna x, sino retorna y
}
La función max() es un ejemplo de programación genérica, y dados dos parámetros de un tipo T (que puede ser int , long, float, double, etc.) devolverá el mayor de ellos (usando el operador >). Al ejecutar la función con parámetros de un cierto tipo, el compilador intentará "calzar" la plantilla a ese tipo de datos, o bien generará un mensaje de error si fracasa en ese proceso.
C++
39 Especialización El siguiente ejemplo: template int myfunction(A a);
crea una plantilla bajo la cual pueden ser definidas en el código de cabecera cualesquiera funciones especializadas para un tipo de datos como int myfunction(int), int myfunction(std::string), int myfunction(bool) , etcétera: int myfunction (int a) { return a + 5;
}; int myfunction (std::string a) { return -a.size();
}; int myfunction (bool a) { return (a & rand());
};
Cada una de estas funciones tiene su propia definición (cuerpo). Cada cuerpo diferente, no equivalente ("no convertible") corresponde a una especialización. Si una de estas funciones no fuera definida, el compilador tratará de aplicar las conversiones de tipos de datos que le fuesen permitidas para "calzar" una de las plantillas, o generará un mensaje de error si fracasa en ese proceso. Todas las definiciones habilitadas de una plantilla deben estar disponibles al momento de la compilación, por lo cual no es posible actualmente "compilar" una plantilla como archivo de objeto, sino simplemente compilar especializaciones de la plantilla. Por lo tanto, las plantillas se distribuyen junto con el código fuente de la aplicación. En otras palabras, no es posible compilar la plantilla std::vector< > a código objeto, pero sí es posible, por ejemplo, compilar un tipo de datos std::vector.
Clases abstractas En C++ es posible definir clases abstractas. Una clase abstracta, o clase base abstracta (ABC), es una que está diseñada sólo como clase padre de las cuales se deben derivar clases hijas. Una clase abstracta se usa para representar aquellas entidades o métodos que después se implementarán en las clases derivadas, pero la clase abstracta en sí no contiene ninguna implementación -- solamente representa los métodos que se deben implementar. Por ello, no es posible instanciar una clase abstracta, pero sí una clase concreta que implemente los métodos definidos en ella. Las clases abstractas son útiles para definir interfaces, es decir, un conjunto de métodos que definen el comportamiento de un módulo determinado. Estas definiciones pueden utilizarse sin tener en cuenta la implementación que se hará de ellos. En C++ los métodos de las clases abstractas se definen como funciones virtuales puras. class Abstracta
{ public: virtual int metodo() = 0;
};
C++
40 class ConcretaA : public Abstracta
{ public:
int metodo() { //haz algo return foo () + 2;
} }; class ConcretaB : public Abstracta
{ public:
int metodo() { //otra implementación return baz () - 5;
} };
En el ejemplo, la clase ConcretaA es una implementación de la clase Abstracta, y la clase ConcretaB es otra implementación. Debe notarse que el = 0 es la notación que emplea C++ para definir funciones virtuales puras.
Espacios de nombres Una adición a las características de C son los espacios de nombre (namespace en inglés), los cuales pueden describirse como áreas virtuales bajo las cuales ciertos nombres de variable o tipos tienen validez. Esto permite evitar las ocurrencias de conflictos entre nombres de funciones, variables o clases. El ejemplo más conocido en C++ es el espacio de nombres std::, el cual almacena todas las definiciones nuevas en C++ que difieren de C (algunas estructuras y funciones), así como las funcionalidades propias de C++ (streams) y los componentes de la biblioteca STL. Por ejemplo: # include // Las funciones en esta cabecera existen dentro del espacio de nombres std:: namespace mi_paquete{
int mi_valor; }; int main() { int mi_valor = 3; mi_paquete::mi_valor = 4; std::cout << mi_valor << '\n'; // imprime '3' std::cout << mi_paquete::mi_valor << '\n'; // imprime '4'
C++
41
return 0;
}
Como puede verse, las invocaciones directas a mi_valor darán acceso solamente a la variable descrita localmente; para acceder a la variable del espacio de nombres mi_paquete es necesario acceder específicamente el espacio de nombres. Un atajo recomendado para programas sencillos es la directiva using namespace, que permite acceder a los nombres de variables del paquete deseado en forma directa, siempre y cuando no se produzca alguna ambigüedad o conflicto de nombres.
Herencia Existen varios tipos de herencia entre clases en el lenguaje de programación C++. Estos son: Herencia simple La herencia en C++ es un mecanismo de abstracción creado para poder facilitar y mejorar el diseño de las clases de un programa. Con ella se pueden crear nuevas clases a partir de clases ya hechas, siempre y cuando tengan un tipo de relación especial. En la herencia, las clases derivadas "heredan" los datos y las funciones miembro de las clases base, pudiendo las clases derivadas redefinir estos comportamientos (polimorfismo) y añadir comportamientos nuevos propios de las clases derivadas. Para no romper el principio de encapsulamiento (ocultar datos cuyo conocimiento no es necesario para el uso de las clases), se proporciona un nuevo modo de visibilidad de los datos/funciones: "protected". Cualquier cosa que tenga visibilidad protected se comportará como pública en la clase Base y en las que componen la jerarquía de herencia, y como privada en las clases que NO sean de la jerarquía de la herencia. Antes de utilizar la herencia, nos tenemos que hacer una pregunta, y si tiene sentido, podemos intentar usar esta jerarquía: Si la frase ES-UN tiene sentido, entonces estamos ante un posible caso de herencia donde clase A será la clase base y clase B la derivada. Ejemplo: clases Barco, Acorazado, Carguero, etc. un Acorazado ES-UN Barco, un Carguero ES-UN Barco, un Trasatlántico ES-UN Barco, etc. En este ejemplo tendríamos las cosas generales de un Barco (en C++) class Barco { protected :
char* nombre; float peso; public:
//Constructores y demás funciones básicas de barco };
y ahora las características de las clases derivadas, podrían (a la vez que heredan las de barco) añadir cosas propias del subtipo de barco que vamos a crear, por ejemplo: class Carguero: public Barco { // Esta es la manera de especificar que
hereda de Barco private:
float carga; //El resto de cosas };
C++
42 class Acorazado: public Barco { private:
int numeroArmas; int Soldados; // El resto de cosas };
Por último, hay que mencionar que existen 3 clases de herencia que se diferencian en el modo de manejar la visibilidad de los componentes de la clase resultante: • Herencia publica (class Derivada: public Base ): Con este tipo de herencia se respetan los comportamientos originales de las visibilidades de la clase Base en la clase Derivada. • Herencia privada (clase Derivada: private Base): Con este tipo de herencia todo componente de la clase Base, será privado en la clase Derivada (las propiedades heredadas serán privadas aunque estas sean públicas en la clase Base) • Herencia protegida (clase Derivada: protected Base): Con este tipo de herencia, todo componente publico y protegido de la clase Base, será protegido en la clase Derivada, y los componentes privados, siguen siendo privados. Herencia múltiple La herencia múltiple es el mecanismo que permite al programador hacer clases derivadas a partir, no de una sola clase base, sino de varias. Para entender esto mejor, pongamos un ejemplo: Cuando ves a quien te atiende en una tienda, como persona que es, podrás suponer que puede hablar, comer, andar, pero, por otro lado, como empleado que es, también podrás suponer que tiene un jefe, que puede cobrarte dinero por la compra, que puede devolverte el cambio, etc. Si esto lo trasladamos a la programación sería herencia múltiple (clase empleado_tienda): class Persona {
... Hablar(); Caminar(); ... }; class Empleado {
Persona jefe; int sueldo; Cobrar(); ... }; class empleado_tienda: public Persona, Empleado {
... AlmacenarStock(); ComprobarExistencias(); ... };
Por tanto, es posible utilizar más de una clase para que otra herede sus características.
C++
43
Sobrecarga de operadores La sobrecarga de operadores es una forma de hacer polimorfismo. Es posible definir el comportamiento de un operador del lenguaje para que trabaje con tipos de datos definidos por el usuario. No todos los operadores de C++ son factibles de sobrecargar, y, entre aquellos que pueden ser sobrecargados, se deben cumplir condiciones especiales. En particular, los operadores sizeof y :: no son sobrecargables. No es posible en C++ crear un operador nuevo. Los comportamientos de los operadores sobrecargados se implementan de la misma manera que una función, salvo que esta tendrá un nombre especial: Tipo de dato de devolución operator( parámetros )
Los siguientes operadores pueden ser sobrecargados: • Operadores Unarios • Operador * (de indirección) • Operador -> (de indirección) • Operador & (de dirección) • Operador + • Operador • Operador ++ • Operador -• Operadores Binarios • Operador == • Operador + • Operador • Operador * • Operador / • Operador % • Operador << • Operador >> • Operador & • Operador ^ • Operador | • Operador [] • Operador () • Operadores de Asignación • • • • • • • • • • •
Operador = Operador += Operador -= Operador *= Operador /= Operador %= Operador <<= Operador >>= Operador &= Operador ^= Operador |=
C++
44 Dado que estos operadores son definidos para un tipo de datos definido por el usuario, éste es libre de asignarles cualquiera semántica que desee. Sin embargo, se considera de primera importancia que las semánticas sean tan parecidas al comportamiento natural de los operadores como para que el uso de los operadores sobrecargados sea intuitivo. Por ejemplo, el uso del operador unario - debiera cambiar el "signo" de un "valor". Los operadores sobrecargados no dejan de ser funciones, por lo que pueden devolver un valor, si este valor es del tipo de datos con el que trabaja el operador, permite el encadenamiento de sentencias. Por ejemplo, si tenemos 3 variables A,B y C de un tipo T y sobrecargamos el operador = para que trabaje con el tipo de datos T, hay dos opciones: si el operador no devuelve nada una sentencia como "A=B=C;" (sin las comillas) daría error, pero si se devuelve un tipo de datos T al implementar el operador, permitiría concatenar cuantos elementos se quisieran, permitiendo algo como "A=B=C=D=...;"
Biblioteca estándar de plantillas (STL, Standard Template Library) Los lenguajes de programación suelen tener una serie de bibliotecas de funciones integradas para la manipulación de datos a nivel más básico. En C++, además de poder usar las bibliotecas de C, se puede usar la nativa STL (Standard Template Library), propia del lenguaje. Proporciona una serie plantillas (templates) que permiten efectuar operaciones sobre el almacenado de datos, procesado de entrada/salida.
Biblioteca de entrada y salida Las clases basic_ostream y basic_stream, y los objetos cout y cin, proporcionan la entrada y salida estándar de datos (teclado/pantalla). También está disponible cerr, similar a cout, usado para la salida estándar de errores. Estas clases tienen sobrecargados los operadores << y >>, respectivamente, con el objeto de ser útiles en la inserción/extracción de datos a dichos flujos. Son operadores inteligentes, ya que son capaces de adaptarse al tipo de datos que reciben, aunque tendremos que definir el comportamiento de dicha entrada/salida para clases/tipos de datos definidos por el usuario. Por ejemplo: ostream& operator<<(ostream& fs, const Punto& punto) { return fs << punto.x << "," << punto.y;
}
De esta forma, para mostrar un punto, solo habría que realizar la siguiente expresión: //... Punto p(4,5); //... cout << "Las coordenadas son: " << p << endl; //...
Es posible formatear la entrada/salida, indicando el número de dígitos decimales a mostrar, si los textos se pasarán a minúsculas o mayúsculas, si los números recibidos están en formato octal o hexadecimal, etc.
C++
45
Fstreams Tipo de flujo para el manejo de ficheros. La definición previa de ostreams/istreams es aplicable a este apartado. Existen tres clases (ficheros de lectura, de escritura o de lectura/escritura): ifstream,ofstream y fstream. Como abrir un fichero: (nombre_variable_fichero).open("nombre_fichero.dat/txt",ios::in); para abrirlo en modo lectura. (nombrevariablefichero).open("nombre_fichero.dat/txt",ios::out); para abrirlo en modo escritura. Ejemplo: f.open("datos.txt",ios::in); Como cerrar el fichero: nombre_variable_fichero.close(); Ejemplo: f.close(); Leer un fichero: 1-Si es fichero de texto plano: #include #include #include using namespace std;
int main() { ifstream entrada; entrada.open("textoPlano.txt"); string unString; while(entrada >> unString)
cout << "Lei: " << unString << endl; return 0;
}
2-Si es un fichero binario(.dat); nombre_variable_fichero.read((char*)&nombre_variable,sizeof(tipo_variable)); Ejemplo: f.read((char*)&e,sizeof(int));
Escribir un fichero: 1-Si es fichero de texto(.txt): nombrevariable<<"texto"; donde "texto" puede ser también una variable de cualquier tipo primitivo, o un string.
Ejemplo: f<
2-Si es un fichero binario(.dat); nombre_variable_fichero.write((char*)&nombre_variable,sizeof(tipo_variable)); Ejemplo: f.write((char*)&e,sizeof(int));
Pueden abrirse pasando al constructor los parámetros relativos a la ubicación del fichero y el modo de apertura:
C++
46
Sstreams Se destacan dos clases, ostringstream e istringstream. Todo lo anteriormente dicho es aplicable a estas clases. Tratan a una cadena como si de un flujo de datos se tratase. ostringstream permite elaborar una cadena de texto insertando datos cual flujo, e istringstream puede extraer la información contenida en una cadena (pasada como parámetro en su constructor) con el operador >>. Ejemplos: ostringstream s; s << nombre << "," << edad << "," << estatura << "," << punto(5,6) << endl; cout << s.str(); istringstream s(cadena); s >> nombre >> edad >> estatura >> p;
Contenedores Son clases plantillas especiales utilizadas para almacenar tipos de datos genéricos, sean cuales sean. Todos los contenedores son homogéneos, es decir, una vez que se declaran para contener un tipo de dato determinado, en ese contenedor, solo se podrán meter elementos de ese tipo. Según la naturaleza del almacenado, disponemos de varios tipos: • Vectores: Se definen por vector nombre_del_vector;
Son arreglos que se redimensionan automaticamente al agregar nuevos elementos, por lo que se le pueden agregar "teóricamente", infinitos elementos. Los vectores nos permiten acceder a cualquier elemento que contenga, mediante el operador[]. Debe tenerse en cuenta que si se intenta acceder a una posición que excede los límites del vector, este no hará ningún chequeo, por lo que se debe ser cuidadoso al utilizar este operador. Para asegurar un acceso seguro al vector, se puede utilizar el método at(int), que lanza una excepción de tipo std::out_of_range en caso de que esto ocurra. Para agregar elementos al final del vector, se utiliza el método push_back(const T&). Por otro lado, para eliminar un elemento del final del vector, se debe usar el método pop_back(). #include
//libreria que contiene a la clase vector
#include using namespace std;
int main() { vector intVector;
//crea un vector de enteros(sin elementos)
intVector.push_back( 25); //agrega el entero 25 al vector cout << "El primer elemento es: " << intVector.front() << " y mi vector tiene " << intVector.size() << " elementos." << endl; //imprime el primer elemento(retornado por el metodo front()
intVector.push_back( 32); //agrego el entero 32 al vector cout << "El primer elemento es: " << intVector[ 0] << endl; //imprime 25 intVector.pop_back();
//elimina el ultimo elemento del vector(osea
32) cout << "Ahora tengo: " << intVector.size() << " elementos." << endl; //imprimirá 1
return 0;
}
C++
47 • Colas dobles: son parecidas a los vectores, pero tienen mejor eficiencia para agregar o eliminar elementos en las "puntas". deque nombre_de_la_cola;
Además de los métodos push_back(const T&) y pop_back(), se agregan los métodos push_front(const T&) y pop_front(), que realizan lo mismo que los ya explicados, pero en el comienzo de la cola. #include
//libreria de deques
using namespace std;
int main() { deque intDeque; intDeque.push_front(25); intDeque.push_back(12); while(intDeque.size())
intDeque.pop_back(); //borra todos los elementos return 0;
}
• Listas: Son eficientes a la hora de agregar elementos. La diferencia con las colas dobles, es que son mas eficientes para eliminar elementos que no estén en alguna de las "puntas" list nombre_de_la_lista;
• Adaptadores de secuencia. • Contenedores asociativos: map y multimap, que permiten asociar una "clave" con un "valor". map no permite valores repetidos, mientras que multimap si. map nombre_del_map; multimap nombre_del_multimap; #include