Em C++, as declarações costumam ser separadas das definições. As declarações ficam agrupadas nos chamados arquivos de cabeçalho, e as respectivas implementações ficam nos arquivos-fonte. Você pode pensar nos arquivos de cabeçalho como uma API. O arquivo de cabeçalho mostra o que uma base de código tem a oferecer, sem entrar nos detalhes de como.
A extensão de arquivo mais comum para arquivos de cabeçalho é .h.
Alguns projetos usam .hpp ou dispensam a extensão por completo.
As definições ficam em um arquivo .cpp separado.
Para reunir as partes, o arquivo-fonte começa incluindo o arquivo de cabeçalho correspondente.
Se você quiser escrever uma biblioteca chamada "quick_math" que oferece uma função "super_root" que você quer usar com frequência, os arquivos 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 você precisar incluir um cabeçalho que só a implementação exige, a linha #include correspondente só é necessária no arquivo-fonte.
Tudo o que é incluído no cabeçalho também fica disponível no arquivo .cpp, como a biblioteca string no exemplo abaixo.
Atenção: o ; é necessário depois da declaração no arquivo de cabeçalho, mas não depois da definição no arquivo-fonte.
Muitos exercícios de C++ no Exercism começam com dois arquivos quase vazios: o cabeçalho e o arquivo-fonte.
Você precisa conferir o arquivo *_test.cpp para ver os nomes e os namespaces das funções esperadas e resolver o exercício.
As classes podem ficar muito complexas, e a relação delas com a divisão entre cabeçalho e arquivo-fonte pode ser confusa. Uma organização possível é manter todos os detalhes de implementação no arquivo-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 como uma visão geral da API, é ali que a pessoa procura informações como valores padrão.
Por isso, o valor padrão do parâmetro size do construtor fica no cabeçalho e não na implementação.
As definições no arquivo-fonte vêm precedidas do namespace robots e do tipo da classe Flower.
Outra opção de organização é uma biblioteca header only, que não tem nenhum arquivo .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 dessas organizações, e há muita discussão sobre qual seria a melhor para cada caso de uso.
Você deve ter reparado na linha #pragma once no arquivo de cabeçalho de exemplo acima.
Isso é chamado de guarda de inclusão e garante que o conteúdo do arquivo seja incluído apenas uma vez durante a compilação, para evitar erros.
Existe outra variação, mais complexa, de guarda de inclusão, que começa com #ifndef e termina com #endif.
Ela serve ao mesmo propósito, e seu uso aparece no exemplo da classe Flower acima.
Você começa seu primeiro dia em uma empresa australiana chamada "Doctor Data", especializada em recuperação de informações. Você arrasou na entrevista de emprego graças ao seu conhecimento de C++ e de sondas de von Neumann. Como você já viu muitos arquivos de teste, seu novo chefe quer que você recrie os respectivos arquivos de código-fonte e de cabeçalho a partir de um código de teste que a empresa recuperou recentemente.
Neste exercício, você vai recriar arquivos perdidos. No momento, o conteúdo dos arquivos são dados inutilizáveis, puro lixo.
O fluxo de trabalho deste exercício de conceito é muito parecido com a estrutura dos exercícios de prática do Exercism. O texto de introdução do exercício é apenas uma parte da especificação. O arquivo de teste é seu guia definitivo para resolver um determinado problema. Por causa de como a compilação de C++ funciona, os resultados dos testes podem não aparecer na interface web até que você implemente uma versão mínima de cada classe, função e enumeração exigida pelos testes. Se você receber erros de compilação, eles podem sumir depois que você resolver todas as tarefas e todos os testes.
Você tem quatro tarefas, todas relacionadas a recuperar o código perdido nos arquivos de cabeçalho e de código-fonte.
Você olha para duas linhas recuperadas dos arquivos de teste:
heaven::Vessel bob{"Robert Johansson", 1};
heaven::Vessel will{"Riker", 2, star_map::System::BetaHydri};
Seu olhar afiado reconheceu na hora um namespace heaven.
Você também percebe que deve existir uma classe chamada Vessel.
Aparentemente, 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.
Você até descobriu uma das enumerações: BetaHydri.
Prepare os arquivos de código-fonte e de cabeçalho com as informações que você descobriu.
Você precisa 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 ficar no namespace star_map e tem uma enumeração BetaHydri.
Fique de olho em outras enumerações, para poder atualizar o enum System o tempo todo.
Você descobre 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 recém-descobertas revelam outra função membro da classe Vessel: replicate.
Essa função recebe uma string e retorna outra instância de Vessel.
Você tem uma ideia do valor padrão do terceiro argumento do construtor a partir da tarefa anterior.
Você também vê duas variáveis membro públicas da classe Vessel e a especificação da função membro replicate.
Adicione a função replicate e as variáveis membro públicas current_system e generation aos arquivos de cabeçalho e de código-fonte.
Você encontra mais algumas linhas interessantes nos arquivos de teste 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);
Aparentemente, 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 outras informações não aparecem, você chuta que a função make_buster retorna void.
Como há um teste para o valor de retorno de shoot_buster, você presume que a função retorna um bool.
Adicione as duas funções e a variável membro à classe Vessel.
Continue procurando outros enumeradores de System.
Ao escanear os arquivos de teste, você encontra apenas duas seções de código ainda não descobertas:
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));
Você vê 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 retorna uma string.
in_the_same_system compara duas instâncias de Vessel e retorna um bool.
Implemente as últimas funções que faltam, a partir das linhas recuperadas acima.
Crie sua conta no Exercism para aprender e dominar C++ com 19 conceitos100 exercícios e mentoria humana de verdade, tudo de graça.