Pooc 1

11
1 Algoritmo. La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS Programación Orientada al Objeto con C++ Por Antonio Rojo Breve recorrido histórico Antes de comenzar daremos un breve repaso histórico al nacimiento del C++. En un principio se pretendía que fuera un lenguaje distinto del C pero aprovechando todas sus características. Pero el tiempo lo tornó como una extensión del C o como un avance de éste. El caso es que su nacimiento se lo debemos al Sr. Bjarne Stroustrup mientras trabajaba en los laboratorios Bell de New Jersey, allá por el año 1980. En un principio se llamó C con Clases para posteriormente, llamarse C++. El mismo Sr. Bjarne reconoció que se basó en el lenguaje C y en el lenguaje Simula67 aprovechando por entero las características del primero y añadiendo la potencia de la programación orientada al objeto del segundo. El lenguaje C++ ha sido revisado en dos ocasiones. La primera en 1985 que determinó las bases generales del hoy conocido C++. La segunda fue en el año 1990 (bastante reciente) en la que se le añadieron algunas nuevas características como es la herencia múltiple, de la que carecía en la revisión 1. Es por ello, que a veces se puede ver en la publicidad de algunos compiladores de C++ el hecho de hacer referencia a cual de las revisiones soportan. No obstante la mayoría de los lenguajes C++ de hoy soportan la revisión 2. No obstante la ampliación que C++ hace al lenguaje C no es exclusivamente en la programación orientada a objetos, sino en otras facilidades para el programador que pueden ayudarnos en esta ardua tarea. Bjarne Stroustrup diseñó este lenguaje sin perder la flexibilidad que tenía el lenguaje C y siempre teniendo en cuenta que es el programador quien debe controlar el programa no el lenguaje. Quizás sea por esto que sigue siendo el lenguaje de programación más utilizado para cualquier tipo de programación, ya sea de gestión, matemática, críticas en velocidad, de control de aparatos, y un largo etc. La base OOP en C++ La extensión del lenguaje C++ se construye aprovechando las cualidades de su predecesor, el C. Si además, tenemos en cuenta que las clases no dejan de ser estructuras de datos asociadas con programas, no es de extrañar que las propias estructuras de C se conviertan en clases en C++ (en realidad no es cierto, pero lo vamos a ver desde este punto de vista para que resulte más cómodo para el lector). Veamos la definición de las estructuras en el lenguaje C. struct [<nombre estructura>] { <tipo variable> <nombre variable> ; ... } [<lista de variables de la estructura>] ; Podemos recordar los artículos de la revista ClippeRmanía en su curso de C, para ver este tipo

Transcript of Pooc 1

Page 1: Pooc 1

1

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

Programación Orientada al Objeto con C++Por Antonio Rojo

Breve recorrido histórico

Antes de comenzar daremos un breve repaso histórico al nacimiento del C++. En un principio sepretendía que fuera un lenguaje distinto del C pero aprovechando todas sus características.Pero el tiempo lo tornó como una extensión del C o como un avance de éste.

El caso es que su nacimiento se lo debemos al Sr. Bjarne Stroustrup mientras trabajaba en loslaboratorios Bell de New Jersey, allá por el año 1980. En un principio se llamó C con Clasespara posteriormente, llamarse C++.

El mismo Sr. Bjarne reconoció que se basó en el lenguaje C y en el lenguaje Simula67aprovechando por entero las características del primero y añadiendo la potencia de laprogramación orientada al objeto del segundo.

El lenguaje C++ ha sido revisado en dos ocasiones. La primera en 1985 que determinó lasbases generales del hoy conocido C++. La segunda fue en el año 1990 (bastante reciente) en laque se le añadieron algunas nuevas características como es la herencia múltiple, de la quecarecía en la revisión 1. Es por ello, que a veces se puede ver en la publicidad de algunoscompiladores de C++ el hecho de hacer referencia a cual de las revisiones soportan. Noobstante la mayoría de los lenguajes C++ de hoy soportan la revisión 2.

No obstante la ampliación que C++ hace al lenguaje C no es exclusivamente en la programaciónorientada a objetos, sino en otras facilidades para el programador que pueden ayudarnos enesta ardua tarea. Bjarne Stroustrup diseñó este lenguaje sin perder la flexibilidad que tenía ellenguaje C y siempre teniendo en cuenta que es el programador quien debe controlar elprograma no el lenguaje.

Quizás sea por esto que sigue siendo el lenguaje de programación más utilizado para cualquiertipo de programación, ya sea de gestión, matemática, críticas en velocidad, de control deaparatos, y un largo etc.

La base OOP en C++

