Sie sind auf Seite 1von 24

5/12/2017 Programación Orientada a Objetos en C++

Curso de C++ Builder

Programación Orientada a Objetos en C++

5.1. El Paradigma de la POO en C++.


5.2. Creación y Destrucción de Objetos.
5.3. Encapsulamiento.
5.4. Constructores y Destructores (Inicialización de Clases I).
5.5. Herencia.

5.5.1. Herencia de Constructores y Destructores (Inicialización de


Clases II).
5.5.2. Clases Abstractas.
5.5.3. Herencia Múltiple.

5.6. Abstracción.

5.6.1. Restricciones de acceso en C++.


5.6.2. Propiedades Virtuales.

5.7. Polimorfismo.

5.7.1. Sobrecarga de funciones..


5.7.2. Polimorfismo en las clases y métodos virtuales..

En esta sección no se pretende dar una teoría completa de P.O.O. tan sólo se presentarán los
conceptos necesarios para una correcta programación en C++ Builder.

La P.O.O. es un paradigma de programación que se fundamenta en los conceptos de objeto


y clase. En primer lugar, definamos que entendemos por objeto y clase:

Objeto: Una entidad autónoma con una funcionalidad concreta y bien definida.
Clase: Especificación de las características de un conjunto de objetos.
Un objeto es una instancia de una clase.

Los conceptos presentados en esta sección se ilustrarán usando un ejemplo que se irá
completando poco a poco a medida que se introduzcan nuevos conceptos. Es más, este
mismo ejemplo se emplea en las secciones dedicadas al tratamiento de excepciones y a la
programación con hebras. Así, preparemos el camino creando un proyecto:

Crear un proyecto (File | New | Application)

Cambiar el nombre del formulario (Name=PpalFrm). Colocar un PaintBox de la


página de componentes System que se llame PaintBox, con Align=alTop. Dejar
espacio por debajo del PaintBox para colocar un botón.
http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 1/24
5/12/2017 Programación Orientada a Objetos en C++

Colocar un bevel de ancho 4 y alinearlo en lo alto (Align=alTop). La idea es que


delimite la parte inferior del PaintBox.

Colocar un botón bit que permita terminar la ejecución del programa. El botón
estará centrado horizontalmente en la parte inferior del formulario.

Guardar el código del formulario como Ppal.cpp y el proyecto como


Ejemplo.bpr.

Crear una unidad (File | New | Unit). Guardarla con el nombre ObjGraf.cpp

Cuando se crea una unidad de esta manera se crean, en realidad, dos ficheros,
uno con extensión .cpp y otro con extensión .h. Así, disponemos de dos
ficheros: ObjGraf.h, que contendrá las declaraciones de las clases con las que
vamos a trabajar, y ObjGraf.cpp, que contendrá la definición (implementación de
los métodos) de éstas.

En ObjGraf.h:
//--------------------------------------------------

#ifndef ObjGrafH
#define ObjGrafH

// Definición de la clase TObjGraf

class TObjGraf {};

#endif

//--------------------------------------------------

Nótese que el nombre de la clase va precedido por una T, y, aunque no es obligatorio, si es


muy recomendable ya que es una convención de C++ Builder que casi todos los nombres
de clases vayan precedidos por T.

Muy Importante: Con el ejemplo anterior sólo conseguimos definir la clase, pero no se
crea ningún objeto.

5.1. El Paradigma de la POO en C++


Existen cuatro principios básicos que cualquier sistema orientado a objetos debe incorporar,
que se esquematizan en la figura 5.1.

Figura 5.1. Pilares de la POO.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 2/24
5/12/2017 Programación Orientada a Objetos en C++

5.2. Creación y Destrucción de Objetos


Ya se ha dicho que una clase es únicamente una especificación. Para poder utilizar la
funcionalidad contenida en la misma, se deben instanciar las clases.

Creación por Declaración.

Un objeto se puede instanciar de una forma simple, declarando una variable del
tipo de la clase.

En Ppal.h:
#include "ObjGraf.h"

En Ppal.cpp:

Pulsando dos veces en OnCreate de la pestaña Events del editor de objetos de


PpalFrm:

//--------------------------------------------------

void __fastcall TPpalFrm::FormCreate(TObject *Sender)


{
TObjGraf ObjGraf1();
TObjGraf ObjGraf2;
}

//--------------------------------------------------

Aunque esta forma es posible, y bastante utilizada en la


programación de C++ clásica, en C++ Builder se utiliza en muy
contadas ocasiones. Esto es así por dos razones, fundamentalmente:

La duración de los objetos suele ir más allá de una simple


función o bloque. Debido al enfoque de la programación
dirigida por eventos, suele ser habitual que un objeto se
cree en un gestor de eventos y se destruya en otro.
http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 3/24
5/12/2017 Programación Orientada a Objetos en C++

No se puede usar esta modalidad de creación con la VCL.

Por lo tanto, nosotros no la utilizaremos.

Creación Dinámica

Es la forma habitual de crear objetos en C++ Builder, y se realiza mediante el


operador new .

