Práctica 1
A continuación se encuentra el enunciado de la práctica 1. Lee cuidadosamente el enunciado y sigue las instrucciones. Se recomienda consultar el apartado de Teoría para tratar de resolver dudas de concepto. También puedes consultar el enunciado de la práctica 0, donde puedes encontrar algún ejemplo sobre vectores del Standard Template Library (STL) que podría serte útil en esta práctica.
1 Cambios
1.1 Versión 1.0.0
- Enunciado original.
2 Archivos de partida.
- Aquí se encuentran los archivos de partida. Descárgalos y utilizalos como base para resolver la práctica.
3 Objetivos
- Adquirir práctica y destreza con las características de C++ que lo hacen un mejor C.
- Profundizar en los conocimientos aprendidos en Programación I.
- Hacer uso de las herramientas estudiadas en la práctica 0.
4 Enunciado
Esta práctica consta de un ejercicio en el que trabajarás con una matriz de datos que contendrá el stock de ejemplares de una biblioteca en C++.
La biblioteca tiene varios almacenes (depósitos) y gestiona varios libros (títulos). La matriz representa cuántos ejemplares de cada libro hay en cada almacén:
- Las filas se corresponden con los almacenes.
- Las columnas representan los libros.
Es decir, si tenemos 3 almacenes y 5 libros, data dentro del registro LibraryStock será un puntero que apunte a un array de 3 elementos (los 3 almacenes) y, a su vez, cada uno de esos almacenes será otro puntero que apuntará a la lista de stocks por libro, en este caso a un array de 5 valores.
Emplearemos los siguientes tipos de datos y constantes que se encuentran en p1.h:
/** El tipo de los elementos de la matriz de bajo nivel. */using copies_t = uint32_t;/// El tipo de la fila de la matriz de bajo nivelusing warehouse_t = copies_t*;//! El tipo matriz de bajo nivel.using matrix_t = warehouse_t*;
// Estructura que contiene los datos de la matriz de stock de bibliotecastruct LibraryStock { uint32_t warehouses; uint32_t books; matrix_t data;};
// Constante que representa un dato de tipo LibraryStock que no tiene información// (con la matriz nula y número de almacenes y de libros con el valor 0)const LibraryStock empty_stock = {0, 0, nullptr};Nota: En esta práctica, por simplicidad, el stock en cada celda se codifica como un entero en el rango 0..10 (ambos incluidos).
4.1 Funciones de gestión de los datos.
Para poder implementar las funciones, se recomienda seguir el mismo orden
en el que se encuentran en el fichero p1.h.
Si te fijas, las primeras funciones que aparecen son funciones básicas para
trabajar con el registro LibraryStock: para crear una nueva variable (stock_reserve),
para liberar la memoria reservada una vez que quiera destruir dicha variable (stock_free),
para mostrar la información que contiene (stock_show) y para modificar el stock
de un almacén en un libro concreto (set_copies). Vamos a ver en detalle primero estas funciones
y más adelante veremos las restantes.
/* Funciones para crear un registro de LibraryStock utilizando memoria dinámica para construir los datos de acuerdo a la cantidad de almacenes y libros. Inicializa los valores con el parámetro v, cuyo valor por defecto es 0.*/LibraryStock stock_reserve(uint32_t warehouses, uint32_t books, copies_t v = 0);
// Libera la memoria creada con memoria dinámicavoid stock_free(LibraryStock& s);
// Visualiza en pantalla los datos del stock en forma de matriz.// Las filas son los almacenes y las columnas los libros.void stock_show(const LibraryStock& s);
// Función para modificar el stock de un almacén para un libro concretovoid set_copies(LibraryStock& s, uint32_t warehouse_idx, uint32_t book_idx, copies_t new_copies);Veamos lo que tienen que hacer estas funciones:
-
LibraryStock stock_reserve(uint32_t warehouses, uint32_t books, copies_t v = 0):- Crea y devuelve un registro de tipo LibraryStock que contiene una matriz con el stock de la biblioteca.
datacontendrá una matriz con un número de filas igual al número de almacenes (warehouses) y un número de columnas igual al número de libros (books). Fíjate quedataes de tipomatrix_t, que a su vez es de tipowarehouse_t\*. Es decir,dataserá un puntero que apuntará a un array de tipowarehouse_t, y a su vez, cada uno de los elementos de ese array será otro puntero que apuntará a un array decopies_t, que contendrá el stock del almacén para todos los libros.- Para rellenar los datos de data, necesitarás trabajar con memoria dinámica, utilizando el operador
new. - Si el número de almacenes o de libros solicitado es
0, entonces no debes reservar memoria y debes devolverempty_stock(definido enp1.h). - Ten en cuenta que si por ejemplo tienes
warehouses = 5ybooks = 0, se devolverá empty_stock ya que no se ha podido generar la matriz data. En este caso, tantowarehousescomobooksdentro del nuevo registro creado tendrán el valor 0, por coherencia conempty_stock. - La función inicializará todos los valores de la matriz con el valor proporcionado por el parámetro v, que es opcional y su valor por defecto es 0. Debes comprobar que
vtiene un valor entre0y10(ambos incluidos). Sivno tiene un valor correcto, entonces se devolveráempty_stock.
-
void stock_free(LibraryStock& s);:- Libera la memoria dinámica de la matriz
datadentro del parámetro que se recibe por referencias. Cuando termine la función,sdebe de ser comoempty_stock, es decir,datatendrá el valornullptry los atributoswarehousesybookstendrán el valor 0.
- Libera la memoria dinámica de la matriz
-
void stock_show(const LibraryStock& s);:- Muestra la matriz
dataque contienespor pantalla como tienes en los ejemplos más abajo. Si la matriz está vacía, no se mostrará nada.
- Muestra la matriz
-
void set_copies(LibraryStock& s, uint32_t warehouse_idx, uint32_t book_idx, copies_t new_copies);:- Modifica el stock de un almacén
warehouse_idxpara un libro específicobook_idx. Estos dos parámetros contienen la posición donde se encuentra el almacén y el libro dentro de la matriz. La primera posición será la0. - Si se reciben índices que no existen dentro de la matriz o un valor en
new_copiesque no está entre0y10, no se debe realizar ninguna modificación ni mostrar ningún mensaje en pantalla.
- Modifica el stock de un almacén
4.2 Funciones de cálculo.
Se nos pide implementar tres funciones que utilizarán los datos de la matriz para calcular promedios y también para filtrar los datos seleccionando unos pocos. En esta parte de la práctica, trabajarás con vectores.
Al igual que con las funciones anteriores, en p1.h tienes el prototipo de las funciones que tienes que implementar:
// Función que filtra el stock creando un nuevo registro con una copia de los datos// para determinados almacenes y libros.LibraryStock stock_filter(const LibraryStock& s, const std::vector<uint32_t> &warehouse_idxs, const std::vector<uint32_t> &book_idxs);
// Función que devuelve el promedio del stock por libro (columnas) en forma de vector.std::vector<copies_t> average_per_book(const LibraryStock& s);
// Función que devuelve el promedio del stock por almacén (filas) en forma de vector.std::vector<copies_t> average_per_warehouse(const LibraryStock& s);Y su funcionamiento es el siguiente:
LibraryStock stock_filter(const LibraryStock& s, const std::vector<uint32_t> &warehouse_idxs, const std::vector<uint32_t> &book_idxs);:-
Esta función filtrará los datos de la matriz para seleccionar solo los datos de algunos almacenes y libros indicados por los parámetros de la función.
-
El objetivo de esta función es construir un nuevo registro
LibraryStocka partir de los datos del parámetros, y de los vectoreswarehouse_idxsybook_idxs, que representan los índices de los almacenes y de los libros de los cuales se quiere obtener la información. La idea es que la función devuelva un registro nuevo que contendrá únicamente los datos de los almacenes en las posiciones indicadas porwarehouse_idxsy de los libros en las posiciones indicadas porbook_idxs. -
Si se da el caso de que todos los índices de almacenes y libros existen dentro del parámetro
s, entonces la nueva matriz tendrá un número de filas equivalente al tamaño dewarehouse_idxsy un número de columnas equivalente al tamaño debook_idxs. -
Si hay algún índice de almacenes o de libros en
warehouse_idxsobook_idxsque no exista dentro des, se ignorará ese índice y se pasará al siguiente. -
Si se da el caso de que alguno de los vectores
warehouse_idxsobook_idxsno contiene ningún índice válido, entonces se asume que el nuevo registro no se puede rellenar, por lo que el contenido del registro estará vacío. Esto quiere decir que la matrizdatano tendrá datos (tendrá el valornullptr) y el número de almaceneswarehousesy de librosbooksserá0, por lo tanto su contenido será como el que tieneempty_stock, definido enp1.h. Debes devolver lo mismo si tras comprobarlo, se obtienen0almacenes o0libros. -
La primera posición será la 0 en los índices recibidos por parámetro.
-
Recuerda que los almacenes se representan por filas y los libros por columnas dentro de
data. -
Si la función recibe índices repetidos, se deben ignorar los duplicados y sólo utilizar el índice la primera vez que aparece en el vector. Esto aplica tanto a
warehouse_idxscomo abook_idxs. Hay muchas formas de hacer esto, pero aquí tienes una pista de una posible solución:- Pista: puedes declarar un nuevo vector (inicialmente vacío) donde copiar los valores que se encuentran en el vector original, copiandolos únicamente si no se han copiado antes a ese nuevo vector. Si lo haces así, puede serte útil crear una función simple que compruebe si existe o no un índice dentro de un vector. Recuerda no mezclar índices de libros con índices de almacenes.
-
Veamos un ejemplo (tienes más en mainp1.cc). Supongamos este ejemplo con 3 almacenes y 4 libros:
[ 5 3 6 8 ][ 7 10 8 9 ][ 3 8 1 4 ]Si usamos la función stock_filter pidiendo los almacenes 0 y 2 (en el vector warehouse_idxs) y los libros 1 y 3 (en el vector book_idxs), el resultado sería:
[ 3 8 ][ 8 4 ]Si usamos la función stock_filter pidiendo los almacenes 0, 2 y 21 (en el vector warehouse_idxs) y los libros 1, 3 y 122 (en el vector book_idxs), el resultado sería exactamente el mismo (se ignoran índices inválidos).
Si usamos stock_filter pidiendo el almacén 21 (en el vector warehouse_idxs) y los libros 1 y 3 (en el vector book_idxs), el resultado tendŕia el mismo contenido que empty_stock porque no hay ningún índice correcto de almacenes.
Si usamos stock_filter pidiendo un almacén válido (por ejemplo el 2) y los índices de libros no son válidos (por ejemplo 200, 300 y 400), el resultado tendrá el mismo contenido que empty_stock porque no hay ningún índice correcto de libros.
Por otro lado, si utilizamos stock_filter pidiendo los almacenes 0, 2, 2, 1 (en el vector warehouse_idxs) y los libros 1, 1 y 3 (en el vector book_idxs), el resultado sería:
[ 3 8 ][ 8 4 ][ 10 9 ]-
std::vector<copies_t> average_per_book(const LibraryStock& s);:-
Calculará el promedio del stock por cada libro (por columna), devolviendo un vector de tamaño igual al número de libros.
-
Si el stock no tiene almacenes o libros, devolverá un vector vacío (sin elementos).
-
Nota: como
copies_tesuint32_t, el resultado no tendrá valor decimal y se truncará (no se aproximará).
-
-
std::vector<copies_t> average_per_warehouse(const LibraryStock& s);:-
Calculará el promedio del stock por almacén (por fila), devolviendo un vector de tamaño igual al número de almacenes.
-
Si el stock no tiene almacenes o libros, devolverá un vector vacío (sin elementos).
-
También se trunca por ser entero.
-
4.3 Ejemplo de mainp1.cc.
Tienes un programa de ejemplo en mainp1.cc y su salida debería ser:
Matriz de stock completa:[ 7 5 9 6 ][ 4 8 6 7 ][ 9 6 5 8 ]
Promedios por libro:Libro 0: 6Libro 1: 6Libro 2: 6Libro 3: 7
Promedios por almacén:Almacén 0: 6Almacén 1: 6Almacén 2: 7
Matriz filtrada (almacenes 0 y 2, libros 1 y 3):[ 5 6 ][ 6 8 ]Consejo 1: No te conformes con el main que dispones en los archivos de partida. Asegúrate de probar tu código tanto como puedas.
Puedes modificar la función main del fichero mainp1.cc tanto como necesites para incluir todas las pruebas que consideres oportunas. No se utilizará en la evaluación (solo se utilizará p1.cc).
Consejo 2: cuando pruebes tu código, diseña las pruebas sin mirar tu implementación. Diseñalas solo mirando la especificación del enunciado.
5 Estructura del proyecto.
-
En programación es muy común que los proyectos tengan varios ficheros que separen ciertas partes del código.
-
Por un lado, los archivos
.cc, que incluyen la lógica del programa y la implementación de las funciones. La función main está separada en un fichero apartemainp1.cc. Por otro lado, cada fichero de implementación (excepto mainp1.cc) tendrá un fichero con extensión.hque incluirá la interfaz del código implementado en el archivo.cccon mismo nombre. -
Aclaración sobre la estructura: imagínate el siguiente supuesto: Has implementado unas funciones que trabajan con fechas en
fecha.ccy necesitas invocarlas desde otro fichero de código diferentemain.cc. Para poder hacerlo, necesitas declarar la interfaz (las funciones y/o tipos de datos y/o constantes que queremos que sean públicos y visibles desde fuera defecha.cc). Esto se hace enfecha.h. Después, para utilizarla desdemain.ccsolo necesitamos añadir la instrucción\#include"fecha.h". Esto lo puedes ver en esta práctica, en los ficherosp1.hymainp1.cc.
En este caso, la estructura es la siguiente:
irp2-p1.tgz└── irp2-p1 ├── mainp1.cc (incluye la función principal. Puedes modificarlo para probar tu código. No se evaluará.) ├── p1.h (no puedes modificarlo) ├── p1.cc (aquí implementarás tu código. Solo se evaluará este fichero.) └── Makefile (sirve para configurar la herramienta make. No debes modificarlo)-
Tendremos un único archivo de cabecera (
p1.h) con los prototipos de las funciones además de los\#includenecesarios para que no de errores de compilación. No debes modificarlo. -
Este archivo hará uso de la guarda para protegerlo de su inclusión más de una vez que hemos visto en el tema-1.
-
Tendremos un único archivo de implementación (
p1.cc) en la que debes implementar todas las funciones que se piden y añadir los\#includenecesarios para que no haya errores de compilación (también incluirá ap1.h). Este archivo no contendrá ningún programa principal (main) pero sí podrá contener las funciones auxiliares que necesites. -
Para poder probar tus funciones puedes modificar la función main que tienes en el archivo
mainp1.cc(el cual incluirá ap1.h) y compilarlo y enlazarlo con el anterior así:g++ -g -Wall --std=c++17 mainp1.cc p1.cc -o p1. -
También puedes emplear la herramienta
makepara compilar y enlazar ya que dispones del ficheroMakefileque la configura. Solo necesitas escribir en consolamake. -
Solo se evaluará p1.cc en esta práctica, pero el archivo entregado puede tener más ficheros. El resto de ficheros no se utilizarán en la evaluación. Lo importante es que esté
p1.cc. -
Todos los archivos
.hy.ccse encontrarán dentro de una carpeta llamadairp2-p1.
6 Uso de make.
El fichero makefile incluye la configuración de la herramienta make con varias opciones que podrían serte útiles. A continuación tienes una lista de las cosas que puedes hacer:
| Comando | Descripción |
|---|---|
make | Compila y enlaza el código generando el ejecutable. |
make run | Ejecuta la práctica (deberías compilarla primero con make) |
make runv | Ejecuta la práctica activando Valgrind para detectar fugas de memoria. |
make vstats | Ejecuta la práctica activando Valgrind y el resumen de detección de fugas de memoria. |
make debug | Ejecuta la práctica en modo depuración con GDB y el entorno gráfico. |
make tgz | Borra archivos compilados y genera el archivo comprimido irp2-p1.tgz que debes entregar. |
make clean | Elimina los archivos compilados de los dos ejercicios (ejecutables, objetos, etc.). |
-
Si has compilado tu programa y vuelves a compilar sin modificar nada del código,
makedetectará que no necesita compilar nada. Si aún así necesitas compilar, puedes usar primeromake cleany despuésmake. -
Para la ejecución con valgrind necesitas tener instalado el programa
valgrind. Puedes instalarlo en linux así:sudo apt-get install valgrind- Te será muy útil para detectar accesos a memoria fuera de rango (por ejemplo, si tienes un array de 10 elementos e intentas acceder a la posición 10 (ya que va de 0 a 9), o si utilizas memoria dinámica pero luego se te ha olvidado liberarla cuando ya no la necesitas).
7 Entrega. Requisitos técnicos.
Requisitos que tiene que cumplir este trabajo práctico para considerarse válido y ser evaluado (si no se cumple alguno de los requisitos la calificación será cero):
- El archivo entregado se llama
irp2-p1.tgz(todo en minúsculas), no es necesario entregar ningún programa principal, sólo es necesario tu archivop1.ccen una carpeta llamadairp2-p1dentro del comprimidoirp2-p1.tgz. Este archivo lo puedes crear así en la terminal, si te sitúas en la carpeta padre donde tienes la carpetairp2-p1:
tar cfz irp2-p1.tgz irp2-p1
- También puedes emplear el archivo
Makefileque tienes en la carpeta de la práctica para generar el fichero de la entrega tal y como hemos visto en clase de prácticas situandote en la terminal dentro de la carpeta que contiene el ficheroMakefile:
make tgz.
-
Una vez comprimido, ábrelo y asegúrate de que tiene la estructura que se pide.
-
Al descomprimir el archivo
irp2-p1.tgzse crea un directorio de nombreirp2-p1(todo en minúsculas). -
Dentro del directorio
irp2-p1estará al menos el archivop1.cc(todo en minúsculas). -
Recuerda que el fichero
p1.hno se debe modificar, de lo contrario tus ficheros.ccno compilarán con el corrector. No es necesario entregarlos, pero si se hace no pasa nada. -
Las clases, métodos y funciones implementados se llaman como se indica en el enunciado (respetando en todo caso el uso de mayúsculas y minúsculas). También es imprescindible respetar estrictamente los textos y los formatos de salida que se indican en este enunciado.
-
Al principio de todos los ficheros fuente (
.hy.cc) entregados y escritos por ti se debe incluir un comentario con el nombre y el NIF (o equivalente) de la persona que entrega la práctica, como en el siguiente ejemplo:
// NIF: 12345678-Z// NOMBRE: GARCIA PEREZ, LAURA-
Un error de compilación/enlace implicará un cero en esa parte de la práctica.
-
Se utilizará
valgrindpara comprobar que no haya fugas de memoria en tu programa. Puedes probarlo antes de realizar la entrega para asegurarte de corregir posibles errores relacionados con la gestión de la memoria. -
Lugar y fecha de entrega : según se publique en la plataforma de prácticas.
8 Detección de plagios/copias.
- IMPORTANTE:
- Cada práctica debe ser un trabajo original de la persona que la entrega.
- En caso de detectarse copia se tomarán las medidas disciplinarias correspondientes.