En C++, las declaraciones suelen estar separadas de las definiciones. Las declaraciones se agrupan en los llamados ficheros de cabecera, y las implementaciones correspondientes se colocan en ficheros fuente. Puedes pensar en los ficheros de cabecera como en una API. El fichero de cabecera te dirá qué tiene que ofrecer un código base sin entrar en los detalles de cómo.
La extensión de fichero más habitual para los ficheros de cabecera es .h.
Algunos proyectos usan .hpp o prescinden por completo de la extensión.
Las definiciones se encuentran en un fichero .cpp aparte.
Para volver a unir las partes, el fichero fuente empieza incluyendo el fichero de cabecera correspondiente.
Si quieres escribir una biblioteca llamada «quick_math» que ofrece una función «super_root» que quieres usar a menudo, los ficheros tendrían este aspecto:
// A file named quick_math.h
#pragma once
namespace quick_math {
double super_root(double x, int n);
}
// A file named quick_math.cpp
#include "quick_math.h"
#include <cmath>
double quick_math::super_root(double x, int n) {
while(n) { x = std::sqrt(x), --n;}
return x;
}
Si necesitas incluir una cabecera que solo requiere la implementación, la línea #include correspondiente solo hace falta en el fichero fuente.
Todo lo que se incluye en la cabecera también está disponible en el fichero .cpp, como la biblioteca string del ejemplo siguiente.
Atención: el ; hace falta después de la declaración en el fichero de cabecera, pero no después de la definición en el fichero fuente.
Muchos ejercicios de C++ en Exercism empiezan con dos ficheros casi vacíos: la cabecera y el fuente.
Tienes que consultar el fichero *_test.cpp para ver los nombres y los espacios de nombres de las funciones que se esperan, y así poder resolver el ejercicio.
Las clases pueden volverse muy complejas, y su relación con la división entre cabecera y fuente puede resultar confusa. Una posible organización es mantener todos los detalles de implementación en el fichero fuente y todas las declaraciones y variables miembro en la cabecera:
// A file named robot_flower.h
#if !defined(ROBOT_FLOWER_H)
#define ROBOT_FLOWER_H
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0);
void give_water();
std::string get_name();
int get_size();
void start_next_day();
};
}
#endif
// A file named robot_flower.cpp
#include "robot_flower.h"
robots::Flower::Flower(std::string name, int size) {this->name = "Robotica " + name; this->size = size;}
void robots::Flower::start_next_day() {if (!needs_water) ++size; needs_water = true;}
std::string robots::Flower::get_name() {return name;}
int robots::Flower::get_size() {return size;}
Cuando la cabecera se usa como vista general de la API, es ahí donde una persona buscaría información como los valores por defecto.
El valor por defecto del parámetro size del constructor se gestiona, por tanto, en la cabecera y no en la implementación.
Las definiciones del fichero fuente llevan como prefijo el espacio de nombres robots y el tipo de clase Flower.
Otra opción de organización es una biblioteca solo cabecera, que no tiene ningún fichero .cpp:
// A file named robot_flower.h
#pragma once
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0) {this->name = "Robotica " + name; this->size = size;}
void give_water() {needs_water = false;}
std::string get_name() {return name;}
int get_size() {return size;}
void start_next_day() {if (!needs_water) ++size; needs_water = true;}
};
}
Los proyectos pueden usar combinaciones de estas organizaciones, y hay mucho debate sobre cuál puede encajar mejor en cada caso de uso.
Es posible que te hayas fijado en la línea #pragma once del fichero de cabecera de ejemplo de arriba.
Esto se llama guarda de inclusión y garantiza que el contenido del fichero se incluya solo una vez durante la compilación, para evitar errores.
Existe otra variante más compleja de guarda de inclusión que empieza por #ifndef y termina en #endif.
Cumple la misma función y su uso se muestra en el ejemplo de la clase Flower de arriba.
Empiezas tu primer día en una empresa australiana llamada «Doctor Data», que se especializa en la recuperación de información. Superaste tu entrevista de trabajo gracias a tus conocimientos de C++ y de las sondas de von Neumann. Como has visto muchos archivos de test, tu nuevo jefe quiere que recrees los correspondientes archivos fuente y de cabecera a partir de parte del código de test que la empresa ha recuperado recientemente.
En este ejercicio vas a recrear archivos perdidos. Actualmente, el contenido de los archivos son datos basura inservibles.
El flujo de trabajo de este ejercicio de concepto es muy similar a la estructura de los ejercicios de práctica de Exercism. El texto de introducción del ejercicio es solo una parte de la especificación. El archivo de test es tu guía definitiva para resolver un problema dado. Debido a cómo funciona la compilación de C++, es posible que los resultados de los tests no aparezcan en la interfaz web hasta que hayas implementado una versión mínima de cada clase, función y enumeración que requieren los tests. Si recibes errores de compilación, es posible que desaparezcan una vez que hayas completado todas las tareas y los tests.
Tienes cuatro tareas, todas relacionadas con recuperar el código perdido en los archivos de cabecera y fuente.
Observas dos líneas recuperadas de los archivos de test:
heaven::Vessel bob{"Robert Johansson", 1};
heaven::Vessel will{"Riker", 2, star_map::System::BetaHydri};
Tu ojo avispado reconoció al instante un espacio de nombres heaven.
También ves que debe de haber una clase llamada Vessel.
Al parecer, se puede llamar al constructor con dos o tres argumentos.
El primer argumento parece ser de tipo string y el segundo es un número.
Es posible inicializar la clase Vessel con un tercer argumento.
El tercer argumento proviene de un espacio de nombres star_map.
Es un enumerador de tipo System.
Incluso conoces una de las enumeraciones: BetaHydri.
Prepara los archivos fuente y de cabecera con la información que has descubierto.
Necesitas dos espacios de nombres: heaven y star_map.
El espacio de nombres heaven tiene una clase Vessel, a la que se puede llamar con dos o tres argumentos.
La enumeración System debe colocarse en el espacio de nombres star_map y tiene una enumeración BetaHydri.
Estate atento a otras enumeraciones para que puedas actualizar constantemente la enumeración System.
Descubres más líneas del test:
heaven::Vessel bob{"Robert Johansson", 1};
REQUIRE(bob.current_system == star_map::System::Sol);
REQUIRE(bob.generation == 1);
heaven::Vessel bob5 = bob.replicate("Mario");
REQUIRE(bob5.current_system == star_map::System::Sol);
REQUIRE(bob5.generation == 2);
Las líneas de test recién encontradas revelan otra función miembro de la clase Vessel: replicate.
Esta función recibe un string y devuelve otra instancia de Vessel.
Te haces una idea del valor predeterminado del tercer argumento del constructor a partir de la tarea anterior.
También ves dos variables miembro públicas de la clase Vessel y la especificación de la función miembro replicate.
Añade la función replicate y las variables miembro públicas current_system y generation a los archivos de cabecera y fuente.
Encuentras algunas líneas más interesantes en los archivos de test recuperados:
heaven::Vessel bob6{"Homer", 3, star_map::System::EpsilonEridani};
REQUIRE(bob6.busters == 0);
bob6.make_buster();
REQUIRE(bob6.busters == 1);
bool success = bob6.shoot_buster();
REQUIRE(success);
REQUIRE(bob6.busters == 0);
success = bob6.shoot_buster();
REQUIRE_FALSE(success);
Al parecer, la clase Vessel tiene una variable miembro busters, que se puede cambiar con las dos funciones miembro de la clase make_buster y shoot_buster.
Hasta que aparezca más información, supones que la función make_buster devuelve void.
Como hay un test para el valor devuelto de shoot_buster, asumes que la función devuelve un bool.
Añade las dos funciones y la variable miembro a la clase Vessel.
Sigue buscando otros enumeradores de System.
Durante tu escaneo de los archivos de test, encuentras solo dos secciones del código sin descubrir:
heaven::Vessel bob1{"Bob", 1, star_map::System::AlphaCentauri};
heaven::Vessel marv{"Marvin", 2, star_map::System::DeltaEridani};
heaven::Vessel milo{"Milo", 3, star_map::System::DeltaEridani};
heaven::Vessel howie{"Howard", 4, star_map::System::Omicron2Eridani};
REQUIRE("Bob" == heaven::get_older_bob(bob1, marv));
REQUIRE(heaven::in_the_same_system(marv, milo));
REQUIRE_FALSE(heaven::in_the_same_system(marv, howie));
Ves dos funciones que no son miembros de la clase Vessel, ya que no se las llama con una instancia.
get_older_bob compara dos instancias de Vessel y devuelve un string.
in_the_same_system compara dos instancias de Vessel y devuelve un bool.
Implementa las últimas funciones que faltan a partir de las líneas recuperadas de arriba.
Regístrate en Exercism para aprender y dominar C++ con 19 conceptos100 ejercicios y mentoría humana real, todo gratis.