Cuando usamos new para instanciar un objeto, se usa una variable que referencie
o apunte al nuevo objeto creado (de otra manera éste quedaría totalmente
inaccesible). En definitiva, se requiere la declaración previa de un puntero a
objetos del tipo del que se va a crear.

En Ppal.cpp:
TObjGraf * ObjGraf; // Variable Global.
// ObjGraf es un puntero a objetos de tipo TObjGraf

//--------------------------------------------------

void __fastcall TPpalFrm::FormCreate(TObject *Sender)


{
ObjGraf = new TObjGraf;
}

//--------------------------------------------------

La forma de establecer el estado inicial o destruir las componentes de un objeto


se estudiarán en el apartado dedicado a Constructores y Destructores (sección
5.4).

¡Cuidado! Cuando se utiliza esta forma de instanciación de clases es


responsabilidad únicamente del programador la correcta destrucción de los
mismos.

Destrucción de objetos

Cuando un objeto deja de ser útil hay que eliminarlo. De esta manera la
aplicación recupera los recursos (memoria) que ese objeto había acaparado
cuando se creó.

La destrucción de objetos creados en tiempo de ejecución con new se realiza


mediante el operador delete.

En Ppal.cpp:

Pulsando dos veces en OnDestroy de la pestaña Events del editor de objetos de


PpalFrm:

//--------------------------------------------------

void __fastcall TPpalFrm::FormDestroy(TObject *Sender)


{
delete ObjGraf;
http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 4/24
5/12/2017 Programación Orientada a Objetos en C++
}

//--------------------------------------------------

5.3. Encapsulamiento
En la programación clásica (lenguaje C, p.e.) existen datos y procedimientos que actúan
sobre esos datos. No hay una relación aparente entre datos y procedimientos (funciones) y
esta relación se establece de manera más o menos pecisa de acuerdo a la profesionalidad del
programador.

En un objeto podemos distinguir dos aspectos bien diferenciados:

Estado -----------> Propiedades


Comportamiento ---> Métodos

En P.O.O. los datos y los procedimientos que los gestionan están relacionados
explícitamente y se "encapsulan" en un objeto. La especificación de las propiedades de un
objeto y los métodos de acceso se realiza en la declaración de la clase de la que se instancia
el objeto.

En la figura 5.2 esquematizamos las propiedades y métodos que se van a asociar a los
objetos de la clase TObjGraf:

Figura 5.2. Propiedades y métodos de los objetos de la clase TObjGraf.

La declaración de propiedades y métodos de los objetos de la clase TObjGraf se realiza de la


siguiente manera:

En ObjGraf.h:
//--------------------------------------------------

class TObjGraf {

public:
int X; // Propiedades
int Y;
TColor Color;
TPaintBox *PaintBox;

void Mostrar (void); // Métodos


};

//--------------------------------------------------

Acceso a Miembros de un Objeto

Para acceder a los miembros de un objeto se usan los operadores típicos de acceso a
miembros: el operador . para referencia directa al objeto y el operador -> para acceso a
http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 5/24
5/12/2017 Programación Orientada a Objetos en C++

través de un puntero. Como nosotros siempre creamos los objetos con new, y los
referenciamos mediante un puntero, el operador de acceso que utilizaremos es el operador -
>

En Ppal.cpp:
//--------------------------------------------------

void __fastcall TPpalFrm::FormCreate(TObject *Sender)


{
...
int ValorY;
...
ObjGraf->X = 5;
ValorY = ObjGraf->Y;
ObjGraf->Mostrar(); //Equivalente a (*Obj).Mostrar();
}

//--------------------------------------------------

Nota: Los puntos suspensivos no son una palabra reservada de C++, simplemente significan
que se omite una parte del código, ya sea porque es irrelevante o porque ya se ha expuesto
anteriormente.

5.4. Constructores y Destructores (Inicialización de Clases I)


Son métodos que permiten establecer el estado inicial y final de un objeto. Los
constructores se pueden definir con un conjunto de argumentos arbitrario, pero no pueden
devolver nada. Y los destructores no pueden recibir ni devolver ningún valor.

El constructor debe llamarse igual que la clase, y el destructor el nombre de la clase


precedido del carácter ~

Un constructor se ejecuta cuando se crea un nuevo objeto: 1) por declaración, ó 2) cuando


se crea dinámicamente con el operador new. Un destructor se ejecuta cuando el objeto deja
de existir: 1) porque su ámbito acaba, ó 2) cuando se libera explícitamente con el operador
delete.

En ObjGraf.h:
class TObjGraf {
...

// Constructor de objetos TObjGraf

TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0);

// El destructor sería: ~TObjGraf (void);


};

En ObjGraf.cpp:
TObjGraf :: TObjGraf (TPaintBox * _PaintBox, TColor _Color,
int _X, int _Y)
{
PaintBox = _PaintBox;
Color = _Color;
X = _X;

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 6/24
5/12/2017 Programación Orientada a Objetos en C++
Y = _Y;
}

En Ppal.cpp:
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
{
ObjGraf = new TObjGraf (PaintBox, clRed, 10, 10);
}

Importante: No es necesario escribir un destructor salvo si el objeto requiere memoria


dinámica adicional. De ser así, la tarea del destructor será, básicamente, liberar la memoria
dinámica que ocupa el objeto que se va a destruir.

