Tres Pilares de la Programación Orientada a Objetos en C++: Herencia, Encapsulamiento y Polimorfismo

C++ posee tres características fundamentales en la programación orientada a objetos: herencia, encapsulamiento y polimorfismo.

En C++, todo se considera un objeto, y cada objeto tiene atributos y comportamientos.

Por ejemplo:

Los humanos pueden ser considerados objetos, con atributos como nombre, edad, altura, peso..., y comportamientos como caminar, correr, saltar, comer, cantar...

Los automóviles también son objetos, con atributos como ruedas, volante, luces..., y comportamientos como transportar personas, reproducir música, encender aire acondicionado...

Objetos con características similares pueden ser agrupados en clases. Los humanos pertenecen a la clase Humano, y los vehículos a la clase Vehículo.

1. Encapsulamiento

1.1 Importancia del encapsulamiento

El encapsulamiento es una de las tres características fundamentales de la programación orientada a objetos en C++.

La importancia del encapsulamiento:

  • Combina atributos y comportamientos en una sola entidad, representando conceptos del mundo real
  • Controla el acceso a atributos y comportamientos mediante permisos

Importancia uno:

Al diseñar una clase, los atributos y comportamientos se agrupan juntos, representando entidades del mundo real.

Sintaxis: class NombreClase { permisos_de_acceso: atributos / comportamientos };

Ejemplo de código:

#include <iostream>

using namespace std;

const double PI = 3.14;

class Circulo {
public:
    int radio;

    double calcularPerimetro() {
        return 2 * PI * radio;
    }
};

int main() {
    // Instanciación: proceso de crear un objeto a partir de una clase
    Circulo c1;
    c1.radio = 10;
    cout << "El perímetro del círculo es: " << c1.calcularPerimetro() << endl;
    return 0;
}

Importancia dos:

Al diseñar clases, los atributos y comportamientos pueden colocarse bajo diferentes permisos para controlar su acceso.

Dentro de una clase, los atributos y comportamientos se conocen colectivamente como miembros:

Atributos — miembros de datos, variables miembro

Comportamientos — funciones miembro, métodos miembro

Existen tres tipos de permisos de acceso:

(1)public - permiso público

(2)protected - permiso protegido

(3)private - permiso privado

Ejemplo de código:

#include <iostream>
#include <string>

using namespace std;

class Estudiante {
    // Atributos
public:
    string nombre;
    int edad;
    // Comportamientos
public:
    void establecerNombre(string nombre) {
        this->nombre = nombre;
    }

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

    void mostrarEstudiante() {
        cout << "Nombre del estudiante: " << nombre << endl << "Edad del estudiante: " << edad << endl;
    }
};

int main() {
    Estudiante e1;
    e1.establecerNombre("Carlos");
    e1.establecerEdad(20);
    e1.mostrarEstudiante();
}

1.2 Diferencias entre struct y class

En C++, la única diferencia entre struct y class radica en los permisos de acceso predeterminados.

Diferencias:

  • struct tiene permisos públicos por defecto
  • class tiene permisos privados por defecto

1.3 Establecer atributos como privados

Ventaja uno: Establecer todos los atributos como privados permite controlar los permisos de lectura y escritura.

Ventaja dos: Para los permisos de escritura, podemos verificar la validez de los datos.

Ejemplo de código:

#include<iostream>
#include<string>

using namespace std;

class Persona {
private:
    string nombre; // Nombre - lectura y escritura permitidas
    int edad = 18; // Edad - solo lectura
    string direccion; // Dirección - solo escritura

public:
    void establecerNombre(string nombre) {
        this->nombre = nombre;
    }

    string obtenerNombre() {
        return nombre;
    }

    int obtenerEdad() {
        return edad;
    }

    void establecerDireccion(string direccion) {
        this->direccion = direccion;
    }

private:
    void mostrarPersona() {
        cout << "Nombre: " << nombre << " Edad: " << edad << endl;
    }
};

int main() {
    Persona p1;
    p1.establecerNombre("Ana");
    cout << "Nombre: " << p1.obtenerNombre() << endl;
    cout << "Edad: " << p1.obtenerEdad() << endl;
}

2. Herencia

2.1 Importancia de la herencia

La herencia es una de las tres características fundamentales de la programación orientada a objetos en C++.

Una subclase hereda de una superclase.

Beneficios de la herencia: Reduce la duplicación de código.

Por ejemplo:

class B:public class A;

La clase A se conoce como clase padre o clase base.

La clase B se conoce como clase hija o clase derivada.

Los miembros de una clase derivada constan de dos partes:

  • Miembros heredados de la clase base
  • Miembros añadidos específicamente

Los miembros heredados de la clase base representan las características comunes, mientras que los nuevos miembros representan las características específicas.

2.2 Tipos de herencia

La sintaxis de herencia: class Subclase : forma_de_herencia Superclase

Existen tres tipos de herencia:

  • Herencia pública
  • Herencia protegida
  • Herencia privada

Resumen:

Los miembros con permiso público en la clase padre mantienen su permiso público en la clase hija; Los miembros con permiso protegido en la clase padre mantienen su permiso protegido en la clase hija; Los miembros con permiso privado en la clase padre no son accesibles desde la clase hija.

3. Polimorfismo

3.1 Importancia del polimorfismo

