EXAMEN FINAL DE TACCII 4º INGENIERIA INFORMÁTICA, UAM, 31-ENERO–2006 Problema 1) NOTAS: i) Se recomienda leer todo el enunciado antes de comenzar a resolver este problema. ii) El apartado c) es opcional. Las restantes partes del examen suman 10 puntos. a) (1,5 puntos) Se desea desarrollar un programa en C++ que permita colocar ítems rectangulares de tamaños diversos en un contenedor formando hileras. La longitud máxima de las hileras es fija y los ítems se añaden colocándolos al final de la última hilera hasta que no caben en ella, momento en que se comienza una hilera nueva (para fijar ideas, el contenedor podría ser esta hoja de papel, los ítems podrían ser las palabras que aparecen en este párrafo y las hileras las líneas que lo forman). Se pide un programa que incluya la estructura de un conjunto de clases que permitan la funcionalidad indicada y la definición de un método que añade un item al contenedor, colocándolo al final de la última hilera o, si no hay espacio, al comienzo de la siguiente. Las hileras tendrán una altura variable para ajustarse a la altura de los itms que contienen. Entre cada dos hileras y entre cada dos ítems consecutivos no se dejará ningún espacio. Los ítems contenidos en una hilera tendrán la parte más próxima a la hilera anterior pegada a la misma. La altura de cada hilera será la mayor de las alturas de los ítems que contiene. b) (1,5 puntos) Se desea utilizar el programa anterior (adaptándolo o extendiéndolo) para programar un visualizador de documentos de texto basado en MFC de acuerdo con las siguientes especificaciones: I. Los documentos no tienen márgenes superiores, inferiores, izquierdos ni derechos. Los documentos están formados por un sólo párrafo en el que las palabras se alinean a la izquierda, como en el primer párrafo de este enunciado. Las longitudes que se mencionan a partir de aquí se miden en pixels. II. Los párrafos están formados por palabras. Las palabras corresponden a cadenas de caracteres, implementadas mediante la clase CString de MFC y se dibujan mediante el método CDC::TextOut(int x, int y, CString contenido), en el que los argumentos x e y denotan la posición del vértice más próximo al comienzo del documento donde se va a escribir la cadena de caracteres. Las dimensiones (altura y anchura en pixels) de una cadena de caracteres al dibujarla en la pantalla se obtienen mediante el método CDC::GetTextExtent(CString), que devuelve una estructura (struct) de tipo CSize con atributos cx y cy que son de tipo int. (Nota: Se puede también utilizar la subclase CPaintDC de CDC en lugar de esta última). Las palabras no están separadas por ningún espacio (se puede considerar que contienen los espacios en blanco necesarios como caracteres). Se pide un programa que incluya la estructura de un conjunto de clases que permitan la funcionalidad indicada y la definición de un método que dibuja el documento. c) (opcional, 1,5 puntos) Se pide un programa semejante al del apartado b) anterior (conjunto de clases y método de dibujado) que permita visualizar documentos cuyo contenido está formado por palabras y rectángulos. Para ello se supondrá que en la clase CDC se dispone de un método DibujaRectangulo(int x, int y, int ancho, int alto), en el que los argumentos x e y tienen un significado análogo al que tienen en el método CDC::TextOut. d) (2 puntos) Se pide un diagrama de clases que englobe en la mayor proporción posible los programas pedidos en los apartados anteriores. Apartado 1.a) template <class I> class Contenedor { public: int int vector<Hilera<I> *> void ancho; alto; // Hasta la última hilera completa hileras; Contenedor(int ancho); anyadir(I *); }; template <class I> class Hilera { public: Contenedor<I> * int int int vector<I *> Bool cont; pos; // Altura dentro del contenedor largo; alto; items; Hilera(Contenedor<I> *cont, int pos, I *item); anyadir(I *item); }; // Para utilizar en las clases Contenedor e Hilera template <class C> class Item { public: int ancho; int alto; int posx; int posy; C contenido; }; template <class I> void Contenedor<I>::anyadir(I *item) { if (hileras.size() == 0) { hileras.push_back(new Hilera<I>(this, 0, item)); return; } else if (hileras[hileras.size()-1]->anyadir(item)) return; else { alto += hileras[hileras.size()-1]->alto; hileras.push_back(new Hilera<I>(this, alto, item)); }; }; template <class I> bool Hilera<I>::anyadir(I *item) { if (largo + item->ancho > cont->ancho) return false; item->posx = largo; item->posy = cont->alto; items.push_back(item); alto = max(alto, item->alto); largo += item->ancho; return true; }; Apartado 1.b) La versión que se muestra utiliza documentos y vistas; se puede hacer análogamente sin utilizar documentos, utilizando directamente una ventana marco sin vista, con una vista de formulario o con un diálogo. Mediante el wizard de Visual C++ o por otro método se definen de manera estándar las clases CContenedorMFCApp, CContenedorMFCView y CContenedorMFCDoc. A la clase CContenedorMFCDoc se le añade un atributo vector<CString *> contenido. Se define una clase específica de ítems: class ContenedorMFCItem : Item<CString *> { ContenedorMFCItem(CString *str, CDC *cdc); }; El constructor de esta clase asigna las dimensiones al item utilizando el resultado del método CDC::GetTextExtent(CString): ContenedorMFCItem(CString *str, CDC *dc) : Item<CString *>(str) { CSize size = dc->GetTextExtent(*str); ancho = size.cx; alto = size.cy; }; La clase CContenedorMFCView se hace que sea subclase de Contenedor<ContenedorMFCItem> además de serlo de CView. El método CContenedorMFCView::OnInitialUpdate() asigna el ancho del contenedor y construye iterativamente un ContenedorMFCItem por cada cadena del contenido del documento, que añade al contenedor: void CContMFCView::OnInitialUpdate() { CView::OnInitialUpdate(); GetDocument()->cView = this; LPRECT rect = new tagRECT; GetClientRect(rect); ancho = rect->right; for (int i = 0; i < GetDocument()->contenido.size(); i++) anyade(CContenedorMFItem(GetDocument()->strings[i])); }; El método de dibujado recorre las hileras y sus ítems y dibuja en las posiciones correspondientes sus contenidos: void CContenedorMFCView::OnDraw(CDC* pDC) { vector<Hilera<Item<String> > > hileras = getDocument() ->getContainer()->getHileras(); for (int i = 0; i < hileras.size(); i++) for (int j = 0; j < hileras[i]->items.size(); j++) pDC->TextOut(hileras[i]->items[j]->posx, hileras[i]->items[j]->posy, *hileras[i]->items[j]->str); }; Apartado 1.c) Los cambios a realizar con respecto al apartado anterior son: i) Definimos la clase ItemContenido y las subclases ItemCString y ItemRectangulo: class ItemContenido { }; class ItemCString : public ItemContenido { CString str; }; class ItemRectangulo : public ItemContenido { int ancho; int alto; }; El contenido del documento es un vector de punteros a ItemContenidos en lugar de CStrings. ii) Se crean dos subclases de la clase ContenedorMFCItem: ContenedorMFCItemString y ContenedorMFCItemRect, que son subclases respectivamente de Item<ItemCString> y de Item<ItemRectangulo>. Los constructores calculan las dimensiones de los ítems de distintas formas según el caso (en un caso lo hacen como en el apartado anterior, en otro a través del rectángulo). Además, la clase ContenedorMFCItem tiene un método virtual abstracto draw(CDC *). Este método se define en cada subclase mediante los métodos CDC::TextOut y CDC::DibujaRectangulo iii) El método de dibujado sustituye la llamada a pDC->TextOut por una llamada al método draw sobre los ítems, con pDC como argumento: void CContenedorMFCView::OnDraw(CDC* pDC) { for (int i = 0; i < hileras.size(); i++) for (int j = 0; j < hileras[i]->items.size(); j++) hileras[i]->items[j]->draw(pDC); }; Apartado 1.d) Diagrama de clases para el apartado a): Contenedor 1 Hilera 1 Item Diagrama de clases para el apartado b) (no se incluye la clase de la aplicación ni la cardinalidad de las relaciones): Contenedor Hilera Item ContMFCView MFCItem ContMFCDoc CView CDocument CString El diagrama de clases para el apartado 1.c) es análogo, excepto que la clase CString se sustituye por la jerarquía formada por la clase ItemContenido y las subclases ItemCString y ItemRectangulo y análogamente a la clase MFCItem se le añaden sus dos subclases: Contenedor Hilera Item ContMFCIStr ContMFCView MFCItem ContMFCDoc ContMFCIRect CView CDocument ItemCStr ItemCont ItemRect Problema 2) (10 puntos) Completar el siguiente programa para que la salida sea la que se indica: Programa: #include <iostream> #include <string> using namespace std; class X { public: void f () { cout << "Metodo f en clase X" << endl; } }; class Y { public: void f () { cout << "Metodo f en clase Y" << endl; } }; void main(int argc, char* argv[]) { X x; Y y; x.f(); y.f(); Z<X*> z1=&x; Z<Y*> z2=&y; z1.f(); z2.f(); } Salida: Metodo Metodo Metodo Metodo f f f f en en en en clase clase clase clase X Y X Y Solución: template <class T> class Z { public: T t; Z (T t) : t(t) {} void f () { t->f(); } }; Problema 3) (10 puntos) Escribir el código de la clase A para que la salida sea la que se indica, y que se verifique para cualesquiera valores de (x1,x2,x3): Programa: #include <iostream> #include <string> using namespace std; void main(int argc, char* argv[]) { int x1=5, x2=7, x3=8; A a1(x1), a2(x2), a3(x3); a1.valor(); a2.valor(); a3.valor(); (a1+=a2)+=a3; a1.valor(); a2.valor(); a3.valor(); (x1+=x2)+=x3; cout << "Valores: " << x1 << ", " << x2 << ", " << x3 << endl; } Salida: Valor: 5 Valor: 7 Valor: 8 Valor: 20 Valor: 7 Valor: 8 Valores: 20, 7, 8 Solución: class A { public: int x; A (int x) : x(x) {} void valor () { cout << "Valor: " << x << endl; } A& operator+= (const A& a) { x=x+a.x; return *this; }; }; Problema 4) Suponer un sistema de realización de preguntas basadas en menús, en el cual se formulan preguntas a un usuario y éste debe averiguar la respuesta correcta. Por ejemplo, un menú basado en números para dos iteraciones sería del estilo siguiente (“ejemplo 1”): Pregunta: Capital de Francia 1. Londres 2. Roma 3. Paris 4. Caracas Introduce el numero correcto 1 Respuesta incorrecta Pregunta: Capital de Francia 1. Londres 2. Roma 3. Paris 4. Caracas Introduce el numero correcto 3 Respuesta correcta Cada menú corresponde a un estilo distinto. Por ejemplo, otro caso sería el estilo correspondiente a los menús basados en subcadenas, en donde el usuario debe responder con las dos primeras letras de la opción ante las mismas preguntas formuladas con este otro tipo de menú. En este caso el ejemplo (“ejemplo 2”) sería: Pregunta: - Londres - Roma - Paris - Caracas Introduce Pa Respuesta Pregunta: - Londres - Roma - Paris - Caracas Introduce Ca Respuesta Capital de Francia las dos primeras letras de la solucion correcta Capital de Francia las dos primeras letras de la solucion incorrecta a) Implementar este sistema en C++ usando el patrón de “Fábrica Abstracta” correspondiente al siguiente pseudocódigo para el método “main” en el caso de un menú de números, de modo que la ejecución de este método “main” sea el “ejemplo 1” anterior int main(int argc, char* argv[]) { <Creación de una fábrica de menús de números> <Creación por la fábrica de un menú > <Crear la pregunta "Capital de Francia" para el menú> <Crear la opción "Londres" para el menú> <Crear la opción "Roma" para el menú> <Crear la opción "París" para el menú, como la correcta> <Crear la opción "Caracas" para el menú> <repetir dos veces> <Ejecutar la acción “preguntar” en el menú> <Ejecutar la acción “responder” en el menú> <fin de repetición> } En la implementación se deberán utilizar las plantillas “vector” y “string” (para representar colecciones de objetos y caracteres, respectivamente, sin utilizar arrays en ningún caso), liberando la memoria dinámica utilizada. Además se debe cumplir (que es lo que permite este patrón de diseño) que para obtener el “ejemplo 2” en lugar del “ejemplo 1” baste con cambiar la línea de <Creación de una fábrica de menús de números> por la correspondiente de cadenas <Creación de una fábrica de menús de subcadenas>. Solución: #include <iostream> #include <vector> #include <string> using namespace std; class Menu { protected: vector<string> elementos; string solucion; string pregunta; string respuesta; public: void crearPregunta(string s) { pregunta=s; } void crearNuevaOpcion(string s, int correcta=0) { elementos.push_back(s); if (correcta!=0) solucion=s; } void responder () { if (respuesta==solucion) cout << "Respuesta correcta" << endl; else cout << "Respuesta incorrecta" << endl; } virtual void preguntar () = 0; }; class MenuDeNumeros : public Menu { public: virtual void preguntar () { cout << "Pregunta: " << pregunta << endl; for (int i=0; i<elementos.size(); i++) { cout << (i+1) << ". " << elementos[i] << endl; } cout << "Introduce el numero correcto" << endl; int j; cin >> j; respuesta=elementos[j-1]; } }; class MenuDeSubcadenas : public Menu{ public: virtual void preguntar () { cout << "Pregunta: " << pregunta << endl; for (int i=0; i<elementos.size(); i++) { cout << "- " << elementos[i] << endl; } cout << "Introduce las dos primeras letras de la solucion" << endl; string s; cin >> s; for (int j=0; j<elementos.size(); j++) { if (elementos[j].substr(0,2)==s) respuesta=elementos[j]; } } }; class FabricaDeMenus { public: virtual Menu* crearMenu () = 0; }; class FabricaDeMenusDeNumeros : public FabricaDeMenus { public: virtual Menu* crearMenu () { return new MenuDeNumeros(); } }; class FabricaDeMenusDeSubcadenas : public FabricaDeMenus { public: virtual Menu* crearMenu () { return new MenuDeSubcadenas(); } }; int main(int argc, char* argv[]) { FabricaDeMenus* f; // f=new FabricaDeMenusDeNumeros (); f=new FabricaDeMenusDeSubcadenas (); Menu* m=f->crearMenu(); m->crearPregunta("Capital de Francia"); m->crearNuevaOpcion("Londres"); m->crearNuevaOpcion("Roma"); m->crearNuevaOpcion("Paris", 1); m->crearNuevaOpcion("Caracas"); for (int i=0; i<2; i++) { m->preguntar(); m->responder(); } delete m; delete f; return 0; } b) Dibujar el diagrama de clases necesarias para los “ejemplo 1” y “ejemplo 2”. Solución: Menu #elementos: vector<string> #solucion: string #pregunta: string #respuesta +crearPregunta(s:string): void +crearNuevaOpcion(string,correcta:int): void +responder(): void +preguntar() FabricaDeMenus +crearMenu(): Menu FabricaDeMenusDeSubcadenas +crearMenu(): Menu FabricaDeMenusDeNumeros MenuDeNumeros +crearMenu(): Menu +preguntar(): void MenuDeSubcadenas +preguntar(): void c) Dibujar el diagrama de secuencia correspondiente al “ejemplo 1” un Usuario new una FabricaDeMenusDe Numeros crearMenu new crearPregunta * crearNuevaOpcion preguntar responder un MenuDeNumeros