5.5. Herencia
Cuando una clase hereda de otra, la clase derivada incorpora todos los miembros de la
clase base además de los suyos propios.

La herencia es una herramienta muy importante en muchos aspectos del desarrollo de


aplicaciones:

Organización del diseño.


Reusabilidad de clases (propias o no).
Mejora del mantenimiento.

Tomando como base la clase TObjGraf se van a construir dos nuevas clases, TCirculo y
TCuadrado, que derivan de TObjGraf. Esto significa que los objetos de estas clases tienen
asociados las propiedades y métodos de la clase base, TObjGraf, además de los suyos
propios. En la figura 5.3 esquematizamos el mecanismo de herencia para las nuevas clases y
las nuevas propiedades que se asocian a los objetos de las clases derivadas.

Figura 5.3. Las clases TCirculo y TCuadrado heredan las propiedades y métodos de la clase
TObjGraf.4

En ObjGraf.h:
//*************************************************/
// Definicion de la clase derivada TCirculo
// Deriva de la clase base TObjGraf
//*************************************************/

class TCirculo : public TObjGraf {

public:

int Radio; // Propiedad exclusiva de TCirculo

};

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 7/24
5/12/2017 Programación Orientada a Objetos en C++

//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/

class TCuadrado : public TObjGraf {

public:

int Lado; // Propiedad exclusiva de TCuadrado

};

Antes del nombre de la clase base hay que poner public, esto es así porque C++ permite
también la herencia private. Pero ésta no se suele usar, por lo que nosotros supondremos
que sólo existe la public.

5.5.1. Herencia de Constructores y Destructores (Inicialización de Clases II)

Los constructores y destructores de una clase no son heredadas automáticamente por sus
descendientes. Deberemos crear en las clases hijas sus propios constructores y destructores.
Es posible, no obstante, emplear los constructores de la clase base pero hay que indicarlo
explícitamente. De ser así, es necesario saber:

que los constructores y destructores de las clases base son invocados


automáticamente antes que los constructores de las clases derivadas, y
que los destructores de las clases derivadas se invocan antes que los de las clases
base.

Para determinar con qué parámetros se llaman a los constructores de las clases base, se
utiliza la lista de inicialización.

En ObjGraf.h:
//*************************************************/
// Definicion de la clase derivada TCirculo
// Deriva de la clase base TObjGraf
//*************************************************/

class TCirculo : public TObjGraf {

public:

int Radio; // Propiedad exclusiva de TCirculo

// Metodo constructor

TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0, int _Radio=1);

};

//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/

class TCuadrado : public TObjGraf {

public:
http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 8/24
5/12/2017 Programación Orientada a Objetos en C++

int Lado; // Propiedad exclusiva de TCuadrado

// Metodo constructor

TCuadrado (TPaintBox * _PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0, int _Lado=1);

};

En ObjGraf.cpp:
TCirculo :: TCirculo (TPaintBox * _PaintBox, TColor _Color,
int _X, int _Y, int _Radio) :
TObjGraf (_PaintBox, _Color, _X, _Y)
{
Radio = _Radio;
}

TCuadrado :: TCuadrado (TPaintBox * _PaintBox, TColor _Color,


int _X, int _Y, int _Lado) :
TObjGraf (_PaintBox, _Color, _X, _Y)
{
Lado = _Lado;
}

5.5.2. Clases Abstractas

Clase abstracta: es una clase que no está completamente especificada (posee métodos sin
implementar), por lo tanto no se pueden crear instancias de la misma. Una clase abstracta
se usa para servir de clase base a otras clases. En terminología C++ se dice que una clase
abstracta es aquella que posee al menos un método virtual puro.

Virtual: obliga a las clases derivadas a implementar ese método.


Puro: no pueden crearse instancias de esa clase.

En ObjGraf.h:
class TObjGraf {

public:
...

// Otros metodos

virtual void Mostrar (void) = 0; // Metodo virtual puro


};

class TCirculo : public TObjGraf {

public:
...

// Instanciacion del metodo virtual puro de la clase TObjGraf

void Mostrar (void);

};

class TCuadrado : public TObjGraf {

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 9/24
5/12/2017 Programación Orientada a Objetos en C++

public:
...

// Instanciacion del metodo virtual puro de la clase TObjGraf

void Mostrar (void);

};

En ObjGraf.cpp:
void TCirculo :: Mostrar (void)
{
PaintBox->Canvas->Pen->Color = Color;
PaintBox->Canvas->Brush->Color = Color;
PaintBox->Canvas->Ellipse(X, Y, X+Radio*2, Y+Radio*2);
}
...

void TCuadrado :: Mostrar (void)


{
PaintBox->Canvas->Pen->Color = Color;
PaintBox->Canvas->Brush->Color = Color;
PaintBox->Canvas->Rectangle(X, Y, X+Lado, Y+Lado);
}

¿Por qué se especifica el método Mostrar en TObjGraf, como virtual puro, en lugar de
omitirlo? Fundamentalmente podemos considerar dos razones para usar métodos virtuales
puros:

Para obligar a que las clases descendientes los implementen. De esta forma
estamos seguros de que todas las clases descendientes no abstractas de TObjGraf
poseen el método, y se podrá invocar con seguridad.

Para evitar que se puedan crear instancias de la clase abstracta.

En este estado, si probamos a ejecutar el programa, nos aparecerá un error: no se puede


crear una instancia de una clase abstracta. ¿Porqué?: Recordar que en Ppal.cpp el gestor
asociado al evento OnCreate del formulario está escrito como sigue:
//--------------------------------------------------

void __fastcall TPpalFrm::FormCreate(TObject *Sender)


{
ObjGraf = new TObjGraf (PaintBox, clRed, 10, 10);
};

//--------------------------------------------------

Así, creemos entonces objetos de las clases hijas:

En primer lugar, en Ppal.cpp hay que borrar la declaración de la variable global:


TObjGraf *ObjGraf; // Variable Global.

y en su lugar se declararán cuatro punteros, dos para referenciar a objetos de tipo


TCirculo y otros dos para referenciar a objetos de tipo TCuadrado:

// Punteros a objetos de las clases derivadas.

TCirculo *Cir1, *Cir2;


TCuadrado *Cuad1, *Cuad2;

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 10/24
5/12/2017 Programación Orientada a Objetos en C++

En segundo lugar, modificaremos la función FormCreate para que cree dos


objetos de cada clase referenciados por los punteros declarados anteriormente:
//--------------------------------------------------

void __fastcall TPpalFrm::FormCreate(TObject *Sender)


{
Cir1 = new TCirculo (PaintBox, clBlack, 100, 100, 30);
Cir2 = new TCirculo (PaintBox, clGreen, 210, 40, 20);
Cuad1 = new TCuadrado (PaintBox, clRed, 200, 150, 45);
Cuad2 = new TCuadrado (PaintBox, clYellow, 120, 70, 25);
};

//--------------------------------------------------

Finalmente, modificaremos la función FormDestroy para que elimine los objetos


creados:
//--------------------------------------------------

void __fastcall TPpalFrm::FormDestroy(TObject *Sender)


{
delete Cir1;
delete Cir2;
delete Cuad1;
delete Cuad2;
}

//--------------------------------------------------

Ahora, al ejecutar el programa se crean y se destruyen objetos de las clases derivadas,


aunque no se visualizan en la ventana. ¿Porqué? En ningún momento se ha llamado al
método Mostrar() asociado a cada objeto. Para mostrar los objetos, basta con indicarlo en el
gestor asociado al evento OnPaint del componente PaintBox:
//--------------------------------------------------

void __fastcall TPpalFrm::PaintBoxPaint(TObject *Sender)


{
Cir1->Mostrar();
Cir2->Mostrar();
Cuad1->Mostrar();
Cuad2->Mostrar();
}

//--------------------------------------------------

En este punto, el proyecto debe estar como se indica en el proyecto Ejemplo0.

El resultado es el mostrado en la figura 5.4:

4 Figura 5.4. Resultado del proyecto Ejemplo.bpr.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 11/24
5/12/2017 Programación Orientada a Objetos en C++

Ejercicio:

Construir la clase TTriangulo y modificar el proyecto para que proporcione un resultado


similar al de la figura 5.5.

Figura 5.5. Resultado del proyecto Ejemplo.bpr mostrando objetos de la clase TTriangulo.

5.5.3. Herencia Múltiple

La herencia múltiple es el hecho de que una clase derivada se genere a partir de varias
clases base.

Ejemplo:

En un concesionario de coches podríamos considerar la siguiente jerarquía de clases:


class TProducto {
long Precio;
...
};

class TVehiculo {

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 12/24
5/12/2017 Programación Orientada a Objetos en C++
int NumRuedas;
...
};

class TCocheEnVenta : public TProducto, public TVehiculo {


...
};

Observar que los objetos de la clase TCocheEnVenta derivan de las clases TProducto y
TVehiculo.

Existen dos formas para que una clase saque partido de las ventajas de otra, una es la
herencia, y la otra es que una clase contenga un objeto de la otra clase.

Ninguna de las dos posibilidades es mejor que la otra, en cada caso particular habrá que
estudiar cual es la mejor opción.

Por ejemplo, si quisieramos diseñar una clase (TMarco) que represente un marco
(representado por un cuadrado y un círculo), podemos decidir distintas estrategias a la hora
de llevarlo a cabo:

Que herede de TCirculo y TCuadrado.


Que herede de TObjGraf y contenga un objeto de la clase TCirculo y otro de
TCuadrado.
Que herede de TCirculo y contenga un objeto de la clase TCuadrado.
Que herede de TCuadrado y contenga un objeto de la clase TCirculo.

5.6. Abstracción
Es la ocultación de detalles irrelevantes o que no se desean mostrar. Podemos distinguir en
una clase dos aspectos desde el punto de vista de la abstracción:

Interfaz: lo que se puede ver/usar externamente de un objeto.


Implementación: cómo lleva a cabo su cometido.

Resumiendo: nos interesa saber qué nos ofrece un objeto, pero no cómo lo lleva a cabo.

5.6.1. Restricciones de acceso en C++

