1 Implementación de clases La traducción a C++ de una clase sigue los mismos pasos que la implementación de un módulo de datos, es decir, hay que obtener una representación de la misma a partir de las clases ya existentes y, a continuación, codificar sus operaciones en C++. Recordemos que para garantizar que la clase sea independiente de su implementación, el acceso a los atributos de sus objetos ha de estar prohibido, salvo si se realiza mediante las operaciones de modificación y consulta (la mayoría de lenguajes proporciona mecanismos para eludir esta prohibición; en otros contextos su uso puede ser ventajoso pero aquí no lo permitiremos). Como ya hemos mencionado, necesitamos dos ficheros: el .hpp, que ha de contener al menos las cabeceras de las operaciones y cierta información sobre la representación de la clase, y el .cpp, que ha de incluir al menos el código de las operaciones. En la implementación ya conocida de la clase Estudiante, el fichero Estudiante2.hpp apenas hace mención de los campos de la clase y casi toda la información relevante está oculta en el fichero Estudiante2.cpp. Esta técnica requiere conocimientos de C++ que sobrepasan los objetivos de esta asignatura, pero es interesante haber visto un ejemplo de ella. En vuestras clases no ocultaréis los campos pero al menos los declararéis privados en los ficheros .hpp, como veremos a continuación. En el ejemplo de la clase Estudiante, comenzamos por el fichero Estudiante.hpp. Las cabeceras de las operaciones son las mismas que en la versión anterior y se declaran igualmente públicas. En cuanto a los campos, recordemos que había un campo entero para el DNI, otro real para la nota y otro booleano para saber si la nota está definida. Todo ellos son declarados private para obligar a que el acceso a los mismos desde fuera de la clase se realice mediante las operaciones correspondientes. Para limitar el conjunto de notas válidas, introducimos una constante MAX_NOTA, que además será declarada static (ya que si no, cada objeto tendría su propia constante como atributo). Notad que no se ha de usar el equivalente en C++ al constructor de tipos tupla, es decir, struct. Si necesitamos tuplas auxiliares (es decir, una tupla que forma parte de otra tupla), entonces sí que emplearemos un struct. Es interesante notar que las asignaciones entre miembros de una clase en realidad se descomponen simplemente en asignaciones campo a campo. En este caso, como los campos son de tipos simples, las asignaciones de los estudiantes implementados no generan aliasing. Por lo tanto, ya no necesitamos introducir el operador correspondiente, #ifndef ESTUDIANTE_HPP #define ESTUDIANTE_HPP class Estudiante { private: int DNI; double nota; bool tieneNota; 1 static const int MAX_NOTA = 10; public: /* Constructoras */ Estudiante(); /* Destructora por defecto */ ~Estudiante(); /* Consultoras de los campos */ bool tiene_nota() const; double consultar_nota() const; int consultar_DNI() const; /* Modificadoras de los campos */ void crear_estudiante(int dni); void anadir_nota(double nota); void modificar_nota(double nota); /* Entrada / Salida */ void leer_estudiante(); void escribir_estudiante() const; }; #endif Pasemos al fichero Estudiante.cpp, donde codificamos los métodos. Notad que en todas las cabeceras hay que insertar la declaración Estudiante:: delante del nombre de cada operación. Así queda establecido que se trata de la misma operación que consta en el fichero Estudiante.hpp. En otros ejemplos más complejos, veremos que las operaciones que no incluyan la mencionada declaración se consideran automáticamente como operaciones privadas. En las instrucciones de un método será necesario referirse al parámetro implícito: para ello C++ reserva la palabra this. Hay que notar que ésta no es siempre obligatoria: si mencionamos el nombre de un campo de la clase, C++ interpreta que nos referimos al correspondiente campo del parámetro implícito. Sin embargo, en casos como los de las operaciones añadir_nota y modificar_nota en los que existe la posibilidad de confusión entre el campo nota y el parámetro del mismo nombre, el uso del this es imprescindible. También lo es cuando se hace referencia al parámetro implícito en su conjunto, por ejemplo, si se necesita pasarlo como parámetro no implícito de alguna operación. Por último, veréis que algunos métodos lanzan excepciones para controlar situaciones no previstas en la precondición, cada una con un texto explicativo (ER1,...,ER4, definidos en el .cpp). En general, no pediremos que lo hagáis así en vuestros programas, sino que bastará con comprobar que una precondición se cumple antes de usar la correspondiente operación. 2 Comenzamos con la creadora que genera un estudiante vacío y la destructora por defecto. Las demás posibilidades quedan como ejercicio. Estudiante::Estudiante(){} Estudiante::~Estudiante(){} Entre las modificadoras tenemos las operaciones crear_estudiante y las dos operaciones que se encargan de la nota. Se verán otras opciones en los ejercicios. void Estudiante::crear_estudiante(int dni) { if (dni<0) throw PRAPExcepcio(ER4); DNI = dni; tieneNota = false; } void Estudiante::anadir_nota(double nota) { if (tieneNota) throw PRAPExcepcio(ER3); if (nota < 0 or nota > MAX_NOTA) throw PRAPExcepcio(ER2); this->nota = nota; tieneNota = true; } void Estudiante::modificar_nota(double nota) { if (not tieneNota) throw PRAPExcepcio(ER1); if (nota < 0 or nota > MAX_NOTA) throw PRAPExcepcio(ER2); this->nota = nota; } Pasemos a las operaciones consultoras. En este ejemplo, sólo tenemos las consultas a los valores de los campos, pero en otras situaciones podríamos necesitar cálculos más complicados. bool Estudiante::tiene_nota() const { return tieneNota; } 3 double Estudiante::consultar_nota() const { if (!tieneNota) throw PRAPExcepcio(ER1); return nota; } int Estudiante::consultar_DNI() const { return DNI; } Por último, las operaciones de lectura y escritura: void Estudiante::leer_estudiante() { crear_estudiante(readint()); double x = readdouble(); if (x>=0 && x<=10) anadir_nota(x); } void Estudiante::escribir_estudiante() const { if (tieneNota) cout << DNI << " " << nota << endl; else cout << DNI <<" NP" << endl; } 4