Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% encontró este documento útil (0 votos)
5 vistas17 páginas

Herencia de Clases en C

Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Está en la página 1/ 17

Herencia de clases en C++

La herencia es la capacidad de una clase (derivada) de heredar y extender los miembros de otra clase
(base). En la herencia pública, las clases derivadas pueden acceder y utilizar los miembros públicos de la
clase base, pero la clase derivada no tiene acceso directo a los miembros privados. Lo mismo aplica a los
amigos de la clase derivada, que tienen acceso a los miembros privados derivados, pero no a la clase base:

/* Clase base Persona */


class Persona
{
friend std::ostream &operator<<(std::ostream &os, const Persona &p);

public:
Persona() = default;
Persona(std::string nombre) : nombre(nombre){};
std::string getNombre() const { return this->nombre; }
void setNombre(std::string nombre) { this->nombre = nombre; }

private:
std::string nombre{"?"};
};

std::ostream &operator<<(std::ostream &os, const Persona &p)


{
os << p.nombre;
return os;
}

/* Clase derivada Jugador */


class Jugador : public Persona
{
friend std::ostream &operator<<(std::ostream &os, const Jugador &j);

public:
Jugador(std::string juego) : juego(juego){};

private:
std::string juego{"?"};
};

std::ostream &operator<<(std::ostream &os, const Jugador &j)


{
os << j.getNombre() << " (" << j.juego << ")";
return os;
}

int main()
{
Jugador jugador("Volleyball");
jugador.setNombre("Marcos");
std::cout << jugador << std::endl; // Marcos (Volleyball)
return 0;
}
No es posible acceder a los miembros privados de la clase base desde la clase derivada y para ello
necesitamos crear getters públicos.

El kit de la cuestión es que mediante la cláusula protected podemos definir miembros que sean accesibles
solo desde clases derivadas:

/* Clase base Persona */


class Persona
{
// Bloque de miembros accesibles en clases derivadas
protected:
std::string nombre{"?"};
};

// La clase derivada ahora tiene acceso directo a los miembros


std::ostream &operator<<(std::ostream &os, const Jugador &j)
{
os << j.nombre << " (" << j.juego << ")";
return os;
}
Hay tres formas de heredar los miembros de una clase base:

En la herencia pública, los miembros públicos y protegidos permanecen igual en la clase derivada.
En la herencia protegida, los miembros públicos y protegidos se hacen protegidos en la clase derivada.
En la herencia privada, los miembros públicos y protegidos de la clase base se vuelven privados en la
clase derivada.

class Base
{
public:
int x;

protected:
int y;

private:
int z;
};
// Herencia pública
class PublicaDerivada : public Base
{
// x es public
// y es protected
// z no es accesible desde PublicaDerivada
};

// Herencia protegida
class ProtegidaDerivada: protected Base
{
// x es protected
// y es protected
// z no es accesible desde ProtegidaDerivada
};

// Herencia privada
class PrivadaDerivada: private Base
{
// x is privada
// y is privada
// z no es accesible desde PrivadaDerivada
};
Debemos tener en cuenta lo anterior, especialmente al heredar de clases derivadas, pues los miembros
habrán cambiado su visibilidad.

Resurrección de miembros
Sin embargo existe un método para recuperar o cambiar la visibilidad de los miembros en las clases
heredadas conocido como resurrección de miembros en el contexto.
La resurrección solo funcionará en miembros públicos y protegidos, los miembros privados de la clase
base no se pueden resucitar:

class Base
{
public:
int x;

protected:
int y;

private:
int z;
};

// La herencia privada hará x e y privadas


class PrivadaDerivada : private Base
{
public:
using Base::x; // Resucitamos x como pública
protected:
using Base::y; // Resucitamos y como protected
};
Constructores en la herencia
Los constructores por defecto se llaman en cada una de las clases derivadas al crear una instancia,
podemos comprobarlo con el siguiente experimento:

#include <iostream>
class A
{
public:
A() { std::cout << "Constructor de A" << std::endl; }
};

class B : public A
{
public:
B() { std::cout << "Constructor de B" << std::endl; }
};

class C : public B
{
public:
C() { std::cout << "Constructor de C" << std::endl; }
};

class D : public C
{
public:
D() { std::cout << "Constructor de D" << std::endl; }
};

int main()
{
D d;
return 0;
}
Constructor de A
Constructor de B
Constructor de C
Constructor de D
En caso de que las clases derivadas extiendan los parámetros de inicialización en el constructor, la forma
de abordar el problema es mediante inicialización listada con los propios constructores y luego los
miembros de la clase derivada, tal como ilustra este ejemplo:

class A
{
public:
int a;
A(int a) : a(a)
{
std::cout << "Constructor de A: "
<< "a -> " << a << std::endl;
}
};

