Práctica 0 | P2 GIR Saltearse al contenido

Práctica 0

A continuación se encuentra el enunciado de la práctica 0. Lee cuidadosamente el enunciado y sigue las instrucciones. Se recomienda consultar el apartado de Teoría para tratar de resolver dudas de concepto.

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.

Son varios los objetivos de esta práctica 0:

  1. Habituarnos al estilo de los enunciados de las prácticas, p.e. observa que al principio del mismo hay una etiqueta de versión: 1.0.0, la cual cambiará si se corrige algún fallo o se modifica por algún motivo el enunciado.

    De este modo siempre sabrás con qué versión del enunciado estas trabajando.

  2. Conocer el formato de entrega de prácticas.

  3. Probar el sistema de entregas en pracdlsi y asegurarnos de que podemos entregar prácticas allí sin problemas.

  4. Aprender a usar algunas herramientas muy útiles a la hora de programar:

  5. Repasar algunos conceptos de Programación I.

  6. Hacer uso de tipos de datos numéricos enteros de tamaño fijo.

  7. Conocer algunas de las nuevas características que nos ofrece C++ (el lenguaje de programación que usaremos en modo C++-17).

  8. Dadas los objetivos anteriores y características especiales de esta práctica no se realizará corrección de la misma.

4 Enunciado.

  • Esta práctica consiste en una colección de ejercicios que se describen a continuación.

  • Deben aparecer todos en el mismo fichero fuente, p0.cc, y en dicho fichero no debe haber una función main en el momento de la entrega. Este fichero se debe poder compilar con la orden make y un fichero Makefile.

  • Puedes crear un programa principal de prueba en un fichero aparte (mainp0.cc). Para compilar este programa principal con el fuente p0.cc puedes utilizar esta orden: ‘g++ -g -Wall --std=c++17 mainp0.cc p0.cc -o p0’, pero lo lógico es que prepares un archivo Makefile para que realice compilación separada.

  • Las opciones empleadas en la llamada al compilador son:

    • -g : Está relacionada con el uso del depurador a nivel de código fuente.

    • --std=c++17 : Hace que el compilador funcione en modo C++17.

    • -Wall : Activa la generación de un gran número de advertencias. No son fallos de compilación pero debes prestar atención a lo que te dicen ya que pueden dar lugar a errores en tiempo de ejecución.

  • La corrección de la práctica se realizará probando cada ejercicio por separado, añadiendo para ello una función main (en otro fichero fuente, parecido al mainp0.cc).

5 Herramientas.

5.1 Make

  • Make lee su configuración de un archivo de texto normalmente llamado Makefile.
  • En estos archivos encontraremos (entre otras cosas) una colección de reglas:
    objetivo : dependencia1 dependencia2 ... dependenciaN
    (TAB) orden1
    (TAB) orden2
    ...
    (TAB) ordenN
  • Los objetivos suelen ser archivos que generamos con las órdenes asociadas a cada regla, pero también pueden ser otros objetivos definidos en el archivo Makefile.
  • Se pueden ejecutar objetivos específicos del Makefile indicándolo en el momento de usarlo.
    • Si escribes make en la terminal, se compilará el programa.
    • Si escribes make tgz, se ejecutarán las instrucciones pertinentes para generar un archivo comprimido tgz. Este lo puedes utilizar para obtener el archivo de entrega.
    • Si escribes make run, se ejecutará el programa que previamente has compilado con el comando make.
    • Si escribes make clean, se borrarán los ficheros generados para la compilación (principalmente los ficheros .o y el ejecutable).
  • Puedes descargar un ejemplo de un programa para probar make con varios ficheros de código fuente y un makefile.
    • Primero ejecuta el comando make en la consola para compilar el programa de ejemplo.
    • Prueba a modificar el fichero utilidades.cc, añadiendo por ejemplo un espacio en blanco. Después vuelve a ejecutar make en la consola. ¿Observas alguna diferencia con la compilación anterior?
    • Repite lo mismo con utilidades.h.
      • ¿Por qué crees que se compilan archivos diferentes en estas pruebas?
      • ¿Qué crees que pasaría si tuvieras que compilar un programa que contiene 100 ficheros y modificamos uno solo? ¿Es necesario compilarlo todo si solo ha cambiado un único fichero con código fuente? ¿Qué ficheros tendrían que compilarse?
    • Puedes comprobar que el comando make tgz no funciona en este caso. Eso es porque no se ha definido tal objetivo en el fichero makefile del código de emeplo. Sin embargo, si descargas los archivos de partida de la práctica 0, encontrarás un archivo Makefile con ese objetivo ya configurado, y en ese caso sí que podrás utilizarlo.

