Kurzusok
/
C++
C++
/
Feladatok
/
Adatdoktor
Adatdoktor

Adatdoktor

Tanulófeladat

Bevezetés

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.

Header és forrás

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.

Note

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.

Osztályok és headerek

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.

Include guardok

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.

Utasítások

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.

Note

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.

1. Próbálj kinyerni néhány első nyomot

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.

2. Keress további részleteket

Ú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.

3. Nézz bele a belső működésbe

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.

4. Tedd teljessé a képet

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.

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
C++ Exercism

Készen állsz elkezdeni a(z) Adatdoktor feladatot?

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.