Descarga la aplicación para disfrutar aún más
Esta es una vista previa del archivo. Inicie sesión para ver el archivo original
informatica industrial/practicas/P2 - Clases I.pdf Práctica de Informática Industrial I Grado en Ingeniería en Electrónica Industrial y Automática 2015‐2016 Práctica 2 Clases I. Definición, Constructores, Destructores, … Profesores: Mohamed Abderrahim Álvaro Castro González Ioannis Douratsos José Carlos Castillo Javier Fernandez de Gorostiza Juan Carlos González Victores Informática Industrial 2015/2016 2/11 Práctica 2 Práctica 2 - Clases 1. Clases 1.1. Definición Lo primero que se va a ver es como se estructura un fichero main.cpp en C++. //////////////////////////////////////////////////// // main.cpp //////////////////////////////////////////////////// #include <iostream> #include “point.h” using namespace std; using namespace space; int main(int argc, char **argv) { Point P; cout << P.getX() << endl; cout << P.getY() << endl; P.set(10, 10); cout << P.getX() << endl; cout << P.getY() << endl; P.display(); } En este programa, lo primero que se hace es incluir la biblioteca estándar de C++ y el fichero de cabecera point.h, que contendrá la declaración de la clase que se va a crear posteriormente. Nótese que la biblioteca estándar no se incluye de igual manera que en C, por ejemplo, en el que sería iostream.h. En C++, la mayoría de los ficheros de inclusión estándar, acaban sin .h y a veces comienzan con la letra “c”. Algunos ejemplos de ficheros de C que pasan a C++ son: #include <stdlib.h> → #include <cstdlib> #include <assert.h> → #include <cassert> Después, se pueden encontrar las directivas: using namespace std; using namespace space; Informática Industrial 2012/2013 3/11 Práctica 2 Estas indican al compilador que se van a usar los espacios de nombres std (estándar) y space (que se crea por el usuario más adelante). En C++, las clases se pueden encapsular en espacios de nombres. De esta forma se organizan mejor para proyectos grandes. El espacio de nombres std define el espacio de nombres estándar de C++. Si no se indica explícitamente que se va a usar, el compilador dará un error al tratar de usar por ejemplo cout, ya que esta es una clase de este espacio de nombres. En caso de no especificar el espacio de nombre y para que no de error al utilizar alguna de estas funciones, se puede llamarlo de forma explícita utilizando std::cout. En la función main, observar la siguiente declaración: Point P; Esto está indicando al compilador que debe crear una variable de tipo Point. Es decir, que se va a crear una variable de la clase Point, o también se dice que se ha creado un objeto de la clase Point. Como ya se ha visto en teoría, se llama a los métodos de una clase usando el operador “.” (punto). Por eso, la expresión P.getX() es una llamada a la función miembro getX() del objeto P. Si se considera el código siguiente: Point P, P2; cout << P.getX() << endl; cout << P2.getX() << endl; Se estarían creando dos objetos distintos, ambos de la clase Point. Y P.getX() sería una llamada a la función miembro del objeto, que sería distinto a llamar a p2.getX(). La diferencia, como se verá más adelante, es que P.getX() usará los valores del objeto P, y P2.getX() usara los valores de P2. En este momento ya se puede ver cómo se va a definir una clase. Una clase se define en dos ficheros, un fichero .h y un fichero .cpp. El fichero .h contiene la definición o declaración de la clase, mientras que el .cpp contiene la implementación de la clase. Las clases se pueden definir dentro de un espacio de nombres “namespace”. Los espacios de nombres comienzan por letra minúscula (por convenio). El nombre de las clases comienza con mayúscula (por convenio). La clase Point representará un punto en el espacio. Esta clase permitirá manejar un punto mediante las funciones miembro o métodos de la clase. Las clases se definen usando la palabra clave class. Seguidamente se define la parte pública, es decir, aquella que podrá ser usada por otras clases o funciones. Más adelante se define la privada, donde se colocan las variables o funciones que solo podrán accederse desde funciones miembro de la misma clase. A continuación se muestra el fichero point.h. Informática Industrial 2012/2013 4/11 Práctica 2 //////////////////////////////////////////////////// // point.h //////////////////////////////////////////////////// #ifndef _POINT_H_ #define _POINT_H_ #include <iostream> using namespace std; namespace space { /**\brief This class represents a point */ class Point { public: int getX(); int getY(); void set(int x, int y); void display(); private: int _x, _y; }; } #endif Dentro de la clase se han definido dos variables privadas y cuatro métodos públicos. El primer y segundo método sirven para obtener los valores de las coordenadas del punto, el segundo método sirve para especificar los valores del punto, y el tercer método sirve para ver los valores de las coordenadas del punto. Finalmente, en la parte privada, se han declarado dos enteros que representan los valores del punto de la clase. Las variables privadas se declaran comenzando por subrayado “_” (por convenio). Para ocultar los detalles de implementación de una clase y así permitir cambiar su implementación sin tener que cambiar el resto del código que la utiliza, C++ introduce los especificadores de acceso. Estos permiten definir qué miembros de una clase son accesibles desde fuera de ésta y cuáles no. Hay tres especificadores de acceso: public, private y protected. El primero, public, significa que el miembro es accesible desde fuera de la definición de la clase, mientras que los miembros private y protected sólo se pueden acceder desde dentro de las funciones de la clase o las clases habilitadas para ello. Por defecto, todos los miembros de una clase son privados. Si se intenta acceder a un atributo o método privado, el compilador dará un error y no se podrá crear el ejecutable. En el siguiente código se puede probar como fallaría la compilación del main.cpp. Informática Industrial 2012/2013 5/11 Práctica 2 int main() { Point P; P._x = 10; // imposible! ERROR! P.display(); } Otro aspecto muy importante de la programación es la documentación. Se deben documentar todos los ficheros fuente que se realicen. Los comentarios tendrán formato Doxygen como el que se muestra a continuación: /** */ Para este formato de documentación existen herramientas que nos crearán una documentación HTML del código y que se podrá consultar en cualquier navegador. Ahora, se va a crear el .cpp. //////////////////////////////////////////////////// // point.cpp //////////////////////////////////////////////////// #include "point.h" namespace space { //////////////////////////////////// // //////////////////////////////////// int Point::getX() { return _x; } //////////////////////////////////// // //////////////////////////////////// int Point::getY() { return _y; } //////////////////////////////////// // //////////////////////////////////// void Point::set(int x, int y) { _x = x; _y = y; } //////////////////////////////////// // Informática Industrial 2012/2013 6/11 Práctica 2 //////////////////////////////////// void Point::display() { cout << _x << “, “ << _y << endl; } } Finalmente, compilar haciendo uso de QtCreator y ejecutar el programa. 1.2. Constructores y destructores 1.2.1. Constructores Probablemente, ya se habrán dado cuenta que cuando se instancia un objeto de una clase, los valores de los atributos de la clase tienen valor inicial “basura”. Sin embargo, es posible alterar este comportamiento de la clase añadiendo constructores. El constructor es una función que se invoca al crear un objeto de la clase, pero para ello ha de estar definida. Constructor vacío Crear primero el constructor vacio. Añadir el siguiente código a la definición de la clase justo debajo de la palabra clave “public”. /** Empty constructor */ Point(); Esto es un constructor, que es una función miembro que no devuelve nada y que se llama igual que la clase. Fíjese muy bien que no solo no devuelve nada, sino que tan siquiera dice void. Al ser una función miembro puede tener parámetros, pero en este caso se encuentra vacío de parámetros. Los constructores siempre son las primeras funciones de la clase y deben ser públicos. Ahora ver la implementación en el .cpp. /////////////////////////// // ///////////////////////// Point::Point() { _x = 0; _y = 0; } Lo que se hace es inicializar las variables de la clase a valor 0, por ejemplo, para que no sea “basura” al crearse el objeto. Finalmente, volver a compilar y ejecutar. Como se observa, la salida ahora es 0. Sobrecarga de constructores En C++ es posible crear varias funciones miembro con el mismo nombre aunque con diferentes argumentos, a esto se le llama sobrecarga. Será el compilador el que se encargue de decidir a qué Informática Industrial 2012/2013 7/11 Práctica 2 función se debe llamar dependiendo de los argumentos que se usen. Se va a probar la sobrecarga añadiendo dos constructores típicos que son: el constructor parametrizado y el constructor de copia. El primero servirá para asignar un valor a las variables en la creación de la clase y el segundo servirá para crear un objeto que sea copia de otro. Añadir el siguiente código al .h. /** Parametrized constructor */ Point(int x, int y); /** Copy constructor */ Point(const Point & P); Y ahora habrá que completar el .cpp. /////////////////////////// // ///////////////////////// Point::Point(int x, int y) { _x = x; _y = y; } /////////////////////////// // ///////////////////////// Point::Point(const Point & P) { _x = P._x; _y = P._y; } Obsérvese que el primer constructor recibe como argumento valores enteros y los asigna a las variables. El segundo requiere un poco mas de explicación. Se denomina constructor de copia y lo que hace es que hace que el objeto creado sea una copia del que se pasa. El argumento (único) es una referencia a un objeto de la misma clase. const Point & P La palabra clave const sirve para indicar que el elemento pasado no debe ser modificado en el interior de la función. Después se observa el símbolo &. En C hay dos formas básicas de pasar argumentos, por valor y por referencia. En el primer caso, se pasaba un elemento y dentro de la función se hacia una copia con la que se trabajaba. Por referencia (puntero), se pasaba la dirección de memoria y se podía modificar el valor directamente. Cuando se quiere pasar como parámetro a una clase en C++, se tiene la misma idea, paso por Informática Industrial 2012/2013 8/11 Práctica 2 valor o por referencia. Sin embargo, el paso por valor no es recomendable cuando se pasa a una clase. Esto es porque las clases pueden contener una gran cantidad de variables y el realizar una copia podría llegar a ser muy costoso e ineficiente. Por tanto, es preferible hacer el paso de un objeto siempre por referencia. Para ello, en C++ se define el operador &. Cuando se pasa &, se está indicando que se puede modificar el valor de la variable dentro de la función. Las referencias se verán con detalle en la siguiente práctica. Ahora se puede ver un ejemplo simple. main() { int v = 10; modify(v); cout << v << endl; } void modify(int & v) { v = 0; } En el caso del constructor de copia, se está pasando por referencia el objeto (para evitar una copia de todos sus datos), pero a la vez se le está diciendo que se haga constante. Es decir, que no sea posible modificar sus valores en el interior. Por tanto, el siguiente código sería incorrecto: /////////////////////////// // ///////////////////////// Point::Point(const Point & P) { P._x = 100; P._y = 101; } Ya que se está modificando las variables internas del objeto pasado P, que se supone que es constante dentro de la función. 1.2.2. Destructores El destructor es la función a la que se llama cuando se debe destruir el objeto. Es decir, la función a la que se llama cuando ya no se va a usar el objeto nunca más. Si se define en el .h. /** Destructor */ ~Point(); Ahora se tiene algo nuevo ~Point(). Esto será el destructor de la clase. El objeto tendrá que liberar la memoria en caso de que se hubiese creado. Lo bueno de los destructores es que es una función a la que llama el compilador automáticamente cuando detecta que el objeto no se va a usar más. No se tiene que llamar al destructor, el compilador lo hace por el usuario. En el caso del ejemplo de la clase Point, el destructor no hará nada de momento pero cuando se vea la reserva Informática Industrial 2012/2013 9/11 Práctica 2 de memoria dinámica en la siguiente práctica se verá que será muy útil. El código que iría en el .cpp sería: /////////////////////////// // ///////////////////////// Point::~Point() { } 1.2.3. Operadores Otra de las ventajas de las clases es que se pueden definir operadores sobre las clases (=, +, -, *, /). Se va a definir el operador de asignación. Añadir en la parte publica de la clase Point la siguiente definición: /** Assign operator */ Point & operator=(const Point & P); Y en el .cpp: /////////////////////////// // ///////////////////////// Point & Point::operator=(const Point & P) { _x = P._x; _y = P._y; return *this; } Los operadores son funciones miembro que permiten definir operaciones habituales. Todos los operadores devuelven una copia del objeto actual con el objetivo de poder realizar un encadenamiento. El valor de retorno es Point &, es decir, una referencia a un objeto de la clase. Si se observa la implementación, una vez se realiza la copia, se retorna *this. La palabra reservada this, es puntero al objeto actual. El puntero this, usado dentro de una función de la clase Point, es un puntero al objeto, por tanto en este caso es un puntero de tipo Point *. Si this es un Point *, entonces *this es el propio objeto, es decir Point. Entonces, lo que se está devolviendo es el propio objeto. Ver un ejemplo: Informática Industrial 2012/2013 10/11 Práctica 2 int main(int argc, char **argv) { Point P(10, 10); // parametrized constructor Point P2; cout << P2.getX() << endl; cout << P2.getY() << endl; P2 = P; P2.display(); } En este ejemplo, P es asignado a P2. Por tanto, P2 será una copia de P. Se puede ver ahora entonces: int main(int argc, char **argv) { Point P(10); Point P2, P3; P2.display(); P3 = P2 = P; P3.display(); } En este ejemplo, P es asignado a P2, que a su vez es asignado a P3. Al escribir P3 = P2 = P, el compilador primero realizará P2 = P. El resultado de esta operación es P2, que será asignado a P3. Es decir, el compilador descompondrá las operaciones en: P2 = P; P3 = P2; Esto es posible gracias a que el resultado de la asignación es un Point &. Informática Industrial 2012/2013 11/11 Práctica 2 Ejercicios Implementar las clases necesarias para realizar una base de datos de personas. Para ello, se debe definir: o Una clase Person en un fichero Person.h, y la implementación de sus funciones miembro en un fichero Person.cpp. Deberá contener dos atributos privados, _name, de tipo string y _age de tipo entero. Deberá contener las siguiente funciones miembro públicas: Constructor vacío, parametrizado y de copia. Realizarán las inicializaciones pertinentes. Destructor de la clase. Métodos getName y setName: obtener y poner el nombre de la persona. Métodos getAge y setAge: obtener y poner la edad de la persona. Operador de asignación. Realizar un fichero main.cpp en el que la función main deberá realizar lo siguiente: o Instanciar un objeto de tipo Person mediante su constructor parametrizado y que se muestren por pantalla los datos. o Instanciar otro objeto de tipo Person mediante su constructor de copia, teniendo como parámetro el objeto anterior y que se muestren los datos de ambos objetos por pantalla. o Instanciar un último objeto de tipo Person mediante su constructor vacio y llamar a sus funciones setAge y setName. Mostrar los datos por pantalla. o Instanciar dos objetos más de tipo Person y hacer que valgan lo mismo que el objeto anterior utilizando el operador de asignación. o Por último, mostrar todos los nombres de las personas que contengan una ‘s’. Mostrar todos los nombres de las personas que tengan una edad superior a 18 años. Notas 1. Al menos debe haber un resultado. 2. Se espera que los alumnos completen el trabajo por su cuenta si no logran terminarlo durante práctica. informatica industrial/practicas/P3 - Clases II Memoria dinámica y Referencias.pdf Práctica de Informática Industrial I Grado en Ingeniería en Electrónica Industrial y Automática 2015‐2016 Práctica 3: Clases II. Métodos, Reserva dinámica y Referencias Profesores: Mohamed Abderrahim Álvaro Castro González Ioannis Douratsos José Carlos Castillo Javier Fernandez de Gorostiza Juan Carlos González Victores Informática Industrial 2015/2016 2/9 Práctica 3 Práctica 3 – Clases II. Métodos, Reserva dinámica y Referencias. 1. Métodos 1.1.1. Métodos constantes En C++, se puede usar el modificador const para definir una función constante. Mirad en el siguiente ejemplo la función getVal(). class MyC { public: MyC() {_val = 10;} ~MyC() {} void setVal(int v) {_val = v;} int getVal() const {return _val;} int getValNoConst() {return _val;} private: int _val; }; Al declarar una función como constante, le estamos indicando al compilador que la función no modificará en su interior el contenido del objeto. Es decir, es una función que accede a los valores, pero solo para ver su valor, posiblemente calcular algo usándolos y a lo mejor retornar algo. Pero no modificar el estado del objeto, es decir, ninguna de sus variables internas. ¿Para qué sirve esto? Mirad del siguiente ejemplo. class MyCC { public: MyCC() {} ~MyCC() {} void readFromMyC(const MyC & M) {_val = M.getVal();} private: float _val; }; La clase MyCC, en la función miembro readFromMyC, se hace que _val valga lo mismo que vale getVal(). Darse cuenta que en la declaración se pone const MyC & M, es decir, que M es una variable constante. Lo que quiere decir que no se debe poder modificar M dentro de la función. Al ser M de tipo constante, el compilador comprobará que no se hacen llamadas que descarten ese cualificador (el cualificador constante). Es decir, solo permitirá llamadas a funciones que sean de tipo constante. Por tanto, si la variable getVal() de MyC no se hubiese definido como constante, no se hubiese podido usar en readFromMyC. O dicho de otro modo, sólo se podrá llamar a funciones constantes de objetos que se comportan como constantes. Con lo que el siguiente código daría error de compilación: Informática Industrial 2015/2016 3/9 Práctica 3 class MyCC { public: MyCC() {} ~MyCC() {} void readFromMyC(const MyC & M) {_val = M.getValNoConst();} private: float _val; }; Pero esto tiene otra limitación. Se ha comentado que al definir una función como constante, no se puede modificar el valor del objeto. Por tanto, dentro de una función constante, sólo se podrán hacer llamadas a funciones constantes. Por ejemplo, el siguiente código daría error de compilación porque foo() se declara como una función constante pero dentro se hace una llamada a una función no constante. Aunque en realidad la función getValueNoConst() no modifica el objeto _myc, el compilador no lo sabe a priori. El compilador “se cura en salud” y dice, “como getValueNoConst() no es contante, podría ocurrir que se modificase el objeto. Así que no lo voy a permitir”. class MyCCC { public: MyCCC() {} ~MyCCC() {} int foo() const {return _myc.getValNoConst()*10;} private: MyC _myc; }; ¿Cuál sería la solución al problema? Pues llamar a una función constante. El siguiente código si sería correcto: class MyCCC { public: MyCCC() {} ~MyCCC() {} int foo() const {return _myc.getVal()*10;} private: MyC _myc; }; 2. Memoria dinámica y Referencias 2.1. Reserva y liberación de memoria Para la reserva de memoria dinámica se usa la directiva new. Y para la liberación de la misma se utiliza delete. Incluir lo siguiente en el .h de la clase Point como variable pública: Informática Industrial 2015/2016 4/9 Práctica 3 int size; int *v; Ahora, añadir al constructor vacío lo siguiente: size = 2; v = new int[size]; for (int i = 0; i < size; i++) { v[i] = 0; } Como se puede observar, se crea un vector de tipo entero de 10 elementos. Y en el constructor vacío de la clase se reserva memoria y se inicializan sus valores. La sintaxis general para la reserva de memoria en C++ es: tipo_dato *var = new tipo_dato[numero_elementos]; Lo bueno de new es que el calcula el tamaño de la memoria por el usuario. Probar el siguiente código de ejemplo: #include <iostream> using namespace std; int main() { int *v = new int[10]; // vector entero de 10 elementos v[0] = v[1] = 1; cout << v[0] << ", " << v[1] << endl; delete[] v; } Además, este mismo ejemplo se podría aplicar dentro de una clase. Si se vuelve a tomar la clase Point y se añade una variable pública int *v; en el constructor de la clase se podrá inicializar reservando memoria para dicho vector. Informática Industrial 2015/2016 5/9 Práctica 3 /////////////////////////// .h /////////////////////////// class Point { public: int *v; int size; } /////////////////////////// .cpp /////////////////////////// Point::Point() { size = 10; v = new int[size]; for (int i = 0; i < size; i++) { v[i] = 0; } } Creación dinámica de objetos Además de poderse reservar memoria para los tipos básicos, también se puede reservar memoria dinámica para los objetos. En este caso sería de la siguiente forma: main() { Point *P = new Point; } Con esto, se está creando de forma dinámica un objeto de tipo Point. El operador new se encargará de llamar al constructor, en este caso al constructor vacío. Aunque también se podría haber escrito lo siguiente para llamar a cualquier otro constructor, en este caso al parametrizado. main() { Point *P1 = new Point(10, 10); P1‐>display(); } Ahora bien, cuando se crea un objeto de forma dinámica hay cambios a la hora de llamar a sus funciones miembro. En lugar de usar el operador punto (.), como lo que se tiene es un puntero a un objeto (Point *), se usará el operador flecha (‐>). Esta es una de las novedades de C++ y que ya se verá como dará una gran potencia de reusabilidad. Destrucción de objetos dinámicos Informática Industrial 2015/2016 6/9 Práctica 3 Cuando se acaba de usar el objeto dinámico, no se debe de olvidar de destruirlo. Para ello, se usará la sentencia delete, que se encargará de llamar automáticamente al destructor, en caso de estar definido. Es importante indicar que si el destructor no está definido no se le podrá llamar. Si ahora se pone en el .cpp, siguiendo con el ejemplo anterior: Point::~Point() { delete[] v; } Se liberará la memoria reservada en el constructor dejándola libre para otros usos. Hacer esto es muy importante para no sobrecargar el sistema. Ver el siguiente ejemplo completo. #include <iostream> #include “point.h” using namespace std; using namespace space; int main() { // memoria dinamica objetos Point *P = new Point; Point *P1 = new Point(10, 10); P1‐>display(); delete P; delete P1; Point *P2 = new Point; for (int i = 0; i < P2‐>size; i++) { cout << "v[" << i << "] = " << P2‐>v[i] << endl; } } En el ejemplo, se crean varios objetos de la clase Point usando new. Después, se muestran sus valores y después se destruyen los objetos. Tener en cuenta que para liberar la memoria reservada para el objeto no hay que poner los corchetes ([]) como en el vector del ejemplo anterior. Posteriormente se crea otro objeto de forma dinámica y se accede a los elementos del vector dinámico creado en el interior de la clase. Para avanzados Si se quiere reservar memoria para una matriz debe tenerse en cuenta ciertos aspectos a la hora de la reserva y la liberación de la memoria. Para crear una matriz dinámica de NxM elementos sería: Informática Industrial 2015/2016 7/9 Práctica 3 double **matrix = new double*[n]; for (int i = 0; i < n; i++) { matrix[i] = new double[m]; } Y para liberar su memoria se haría al revés: for (int i = 0; i < n; i++) { delete[] matrix[i]; } delete[] matrix; 2.2. Referencias de objetos Para ver como se usan las referencias de objetos el ejemplo típico es mediante el paso de objetos dinámicos a funciones. Una vez se tiene creado el objeto de forma dinámica, ¿cómo se pasa a otra función?. A continuación se puede ver un ejemplo de uso. void modify(Point *P, int v) { P‐>setV(v); // implementar el metodo setV } main() { Point *P = new Point(10, 10); modify(P, 11); cout << P‐>getV() << endl; // implementar el metodo getV delete F; } En el ejemplo, la función recibe un puntero a un objeto de la clase y lo modifica en el interior. Otra forma alternativa de escribir la función hubiese sido. void modify(Point *P, int v) { (*P).setV(v); } Mediante (*P) se accede al contenido del objeto y no a la referencia, con lo que se puede usar el operador punto (.) en lugar del operador flecha (‐>). Pero se puede ir un poco más allá, se podría tener la siguiente función: Informática Industrial 2015/2016 8/9 Práctica 3 void modify(Point & P, int v) { P.setV(v); } En este caso se está usando la notación de paso por referencia de C++. Como se ve, es posible intercambiar entre las notaciones de C y C++. Para poder llamar a la función, se tendría que haber escrito: main() { Point *P = new Point(10, 10); modify(*P, 11); cout << P‐>getV() << endl; } Con el que se obtendría el mismo resultado. Informática Industrial 2015/2016 9/9 Práctica 3 Ejercicios Utilizando la librería estándar que se ha visto en las prácticas anteriores, escribir un programa en C++ que lea un array de n enteros por teclado y muestre la suma de todos ellos. El tamaño del array (n) debe ser dinámico y se pedirá al usuario por el teclado al principio del programa. Completar la implementación de las clases necesarias para realizar la base de datos de personas. Para ello, se debe definir: o Una clase PersonList en un fichero PersonList.h, y su implementación en un fichero PersonList.cpp. Deberá contener los siguientes atributos privados: Vector de tipo Person: _v. Número de personas: _n. Deberá contener los siguientes métodos públicos: Constructor vacío, parametrizado y de copia. Realizarán las inicializaciones pertinentes y las reservas de memoria. Destructor de la clase. Liberará la memoria reservada. Método readData: lee el número de personas a introducir en la base de datos y seguidamente los datos de cada una obteniéndolos por teclado. Método display: muestra la lista de personas incluidas en la base de datos. Operador []: que devuelva el elemento N de tipo Person solicitado. Realizar un fichero main.cpp en el que la función main deberá realizar lo siguiente: o Instanciar un objeto de tipo PersonList y llamar a su método readData, pedirá al usuario que introduzca por teclado el número de personas a introducir, se reservará la memoria de manera dinámica para esas personas y se pedirá por teclado los datos de cada una de ellas, seguidamente se llamará al método display, el cual mostrará por pantalla el contenido de la base de datos con todas las personas introducidas. informatica industrial/practicas/P4 - Herencia.pdf Práctica de Informática Industrial I Grado en Ingeniería en Electrónica Industrial y Automática 2014‐2015 Práctica 4 Herencia Profesores: Fares Abu‐Dakka Mohamed Abderrahim Álvaro Castro González Avinash Ranganath Carlos Alonso Ioannis Douratsos Informática Industrial 2015/2016 2/7 Práctica 4 Práctica 4 – Herencia. La herencia es una funcionalidad de los lenguajes objetos que permite reutilizar el código de clases existentes y extenderlo. 1. Herencia para extender En C++ se puede declarar una clase (llamada clase hija) que derive de otra clase (clase padre). Esto implica que la clase hija recuperará todos los miembros de la clase padre y puede ampliarse con nuevas funciones miembro. El siguiente ejemplo muestra cómo se puede definir una clase punto con color (el color se representará con un entero) derivada de la clase Point para recuperar la gestión de las coordenadas que ya se ha implementado. Poniendo el siguiente código en el .h de la clase Point: class Point { public: Point(int x, int y); void set(int x, int y); int getX() const {return _x;} int getY() const {return _y;} void display () const; private: int _x, _y; } Y el siguiente en el .h de la nueva clase ColorPoint: class ColorPoint : public Point { public: ColorPoint(int x, int y, int c) : Point(x, y) {_color = c;} int getColor() const {return _color;} private: int _color; } Para el siguiente código de prueba: Informática Industrial 2015/2016 3/7 Práctica 4 int main() { ColorPoint CP(10, 20, 255); CP.getX(); // come from Point class CP.display(); // come from Point class, display x and y CP.getColor(); // come from PointColor class } Gracias a la herencia, la clase ColorPoint sólo tiene que definir su miembro adicional (getColor), y recuperar de la clase Point los miembros relacionados con las coordenadas. Notad como el constructor de PointColor llama al constructor de Point que tomará dos parámetros. 2. Redefinición de una función miembro Esta solución todavía no es completamente satisfactoria ya que la función display, heredada de Point no muestra el color del punto. Por ello se puede redefinir las funciones miembros cuyo comportamiento queremos cambiar en la clase hija. Así, la clase Point quedará: class Point { public: Point(int x, int y); // set, getX, getY void display() const {cout << _x << _y;} private: int _x, _y; }; Y la clase ColorPoint: class ColorPoint : public Point { public: ColorPoint(int x, int y, int c) : Point(x, y) {_color = c;} int getColor() const {return _color;} void display() const {cout << _x << _y << _color;} private: int _color; }; De este modo, se puede cambiar el comportamiento de una clase hija con la redefinición de sus propios métodos. Con lo que el main quedaría: Informática Industrial 2015/2016 4/7 Práctica 4 int main() { ColorPoint CP(10, 20, 255); CP.display(); // come from PointColor class, display x, y and color Point P(10, 20); P.display(); // come from Point class, display x and y } Nota: En este momento la clase ColorPoint no compilaría. Encontrar el error y proponer una solución. Informática Industrial 2015/2016 5/7 Práctica 4 Ejercicios Realizar un simulador de ordenador: o Para ilustrar los conceptos de la programación orientada a objetos en C++, se va a realizar un pequeño proyecto a lo largo de las prácticas. Este se basará en desarrollar un simulador de ordenador. El programa deberá simular teclados, procesadores, programas, pantallas, etc. Para que el programa quede sencillo, nuestro ordenador solo será capaz de manejar cadenas de caracteres. De momento se propone implementar el código que corresponde al diagrama UML del anexo. o En concreto, el programa deberá poder leer caracteres desde el teclado, transmitirlos al procesador para que los transforme y enviarlos a una pantalla que muestre la cadena procesada. El diseño deberá estar pensado para que sea modular, es decir, que sea relativamente sencillo añadir o modificar cualquier elemento de las clases. o Para cada clase se debe crear un fichero de cabecera nombre_clase.h y un fichero fuente nombre_clase.cpp. o La descripción de los métodos a implementar se puede encontrar en la tabla del anexo. Informática Industrial 2015/2016 6/7 Práctica 4 Anexo Clase Métodos Descripción Device Device::Device(const string & name) Inicializa un componente con el nombre especificado const string & Device::getName() const Devuelve el nombre del componente. Display Display::Display(const string & name) Inicializa una pantalla con el nombre especificado. void Display::process(const string & data) Muestra la cadena por pantalla utilizando cout. Processor Processor::Processor(const string & name) Inicializa un procesador con el nombre especificado. void Processor::connectTo(Display & display) Conecta el procesador con un Display. void Processor::process(const string & data) Invierte la cadena de caracteres data y añade el prefijo “PROCESSED: ”. Manda el resultado al Display al cual está conectado. Por ejemplo, si data vale “hola”, la cadena enviada al Display debe ser “PROCESSED: aloh”. Keyboard Keyboard::Keyboard(const string & name) Inicializa un teclado con el nombre especificado. void Keyboard::connectTo(Processor & cpu) Conecta el teclado con un procesador. void Keyboard::process() Implementación por defecto de un teclado, lee una cadena de caractere desde el teclado real utilizando el operador de flujo cin. Después manda esta cadena al procesador llamando al método process() del procesador al cual está conectado. CharKeyboard CharKeyboard::CharKeyboard(const string & name) Inicializa un teclado de caracter con el nombre especificado. void CharKeyboard::process() Igual que Keyboard::process() pero solo lee un carácter (para ello se puede utilizar la función estándar cin.get()). LineKeyboard LineKeyboard::LineKeyboard(const string & name) Inicializa un teclado de línea con el nombre especificado. void LineKeyboard::process() Igual que Keyboard::process() pero lee un línea completa, es decir, lee caracteres hasta encontrar un '\n' y manda toda la línea directamente al procesador. Informática Industrial 2015/2016 7/7 Práctica 4 informatica industrial/practicas/P5 - Clases abstractas y polimorfismo.pdf Práctica de Informática Industrial I Grado en Ingeniería en Electrónica Industrial y Automática 2015‐2016 Práctica 5 Clases Abstractas y Polimorfismo Profesores: Mohamed Abderrahim Álvaro Castro González Ioannis Douratsos José Carlos Castillo Javier Fernandez de Gorostiza Juan Carlos González Victores Informática Industrial I 2015/2016 2/11 Práctica 5 Práctica 5 – Clases abstractas y polimorfismo. Las clases abstractas están diseñadas para que sean clases padre y sean heredadas por clases hijas. Las clases abstractas presentan los métodos que se deben implementar en las clases hijas pero no se implementan, por lo tanto las clases abstractas no se pueden instanciar. Para instanciar una clase hija que hereda una abstracta, ésta debe implementar todos los métodos abstractos. En C++ las clases abstractas se declaran utilizando funciones miembro virtuales puras mediante la palabra reservada virtual. Una función miembro virtual pura es aquella que tiene que ser implementada en una clase derivada. Una clase abstracta es aquella que tiene al menos una función miembro virtual pura. class Operation { // abstract class public: virtual int method(int a, int b)=0; }; class Addition: public Operation { public: int method(int a, int b) {return a+b;} }; class Subtraction: public Operation { public: int method(int a, int b) {return a‐b;} }; int main() { int n1, n2; cout << "Introduce dos enteros:" << endl; cin >> n1 >> n2; Addition A; Subtraction S; cout << "Adding " << n1 << "+" << n2 << "=" << A.method(n1,n2) << endl; cout << "Subtracting " << n1 << "‐" << n2 << "=" << S.method(n1,n2) << endl; return 0; } Como se ha visto, las funciones miembro virtuales puras se declaran como una función virtual igualada a cero. Normalmente las funciones virtuales puras no se implementan en las clases abstractas pero podría hacerse si fuese estrictamente necesario. A continuación se muestra cómo sería y cómo se realiza la llamada a las funciones miembro implementadas. Informática Industrial I 2015/2016 3/11 Práctica 5 ... int Operation::method(int a, int b) { cout << "Operandos " << a << " y " << b << endl; return 0; } class Addition: public Operation { public: int method(int a, int b) { Operation::method(a,b); return a+b; } }; ... Las funciones abstractas puras hacen que no se puedan definir objetos de su clase. Definiendo los métodos de una clase base como virtual, permite que las clases derivadas sobrescriban los métodos de la clase base. Lo que hace es que la función que se llama se decida en tiempo de ejecución, esto es el enlazado dinámico. Atendiendo al siguiente código, fijarse en el distinto comportamiento de las funciones print y food¿Qué salida se esperaría del programa? ¿Por qué? #include <iostream> class Animal { protected: int age; public: Animal(){this‐>age=‐1;} Animal(int n){this‐>age=n;} virtual int getAge()=0; virtual void print(){cout << "An animal " <<endl;} void food() {cout << "not defined yet" << endl;}; }; class Aquatic : public Animal { public: Aquatic():Animal(0) {} Aquatic(int nn):Animal(nn) {} int getAge() { return this‐>age; } void print() { cout << "Aquatic Animal: " << this‐>age << " years old" << endl; } void food() { cout << "little fishes and seaweeds" << endl; } }; Informática Industrial I 2015/2016 4/11 Práctica 5 int main() { Aquatic*A = new Aquatic(11); cout << "Aquatic:" <<endl; A‐>print(); A‐>food(); Animal *animal = a; cout << "Animal:" <<endl; animal‐>print(); animal‐>food(); return 0; } El polimorfismo es el mecanismo por el cual un mismo método identificado por su nombre, se comporta de forma distinta dependiendo del tipo de objeto que lo invoque. Es decir, es la capacidad de un método de comportarse de diferente forma según el tipo de objeto que lo llama. En resumen, el polimorfismo permite que una serie de operaciones diferentes tengan el mismo nombre. Se verán tres formas de realizar el polimorfismo: 1. Sobrecarga de métodos 2. Sobrecarga de operadores 3. Métodos virtuales Los dos primeros casos corresponden al polimorfismo estático, es decir, se decide en tiempo de compilación qué función va a ser llamada. El último caso corresponde al polimorfismo dinámico donde es en tiempo de ejecución cuando se decide la función a ejecutar. 1. Sobrecarga de métodos Una forma habitual de lograr el polimorfismo es a través de la sobrecarga de funciones y operadores. La sobrecarga consiste en declarar varias funciones con el mismo nombre pero con diferentes parámetros de entrada, ya sea en el númeroo en el tipo. El compilador utilizará la definición adecuada de la función dependiendo del tipo y del número de parámetros con los que se ha realizado la llamada a la función. Un ejemplo de esto se puede observar en los constructores del ejemplo anterior de los animales. Para crear instancias de la clase Aquatic se podrá realizar la llamada de dos formas diferentes: ... Aquatic*A1 = new Aquatic(11); Aquatic*A2 = new Aquatic(); ... Ejercicio 1 Informática Industrial I 2015/2016 5/11 Práctica 5 Utilizando el primer ejemplo de los operadores aritméticos, modificarlo para que las clases para que permitantambién sumar o restar tres números utilizando la función method. 2. Sobrecarga de operadores La sobrecarga de operadores proporciona funcionalidad adicional a operadores de C++ como +, *, >=, +=, etc., cuando se aplican a tipos de datos definidos por el usuario. Siguiendo con el ejemplo de los animales, a continuación se presenta la sobrecarga de un operador unario y otro binario: ... class Aquatic: public Animal { ... void operator ++(){this‐>age++;} //prefix void operator ++(int){this‐>age++;} //postfix bool operator>(Aquatic A) {return this‐>age > A.age;} }; ... int main(){ ... Aquatic A1(1); Aquatic A2(2); cout << "A1 age:" << A1.getAge() << endl; cout << "A2 age:" << A2.getAge() << endl; if(A1> A2) { cout << "A1>A2" << endl; } else { cout << "A1<=A2" << endl; } ++A1; A1++; cout << "A1 age:" << A1.getAge() << endl; cout << "A2 age:" << A2.getAge() << endl; if(A1>A2) { cout << "A1>A2 " << endl; } else { cout << "A1<=A2 " << endl; } ... } Sin embargo, no todos los operadores pueden ser sobrecargados. Los siguientes no lo permiten: Informática Industrial I 2015/2016 6/11 Práctica 5 Categoría del operador Operador Acceso a miembro Resolución de ámbito Condicional Puntero a miembro Tamaño de tipo de dato . (operator punto) :: (acceso global) ?: (sentencia condicional) * sizeof(...) Ejercicio 2 Utilizando el ejemplo de los animales, implementa la sobrecarga del operador >> para la clase Animal de tal forma que nos permita asignarle la edad. 3. Métodos virtuales La definición de clases abstractas a través de métodos miembro virtuales permite la última forma de polimorfismo que se verá. Esto se ilustra en el siguiente ejemplo donde se determina qué operación se va a realizar en tiempo de ejecución. class Operation { virtual int method(int a, int b)=0; }; class Addition: Operation { public: int method(int a, int b) {return a+b;} }; class Subtraction: Operation { public: int method(int a, int b) {return a‐b;} }; class Multiplication: public Operation { public: int method(int a, int b) {return a*b;} }; Informática Industrial I 2015/2016 7/11 Práctica 5 int main() { int n1, n2; cout << "Introduce dos enteros:" <<endl; cin >> n1 >> n2; srand(time(NULL)); /* initialize random seed: */ Operation *O; switch(rand() % 3){ case 0: O = new Addition; cout << "Operation ADDITION" <<endl; break; case 1: O = new Subtraction; cout << "Operation SUBTRACTION" <<endl; break; case 2:O = new Multiplication; cout << "Operation MULTIPLICATION" <<endl; break; } cout << "Result: " <<O‐>method(n1, n2) <<endl; } Informática Industrial I 2015/2016 8/11 Práctica 5 Ejercicios Continuando con la implementación del simulador de un ordenador que se empezó en la práctica anterior. Basándose en lo que ya se tenía, se van a realizar las pertinentes modificaciones para implementar el diagrama UML mostrado en el anexo junto a la descripción de cada una de las clases. El programa principal deberá ser capaz de ejecutar el siguiente programa: int main() { LineKeyboard LK("logitech"); Uppercase UP("intel"); Display D("lg"); Input *I = &LK; LK.connectTo(UP); I‐>connectTo(UP); // do same as before line UP.connectTo(D); LK.process(); CharKeyboard CK("logitech char"); Reverse R("intel reverse"); Printer P("Ricoh"); I = &CK; (*I) >> R>>P; I‐>process(); } Informática Industrial I 2015/2016 9/11 Práctica 5 A continuación se da una descripción de todas las clases: Clase Métodos Descripción Device Permanece igual que se definió. Input Input::Input(const string & name) Inicializa un dispositivo de entrada con un nombre. Input::connectTo(Processor & P) Conecta un dispositivo de entrada con un procesador. Processor Input::operator>>(Processor & P) Conecta un dispositivo de entrada con un procesador. void Input::process() Función virtual pura. Keyboard Keyboard::Keyboard(const string & name) Inicializa un teclado con el nombre especificado. void Keyboard::process() Implementación por defecto de un teclado, lee una cadena de caracteres desde el teclado real utilizando el operador de flujo cin. Después manda esta cadena al procesador llamando al método process() del procesador al cual está conectado. CharKeyboard CharKeyboard::CharKeyboard(const string & name) Inicializa un teclado de caracter con el nombre especificado. void CharKeyboard::process() Igual que Keyboard::process() pero solo lee un carácter (para ello se puede utilizar la función estándarcin.get()). LineKeyboard LineKeyboard::LineKeyboard(const string & name) Inicializa un teclado de línea con el nombre especificado. void LineKeyboard::process() Igual que Keyboard::process() pero lee un línea completa, es decir, lee caracteres hasta encontrar un '\n' y manda toda la línea directamente al procesador. Processor Processor::Processor(const string & name) Inicializa un procesador con el nombre especificado. void Processor::connectTo(Output & O) Conecta el procesador con una salida. void Processor::process(const string & data) Función miembro virtual que se va a encargar de buscar algunos comandos especiales en los datos que le llegan como puede ser el comando “quit”. void Processor::process(const string & data, int n) Lo mismo que la función anterior pero considerando sólo los n primeros caracteres. Output Processor::operator>>(Output & O) Conecta un dispositivo de entrada con un procesador. Informática Industrial I 2015/2016 10/11 Práctica 5 Uppercase Uppercase::Uppercase(const string & name) Inicializa un procesador de mayúsculas con el nombre especificado. void Uppercase::process(const string & data) Convierte en mayúsculas toda la cadena de caracteres data y añade un prefijo “PROCESSED: ”. Manda el resultado al Display al cual está conectado. Por ejemplo, si data vale “hola”, la cadena enviada al Display debe ser “PROCESSED: HOLA”. void Uppercase::process(const string & data, int n) Lo mismo que la función anterior pero considerando sólo los n primeros caracteres. Reverse Reverse::Reverse(const string & name) Inicializa un procesador para invertir el orden de caracteres con el nombre especificado. void Reverse::process(const string & data) Invierte la cadena de caracteresdata y añade un prefijo “PROCESSED: ”. Manda el resultado al Displayal cual está conectado. Por ejemplo, si data vale “hola”, la cadena enviada al Displaydebe ser “PROCESSED: aloh”. void Reverse::process(const string & data, int n) Lo mismo que la función anterior pero considerando sólo los n primeros caracteres. Output Output::Output(const string & name) Inicializa un dispositivo de salida con el nombre especificado. void Output::process() Función virtual pura. Display Display::Display(const string & name) Inicializa una pantalla con el nombre especificado. void Display::process(const string & data) Muestra la cadena en la pantalla utilizando cout. Printer Printer::Printer(const string & name) Inicializa una impresora con el nombre especificado. void Printer::process(const string & data) Simula la impresión de la cadena recibida mostrando por pantalla la cadena con el prefijo “Imprimiendo...”.Por ejemplo, si le llega la cadena “PROCESSED: aloh”, se imprimirá por pantalla utilizandocout la cadena “Imprimiendo... PROCESSED: aloh”. Informática Industrial I 2015/2016 11/11 Práctica 5 informatica industrial/practicas/P6 - Plantillas.pdf Práctica de Informática Industrial I Grado en Ingeniería en Electrónica Industrial y Automática 2015‐2016 Práctica 6 Plantillas Profesores: Mohamed Abderrahim Álvaro Castro González Ioannis Douratsos José Carlos Castillo Javier Fernandez de Gorostiza Juan Carlos González Victores Informática Industrial I 2015/2016 2/6 Práctica 6 Práctica 6 – Plantillas. Las plantillas es una de las características más poderosas y sofisticadas de C++. Las plantillas permiten crear funciones y clases genéricas que se pueden aplicar a distintos tipos de datos sin tener que modificar el código para cada tipo. 1. Funciones genéricas Una función genérica es un conjunto de operaciones que se aplicarán a distintos tipos de datos. El tipo de dato sobre el que la función operará se pasa como parámetro. En esencia, cuando se crea una función genérica se está creando una función que se sobrecarga así misma automáticamente. Para definir una plantilla se utiliza la palabra reservada template y la forma general de definir una plantilla para una función es: template <class Type,...> return_type name_function(parameter list) { //body of function } Type es una referencia al tipo de dato que se usará dentro de la función tanto para los parámetros de entrada como el de salida. El compilador lo sustituirá por el tipo de dato real que corresponda en cada llamada. A continuación se muestra un ejemplo en el que se crea una función genérica que intercambia los valores de dos variables. Puesto que el proceso para intercambiar dos valores es independiente del tipo de datos de las variables, es un ejemplo que se ajusta muy bien para el uso de plantillas. #include <iostream> using namespace std; template <class Type> void swaping(Type & a, Type & b){ Type aux = a; a = b; b = aux; }; int main(int argc, char *argv[]) { int i = 10, j = 20; double x = 10.2, y = 43.11; char a = 'v', b = 's'; cout << "Original i, j: " << i << ' ' << j << '\n'; cout << "Original x, y: " << x << ' ' << y << '\n'; cout << "Original a, b: " << a << ' ' << b << '\n'; swaping<int>(i,j); swaping<double>(x,y); swaping<char>(a,b); cout << "Swaped i, j: " << i << ' ' << j << '\n'; Informática Industrial I 2015/2016 3/6 Práctica 6 cout << "Swaped x, y: " << x << ' ' << y << '\n'; cout << "Swaped a, b: " << a << ' ' << b << '\n'; return 0; } En este ejemplo, Type es el tipo genérico que representa el tipo de dato de los valores que serán intercambiados. El compilador lo sustituirá por int, double y char según cree las instancias necesarias de la plantilla de la función. ¿Cuál es la salida? En una plantilla se pueden usar más de un tipo genérico de datos (de ahí los puntos suspensivos en la definición genérica de una plantilla de función). Implementa la función genérica que muestre los valores de dos variables de distintos tipos que se le pasan como parámetro. Prueba que funciona. La cabecera de la función genérica sería: ... template<class tipo1, class tipo2> void print(tipo1 a, tipo2 b); ... Recordando la práctica anterior, al igual que ocurre con las clases, las plantillas también se pueden sobrecargar. Además, también se pueden mezclar tipos de datos estándar con tipos genéricos de datos. Continuando con el último ejemplo implementa la función genérica que se corresponde con: ... template<class tipo1, class tipo2> void print(tipo1 a, tipo2 b, double c); ... ¿Qué ocurre si ejecutas print(“cadena de texto”, 2.5, ‘a’);? ¿Por qué? Resumiendo, las funciones genéricas deben realizar la misma acción general para todas las versiones, sólo diferenciándose en el tipo de dato. En contraste, la sobrecarga de funciones “normales” permite que cada implementación se comporte de una forma completamente diferente. 2. Clases genéricas Una clase genérica define todos los algoritmos que una clase va a usar y el tipo de dato que va a manipular se especificará como un parámetro cuando se instancie la clase. Una clase genérica es útil cuando usa una lógica que se puede generalizar. La forma general de definir una clase genérica es la siguiente: template <class Type,...> class class_name { ... } Informática Industrial I 2015/2016 4/6 Práctica 6 Lo mencionado sobre Type para funciones genéricas también aplica para clases genéricas. Para crear un objeto de la clase genérica se realiza de esta forma: class_name <type> object; Donde type es el tipo de dato que manejará la clase. A continuación se puede observar un ejemplo de la implementación de la estructura de datos pila funcionando con todo tipo de datos. ¿Hay algún error en el código anterior? Encuéntralo. #include <iostream> using namespace std; template<class Type> class Stack { public: Stack() : _size(10), _index(0) { // another way to initialize variables _data = new Type[_size]; } void push(Type data); Type pop(); private: int _size, _index; // stack size Type *_data; }; template<class Type> void Stack<Type>::push(Type data) { if(_index == _size) { cout << "Full stack" << endl; } else { this‐>_index++; this‐>_data[this‐>_index] = data; } } template<class Type> Type Stack<Type>::pop() { if(this‐>_index == 0) { cout << "Empty stack" << endl; return 0; } this‐>_index‐‐; return this‐>_data[this‐>_index]; } int main() { Stack<char> S; S.pop(); S.push('z'); Informática Industrial I 2015/2016 5/6 Práctica 6 S.push("q"); S.push(2.1); } En la especificación de una plantilla de clase también se puede utilizar lo que normalmente se piensa que es un argumento estándar. Por ejemplo, en el ejemplo anterior, si quisiéramos que el tamaño de la pila pudiera definirse en la instanciación de un objeto de la plantilla de la clase se haría como sigue: ... template<class T, int size> class Stack { T data[_size]; } ... ¿Qué habría que cambiar en la implementación de las funciones miembro de la clase genérica? Nota: hay que tener en cuenta que en C++ sólo se permiten utilizar “argumentos estándar” en las plantillas de los tipos entero, puntero o referencia y son tratados como constantes. 3. STL La Biblioteca de Plantillas Estándar (del inglés, Standar Template Library) de C++ es una biblioteca que proporciona clases de propósito general para implementar las estructuras de datos más conocidas y usadas. Están basadas en plantillas de clases y funciones. En la STL existen tres elementos principales: los contenedores que almacenan objetos, los algoritmos que actúan sobre los contenedores, y los iteradores que permiten recorrer los contenedores de diversas formas. Antes de desarrollar propias estructuras de datos y algoritmos (como se hizo con el ejemplo de la pila) asegurar que no se puede utilizar alguna de las STL ya existentes. Como ejemplo se va a ver como se trabaja con un vector de elementos y sus operaciones básicas. #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { // creating vector with size 10 unsigned int i; vector<char> myvector(10); cout << "size = " << myvector.size() << endl; // assign elements and showing for(i=0;i<10;i++) { myvector[i] = 'z' ‐ i; } for(i=0;i<myvector.size();i++) { cout << myvector[i] << ' '; } cout << endl << endl; Informática Industrial I 2015/2016 6/6 Práctica 6 // add new elements if necessary for(i=0;i<10;i++) { myvector.push_back('z'‐i‐10); } for(i=0;i<myvector.size();i++) { cout << myvector[i] << ' '; } cout << endl << endl; // using an iterator vector<char>::iterator it; it = myvector.begin(); while(it != myvector.end()) { *it = toupper(*it); it++; } for(i=0;i<myvector.size();i++) { cout << myvector[i] << ' '; } cout << endl <<endl; // sorting the vector sort(myvector.begin(), myvector.end()); for(i=0;i<myvector.size();i++) { cout << myvector[i] << ' '; } cout << endl << endl; cin.get(); // wait an INTRO return 0; } Recordad: Es muy útil mientras se programa el autocompletado de código (Ctrl+Espacio), de este modo se podrán ver todas las posibilidades disponibles. Existen multitud de fuentes donde se puede encontrar más información sobre las STL. Por ejemplo se puede consultar cualquiera de los libros recomendados en la asignatura o alguno de los siguientes enlaces: http://www.cplusplus.com/reference/stl/ http://www.cppreference.com/wiki/stl/start Ejercicios Puesto que las plantillas se pueden aplicar prácticamente a cualquier tipo de dato, incluidos los definidos por uno mismo, aplicar lo aprendido al simulador de ordenador de las prácticas anteriores. Crear una lista de todas las cadenas que le llegan al procesador. Cuando le llegue la cadena history mostrar por pantalla el historial de todas las cadenas que haya recibido. informatica industrial/practicas/P7 - Ficheros.pdf Práctica de Informática Industrial I Grado en Ingeniería en Electrónica Industrial y Automática 2015‐2016 Práctica 7 Ficheros Profesores: Mohamed Abderrahim Álvaro Castro González Ioannis Douratsos José Carlos Castillo Javier Fernandez de Gorostiza Juan Carlos González Victores Informática Industrial I 2015/2016 2/6 Práctica 7 Práctica 7 – Ficheros. 1. Apertura y cierre de ficheros En esta práctica se verá cómo trabajar con ficheros del sistema. Hay que tener en cuenta que los ficheros de I/O (Input/Output) son simplemente un caso especial del sistema general de I/O del sistema y por lo tanto lo que se va a ver se puede aplicar también a cualquier otro flujo de datos conectado a otros tipos de dispositivos. Para trabajar con ficheros en C++ es necesario incluir el fichero de cabecea: fstream. Lo primero que hay que hacer para abrir un fichero es crear un flujo que dependerá de la “dirección” de los datos. Hay tres tipos de flujos: De entrada: ifstream De salida: ofstream De entrada/salida: fstream Para abrir un fichero se llamará a la función open() o directamente mediante el constructor correspondiente. Esta función recibe como parámetros la ruta del fichero y el modo de apertura. El modo se determina con uno o más de estos valores: ios::app → añadir datos al final del fichero. ios::ate → posicionarse al final del fichero. ios::in → determina un flujo de entrada. ios::out → determina un flujo de salida. ios::binary → modo binario (por defecto está el modo texto). ios::trunc → cambia el tamaño del fichero a 0 eliminando los datos. Esta función tiene asignados varios valores por defecto, con lo que no estrictamente necesario ponerlos. A continuación se puede ver un ejemplo de uso: #include <iostream> #include <fstream> Using namespace std; int main(intargc, char *argv[]) { ifstream input("input.txt"); //reading file in text mode if(!input){ cerr << "Error opening input file!" << endl; } ofstream output; //writing file in binary mode output.open("output.bin", ios::out | ios::binary); if(output.is_open()) { //check if file is opened cerr << "Output file ready" << endl; } // ... do things with files ... Informática Industrial I 2015/2016 3/6 Práctica 7 input.close(); output.close(); return 0; } 2. Lectura y escritura de ficheros de texto Para leer y escribir ficheros de texto se pueden usar directamente los operadores >> y << respectivamente, igual que si se hiciera por consola. Por ejemplo se podría hacer algo como: output << “escribiendo en el flujo de salida output” << endl; Ejercicio Escribir un programa que pida al usuario el nombre y apellidos de varias personas y los escriba en un fichero usando una línea para los datos de cada persona. A continuación hacer otro programa que los lea y los muestre por pantalla. ¿Cómo detectar el final del fichero? Se puede usar la función eof() (end of file) del flujo correspondiente o tener en cuenta que cuando se llega al final de fichero el flujo asociado devuelve false). Otra interesante función que permite la lectura de datos es getline() cuyos prototipos son: istream&getline(char *buf, streamsize num); istream&getline(char *buf, streamsize num, char delim); Estas funciones leen caracteres del flujo y los almacenan en el array apuntado por buf hasta que se haya leído num‐1 caracteres o hasta que se encuentra un final de línea (el carácter delim en la segunda forma) o hasta que se llegue al final de fichero. El carácter delimitador se extraerá y no se añadirá al array buf. La función get() tiene otras dos formas que funcionan de manera muy parecida a getline. Estos son los prototipos: istream & get(char *buf, streamsize num); istream & get(char *buf, streamsize num, char delim); intget(); La única diferencia entre las dos primeras formas y getline es que las formas de get no eliminan el delimitador y permanece en el flujo hasta la siguiente operación de lectura. En cuanto a get() devuelve el siguiente carácter y en caso de llegar al final de fichero devolverá EOF. Haz un programa que se encargue de leer el fichero que anteriormente has creado de nombres y los muestre por pantalla línea por línea. Informática Industrial I 2015/2016 4/6 Práctica 7 3. Ficheros binarios Los ficheros de texto no son siempre los más eficaces. Además puede ser que se necesite escribir datos que no tienen formato, es decir, en crudo. Por estos motivos es necesario poder trabajar con ficheros binarios. Para trabajar con ficheros binarios es necesario abrir el fichero en el siguiente modo (ios::binary). Para leer y escribir un fichero binario se puede hacer byte a byte, usando las funciones get() y put(). O se puede hacer por bloques, usando las funciones read() y write().Que se encargan respectivamente de leer/escribir num caracteres desde la posición apuntada por buf. istream&read(char *buf, streamsizenum); ostream&write(char *buf, streamsizenum); Ejercicio Crear un programa que escriba carácter a carácter todos los caracteres del 0 al 255 en un fichero binario. Leer dicho fichero y mostrarlo por pantalla los números pares. 4. Acceso aleatorio En C++ existen dos punteros asociados a los ficheros: uno es el puntero get, que apunta a la posición donde se va a realizar la siguiente operación de entrada (lectura), y el otro es el puntero put, que especifica la siguiente posición donde se realizará la próxima operación de salida (escritura). Para modificar estos punteros existen las funciones seekg y seekp: istream & seekg(off_type offset, seekdir origin); ostream & seekp(off_type offset, seekdir origin); Estas funciones desplazan sus respectivos punteros con un offset desde la posición indicada por origin, la cual puede ser uno de los siguientes valores: ios::beg → el comienzo del fichero. ios::cur → la posición actual. ios::end → el final del fichero. Las operaciones de acceso aleatorio a un fichero generalmente sólo se utilizan en operaciones binarias y hay que ser especialmente cuidadoso puesto que puede provocar una falta de sincronización con los datos del fichero. Para determinar la posición actual de los punteros se usan las funciones: pos_typeseekg(); pos_typeseekp(); Cuyos resultados se pueden aplicar directamente a las funciones seekg y seekp de la siguiente manera: Informática Industrial I 2015/2016 5/6 Práctica 7 istream & seekg(pos_type pos); istream & seekp(pos_type pos); Ejercicio Escribir un programa que guarde en formato binario los números enteros del 0 al 10 en un fichero. A continuación leerlos del fichero e imprimirlos por pantalla. Finalmente desplazar el puntero get del fichero un carácter desde el comienzo del fichero y volver a leerlos. ¿Qué resultado se obtiene? ¿Por qué? Informática Industrial I 2015/2016 6/6 Práctica 7 Ejercicios Continuar ampliando la funcionalidad del simulador de ordenador. En este caso se deberá crear dos nuevas clases InputFile y OutputFile que heredarán de Input y Output respectivamente. InputFile se encargará de leer un fichero de texto y enviarlo al procesador correspondiente. A su vez crear otras dos clases que hereden de InputFile y que se comporten de forma diferente: una enviará los datos línea a línea y la otra palabra a palabra. OutputFile escribirá cadenas en un fichero de texto añadiéndolas al final. El formato de cada línea será [fecha+hora]:[cadena de texto]. Para obtener la fecha y la hora se podrá usar algo similar al siguiente código: #include <time.h> #include <string.h> // strtok: used to remove the line scape (‘\n’) ... time_t rawtime; struct tm *timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); cout << “TIME: “ << strtok(asctime(timeinfo), ”\n”) << endl; La salida en el fichero deberá ser algo parecido a lo siguiente: MonSep 06 18:39:05 2010:Processed by Uppercaseprocessor: EN MonSep 06 18:39:05 2010:Processed by Uppercaseprocessor: UN MonSep 06 18:39:05 2010:Processed by Uppercaseprocessor: LUGAR MonSep 06 18:39:05 2010:Processed by Uppercaseprocessor: DE MonSep 06 18:39:05 2010:Processed by Uppercaseprocessor: LA MonSep 06 18:39:05 2010:Processed by Uppercaseprocessor: MANCHA, MonSep 06 18:39:05 2010:Processed by Uppercaseprocessor: DE informatica industrial/problemas/ejerciciosII_v4.pdf Ejercicios de Informática Industrial 2014-2015 1. Calcular el máximo de un array de números 2. Calcular el factorial de un número. 3. Ordenar un vector de números. 4. Detectar cuando una palabra introducida es un palíndromo (se lee igual de izquierda a derecha, que de derecha a izquierda). Por ejemplo: Ana, arenera, oso, radar, reconocer, rotor, salas, seres, somos, sometemos,etc. 5. Introducir dos matrices y multiplicarlas. 6. Realizar un programa que nos detecte si los caracteres que introducimos son letras, números u otra cosa. 7. Convertir una cadena de caracteres en minúscula a mayúscula. 8. Escribir un programa que permita realizar distintas operaciones matemáticas sobre un par de números: sumar, restar, multiplicar, dividir, etc. El programa pedirá que se introduzcan los números con los que se va a operar así como la operación que se desea aplicar. Las funciones que implementan las operaciones tienen que estar implementadas en un fichero distinto de donde se encuentra la función main y deben contar además con el correspondiente fichero de cabecera. 9. Realizar el mismo ejercicio que el anterior pero ahora los números para operar se le pasarán como parámetros al programa. 10. Crear una estructura/clase que represente cada uno de los alumnos de la asignatura. Debe existir al menos los siguientes campos: nombre, apellidos, nie y nota. Crear un array/lista donde se vayan almacenando todos los alumnos para posteriormente calcular la nota media de todos; también se podrán listar los que tienen sobresaliente, notable, aprobado o suspenso. 11. Hacer un programa que pida al usuario una cadena de caracteres y detecte si es o no un número entero, y, si lo es, que lo calcule. 12. Lo mismo que el anterior, pero incluyendo la posibilidad de detectar números decimales. 13. Difícil. Hacer un programa que almacene los nodos contiguos de una red, con las distancias entre ellos, y que calcule la ruta más rápida entre dos lugares. Se puede hacer con parte de la red de transportes de la Comunidad de Madrid. 14. Hacer un programa que calcule si un número es primo o no, y que, si no lo es, lo descomponga en factores. 15. Hacer un juego en el que, un tablero de diez por diez se llene aleatoriamente de unos y ceros. Luego, cada elemento mantendrá su valor (uno o cero) hasta la generación siguiente si más de dos tercios de sus vecinos son iguales a él, cambiará si menos de un tercio de sus vecinos son iguales a él, y tendrá un 50% de probabilidad de mantener su valor (y el restante 50% de cambiarlo) si entre un tercio y dos tercios de sus vecinos son iguales a él. Se mostrará por pantalla el valor de los elementos en forma de matriz. 16. Sencillo. Hacer un programa que pida los elementos de una matriz, de tamaño prefijado, y que la muestre por pantalla. 17. Lo mismo, pero con un tamaño de la matriz variable, que se pregunta previamente al usuario. 18. Hacer un programa que detecte si un número de teléfono es fijo o móvil, y, si es fijo, que diga de qué provincia es (en una primera aproximación, se puede hacer que distinga si es de Barcelona, de Madrid, o de otra provincia). 19. Sencillo. Hacer un programa que pida las dimensiones de un paralelepípedo, y a partir de ello, calcule su área y su volumen. 20. Hacer un programa que pida el nombre, primer apellido y segundo apellido de un conjunto de personas, que los almacene, y que luego permita ordenarlos alfabéticamente por cualquiera de los tres campos, o sacar los que empiecen por una determinada letra de algún campo. 21. Difícil. Lo mismo que el anterior, pero con un número variable de personas (memoria dinámica) y la posibilidad de encontrar cualquier cadena de caracteres en cualquier parte de cualquiera de los campos. 22. Difícil. Hacer un programa que detecte rimas en un texto. 23. Difícil. Hacer lo mismo que el anterior, pero además, contando sílabas. 24. Hacer un programa que detecte tantos números como haya en un texto, reserve la memoria necesaria para ellos, los calcule como enteros y los guarde. 25. La sucesión o serie de Fibonacci es la sucesión de números: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... Cada número se calcula sumando los dos anteriores a él. • El 2 se calcula sumando (1+1) • Análogamente, el 3 es sólo (1+2), • Y el 5 es (2+3), • ….etc.. Realice un programa que cree un vector de números de Fibonacci e imprímalos por pantalla. Para crear el vector escriba una función fibonacci(x,y,v,n) Donde x e y son dos enteros que me indicarán los dos números iniciales del vector v[0] = x v[1] = y Cada elemento del vector es la suma de los dos anteriores. v es el vector que vamos a crear y n el número de elementos del vector. 26. Al programa anterior, añada una nueva función que ordene en forma decreciente los números del vector de Fibonacci creado e imprímalos por pantalla. 27. Compruebe si un número entero positivo corresponde a un número de la serie de Fibonacci. Para esto use la fórmula de Binet: Si N es un número de Fibonacci entonces 5*N2+4 ó 5*N2-4 son cuadrados perfectos. 28. Fácil: Escribir una función que acepta un límite superior y un límite inferior, calcular los cuadrados de cada uno de los números enteros entre el superior y el límite inferior [inclusive], y luego imprimir la suma de todas las cuadrados. Hacerlo a través de la recursividad. Eazy: Write a function to accept an upper and a lower limit, calculate the squares of each of the integer number between the upper and the lower limit [inclusive], and then print the sum of all the squares. Do so using recursion. 29. Medio: Aceptar un número entero, junto con su base [binario, octal, decimal o hexadecimal], validar las entradas, a continuación, convertir el número en una base diferente, conforme a lo solicitado por el usuario, y luego imprimir el número de convertidos. Medium: Accept an integer number along with its base [Binary, octal, decimal or hexadecimal], validate the inputs, then convert the number into a different base, as requested by the user, and then print the converted number. 30. Difícil: Escribir una función para aceptar un número entero [hasta cinco dígitos], y imprimir el número de palabras. Eg: (a) 65 – Sixty Five. (b) 12345 – Twelve Thousand Three Hundred and Forty Five. Difficult: Write a function to accept an integer number[Up to five digits long], and display the number in words. Eg: (a) 65 – Sixty Five. (b) 12345 – Twelve Thousand Three Hundred and Forty Five. 31. Difícil: Escribir un programa para aceptar cualquier fecha en el siglo 21 y la salida del día de la semana de esta fecha cae. Validar la entrada del número de días en un mes, año bisiesto, el número de meses en un año, etc. Difficult: Write a program to accept any date in the 21st century and output the day of the week this date falls on. Validate the input for number of days in a month, leap year, number of months in a year, etc. informatica industrial/problemas/Enunciado ejercicio UML.pdf Ejercicio: Una biblioteca tiene copias de libros. Estos últimos se caracterizan por su nombre, tipo (ingeniería, literatura, informática, historia ...), editorial, año y autor. Los autores se caracterizan por su nombre, nacionalidad y fecha de nacimiento. Cada copia tiene un identificador, y puede estar en la biblioteca, prestada, con retraso o en reparación. Los lectores pueden tener un máximo de 3 libros en préstamo. Cada libro se presta un máximo de 30 días, y por cada día de retraso, se impone una “multa” de dos días sin posibilidad de coger un nuevo libro. Realiza un diagrama de clases y añade los métodos necesarios para realizar el préstamo y devolución de libros. informatica industrial/problemas/Mas ejercicios ingenieria del software resueltos.pdf INFORMÁTICA INDUSTRIAL I Grado en Ingeniería Electrónica Industrial y Automática Ejercicios Ingeniería del Software Curso 2013-2014 Ejercicio: Venta de coches Realizar el diagrama de clases correspondiente al siguiente sistema. Se trata de una empresa de venta de coches de segunda mano con las siguientes características: • Los coches los suministran distintos proveedores, nos interesa conocer la marca, modelo, matrícula, precio de compra, de venta. Los coches pueden ser turismos, industriales y todoterrenos. Además pueden necesitar ser reparados, por lo que se debe tener un control de las reparaciones hechas, que pueden ser mecánicas, eléctricas o de chapa. • En la empresa habrá dos tipos de vendedores: asalariados y por comisión. De los asalariados nos interesa saber también el salario y de los que van con comisión los coches que se han venido. Además se tendrá un control de los clientes tanto de los que han comprado un coche, como de los interesados en algún tipo de coche que podrán hacer reserva. • Los coches pueden estar en distintas exposiciones, y debemos saber en todo momento dónde se encuentra cada coche. • Se necesitan operaciones para realizar una venta de un coche, para reparar los coches que los necesiten, etc. También interesa tener operaciones que nos devuelvan qué cliente compró un cierto coche, que se realicen listados de los coches que se encuentran en stock en un momento dado. Ejercicio: Distribución de productos alimentarios Se desea realizar la gestión de un negocio de distribución de productos de alimentación. Para ello se pide: 1. Gestión clientes Los clientes pueden ser personas jurídicas o físicas. Los datos que interesa mantener de los clientes son un código único de cliente, nombre, dirección, lista de teléfonos de contacto (con su descripción: oficina, almacén,…), ciudad, código postal, y la forma de pago (que puede ser contado o crédito. Las personas físicas tienen además un NIF para identificarlas, mientras que las personas jurídicas cuentan con una razón social y un CIF. Los clientes pueden darse de alta, modificarse y darse de baja. 2. Gestión de proveedores De los proveedores interesa mantener los siguientes datos: un código único, nombre, dirección, ciudad, código postal, lista de teléfonos de contacto (con su descripción: oficina, almacén, fax,…). Los proveedores también pueden ser personas físicas, identificadas por su NIF, o jurídicas, identificadas por su CIF y razón social. Los proveedores pueden darse de alta, modificarse y darse de baja. 3. Gestión de artículos Los artículos se dividen en familias. Cada familia se caracteriza por un código y una descripción. Cada artículo se compone de un código, nombre, IVA que se le aplica, precio, número de unidades en stock. Cada artículo lo sirve un único proveedor. Los artículos pueden darse de alta, modificarse y darse de baja. 4. Gestión de albaranes Un albarán estaría formado por una cabecera, por unas líneas de albarán y por un pie con los totales. La cabecera tiene el número de albarán, los datos del cliente que se estimen oportunos y la fecha de creación del albarán. Cada línea del albarán consta del código y la descripción del artículo, el número de unidades, el precio y el importe total del artículo. El pie recoge los totales de la siguiente forma: Precio de los artículos sin IVA y precio con IVA. De un albarán debe saberse si está pagado o no. Los albaranes pueden crearse, modificarse y borrarse. informatica industrial/problemas/solución ejercicio UML.pdf Ejercicio: Una biblioteca tiene copias de libros. Estos últimos se caracterizan por su nombre, tipo (ingeniería, literatura, informática, historia ...), editorial, año y autor. Los autores se caracterizan por su nombre, nacionalidad y fecha de nacimiento. Cada copia tiene un identificador, y puede estar en la biblioteca, prestada, con retraso o en reparación. Los lectores pueden tener un máximo de 3 libros en préstamo. Cada libro se presta un máximo de 30 días, y por cada día de retraso, se impone una “multa” de dos días sin
Compartir