5.2 Gdb

  • Es un depurador a nivel de código fuente.
  • Nos permite ejecutar nuestra aplicación paso a paso, consultar el valor de variables, tomar el control de ella en cualquier momento.
  • Para que Gdb funcione correctamente necesitamos compilar todo nuestro código con la opción ‘-g’ de g++ o gcc.
  • Funciona en modo texto, pero es muy sencillo.
  • Prueba la interfaz que tiene al lanzarlo con la opción ‘--tui’.
  • Usa gdb así: gdb tu-programa
  • Para salir teclea: q + ENTER o quit + ENTER
  • Disponemos de otros interfaces sobre gdb, p.e.:
    1. Uno en modo gráfico: Nemiver.
    2. Otro en modo texto: cgdb.
  • Veamos un ejemplo.
#include <iostream>
#include <cstring>
using namespace std;
int busca_caracter(char cadena[],char c)
{
int i, posicion=-1;
for (i=0;i<strlen(cadena) || cadena[i]!=c;i++) { // debe ser con '&&'
cout << cadena[i];
}
if (i<strlen(cadena)) posicion=i;
return posicion;
}
int main()
{
int p;
char holamundo[]="hola, mundo";
cout << "---------------------" << endl;
p=busca_caracter(holamundo,'o');
cout << endl << "(o) p=" << p << endl;;
cout << "---------------------" << endl;
p=busca_caracter(holamundo,'u');
cout << endl << "(u) p=" << p << endl;;
cout << "---------------------" << endl;
p=busca_caracter(holamundo,'A');
cout << endl << "(A) p=" << p << endl;;
}

5.3 Valgrind

  • No es una única herramienta, son varias, según lo uses de una manera u otra tienes acceso a una u otra herramienta.

  • Estas herramientas analizan la ejecución de nuestra aplicación.

  • Nosotros lo emplearemos para saber si nuestra aplicación se deja bloques de memoria sin liberar.

  • Es muy útil que compilemos nuestro código con la opción ‘-g’.

  • Ejecuta valgrind con la opción ‘--help’ para saber todas las opciones con las que lo puedes invocar.

  • La forma más sencilla de emplearlo es así: valgrind tu-programa

  • Veamos algunos ejemplos.

int main(int argc, char *argv[])
{
int* p;
int* q = new int;
//*p = 3;
return 0;
}

6 Ayuda de C++.

6.1 Compilador de C++.

  • El compilador de C++ del proyecto GNU se llama: g++.
  • Los ficheros de C++ suelen tener la extensión .cc o .cpp o .C. Nosotros emplearemos siempre .cc.
  • Los archivos de cabecera propios de C++ no tienen extensión .h. Si en algún momento necesitas incluir un archivo de cabecera de C, estos se llaman quitándoles la extensión .h y anteponiéndoles el prefijo c, p.e. math.h pasa a llamarse cmath:
    #include <cmath>
  • Lo usaremos con la opción --std=c++17 para asegurarnos que trabajamos en modo C++17.

6.2 Tipo bool.

  • C++ define el tipo bool en el lenguaje. Son palabras reservadas los valores true y false.
    • Apunte curioso: a diferencia de C++, en C no tenemos un tipo de dato primitivo nativo para valores booleanos. Para utilizarlos (true y false), en C se debe utilizar la librería stdbool.h. No obstante, deberías saber que esos valores realmente son de tipo entero que son representados como 1 y 0, respectivamente. En C++ el tipo bool es un tipo de dato nativo y primitivo, por lo que no necesita de ninguna librería adicional.

6.3 Entrada/salida en C++.

  • En C++ es recomendable usar la E/S proporcionada por los iostreams.

  • Para poder emplear cout debes incluir la cabecera <iostream> y , de momento, llamarlo como en el ejemplo anterior:

    std::cout << "n = " << n << std::endl;
  • O emplear la construcción using namespace std y así poder emplear cout sin el prefijo:

using namespace std;
// Fíjate que ahora ya no hay prefijo 'std::'
cout << "n = " << n << endl;

6.4 Mini tutorial sobre la clase std::string de C++.

  1. Para usar el tipo string debemos incluir la cabecera <string>, de lo contrario es posible que tengamos un error de compilación.

  2. Las cadenas de C++ se pueden concatenar con el operador +, p.e.:
    std::string s = "hola"; s = s + " mundo";

  3. Las cadenas de tipo std::string no acaban en un \0. Si necesitas una cadena de C (acabada en \0) puedes usar el método std::string::c_str así: const char* cstr = s.c_str();.

  4. En esta práctica-0 (salvo en los lugares donde explícitamente se usen cadenas de C) puedes emplear cadenas de C++ si las necesitas.