class B : public A
{
public:
int b;
B(int a, int b) : A(a), b(b)
{
std::cout << "Constructor de B: "
<< "a -> " << a
<< ", b -> " << b << std::endl;
}
};
class C : public B
{
public:
int c;
C(int a, int b, int c) : B(a, b), c(c)
{
std::cout << "Constructor de C: "
<< "a -> " << a
<< ", b -> " << b
<< ", c -> " << c << std::endl;
}
};

class D : public C
{
public:
int d;
D(int a, int b, int c, int d) : C(a, b, c), d(d)
{
std::cout << "Constructor de D: "
<< "a -> " << a
<< ", b -> " << b
<< ", c -> " << c
<< ", d -> " << d << std::endl;
}
};

int main()
{
D d(1, 2, 3, 4);
return 0;
}

Constructor de A: a -> 1
Constructor de B: a -> 1, b -> 2
Constructor de C: a -> 1, b -> 2, c -> 3
Constructor de D: a -> 1, b -> 2, c -> 3, d -> 4
Herencia de los símbolos
Comentar por último que si en una clase derivada se redefinen los miembros de una clase base, se tomará
el comportamiento de la clase derivada y se descartará el de la clase base:

class Base
{
public:
std::string texto{"Hola mundo"};

void print()
{
std::cout << texto << " (publico) desde base\n";
}
};

class Derivada : public Base


{

private:
std::string texto{"Adios mundo"};
public:
void print()
{
std::cout << texto << " (privado) desde derivada\n";
}
};

main()
{
Base b;
b.print();
Derivada d;
d.print();
return 0;
}

Hola mundo (publico) desde base


Adios mundo (privado) desde derivada

#include<iostream>
using namespace std;

class A
{
public:
void unMetodoX(){cout<<"Soy de la clase A"<<endl;}
};

class B: virtual public A


{
};

class C: virtual public A


{
};

class D: virtual public B, virtual public C


{
};

int main()
{
D obj;
obj.unMetodoX();
return 0;
};

#include <iostream>
class Persona {
private:
std::string nombre;
int edad;

void metodoPrivado() {
std::cout << "Solo puedo ser llamado desde dentro de la clase";
}

public:
// Constructor sin argumentos
Persona() {
std::cout << "Se llama al constructor";
this->metodoPrivado();
}
// Constructor con nombre y edad
Persona(std::string nombre, int edad) {
this->edad = edad;
this->nombre = nombre;
}

int getEdad() { return this->edad; }

void setEdad(int edad) { this->edad = edad; }

std::string getNombre() { return this->nombre; }

void setNombre(std::string nombre) { this->nombre = nombre; }

void saludar() {
std::cout << "Hola, me llamo " << this->nombre << " y mi edad es "
<< this->edad << "\n";
}
// Definido virtual para que se pueda sobrescribir ;)
virtual void saludarAmigo(std::string nombre) {
std::cout << "Hola " << nombre << ", me llamo " << this->nombre
<< " y mi edad es " << this->edad << "\n";
}
};
class Estudiante : public Persona {
public:
// Constructor vacío
Estudiante(std::string nombre, int edad, std::string escuela)
: Persona(nombre, edad) {}

// Definir propios métodos


void estudiar() { std::cout << "*estudia*\n"; }
// Sobrescribir otros
void saludarAmigo(std::string nombre) override {
// Usamos getEdad y getNombre porque es una subclase
// y las variables son privadas
std::cout << "Qué onda " << nombre << ", me llamo " << this->getNombre()
<< " y mi edad es " << this->getEdad() << "\n";
}
};

int main() {
Persona p1("Luis", 21);
p1.saludar();
Persona p2;
p2.setEdad(1);
p2.setNombre("John Galt");
p2.saludar();
Estudiante e1("Luis", 3, "Ninguna");
e1.saludar();
e1.saludarAmigo("Pedro");
e1.estudiar();
}
Funciones virtuales
Una función virtual es una función miembro que se espera volver a definir en clases
derivadas. Cuando se hace referencia a un objeto de clase derivada mediante un
puntero o una referencia a la clase base, se puede llamar a una función virtual para ese
objeto y ejecutar la versión de la clase derivada de la función.

Las funciones virtuales garantizan que se llame a la función correcta para un objeto, con
independencia de la expresión utilizada para llamarla.

Suponga que una clase base contiene una función declarada como virtual y una clase
derivada define la misma función. La función de la clase derivada se invoca para los
objetos de la clase derivada, aunque se llame mediante un puntero o una referencia a la
clase base. En el ejemplo siguiente se muestra una clase base que proporciona una
implementación de la función PrintBalance y dos clases derivadas

// deriv_VirtualFunctions.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;