En C++ se puede especificar el acceso a los miembros de una clase utilizando los siguientes
especificadores de acceso:

public: Interfaz de la clase.


private: Implementación de la clase.
protected: Implementación de la familia.

Estos especificadores no modifican ni la forma de acceso ni el comportamiento, únicamente


controlan desde dónde se pueden usar los miembros de la clase:

public: desde cualquier sitio.


private: desde los métodos de la clase.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 13/24
5/12/2017 Programación Orientada a Objetos en C++

protected: desde los métodos de la clase y desde los métodos de las clases
derivadas.

En ObjGraf.h:

//*************************************************/
// Definicion de la clase base TObjGraf
//*************************************************/

class TObjGraf {

private: // Puede acceder SOLO los objetos de esta clase.

int X;
int Y;

protected: // Pueden acceder los objetos de esta clase y sus descendientes.

TColor Color;
TPaintBox * PaintBox;

public: // Pueden usarlas todas.

// Constructor de objetos TObjGraf

TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0);

// Otros metodos

virtual void Mostrar (void) = 0; // Metodo virtual puro

};

Modificar de la misma manera las clases TCirculo y TCuadrado para que sus propiedades
Radio y Lado queden protegidas y los métodos públicos:

//*************************************************/
// Definicion de la clase derivada TCirculo.
// Deriva de la clase base TObjGraf
//*************************************************/

class TCirculo : public TObjGraf {

protected: // Pueden acceder los objetos de esta clase y sus descendientes.

int Radio;

public:

// Metodo constructor

TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0, int _Radio=1);

void Mostrar (void); // Instanciacion del metodo virtual puro


// de la clase TObjGraf
};

//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/

class TCuadrado : public TObjGraf {

protected: // Pueden acceder los objetos de esta clase y sus descendientes.


http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 14/24
5/12/2017 Programación Orientada a Objetos en C++

int Lado;

public:

// Metodo constructor

TCuadrado (TPaintBox * _PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0, int _Lado=1);

void Mostrar (void); // Instanciacion del metodo virtual puro


// de la clase TObjGraf
};

Así, si en Ppal.cpp escribiéramos:


//--------------------------------------------------

void __fastcall TPpalFrm::FormCreate (TObject *Sender)


{
...
Cir1->Mostrar(); // Se puede.
Cir1->X = 10; // No se puede porque X es private.
}

//--------------------------------------------------

En realidad estos tres especificadores de acceso son los propios de C++, pero en C++
Builder existe otro adicional, que es el __published. No vamos a dar mucha importancia a
este modificador, porque su uso está restringido al IDE. Cuando en una clase veamos una
sección __published quiere decir que los miembros contenidos en la misma son mantenidos
automáticamente por el IDE y no deberemos modificar nada en dicha sección, ya que de lo
contrario los resultados pueden ser imprevisibles.

Es una buena técnica de programación no permitir el acceso público a las propiedades de un


objeto, ya que si esto ocurriera podría peligrar la integridad del objeto. ¿Entonces cómo se
puede cambiar el estado de un objeto desde el exterior?

Ofreciendo métodos (públicos) que se encarguen de modificar las propiedades


(privadas) que se desee. De esta manera son los métodos los que acceden a las
propiedades y el usuario de la clase sólo accede a través de ellos. Esta es la
técnica clásica que se emplea en C++

A través de los métodos y de las propiedades "virtuales". Esta técnica es


exclusiva de C++ Builder y la describimos en la siguiente sección.

5.6.2. Propiedades Virtuales

Son propiedades definidas mediante métodos de lectura (read) y/o escritura (write). Se
llaman virtuales porque, realmente, no existen. El usuario de la clase usa estas propiedades
como si fueran propiedades reales y en última instancia se traducen en la llamada a un
método o en el acceso a una propiedad real. Es más, si una propiedad virtual se usa para
lectura (p.e. en la parte derecha de una asignación) se traduce en una acción diferente que si
esa popiedad virtual se usa para escritura. La acción que se produce cuando la propiedad
virtual es de lectura se especifica, sintácticamente, mediante la palabra reservada read
mientras que si se usa para escritura se especifica con write. Veamos un ejemplo.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 15/24
5/12/2017 Programación Orientada a Objetos en C++

En ObjGraf.h:
//*************************************************/
// Definicion de la clase base TObjGraf
//*************************************************/

class TObjGraf {

private: // Puede acceder SOLO los objetos de esta clase.

int FX; // OJO: Se ha cambiado el nombre a


int FY; // los miembros X e Y por FX y FY.

void SetX (int _X);


void SetY (int _Y);
virtual int GetAncho (void) = 0; // Metodo virtual puro
virtual int GetAlto (void) = 0; // Metodo virtual puro

protected: // Pueden acceder los objetos de esta clase y descendientes.

TColor FColor; // OJO: Se ha cambiado Color por FColor


TPaintBox *PaintBox;

public: // Pueden usarlas todas.

// Constructor de objetos TObjGraf

TObjGraf (TPaintBox *_PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0);

// Otros metodos

virtual void Mostrar (void) = 0; // Metodo virtual puro

// NUEVAS FORMAS DE ACCESO CON PROPIEDADES VIRTUALES.

__property int X = {read=FX, write=SetX };


__property int Y = {read=FY, write=SetY };
__property TColor Color = {read=FColor, write=FColor};
__property int Ancho = {read=GetAncho };
__property int Alto = {read=GetAlto };

};