#include<string>
using namespace std; // Para no usar std::string
// Creación de una cadena vacía, "")
string s;
// Creación de una cadena no vacía)
string s2 = "Programación-II";
// Lectura hasta el final de la línea
getline(cin,s);
// Mostrar por pantalla
cout << s << endl;
s="hola"; // Asignación
// Recorrido, s.length() es la longitud
for (uint i=0u; i < s.length(); i++)
cout << s[i]; // imprimir un carácter
s[3] = 'A'; // Asignación de un carácter
s = s + ','; // concatena un carácter "holA,"
s = s + " mundo"; // concatena una cadena "holA, mundo"
if (s <= "hola") // Comparacion de strings
if (s == "adios")
...

6.5 Mini tutorial sobre la clase std::vector de C++.

  1. Para usar el tipo vector, que es un tipo de contenedor de datos de tamaño dinámico que pertenece al Standard Template Library (STL), debemos incluir la cabecera <vector>, de lo contrario es posible que tengamos un error de compilación.

  2. Para declarar una variable de tipo vector debemos indicar también el tipo de los elementos del vector, esto se hace poniendo ese tipo entre ángulos, p.e: std::vector<float> fv; // vector de numeros reales.

  3. Los vectores de tipo std::vector:

    • Son dinámicos, pueden crecer y decrecer en tiempo de ejecución.

    • Saben cuantas componentes tienen, para ello se emplea la función size que, al igual que con el tipo string se utiliza con la notación: mi_vector.size().

    • Una de las maneras de añadir elementos a un vector por el final es usando la función push_back, como puedes ver en el ejemplo siguiente.

#include <cstdint>
#include <iostream>
#include <vector>
#include <string>
void print_int8_vector(const std::vector<int8_t> &v) {
for (auto &e : v) {
std::cout << "· " << (int)e << '\n';
}
}
void print_string_vector(const std::vector<std::string> &v) {
for (auto &e : v) {
std::cout << "· " << e << '\n';
}
}
int main(int argc, char *argv[]) {
std::vector<int8_t> iv;
std::vector<std::string> sv;
for (int8_t i = 0; i < 10; i++)
iv.push_back(i);
for (int8_t i = 0; i < 15; i++) {
std::string s = "cadena-";
s += std::to_string(i);
sv.push_back(s);
}
std::cout << "vector<int8>: elementos: " << iv.size() << '\n';
print_int8_vector(iv);
std::cout << "vector<string>: elementos: " << sv.size() << '\n';
print_string_vector(sv);
return 0;
}

6.6 Mini tutorial sobre memoria dinámica en C++.

  • Tal y como hemos visto en clase de teoría la gestión de la memoria dinámica (reserva y liberación) corre a cargo del lenguaje y no de funciones de biblioteca como en C ( malloc y free ).

  • La reserva de memoria la realizan los operadores new y new[] mientras que la liberación la hacen los operadores delete y delete[], es decir, cuando reservas memoria con new debes liberarla posteriormente con delete y cuando lo haces con new[] debes liberarla con delete[].

  • Ejemplos:

// iv es un vector de 4 enteros, empleamos 'new[]'
int n = 4;
int* iv = new int[n];
...
// Liberamos la memoria con 'delete[]', los '[]' van vacios
delete[] iv;
// Reservamos un puntero a un caracter, empleamos 'new'
char* cp = new char;
...
// Liberamos la memoria, empleamos 'delete'
delete cp;
  • El lenguaje incorpora el literal nullptr que representa un puntero nulo de cualquier tipo. En C++ no debemos emplear la macro NULL usada en lenguaje C.
    • Es preferible utilizar nullptr porque representa un puntero que apunta a una dirección de memoria nula. En cambio, NULL es de tipo entero y no representa un puntero explícitamente. De hecho, es una constante con el valor entero 0. Si se utiliza NULL, se corre el riesgo de tener alguna ambiguedad en el código que podría causar errores difíciles de encontrar en tiempo de ejecución.
int* v = nullptr;

7 Ejercicio.

  • En este ejercicio puedes crear las funciones auxiliares que necesites. Para ello debes definirlas en el archivo p0.cc, recuerda que los archivos de cabecera entregados como p.e. p0.h no deben ser modificados.