class Account {
public:
Account( double d ) { _balance = d; }
virtual ~Account() {}
virtual double GetBalance() { return _balance; }
virtual void PrintBalance() { cerr << "Error. Balance not available for base
type." << endl; }
private:
double _balance;
};

class CheckingAccount : public Account {


public:
CheckingAccount(double d) : Account(d) {}
void PrintBalance() { cout << "Checking account balance: " << GetBalance() <<
endl; }
};

class SavingsAccount : public Account {


public:
SavingsAccount(double d) : Account(d) {}
void PrintBalance() { cout << "Savings account balance: " << GetBalance(); }
};

int main() {
// Create objects of type CheckingAccount and SavingsAccount.
CheckingAccount checking( 100.00 );
SavingsAccount savings( 1000.00 );

// Call PrintBalance using a pointer to Account.


Account *pAccount = &checking;
pAccount->PrintBalance();

// Call PrintBalance using a pointer to Account.


pAccount = &savings;
pAccount->PrintBalance();
}

En el código anterior, las llamadas a PrintBalance son idénticas, excepto por el objeto al
que apunta pAccount. Dado que PrintBalance es virtual, se llama a la versión de la
función definida para cada objeto. La función PrintBalance de las clases
derivadas CheckingAccount y SavingsAccount “reemplaza” la función en la clase
base Account.

Si se declara una clase que no proporciona una implementación de reemplazo de la


función PrintBalance, se usa la implementación predeterminada de la clase base Account.

Las funciones de clases derivadas reemplazan funciones virtuales de clases base solo si
son del mismo tipo. Una función de una clase derivada no puede diferir de una función
virtual de una clase base solo en su tipo de valor devuelto; la lista de argumentos
también debe ser diferente.

Al llamar a una función mediante punteros o referencias, se aplican las siguientes reglas:

 Una llamada a una función virtual se resuelve de acuerdo con el tipo


subyacente del objeto para el que se llama.
 Una llamada a una función no virtual se resuelve de acuerdo con el tipo de
puntero o de referencia.

En el ejemplo siguiente se muestra cómo se comportan las funciones virtuales y no


virtuales cuando se llaman mediante punteros:

// deriv_VirtualFunctions2.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;

class Base {
public:
virtual void NameOf(); // Virtual function.
void InvokingClass(); // Nonvirtual function.
};

// Implement the two functions.


void Base::NameOf() {
cout << "Base::NameOf\n";
}

void Base::InvokingClass() {
cout << "Invoked by Base\n";
}

class Derived : public Base {


public:
void NameOf(); // Virtual function.
void InvokingClass(); // Nonvirtual function.
};

// Implement the two functions.


void Derived::NameOf() {
cout << "Derived::NameOf\n";
}

void Derived::InvokingClass() {
cout << "Invoked by Derived\n";
}

int main() {
// Declare an object of type Derived.
Derived aDerived;

// Declare two pointers, one of type Derived * and the other


// of type Base *, and initialize them to point to aDerived.
Derived *pDerived = &aDerived;
Base *pBase = &aDerived;

// Call the functions.


pBase->NameOf(); // Call virtual function.
pBase->InvokingClass(); // Call nonvirtual function.
pDerived->NameOf(); // Call virtual function.
pDerived->InvokingClass(); // Call nonvirtual function.
}
OutputCopiar
Derived::NameOf
Invoked by Base
Derived::NameOf
Invoked by Derived

Observe que, independientemente de si la función NameOf se invoca a través de un puntero a Base o un


puntero a Derived, llama a la función para Derived. Llama a la función para Derived porque NameOf es
una función virtual y tanto pBase como pDerived apuntan a un objeto de tipo Derived.
Dado que solo se llama a funciones virtuales para objetos de tipos de clase, no se puede declarar
funciones globales o estáticas como virtual.
La palabra clave virtual se puede usar para declarar funciones de reemplazo en una clase derivada, pero
no es necesario; los reemplazos de funciones virtuales son siempre virtuales.
Las funciones virtuales de una clase base se deben definir a menos que se declaren mediante pure-
specifier. (Para más información sobre las funciones virtuales puras, consulte Clases abstractas).
El mecanismo de llamada a funciones virtuales se puede suprimir calificando explícitamente el nombre de
función con el operador de resolución de ámbito (::). Considere el ejemplo anterior que implica la clase
de Account. Para llamar a PrintBalance en la clase base, utilice código como el siguiente:
CheckingAccount *pChecking = new CheckingAccount( 100.00 );

pChecking->Account::PrintBalance(); // Explicit qualification.

Account *pAccount = pChecking; // Call Account::PrintBalance

pAccount->Account::PrintBalance(); // Explicit qualification.


Ambas llamadas a PrintBalance en el ejemplo anterior suprimen el mecanismo de llamada de función
virtual.

También podría gustarte