Clases y objetos Álvaro Sánchez Miralles José Porras (Fidel Fernández) Índice • Conceptos iniciales • Clases y objetos – Ejemplo para romper el hielo – Definiciones – Vida de un objeto • Funciones miembro – – – – – – Constructor de un objeto Destructor de un objeto Sobrecarga de operadores Paso de parámetros por referencia Sobrecarga de constructor de copia Mejora del ejemplo • Diseño básico en C++ Clases y objetos - 2 Conceptos iniciales Modificador de tipo const • Indica que un tipo permanece constante – Sólo vale para evitar errores en tiempo de desarrollo const int LON=5; LON = 6; // ERROR int vi[LON]; const int *pi = vi; vi[0]=0; pi++; *pi=1; // ERROR int * const pii = vi; pii[0]=1; pii++; // ERROR const int * const piii = vi; piii++; // ERROR piii[1]=3; // ERROR Clases y objetos - 4 Memoria dinámica I • new – Reserva memoria • delete – Libera memoria main () { int *pi; pi = new int; double *pd=new double; delete pd; delete pi; int *pi=new int[10]; delete [] pi; } • Todo new tiene un delete asociado Clases y objetos - 5 Memoria dinámica II • ¿Qué está mal? main() { int *pi; pi = get_vector_discreto(1, 4); } /** función que devuelve un vector con los valores enteros * comprendidos entre min y max*/ int* get_vector_discreto(int min, int max){ int i, v[max-min]; for (i=0; i<max-min; i++) v[i] = i; return v; v es local !!!! } Clases y objetos - 6 Memoria dinámica III • Solución con memoria dinámica #define MAX 50 main() { int* pi; pi = get_vector_discreto(1, 4); } ¿Qué está mal? int* get_vector_discreto(int min, int max){ int i; int *pi = new int[max-min]; for (i=0; i<max-min; i++) pi[i] = i; return pi; } Clases y objetos - 7 Memoria dinámica IV • Solución con memoria dinámica main() { int* pi; pi = get_vector_discreto(1, 4); delete [] pi; } int* get_vector_discreto(int min, int max){ int i; int *pi = new int[max-min]; for (i=0; i<max-min; i++) pi[i] = i; return pi; } Clases y objetos - 8 Clases y objetos Ejemplo CComplejo I #ifndef CCOMPLEJO_H #define CCOMPLEJO_H CComplejo.h /** * Descripcion: clase que representa a los números complejos */ class CComplejo { private: double m_d_real; /** parte real*/ double m_d_imag; /** parte imaginaria*/ public: // constructores y destructor CComplejo(); CComplejo(double r, double i); ~CComplejo(); // funciones de acceso a miembros double getReal(); double getImag(); }; #endif Clases y objetos - 10 Ejemplo CComplejo II #include "CComplejo.h" #include <iostream> CComplejo.cpp using namespace std; // ---------- constructores CComplejo::CComplejo(){ m_d_imag = 0; m_d_real = 0; cout << "Me han creado por defecto" << endl; } CComplejo::CComplejo(double r, double i){ m_d_imag = i; m_d_real = r; cout << "Me han creado con r = " << m_d_real << " e i = " << m_d_imag << endl; } Clases y objetos - 11 Ejemplo CComplejo III CComplejo::~CComplejo(){ cout << "Me han eliminado (" << m_d_real << "," << m_d_imag << ")" << endl; } // ---------- funciones de acceso a miembros double CComplejo::getReal(){ return m_d_real; } CComplejo.cpp double CComplejo::getImag(){ return m_d_imag; } Clases y objetos - 12 Ejemplo CComplejo IV #include "CComplejo.h" main.cpp void pruComplejos(){ CComplejo C1,C2; cout << "Dentro de pruComplejos" << endl; Me han creado por defecto } Antes de llamar a pruComplejos Me han creado por defecto void main(void) Me han creado por defecto { Dentro de pruComplejos CComplejo C1; Me han eliminado cout << "Antes de llamar a pruComplejos" << (0,0) endl; Me han eliminado (0,0) pruComplejos(); Fuera pruComplejos cout << "Fuera de pruComplejos" << de endl; Me han creado con r = 0 e i = 3 CComplejo C2(0,3); Antes del final cout << "Antes del final" << endl; Me han eliminado (0,3) } Me han eliminado (0,0) Clases y objetos - 13 Ejemplo CComplejo V • ¿Y el main con memoria dinámica? void main(void) ¿Qué está { mal? CComplejo *C1 = new CComplejo(); cout << "Antes de llamar a pruComplejos" << endl; pruComplejos(); cout << "Fuera de pruComplejos" << endl; CComplejo *C2 = new CComplejo(0,3); cout << "Antes del final" << endl; } Clases y objetos - 14 Ejemplo CComplejo VI • ¿Y el main con memoria dinámica? void main(void) { CComplejo *C1 = new CComplejo(); cout << "Antes de llamar a pruComplejos" << endl; pruComplejos(); cout << "Fuera de pruComplejos" << endl; CComplejo *C2 = new CComplejo(0,3); cout << "Antes del final" << endl; delete C1; delete C2; } Clases y objetos - 15 Definición de una clase I • Una clase es un tipo de datos que tiene cuatro atributos asociados: – Variables miembro – Funciones miembro – Niveles de acceso tanto a las variables miembro como a las funciones miembro. • private: no se puede acceder desde fuera de la clase. Oculta la información. • public: se puede acceder desde fuera • protected: sólo las clases hijas de esta clase pueden acceder a la información (se verá más adelante). – Un nombre como etiqueta que sirve para identificar al nuevo tipo definido por la clase Clases y objetos - 16 Definición de una clase II class CClase { // Privada por defecto public: // Accesible desde fuera protected: // Acceso restringido private: // Inaccesible desde fuera }; Clases y objetos - 17 Definición de una clase III • Elementos de una clase: – Variables miembro (Atributos). En el nombre se añade al principio m_, como por ejemplo double m_d_real. – Funciones miembro (Métodos). • Un objeto es un elemento de una determinada clase. – Clase número complejo CComplejo. – C1, C2 son objetos de clase CComplejo. • Estado del objeto – Lo definen las variables miembro – No se debe poder acceder desde fuera del objeto • Comportamiento del objeto – Lo definen las funciones miembro • Objeto es a clase como variable es a tipo. Clases y objetos - 18 Vida de un objeto • Igual que un tipo básico • Creación de un objeto – Se reserva en memoria espacio para los datos internos del objeto. – Se llama a una función para inicializar estos datos: constructor del objeto. • Acceso al objeto a través de las funciones miembro visibles. • Destrucción de un objeto – Al igual que después de reservar espacio se llama al constructor del objeto, antes de destruirlo se llama al destructor del objeto. Clases y objetos - 19 Funciones miembro de una clase Constructores de un objeto • Son funciones miembro cuyo nombre es el nombre de la clase. – No se puede llamar directamente • Cada vez que se crea un objeto de una clase, se crea espacio de forma automática para las variables miembro y a continuación se llama al constructor para que termine de inicializarlas • Siempre hay dos constructores por defecto: – Sin parámetros: no hace nada. – Desde otro objeto: inicializa las variables miembros con el valor que tienen en el otro objeto. • Se pueden definir constructores adicionales con parámetros, que a su vez tengan valores por defecto. Clases y objetos - 21 Destructor de un objeto • Es una función miembro de la clase cuyo nombre es el nombre de la clase acompañado ~. – No se puede llamar directamente • Cuando un objeto va a ser eliminado de un programa, antes de liberar la memoria asignada a sus variables miembro, se llama al destructor. • Hay un destructor por defecto que no hace nada. • El destructor se puede sobrecargar: – Esto es muy útil en el caso de variables que utilicen memoria dinámica para poderla liberar. • El destructor no lleva parámetros. • Sólo puede haber un destructor Clases y objetos - 22 El operador de asignación = (I) • Al igual que en los tipos básicos (int, double, etc) las clases tienen automáticamente asociadas un operador de asignación (=) entre objetos de una misma clase. • Por defecto, al asignar un objeto a otro de la misma clase hay una asignación o copia miembro a miembro. • Se puede redefinir esta asignación (sobrecarga del operador =) Clases y objetos - 23 El operador de asignación = (II) void main(void) ¡OJO! Acceso a { miembro CComplejo C1(0,1), C2; C2 = C1; cout << "C2 = (" << C2.getReal() << "," << C2.getImag() << ")" << endl; } Me Me C2 Me Me han creado con r = 0 e i = 1 han creado por defecto = (0,1) han eliminado (0,1) han eliminado (0,1) Clases y objetos - 24 El operador de asignación = (III) • ¿Y el main con memoria dinámica? void main(void) ¡OJO! Acceso a { miembro CComplejo *C1, *C2; C1 = new CComplejo(0,1); *C2 = *C1; cout << "C2 = (" << C2->getReal() << "," << C2->getImag() << ")" << endl; delete C1; delete C2; } Clases y objetos - 25 Sobrecarga de operador = (I) class CComplejo { private: double m_d_real; /** parte real*/ double m_d_imag; /** parte imaginaria*/ public: // constructores y destructor CComplejo(); CComplejo(double r, double i); ~CComplejo(); // funciones de acceso a miembros double getReal(); double getImag(); void operator=(CComplejo c); }; Clases y objetos - 26 Sobrecarga de operador = (II) • Es importante si la clase tiene miembros que son memoria dinámica void CComplejo::operator=(CComplejo c){ m_d_real=c.m_d_real; m_d_imag=c.m_d_imag; cout << "Operador igualdad" << endl; } ¡OJO! Acceso a zona privada Clases y objetos - 27 Sobrecarga de operador = (III) void main(void) { CComplejo C1(0,1), C2; C2 = C1; cout << "C2 = (" << C2.getReal() << "," << C2.getImag() << ")" << endl; } He creado dos objetos y ¡ se eliminan tres !! Me han creado con r = 0 e i = 1 Me han creado por defecto Operador igualdad Me han eliminado (0,1) C2 = (0,1) Me han eliminado (0,1) Me han eliminado (0,1) Clases y objetos - 28 Paso por referencia (I) class CComplejo { private: double m_d_real; /** parte real*/ double m_d_imag; /** parte imaginaria*/ public: // constructores y destructor CComplejo(); CComplejo(double r, double i); ~CComplejo(); // funciones de acceso a miembros double getReal(); double getImag(); void operator=(const CComplejo& c); }; Clases y objetos - 29 Paso por referencia (II) void main(void) { CComplejo C1(0,1), C2; ¡¡Uhm, qué C2 = C1; raro!! cout << "C2 = (" << C2.getReal() << "," << C2.getImag() << ")" << endl; } void CComplejo::operator=(const CComplejo& c){ m_d_real=c.m_d_real; m_d_imag=c.m_d_imag; cout << "Operador igualdad" << endl; } Me han creado con r = 0 e i = 1 Me han creado por defecto Operador igualdad C2 = (0,1) Me han eliminado (0,1) Me han eliminado (0,1) Clases y objetos - 30 Sobrecarga del constructor de copia (I) class CComplejo { private: double m_d_real; /** parte real*/ double m_d_imag; /** parte imaginaria*/ public: // constructores y destructor CComplejo(); CComplejo(double r, double i); CComplejo(const CComplejo& c); ~CComplejo(); // funciones de acceso a miembros double getReal(); double getImag(); void operator=(const CComplejo& c); }; Clases y objetos - 31 Sobrecarga de constructor de copia (II) void copia(CComplejo c){ cout << "Dentro de copia (" << c.getReal() << "," << c.getImag() << ")" << endl; } void main(void) { CComplejo C1(0,1); copia(C1); } ¿Se puede quitar la referencia? CComplejo::CComplejo(const CComplejo& c){ m_d_real=c.m_d_real; m_d_imag=c.m_d_imag; cout << "Me han creado por copia de (" << c.m_d_real << "," << c.m_d_imag << ")" << endl; } Clases y objetos - 32 Sobrecarga de constructor de copia (III) void copia(CComplejo c){ cout << "Dentro de copia (" << c.getReal() << "," << c.getImag() << ")" << endl; } void main(void) { CComplejo C1(0,1); copia(C1); } Me han Me han Dentro Me han Me han ¿Cómo se evita la copia? creado con r = 0 e i = 1 creado por copia de (0,1) de copia (0,1) eliminado (0,1) eliminado (0,1) Clases y objetos - 33 Definición del operador << (I) #include <iostream> class CComplejo { private: double m_d_real; /** parte real*/ double m_d_imag; /** parte imaginaria*/ public: // constructores y destructor ... void operator=(const CComplejo& c); friend std::ostream& operator<<(std::ostream &o, const CComplejo& c); }; Es una función que no es de la clase, pero que puede acceder a la parte privada de la misma Clases y objetos - 34 Definición del operador << (II) ostream& operator<<(ostream &o, const CComplejo& c){ o << "(" << c.m_d_real << "," << c.m_d_imag << ")"; return o; } ¡OJO! Acceso a zona privada void main(void) { CComplejo C1(0,1); cout << "C1: " << C1 << endl; } Me han creado con r = 0 e i = 1 C1: (0,1) Me han eliminado (0,1) Clases y objetos - 35 Mejora de la clase CComplejo (I) • ¿Que pasa si en vez de querer sacar por pantalla el complejo en formato (r,i) se quiere en [r,i]? CComplejo::CComplejo(const CComplejo& c){ m_d_real=c.m_d_real; m_d_imag=c.m_d_imag; cout << "Me han creado por copia de " << c << endl; } CComplejo::CComplejo(double r, double i){ m_d_imag = i; m_d_real = r; cout << "Me han creado con " << *this << endl; } CComplejo::~CComplejo(){ cout << "Me han eliminado " << *this << endl; } this es un puntero al objeto en ejecución Clases y objetos - 36 Mejora de la clase CComplejo (II) • Quitar más código duplicado CComplejo::CComplejo(const CComplejo& c){ *this = c; cout << "Me han creado por copia de " << c << endl; } Clases y objetos - 37 Funciones miembro de una clase • Representan los métodos a través de los cuales se establece relación con el objeto. • El nombre de la función representa la acción a realizar por el objeto. • Para mantener la integridad del objeto, toda comunicación con el objeto debería ser a través de la funciones miembro: • Si una función externa a la clase quiere acceder a información interna como es el caso de >> o << de iostream debe ser declarada como amiga en la zona pública (friend). • Se pueden sobrecargar los nombres de las funciones. Clases y objetos - 38 Resumen hasta ahora • Toda clase debe tener – – – – – Destructor Constructor de copia que llama al operador = Operador = sobrecargado Constructor por defecto Funciones de acceso a miembros • Siempre que se pueda se pasan los objetos por referencia • No olvidarse del uso de const Clases y objetos - 39 Definición del operador + (I) CComplejo CComplejo::operator+(const CComplejo c){ CComplejo t; t.m_d_real=m_d_real+c.m_d_real; t.m_d_imag=m_d_imag+c.m_d_imag; cout << "Suma de complejos" << endl; return t; } Clases y objetos - 40 Definición del operador + (II) void main(void) { CComplejo C1(0,1), C2(2,3), C3; C3 = C1 + C2; cout << "C3: " << C3 << endl; Me han creado con (0,1) } Inicialmente eran 3 objetos y he necesitado 6 Me han creado con (2,3) Me han creado por defecto Operador igualdad Me han creado por copia de (2,3) Me han creado por defecto Suma de complejos Operador igualdad Me han creado por copia de (2,4) Me han eliminado (2,4) Me han eliminado (2,3) Operador igualdad Me han eliminado (2,4) C3: (2,4) Me han eliminado (2,4) Me han eliminado (2,3) Me han eliminado (0,1) Clases y objetos - 41 Definición del operador + (III) CComplejo CComplejo::operator+(const CComplejo& c){ CComplejo t; t.m_d_real=m_d_real+c.m_d_real; t.m_d_imag=m_d_imag+c.m_d_imag; cout << "Suma de complejos" << endl; return t; } Clases y objetos - 42 Definición del operador + (IV) void main(void) { CComplejo C1(0,1), C2(2,3), C3; C3 = C1 + C2; cout << "C3: " << C3 << endl; } Me han creado con (0,1) Me han creado con (2,3) Sigue Me han creado por defecto habiendo Me han creado por defecto muchos Suma de complejos objetos Operador igualdad Me han creado por copia de (2,4) Me han eliminado (2,4) Operador igualdad Me han eliminado (2,4) C3: (2,4) Me han eliminado (2,4) Me han eliminado (2,3) Me han eliminado (0,1) Clases y objetos - 43 Definición del operador + (V) CComplejo& CComplejo::operator+(const CComplejo& c){ CComplejo t; t.m_d_real = m_d_real+c.m_d_real; t.m_d_imag = m_d_imag+c.m_d_imag; cout << "Suma de complejos" << endl; return t; } CComplejo& CComplejo::operator+(const CComplejo& c){ m_d_real+=c.m_d_real; m_d_imag+=c.m_d_imag; cout << "Suma de complejos" << endl; return *this; } Clases y objetos - 44 Definición del operador + (VI) void main(void) { CComplejo C1(0,1), C2(2,3), C3; C3 = C1 + C2; cout << "C3: " << C3 << endl; } ¡Aquí hay algo raro! Me han creado con (0,1) Me han creado con (2,3) Me han creado por defecto Suma de complejos Operador igualdad C3: (2,4) Me han eliminado (2,4) Me han eliminado (2,3) Me han eliminado (2,4) Clases y objetos - 45 Definición del operador + (VII) void CComplejo::operator+=(const CComplejo& c){ m_d_real+=c.m_d_real; m_d_imag+=c.m_d_imag; cout << "Suma monaria de complejos" << endl; } Clases y objetos - 46 Definición del operador + (VIII) void main(void) { CComplejo C1(0,1), C2(2,3); C1 += C2; cout << "C1: " << C1 << endl; } Me han creado con (0,1) Me han creado con (2,3) Suma monaria de complejos C1: (2,4) Me han eliminado (2,3) Me han eliminado (2,4) Clases y objetos - 47 Definición del operador + (IX) • Mejora del operador + CComplejo CComplejo::operator+(const CComplejo& c){ CComplejo t = *this; t += c; return t; } Clases y objetos - 48 Sobrecarga de operadores • Los operadores de C++ funcionan exactamente igual que una función: CComplejo C1,C2,C3; C1=C2+C3; C1.operator=(C2.operator+(C3)); • Siempre hay definido por defecto: = y & para cualquier clase (por supuesto, se pueden sobrecargar). • Permiten definir un álgebra de los objetos de una clase. • No se debe abusar. Si a partir del operador no se sabe claramente la acción a realizar es mejor utilizar funciones. Clases y objetos - 49 Ejercicios con operadores • Definir el operador > de CComplejo • Definir el operador+ pero que sea friend de CComplejo • Definir el operador + para trabajar con punteros de CComplejo En C++ las funciones pueden tener el mismo nombre, si los parámetros son distintos Clases y objetos - 50 Diseño básico en C++ C++. Un nuevo estilo de programación • Programación estructurada – La solución del problema se consigue construyendo un programa formado por funciones, cada una especializada en resolver una parte del problema. – Los datos son intercambiados entre funciones bajo control del programa principal. • Programación orientada a objetos – La solución del problema es un conjunto de objetos que interactúan entre sí, a través de sus interfaces. – Ninguno sabe como funciona el otro por dentro. Objeto2 Programa principal Objeto1 Datos Objeto3 Función1 Función2 Función3 Objeto4 Clases y objetos - 52