Observaciones:

Observar que la "antigua" propiedad X (resp. Y) se llama ahora FX (resp. FY).


Además, hay una propiedad virtual (pública) llamada X (resp. Y). Estas
propiedades están declaradas en la clase TObjGraf lo que significa que sus
descendientes también las heredan.

Si en Ppal.cpp se usara esta propiedad para lectura:


int CX = Cir1->X;

En realidad es como si se hiciera


int CX = Cir1->FX;

ya que cuando se accede para lectura a la propiedad (virtual) X en realidad se


accede a la propiedad (real) FX. La última instrucción, no obstante, povocaría un
error porque FX es una propiedad privada.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 16/24
5/12/2017 Programación Orientada a Objetos en C++

Si se usara esta propiedad para escritura:


Cir1->X = 100;

En realidad es como si se hiciera


Cir1->SetX(100);

ya que cuando se accede para escritura a la propiedad (virtual) X en realidad se


llama al método SetX(). La última instrucción, no obstante, povocaría un error
porque SetX() es un método privado. Al "redirigir" la escritura al método SetX()
podemos controlar la validez del parámetro y corregir, en su caso, el valor: una
ventaja adicional.

La propiedad virtual Color tiene asociado el mismo método de acceso para


lectura y escritura: devuelve o escribe, directamente, en la propiedad real FColor.

Finalmente, observar que las propiedades virtuales Ancho y Alto no tienen


asociados métodos de acceso para escritura.

Como hemos incorporado dos nuevos métodos a la clase base TObjGraf (GetAncho() y
GetAlto()) y éstos se han declarado virtuales puros necesitamos instanciarlos en las clases
derivadas:
//*************************************************/
// Definicion de la clase derivada TCirculo.
// Deriva de la clase base TObjGraf
//*************************************************/

class TCirculo : public TObjGraf {

protected: // Pueden acceder los objetos de esta clase y descendientes.

int Radio;

inline int GetAncho (void) {return(Radio*2);}


inline int GetAlto (void) {return(Radio*2);}

public:

// Metodo constructor

TCirculo (TPaintBox *_PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0, int _Radio=1);

void Mostrar (void); // Instanciacion del metodo virtual puro de


// la clase TObjGraf

};

//*************************************************/
// Definicion de la clase derivada TCuadrado.
// Deriva de la clase base TObjGraf
//*************************************************/

class TCuadrado : public TObjGraf {

protected: // Pueden acceder los objetos de esta clase y descendientes.

int Lado;

inline int GetAncho (void) {return(Lado);}


inline int GetAlto (void) {return(Lado);}
http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 17/24
5/12/2017 Programación Orientada a Objetos en C++

public:

// Metodo constructor

TCuadrado (TPaintBox * _PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0, int _Lado=1);

void Mostrar (void); // Instanciacion del metodo virtual puro de


// la clase TObjGraf
};

Ahora, añadimos (en ObjGraf.cpp) las funciones write de las propiedades virtuales X e Y:
// Funciones de escritura de las propiedades virtuales X e Y

void TObjGraf :: SetX (int _X)


{
if (_X < 0) // Coordenada negativa
FX = 0; // Ajustar al margen izquierdo
else
if (_X > (PaintBox->Width - Ancho)) // Demasiado alta
FX = PaintBox->Width - Ancho; // Ajustar al margen derecho
else
FX = _X; // Correcto: escribir sin modificar
}

void TObjGraf :: SetY (int _Y)


{
if (_Y < 0) // Coordenada negativa
FY = 0; // Ajustar al margen superior
else
if (_Y > (PaintBox->Height - Alto)) // Demasiado alta
FY = PaintBox->Height - Alto; // Ajustar al margen inferior
else
FY = _Y; // Correcto: escribir sin modificar
}

Muy importante: Cambiamos el constructor de la clase TObjGraf porque no se puede


llamar a los métodos virtuales puros de una propiedad virtual desde un constructor de una
clase base.

En este caso, no se puede llamar a los métodos virtuales puros (GetAncho() y GetAlto()) de
las propiedades virtuales (Ancho y Alto) desde el constructor de la clase base TObjGraf.

En ObjGraf.cpp:
TObjGraf :: TObjGraf (TPaintBox *_PaintBox,
TColor _Color, int _X, int _Y)
{
PaintBox = _PaintBox;
FColor = _Color; // MUY IMPORTANTE: Estas tres lineas han cambiado:
FX = _X; // No se puede llamar a los metodos virtuales puros
FY = _Y; // (GetAncho, GetAlto) para fijar el valor de una
// propiedad virtual (Alto, Ancho) desde un
// constructor de la clase base.
}

En este punto, el proyecto debe estar como se indica en el proyecto Ejemplo1.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 18/24
5/12/2017 Programación Orientada a Objetos en C++

El resultado el el mismo que el mostrado en la figura 5.4.

Experimentación con las propiedades virtuales.

