Em C++, as declarações estão muitas vezes separadas das definições. As declarações agrupam-se em ficheiros de cabeçalho, e as respetivas implementações ficam em ficheiros de código-fonte. Podes pensar nos ficheiros de cabeçalho como uma API. O ficheiro de cabeçalho diz-te o que um projeto de código tem para oferecer, sem entrar nos detalhes de como.
A extensão de ficheiro mais comum para ficheiros de cabeçalho é .h.
Alguns projetos usam .hpp ou dispensam completamente a extensão.
As definições ficam num ficheiro .cpp à parte.
Para reunir as partes, o ficheiro de código-fonte começa por incluir o respetivo ficheiro de cabeçalho.
Se quiseres escrever uma biblioteca chamada "quick_math" que oferece uma função "super_root" que queres usar com frequência, os ficheiros ficariam assim:
// 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;
}
Se precisares de incluir um cabeçalho que só é necessário para a implementação, a respetiva linha #include só é precisa no ficheiro de código-fonte.
Tudo o que está incluído no cabeçalho também fica disponível no ficheiro .cpp, como a biblioteca string no exemplo abaixo.
Atenção: o ; é necessário depois da declaração no ficheiro de cabeçalho, mas não depois da definição no ficheiro de código-fonte.
Muitos exercícios de C++ no Exercism começam com dois ficheiros quase vazios: o cabeçalho e o código-fonte.
Tens de verificar o ficheiro *_test.cpp para conhecer os nomes e os namespaces das funções esperadas, de modo a resolver o exercício.
As classes podem tornar-se muito complexas e a sua relação com a divisão entre cabeçalho e código-fonte pode ser confusa. Uma organização possível é manter todos os detalhes de implementação no ficheiro de código-fonte e todas as declarações e variáveis membro no cabeçalho:
// 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;}
Quando o cabeçalho serve de visão geral da API, é aí que alguém procuraria informações como os valores predefinidos.
O valor predefinido do parâmetro size do construtor é, por isso, tratado no cabeçalho e não na implementação.
As definições no ficheiro de código-fonte são prefixadas com o namespace robots e o tipo de classe Flower.
Outra opção de organização é uma biblioteca header only, que não tem sequer um ficheiro .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;}
};
}
Os projetos podem usar combinações destas organizações e há muita discussão sobre qual será a mais adequada para cada caso de uso.
Deves ter reparado na linha #pragma once no ficheiro de cabeçalho de exemplo acima.
Isto chama-se uma guarda de inclusão e garante que o conteúdo do ficheiro é incluído apenas uma vez durante a compilação, para evitar erros.
Existe outra variante, mais complexa, de uma guarda de inclusão, que começa com #ifndef e termina com #endif.
Tem o mesmo propósito e o seu uso é mostrado no exemplo da classe Flower acima.
Começas o teu primeiro dia numa empresa australiana chamada "Doctor Data", especializada em recuperação de informação. Arrasaste na entrevista de emprego graças aos teus conhecimentos de C++ e de sondas de von Neumann. Como já viste muitos ficheiros de testes, o teu novo chefe quer que recries os respetivos ficheiros fonte e de cabeçalho a partir de algum código de teste que a empresa recuperou recentemente.
Neste exercício, vais recriar ficheiros perdidos. Atualmente, o conteúdo dos ficheiros é lixo inutilizável.
O fluxo de trabalho deste exercício de conceito é muito semelhante à estrutura dos exercícios de prática do Exercism. O texto de introdução do exercício é apenas uma parte da especificação. O ficheiro de testes é o teu guia definitivo para resolver um determinado problema. Devido à forma como a compilação de C++ funciona, os resultados dos testes podem não aparecer na interface web enquanto não tiveres implementado uma versão mínima de todas as classes, funções e enumerações exigidas pelos testes. Se receberes erros de compilação, estes podem desaparecer depois de teres tratado de todas as tarefas e testes.
Tens quatro tarefas, todas relacionadas com a recuperação do código perdido nos ficheiros de cabeçalho e fonte.
Olhas para duas linhas recuperadas dos ficheiros de testes:
heaven::Vessel bob{"Robert Johansson", 1};
heaven::Vessel will{"Riker", 2, star_map::System::BetaHydri};
O teu olhar atento reconheceu imediatamente um namespace heaven.
Vês também que deve haver uma classe chamada Vessel.
Pelos vistos, o construtor pode ser chamado com dois ou três argumentos.
O primeiro argumento parece ser do tipo string e o segundo é um número.
É possível inicializar a classe Vessel com um terceiro argumento.
O terceiro argumento vem de um namespace star_map.
É um enumerador do tipo System.
Até já descobriste uma das enumerações: BetaHydri.
Prepara os ficheiros fonte e de cabeçalho com a informação que descobriste.
Precisas de dois namespaces: heaven e star_map.
O namespace heaven tem uma classe Vessel, que pode ser chamada com dois ou três argumentos.
O enum System deve ser colocado no namespace star_map e tem uma enumeração BetaHydri.
Fica atento a outras enumerações, para poderes atualizar constantemente o enum System.
Desenterras mais linhas do teste:
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);
As linhas de teste que acabaste de encontrar revelam outra função membro da classe Vessel: replicate.
Esta função recebe uma string e devolve outra instância de Vessel.
Ficas com uma ideia do valor predefinido do terceiro argumento do construtor da tarefa anterior.
Vês também duas variáveis membro públicas da classe Vessel e a especificação da função membro replicate.
Adiciona a função replicate e as variáveis membro públicas current_system e generation aos ficheiros de cabeçalho e fonte.
Encontras mais algumas linhas interessantes nos ficheiros de testes 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);
Ao que parece, a classe Vessel tem uma variável membro busters, que pode ser alterada com as duas funções membro da classe make_buster e shoot_buster.
Enquanto não surge mais informação, arriscas um palpite de que a função make_buster devolve void.
Como há um teste para o valor devolvido de shoot_buster, assumes que a função devolve um bool.
Adiciona as duas funções e a variável membro à classe Vessel.
Continua à procura de outros enumeradores de System.
Enquanto analisas os ficheiros de testes, encontras apenas duas secções de código por descobrir:
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));
Vês duas funções que não são membros da classe Vessel, pois não são chamadas com uma instância.
get_older_bob compara duas instâncias de Vessel e devolve uma string.
in_the_same_system compara duas instâncias de Vessel e devolve um bool.
Implementa as últimas funções em falta a partir das linhas recuperadas acima.
Inscreve-te no Exercism para aprenderes e dominares C++ com 19 conceitos100 exercícios, e mentoria humana real, tudo grátis.