7.1 Extracción de palabras de una frase.

  • Nos piden que devolvamos las palabras que componen una frase. Una cadena vacía no se considera una palabra.

  • La frase se representa como un dato de tipo std::string y el separador entre palabras es un carácter, un dato de tipo char.

  • Para ello nos piden que escribamos el código de una función con el siguiente prototipo

    std::vector<std::string> split_on(const std::string& s, char c);
  • Esta función:

    1. Devuelve como resultado un vector de cadenas en el que cada componente del mismo es una palabra de la frase original.

    2. Si la frase original es una cadena vacía, devuelve un vector con cero componentes. El carácter separador nunca será un carácter vacío.

    3. Si la frase original no contiene ningún carácter separador se considera que toda la frase es una única palabra.

8 División del código fuente en archivos.

  • Tendremos un único archivo de cabecera (p0.h) con los prototipos de las funciones además de los #include necesarios para que no de errores de compilación. Este archivo ya lo tienes en los archivos de partida.

  • Este archivo hace 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 (p0.cc, trabajamos en C++) con la implementación de todas las funciones además de los #include necesarios para que no de errores de compilación (también incluirá a p0.h). Este archivo no contendrá ningún programa principal (main).

  • Para poder probar tus funciones puedes crearte la función main que quieras en otro archivo, p.e. mainp0.cc (el cual incluirá a p0.h) y compilarlo y enlazarlo con el anterior así: g++ -g -Wall --std=c++17 mainp0.cc p0.cc -o p0. No es necesario entregarlo, si se hace no pasa nada.

  • También puedes emplear el archivo Makefile para compilar y enlazar. Recuerda que lo tienes en la carpeta de la práctica.

  • Todos los archivos `.h' y `.cc' se encontrarán dentro de una carpeta llamada irp2-p0.

9 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-p0.tgz (todo en minúsculas), no es necesario entregar ningún programa principal, sólo son necesarios tus archivos `.h' y `.cc' en una carpeta llamada irp2-p0. A su vez, esta carpeta estará comprimida en el fichero llamado irp2-p0.tgz. Este archivo lo puedes crear así en la terminal:

    tar cfz irp2-p0.tgz irp2-p0

  • También puedes emplear el archivo Makefile que tienes en la carpeta de la práctica para generar el fichero de la entrega. Para ello, solo necesitas ejecutar el comando make tgz.

  • Al descomprimir el archivo irp2-p0.tgz se crea un directorio de nombre irp2-p0 (todo en minúsculas).

  • Dentro del directorio irp2-p0 estará al menos el archivo p0.cc (todo en minúsculas).

    Recuerda que el fichero ‘p0.hno se debe modificar, de lo contrario tus ficheros ‘.cc’ no compilarán con el corrector. No es necesario entregarlo, 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 (`.h’ y `.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.

    Aunque esta práctica no cuenta para la nota final y no será evaluada, su entrega es obligatoria y sirve para comprobar que tu usuario para la entrega de prácticas posteriores funciona correctamente y para asegurarte de que has entendido el procedimiento para entregar las prácticas en esta asignatura. Además te permitirá comprobar cuál es tu nivel real de conocimientos de programación adquiridos en Programación-I. Si tienes cualquier problema con la entrega comunícaselo a tu profesor lo antes posible.

  • Lugar y fecha de entrega : Recuerda que las entregas se realizan siempre en (y sólo en) https://pracdlsi.dlsi.ua.es en las fechas y condiciones allí publicadas. Puedes entregar la práctica tantas veces como quieras, sólo se corregirá la última entrega (las anteriores no se borran). El usuario y contraseña para entregar prácticas es el mismo que se utiliza en UACloud.

10 Detección de plagios/copias.

  • En el Grado en Ingeniería Robótica, la Programación es una materia muy importante. La manera más efectiva de aprender a programar es programando y, por tanto, haciendo las prácticas correspondientes además de otros programas (más sencillos o más complicados, eso da igual).

  • Tratar de aprobar las asignaturas de programación sin programar (copiando) es complicado y obviamente, te planteará problemas para seguir otras asignaturas así como en tu futura vida laboral.

  • En una asignatura como Programación-II es difícil que alguien que no ha hecho las prácticas supere los exámenes de teoría o el de recuperación de prácticas.

  • IMPORTANTE:

    • Cada práctica debe ser un trabajo original de la persona que la entrega.

    • En caso de detectarse indicios de copia de una o más prácticas se tomarán las medidas disciplinarias correspondientes, informando a las direcciones de Departamento y de la EPS por si hubiera lugar a otras medidas disciplinarias adicionales.