POO 2004 P M Introducción a la POO en C++ Algunas diferencias entre C y C++ Objetos, métodos, encapsulado. T e m a Clases Métodos Constructores y Destructores Sobrecarga Amigos Entrada y salida en C++ Espacios de nombres Miembros estáticos 2 PM. 2. POO (1) Características de la POO en C++ Respecto a la POO, las características más importantes de C++ son: • • • • • • Herencia múltiple Sobrecarga de operadores y funciones Derivación Funciones virtuales Plantillas Gestión de excepciones PM. 2. POO (2) Pequeñas diferencias C-C++ (1) Argumentos con valor por defecto int a(int a=1, int b=2, int c) NO a(4,5) ?? int a(int c, int a=1, int b=2) hace que a(1), ó a(1,2) ó a(1,2,3) no sean ambiguos PM. 2. POO (3) Pequeñas diferencias C-C++ (2) Funciones inline frente a macros de preprocesador supongamos definido #define cuadrado(x) (x*x) entonces void f(double d, int i) { r = cuadrado(d); // va bien r = cuadrado(i++); // va mal: significa (i++*i++); r = cuadrado(d+1); // va mal: significa (d+1*d+1); // que es, (d+d+1); iría mejor #define cuadrado(x) ((x)*(x)) // va mejor PM. 2. POO (4) Pequeñas difs. C-C++: funciones inline Pero sigue sin resolver cuadrado(i++) Así que C++ aporta una solución: inline int icuadrado(int x) { return x * x; } que expande C++ (no el procesador), sí resuelve el problema: es como una función normal pero que se expande entre las líneas del código que la llama PM. 2. POO (5) Pequeñas difs. C-C++: funciones inline La ventaja de las funciones inline es que no retrasan con llamadas a función. Son expresiones que se repinten en el código y no se quieren convertir en función para no recargar el número de llamadas a función. icuadrado(i++) // va bien PM. 2. POO (6) C no tiene parámetros por referencia C++ C void swap(int *pa, int *pb) void swap(int & a, int & b) { int t = *pa; *pa=*pb; *pb=t; } { int t = a; a=b; b=t; } main() main() { swap(&x, &y);} { swap(x, y);} El modificador & en los parámetros formales en C++ indica que ese parámetro es pasado por referencia. Algo que en C es imposible. En C no existía el paso por referencia, había que pasar la dirección (operador &) de la variable. PM. 2. POO (7) Operadores new y delete int *pint, *parr; pint = new int; parr = new int[10] // … delete pint; delete[] parr; Son operadores y por lo tanto el compilador interviene "inteligentemente" decidiendo el tipo de solicitud de memoria dinámica y el tamaño necesitado. Esto es más adecuado en la creación de objetos PM. 2. POO (8) Operadores new y delete, ventajas • No hace falta calcular los tamaños con sizeof(): pa = new int; // malloc(sizeof(int)) • El propio delete se encarga de poner a 0 (NULL) el puntero y new de poner a 0 la memoria adquirida delete pint; // deja 0 modificando pint // antes era: free(pint); pint=NULL; PM. 2. POO (9) Equivalencia entre new y malloc truco en C #define NEW(X) (X *) malloc(sizeof(X)) pa = NEW(X); equivale en C++ a pa = new X; PM. 2. POO (10) Classes • La mayor diferencia entre C y C++ está en soporte que este último ofrece para las clases • El nombre original de C++ era "C with Classes" • Esencialmente una clase es un tipo abstracto de datos (TAD): colección de datos junto a operadores para acceder y modificarlos PM. 2. POO (11) Clases, uso Se puede definir una clase "Complejo" y variables de tipo Complejo: Complejo c1, c2, c3; y entonces seguir operando no sólo con funciones sino también con operadores: c3 = c1 / c2; c3 = c1 + c2; c3++;… en la que "/", "+", "++" se han sobredefinido para operar con Complejo. PM. 2. POO (12) Clases, declaración La declaración es muy parecida a la declaración de estructuras, de la que son extensión: class Complejo { float parteReal; float parteImaginaria; }; PM. 2. POO (13) Clases, declaración La declaración es muy parecida a la declaración de estructuras, de la que son extensión: class Complejo { float parteReal; float parteImaginaria; }; Los campos son ahora llamados PM. 2. POO (13) Clases, declaración La declaración es muy parecida a la declaración de estructuras, de la que son extensión: class Complejo { float parteReal; float parteImaginaria; }; Los campos son ahora llamados atributos PM. 2. POO (13) Clases, declaración La declaración es muy parecida a la declaración de estructuras, de la que son extensión: class Complejo { float parteReal; float parteImaginaria; }; Los campos son ahora llamados atributos de la clase. PM. 2. POO (13) Clases, uso Una vez declarada la clase, se pueden crear objetos de esa clase: Complejo c1, c2; no es necesario decir Class Complejo c1… Los objetos c1 y c2 son instancias de la clase Complejo Una instancia de cualquier clase se denomina objeto PM. 2. POO (14) Los atributos están ocultos Los miembros de una estructura son accedidos mediante los operadores "." y "->" En una clase, por otro lado, los miembros están ocultos, mientras no se indique lo contrario, o sea, que por defecto son inaccesibles, de manera que las siguientes acciones son ilegales: c1.parteReal = 0.0; // ilegal, no accesible im = c2.parteImaginaria; // ilegal, no accesible PM. 2. POO (15) Los atributos están ocultos Los miembros de una estructura son accedidos mediante los operadores "." y "->" En una clase, por otro lado, los miembros están ocultos, mientras no se indique lo contrario, o sea, que por defecto son inaccesibles, de manera que las siguientes acciones son ilegales: c1.parteReal = 0.0; // ilegal, no accesible im = c2.parteImaginaria; // ilegal, no accesible Decimos que parteReal y parteImaginaria son miembros privados de la clase Complejo. PM. 2. POO (15) Atributos public Si queremos permitir el acceso desde fuera a los atributos de una clase hay que declararlos públicos: class Complejo { public: float parteReal; float parteImaginaria; }; Podemos combinar partes públicas y privadas de una clase: class Complejo { public: float parteReal; private: float parteImaginaria; }; PM. 2. POO (16) Miembros Si no son visibles los miembros privados de una clase… ¿cómo se pueden modificar o ver sus valores? La respuesta es simple: PM. 2. POO (17) Miembros Si no son visibles los miembros privados de una clase… ¿cómo se pueden modificar o ver sus valores? La respuesta es simple: Las funciones que necesiten acceder a los datos miembros de una clase deben estar declarados dentro de la misma clase PM. 2. POO (17) Miembros Si no son visibles los miembros privados de una clase… ¿cómo se pueden modificar o ver sus valores? La respuesta es simple: Las funciones que necesiten acceder a los datos miembros de una clase deben estar declarados dentro de la misma clase Son las funciones miembro o métodos. PM. 2. POO (17) Diferencia con las estructuras En resumen • Para controlar el uso de la información que determina el estado de un objeto se deben usar métodos • Un objeto tiene un estado interno sólo reconocible por las respuestas a las llamadas a sus métodos • Para modificar el estado de un objeto se debe llamar a sus métodos de acceso PM. 2. POO (18) Ejemplo de métodos class Complejo { public: void crear(int preal, int pimag); void print(); private: float parteReal; float parteImaginaria; }; PM. 2. POO (19) Métodos públicos y privados Vemos que los métodos para pueden ser declarados: • en la parte pública: son la interfaz de uso • en la parte privada: procedimientos internos necesarios para el funcionamiento del objeto. PM. 2. POO (20) Ejemplo interfaz de uso procedimientos internos atributos class Fraccion { public: void crear(int num, int denom); void print(); private: void reduce(); int numerador, denominador; } PM. 2. POO (21) Métodos especializados • En la mayoría de las clases es necesario dar valores iniciales a sus atributos: – Constructor • Liberar memoria, cerrar canales, etc. al destruirlo: – Destructor PM. 2. POO (22) Métodos especializados • Constructor – Sería el miembro público cuyo nombre es el mismo que el de la clase, sin devolver nada. – Puede llevar parámetros • Destructor – Sería el miembro público cuyo nombre es el mismo que el de la clase con un ~ delante, sin devolver nada, ni void • Constructor de copia – Sería el miembro público cuyo nombre es el mismo que el de la clase y cuyo único argumento es un objeto de la clase en modo const & PM. 2. POO (23) Constructor • Constructor – Sería el miembro público cuyo nombre es el mismo que el de la clase, sin devolver nada. – Puede llevar parámetros – Puede haber varios con distintas signaturas. – El Constructor de copia es uno de los posibles pueden inicializar directamente los atributos con la notación : atrib1(parám1), atrib2(parám2) que se pone tras la declaración. PM. 2. POO (24) Ejemplo: complejo.h interfaz de uso atributos class Complejo { public: constructor automático Complejo(int re=0, int im=1) : equivale a real(re), imag(im) { }; {real=re; imag=im;} void print() const; // no modif. objeto void pon(const int re=0, const int im=1); private: int real, imag; }; Complejo c1(3,2), c2; PM. 2. POO (25) Constructor, casos • Ejemplos de uso de Constructor Según encajen pueden ser llamados un constructor u otro. Complejo c(10, -2), c2(5,4), c3[8]; nótese que al tenerse valores por defecto, cuando no se ponen parámetros como en el caso de los 8 elementos del array de Complejo c3, todos toman el valor, los valores por defecto. PM. 2. POO (26) Cuerpo de los métodos La mayoría de los métodos no se desarrollan en línea, como los constructores sino que el desarrollo (los cuerpos de los métodos) se desarrollan en el fichero pareja de la cabecera .h y que sería .cpp .h .cpp en él se accede a los miembros de la clase mediante la notación de ámbito nombreClase:: PM. 2. POO (27) Cuerpo de los métodos .h class Nave { public: Nave(const int x=0, const int y=0) {X=x;Y=y;}; Mover(const int dx, const int dy); ... } .cpp Nave:: Mover(const int dx, const int dy) { X += dx; Y += dy; } PM. 2. POO (28) Ejemplo: complejo.cpp #include <iostream.h> #include "complejo.h" // siempre al final Complejo::pon(const int re, const int im) { real = re; imag = im; } Complejo::print() { cout << real <<"+ i "<<imag; } PM. 2. POO (29) Uso de una clase Para usar una clase bastará entonces importar su descripción (la declaración de la clase que está en la cabecera .h) y crear objetos, etcétera, en el programa: PM. 2. POO (30) Uso de una clase ojo: se importa clase.h tanto en clase.cpp como en prueba.cpp clase.h #include "clase.h" clase.cpp #include "clase.h" prueba.cpp PM. 2. POO (31) Ejemplo: prucomp.cpp #include <iostream.h> #include "complejo.h" llamada a miembro int main() { del objeto Complejo c1; c1.print(); cout << endl; c1.pon(2,-3); c1.print(); cout << endl; } PM. 2. POO (32) Métodos destructor • Destructor – Sería el miembro público cuyo nombre es el mismo que el de la clase con un ~ delante, sin devolver nada, ni void el destructor no siempre hace falta, sólo cuando se llame a rutinas que modifiquen el estado del sistema y antes de destruir el objeto haya que reponer ese estado PM. 2. POO (33) Ejemplo: destructor en matriz.h class Matriz { public: Matriz(int n, int m) : N(n), M(m), datos(new int[n*m]) { }; ~Matriz(void) { delete[ ] datos; }; int ver(const int fila,const int col); void pon(const int fila,const int col,const int x); void dims(int &n, int &m) {n=N;m=M;}; private: int N, M; int *datos; }; PM. 2. POO (34) Destrucción implícita La destrucción implícita se llama sóla, automáticamente, cuando el objeto deja de estar accesible, por ejemplo, al acabar el ámbito de su visibilidad La destrucción puede llamarse explícitamente llamando al método de destrucción aunque esto último suele ser innecesario PM. 2. POO (35) Construcción por copia de objetos • Constructor por copia – Sería el miembro público cuyo nombre es el mismo que el de la clase y cuyo único argumento es un objeto de la clase en modo const clase& No siempre hace falta. Al igual que el destructor, el método de copia hace falta cuando la creación solicita memoria al sistema, abre ficheros o cosas así, en general, modificando el estado externo del sistema. PM. 2. POO (36) Construcción por copia de objetos La construcción por copia se "ejecuta" con la declaraci: • implícita en la creación: Matriz m2 = m1; donde m2, obsérvese que no lleva parámetros (obligados) del constructor, por hacerse mediante copia. • explícita en la creación: Matriz m3(m1); donde de nuevo, no se usan los parámetros del constructor automático por llamarse explícitamente al método constructor por copia PM. 2. POO (37) Ejemplo: matriz.h class Matriz { public: Matriz(int n, int m) : N(n), M(m), datos(new int[n*m]) { }; ~Matriz(void) { delete[ ] datos; }; Matriz::Matriz(const Matriz& origen); int ver(const int fila,const int col); void pon(const int fila,const int col,const int x); void dims(int &n, int &m) {n=N;m=M;}; private: int N, M; int *datos; }; PM. 2. POO (38) Ejemplo: matriz.cpp Matriz::Matriz(const Matriz& origen) { int i, j; N = origen.N; M = origen.M; datos = new int[N*M]; for (i=0;i<N;i++) for (j=0;j<M;j++) pon(i,j, origen.ver(i,j)); } int Matriz::ver(const int fil,const int col) { return datos[fil*M + col]; } void Matriz::pon(const int fil,const int col, const int x) { datos[fil*M + col] = x; } PM. 2. POO (39) Ejemplo: prumatriz.cpp int main() { Matriz m(10, 20); m.pon(2,2,1); printf("en m 2,2: %d\n", m.ver(2,2)); Matriz mm = m; // actúa el constructor de copia implícito printf("en mm 2,2: %d\n", mm.ver(2,2)); Matriz mmm(m); // constructor de copia explícita printf("en mmm 2,2: %d\n", mmm.ver(2,2)); return 0; } PM. 2. POO (40) Copia de objetos La iniciación de objetos durante la construcción admite pues diversas formas: ejemplo: class T { public: T(): dato(valor), ... {}; // Creador sin parámetros T(tipo uno): dato(uno), ... {}; // Creador con un parámetro T(const T& de): dato(de.dato), ... {}; // constructor por copia .... pero una operación como: ... t1 = t2; ... hará sólo la copia de los atributos, y no será correcta en general PM. 2. POO (41) Peligro de la copia de objetos en parámetros Peor aún, si hacemos una llamada a una función externa como: ... pinta(t1); ... siendo void pinta(const T t1) los resultados serán desastrosos. ¿Por qué? PM. 2. POO (42) Peligro de la copia de objetos en parámetros El motivo es que pinta(t1) construye una copia de t1 para el parámetro local de pinta y no sólo hace esa copia sino que al acabar pinta() destruye esa copia. Una solución es hacer void pinta(const T& t) • sea, declarar el parámetro como referencia constante – No se copia – No se cambia PM. 2. POO (43) Copia de objetos en parámetros Al recibirse el objeto por referencia no hay destrucción automática al acabar el procedimiento y no habrá sorpresas en los casos de memoria dinámica enlazada. void pinta(const T& t) PM. 2. POO (44) Copia por asignación de objetos en general Pero el problema de la copia es más general: sólo hemos visto como evitarlo durante la creación por copia y durante el paso de parámetros. Para que la operación = funcione correctamente con nuestros objetos particulares hace falta hará falta sobrecargar el operador asignación con nuestro algoritmo particular de copiado. T& operator= (const T& orig); PM. 2. POO (45) Copia por asignación de objetos en general ejemplo: class T { public: T(); // Creador sin parámetros T(tipo uno); // Creador con un parámetro T(const T& de); // constructor por copia T& operator= (const T& de); // operador de copia .... PM. 2. POO (46) Miembros amigos ¿Puede ser posible que otra clase acceda a los miembros de una clase?: Sí permiso garantizado class Una { friend class Otra; private: int atri; void metodo(); }; class Otra { void metodo2(Una u) { u.atri = 33; u.metodo(); } }; PM. 2. POO (47) Sobrecarga de << Si se define la clase << como amiga friend ostream& operator<<(ostream &os, const Text& orig); y se desarrolla como por ejemplo: ostream& operator<<(ostream &os, const Text& orig) { os << orig.s; // accede a su parte privada return os; } PM. 2. POO (48) Ejemplo +completo class Text { public: Text(const char* txtPtr); Text() : s(0) {}; ~Text(); Text(const Text& orig); bool operator== (const Text& derecho) const; char operator[ ] (unsigned long indice) const; unsigned long Longitud() const; void pinta(const bool nl = true) const; friend Text operator+ (const Text& ls, const Text& rs); operator const char*() const; Text& operator= (const Text& orig); Text& operator= (const char *str); friend ostream& operator<<(ostream &os, const Text& orig); private: char *s; // la cadena C unsigned long l; unsigned long strlen(const char *str) const; void strasigna(const char *desde); bool strcmp(const char *con) const; Ejemplo +completo }; class Text { public: Text(const char* txtPtr); Text() : s(0) {}; ~Text(); Text(const Text& orig); bool operator== (const Text& derecho) const; char operator[ ] (unsigned long indice) const; unsigned long Longitud() const; void pinta(const bool nl = true) const; friend Text operator+ (const Text& ls, const Text& rs); operator const char*() const; Text& operator= (const Text& orig); Text& operator= (const char *str); friend ostream& operator<<(ostream &os, const Text& orig); private: char *s; // la cadena C unsigned long l; PM. 2. POO (49) uso: t1 == t2 t1[33] t3 = t1 + t2 (char*) (t2) t1 = t2 t1 = "abc" cout << t1; unsigned long strlen(const char *str) const; void strasigna(const char *desde); bool strcmp(const char *con) const; }; PM. 2. POO (50) Ejemplo +completo #include <iostream.h> #include "text.h" // Constructor desde cadena C Text::Text(const char* txtPtr) : s(0), l(0) { if (txtPtr != 0) strasigna(txtPtr); } // Constructor de copia Text::Text(const Text& orig) : s(0), l(orig.l) { if (orig.l != 0) strasigna(orig.s); } // destructor Text::~Text() { delete[] s; } bool Text::operator== (const Text& derecho) const { if (derecho.l == 0 && 0 == l) return true; else return strcmp(derecho.s); } char Text::operator[] (unsigned long indice) const { if (0 <= indice && indice < l) return s[indice]; else return '\0'; } // averigua la longitud unsigned long Text::Longitud() const { return l; } // Adapta el tipo (cast) a cadena C Text::operator const char*() const { return s; } Ejemplo +completo PM. 2. POO (51) // Operador de asignación Text& Text::operator= (const Text& orig) { // evita a = a; if (this == &orig) return *this; if (l != 0) { delete[] s; l = 0; } if (orig.l) strasigna(orig.s); return *this; } // Operador de asignación desde una str Text& Text::operator= (const char *str) { if (l != 0) { delete[] s; l = 0; } if (str != 0) strasigna(str); return *this; } unsigned long Text::strlen(const char *str) const { unsigned long n = 0; while (str[n]) n++; return n; } void Text::strasigna(const char *desde) { unsigned long i = 0; l = strlen(desde); s = new char[l+1]; while (s[i] = desde[i]) i++; } bool Text::strcmp(const char *con) const { unsigned long i = 0; while (s[i] && con[i] && s[i] == con[i]) i++; return s[i] == '\0' && con[i] == '\0'; } PM. 2. POO (52) Ejemplo +completo // amigo que permite sobrecargar '+' para concatenar Texts Text operator+ (const Text& ls, const Text& rs) { Text ret; unsigned long i=0, ii=0; ret.l = ls.l + rs.l; ret.s = new char[ret.l+1]; ret.s[0] = '\0'; if (ls.s != NULL) { while (ret.s[i] = ls.s[i]) i++; ii = i-1; i = 0; } if (rs.s != NULL) while (ret.s[ii] = rs.s[i]) i++; ii++; return ret; } ostream& operator<<(ostream &os, const Text& orig) { os << orig.s; return os; } PM. 2. POO (53) espacios de nombres Una declaración accesible en todo el programa es una declaración global. La colección de todas las declaraciones se denomina espacio de nombres global. PM. 2. POO (54) using namespace C++, sin embargo, proporciona a los programadores la posibilidad de situar las declaraciones en espacios de nombres definidos por el programa. namespace nombre { void f() { cout << desde f en nombre<<endl; }; la llamada hay que hacerla: nombre::f(); excepto que se ponga antes: using namespace nombre; PM. 2. POO (55) librerías con namespace Las cabeceras que utilizan el espacio de nombres global suelen acabarse como clásicamente: .h Las librerías que sitúan sus declaraciones dentro de espacios de nombres (no globales) no usan extensión. Por ejemplo: <iostream> es un archivo de cabecera cuyas declaraciones están en el espacio de nombres std PM. 2. POO (56) librerías con namespace Por eso se suele hacer: #include <iostream> #include <iomanip> using namespace std; int main () { cout << setw (10); cout << 77 << endl; return 0; } PM. 2. POO (57) Miembros estáticos PM. 2. POO (58) Miembros estáticos Se pueden definir miembros (atributos o métodos) con el atributo static: PM. 2. POO (58) Miembros estáticos Se pueden definir miembros (atributos o métodos) con el atributo static: • Tal miembro es común a todos los objetos de tal clase. PM. 2. POO (58) Miembros estáticos Se pueden definir miembros (atributos o métodos) con el atributo static: • Tal miembro es común a todos los objetos de tal clase. • Tales miembros existen aunque no haya ningún objeto de tal clase PM. 2. POO (58) Miembros estáticos Se pueden definir miembros (atributos o métodos) con el atributo static: • Tal miembro es común a todos los objetos de tal clase. • Tales miembros existen aunque no haya ningún objeto de tal clase • Para llamarlos se puede usar un objeto cualquiera de los de la clase o usar el especificador de ámbito Clase::metod() PM. 2. POO (58) Miembros estáticos Se pueden definir miembros (atributos o métodos) con el atributo static: • Tal miembro es común a todos los objetos de tal clase. • Tales miembros existen aunque no haya ningún objeto de tal clase • Para llamarlos se puede usar un objeto cualquiera de los de la clase o usar el especificador de ámbito Clase::metod() • Pueden servir, por ejemplo, para mantener información global, como la cuenta de todos los objetos. No son datos globales, ya que mantienen los criterios de visibilidad. PM. 2. POO (58) FIN PM. 2. POO (59)