SEMINARIO C++ Introducción a la Programación Orientada a Objetos Parte III v. 20070918 Cristina Cachero Pedro J. Ponce de León Depto. Lenguajes y Sistemas Informáticos - Universidad de Alicante C++ ÍNDICE 1. 2. 3. 4. 5. 6. Funciones amigas Entrada / Salida Sobrecarga de funciones y operadores Gestión de memoria dinámica Atributos y métodos de clase Implementación de relaciones entre objetos 2 C++ FUNCIONES AMIGAS La parte privada de una clase sólo es accesible por: Métodos (funciones miembro) de la propia clase Funciones amigas (C++). Función Amiga: Función NO miembro de una clase, que puede tener acceso a la parte privada de esa clase. Rompe el principio de “encapsulación”. Una función se declara como amiga de una clase mediante la palabra reservada “friend”. class MiClase { friend void unaFuncionAmiga(int, MiClase&); public: //... private: int datoPrivado; }; 3 C++ FUNCIONES AMIGAS void unaFuncionAmiga(int x, MiClase& c) { c.datoPrivado = x; // ¡OK! } Conceptualmente, las funciones amigas forman parte de la interfaz de una clase (tanto como las funciones miembro). No es aconsejable abusar de su uso. 4 C++ FUNCIONES AMIGAS Razones para usarlas: Algunas funciones necesitan acceso privilegiado a más de una clase. (por ejemplo, una función para multiplicar un objeto Matriz por un objeto Vector). Algunas funciones (operadores) tienen como primer operando un objeto distinto del que llama a la función. Algunas funciones resultan más legibles si pasamos todos sus argumentos a través de la lista de argumentos. 5 C++ FUNCIONES AMIGAS Ejemplos Funciones Amigas class TFecha { friend void Copiar(TFecha*,TFecha*); // por legibilidad ... }; void Copiar( TFecha* p1, TFecha* p2 ) { p2->dia = p1->dia; p2->mes = p1->mes; Preguntas: p2->anyo = p1->anyo; } int main() { TFecha p, q; p.dia = 3; p.setDia(3); Copiar(&p, &q); } ¿Hay algún error en el main? ¿Es la función Copiar una función miembro de la clase TFecha? ¿Se os ocurre algún otro modo de implementar la función Copiar sin que sea amiga de TFecha? 6 C++ FUNCIONES AMIGAS A veces, por comodidad se decide declarar TODA UNA CLASE como amiga de otra. En este caso todas las funciones de la clase amiga pueden acceder a las partes privadas de la otra clase. Sólo para clases intimamente relacionadas. class IteradorLista { /*…*/ }; class Lista { friend class IteradorLista; //… }; 7 C++ ÍNDICE 1. 2. 3. 4. 5. 6. Funciones amigas Entrada / Salida Sobrecarga de funciones y operadores Gestión de memoria dinámica Atributos y métodos de clase Implementación de relaciones entre objetos 8 C++ BIBLIOTECA ENTRADA/SALIDA ifstream y ofstream son en realidad clases. La biblioteca iostream (#include <iostream>) define otras clases (más generales) para los flujos de entrada y salida: istream : flujo de entrada de caracteres istream cin; ostream : flujo de salida de caracteres ostream cout; ostream cerr; Nota: Las operaciones de lectura y escritura sobre ‘streams’ que se presentan a continuación funcionan para cualquier objeto de tipo istream/ostream (no sólo cin y cout). 9 C++ BIBLIOTECA ENTRADA/SALIDA Operaciones de salida: Operador << cout << 17; Se pueden concatenar llamadas cout << x << y; Algunas operaciones: Indicar el carácter de relleno: cout.fill(‘*’); // por defecto, espacio en blanco Especificar el número mínimo de caracteres para la próxima operación de salida: cout.width(4); // por defecto, cero cout.fill(‘*’); cout.width(4); cout << 12 << “+” << 1; // imprime **12+1 y no **12***+***1 11 C++ BIBLIOTECA ENTRADA/SALIDA Operaciones de entrada Operador >>. Definido para los tipos básicos. Salta espacios en blanco (espacio, tabulador o salto de línea) Entrada numérica: int x; float y; cin >> x >> y; Salta espacios y lee dígitos hasta encontrar un espacio en blanco o un carácter no numérico. Importante: si la operación encuentra un carácter no númerico, deja el flujo de entrada en estado de error y no almacena nada en la variable; Cuando el formato de entrada no es conocido (por ejemplo, en entrada interactiva), al leer enteros o reales se debe comprobar tras cada lectura que la operación no ha producido error. Por tanto no es buena idea concatenar lecturas como en el ejemplo de arriba. 13 C++ BIBLIOTECA ENTRADA/SALIDA Operaciones de entrada para carácter char c; cin>>c; Salta espacios y guarda en ‘c’ el primer carácter no blanco. c=cin.get(); // ó cin.get(c) get() devuelve el siguiente carácter en el flujo de entrada. No salta espacios. 14 C++ BIBLIOTECA ENTRADA/SALIDA Operaciones de entrada para cadenas string s; cin >> s; Salta espacios. Almacena caracteres en ‘s’ hasta encontrar un espacio en blanco o el final del fichero. Lectura de una línea completa: Con cadenas tipo C: cin.getline(char* destino,int numcar,char delimitador=‘\n’); Lee como máximo numcar-1 caracteres hasta encontrar el carácter delimitador o el final del fichero. No salta espacios. char cadena[100]; cin.getline(cadena, 100); // equiv. a cin.getline(cadena,100,’\n’); Con string: getline(istream& is, string s); Lee una línea completa de la entrada y la almacena en ‘s’ (no almacena el salto de línea final) 15 C++ BIBLIOTECA ENTRADA/SALIDA Otras operaciones de entrada cin.ignore() cin.ignore(int ncar) descarta ‘ncar’ caracteres en cin cin.ignore(int ncar, char delim) descarta el siguiente carácter en cin descarta ‘ncar’ caracteres como máximo hasta llegar al delimitador (que también se descarta) Los métodos ignore() son útiles para limpiar el flujo de entrada tras un error de lectura. 16 C++ BIBLIOTECA ENTRADA/SALIDA Otras operaciones de entrada cin.fail() : devuelve cierto si ha habido algún error al leer la entrada cin.eof() : devuelve cierto si se ha alcanzado el final del fichero (se ha intentado leer cuando no había nada en el buffer de entrada) cin.clear() : recupera al stream del estado "fail". if (!cin) { // o ‘if (cin.fail())’: si ha habido algún error... cin.clear(); cin.ignore(...); // ...siguiente operación de lectura } Importante: cuando un flujo de entrada está en estado ‘fail’ no se puede leer nada de él. 17 C++ BIBLIOTECA ENTRADA/SALIDA Funciones de conversión de C (<cstdlib> ó <stdlib.h>) de cadena a número long double float long double strtol(char* cad,char** endp, int base); strtod(char* cad,char** endp); strtof(char* cad,char** endp); strtold(char* cad,char**endp) cad: cadena a convertir endp: Si es !=NULL, se devuelve aquí un puntero al primer carácter no analizado base=0 : decimal, octal si empieza con ‘0’, hexadecimal si empieza con ‘0x’ 2 <= base <= 36 $ man strtol; man strtof 18 C++ BIBLIOTECA ENTRADA/SALIDA: ejemplo void TFecha::leer() { char *dAux=new char[3]; char *mAux=new char[3]; char *aAux=new char[5]; do{ cout<<"Introduce fecha formato dd/mm/aaaa (3)"<<endl; cin.clear(); cin.ignore(100,’\n’); cin.getline(dAux,3,'/'); //lee hasta / cin.getline(mAux,3,'/'); //lee hasta / cin.getline(aAux,5); //lee hasta newline if (cin) { // sin problemas de lectura… dia=(int)strtol(dAux,NULL,0); //string to double mes=(int)strtol(mAux,NULL,0); anyo=(int)strtol(aAux,NULL,0); } } while (!cin); } 19 C++ BIBLIOTECA ENTRADA/SALIDA Manipuladores Operaciones para modifcar el estado del flujo o formato de entrada/salida. Se insertan en los lugares apropiados en una operación de entrada/salida. Manipuladores sin argumentos: <iostream> Manipuladores con argumentos: <iomanip> 20 C++ BIBLIOTECA ENTRADA/SALIDA Algunos manipuladores sin argumentos Numéricos: dec, hex, oct, fixed, scientific (Para entrada y salida) int num=12; float num_real=0.0; cout << hex << num; // escribe ‘c’ cin >> hex >> num; //lee en formato hexadecimal cout << 12.3456789; // 12.3457 (precision 6) cout << fixed << 12.3456789; // 12.345679 cin >> scientific >> num_real; // 1.23456e+1 Tienen efecto a partir del momento en que se insertan en el flujo. cout.unsetf(ios_base::floatfield); // volver a formato float general 21 C++ BIBLIOTECA ENTRADA/SALIDA Algunos manipuladores sin argumentos: Sólo salida: flush (vacia buffer de entrada) cout << “sin salto de línea” << flush; endl (envía fin línea y vacía buffer de entrada) cout << “con salto de línea” << endl; 22 C++ BIBLIOTECA ENTRADA/SALIDA Manipuladores con argumentos (para salida): setfill(int c) : fija el carácter de relleno para el siguiente dato. setprecision(int n) : fija la conversión en coma flotante al número de dígitos especificado. (por defecto, n=6) setw(int n) : fija la anchura del siguiente dato en la salida. Justifica valores numéricos a la derecha y los datos tipo carácter a la izquierda. cout << setw(10) << setfill(‘*’) << 12.3456 << endl; // imprime ***12.3456 cout << setprecision(3) << 12.3456 << endl; // imprime 12.4 cout << fixed << 12.3456 << endl; // imprime 12.346 23 C++ ÍNDICE 1. 2. 3. 4. 5. 6. Funciones amigas Entrada / Salida Sobrecarga de funciones y operadores Gestión de memoria dinámica Atributos y métodos de clase Implementación de relaciones entre objetos 24 C++ SOBRECARGA DE FUNCIONES En C++ varias funciones pueden utilizar el mismo nombre (selector) en el mismo ámbito, distinguiéndose por el número y tipo de sus argumentos. Estas funciones se dice que están sobrecargadas. Sobrecarga de funciones miembro (en el ámbito de la clase) class Coordenada { public: ... Coordenada distancia(Coordenada &op2); Coordenada distancia(float &op2); }; Sobrecarga de funciones amigas (en el ámbito de la aplicación). friend Coordenada suma (int &b, Coordenada &a); friend Coordenada suma (float &b, Coordenada &a); En general, cualquier función se puede sobrecargar. 25 C++ SOBRECARGA DE OPERADORES En C++ se pueden sobrecargar los operadores del lenguaje para utilizarlos con cualquier tipo de dato, incluso clases definidas por el usuario. Los operadores son en realidad funciones cuyo nombre está formado por la palabra reservada operator seguida del operador a sobrecargar. int operator+(int,int); float operator+(float,int); 26 C++ SOBRECARGA DE OPERADORES Las expresiones 1), 2) y 3) son equivalentes: Coordenada a, b(5,3), c(10,10); 1) a=b+c; 2) a.operator=(b.operator+(c)); 3) operator+(a,operator+(b,c)); En 2) los métodos operator= y operator+ deben ser funciones miembro de la clase. El primer operando es un objeto de la clase (el segundo puede no serlo). En 3) los métodos no son miembros de la clase. Se debe respetar el significado original de los operadores para no confundir al usuario. 27 C++ SOBRECARGA DE OPERADORES. Asignación Sobrecarga del operador de asignación (=) TFecha& operator=(const TFecha& f) { if (this!=&f) // protección contra autoasignación { d=f.d; m=f.m; a=f.a; } return *this; } Es un ejemplo de sobrecarga de un operador binario que modifica al objeto (operando de la izquierda): Se almacena el resultado de la operación en el propio objeto Se devuelve referencia al objeto (esto permite concatenar operadores) TFecha a,b,c; a=b=c; // a.operator=(b.operator=(c)); 28 C++ SOBRECARGA DE OPERADORES. Operadores binarios Sobrecarga de un operador binario que no modifica a los operandos: El resultado se devuelve por valor Ejemplo: Sobrecarga de la suma (+) TFecha operator+(const TFecha& f) { TFecha suma(*this); suma.d+=f.d; suma.m+=f.m; suma.a+=f.a; return suma; } TFecha a,b,c; a+b+c; // a.operator+(b).operator+(c); 29 C++ SOBRECARGA DE OPERADORES. Entrada/salida Sobrecarga de los operadores de entrada/salida. cout << x; cin >> x; No pueden ser funciones miembro de la clase de ‘x’ porque el primer operando no es un objeto de esa clase (es un ‘stream’). Se sobrecargan como funciones amigas: friend ostream& operator<< (ostream &o, const TFecha& obj); friend istream& operator>> (istream &o, TFecha& obj); TFecha a,b; cout << a << b; // operator<<(operator<<(cout,a),b); cin >> a >> b; // operator>>(operator>>(cin,a),b); 30 C++ SOBRECARGA DE OPERADORES. Entrada/salida class TFecha { friend ostream& operator<< (ostream &os, const TFecha& obj); friend istream& operator>> (istream &is, TFecha& obj); public: TFecha (int d=1, int m=1, int a=1900); ... private: int dia, mes, anyo; }; 31 C++ SOBRECARGA DE OPERADORES. Entrada/salida ostream& operator<< (ostream &os, const TFecha& obj) { os << obj.dia << ”/” << obj.mes << ”/” << obj.anyo; //formato D/M/A return os; } Implementación: Salida con formato del estado del objeto. Devolver siempre referencia al primer argumento 32 C++ SOBRECARGA DE OPERADORES. Entrada/salida istream& operator>> (istream &is, TFecha& obj) { char *dAux=new char[3]; char *mAux=new char[3]; char *aAux=new char[5]; is.getline(dAux,3,'/'); //lee hasta / is.getline(mAux,3,'/'); //lee hasta / is.getline(aAux,5); //lee hasta newline if (is) { // sin errores obj.dia=(int)strtol(dAux,NULL,0); obj.mes=(int)strtol(mAux,NULL,0); obj.anyo=(int)strtol(aAux,NULL,0); } return is; } Implementación: Lectura con formato de todos los atributos. Leer de la entrada una sola vez (no hacer bucles). Devolver siempre referencia al primer argumento. 33 C++ ÍNDICE 1. 2. 3. 4. 5. 6. Funciones amigas Entrada / Salida Sobrecarga de funciones y operadores Atributos y métodos de clase Gestión de memoria dinámica Implementación de relaciones entre objetos 38 Atributos y métodos de clase También llamados estáticos. Se representan subrayados en UML. Los atributos de clase son comunes a todos los objetos de la clase. Sólo existe una copia en memoria compartida por todos los objetos. Los métodos de clase sólo pueden acceder a atributos de clase 39 Atributos y métodos de clase class TFecha { public: static const int semanasPorAño = 52; static const int diasPorSemana = 7; static const int diasPorAnyo = 365; static string getFormato(); static boolean setFormato(string); private: static string cadenaFormato; }; 40 Atributos y métodos de clase: Definición y acceso //TFecha.cc string TFecha::cadenaFormato = “DD/MM/AAAA”; // Definición string TFecha::getFormato() { return cadenaFormato; } boolean TFecha::setFormato(string f) { if (formatoValido(f)) { cadenaFormato=f; return true; } else return false; } // main.cc int main() { TFecha f; cout << TFecha::semanasPorAnyo << “ “ << f.diasPorSemana << endl; cout << TFecha::getFormato() << f.getFormato() << endl; } 41 C++ ÍNDICE 1. 2. 3. 4. 5. 6. Funciones amigas Entrada / Salida Sobrecarga de funciones y operadores Atributos y métodos de clase Gestión de memoria dinámica Implementación de relaciones entre objetos 42 C++ GESTIÓN DE MEMORIA DINÁMICA (recordatorio) Operadores new y delete new delete Dato *pDato = new Dato; Dato *pArray = new Dato [nElem] ; Comprobación error: if (pDato==NULL)... Ventaja frente a array convencional: permite decidir el número de elementos en tiempo de ejecución. delete pDato; pDato=NULL; delete [] pArray; pArray=NULL; IMPORTANTE: No olvidar los corchetes en delete si los hemos usado en el new correspondiente 43 Estructura de una Clase C++ GESTIÓN DE MEMORIA DINÁMICA: creación Suponed que deseamos simular un tablero de coordenadas. int main(){ int i,j,cfila,nfilas; cout<<"Introduce num coord por fila"<<endl; cin>>cfila; cout<<"Introduce num filas"<<endl; cin>>nfilas; TCoordenada **c=new TCoordenada*[nfilas]; //puntero a un array de nfilas punteros a Coordenada for (i=0;i<nfilas;i++) c[i]=new TCoordenada[cfila]; //aquí creamos array de coordenadas ... 45 Estructura de una Clase C++ GESTIÓN DE MEMORIA DINÁMICA: trabajo ... for (int i=0; i<nfilas;i++){ for (j=0;j<cfila;j++){ c[i][j].setCoordX(i); c[i][j].setCoordY(j); } } for (int i=0; i<nfilas;i++){ cout<<endl; for (j=0;j<cfila;j++){ cout<<c[i][j]; } } ... 46 Estructura de una Clase C++ GESTIÓN DE MEMORIA DINÁMICA: destrucción ... for (i=0; i<nfilas; i++){ delete [] c[i]; //borramos filas c[i]=NULL;//Ojo!!! } delete [] c; //borramos array ptros c=NULL;//Ojo!!! } 47 C++ ÍNDICE 1. 2. 3. 4. 5. 6. Funciones amigas Entrada / Salida Sobrecarga de funciones y operadores Gestión de memoria dinámica Atributos y métodos de clase Implementación de relaciones entre objetos 48 Implementación de relaciones Composición Composición: Un objeto A tiene (contiene, esta formado por) objetos B A 1 -b B class A { private: B b; …}; A 10 -b B A 0..10 -b B class A { class A { private: private: B b[10]; B *b[10]; …}; int num_b; // o B *b[10]; …}; // si B tiene // clases derivadas A 0..* -b B class A { private: B **b; int num_b; …}; 50 Implementación de relaciones Composición A::A(): numb(0) { A A::~A() { … … for (int i=0; i<10; i++) for (int i=0; i<numb; i++) { b[i]=NULL; 0..10 -b … } B class A { private: B *b[10]; int num_b; …}; Inicialmente A no contiene ningún B (opcional) delete b[i]; b[i]=NULL;} numb=0; …} Los componentes B desaparecen con A A::addB(B& unB) { A::A(const A& otroA) { … if (numb<10) …// ‘deep copy’ for (int i=0; i<num_b; i++) b[numb++] = new B(unB); b[i] = new B(otroA.b[i]); …} El objeto de tipo A tiene su propia copia de componentes B for (int i=num_b; i<MAX_B; i++) b[i]=NULL; …} 51 Implementación de relaciones Composición A::A(): numb(0) { A * … … for (int i=0; i<numb; i++) B = new B*[MAX_B]; { delete b[i]; b[i]=NULL; } for (int i=0; i<MAX_B; i++) numb=0; delete b; b=NULL;… b[i]=NULL; -b B A::~A() { … } } A::A(const A& otroA) { …// ‘deep’ copy’ class A { private: B **b; int num_b; …}; #define MAX_B … A::addB(B& unB) { b = new B*[MAX_B]; … if (numb<MAX_B) for (int i=0; i<num_b; i++) b[i] = new B(otroA.b[i]); b[numb++] = new B(unB); for (int i=num_b; i<MAX_B; i++) …} b[i]=NULL; …} 52 SEMINARIO C++ Introducción a la Programación Orientada a Objetos FIN parte III