• No se han encontrado resultados

Tema 5. Programación Orientada a Objetos en C++ Programación Orientada a Objetos Curso 2010/2011

N/A
N/A
Protected

Academic year: 2021

Share "Tema 5. Programación Orientada a Objetos en C++ Programación Orientada a Objetos Curso 2010/2011"

Copied!
131
0
0

Texto completo

(1)

Tema 5

Programación Orientada a Objetos

Curso 2010/2011

(2)

Contenido



Introducción



Clases y objetos



Herencia



Herencia múltiple



Genericidad



Colecciones



Definición de estrategias. Punteros a función.



Corrección y robustez en C++

 Asertos

(3)

Introducción

 Creado por Bjarne Stroustrup en los 80.

 Diseñado como una extensión de C que incluye

características orientadas a objetos.

 Es un lenguaje híbrido.

 Ha sido inspiración de lenguajes de programación

posteriores como Java y C#.

 A finales de los 90 fue estandarizado: ISO C++

 Las librerías del lenguaje son escasas. La librería más

notable es STL (Standard Template Library).

 Actualmente, sigue siendo un lenguaje de programación

(4)

Clases y Objetos en C++

 Este apartado trata:

 Definición de clases.

 Visibilidad y clases amigas.  Espacios de nombres.

 Tipos del lenguaje.

 Constructores y destructores.  Semántica de los objetos.

 Semántica de los operadores de asignación e igualdad.  Instancia actual.

 Métodos y mensajes. Paso de parámetros.  Función main.

(5)

Clases



Los elementos que definen una clase son:

 Atributos, Métodos y Constructores.



A diferencia de Java:

 Se separa:

 Definición de una clase (fichero cabecera .h)  Implementación (fichero código fuente .cpp).

 No es obligatorio que los ficheros fuente correspondan

con las clases.

 Un fichero puede implementar varias clases.

 Para poder utilizar o implementar una clase hay que

(6)

Clase Cuenta - Especificación

Cuenta.h Definición de la clase cuenta Banco.cpp #include “Cuenta.h” utiliza Cuenta Cuenta.cpp #include “Cuenta.h” implementa Cuenta

(7)

Cuenta.h (1/2)

#include “Persona.h”

class Cuenta {

public:

void reintegro(double cantidad);

void ingreso(double cantidad);

double getSaldo() const;

Persona* getTitular() const;

double* getUltimasOperaciones(int n) const;

static int getNumeroCtas();

(8)

Cuenta.h (2/2)

private:

const static int MAX_OPERACIONES = 20;

const static double SALDO_MINIMO = 100;

Persona* const titular;

double saldo;

int codigo;

static int ultimoCodigo;

double* ultimasOperaciones;

bool puedoSacar(double cantidad); };

(9)

Cuenta.cpp

int Cuenta::ultimoCodigo = 0;

void Cuenta::reintegro(double cantidad){

if (puedoSacar(cantidad)) {

saldo = saldo - cantidad; }

}

void Cuenta::ingreso(double cantidad){

saldo = saldo + cantidad; }

bool Cuenta::puedoSacar(double cantidad){

return (saldo >= cantidad); }

(10)

Clases



Los miembros de una clase pueden ser de

instancia

(por defecto) o de

clase

, utilizando el modificador

static

( = Java).



Las

constantes

se declaran

const static

.



Método de consulta

que no va a modificar el estado

se marca utilizando también el modificador const.

 Ejemplo: double getSaldo() const;



En la implementación, los nombres de los miembros

son calificados con la clase:

(11)

Modificador const en los atributos



El modificador const se utiliza para proteger los

atributos, el estado de los objetos o ambos.



Persona

*

const

titular

;

 const protege el atributo.

 titular es un atributo de sólo lectura (= final de Java)



const

Persona

*

titular

;

 const protege el objeto apuntado por el atributo.  El objeto Persona no puede cambiar de estado.



const

Persona

*

const

titular

;

(12)

Modificador const en los atributos



Persona

*

const

titular

;

Pepito

23456789

20

600.0

saldo ultimasOperaciones codigo titular

(13)

Modificador const en los atributos



const

Persona

*

titular

;

Pepito

23456789

20

600.0

saldo ultimasOperaciones codigo titular

(14)

Modificador const en los atributos



const

Persona

*

const

titular

;

Pepito

23456789

20

600.0

saldo ultimasOperaciones codigo titular

(15)

Visibilidad de las declaraciones



El nivel de visibilidad se especifica para un grupo

de miembros (declaraciones):

 public: visible para todo el código.  private: visible sólo para la clase.



Clases amigas:

 En C++ no se existe el concepto de paquete.

 En la declaración de una clase podemos indicar el

nombre de otras clases con acceso total a la clase.

 El privilegio de amistad de una clase no es transitivo ni

(16)

Clases amigas

class Arbol{ private: NodoArbol *raiz; ... … raiz->valor = 50; ... }; class NodoArbol {

friend class Arbol;

private: int valor; NodoArbol decha; NodoArbol izda; … };

(17)

Estructuras

 Unidad modular heredada de C.

 En C++ equivale al concepto de clase salvo:

 Cambia la palabra class por struct

 Por defecto todos los miembros son públicos excepto que se

diga lo contrario.

struct Cuenta{

void ingreso (double cantidad);

void reintrego (double cantidad);

private:

int codigo;

Persona* const titular;

(18)

Espacios de nombres

 Un espacio de nombres (namespace) es un mecanismo para

agrupar un conjunto de elementos (clases, enumerados, funciones, etc.) relacionados.

 Evita la colisión de los nombres de identificadores.  El orden de declaración de elementos es relevante.  Puede estar definido en varios ficheros.

 Es un concepto diferente a los paquetes de Java:  No hay relación entre la estructura lógica y física.

(19)

Espacios de nombres



Para utilizar un elemento definido en un espacio

de nombres:



Se utiliza el nombre calificado del elemento:

 gestionCuentas::Cuenta



Se declara el uso del espacio de nombres:

 using namespace gestionCuentas;



No existen elementos privados en un espacio de

(20)

Espacios de nombres

namespace gestionCuentas{ class Cuenta { … }; } namespace gestionCuentas{ class Banco { … }; } 

En Cuenta.h



En Banco.h

(21)

Tipos del lenguaje

 Tipos de datos primitivos:

 byte, short, int, long,float, double,char, bool,

etc.

 Enumerados:

 enum Estado {OPERATIVA, INMOVILIZADA, NUM_ROJOS};  Objetos.

 Punteros:

 Cuenta* es el tipo “puntero a Cuenta”

 Una variable de tipo Cuenta* puede contener la dirección de

un objeto de tipo Cuenta.

 Un puntero a un objeto es un concepto similar a una

(22)

Arrays

 En la declaración de un array se indica su tamaño ( = C):  int array[10];

 Si el tipo es una clase, es necesario que la clase tenga un

constructor sin parámetros:

 Posicion posiciones[4];

 La declaración y uso de un array al estilo Java sería:  Cuenta** cuentas = new Cuenta*[10];  Importante: los punteros del array no están

inicializados.

(23)

Enumerados

 Es un tipo que puede almacenar un conjunto de valores.

 El enumerado define un conjunto de constantes de tipo

entero.

 Por defecto los valores se asignan de forma creciente

desde 0.

(24)

Enumerados – Definición

namespace banco{

enum EstadoCuenta{

OPERATIVA, INMOVILIZADA, NUM_ROJOS

//OPERATIVA == 0, INMOVILIZADA == 1, NUM_ROJOS == 2

}; class Cuenta { private: … EstadoCuenta estado; }; }

(25)

Enumerados – Uso

 En Cuenta.cpp

