Estructuras de datos
El programa tiene que leer dos horas diferentes. Ya tienes dos elmentos del mismo tipo... es un buen momento para crear una estructura de datos:
struct Time
{
int hours;
int mins;
};
¿Y por qué una estructura de datos y no algo que nos devuelva directamente los minutos totales?
Por varias razones:
- Los datos son más fáciles de tratar si están debidamente organizados (es decir, estructurados)
- C++ es orientado a objetos, vamos a aprovechar ese punto.
- Es mi respuesta y la estoy escribiendo yo :)
Bien, seguimos.
Sobrecarga de operadores
Además, las dos horas se leen desde el teclado. Así que no estaría de más proporcionar un mecanismo que almacene una hora en esta estructura. Esto no pretende ser una clase magistral porque entonces sería demasiado larga, así que asumiremos que el usuario va a introducir los valores correctos. Partiendo de esta base podemos sobrecargar fácilmente el operador de extracción de la siguiente manera:
std::istream & operator>>(std::istream& is, Time & time)
{
char sep;
is >> time.hours >> sep >> time.mins;
return is;
}
Lo que hace esta función es leer un entero (la hora), después lee un carácter (el separador), el cual descarta, y finalmente un segundo entero (los minutos). Y sí, esa cosa tan rara es una función, puedes comprobarlo fácilmente con el siguiente código:
Time time;
operator>>(std::cin, time);
std::cout << time.hours << ':' << time.mins;
Si introduces por teclado una hora (horas:minutos
) te la imprimirá por pantalla... aunque claro, los operadores no están pensados para ser usados como funciones. Seguramente este otro ejemplo te parecerá más bonito pese a que a nivel funcional son exactamente iguales:
Time time;
std::cin >> time;
std::cout << time.hours << ':' << time.mins;
El caso es que con lo que hemos visto hasta ahora el inicio del programa ya queda mucho más legible:
Time entrada, salida;
std::cout << "Introduce hora de entrada: \n";
std::cin >> entrada;
std::cout << "Introduce hora de salida: \n";
std::cin >> salida;
Conversión de valores
A continuación vemos que se pide convertir la hora a minutos totales... es decir, otra función. En este caso vamos a aventurarnos a crear una función miembro... por tener variedad:
struct Time
{
int hours;
int mins;
int ToMinutes() const
{ return hours * 60 + mins; }
};
Con esto el inicio del programa ya estaría completo:
Time entrada, salida;
std::cout << "Introduce hora de entrada: \n";
std::cin >> entrada;
std::cout << entrada.ToMinutes() << " minutos\n";
std::cout << "Introduce hora de salida: \n";
std::cin >> salida;
std::cout << salida.ToMinutes() << " minutos\n";
Claro está que esto es C++ y aquí no hay una única forma de resolver los problemas sino cien millones... Otra posibilidad pasaría por aprovechar los operadores de conversión... a mi esta opción no me suele gustar porque es relativamente sencillo meter la pata al programar:
struct Time
{
int hours;
int mins;
operator int() const
{ return hours * 60 + mins; }
};
Con este operador podríamos escribir la siguiente obra gráfica, aberración, engendro, ... cada uno que use el adjetivo que mejor le parezca:
int opcion1 = entrada;
int opcion2 = (int)entrada;
int opcion3 = static_cast<int>(entrada);
std::cout << opcion1 << ' ' << opcion2 << ' ' << opcion3;
El caso es que, como podrás comprobar si pruebas el ejemplo anterior, funciona. Como digo a mi estos operadores no suelen gustarme demasiado, pero existen y es conveniente conocer al menos su existencia.
Operaciones aritméticas
A continuación se hace una operación de resta de las fechas... en este punto podríamos tomar varios caminos, yo destaco dos:
Aprovechar el método ToMinutes
. Si ya tenemos el total de minutos, el siguiente paso podría ser, simplemente, restar ambos totales. Esta sería quizás la opción más recomendable en este caso:
int mins_entrada = entrada.ToMinutes();
int mins_salida = salida.ToMinutes();
int total_mins = mins_salida - mins_entrada;
Crear una función que reste objetos de tipo Time
. Esta opción podría ser más versátil en otros contextos... para este caso concreto sirve más como fines didácticos:
Time operator-(Time const& t1, Time const& t2)
{
Time to_return{t1.hours - t2.hours, t1.mins - t2.mins};
if( to_return.mins < 0 )
{
to_return.mins += 60;
to_return.hours--;
}
return to_return;
}
Time diff = salida - entrada;
int total_mins = diff.ToMinutes();
El caso es que ya tenemos el número de minutos entre las dos horas. El único punto que podría ser conflictivo en este punto es sacar las horas con decimales... pero la verdad es que tampoco tiene demasiada complicación: Basta con recordar que si dividimos un entero entre un float
el resultado de la operación será un float
, es decir, tendrá decimales:
float total_hours = total_mins / 60.f;
Posibles mejoras
Existe una estructura std::tm
que sirve para la gestión de fechas y horas. Normalmente no sale a cuenta reinventar la rueda, así que sería conveniente aprender a lidiar con funciones y clases ya existentes. Queda a elección del lector realizar los cambios oportunos para que el programa utilice std::tm
en vez de la clase Time
.
Conclusiones
Y ya está, ya tenemos el programa terminado, solo falta imprimir los resultados finales.
Puedes ver el código funcionando en wandbox.