La extensión del lenguaje C++ se construye aprovechando las cualidades de su predecesor, elC. Si además, tenemos en cuenta que las clases no dejan de ser estructuras de datosasociadas con programas, no es de extrañar que las propias estructuras de C se conviertan enclases en C++ (en realidad no es cierto, pero lo vamos a ver desde este punto de vista para queresulte más cómodo para el lector).

Veamos la definición de las estructuras en el lenguaje C.

struct [<nombre estructura>] { <tipo variable> <nombre variable> ; ... } [<lista de variables de la estructura>] ;

Podemos recordar los artículos de la revista ClippeRmanía en su curso de C, para ver este tipo

Page 2: Pooc 1

2

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

de datos. Ahora nos vamos a basar en que el lector conoce el lenguaje C y solamente vamos aexplicar la extensión C++.

Ahora en C++ este tipo de datos se ve ampliado por una nueva sintaxis para la construcción declases quedando la siguiente sintaxis:

struct [<nombre estructura>] { [<modificador de ámbito>:] <tipo variable> <nombre variable> ; ... [<declaración de función miembro> ;] ... } [<lista de variables de la estructura>] ;

Veamos la gran ampliación que se ha efectuado a este tipo de dato añadiendo dos nuevosconceptos, como son el Modificador de ámbito y las Funciones miembro.

Veamos por separados cada uno de ellos. En primer lugar veremos lo que significa elmodificador de ámbito. Este modificador señala el nuevo ámbito de las siguientes variables ofunciones miembro de la estructura. Este ámbito no es otra cosa que la posibilidad de acceder adichas variables o funciones desde fuera de la estructura, pudiendo ser o public, private oprotected, seguido de dos puntos.

Hay que señalar dos cosas, la primera es que para el tipo de dato struct el ámbito por defectoes público, y como segunda cosa es que no existe un orden entre la declaración de variables ylas funciones miembro, pudiéndose mezclar entre ellas. Lo normal es que en la utilización deestructuras se declaren primero las variables públicas seguidas de las funciones con el mismoámbito, para luego utilizar el modificador private: y declarar seguidamente las variables yfunciones locales a la estructura. Veamos un ejemplo muy sencillo:

struct point { int x, y; void say( void ); void suma( int a, int b ); } ;

Como ya vimos en C una estructura puede contener en su definición como dato tipo variable,otra estructura. Ahora el concepto resulta un poco mas complejo ya que las estructuras puedencontener funciones. Sin embargo, no debe preocuparnos, ya que cada estructura sabe en todomomento cuales son sus funciones miembro. Pero hasta ahora hemos visto como se declaranlas estructuras y sus funciones miembro, pero no el código de dichas funciones. Para ellodebemos tener en cuenta que el código de una función miembro no tiene por qué estar dentrodel mismo fuente, por lo que hay que definirle de alguna forma a que clase pertenece. Para ellono hay forma mas sencilla que declarar la función precedida del nombre de la clase y seguida decuatro puntos (::). El resto de la definición de la función es similar a la declaración de una funciónnormal en C.

void point::say( void ) {

Page 3: Pooc 1

3

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

... }

o

void point::suma( int a, int b ) { ... }

La forma de acceso a los datos de la estructura dentro de las funciones miembro, se efectúadirectamente en la declaración de variables de dicha estructura, sin ser precedidas del nombrede la estructura. Si completamos la función miembro suma() el resultado sería el siguiente:

void point::suma( int a, int b ) { x += a; y += b; }

La función suma() ya reconoce a las variables x e y como variables de la estructura por lo queno necesitan ser declaradas ni referenciadas.

Las clases en C++

Hasta ahora solo hemos visto un acercamiento a las extensiones de C++ para las estructuras,dotándolas de la potencia de las Clases, sin embargo no dejan de ser estructuras, y es asícomo se las debe de ver. Aunque la diferencia solo sea de concepto y no de contenido existeuna forma de declaración de clases en C++ siendo similar a las estructuras, pero utilizando lapalabra Clave Class.

Todo lo dicho anteriormente para las estructuras es aplicable a este modo, siendo incluso susintaxis muy similar, hasta el punto que solo existen dos diferencias. Una, el cambio de lapalabra clave struct por class, y la segunda, que el ámbito por defecto para las clases es elprivado. La declaración de las clases quedaría de la siguiente forma:

class [<nombre clase>] { [<modificador de ámbito>:] <tipo variable> <nombre variable> ; ... [<declaración de función miembro> ;] ... } [<lista de variables de la clase>] ;

Page 4: Pooc 1

4

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

La misma definición de clase que se dio para la estructura point vendría a ser la siguiente:

