Udostępnij za pośrednictwem


Pliki nagłówkowe (C++)

Nazwy elementów programu, takich jak zmienne, funkcje, klasy itd., muszą być zadeklarowane przed ich zastosowaniem. Na przykład nie można po prostu zapisać x = 42 bez uprzedniego deklarowania ciągu "x".

int x; // declaration
x = 42; // use x

Deklaracja informuje kompilator, czy element jest elementem int, , doublefunkcją, czy class inną. Ponadto każda nazwa musi być zadeklarowana (bezpośrednio lub pośrednio) w każdym pliku .cpp, w którym jest używana. Podczas kompilowania programu każdy plik .cpp jest kompilowany niezależnie w jednostce kompilacji. Kompilator nie ma wiedzy o tym, jakie nazwy są deklarowane w innych jednostkach kompilacji. Oznacza to, że jeśli zdefiniujesz klasę lub funkcję lub zmienną globalną, musisz podać deklarację tego elementu w każdym dodatkowym pliku .cpp, który go używa. Każda deklaracja tej rzeczy musi być dokładnie identyczna we wszystkich plikach. Niewielka niespójność spowoduje błędy lub niezamierzone zachowanie, gdy konsolidator próbuje scalić wszystkie jednostki kompilacji w jednym programie.

Aby zminimalizować potencjalne błędy, język C++ przyjął konwencję używania plików nagłówkowych do zawierania deklaracji. Deklaracje są wykonywane w pliku nagłówkowym, a następnie należy użyć dyrektywy #include w każdym pliku .cpp lub innym pliku nagłówka, który wymaga tej deklaracji. Dyrektywa #include wstawia kopię pliku nagłówka bezpośrednio do pliku .cpp przed kompilacją.

Uwaga

W programie Visual Studio 2019 funkcja modułów języka C++20 jest wprowadzana jako poprawa i ostateczna zamiana plików nagłówków. Aby uzyskać więcej informacji, zobacz Omówienie modułów w języku C++.

Przykład

W poniższym przykładzie przedstawiono typowy sposób deklarowania klasy, a następnie używania jej w innym pliku źródłowym. Zaczniemy od pliku nagłówka . my_class.h Zawiera definicję klasy, ale należy pamiętać, że definicja jest niekompletna; funkcja do_something składowa nie jest zdefiniowana:

// my_class.h
namespace N
{
    class my_class
    {
    public:
        void do_something();
    };

}

Następnie utwórz plik implementacji (zazwyczaj z .cpp lub podobnym rozszerzeniem). Wywołamy plik my_class.cpp i udostępnimy definicję deklaracji elementu członkowskiego. Dodamy dyrektywę #include dla pliku "my_class.h" w celu wstawienia deklaracji my_class w tym momencie w pliku .cpp i dołączymy <iostream> polecenie w celu ściągnięcia deklaracji dla std::cout. Należy pamiętać, że cudzysłowy są używane dla plików nagłówków w tym samym katalogu co plik źródłowy, a nawiasy kątowe są używane dla nagłówków biblioteki standardowej. Ponadto wiele standardowych nagłówków biblioteki nie ma .h ani żadnego innego rozszerzenia pliku.

W pliku implementacji możemy opcjonalnie użyć using instrukcji , aby uniknąć konieczności kwalifikowania każdej wzmianki o "my_class" lub "cout" z ciągiem "N::" lub "std::". Nie umieszczaj using instrukcji w plikach nagłówków!

// my_class.cpp
#include "my_class.h" // header in local directory
#include <iostream> // header in standard library

using namespace N;
using namespace std;

void my_class::do_something()
{
    cout << "Doing something!" << endl;
}

Teraz możemy użyć my_class w innym pliku .cpp. #include pliku nagłówka, aby kompilator ściągał deklarację. Każdy kompilator musi wiedzieć, że my_class jest klasą, która ma publiczną funkcję składową o nazwie do_something().

// my_program.cpp
#include "my_class.h"

using namespace N;

int main()
{
    my_class mc;
    mc.do_something();
    return 0;
}

Po zakończeniu kompilowania każdego pliku .cpp do plików .obj przekazuje pliki .obj do konsolidatora. Gdy konsolidator scala pliki obiektów, znajduje dokładnie jedną definicję dla my_class; znajduje się on w pliku .obj utworzonym dla my_class.cpp, a kompilacja zakończy się pomyślnie.

Dołączanie strażników

Zazwyczaj pliki nagłówków mają ochronę dołączania lub dyrektywę #pragma once , aby upewnić się, że nie są one wstawione wielokrotnie do jednego pliku .cpp.

// my_class.h
#ifndef MY_CLASS_H // include guard
#define MY_CLASS_H

namespace N
{
    class my_class
    {
    public:
        void do_something();
    };
}

#endif /* MY_CLASS_H */

Co należy umieścić w pliku nagłówka

Ponieważ plik nagłówka może być potencjalnie dołączony przez wiele plików, nie może zawierać definicji, które mogą zawierać wiele definicji o tej samej nazwie. Następujące kwestie są niedozwolone lub są uważane za bardzo złe rozwiązanie:

  • wbudowane definicje typów w przestrzeni nazw lub zakresie globalnym
  • definicje funkcji innych niż wbudowane
  • definicje zmiennych innych niż const
  • definicje agregacji
  • nienazwane przestrzenie nazw
  • używanie dyrektyw

using Użycie dyrektywy niekoniecznie spowoduje błąd, ale może potencjalnie spowodować problem, ponieważ przenosi przestrzeń nazw do zakresu w każdym pliku .cpp, który bezpośrednio lub pośrednio zawiera ten nagłówek.

Przykładowy plik nagłówka

W poniższym przykładzie przedstawiono różne rodzaje deklaracji i definicji, które są dozwolone w pliku nagłówka:

// sample.h
#pragma once
#include <vector> // #include directive
#include <string>

namespace N  // namespace declaration
{
    inline namespace P
    {
        //...
    }

    enum class colors : short { red, blue, purple, azure };

    const double PI = 3.14;  // const and constexpr definitions
    constexpr int MeaningOfLife{ 42 };
    constexpr int get_meaning()
    {
        static_assert(MeaningOfLife == 42, "unexpected!"); // static_assert
        return MeaningOfLife;
    }
    using vstr = std::vector<int>;  // type alias
    extern double d; // extern variable

#define LOG   // macro definition

#ifdef LOG   // conditional compilation directive
    void print_to_log();
#endif

    class my_class   // regular class definition,
    {                // but no non-inline function definitions

        friend class other_class;
    public:
        void do_something();   // definition in my_class.cpp
        inline void put_value(int i) { vals.push_back(i); } // inline OK

    private:
        vstr vals;
        int i;
    };

    struct RGB
    {
        short r{ 0 };  // member initialization
        short g{ 0 };
        short b{ 0 };
    };

    template <typename T>  // template definition
    class value_store
    {
    public:
        value_store<T>() = default;
        void write_value(T val)
        {
            //... function definition OK in template
        }
    private:
        std::vector<T> vals;
    };

    template <typename T>  // template declaration
    class value_widget;
}