Los constructores no verifican coordenadas.

Los objetos gráficos (Cir1, Cir2, Cuad1 y Cuad2) se crean con sus
correspondientes constructores, que en última instancia llaman al constructor de
la clase base para establecer los valores de las propiedades reales FX, FY, FColor y
PaintBox. La consecuencia es que si se crea un círculo, por ejemplo, con
coordenadas incorrectas no se corrigen ya que como el constructor de la clase
base no puede utilizar la propiedad virtual X para escritura no se emplea el
método SetX().

Como demostración, en la función FormCreate() de Ppal.cpp cambiar la línea:

Cir1 = new TCirculo (PaintBox, clBlack, 100, 100, 30);

por:

Cir1 = new TCirculo (PaintBox, clBlack, -100, 100, 30);

Observar que el círculo negro no se muestra.

Lectura/escritura a través de propiedades virtuales.

Modificar la función PaintBoxPaint() de Ppal.cpp añadiendo esta línea al


principio:

if (Cir1->X < 0) Cir1->X = 100;

Observar que la propiedad virtual X se usa para lectura y escritura. La línea


anterior fija la coordenada X al valor 100.

Un ejemplo más clarificador es cambiar la línea anterior por:

if (Cir1->X < 0) Cir1->X = -200;

Observar que el círculo negro tiene el valor 0 en la coordenada X: este valor se ha


fijado a través del método de escritura SetX().

5.7. Polimorfismo
Es demostrar comportamientos distintos según la situación. Puede darse de tres formas
diferentes:

Funciones: sobrecarga.

Clases: es al que se refiere normalmente el concepto de polimorfismo.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 19/24
5/12/2017 Programación Orientada a Objetos en C++

Enlace dinámico: métodos virtuales.

5.7.1.Sobrecarga de funciones.

Ocurre cuando en una clase existen dos métodos con idéntico nombre pero con distinta lista
de parámetros. El compilador los considera como dos métodos distintos y aplicará cada uno
de ellos en la situación apropiada.

Por ejemplo, podría sobrecargarse el constructor de la clase TObjGraf añadiendo un nuevo


constructor de copia:

En ObjGraf.h:
TObjGraf (TObjGraf *ObjGraf);

En ObjGraf.cpp:
TObjGraf::TObjGraf (TObjGraf *ObjGraf)
{
PaintBox = ObjGraf->PaintBox;
FColor = ObjGraf->Color;
FX = ObjGraf->FX;
FY = ObjGraf->FY;
}

5.7.2. Polimorfismo en las clases y métodos virtuales.

Una clase se puede comportar como cualquiera de sus antecesoras (en la asignación por
ejemplo). Como tenemos variables (punteros) que pueden contener objetos de distintas
clases, el compilador no sabe qué tipo de objeto es al que realmente apunta la variable (en
tiempo de compilación) por lo tanto hay retrasar el enlace a tiempo de ejecución.

El enlace dinámico es retrasar el enlace de una llamada a un método (función) al tiempo de


ejecución.

Para ilustrar el polimorfismo, crearemos una nueva clase, TPelota, que derive de la clase
TCirculo. Una pelota (un objeto de tipo TPelota, para ser más precisos) es un círculo que
tiene la capacidad de movimiento. Para implementar el movimiento de una pelota
necesitamos incorporar nuevas propiedades y métodos propios a la clase TPelota. Sin
embargo, en este momento nos interesa poner de manifiesto el polimorfismo, lo que
conseguimos a través del método Mostrar() asociado a la clase TPelota. Antes,
modificamos la declaración del método Mostrar() de la clase TCirculo para obligar a sus
descendientes a implementar su propio método Mostrar(): basta con indicar que en el
método Mostrar() de la clase TCirculo es virtual.

En ObjGraf.h:
//*************************************************/
// Definicion de la clase derivada TCirculo.
// Deriva de la clase base TObjGraf
//*************************************************/

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 20/24
5/12/2017 Programación Orientada a Objetos en C++
class TCirculo : public TObjGraf {

private:
...

public:
...

// Ahora, el metodo Mostrar() se declara virtual, aunque no es puro:


// 1) Por ser virtual: cualquier clase que derive de TCirculo debe
// tener su propio metodo Mostrar(),
// 2) Por no ser puro: puede llamarse a este metodo con objetos TCirculo.

virtual void Mostrar (void);


...
};

Ahora nos centramos en la nueva clase TPelota. Antes, por comodidad y claridad, definimos
un tipo por enumeración para la dirección del movimiento:

En ObjGraf.h:
// Tipo definido por enumeracion para la direccion de TPelota. Codificacion:
/*

NO N NE
10 2 6
\ | /
O 8 --- * --- 4 E
/ | \
9 1 5
SO S SE
*/

enum TDireccion {S=1, N=2, E=4, O=8, SE=5, NE=6, SO=9, NO=10};

La declaración de la clase TPelota se hará en ObjGraf.h:


//*************************************************/
// Definicion de la clase derivada TPelota.
// TPelota deriva de la clase TCirculo, que a su
// vez deriva de la clase base TObjGraf
//*************************************************/

