SEMINARIO C++ Introducción a la Programación Orientada a Objetos Parte 3 v. 20101014 Pedro J. Ponce de León Depto. Lenguajes y Sistemas Informáticos - Universidad de Alicante C++ ÍNDICE 1. 2. 3. 4. 5. 6. 7. 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 Pruebas unitarias 2 C++ FUNCIONES AMIGAS 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! } … int main() { MiClase objeto; unaFuncionAmiga(10,objeto); } Conceptualmente, las funciones amigas forman parte de la interfaz de una clase (tanto como las funciones miembro). PROHIBIDO SU USO EN POO: rompen el principio de encapsulación. 4 C++ ÍNDICE 1. 2. 3. 4. 5. 6. 7. 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 Pruebas unitarias 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 el siguiente carácter en cin descarta ‘ncar’ caracteres en cin cin.ignore(int ncar, char delim) 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: ejemplo Entrada de datos numéricos con formato void Fecha::leer() { do{ cout<<"Introduce fecha (formato dd/mm/aaaa)” <<endl; if (!cin) { cin.clear(); cin.ignore(100,'\n'); } cin >> dia; cin.ignore(); cin >> mes; cin.ignore(); cin >> anyo; } while (!cin); } 19 C++ ÍNDICE 1. 2. 3. 4. 5. 6. 7. 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 Pruebas unitarias 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); }; 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 (=) Fecha& operator=(const Fecha& 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) Fecha a,b,c; a=b=c; // a.operator=(b.operator=(c)); 28 C++ ÍNDICE 1. 2. 3. 4. 5. 6. 7. 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 Pruebas unitarias 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 directamente a atributos de clase 39 Atributos y métodos de clase class Fecha { 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 //Fecha.cc // Definición de un atributo de clase no constante string Fecha::cadenaFormato = “DD/MM/AAAA”; // Definición de metodo estático string Fecha::getFormato() { return cadenaFormato; } // main.cc (Acceso) int main() { Fecha f; cout << Fecha::semanasPorAnyo << “ “ << f.diasPorSemana << endl; cout << Fecha::getFormato() << “ “ << f.getFormato() << endl; } 41 C++ ÍNDICE 1. 2. 3. 4. 5. 6. 7. 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 Pruebas unitarias 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 C++ GESTIÓN DE MEMORIA DINÁMICA Array de objetos Hay que guardar memoria para dos conceptos El array en sí mismo Cada uno de los objetos que componen el array Con variables automáticas, C++ permite hacer ambas cosas en una sola línea: Naipe arrayDeCartas[52]; //invoca a ctor. por defecto de Naipe para cada componente Con memoria dinámica: Naipe *arrayDeCartas[52]; arrayDeCartas = new *Naipe[52]; // Array de punteros for (int i=0; i<52; i++) arrayDeCartas[i] = new Naipe(…); // permite invocar a ctores. sobrecargados 44 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 49 Implementación de relaciones Asociación/Agregación -> mediante punteros Persona - string nombre - string dni … + bool anyadeAsig(Asignatura &a) 0..10 - amatr Asignatura - string nombre - string créditos … + bool asignaAlumno(Persona &p) class Asignatura {...}; class Persona { private: string nombre; string dni; vector<Asignatura*> amatr; public: Persona() : amatr() {} ~Persona() { amatr.clear(); // no destruye los objetos Asignatura } void setNombre(string n); string getNombre(); bool anyadeAsig(Asig &a) { … amatr.push_back(&a); … } }; 50 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: static const int MAXB=10; static const int vector<B*> b; MAXB=10; public: vector<B*> b; A() { …}; for (int i=0; i<10; i++) b.push_back(new B());…} …}; A 0..* -b B class A { private: vector<B*> b; …}; 52 Implementación de relaciones Composición A::A(): b() { … } A 0..10 -b B Inicialmente A no contiene ningún B A::addB(B& unB) { … if (b.size()<MAXB) A::~A() { … for (int i=0; i<b.size(); i++) { delete b[i]; b[i]=NULL; } b.clear(); …} Los componentes B desaparecen con A b.push_back(new B(unB)); …} class A { private: static const int MAXB=10; vector<B*> b; …}; A::A(const A& otroA) El objeto de tipo A tiene su propia copia de componentes B : b(otroA.b.size()) { …// ‘deep copy’ for (int i=0; i<b.size(); i++) b[i] = new B(*(otroA.b[i])); …} 53 Implementación de relaciones Composición A::A(): b() { … } A::~A() { … A * for (int i=0; i<b.size(); i++) -b B A::addB(const B& unB) { { … b.push_back(new B(unB)); b.clear(); …} delete b[i]; b[i]=NULL; } …} A::A(const A& otroA) { class A { private: vector<B*> b; …}; …// ‘deep’ copy’ for (int i=0; i<otroA.b.size(); i++) b.push_back(new B(*(otroA.b[i]))); … 54 C++ ÍNDICE 1. 2. 3. 4. 5. 6. 7. 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 Pruebas unitarias 55 C++ PRUEBAS UNITARIAS • La interfaz de una clase debe ser probada de manera sistemática. • Una prueba unitaria comprueba que un método determinado, ante una llamada determinada, se comporta como se espera. • Se realiza un caso de prueba unitaria para cada función no trivial o método de forma que cada caso sea independiente del resto. • Las pruebas se realizan mediante aserciones 56 C++ PRUEBAS UNITARIAS Herramientas de prueba Son unas colecciones de clases con las que se pueden desarrollar casos de prueba fácilmente. Para c++ existen multitud de herramientas: • CPPUnit. • Boost.Test. • CPPUnitLite. • NanoCPPUnit. • Unit++. • CxxTest <-- Usaremos ésta 58 C++ PRUEBAS UNITARIAS: CXXTEST Herramienta especifica de C/C++. Se considera una de las herramientas más sencillas y potentes en comparación con el resto. Es software libre (GNU Lesser Public License) Sólo necesita un compilador de c++ ligeramente moderno y actualizado y soporte para alguno de los dos lenguajes en los que se basa: Python o Perl. 59 C++ PRUEBAS UNITARIAS: CXXTEST Instalación 1. Obtener CXXTEST: http://cxxtest.sourceforge.net 2. Desempaquetar en un subdirectorio ‘test’ (normalmente en el directorio de trabajo) (para las prácticas de POO, CXXTEST vendrá preinstalado en los autocorrectores). 60 C++ PRUEBAS UNITARIAS: CXXTEST Uso Las pruebas se organizan en clases escritas en ficheros .h; Cada método cuyo nombre comience por ‘test’ es ejecutado por cxxtest. Por ejemplo: // MyTestSuite.h #include <cxxtest/TestSuite.h> class MyTestSuite : public CxxTest::TestSuite { public: void testAddition( void ) { TS_ASSERT( 1 + 1 > 1 ); TS_ASSERT_EQUALS( 2 * 2, 5 ); } }; 61 C++ PRUEBAS UNITARIAS: CXXTEST Ejecución de las pruebas: $ cxxtestgen.pl --error-printer -o runner.cpp MyTestSuite.h $ g++ -o runner runner.cpp $ ./runner Resultado: Running 2 tests. MyTestSuite.h:15: Expected (2 * 2 == 5), found (4 != 5) Failed 1 of 2 tests Success rate: 50% 62 C++ PRUEBAS UNITARIAS: CXXTEST Especificación de casos de prueba mediante aserciones TS_ASSERT Es la prueba básica. Comprueba que una expresión es cierta. void testCalculadora() { TS_ASSERT( suma( 0, 0 ) == 0 ); } 63 C++ PRUEBAS UNITARIAS: CXXTEST Especificación de casos de prueba mediante aserciones TS_ASSERT_EQUALS Verifica la igualdad entre dos expresiones. Equivalente al operador == en TS_ASSERT. void testResta() { TS_ASSERT_EQUALS(resta(4,2),2); } 64 C++ PRUEBAS UNITARIAS: CXXTEST Especificación de casos de prueba mediante aserciones TS_ASSERT_DELTA Se usa para comparar si dos valores son iguales hasta delta. Básicamente se usa para números en coma flotante. void testSqrt( void ) { TS_ASSERT_DELTA(sqrt(4.0), 2.0, 0.00001); } 65 C++ PRUEBAS UNITARIAS: CXXTEST Especificación de casos de prueba mediante aserciones TS_ASSERT_DIFFERS Es lo contrario a TS_ASSERT_EQUALS. Se usa para comprobar que dos valores son distintos. Es equivalente a usar != en TS_ASSERT. void testNumeros ( void ) { TS_ASSERT_DIFFERS(5,7); } 66 C++ PRUEBAS UNITARIAS: CXXTEST Especificación de casos de prueba mediante aserciones TS_WARN Muestra una lista de mensajes de cosas por hacer del tipo “to do”. void testToDoLista( void ) { TS_WARN( "TODO: Escribir todos ); los test!" 67 C++ PRUEBAS UNITARIAS: CXXTEST Especificación de casos de prueba mediante aserciones TS_FAIL Muestra un fallo incondicional. void testFallo() { TS_FAIL( "No se puede testear." ); } 68 C++ PRUEBAS UNITARIAS: CXXTEST Dada una clase a evaluar, basta incluir su .h en el fichero de pruebas. Por ej., dada la clase Coordenada de la práctica 0: #ifndef PUNTO_TESTSUITE_H_ #define PUNTO_TESTSUITE_H_ #include <cxxtest/TestSuite.h> #include "../include/Punto.h" class PuntoTestSuite : public CxxTest::TestSuite { Punto* c00; Punto* c11; public: void setUp() { // Código que se ejecuta antes de cada prueba c00 = new Punto; c11 = new Punto; c11->setX(1.0); c11->setY(1.0); } void tearDown() { // Código que se ejecuta después de cada prueba delete c00; c00=NULL; delete c11; c11=NULL; } ... 69 C++ PRUEBAS UNITARIAS: CXXTEST // (Continuación) void testFormaCanonica() { // Prueba unitaria: método cuyo nombre empieza por ‘test’ TS_ASSERT_EQUALS(c11->getX(), 1); TS_ASSERT_EQUALS(c11->getY(), 1); // Test constructor de copia const Punto c(*c11); TS_ASSERT_EQUALS(c.getX(),1.0); TS_ASSERT_EQUALS(c.getY(),1.0); // Test operator= Punto c7; c7.setX(2); c7.setY(3); c7=c7; TS_ASSERT_EQUALS(c7.getX(),2.0); TS_ASSERT_EQUALS(c7.getY(),3.0); c7=*c11; TS_ASSERT_EQUALS(c7.getX(),1.0); TS_ASSERT_EQUALS(c7.getY(),1.0); } }; 70 SEMINARIO C++ Introducción a la Programación Orientada a Objetos FIN parte III