 Para referenciar un valor del enumerado no tiene que ir

precedido por el nombre del enumerado. #include "Cuenta.h"

using namespace banco;

void Cuenta::reintegro(double cantidad){

if (estado!=INMOVILIZADA && puedoSacar(cantidad)){

saldo = saldo - cantidad; }

(26)

Constructores

 No se puede asignar un valor inicial a los atributos en el

momento de la declaración a menos que sean const y static (constantes).

 Por tanto, no podemos asegurar que los atributos tengan

un valor inicial.

(27)

Constructores

 Método especial con el mismo nombre que la clase y sin

valor de retorno ( = Java).

 Está permitida la sobrecarga (= Java).

 Si no existe ningún constructor en la clase, el compilador

proporciona el constructor por defecto (= Java).

 Los atributos const se tienen que inicializar en línea.

class Cuenta {

public:

Cuenta (Persona *titular);

Cuenta (Persona *titular, double saldoInicial); …

(28)

Constructores - Clase Cuenta

Cuenta::Cuenta (Persona *persona, double saldoInicial) :titular(persona) {

codigo = ++ultimoCodigo;

saldo = saldoInicial;

ultimasOperaciones = new double[Cuenta::MAX_OPERACIONES];

numOperaciones = 0;

estado = OPERATIVA; }

Cuenta::Cuenta (Persona *persona) :titular(persona) {

(29)

Constructores

 A diferencia de Java, no se utiliza la palabra clave this

para la reutilización de constructores.

 Soluciones:

 Utilizar un método privado que sea invocado desde los

constructores.

(30)

Reutilización de constructores

Cuenta::Cuenta (Persona *persona): titular(persona) { inicializa(Cuenta::SALDO_MINIMO);

}

Cuenta::Cuenta (Persona *persona, double saldoInicial)

:titular(persona) { inicializa(saldoInicial);

}

void Cuenta::inicializa(double saldoInicial) {

codigo = ++ultimoCodigo;

saldo = saldoInicial;

ultimasOperaciones = new double[Cuenta::MAX_OPERACIONES];

estado = OPERATIVA; }

(31)

Reutilización de constructores

 En cualquier función, método o constructor podemos

declarar argumentos con valores por defecto.

 Los argumentos por defecto deben aparecer juntos al final

de la lista de argumentos.

 Los valores por defecto de los argumentos se indican en la

declaración de la función (fichero .h)

 Ejemplo: Cuenta

 Definimos un único constructor.

 Para el argumento saldoInicial establecemos un valor por

(32)

Reutilización de constructores



Fichero cabecera:

 Las siguientes creaciones son equivalentes:  Cuenta(titular);

 Toma como valor del saldo el valor por defecto (100)  Cuenta (titular, 200);

 El parámetro establece el valor del saldo a 200

class Cuenta {

public:

Cuenta (Persona *titular, double saldoInicial = SALDO_MINIMO); …

(33)

Destructores

 A diferencia de Java, no existe un mecanismo automático

de liberación de memoria dinámica.

 Hay que definir destructores de objetos.

 Un destructor es un método sin argumentos con el mismo

nombre de la clase, precedido por ~.

 Se invoca automáticamente cada vez que se libera la

memoria del objeto.

 Libera memoria y recursos adquiridos por el objeto:

 El uso habitual es liberar la memoria adquirida en el

(34)

Destructor – Clase Cuenta



Cuenta.h:



Cuenta.cpp:

class Cuenta {

public:

Cuenta (Persona *titular, double saldoInicial= SALDO_MINIMO);

~Cuenta(); … }; Cuenta::~Cuenta(){ delete[] ultimasOperaciones; }

(35)

Semántica de los objetos

Los objetos pueden declararse de dos modos:



Por referencia

: se declara un puntero a un objeto.

Los objetos se crean con el operador new (= Java).

 Cuenta* cuenta;



Por valor

: la declaración del objeto reserva la

memoria y lo inicializa (llama a un constructor).

(36)

Semántica de los objetos

 Semántica referencia:

Cuenta* cuenta;

cuenta = new Cuenta(persona, 1000);

 El valor null de Java está definido como una constante en

C++: NULL.

 El objeto se destruye con el operador delete:

delete cuenta;

 Semántica valor:

 Cuenta cuenta(persona, 1000);

 En la declaración de la variable también se construye el

objeto.

(37)

Objetos embebidos



La semántica valor permite definir

objetos embebidos

dentro de otros objetos.

Pepito

23456789

María

34546789

20

600.0

saldo autorizado ultimasOperaciones codigo titular

(38)

Objetos embebidos

 La declaración del atributo autorizado es un objeto

embebido. class Cuenta { public: private: Persona autorizado;

Persona* const titular;

double saldo;

int codigo;

EstadoCuenta estado;

(39)

Objetos embebidos

La declaración de objetos embebidos sólo es posible si

se cumple una de las siguiente condiciones:



La clase del objeto embebido tiene un constructor sin

argumentos.



Se realiza una inicialización en línea en el

(40)

Objetos embebidos

 El objeto se inicializa en línea:

 La inicialización en línea es obligatoria si la clase del

objeto embebido no tiene constructor sin argumentos.

Cuenta::Cuenta (Persona* persona,

string nombreAut, string dniAut,

double saldoInicial):autorizado(nombreAut, dniAut),

titular(persona) {

codigo = ++ultimoCodigo;

(41)

Operador de asignación (=)

Cuenta* cta1;

Cuenta cta3;

Cuenta* cta2;

Cuenta cta4;



Semántica:



cta1 = cta2;

 copia de punteros (= Java)



cta3 = cta4;

 copia campo a campo de los

valores de cta4 en cta3

(copia superficial)

(42)

Asignación con semántica valor

Pepito

23456789

20

600.0

saldo codigo titular cuenta1

Juanito

23456780

45

300.0

saldo codigo titular cuenta2 cuenta1 = cuenta2

(43)

Asignación con semántica valor

Pepito

23456789

45

300.0

saldo codigo titular cuenta1

Juanito

23456780

45

300.0

saldo codigo titular cuenta2 cuenta1 = cuenta2



cuenta1.titular = cuenta2.titular cuenta1.codigo = cuenta2.codigo

(44)

Operador de asignación y atributos const



Problema

:

Cuenta cuenta1(persona1);

Cuenta cuenta2(persona2);

cuenta1 = cuenta2;

 En la clase Cuenta el atributo titular se ha declarado

de sólo lectura (const).

 Al hacer una asignación entre objetos cuenta con

semántica valor se hace una copia superficial de los atributos.

 Se intentaría hacer una copia de los titulares. Esto es,

cambiar el valor del atributo titular de cuenta1 por el valor del atributo titular de cuenta2.

(45)

Asignación con semántica valor y atributo const

Pepito

23456789

45

300.0

saldo codigo titular cuenta1

Juanito

23456780

45

300.0

saldo codigo titular cuenta1 = cuenta2 ERROR!! cuenta1.titular = cuenta2.titular cuenta1.titular está protegido.

(46)

Operador de igualdad (==)

Cuenta* cta1;

Cuenta cta3;

Cuenta* cta2;

Cuenta cta4;



Semántica:



cta1 == cta2;

 igualdad de punteros, identidad

(= Java)

(47)

Instancia actual

 Al igual que en Java, se define la palabra reservada this

que es un puntero a la instancia actual. void Cuenta::ingreso(double cantidad) {

this->saldo = this->saldo + cantidad; }

(48)

Métodos y mensajes

 Al igual que en Java, los métodos definidos en una clase son

los mensajes aplicables sobre los objetos de la clase.

 Está permitida la sobrecarga de métodos declarados dentro

de la misma clase.

 La aplicación de un mensaje varía según si el objeto es

manejado por valor o referencia:

 ‘.’ (notación punto) el receptor es un objeto por valor  ‘->’ (notación flecha) el receptor es un puntero a objeto

 Si no se indica el objeto receptor, la llamada se realiza sobre

(49)

Mensajes

Cuenta objCta;

Cuenta* ptroCta;



Notación punto para objetos

objCta.reintegro(1000);

(*ptrCta).reintegro(1000);



Notación “flecha” para punteros:

ptroCta->reintegro(1000); (&objCta)->reintegro(1000);

(50)

Paso de parámetros

 Paso por valor  el parámetro formal es una copia del

parámetro real ( = Java ).

 Paso por referencia  el parámetro formal referencia al

parámetro real.

 Además, hay que tener en cuenta que los objetos pueden tener

semántica valor o semántica referencia.

void Cuenta::reintegro(double cantidad) { } double v = 3000.0; cuenta.reintegro(6000.0); cuenta.reintegro(v);

Parámetro

formal

Parámetro

real

(51)

Paso por valor con semántica referencia

 Paso por valor del puntero ( = Java).

 El estado de cuenta1 y cuenta2 cambia.

 El nuevo puntero de emisor no afecta al parámetro real

void Banco::transferencia1(Cuenta* emisor, Cuenta* receptor,

double cantidad){

emisor->reintegro(cantidad);

receptor->ingreso(cantidad);

emisor = new Cuenta(emisor->getTitular()); }

(52)

Paso por valor con semántica valor

 El estado de los parámetros reales cuenta1 y cuenta2

no cambia.

 emisor y receptor son una copia de los objetos con

semántica valor que se pasan como parámetro.

void Banco::transferencia2(Cuenta emisor, Cuenta receptor,

double cantidad){ emisor.reintegro(cantidad); receptor.ingreso(cantidad); } … banco.transferencia(cuenta1, cuenta2, 3000.0);

(53)

Paso por referencia con semántica referencia

 Paso por referencia del puntero.

 El cambio del puntero en el parámetro formal (receptor)

afecta al parámetro real (cuenta2).

void Banco::transferencia3(Cuenta* emisor, Cuenta*& receptor,

double cantidad){

emisor->reintegro(cantidad);

receptor->ingreso(cantidad);

receptor = new Cuenta(receptor->getTitular()); }

(54)

Paso por referencia con semántica valor

 Paso por referencia del objeto con semántica valor

cuenta2.

 El cambio en receptor (estado y parámetro) afecta a

cuenta2.

void Banco::transferencia4(Cuenta* emisor, Cuenta& receptor,

double cantidad){ emisor->reintegro(cantidad); receptor.ingreso(cantidad); Cuenta otraCuenta; receptor = otraCuenta; } … banco.transferencia(cuenta1, cuenta2, 3000.0);

(55)

Parámetros const

 Pasar un objeto grande por referencia es más eficiente

que pasarlo por valor, pero se corre el riesgo de modificar el objeto.

 Solución: utilizar la palabra reservada const para indicar

que no se puede modificar el estado del objeto.

 const se puede utilizar en otras situaciones diferentes.  Existen 8 combinaciones de paso de parámetros por

valor o por referencia de un objeto con semántica valor o referencia en el que el modificador const protege el objeto o el parámetro.

(56)

Parámetros const en paso por valor con

semántica referencia

void transferencia11(Cuenta* emisor,

const Cuenta* receptor, double cantidad);

 Se protege el estado del objeto cuenta apuntado por

receptor.

void transferencia12(Cuenta* emisor,

Cuenta* const receptor, double cantidad);

 Equivale a los parámetros final de Java.  Se protege el parámetro formal.

 No sería válida la sentencia receptor = NULL; en el cuerpo

(57)

Parámetros const en paso por valor con

semántica valor

void transferencia21(Cuenta* emisor,

const Cuenta receptor, double cantidad);

void transferencia22(Cuenta* emisor,

Cuenta const receptor, double cantidad);

 Ambas declaraciones son equivalentes puesto que con

semántica valor el estado del objeto es equivalente al valor del parámetro.

(58)

Parámetros const en paso por referencia con

semántica referencia

void transferencia31(Cuenta* emisor,

const Cuenta*& receptor, double cantidad);

 Se protege el estado del objeto cuenta apuntado por

receptor.

void transferencia32(Cuenta* emisor,

Cuenta*& const receptor, double cantidad);

 No compila!!

 No tiene sentido proteger un parámetro que se ha declarado

(59)

Parámetros const en paso por referencia con

semántica valor

void transferencia41(Cuenta* emisor,

const Cuenta& receptor, double cantidad);

 Se protege el estado del objeto cuenta receptor.

 Desde el punto de vista del paso de parámetros, tiene poco sentido

pasar un objeto con semántica valor por referencia y utilizar const para evitar la modificación. No obstante, se utiliza por motivos de eficiencia.

void transferencia42(Cuenta* emisor,

Cuenta& const receptor, double cantidad);

 No compila!!

(60)

Función main

 Sólo puede haber un punto de entrada en el espacio raíz.  Sólo exige declarar una función con nombre main, y valor

de retorno int.

 Opcionalmente puede tener un parámetro con los

argumentos del programa.

 Ejemplos:

 int main(char* args[])  int main()

(61)

Herencia en C++

 Este apartado trata:

 Diferencias entre herencia C++ y Java.  Tipos de herencia: pública y privada.  Herencia y constructores.

 Redefinición y métodos virtuales.

 Polimorfismo y ligadura (dinámica y estática).  Conversión de tipos.

 Compatibilidad de tipos.

 Redefinición de la copia e igualdad de objetos. Operadores.  Clases abstractas.

(62)

Herencia

 La herencia en C++ es diferente a Java en varios aspectos:

 Herencia múltiple: una clase puede heredar de varias clases.

 Herencia privada: heredar de una clase sólo el código, pero

no el tipo.

 Ligadura de métodos: los métodos pueden ser invocados por

ligadura dinámica y estática.

(63)

Clase Deposito

class Deposito { private: Persona* titular; double capital; int plazoDias; double tipoInteres; public: Deposito(…); double liquidar(); double getIntereses(); double getCapital(); int getPlazoDias(); double getTipoInteres(); Persona* getTitular();

(64)

Clase Deposito Estructurado

class DepositoEstructurado : public Deposito {

private:

double tipoInteresVariable;

double capitalVariable;

public:

DepositoEstructurado(Persona titular, …,

double capitalVariable);

double getInteresesVariable();

void setTipoInteresVariable(double interesVariable);

double getTipoInteresVariable();

double getCapitalVariable();

(65)

Tipos de herencia

 Herencia pública (= Java): class B: public A

 La clase hereda los atributos y métodos de la clase padre.

 La visibilidad de las declaraciones heredadas puede aumentar

y disminuir al declararlas en el fichero cabecera.

 Herencia privada: class B: private A

 Las declaraciones visibles para la clase hija cambian su

visibilidad a privada.

 Se puede aumentar la visibilidad en el fichero cabecera.

 No se hereda el tipo: los objetos de la clase no pueden ser

asignados a variables polimórficas cuyo tipo estático sea la clase padre.

(66)

Niveles de visibilidad

 Al igual que en Java, los niveles de visibilidad son

incrementales

private

protected

public

public  todo el código

protected  clase + subclases private  clase

(67)

Herencia y constructores

 Los constructores no se heredan ( = Java )

 El constructor de la clase hija tiene que invocar al de la

clase padre.

DepositoEstructurado::DepositoEstructurado(Persona titular,

double capital, int plazoDias,

double tipoInteres, double tipoInteresVariable,

double capitalVariable): Deposito(titular, capital,

plazoDias, tipoInteres) {

this.tipoInteresVariable = tipoInteresVariable;

this.capitalVariable = capitalVariable; }

(68)

Redefinición de métodos

 Cualquier método que sea visible puede redefinirse en una

subclase.

 Método virtual: método que al redefinirse en las

subclases puede resolverse por ligadura dinámica (modificador virtual).

 Para redefinir un método (virtual o normal):

 Hay que declararlo en la subclase (fichero cabecera) con la

misma signatura e implementarlo en el fichero de implementación.

 Para refinar un método en el que es necesario llamar a la

versión del padre:

 NombreClase::NombreMetodo

(69)

Redefinición de métodos

class Deposito { private: Persona* titular; double capital; int plazoDias; double tipoInteres; public: Deposito(…); double liquidar();

virtual double getIntereses();

virtual double getCapital();

int getPlazoDias();

double getTipoInteres();

(70)

Redefinición de métodos

 Métodos redefinidos en DepositoEstructurado:

 Llaman a las versiones de la clase Deposito.

//Override

double DepositoEstructurado::getIntereses() {

return Deposito::getIntereses() + getInteresesVariable();

}

//Override

double DepositoEstructurado::getCapital() {

return Deposito::getCapital() + getCapitalVariable();

(71)

Polimorfismo y ligadura



El polimorfismo de asignación está permitido para

entidades (variables) con semántica por valor y

referencia.



Para objetos por valor, se pierde información en la

asignación.



Ligadura dinámica:

 Sólo es posible para métodos virtuales.

 La entidad polimórfica debe ser de tipo referencia.



Ligadura estática

:

(72)

Polimorfismo y ligadura con semántica valor



deposito

tiene semántica valor

 se aplica

ligadura estática



Se ejecuta la versión de getCapital disponible en

el tipo estático  Deposito::getCapital

Deposito deposito(…);

DepositoEstructurado de(…);

//Asignación polimórfica entre objetos valor

deposito = de;

(73)

Polimorfismo y ligadura con semántica referencia

Deposito* ptrDeposito = new Deposito(…);

DepositoEstructurado* ptrDe = new DepositoEstructurado(…); ptrDeposito = ptrDe;

ptrDeposito->getCapital(); // Ligadura dinámica

ptrDeposito->liquidar(); // Ligadura estática

 ptrDeposito tiene semántica referencia

 se aplica ligadura estática o dinámica

 getCapital() método virtual ligadura dinámica

 Se ejecuta DepositoEstruturado::getCapital

 liquidar() no es virtual  ligadura estática

(74)

Conversión de tipos



Operador

dynamic_cast<Tipo*>(ptro)



Convierte el ptro en el puntero a Tipo



ptro

debe ser una entidad polimórfica (su clase

debe tener algún método virtual)



Al igual que en Java, el tipo de la conversión debe

ser compatible.

(75)

Conversión de tipos



Ejemplo:

 Establecer interés variable a depósitos estructurados.

Deposito** depositos;

depositos = new Deposito*[MAX_DEPOSITOS];

DepositoEstructurado* depEst;

for (int i =0; i < MAX_DEPOSITOS; i++){

depEst = dynamic_cast<DepositoEstructurado*>(depositos[i]);

if (depEst != NULL)

(76)

Compatibilidad de tipos



Ejemplo:

 Calcula el número de depósitos garantizados.



Equivalente al instanceof de Java.

int numGarantizados = 0;

for (int i =0; i < MAX_DEPOSITOS; i++){

if (dynamic_cast<DepositoGarantizado*>(depositos[i])) ++numGarantizados;

(77)

Copia e igualdad de objetos

 No existe una clase raíz como la clase Object de Java.

 No existen métodos como clone o equals.

 Los operadores de asignación y copia entre objetos con

semántica referencia se comportan igual que en Java.

 No se debe cambiar la semántica.

 El operador de asignación entre objetos con semántica valor

realiza una copia superficial.

 ¿Cómo se puede definir una copia diferente?

 La igualdad entre objetos con semántica valor no está

definida.

(78)

Operadores

 Gran parte de los operadores del lenguaje (=, ==, !=, <, <=, etc.)

pueden ser definidos para su aplicación sobre objetos.

 Ejemplo: utilizar + para sumar matrices.

 Se definen utilizando como nombre de método operator

seguido del operador. Ejemplos:

 operator=  operator==  operator<<

 Un operador puede definirse dentro o fuera de una clase. En

(79)

Operador = en Cuenta

Cuenta& Cuenta::operator=(const Cuenta& otraCuenta){

if (this != &otraCuenta){ //¿son el mismo objeto?

codigo = ++ultimoCodigo;

titular = otraCuenta.titular;

saldo = otraCuenta.saldo;

delete [] ultimasOperaciones;

ultimasOperaciones = new double[Cuenta::MAX_OPERACIONES]; }

return (*this); }

(80)

Operador == en Cuenta



Implementar != a partir de == para evitar

inconsistencias.

bool Cuenta::operator==(const Cuenta& otraCuenta){

return (codigo == otraCuenta.codigo); }

bool Cuenta::operator!=(const Cuenta& otraCta){

return !(*this == otraCta); }

(81)

Operador externo << en Cuenta

ostream& banco::operator<<(ostream& salida,

const Cuenta& cuenta){ salida <<"Cuenta [codigo = "<<cuenta.getCodigo()

<<", titular = "<<(cuenta.getTitular())->getNombre() <<", estado = "<<cuenta.getEstado() <<", saldo = "<<cuenta.getSaldo() <<"]"<<endl; return salida; }

ostream& banco::operator<<(ostream& salida, const Cuenta* cta){ salida<<(*cta);

(82)

Operador externo << para el Enumerado

ostream& banco::operator <<(ostream& salida,

const EstadoCuenta& estado){

switch(estado){

case OPERATIVA: salida<<"OPERATIVA"; return salida;

case INMOVILIZADA: salida<<"INMOVILIZADA"; return salida;

case NUM_ROJOS: salida<<"NUMEROS ROJOS"; return salida; }

return salida;

(83)

Declaración de operadores

namespace banco{ class Cuenta { public:

bool operator==(const Cuenta& otraCta); bool operator!=(const Cuenta& otraCta); Cuenta& operator=(const Cuenta& otraCta); };

//Externos

ostream& operator<<(ostream& salida, const EstadoCuenta& estado); ostream& operator<<(ostream& salida, const Cuenta& cuenta);

(84)

Clases abstractas

 En C++ una clase es abstracta si tiene algún método

virtual “puro”: sin implementación.

 No existe una palabra clave para definir una clase

abstracta.

class ProductoFinanciero{

private:

Persona* titular;

public:

ProductoFinanciero(Persona* titular);

virtual double getBeneficio()=0;

double getImpuestos();

(85)

Interfaces

 C++ no define el concepto de interfaz de Java.

 No es necesario, ya que el lenguaje ofrece herencia

múltiple.

 Si una clase quiere ser compatible con varias clases, basta

con que herede públicamente de esas clases.

 El equivalente a las interfaces de Java sería una clase

totalmente abstracta sólo con métodos virtuales puros.

(86)

Herencia múltiple

 En C++ es posible que una clase tenga más de una clase

padre.

 Problemas:

 Colisión de nombres: la clase hija hereda dos métodos con

la misma signatura.

 Herencia repetida: una clase se hereda dos veces,

(87)
(88)

Herencia múltiple – problemas

 La clase CuentaRemunerada

hereda dos veces de la clase ProductoFinanciero.

 Por defecto, se duplican todos los

atributos heredados. titular atributos Deposito titular atributos Cuenta at. CtaRemunerada

(89)

Herencia múltiple – problemas

 El método getTitular() se hereda dos veces: colisión

de nombres.

 Al aplicarlo sobre un objeto CuentaRemunerada existe

ambigüedad.

 Se resuelve la ambigüedad calificando la versión que

queremos aplicar:

CuentaRemunerada* cr = new CuentaRemunerada(); cr->Cuenta::getTitular();

(90)

Herencia repetida – ambigüedad

 La asignación polimórfica a una referencia de tipo

ProductoFinanciero es ambigua:

 Habría que indicar la ruta de herencia:

ProductoFinanciero* pf;

CuentaRemunerada* cr = new CuentaRemunerada(); pf = cr; // Error

ProductoFinanciero* pf;

CuentaRemunerada* cr = new CuentaRemunerada();

(91)

Herencia repetida – ambigüedad

 La aplicación del método getBeneficio() es

ambigua si se aplica sobre una variable de tipo CuentaRemunerada:

ProductoFinanciero* pf;

CuentaRemunerada* cr = new CuentaRemunerada();

pf = (Cuenta*)cr;

pf->getTitular();

pf->getBeneficio(); // Versión clase Cuenta cr->getBeneficio(); // Error

(92)

Herencia virtual

 Parte de los problemas de ambigüedad de la herencia

repetida pueden resolverse con herencia virtual.

 Evita que la clase ProductoFinanciero se herede dos

veces en la clase CuentaRemunerada.

 Las clases Cuenta y Deposito aplican herencia virtual de

ProductoFinanciero:

 La herencia virtual indica que si una subclase hereda de

ProductoFinanciero por otra rama, los atributos y métodos de esa clase no deben repetirse.

(93)

Herencia virtual

class ProductoFinanciero{ …

};

class Deposito: virtual public ProductoFinanciero { …

};

class Cuenta: virtual public ProductoFinanciero { …

};

class CuentaRemunerada: public Cuenta, public Deposito{ …

(94)

Herencia virtual – Constructores

 El constructor de la clase CuentaRemunerada tiene que

llamar al constructor de ProductoFinanciero aunque no sea una clase de la que hereda directamente.

CuentaRemunerada::CuentaRemunerada(Persona* p, double s, int

plazoDias,double tipoInteres) : ProductoFinanciero(p),

Cuenta(p,s),

Deposito(p, s, plazoDias, tipoInteres)

{ …

(95)

Herencia múltiple – Colisión de nombres

 El método getBeneficio() es definido por Cuenta y

Deposito: colisión de implementaciones.

 Se evita al redefinir el método eligiendo una de las

versiones:

class CuentaRemunerada: public Cuenta, public Deposito{

public:

CuentaRemunerada(…);

double getBeneficio(); // Redefine el método

};

double CuentaRemunerada::getBeneficio(){

(96)

Herencia múltiple – Método dominante

 Si un método de la clase ProductoFinanciero se define

o redefine sólo en una de las clases hijas, no existe ambigüedad.

 Se dice que la versión redefinida domina sobre la versión

original.

 En el caso de una asignación polimórfica se ejecutará la

(97)

Genericidad en C++

 La clases genéricas en C++ se conocen como templates.  Ejemplo: Contenedor

template <class T> class Contenedor{

private:

T contenido;

public:

T getContenido();

void setContenido (T elem); };

(98)

Genericidad – Implementación

 La implementación de la clase genérica debe realizarse en

el fichero de cabecera:

template<class T> T Contenedor<T>::getContenido(){

return contenido; }

template<class T> void Contenedor<T>::setContenido(T elem){ contenido = elem;

(99)

Genericidad – Uso

 Se indica el tipo de la clase genérica en su declaración.  Puede ser aplicada a tipos primitivos.

Persona* titular = new Persona("juan", "34914680");

Cuenta* cuenta = new Cuenta(titular);

Contenedor<Cuenta*> contenedor; contenedor.setContenido(cuenta);

Cuenta* cta = contenedor.getContenido(); Contenedor<int> cEntero;

(100)

Genericidad – Críticas

 C++ no implementa un auténtico sistema de

genericidad.

 Cuando se usa una clase genérica, se realiza un

reemplazo del texto del parámetro en la declaración de la clase.

 Por tanto, se genera código para cada uno de los tipos

(101)

Genericidad restringida

 No se puede restringir la genericidad.

 Está permitido aplicar cualquier método y operador

sobre las entidades del tipo genérico (tipo T en el ejemplo).

 Es problemático determinar la sintaxis de los mensajes,

ya que se puede instanciar a tipos por valor o referencia.

 Si la operación no está disponible sobre el tipo genérico, el

(102)

Colecciones en C++

 Todas las entidades de la Librería Estándar C++ se han

incluido en un único espacio de nombres denominado

std.

 La librería estándar de plantillas, o STL (Standard Template Library), implementa un conjunto de

estructuras de datos y algoritmos que conforman una parte sustancial de la Librería Estándar C++.

 Las estructuras de datos genéricas se denominan

contenedores.

 Los contenedores son clases genéricas (template)

pensadas para contener cualquier tipo de objeto.

(103)

Tipos de colecciones



Existen dos tipos de contenedores:

 Secuencias:

 Almacenan los elementos en orden secuencial.  Adecuadas para accesos directos y secuenciales.  vector, list, deque (cola de doble terminación),

stack

 Asociaciones:

 Almacenan pares de valores <clave, valor>.

 Podría verse como una array en el que el índice no tiene

por qué ser un entero.

 Adecuados para accesos aleatorios mediante claves.

(104)

Cuenta.h

#include <list> using namespace std; class Cuenta { public:

list<double>* getOperaciones() const;

private:

list<double>* operaciones;

void anotarOperacion(double cantidad); };

(105)

Cuenta.cpp

Cuenta::Cuenta (Persona* persona, double saldoInicial){ …

operaciones = new list<double>(); }

list<double>* Cuenta::getOperaciones() const{ return new list<double>(*operaciones);

}

void Cuenta::anotarOperacion(double cantidad){

operaciones->push_back(cantidad); }

(106)

Prueba de listas

int main () {

Cuenta* cuenta = new Cuenta(titular); cuenta->ingreso(100);

cuenta->reintegro(300); cuenta->ingreso(1000);

list<double>* movimientos;

movimientos = cuenta->getOperaciones();

list<double>::const_iterator it; it = movimientos->begin();

for (; it != movimientos->end() ;++it) cout << (*it) << endl;

exit(0); }

(107)

Prueba de listas



La implementación de las listas no ofrece métodos

para accesos aleatorios porque serían ineficientes.



Sólo dispone de métodos de acceso al principio y al

final

 Se puede utilizar de forma efectiva como una pila

(108)

Prueba con vectores

int main () {

Cuenta* cuenta = new Cuenta(titular); cuenta->ingreso(100);

cuenta->ingreso(300); cuenta->reintegro(300); cuenta->ingreso(1000);

vector<double>* movimientos;

movimientos = cuenta->getOperaciones();

for (int i = 0; i < movimientos->size(); i++) cout << (*movimientos)[i] <<endl;

exit(0);

(109)

Prueba con vectores



Los vectores se utilizan como arrays de tamaño

fijo.



Aunque tiene operaciones para aumentar el

tamaño del vector se desaconseja su uso por ser

ineficientes.



Acceso fácil y eficiente a los elementos.



size()

no devuelve el número de elementos

(110)

Estrategias

 Patrón estrategia: parametrizar una rutina con un

algoritmo.

 C++ permite definir punteros a funciones.

 Un puntero a función es una variable que guarda la

dirección de comienzo de una función.

 X (*fptr) (A);

 fptr es un puntero a función que recibe A como

(111)

Estrategias

 Ejemplo:

 En la clase Banco, método de búsqueda de productos

financieros. namespace banco{ class Banco { private: ProductoFinanciero** productos; public: Banco(); ProductoFinanciero* buscar(

bool (*condicion) (ProductoFinanciero*)); };

//Condiciones de búsqueda

(112)

Estrategias

 El parámetro del método buscar() es una función que

recibe como parámetro un puntero a un

ProductoFinanciero y devuelve un valor booleano.

 La función depositoAlto podría ser establecida como

parámetro del método buscar() : cumple la definición del puntero a función.

(113)

Estrategias

ProductoFinanciero* Banco::buscar(

bool (*condicion)(ProductoFinanciero*)){

bool encontrado = false;

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

if (condicion(productos[i])){ encontrado = true;

return productos[i]; }

if (!encontrado) return NULL; }

(114)

Estrategias

// Comprueba si el producto es un depósito // y su saldo es mayor que 10000

bool banco::depositoAlto(ProductoFinanciero* producto){

Deposito* deposito = dynamic_cast<Deposito*>(producto);

if (deposito != NULL)

return (deposito->getCapital() > 10000);

else return false; }

Banco banco; …

(115)

Estrategias – Limitaciones

 Los punteros a funciones están limitados a:

 Funciones globales (funciones de C).  Métodos de clase.

 No pueden apuntar a métodos de instancia

 Sin embargo, en la mayoría de casos el mecanismo de los

punteros a función es suficiente.

 Si fuera necesario, se podría simular la solución de Java

(116)

Corrección y Robustez



Este apartado trata:

 Asertos.

 Excepciones.

 Excepciones predefinidas.  Definición de excepciones.

 Declaración de excepciones en métodos.  Tratamiento de excepciones.

(117)

Asertos

 C++ permite el uso de asertos llamando a la macro

assert() definida en <assert.h>

 assert() evalúa una expresión y llama a abort() si el

resultado es cero (false).

void Cuenta::ingreso(double cantidad){ assert(cantidad > 0);

assert(estado == OPERATIVA);

saldo = saldo + cantidad; }

(118)

Asertos

 Antes de abortar, assert escribe en la salida de error el

nombre del archivo fuente y la línea donde se produjo el error.

 Si se define la macro NDEBUG, se desactiva la

(119)

Asertos – Limitaciones

 Los asertos de C++ tienen los mismos problemas que en

Java.

 No se recomienda evaluar las precondiciones con asertos,

ya que se pueden desactivar.

 La verificación de código se realiza con pruebas

(120)

Excepciones

 Mecanismo para notificar que se ha producido una

situación excepcional.

 A diferencia de Java, una excepción no tiene por qué ser

un objeto de una clase:

 Se puede lanzar cualquier dato (un entero, una cadena de

texto, …)

(121)

Lanzamiento de excepciones

 Se utiliza la palabra reservada throw

 Aunque es posible notificar como error cualquier dato, se

recomienda utilizar objetos.

void Cuenta::ingreso(double cantidad) {

if (cantidad < 0)

throw invalid_argument(“Cantidad negativa");

if (estado != OPERATIVA)

throw logic_error("Estado incorrecto");

saldo = saldo + cantidad; }

(122)

Excepciones biblioteca estándar

 Existe un conjunto de excepciones predefinidas en el

espacio de nombres std (<stdexcept.h>)

 Todas ellas heredan de la clase exception.

 No es obligatorio que las excepciones del

programador hereden de alguna excepción predefinida.

(123)
(124)

Uso excepciones estándares

 Para el control de precondiciones se lanzan

excepciones compatibles con logic_error void Cuenta::ingreso(double cantidad) {

if (cantidad < 0)

throw invalid_argument(“Cantidad negativa");

if (estado != OPERATIVA)

throw logic_error("Estado incorrecto");

saldo = saldo + cantidad; }

(125)

Excepciones de usuario

 Para notificar el fallo de una postcondición se recomienda

crear una clase que herede de runtime_error:

 Las excepciones que heredan de exception disponen

del método what() que retorna el mensaje de error. class RedNoDisponible: public runtime_error{

public:

RedNoDisponible(const char* msg); };

RedNoDisponible::RedNoDisponible(const char* msg):

(126)

Declaración de excepciones

 Se utiliza también la palabra reservada throw.

 Se puede especificar el conjunto de excepciones que

puede lanzar un método

 void f(int a) throw (E1, E2);

 f puede lanzar excepciones de tipo E1 y E2, pero no otras

 Si no se dice nada, significa que podría lanzar cualquier

excepción

 int f();

 Se puede indicar que un método no lanzará ninguna

excepción

(127)

Declaración de excepciones

 A diferencia de Java, el compilador ignora la

declaración de excepciones.

 Si en tiempo de ejecución se intenta lanzar una excepción

no declarada, se detecta la violación y termina la ejecución.

 En cambio, el compilador controla la redefinición de un

método para que no pueda lanzar más excepciones que las especificadas en el método de la clase padre.

(128)

Declaración de excepciones

 Declaramos que el método ingreso() sólo puede lanzar

los dos tipos de excepciones.

 Si en el cuerpo del método se produjese alguna otra

excepción sería incompatible con la declaración. void Cuenta::ingreso(double cantidad)

throw (invalid_argument, logic_error){

if (cantidad<0)

throw invalid_argument("cantidad negativa");

if (estado!= OPERATIVA)

throw logic_error("Estado incorrecto");

saldo = saldo + cantidad; }

(129)

Tratamiento de excepciones

 Al igual que en Java, las excepciones pueden ser tratadas en

bloques try-catch.

 A diferencia de Java, el compilador no obliga a dar tratamiento

a las excepciones.

 Las excepciones son pasadas al manejador (catch) por

referencia.

 Cuando ocurre una excepción se evalúan los tipos definidos en

los manejadores y se ejecuta el primero cuyo tipo sea compatible ( = Java).

 Se puede definir un manejador para cualquier tipo de

excepción: catch(…)

(130)

Tratamiento de excepciones

 Si al crear la conexión ocurre la excepción

RecursoNoEncontrado (no manejada) pasará al cliente. void Navegador::visualiza(string url) {

Conexion* conexion;

int intentos = 0;

while (intentos < 20) {

try {

conexion = new Conexion(url);

break;

} catch (RedNoDisponible& e) { intentos++;

if (intentos == 20) throw; //relanza

} …

(131)

Seminario de C++



Este tema se completa con el Seminario de C++

que incluye:

 La instalación del compilador C++ de GNU en

Windows.

 Instalación del entorno de desarrollo Eclipse C++.  Creación y ejecución de un proyecto.

Referencias

Documento similar

Se propone crear un sistema bajo la programación orientada a objetos en Java ya que es el lenguaje que proporciona diversas ventajas, que nos permitirá hacer

En medio de la búsqueda de una modelación adecuada, el Lenguaje de Modelado Orientado a objetos de Aplicaciones Multimedia (OMMMA - L) se lanza como una propuesta de extensión de

El desarrollo de software siempre tiene un costo asociado, aunque puede compensarse con los beneficios que reporta éste a lo largo de su explotación como recurso, la Universidad con

R ESUMEN : El caso de uso se inicia cuando el médico procede a realizar una consulta al paciente, el sistema obtiene los datos del paciente almacenado en la tarjeta de las

Con el análisis desarrollado en este capítulo se puede llegar a la conclusión de que es necesario para definir una DSSA un ambiente de desarrollo, una arquitectura base, un framework

• GNU/Linux (En este sistema operativo hay que instalar el pluggin, a diferencia de los demás que se activan cuando un cliente interactúa con algún flash o banner). Descripción de

Por todo lo que hasta aquí se ha expuesto se propone realizar la investigación a partir del siguiente problema: ¿Cómo utilizar la transparencia que brindan los paradigmas de

&lt;El sistema muestra las reglas para el análisis y la secuencia, luego busca sitios blancos en la secuencia, los SNP, luego de presionar la opción