Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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
, , double
funkcją, 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;
}