El polimorfismo es una de las tres características fundamentales de la programación orientada a objetos en C++.

El polimorfismo se clasifica en dos tipos:

  • Polimorfismo estático: La sobrecarga de funciones y operadores pertenece al polimorfismo estático, reutilizando nombres de función
  • Polimorfismo dinámico: Implementado mediante clases derivadas y funciones virtuales para lograr polimorfismo en tiempo de ejecución

Diferencias entre polimorfismo estático y dinámico:

  • En el polimorfismo estático, la dirección de la función se vincula tempranamente: se determina en tiempo de compilación
  • En el polimorfismo dinámico, la dirección de la función se vincula tardíamente: se determina en tiempo de ejecución

Ejemplo de código 1:

#include <iostream>

using namespace std;

class Animal {
public:
    // Función virtual
    virtual void hablar() {
        cout << "El animal está hablando" << endl;
    }
};

class Gato : public Animal {
public:
    // Sobrescritura
    void hablar() {
        cout << "El gato está maullando" << endl;
    }
};

class Perro : public Animal {
public:
    void hablar() {
        cout << "El perro está ladrando" << endl;
    }
};

// Función para hacer que el animal hable
void hacerHablar(Animal *animal) {
    animal->hablar();
}

void prueba01() {
    // Memoria en la pila
    Gato gato;
    hacerHablar(&gato);
    Perro perro;
    hacerHablar(&perro);
}

int main() {
    prueba01();
}

Resultado de ejecución:

El gato está maullando

El perro está ladrando

3.2 Funciones virtuales puras y clases abstractas

En el polimorfismo, la implementación de funciones virtuales en la clase base suele carecer de sentido, ya que principalmente se utilizan las implementaciones sobrescritas en las clases derivadas.

Por lo tanto, las funciones virtuales pueden convertirse en funciones virtuales puras.

Sintaxis de función virtual pura: virtual tipo_de_retorno nombre_función(lista_de_parámetros) = 0;

Cuando una clase tiene funciones virtuales puras, se le conoce como clase abstracta.

Características de las clases abstractas:

  • No se pueden instanciar objetos
  • Las clases derivadas deben sobrescribir las funciones virtuales puras de la clase abstracta, de lo contrario también serán clases abstractas

Ejemplo de código 2:

#include <iostream>

using namespace std;

class CalculadoraAbstracta {
public:
    // Función virtual pura
    virtual int obtenerResultado() = 0;

    int numero1;
    int numero2;
};

// Calculadora de suma
class CalculadoraSuma : public CalculadoraAbstracta {
public:
    int obtenerResultado() {
        return numero1 + numero2;
    }
};

// Calculadora de resta
class CalculadoraResta : public CalculadoraAbstracta {
public:
    int obtenerResultado() {
        return numero1 - numero2;
    }
};

// Calculadora de multiplicación
class CalculadoraMultiplicacion : public CalculadoraAbstracta {
public:
    int obtenerResultado() {
        return numero1 * numero2;
    }
};

void prueba01() {
    // Memoria en el heap
    CalculadoraAbstracta *calc = new CalculadoraSuma;
    calc->numero1 = 100;
    calc->numero2 = 200;
    cout << calc->numero1 << "+" << calc->numero2 << "=" << calc->obtenerResultado() << endl;
    delete calc;

    calc = new CalculadoraResta;
    calc->numero1 = 300;
    calc->numero2 = 200;
    cout << calc->numero1 << "-" << calc->numero2 << "=" << calc->obtenerResultado() << endl;
}

int main() {
    prueba01();
}

Resultado de ejecución:

100+200=300

300-200=100

Ejemplo de código 3:

#include <iostream>

using namespace std;

class BebidaAbstracta {
public:
    // Funciones virtuales puras
    virtual void Hervir() = 0;

    virtual void Preparar() = 0;

    virtual void VerterEnTaza() = 0;

    virtual void AgregarAditivos() = 0;

    void prepararBebida() {
        Hervir();
        Preparar();
        VerterEnTaza();
        AgregarAditivos();
    }
};

class Cafe : public BebidaAbstracta {
public:
    // Sobrescritura de funciones virtuales puras
    void Hervir() {
        cout << "Hirviendo agua" << endl;
    }

    void Preparar() {
        cout << "Preparando café" << endl;
    }

    void VerterEnTaza() {
        cout << "Vertiendo en taza" << endl;
    }

    void AgregarAditivos() {
        cout << "Añadiendo azúcar y leche" << endl;
    }
};

class Te : public BebidaAbstracta {
public:
    void Hervir() {
        cout << "Hirviendo agua" << endl;
    }

    void Preparar() {
        cout << "Preparando té" << endl;
    }

    void VerterEnTaza() {
        cout << "Vertiendo en taza" << endl;
    }

    void AgregarAditivos() {
        cout << "Añadiendo limón" << endl;
    }
};

void prepararBebida(BebidaAbstracta *bebida) {
    bebida->prepararBebida();
}

void prueba01() {
    // Memoria en la pila
    Cafe cafe;
    prepararBebida(&cafe);
    Te te;
    prepararBebida(&te);
}

int main() {
    prueba01();
}

Etiquetas: C++ Programación Orientada a Objetos herencia Encapsulamiento polimorfismo

Publicado el 6-23 20:04