Kurzusok
/
C++
C++
/
Tanterv
/
Fejlécek
Fe

Fejlécek ebben a kurzusban: C++

2 feladat

A(z) Fejlécek fogalomról

A C++-ban a deklarációk gyakran elválnak a definícióktól. A deklarációk úgynevezett header fájlokba csoportosulnak, a hozzájuk tartozó implementációk pedig forrásfájlokba kerülnek. A header fájlokra gondolhatsz úgy, mint egy API-ra. A header fájl megmondja, mit kínál egy kódbázis, anélkül hogy belemennénk a hogyan részleteibe.

Header és forrás

A header fá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. Hogy a részek újra összeálljanak, a forrásfájl azzal kezdődik, hogy include utasítással behúzza a megfelelő header fájlt.

Ha egy „quick_math” nevű könyvtárat akarsz írni, amely egy „super_root” nevű függvényt kínál, amit gyakran szeretnél használni, 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 <cmath>

#include "quick_math.h"

double quick_math::super_root(double x, int n) {
    while (n) {
        x = std::sqrt(x), --n;
    }
    return x;
}

Ha olyan header fájlt kell include-olni, 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 header fájlban szerepel, elérhető a .cpp fájlban is, ahogy az alábbi példában a string könyvtár is. Figyelem: a ; a header fájlban a deklaráció után szükséges, a forrásfájlban viszont a definíció után nem.

Note

Sok C++ feladat az Exercism-ön két majdnem üres fájllal indul: egy header fájllal és egy forrásfájllal. 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 header fájlok

Az osztályok nagyon összetettek lehetnek, és az, hogy hogyan viszonyulnak a header és a forrásfájl közötti felosztáshoz, könnyen zavaróvá válhat. Az egyik lehetséges elrendezés, hogy minden implementációs részlet a forrásfájlban marad, a deklarációk és a tagváltozók pedig a header fájlban:

// 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();
};
}  // namespace robots
#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 header fájlt API-áttekintésként használjuk, az ember az ilyen információkat, például az alapértelmezett értékeket ott fogja keresni. A konstruktor size paraméterének alapértelmezett értéke ezért a header fájlban szerepel, 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.

Egy másik elrendezé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;
    }
};
}  // namespace robots

A projektek ezeknek az elrendezéseknek a kombinációit használhatják, és sok vita folyik arról, hogy melyik illik legjobban az egyes felhasználási esetekhez.

Include guardok

Talán észrevetted a fenti példa header fájljában a #pragma once sort. Ezt nevezik include guardnak: arról gondoskodik, hogy a fájl tartalma a fordításkor csak egyszer kerüljön bele, így elkerülve a hibákat. Létezik az include guardnak egy másik, összetettebb változata is, amely #ifndef-től #endif-ig tart; ezt részletesen lentebb tárgyaljuk.

Advanced

Elődeklarációk

A C++ kód kiértékelése sorban, egymás után történik. Ha használni akarsz egy függvényt, annak a használat pillanatában már ismertnek kell lennie a fordító számára. Néha nem lehet olyan lineáris sorrendet találni, amelyben a definíciók elrendezhetők a forráskódban. Nézzük az alábbi példát:

int myFunction(int n) {
    if (n < 10) {
        return n;
    } else {
        return myOtherFunction(n / 10);
    }
}

int myOtherFunction(int m) { return myFunction(m / 2); }

Mire a myFunction definiálásra kerül, a fordító még nem ismeri a myOtherFunction-t. Sajnos a körkörös hivatkozás problémáját nem lehet a sorrend megcserélésével megoldani.

A C++ elődeklarációkkal teszi lehetővé, hogy a fordító már a definiálásuk előtt tudomást szerezzen a myFunction és a myOtherFunction függvényről. A fordító feltételezi, hogy a definíció majd valamikor később, a deklaráció után következik. A következő példa megmutatja, hogyan használható az elődeklaráció függvényeknél.

int myFunction(int n);       // Forward declaration of myFunction
int myOtherFunction(int m);  // Forward declaration of myOtherFunction

// Definition of myFunction
int myFunction(int n) {
    if (n < 0) {
        return 0;
    } else {
        return myOtherFunction(n - 2);
    }
}

// Definition of myOtherFunction
int myOtherFunction(int m) { return myFunction(m / 2); }

Include guardok ifndef segítségével

Az nem jelent problémát, ha ugyanazt a fájlt egy projekten belül többször is beillesztjük. A header fájloknak nem szabad definíciókat tartalmazniuk. A teljes projektben ugyanaz a definíció legfeljebb egyszer szerepelhet. Ezt nevezik „egyszeri definíció” szabályának. A fordító gondoskodik arról, hogy ez érvényesüljön.

Az include guardokkal könnyen elkerülhető ugyanannak a definíciónak a véletlen többszöri beillesztése. Ezeket a fordítás egyes szakaszaiban egy speciális eljárás hozza létre. A cél az, hogy egy fájlt csak akkor illesszünk be, ha egy bizonyos változó még nincs beállítva, majd a fájl beillesztése után beállítsuk azt. A változó nevét gyakran a fájlnév valamilyen változatából képezik. Egy másik módszer egy UUID generálása, hogy csökkenjen a név véletlen kétszeri használatának kockázata. Az alábbi példában a szintaxis látható, ahol MY_HEADER_FILE_H a változó.

#ifndef MY_HEADER_FILE_H /* any name uniquely mapped to file name */
#define MY_HEADER_FILE_H

// file content

#endif

A #pragma once problémája, hogy a pragmák nem részei hivatalosan a C++ nyelvnek, és a megvalósításuk fordítóról fordítóra eltér. Sok nagy projekt áttért az egyszerűbb pragma módszerre, de néhányan még mindig óvatosak.

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

Tanuld meg a(z) Fejlécek fogalmat