class point { public: int x, y; void say( void ); void suma( int a, int b ); } ;

Hay que hacer constar la utilización del modificador de ámbito public: ya que sin el no se podríaacceder ni a las variables x e y ni a las funciones miembro say() y suma().

Como veis, no existe una diferenciación de concepto entre las estructuras y las clases, ya que sufuncionamiento es similar, salvo el ámbito. Sin embargo es conveniente seguir diferenciandoentre estructuras y clases ya que un programa escrito en C es a su vez C++ pero sin aprovecharlas características de éste. No olvidemos que C++ es una extensión o un avance del lenguaje C.Hemos visto como se definen las clases pero todavía no hemos visto como se definen losobjetos dentro del programa. Pues bien, se definen como cualquier otro tipo de dato de C. Elacceso a los datos y las funciones miembro del objeto es igual al acceso a los datos de lasestructuras utilizando el operador punto (.) seguido de la variable o función miembro. Veamos unejemplo utilizando la clase point:

main() { int a, b; point c, d; ... c.x = 5; c.y = 15; c.say(); c.suma( 23, 57 ); }

Para aquellas personas que ya hayan trabajado en C les resultará muy lógico el funcionamiento,ya que continúa la línea de acceso a estructuras de este lenguaje. Para aquellas personas queno conozcan el lenguaje C es conveniente que lo estudien antes, ya que estamos basándonos enel conocimiento del lenguaje C por parte del lector.

Hasta aquí no hemos visto nada más que la definición de las clases, sin embargo no hemos vistocomo C++ contempla las características de la programación orientada a objetos. Veamos pueseste comportamiento.

Polimorfismo

Esta característica se podría definir como aquella que permite que dos o más clases tenganfunciones miembro con el mismo nombre pero con distintos resultados para cada clase. O lo quees lo mismo que una clase X puede tener una función miembro say() al mismo tiempo que una

Page 5: Pooc 1

5

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

clase Y puede tener a su vez, una función miembro say(). La función say() que se ejecute será lacorrespondiente a la clase a la que pertenece el objeto.Esto nos permite utilizar nombres lógicos para diversas operaciones sin tener en cuenta si estosnombres ya han sido utilizados por otras clases, ya que el lenguaje sabrá discernir cual de lasfunciones debe ejecutar partiendo del objeto al que se referencia.

Siguiendo con la clase point podríamos definir una clase Window que tuviera también la funciónmiembro say(), con lo que los nombres lógicos de las funciones no se complican para evitarduplicidades como ocurría en C.

En C++ el polimorfismo llega mas allá ya que la diferencia de tipos de datos pude dar lugar adistintas funciones. Un ejemplo sería la posibilidad de pasar dos números largos comoparámetros a la función suma() de la clase point. En principio deberíamos definir una nuevafunción miembro para soportar este tipo de parámetros. Sin embargo C++ incorpora lo que vienea denominarse la sobrecarga de funciones.

Esta sobrecarga consiste en la utilización del mismo nombre de función para dos o másprocesos con diferentes parámetros o retorno de valores. En C clásico éste problema se teníaque tratar utilizando funciones diferentes. Tal es el caso de la conversión de datos numéricos adatos tipo carácter ( itoa(), ltoa(), etc ).

Con la sobrecarga de funciones se puede utilizar el mismo identificador para todas las funciones,siendo el compilador quien ejecute la opción correcta dependiendo de los parámetros puestos.

Las mayor parte de las características que incorpora C++ para las clases no son exclusivas deestas, sino que se pueden trasladar a todo el lenguaje. Esto quiere decir que el concepto desobrecarga de funciones no es exclusivo de las funciones miembro de una clase o estructurasino que también es accesible para cualquier función definida en el lenguaje.

La definición de este tipo de funciones es similar al resto, aunque se utilice varias veces elmismo nombre de función en las definiciones. Tiene que haber al menos alguna diferencia en susparámetros para poder discernir posteriormente cual se ejecutará dependiendo de los datosenviados.

Siguiendo con la clase point vamos a definir una nueva función suma() que tenga comoparámetro un objeto de la clase point. El resultado completo sería el siguiente:

class point { public: int x, y; void say( void ); void suma( int a, int b ); void suma( point p ); } ; ... void point::suma( int a, int b ) { x += a; y += b; } void point::suma( point p ) { x += p.x; y += p.y; }

Page 6: Pooc 1

6

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

En la utilización de los objetos será cuando se diferencie la ejecución de las funciones sin sertarea del programador tener que estar diferenciando cual se ejecutará. Esta ventaja nos ayudamucho a los programadores a la hora de utilizar varias funciones que efectúan una lógica similarpero con diferente tipo de datos.

