Ejemplo

Listado de notas

Descripción del programa

Con el programa se desea realizar una sencilla aplicación que permita a un profesor almacenar en un fichero el DNI de los alumnos y sus notas en un examen.

El formato del fichero es:

DNI1 Nota1

DNI2 Nota2

El campo DNI será un string, como por ejemplo 13132654A y el campo Nota un double, como por ejemplo 6.7.

En definitiva, el formato consiste en una sucesión alternada de campos string y double separados por espacios en blanco.

Como es lógico, la aplicación permitirá ser utilizada en diferentes instantes de tiempo. Por ello, cada vez que el profesor introduzca una nueva nota:

  • se abrirá el fichero con las notas ya introducidas

  • añadirá la nueva nota a las ya existentes

  • salvará a disco cerrando el fichero

Además de almacenar las notas, el programa permitirá calcular alguna propiedad estadística simple de las notas introducidas hasta ese momento. En el ejemplo, la peor nota, la mejor y la media.

El programa no verificará la validez del DNI. Una aplicación comercial lo haría. Tampoco comprueba si el DNI de un alumno ya se encuentra almacenado y, por tanto, o el profesor ha cometido un error o quizás desea alterar la nota.

La aplicación exigirá que la nota esté en el intervalo [0,10].

Diseño del programa

El programa comienza solicitando el nombre del fichero donde se almacenan alumnos y notas.

Un menú permitirá elegir entre 2 opciones:

  • Introducir nueva nota

    Se abrirá el fichero para escritura usando el modo ios::app. Así podremos añadir la nueva nota al final del mismo.

  • Calcular estadísticas

    Se abrirá el fichero para lectura

Los prototipos de las funciones son los siguientes:

  • Introducción del nombre del fichero:

    string introduce_nombre_fichero();
    
  • Menú con las opciones:

      1. Salir del programa

      1. Introducir nota

      1. Listado y estadísticas

int menu();
  • Introducción de una nota

    void introduce_nota(const string& nom_fich);
    
  • Listado y estadísticas

    void calcula_estadisticas(const string& nom_fich);
    

    Esta función invocará a otra que lee el fichero y carga en dos vectores los DNI y las notas. Para ello se usará una función con prototipo:

    void lee_notas(const string& nom_fich, vector<string>& dnis,
                   vector<double>& notas);
    

    Para mostrar por pantalla los resultados, se utilizará una función con signatura:

    void listado(const vector<string>& dnis, const vector<double>& notas,
                 size_t indice_minima, size_t indice_maxima, double media);
    

La función introduce_nombre_fichero()

Se limita a devolver un string con el nombre del fichero.

string introduce_nombre_fichero()
{
   cout << "Nombre con extension del fichero de notas: ";
   string nombre_fichero;
   cin >> nombre_fichero;
   return nombre_fichero;
}

La función menu()

A estas alturas del curso, esta función no tiene mayor misterio. Una posible implementación es:

int menu()
{
   int opcion;
   do
   {
      cout << "Pulse 0 para salir del programa\n"
           << "Pulse 1 para introducir nota\n"
           << "Pulse 2 para listado y estadísticas\n";
      cin >> opcion;
   }
   while (opcion < 0 || opcion > 2);
   return opcion;
}

La función introduce_nota()

La función abre el fichero en modo escritura y para añadir datos. Tras solicitar el DNI y la nota, estos añaden al final del fichero.

void introduce_nota(const string& nombre_fichero)
{
   cout << "DNI con letra de control del alumno: ";
   string dni;
   cin >> dni;
   double nota;
   do
   {
      cout << "Nota(0 al 10): ";
      cin >> nota;
      if (nota < 0 || nota > 10)
         cout << "\n\nERROR: La nota no es válida.\n\n";
   }while (nota < 0 || nota > 10);

   ofstream fichero(nombre_fichero, ios::app);
   if (!fichero)
   {
      cout << "Error, "<< nombre_fichero
           << " no pudo abrirse para escritura.\n";
      exit(EXIT_FAILURE);
   }
   fichero << dni << " " << nota << endl;
}