class TPelota: public TCirculo {

private:

int FDirX; // Dir. en el eje X


int FDirY; // Dir. en el eje Y
int FVelocidad; // Velocidad del movimiento

void SetDireccion (TDireccion _Direccion);


TDireccion GetDireccion (void);

public:

// Constructores

TPelota (TPaintBox *_PaintBox, TColor _Color=clBlack,


int _X=0, int _Y=0, int _Radio=1,
TDireccion _Direccion=SE, int _Velocidad=5);

// Otros metodos

void Mostrar (void);


void Borrar (void);
void Mover (void);
http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 21/24
5/12/2017 Programación Orientada a Objetos en C++

__property int Velocidad = {read = FVelocidad,


write= FVelocidad};
__property TDireccion Direccion = {read = GetDireccion,
write= SetDireccion};
};

La implementación de los métodos propios de la clase TPelota se hará en ObjGraf.cpp:

/*****************************************************/
// Metodos asociados a la clase derivada TPelota.
// TPelota deriva de la clase TCirculo, que a su
// vez deriva de la clase base TObjGraf
/*****************************************************/

TPelota :: TPelota (TPaintBox *_PaintBox, TColor _Color, int _X,


int _Y, int _Radio, TDireccion _Direccion, int _Velocidad) :
TCirculo (_PaintBox, _Color, _X, _Y, _Radio)
{
Direccion = _Direccion;
Velocidad = _Velocidad;
}

// Instanciacion del metodo virtual puro de la clase TObjGraf


// y virtual en TCirculo.

void TPelota :: Mostrar (void)


{
PaintBox->Canvas->Pen->Color = clBlack; // Observar la diferencia
PaintBox->Canvas->Brush->Color = Color;
PaintBox->Canvas->Ellipse(X, Y, X+Radio*2, Y+Radio*2);
}

// Otras funciones propias de TPelota

void TPelota :: Borrar (void)


{
PaintBox->Canvas->Pen->Color = PaintBox->Color;
PaintBox->Canvas->Brush->Color = PaintBox->Color;
PaintBox->Canvas->Ellipse(X, Y, X+Radio*2, Y+Radio*2);
}

void TPelota :: Mover (void)


{
Borrar ();
X += FDirX * Velocidad;
Y += FDirY * Velocidad;
Mostrar ();
}

void TPelota :: SetDireccion (TDireccion _Dir)


{
FDirY = (_Dir & 1) ? +1 : ((_Dir & 2) ? -1 : 0);
FDirX = (_Dir & 4) ? +1 : ((_Dir & 8) ? -1 : 0);
}

TDireccion TPelota :: GetDireccion (void)


{
TDireccion _Dir;

_Dir = (TDireccion) ((FDirY == +1) ? 1 : ((FDirY == -1 ) ? 2 : 0));


_Dir = (TDireccion) (_Dir + (FDirX == +1) ? 4 : ((FDirX == -1 ) ? 8 :0));

return (_Dir);
}

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 22/24
5/12/2017 Programación Orientada a Objetos en C++

Finalmente, para ilustrar el polimorfismo nos basaremos en la existencia de diferentes


métodos con el mismo nombre: Mostrar(), que provocan diferentes acciones dependiendo
del tipo de objetos al que se aplican.

Vamos a crear dinámicamente cuatro objetos de diferentes clases. Estos objetos van a ser
referenciados (mediante punteros) desde un vector de objetos de tipo TObjGraf *. El
polimorfismo se va a manifestar invocando a la función Mostrar() para cada uno de esos
objetos.

En Ppal.cpp declararemos la variable global:


TObjGraf **Objs;

que se interpreta como: Objs es un puntero a una zona de memoria que contendrá punteros a
objetos de tipo TObjGraf.

Así, en Ppal.cpp:
//----------------------------------------------------------------------

void __fastcall TPpalFrm::FormCreate(TObject *Sender)


{
Objs = new TObjGraf * [4];

Objs[0] = new TCirculo (PaintBox, clBlack, 100, 100, 30);


Objs[1] = new TCuadrado (PaintBox, clRed, 200, 150, 45);
Objs[2] = new TCirculo (PaintBox, clGreen, 210, 40, 20);
Objs[3] = new TPelota (PaintBox, clYellow, 120, 70, 25);
}

//----------------------------------------------------------------------

void __fastcall TPpalFrm::FormDestroy(TObject *Sender)


{
for (int i=0; i<4; i++)
delete Objs[i];
delete []Objs;
}

//----------------------------------------------------------------------

void __fastcall TPpalFrm::PaintBoxPaint(TObject *Sender)


{

for (int i=0; i<4; i++)


Objs[i]->Mostrar(); // POLIMORFISMO
}

//----------------------------------------------------------------------

En este punto, el proyecto debe estar como se indica en el proyecto Ejemplo2.

El resultado es el mostrado en la figura 5.6:

Figura 5.6. Resultado del proyecto Ejemplo.bpr.

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 23/24
5/12/2017 Programación Orientada a Objetos en C++

Página principal

© Francisco Cortijo Bon

http://elvex.ugr.es/decsai/builder/intro/5.html#CREAC_DEST_OBJETOS 24/24

Das könnte Ihnen auch gefallen