Clases I: Definiciones y Conceptos de POO¶
Introducción¶
Hasta ahora hemos visto principalmente características de C++ que también existen en C.
A partir de este capítulo, entramos en el corazón de C++: las clases y la Programación Orientada a Objetos (POO).
Cambio de paradigma: La forma de pensar y estructurar programas cambia completamente. En lugar de separar datos y funciones, ahora los agrupamos en entidades llamadas objetos.
Paradigma de Programación¶
Programación Estructurada (C tradicional)¶
// Datos separados
struct Punto {
int x;
int y;
};
// Funciones separadas
void mover_punto(struct Punto* p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
int main() {
struct Punto p = {0, 0};
mover_punto(&p, 10, 20); // Función externa
}
Características: - Datos y funciones están separados - Las funciones operan sobre los datos - Los datos son pasivos
Programación Orientada a Objetos (C++)¶
// Datos y funciones juntos
class Punto {
int x;
int y;
public:
void mover(int dx, int dy) {
x += dx;
y += dy;
}
};
int main() {
Punto p;
p.mover(10, 20); // Método del objeto
}
Características: - Datos y funciones están agrupados en una unidad (objeto) - Las funciones son parte del objeto - Los objetos son activos (tienen comportamiento)
Conceptos Fundamentales de POO¶
1. Programación Orientada a Objetos (POO)¶
Definición: Paradigma de programación que agrupa datos y procedimientos en una única entidad llamada objeto.
Siglas: - Español: POO - Inglés: OOP (Object-Oriented Programming)
Principio básico:
"Cada programa es un objeto, que a su vez está formado de objetos que se relacionan entre sí."
Ventajas: - ✅ Encapsulación: Datos protegidos - ✅ Modularidad: Código organizado - ✅ Reutilización: Herencia y composición - ✅ Mantenibilidad: Cambios localizados
Nota importante: La programación estructurada no desaparece, se refuerza y complementa con POO.
2. Objeto¶
Definición: Unidad que engloba en sí misma: - Datos (atributos, propiedades, campos) - Procedimientos (funciones, métodos)
Analogía del mundo real:
Objeto: Automóvil
├── Datos:
│ ├── marca: "Toyota"
│ ├── modelo: "Corolla"
│ ├── velocidad: 80 km/h
│ └── combustible: 45 litros
└── Métodos:
├── acelerar()
├── frenar()
├── girar()
└── abastecer()
Ejemplo en C++:
class Automovil {
// Datos (privados)
string marca;
string modelo;
int velocidad;
float combustible;
public:
// Métodos (públicos)
void acelerar(int incremento) {
velocidad += incremento;
}
void frenar(int decremento) {
velocidad -= decremento;
}
void abastecer(float litros) {
combustible += litros;
}
};
Comparación:
| Programación Tradicional | POO |
|---|---|
| Datos y funciones separados | Datos y funciones juntos |
struct + funciones externas |
class con métodos |
| Datos expuestos | Datos protegidos (encapsulación) |
3. Clase¶
Definición: Patrón o plantilla para crear objetos.
Analogía: - Clase = Plano de una casa - Objeto = Casa construida según el plano
// Clase: el plano
class Casa {
int habitaciones;
float area;
public:
void construir();
};
// Objetos: casas construidas según el plano
Casa casa1; // Instancia 1
Casa casa2; // Instancia 2
Casa casa3; // Instancia 3
Relación Clase-Objeto:
Clase Perro
│
│ (es el patrón para)
│
┌─────────┼─────────┐
│ │ │
Objeto Objeto Objeto
perro1 perro2 perro3
(Firulais)(Rex) (Max)
Diferencia clave:
| Clase | Objeto |
|---|---|
| Declaración / Plantilla | Instancia / Ejemplar |
| No ocupa memoria (solo definición) | Ocupa memoria |
| No puede recibir mensajes | Puede recibir mensajes |
| Define estructura y comportamiento | Tiene estado y comportamiento |
Ejemplo:
// CLASE: definición, no es un objeto
class Rectangulo {
int ancho;
int alto;
public:
int area() { return ancho * alto; }
};
// OBJETOS: instancias de la clase
Rectangulo r1; // Objeto 1 en memoria
Rectangulo r2; // Objeto 2 en memoria
Rectangulo r3; // Objeto 3 en memoria
// Cada objeto tiene su propia copia de los datos
// Pero comparten la misma definición de métodos
4. Método¶
Definición: Función o procedimiento que pertenece a un objeto/clase.
En C++: Un método es simplemente una función miembro de una clase.
Terminología:
| Contexto | Término |
|---|---|
| POO general | Método |
| C++ técnico | Función miembro |
| Conversación | Ambos son válidos |
Ejemplo:
class Contador {
int valor;
public:
// Métodos
void incrementar() { // Método 1
valor++;
}
void decrementar() { // Método 2
valor--;
}
int obtener() { // Método 3
return valor;
}
};
int main() {
Contador c;
c.incrementar(); // Llamada a método
c.incrementar();
c.decrementar();
int v = c.obtener();
}
Características: - Operan sobre los datos del objeto - Tienen acceso a los miembros privados - Pueden modificar el estado interno del objeto
5. Mensaje¶
Definición: Modo en que se comunican e interrelacionan los objetos entre sí.
En C++: Un mensaje es una llamada a un método de un objeto.
Analogía:
Objeto Persona Objeto Perro
│ │
│ Mensaje: "ladra()" │
├──────────────────────────────>│
│ │
│ [Ejecuta método]
│ │
│ Respuesta: "Guau!" │
│<──────────────────────────────┤
Ejemplo en código:
class Calculadora {
int resultado;
public:
void sumar(int a, int b) {
resultado = a + b;
}
int obtener_resultado() {
return resultado;
}
};
int main() {
Calculadora calc;
// Enviar mensaje "sumar" al objeto calc
calc.sumar(5, 3);
// Enviar mensaje "obtener_resultado"
int res = calc.obtener_resultado();
cout << res << endl; // Output: 8
}
Paso a paso:
1. calc.sumar(5, 3) → Enviamos mensaje "sumar" con parámetros 5 y 3
2. El objeto calc procesa el mensaje ejecutando su método sumar()
3. calc.obtener_resultado() → Enviamos mensaje "obtener_resultado"
4. El objeto responde devolviendo el valor
Comunicación entre objetos:
class Motor {
public:
void encender() { /* ... */ }
void apagar() { /* ... */ }
};
class Automovil {
Motor motor; // Objeto Motor dentro de Automovil
public:
void arrancar() {
motor.encender(); // Automovil envía mensaje a Motor
}
void detener() {
motor.apagar(); // Automovil envía mensaje a Motor
}
};
6. Interfaz¶
Definición: Parte del objeto que es visible para el resto de los objetos. Conjunto de métodos (y a veces datos) públicos.
Analogía: La interfaz de un objeto es como los controles de un aparato: - TV: botones de encender, cambiar canal, volumen → Interfaz - Circuitos internos → Implementación privada
Ejemplo:
class CuentaBancaria {
// PARTE PRIVADA (no es interfaz)
double saldo;
string numero_cuenta;
// Método privado
bool validar_operacion(double monto) {
return monto > 0 && saldo >= monto;
}
public:
// INTERFAZ PÚBLICA
void depositar(double monto) { // ✅ Visible
saldo += monto;
}
bool retirar(double monto) { // ✅ Visible
if (validar_operacion(monto)) {
saldo -= monto;
return true;
}
return false;
}
double consultar_saldo() { // ✅ Visible
return saldo;
}
};
int main() {
CuentaBancaria cuenta;
// Solo podemos usar la INTERFAZ PÚBLICA
cuenta.depositar(1000); // ✅ OK
cuenta.retirar(500); // ✅ OK
double s = cuenta.consultar_saldo(); // ✅ OK
// No podemos acceder a la parte privada
// cuenta.saldo = 9999999; // ❌ ERROR: privado
// cuenta.validar_operacion(100); // ❌ ERROR: privado
}
Componentes de la interfaz:
┌─────────────────────────────────┐
│ Clase │
├─────────────────────────────────┤
│ INTERFAZ PÚBLICA (visible) │
│ - métodos públicos │
│ - datos públicos (raro) │
├─────────────────────────────────┤
│ IMPLEMENTACIÓN PRIVADA (oculta) │
│ - datos privados │
│ - métodos privados auxiliares │
└─────────────────────────────────┘
Ventajas de una buena interfaz: - 🔒 Encapsulación: Oculta detalles internos - 🛡️ Protección: Evita uso indebido - 🔧 Mantenibilidad: Puedes cambiar la implementación sin afectar a los usuarios - 📖 Claridad: Define claramente qué puede hacer el objeto
7. Herencia¶
Definición: Capacidad de crear nuevas clases basándose en clases existentes, aprovechando datos y métodos, descartando otros y añadiendo nuevos.
Terminología: - C++ técnico: Derivación de clases - POO general: Herencia
Analogía biológica:
Animal (clase base)
│
┌──────┴──────┐
│ │
Mamífero Reptil
│ │
┌─┴─┐ ┌─┴─┐
│ │ │ │
Perro Gato Serpiente Lagarto
Ejemplo en C++:
// CLASE BASE
class Vehiculo {
protected:
int velocidad;
string marca;
public:
void acelerar(int v) {
velocidad += v;
}
void frenar(int v) {
velocidad -= v;
}
};
// CLASE DERIVADA 1
class Automovil : public Vehiculo {
int num_puertas;
public:
void abrir_maletero() {
// Método específico de Automovil
}
// Hereda: acelerar(), frenar(), velocidad, marca
};
// CLASE DERIVADA 2
class Motocicleta : public Vehiculo {
bool tiene_sidecar;
public:
void hacer_caballito() {
// Método específico de Motocicleta
}
// Hereda: acelerar(), frenar(), velocidad, marca
};
Uso:
int main() {
Automovil coche;
coche.acelerar(50); // ✅ Heredado de Vehiculo
coche.abrir_maletero(); // ✅ Propio de Automovil
Motocicleta moto;
moto.acelerar(80); // ✅ Heredado de Vehiculo
moto.hacer_caballito(); // ✅ Propio de Motocicleta
}
Conceptos clave:
| Término | Significado |
|---|---|
| Clase base | Clase de la que se hereda (Vehiculo) |
| Clase derivada | Clase que hereda (Automovil, Motocicleta) |
| Heredar | Recibir características de la clase base |
| Sobrescribir | Redefinir un método heredado |
| Extender | Añadir nuevos métodos/datos |
Ventajas: - ♻️ Reutilización: No duplicar código - 🎯 Especialización: Añadir funcionalidad específica - 🔄 Mantenibilidad: Cambios en la base afectan a todas las derivadas - 📐 Estructura lógica: Refleja relaciones del mundo real
Ejemplo más completo:
class Figura {
protected:
int x, y; // Posición
public:
void mover(int dx, int dy) {
x += dx;
y += dy;
}
virtual double area() = 0; // Método abstracto
};
class Rectangulo : public Figura {
int ancho, alto;
public:
double area() override {
return ancho * alto;
}
};
class Circulo : public Figura {
double radio;
public:
double area() override {
return 3.14159 * radio * radio;
}
};
Herencia múltiple:
8. Jerarquía¶
Definición: Orden de subordinación en un sistema de clases.
Características: - Herencia en un solo sentido: de base a derivada - Forma estructuras de árbol - Siempre se puede retroceder a la(s) clase(s) base
Ejemplo de jerarquía:
Ser Vivo
│
┌─────────┴─────────┐
│ │
Vertebrado Invertebrado
│
┌───┴───┐
│ │
Mamífero Reptil
│
┌─┴─┐
│ │
Felino Canino
│ │
┌─┴─┐ ┌─┴─┐
│ │ │ │
Gato León Perro Lobo
En código:
class SerVivo {
bool esta_vivo;
public:
virtual void respirar() = 0;
};
class Vertebrado : public SerVivo {
protected:
int num_vertebras;
public:
void respirar() override { /* ... */ }
};
class Mamifero : public Vertebrado {
protected:
bool tiene_pelo;
public:
void amamantar() { /* ... */ }
};
class Felino : public Mamifero {
protected:
bool tiene_garras;
public:
void cazar() { /* ... */ }
};
class Gato : public Felino {
public:
void maullar() { cout << "Miau!" << endl; }
// Hereda TODO de la jerarquía:
// - esta_vivo (de SerVivo)
// - num_vertebras (de Vertebrado)
// - tiene_pelo (de Mamifero)
// - tiene_garras (de Felino)
// + métodos de todas las clases
};
Niveles jerárquicos:
Gato miGato;
// Todos estos son válidos:
Gato* p1 = &miGato; // Como Gato
Felino* p2 = &miGato; // Como Felino
Mamifero* p3 = &miGato; // Como Mamifero
Vertebrado* p4 = &miGato; // Como Vertebrado
SerVivo* p5 = &miGato; // Como SerVivo
Importancia: - Permite polimorfismo (siguiente concepto) - Facilita diseño modular - Refleja relaciones naturales
9. Polimorfismo¶
Definición literal: "Cualidad de lo que tiene o puede tener distintas formas"
Definición POO: Propiedad según la cual un mismo objeto puede considerarse como perteneciente a distintas clases.
Idea básica: Un objeto de una clase derivada puede tratarse como si fuera de cualquier clase de su jerarquía.
Ejemplo conceptual:
Jerarquía: SerVivo → Vertebrado → Mamífero → Felino → Gato
Un objeto "Gato" puede tratarse como:
- Gato (su clase real)
- Felino (puede actuar como felino genérico)
- Mamífero (puede actuar como mamífero genérico)
- Vertebrado (puede actuar como vertebrado genérico)
- SerVivo (puede actuar como ser vivo genérico)
Ejemplo práctico:
class Felino {
public:
virtual void hacer_sonido() = 0; // Método abstracto
virtual void cazar() { /* ... */ }
};
class Gato : public Felino {
public:
void hacer_sonido() override {
cout << "Miau!" << endl;
}
};
class Leon : public Felino {
public:
void hacer_sonido() override {
cout << "Roar!" << endl;
}
};
class Tigre : public Felino {
public:
void hacer_sonido() override {
cout << "Grrr!" << endl;
}
};
// POLIMORFISMO EN ACCIÓN
void alimentar_felino(Felino* f) { // Acepta CUALQUIER felino
f->hacer_sonido();
f->cazar();
}
int main() {
Gato g;
Leon l;
Tigre t;
// Todos se pueden tratar como "Felino"
alimentar_felino(&g); // ✅ Gato como Felino
alimentar_felino(&l); // ✅ Leon como Felino
alimentar_felino(&t); // ✅ Tigre como Felino
}
Array polimórfico:
// Almacenar diferentes tipos en un array de punteros a la clase base
Felino* felinos[4];
felinos[0] = new Gato();
felinos[1] = new Leon();
felinos[2] = new Tigre();
felinos[3] = new Gato();
// Iterar sobre todos, sin importar su tipo real
for (int i = 0; i < 4; i++) {
felinos[i]->hacer_sonido(); // Cada uno hace su sonido
}
Output:
Tipos de polimorfismo:
| Tipo | Cuándo se resuelve | Ejemplo |
|---|---|---|
| Estático | Compilación | Sobrecarga de funciones |
| Dinámico | Ejecución | Funciones virtuales |
Ejemplo de polimorfismo estático (sobrecarga):
class Calculadora {
public:
int sumar(int a, int b) {
return a + b;
}
double sumar(double a, double b) { // Mismo nombre, diferente tipo
return a + b;
}
int sumar(int a, int b, int c) { // Mismo nombre, diferente cantidad
return a + b + c;
}
};
Calculadora calc;
calc.sumar(2, 3); // Llama a sumar(int, int)
calc.sumar(2.5, 3.7); // Llama a sumar(double, double)
calc.sumar(1, 2, 3); // Llama a sumar(int, int, int)
Ventajas del polimorfismo:
- 🔄 Flexibilidad: Mismo código funciona con diferentes tipos
- 📦 Extensibilidad: Fácil agregar nuevos tipos
- 🎯 Abstracción: Trabajar con conceptos genéricos
- 🧩 Código limpio: Menos condicionales (if/switch)
Comparación Final: Estructural vs POO¶
Programa Estructural¶
// Datos
struct Cuenta {
double saldo;
char titular[50];
};
// Funciones separadas
void depositar(struct Cuenta* c, double monto) {
c->saldo += monto;
}
double consultar_saldo(struct Cuenta* c) {
return c->saldo;
}
// Uso
struct Cuenta c1;
depositar(&c1, 1000);
double s = consultar_saldo(&c1);
Problemas:
- ❌ Datos expuestos: cualquiera puede hacer c1.saldo = -9999
- ❌ Funciones dispersas: no hay agrupación lógica
- ❌ Sin protección: no hay encapsulación
Programa POO¶
// Clase: datos + funciones juntos
class Cuenta {
double saldo; // Privado
string titular; // Privado
public:
void depositar(double monto) {
if (monto > 0) {
saldo += monto;
}
}
double consultar_saldo() {
return saldo;
}
};
// Uso
Cuenta c1;
c1.depositar(1000);
double s = c1.consultar_saldo();
// c1.saldo = -9999; // ❌ ERROR: protegido
Ventajas: - ✅ Datos protegidos: solo acceso controlado - ✅ Funciones agrupadas: todo relacionado junto - ✅ Encapsulación: implementación oculta
Resumen de Conceptos¶
| Concepto | Definición Breve | Analogía |
|---|---|---|
| POO | Paradigma que agrupa datos y funciones | Organizar por objetos del mundo real |
| Objeto | Instancia de una clase con estado y comportamiento | Un coche específico (no el plano) |
| Clase | Plantilla para crear objetos | Plano de una casa |
| Método | Función de un objeto | Botón de un control remoto |
| Mensaje | Llamada a un método | Presionar un botón |
| Interfaz | Parte pública de una clase | Controles visibles de un aparato |
| Herencia | Crear clases basadas en otras | Hijo hereda características de padres |
| Jerarquía | Estructura de árbol de clases | Árbol genealógico |
| Polimorfismo | Mismo objeto, múltiples formas | Actor que interpreta varios roles |
Pilares de la POO¶
1. Encapsulación 🔒¶
class Ejemplo {
private: // Oculto
int dato;
public: // Visible
void set_dato(int d) { dato = d; }
int get_dato() { return dato; }
};
2. Herencia 🧬¶
class Base {
// ...
};
class Derivada : public Base {
// Hereda de Base + añade nuevas características
};
3. Polimorfismo 🎭¶
4. Abstracción 🎨¶
Referencias¶
- Programación Orientada a Objetos (Grady Booch)
- The C++ Programming Language (Bjarne Stroustrup)
- Effective C++ (Scott Meyers)
- Object-Oriented Software Construction (Bertrand Meyer)