A C++-ban a deklarációk gyakran el vannak választva a definícióktól. A deklarációkat úgynevezett headerfájlokba csoportosítják, a hozzájuk tartozó implementációk pedig forrásfájlokba kerülnek. A headerfájlokra gondolhatsz úgy, mint egy API-ra. A headerfájlból megtudod, mit kínál egy kódbázis, anélkül hogy a hogyan részleteibe mennél.
A headerfájlok leggyakoribb kiterjesztése a .h.
Egyes projektek .hpp-t használnak, vagy teljesen elhagyják a kiterjesztést.
A definíciók egy külön .cpp fájlban találhatók.
A részek újraegyesítéséhez a forrásfájl azzal kezdődik, hogy behúzza a megfelelő headerfájlt.
Ha írni szeretnél egy „quick_math” nevű könyvtárat, amely egy „super_root” nevű függvényt kínál, amit gyakran szeretnél használni, akkor a fájlok így néznének ki:
// 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;
}
Ha olyan headert kell behúznod, amelyre csak az implementációnak van szüksége, akkor a megfelelő #include sorra csak a forrásfájlban van szükség.
Minden, ami a headerben be van húzva, a .cpp fájlban is elérhető, például az alábbi példában a string könyvtár.
Figyelem: a deklaráció után kell a ; a headerfájlban, a definíció után viszont már nem a forrásfájlban.
Az Exercism sok C++ feladata két szinte üres fájllal kezdődik: egy headerrel és egy forrással.
Ahhoz, hogy megoldd a feladatot, meg kell nézned a *_test.cpp fájlt, hogy lásd a várt függvények neveit és névtereit.
Az osztályok nagyon összetettekké válhatnak, és zavaró lehet, hogyan viszonyulnak a header és a forrás közötti felosztáshoz. Az egyik lehetséges felosztás, hogy minden implementációs részletet a forrásfájlban tartasz, a deklarációkat és a tagváltozókat pedig a headerben:
// 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;}
Amikor a headert API-áttekintésként használják, az ember az ilyen információkat, például az alapértelmezett értékeket ott keresi.
A konstruktor size paraméterének alapértéke ezért a headerben van kezelve, nem az implementációban.
A forrásfájlban a definíciók elé a robots névtér és a Flower osztálytípus kerül előtagként.
Egy másik felosztási lehetőség a header only könyvtár, amelynek egyáltalán nincs .cpp fájlja:
// 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;}
};
}
A projektek ezeknek a felosztásoknak a kombinációit is használhatják, és sok vita folyik arról, melyik illik legjobban az egyes esetekhez.
Talán észrevetted a fenti példa headerfájljában a #pragma once sort.
Ezt include guardnak nevezik, és biztosítja, hogy a fájl tartalma a fordítás során csak egyszer legyen behúzva, elkerülve ezzel a hibákat.
Létezik az include guardnak egy másik, összetettebb változata is, amely egy #ifndef sorral kezdődik és egy #endif sorral végződik.
Ugyanazt a célt szolgálja, és a használatát a fenti Flower osztály példája mutatja.
Egy „Doctor Data” nevű ausztrál cégnél kezded meg az első munkanapodat, amely információ-helyreállításra szakosodott. Az állásinterjún a C++-ról és a von Neumann-szondákról szerzett tudásodnak köszönhetően teljesítettél kiválóan. Mivel már sok tesztfájlt láttál, az új főnököd szeretné, ha a cég által nemrég helyreállított tesztkódból újraalkotnád a hozzá tartozó forrás- és fejlécfájlokat.
Ebben a feladatban elveszett fájlokat fogsz újraalkotni. A fájlok tartalma jelenleg használhatatlan szemétadat.
Ennek a tanulófeladatnak a menete nagyon hasonlít az Exercism gyakorlófeladataihoz. A feladat bevezető szövege a specifikációnak csak az egyik része. A tesztfájl az, ami végleges útmutatást ad egy adott probléma megoldásához. A C++ fordításának módja miatt előfordulhat, hogy a tesztek eredményei nem jelennek meg a webes felületen, amíg nem implementáltad az összes olyan osztály, függvény és felsorolás minimális változatát, amelyet a tesztek megkövetelnek. Ha fordítási hibákat kapsz, azok eltűnhetnek, miután minden részfeladattal és teszttel végeztél.
Négy részfeladatot kapsz, amelyek mindegyike az elveszett kód helyreállításához kapcsolódik a fejléc- és forrásfájlokban.
Két helyreállított sort nézel meg a tesztfájlokból:
heaven::Vessel bob{"Robert Johansson", 1};
heaven::Vessel will{"Riker", 2, star_map::System::BetaHydri};
Az éles szemed azonnal felismert egy heaven névteret.
Azt is látod, hogy lennie kell egy Vessel nevű osztálynak.
A konstruktort láthatóan két vagy három argumentummal lehet meghívni.
Az első argumentum típusa látszólag string, a második pedig egy szám.
A Vessel osztályt egy harmadik argumentummal is inicializálhatod.
A harmadik argumentum egy star_map névtérből származik.
Ez egy System típusú felsorolási érték.
Sőt, már az egyik felsorolási értéket is megkaptad: BetaHydri.
Készítsd el a forrás- és fejlécfájlokat a felfedezett információid alapján.
Két névtérre lesz szükséged: heaven és star_map.
A heaven névtérben van egy Vessel osztály, amelyet két vagy három argumentummal lehet meghívni.
A System felsorolást a star_map névtérben kell elhelyezni, és tartalmaznia kell egy BetaHydri felsorolási értéket.
Figyelj a többi felsorolási értékre is, hogy folyamatosan bővíthesd a System felsorolást.
Újabb sorokat társz fel a tesztből:
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);
Az újonnan talált tesztsorok felfednek egy újabb tagfüggvényt a Vessel osztályban: replicate.
Ez a függvény egy stringet kap, és visszaad egy másik Vessel példányt.
Az előző részfeladat alapján sejtheted a konstruktor harmadik argumentumának alapértelmezett értékét.
A Vessel osztály két nyilvános tagváltozóját is látod, valamint a replicate tagfüggvény specifikációját.
Add hozzá a replicate függvényt, valamint a current_system és generation nyilvános tagváltozókat a fejléc- és forrásfájlokhoz.
Néhány további érdekes sort találsz a helyreállított tesztfájlokban:
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);
Úgy tűnik, a Vessel osztálynak van egy busters tagváltozója, amelyet a make_buster és shoot_buster tagfüggvényekkel lehet módosítani.
Amíg más információ nem kerül elő, arra tippelsz, hogy a make_buster függvény visszatérési értéke void.
Mivel a shoot_buster visszatérési értékére is van teszt, feltételezed, hogy a függvény egy bool értéket ad vissza.
Add hozzá a két függvényt és a tagváltozót a Vessel osztályhoz.
Keress további System felsorolási értékeket.
Miközben a tesztfájlokat vizsgálod, mindössze két felfedetlen kódrészletre bukkansz:
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));
Két függvényt látsz, amelyek nem tagjai a Vessel osztálynak, mivel nem egy példányon hívják meg őket.
A get_older_bob összehasonlít két Vessel példányt, és egy stringet ad vissza.
Az in_the_same_system összehasonlít két Vessel példányt, és egy bool értéket ad vissza.
Implementáld az utolsó hiányzó függvényeket a fenti helyreállított sorok alapján.
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) C++ nyelvet 19 fogalom100 feladat segítségével, valódi emberi mentorálással, mindez ingyen.