En estos casos es el propio lenguaje quien se encarga de diferenciar la tarea, mientras que parael programador la tarea lógica sigue siendo la misma. Sobre todo, teniendo en cuenta que ellenguaje C es uno de los más estrictos en cuanto a la utilización de los tipos de datos al definirlas funciones.

Este lenguaje exige un uso correcto de los parámetros para su funcionamiento. Un ejemplo deutilización de esta técnica sería el siguiente:

main() { point j, k; ... j.x = 5; j.y = 25; k.x = 12; k.y = 33; ... j.suma( 10, 20 ); j.suma( k ); ... }

Otra de las características del polimorfismo en C++ es la incorporación de lo que se denominanfunciones virtuales para ligamiento dinámico. Estas funciones virtuales de definen en una claseanteponiendo la palabra clave, virtual, a la declaración de la función miembro.

Cuando se efectúa la herencia de la clase donde se definieron las funciones virtuales es cuandose definen las funciones reales. Aquí es cuando se determina en tiempo de ejecución cual es lafunción real a ejecutar dependiendo del objeto que se trate. Esta ejecución en tiempo decompilación no es real, ya que se sabe en tiempo de compilación cual es en realidad la funcióndefinida, ya que el objeto se define como de una determinada clase y es en la definición de laclase donde ya se encuentra la función virtual redefinida.

En C++ no existe en realidad la característica de ligamiento dinámico ya que todo se resuelve entiempo de compilación. Se puede emular utilizando punteros a funciones pero esta manera esexcesivamente compleja, y precisamente va en contra de la principal característica que Bjarnequería implementar con el lenguaje C++, que es precisamente la sencillez para el programador yla posibilidad de controlar mejor el código.

No obstante diremos que en C++, la programación orientada a objetos se reducen a merasdefiniciones en tiempo de compilación, con lo que el ligamiento dinámico se puede emular deforma sencilla sin la necesidad de utilizar las funciones virtuales. Pero esto se queda para unpróximo capitulo, ya que primero deberemos ver todas las ampliaciones que el lenguaje C++hace al antiguo C antes de adentrarnos en pequeños trucos.

Page 7: Pooc 1

7

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

Herencia

Otra de las características importantes de la OOP es la herencia. Quizás sea la característicamás importante ya que gracias a ésta es por la que se puede aprovechar todo el código escritode una forma sencilla y eficiente.

Ya habíamos comentado que la revisión 1 sólo soportaba la herencia simple mientras que en laversión 2 ya se incorporó la herencia múltiple. Nosotros vamos a ver la herencia múltiple, sinembargo quiero resaltar que en mi opinión, la herencia múltiple genera malas practicas deprogramación, ya que conduce a conceptos erróneos. Frente a la herencia múltiple está latécnica de los objetos contenedores y objetos contenidos, que también veremos en próximoscapítulos.

Antes de entrar en la herencia repasemos el ámbito de las variables y las funciones miembro, yaque este ámbito juega un papel muy importante en la herencia.

Habíamos comentado tres ámbitos, el publico, por el que se pueden acceder tanto desde dentrodel objeto como desde fuera a cualquier variable o función así declarada. El privado, por el queno se puede acceder a los datos o funciones nada mas que a trabes de las funciones miembro.Y protegido, que es similar a la anterior pero que juega un papel muy distinto en la herencia.

Para declarar que una clase va ha heredar de otra se expresa en la declaración de la claseseguido de dos puntos, y posteriormente se expresan la clase o las clases de las que seheredará.Cuando hago referencia a las clases también se interpreta para las estructuras inclusopudiéndose mezclar entre ellas dando lugar a que una clase herede de una estructura, o de unaestructura y una clase, etc. Puede se cualquier convinación que se nos ocurra.

Precediendo al nombre de la clase o estructura de la que se hereda se especifica el modo de laherencia. Este modo puede ser o bien publico o bien privado. Aquí es donde interviene ladiferencia entre las declaraciones de ámbito private y protected. La sintaxis de definición declase quedaría de la siguiente forma:

class <nombre clase> [: [<ámbito>] <clase>[, ...]] { ... }

En toda herencia los datos privados se convierten en inaccesibles para la clase que hereda,mientras los públicos y protegidos permanecen del mismo modo, si la especificación fue pública.En caso de ser privada tanto los públicos como protegidos pasan a ser privados en la nuevaclase. Podemos ver un ejemplo en la tabla 1.

public protected private

public public protected inaccesibleprivate private private inaccesible

Tabla 1. Herencia

Page 8: Pooc 1

8

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