Nótese el uso del modo ios::app. Para respetar el formato, en la línea:

fichero << dni << " " << nota << endl;

se separan el DNI y la nota por un espacio en blanco, " " y se introduce una nueva línea, endl.

La función lee_notas()

La función pasa por referencia los vectores donde se almacenarán los DNI y las notas. Dado que el formato no informa del número de alumnos, se usa un bucle while para la lectura.

void lee_notas(const string& nombre_fichero, vector<string>& dnis, vector<double>& notas)
{
   ifstream fichero(nombre_fichero);
   if (!fichero)
   {
      cout << "Error, "<< nombre_fichero
           << " no pudo abrirse para lectura.\n";
      exit(EXIT_FAILURE);
   }

   string dni;
   while (fichero >> dni)
   {
      dnis.push_back(dni);
      double nota;
      fichero >> nota;
      notas.push_back(nota);
   }
}

La condición de salida del bucle while es fichero >> dni, dado que cuando se llegue al final del fichero es el primer dato que al intentar ser leído dará error.

La función listado()

Es la función encargada de mostrar los resultados. Se ha usado el tipo size_t pero hubiese sido perfectamente válido utilizar int.

void listado(const vector<string>& dnis, const vector<double>& notas,
             size_t indice_minima, size_t indice_maxima, double media)
{
   cout << "******************************************\n";
   for (size_t i = 0; i < dnis.size(); ++i)
      cout << dnis[i] << " " << notas[i] << endl;

   cout << "******************************************\n";
   cout << "Nota minima: " << dnis[indice_minima]
      << " con nota " << notas[indice_minima] << endl;
   cout << "Nota maxima: " << dnis[indice_maxima]
      << " con nota " << notas[indice_maxima] << endl;
   cout << "Nota media: " << media << endl;
   cout << "******************************************\n";
}

La función calcula_estadisticas()

Tras leer el fichero y crear los vectores de DNI y notas, se verifica que el fichero no estaba vacío. Posteriormente se calculan las estadísticas y, finalmente, se listan por pantalla.

void calcula_estadisticas(const string& nombre_fichero)
{
   vector<string> dnis;
   vector<double> notas;
   lee_notas(nombre_fichero, dnis, notas);

   if (dnis.empty())
   {
      cout << "\n\nEl fichero esta vacío.\n\n";
      return;
   }

   size_t indice_minima = 0, indice_maxima = 0;
   double nota_minima = notas[0];
   double nota_maxima = notas[0];
   double media = 0.;

   for (size_t i = 0; i < dnis.size(); ++i)
   {
      if (notas[i] < nota_minima)
      {
         nota_minima = notas[i];
         indice_minima = i;
      }
      else if (notas[i] > nota_maxima)
      {
         nota_maxima = notas[i];
         indice_maxima = i;
      }
      media += notas[i];
   }
   media /= dnis.size();

   listado(dnis, notas, indice_minima, indice_maxima, media);
}

La función main()

Nótese como el uso de funciones hace que, con un simple vistazo, la función main() nos apunte claramente el objeto y la estructura del programa.

int main()
{
   string nombre_fichero = introduce_nombre_fichero();
   while (int opcion = menu())
      if(opcion == 1)
         introduce_nota(nombre_fichero);
      else
         calcula_estadisticas(nombre_fichero);
}

Edita, compila y ejecuta el código

Sugerimos al alumno que incorpore al programa nuevas funcionalidades, tales como:

  • permitir alterar la nota de un alumno introduciendo su DNI

  • verificar que, al introducir una nueva nota, el DNI de un alumno no esté en el fichero. Esto evitará posibles despistes del profesor.

  • obtener un listado de los DNI y las notas, ordenado según las notas de menor a mayor

  • obtener un listado de los DNI y las notas, ordenado según los DNI de menor a mayor