Estas modificaciones de ámbito hay que tenerlas muy en cuenta en la programación, ya que undato o función miembro declarada como privada no será accesible desde ninguna clase queherede de ésta.

El ámbito puede ser opcional, siendo por defecto público. Un ejemplo sencillo podría ser la clasecoordenadas tridimensionales heredando de la clase point.

class coord: public point { public: int z; ... }

En este nivel es donde se podrán redefinir las funciones miembro para dotarla de lasnecesidades que la nueva clase va a tener.

Constructores y destructores

Es muy deseable que la inicialización de los datos de objeto se efectúe de forma automática alcrearse éste. Por esta razón las clases de C++ llevan lo que se denomina constructores.

El constructor no es más que una función miembro con el mismo nombre que la clase en la quese codifica la inicialización del objeto a nuestra medida.

Este constructor o función miembro automática se ejecuta en el momento de la definición delobjeto en la declaración de variables de la función.

Hay que tener en cuenta que tanto en C como C++ las variables locales a la función seconstruyen en la pila del procesador reservando espacio únicamente. Este espacio puede estarsucio por anteriores utilizaciones del mismo con lo que los datos allí reflejados pueden resultarmuy erróneos durante la ejecución de la aplicación si éstos no se inicializan correctamente. Conlas estructuras y los objetos esto sigue siendo válido.

A parte de este tipo de inicialización, también se puede utilizar para el seguimiento de los objetoscreados y obtener un mejor control.

Para ayudarnos en este control existen también las funciones destructoras, que se ejecutancuando el objeto desaparece. Estas funciones se ejecutan justo antes de desaparecer el objeto.

Hay que tener muy presente la vida de las variables dentro del lenguaje C y C++ ya que resultadeterminante para la corrección de errores.

Las funciones destructoras tienen el mismo nombre que el de la clase pero precedido delcarácter '~'.

Continuando con el ejemplo podemos ver como quedaría la clase point con la definición delconstructor.

class point { public: int x, y; void say( void );

Page 9: Pooc 1

9

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

void suma( int a, int b ); void suma( point p ); void point( void ); } ; ... void point::suma( int a, int b ) { x += a; y += b; } void point::suma( point p ) { x += p.x; y += p.y; } void point::point( void ) { x = 0; y = 0; }

Su utilización sería automática durante la ejecución del programa al definir los objetos de laclase.

Por ejemplo:

main() { point j, k ; // En esta definición se ejecuta el constructor ... }

Desde el punto de vista de automatismo resulta muy eficiente la ejecución automática delconstructor, pero se queda un poco cojo, por si solo si no se permite una inicialización diferentepara cada objeto. Esta inicialización diferente se consigue con la parametrización de losconstructores.

Esta parametrización consiste simplemente en la declaración de diversos parámetros para laconstrucción del objeto de diferentes formas. Lo mejor es ver esto con un ejemplo para lo queampliaremos el ejemplo del constructor de la clase point.

void point::point( int a, int b ) { x = a; y = b; }

De esta forma resulta muy cómodo la inicialización de los objetos en su creación. Pero veamos

Page 10: Pooc 1

10

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

como se utilizan los constructores parametrizados. Se pueden utilizar de dos formas siendo suefecto similar.

main() { point a = point( 5, 10 ); ... }

o bien

main() { point a( 5, 10 ); ... }

El efecto es el mismo y el código generado también si el compilador está lo suficientementeoptimizado.

Pero aún hay que ir más allá ya que en la mayoría de las ocasiones la construcción de losobjetos se hará con los mismos valores, por lo que resulta bastante redundante tener queexpresarlos en cada creación de un objeto de la clase.

Esto se evita utilizando la definición del valor por defecto para los parámetros. Esta definición sehace asignando el valor por defecto en la declaración de parámetros de la función. Veamoscomo se haría en el constructor de point.

void point::point( int a = 0, int b = 0 ) { x = a; y = b; }

Ahora la utilización del constructor se puede hacer, bien pasando parámetros en la inicialización,o bien sin ellos con lo que los valores que tomará serán los definidos por defecto.

main() { point a( 5, 10 ); // Los valores son: x=5 e y=10 point b; // Los valores son: x=0 e y=0 ... }

Page 11: Pooc 1

11

Algoritmo . La revista para el programador de sistemas de bases de datos. http:/www.eidos.es - © Grupo EIDOS

Los valores por defecto en los parámetros no es exclusivo de los constructores de las clases. Esválido para cualquier función miembro de una clase, o incluso, para cualquier función genéricadefinida. Está definida en C++ como una característica general para las funciones, sin embargo,donde mayor utilidad se le puede ver es precisamente en los constructores de las clases