Zawsze widziałem, jak ludzie piszą
class.h
#ifndef CLASS_H
#define CLASS_H
//blah blah blah
#endif
Pytanie brzmi, dlaczego nie robią tego również dla pliku .cpp, który zawiera definicje funkcji klas?
Powiedzmy, że mam main.cpp
i main.cpp
obejmuje class.h
. class.h
Plik nie include
wszystko, tak jak nie main.cpp
wiem co jest w class.cpp
?
FILE_H
, a nieCLASS_H
.Odpowiedzi:
Po pierwsze, aby odpowiedzieć na pierwsze zapytanie:
Kiedy zobaczysz to w pliku .h :
Jest to technika preprocesora, która zapobiega wielokrotnemu dołączaniu pliku nagłówkowego, co może być problematyczne z różnych powodów. Podczas kompilacji projektu kompilowany jest każdy plik .cpp (zwykle). Mówiąc prościej, oznacza to, że kompilator weźmie plik .cpp , otworzy wszystkie pliki
#included
przez niego, połączy je wszystkie w jeden ogromny plik tekstowy, a następnie przeprowadzi analizę składni, a na koniec przekonwertuje go na jakiś kod pośredni, zoptymalizuje / wykona inne zadań, a na koniec wygeneruj dane wyjściowe zestawu dla architektury docelowej. Z tego powodu, jeśli plik jest#included
wielokrotnie pod jednym .cppplik, kompilator dwukrotnie dopisze zawartość pliku, więc jeśli w tym pliku znajdują się definicje, pojawi się błąd kompilatora informujący o przedefiniowaniu zmiennej. Gdy plik jest przetwarzany przez krok preprocesora w procesie kompilacji, przy pierwszym osiągnięciu jego zawartości pierwsze dwa wiersze sprawdzają, czyFILE_H
został on zdefiniowany dla preprocesora. Jeśli nie, zdefiniujeFILE_H
i będzie kontynuował przetwarzanie kodu między nim a#endif
dyrektywą. Następnym razem, gdy zawartość tego pliku zostanie zobaczona przez preprocesor, sprawdzenieFILE_H
będzie fałszywe, więc natychmiast skanuje w dół do#endif
i kontynuuje po nim. Zapobiega to błędom redefinicji.Aby zająć się drugim problemem:
W programowaniu C ++ jako ogólną praktykę rozdzielamy programowanie na dwa typy plików. Jeden ma rozszerzenie .h i nazywamy go „plikiem nagłówkowym”. Zwykle dostarczają deklaracji funkcji, klas, struktur, zmiennych globalnych, typów definicji, wstępnie przetwarzających makr i definicji, itp. Zasadniczo, po prostu dostarczają informacji o kodzie. Następnie mamy rozszerzenie .cpp, które nazywamy „plikiem kodu”. Zapewni to definicje tych funkcji, składowych klas, dowolnych składowych struktury, które potrzebują definicji, zmiennych globalnych itp. Zatem plik .h deklaruje kod, a plik .cpp implementuje tę deklarację. Z tego powodu zazwyczaj podczas kompilacji kompilujemy każdy plik .cppplik do obiektu, a następnie połącz te obiekty (ponieważ prawie nigdy nie widać jednego pliku .cpp zawierającego inny plik .cpp ).
Sposób rozwiązywania tych zewnętrznych elementów jest zadaniem konsolidatora. Kiedy kompilator przetwarza main.cpp , pobiera deklaracje kodu w class.cpp , dołączając class.h . Musi tylko wiedzieć, jak wyglądają te funkcje lub zmienne (co daje deklaracja). Więc kompiluje twój plik main.cpp do jakiegoś pliku obiektowego (nazwij go main.obj ). Podobnie class.cpp jest kompilowany do pliku class.objplik. Aby utworzyć ostateczny plik wykonywalny, wywoływany jest konsolidator, który łączy te dwa pliki obiektowe ze sobą. W przypadku wszelkich nierozwiązanych zewnętrznych zmiennych lub funkcji kompilator umieści kod pośredniczący w miejscu, w którym następuje dostęp. Konsolidator następnie pobierze ten kod i wyszuka kod lub zmienną w innym wymienionym pliku obiektowym, a jeśli zostanie znaleziony, łączy kod z dwóch plików obiektowych w plik wyjściowy i zastępuje kod pośredniczący ostateczną lokalizacją funkcji lub zmienna. W ten sposób twój kod w main.cpp może wywoływać funkcje i używać zmiennych w class.cpp JEŚLI I TYLKO JEŚLI ZADEKLAROWANE SĄ W class.h .
Mam nadzieję, że to było pomocne.
źródło
CLASS_H
Jest to straż ; służy do unikania wielokrotnego włączania tego samego pliku nagłówkowego (różnymi drogami) w tym samym pliku CPP (lub dokładniej w tej samej jednostce tłumaczeniowej ), co prowadziłoby do błędów w wielu definicjach.Uwzględnij strażników nie są potrzebne w plikach CPP, ponieważ z definicji zawartość pliku CPP jest odczytywana tylko raz.
Wygląda na to, że zinterpretowałeś strażników włączania jako pełniących tę samą funkcję, co
import
instrukcje w innych językach (takich jak Java); tak jednak nie jest.#include
Samo jest w przybliżeniu równowartośćimport
w innych językach.źródło
Tak nie jest - przynajmniej na etapie kompilacji.
Tłumaczenie programu w języku C ++ z kodu źródłowego na kod maszynowy odbywa się w trzech fazach:
class.h
jest wstawiana w miejsce wiersza#include "class.h
. Ponieważ możesz znajdować się w pliku nagłówkowym w kilku miejscach,#ifndef
klauzule pozwalają uniknąć podwójnych błędów deklaracji, ponieważ dyrektywa preprocesora jest niezdefiniowana tylko przy pierwszym dołączeniu pliku nagłówkowego.Podsumowując, deklaracje mogą być współużytkowane przez plik nagłówkowy, podczas gdy mapowanie deklaracji do definicji jest wykonywane przez konsolidator.
źródło
Na tym polega różnica między deklaracją a definicją. Pliki nagłówkowe zwykle zawierają tylko deklarację, a plik źródłowy zawiera definicję.
Aby czegoś użyć, wystarczy znać deklarację, a nie definicję. Tylko konsolidator musi znać definicję.
Dlatego właśnie umieścisz plik nagłówkowy w jednym lub kilku plikach źródłowych, ale nie umieścisz pliku źródłowego w innym.
Ty też masz na myśli
#include
i nie importujesz.źródło
Odbywa się to w przypadku plików nagłówkowych, dzięki czemu zawartość pojawia się tylko raz w każdym wstępnie przetworzonym pliku źródłowym, nawet jeśli jest dołączana więcej niż raz (zwykle dlatego, że jest zawarta w innych plikach nagłówkowych). Gdy jest dołączany po raz pierwszy, symbol
CLASS_H
(znany jako ochrona włączania ) nie został jeszcze zdefiniowany, więc uwzględniana jest cała zawartość pliku. W ten sposób definiuje symbol, więc jeśli zostanie dołączony ponownie, zawartość pliku (wewnątrz bloku#ifndef
/#endif
) jest pomijana.Nie ma potrzeby tego robić dla samego pliku źródłowego, ponieważ (normalnie) nie jest dołączony do żadnych innych plików.
Ostatnie pytanie
class.h
powinno zawierać definicję klasy i deklaracje wszystkich jej członków, powiązanych funkcji i cokolwiek innego, tak aby każdy plik, który ją zawiera, zawierał wystarczającą ilość informacji do użycia klasy. Implementacje funkcji mogą znajdować się w oddzielnym pliku źródłowym; potrzebujesz tylko deklaracji, aby je wywołać.źródło
main.cpp nie musi wiedzieć, co znajduje się w class.cpp . Musi tylko znać deklaracje funkcji / klas, których będzie używać, a te deklaracje znajdują się w class.h .
Linker łączy miejsca, w których używane są funkcje / klasy zadeklarowane w class.h, a ich implementacje w class.cpp
źródło
.cpp
pliki nie są dołączane (używane#include
) do innych plików. Dlatego nie muszą obejmować ochrony.Main.cpp
będzie znać nazwy i sygnatury klasy, w której zaimplementowałeś,class.cpp
tylko dlatego, że określiłeś to wszystko wclass.h
- taki jest cel pliku nagłówkowego. (To do Ciebie należy upewnienie się, żeclass.h
dokładnie opisuje kod, w którym zaimplementujeszclass.cpp
.) Kod wykonywalnyclass.cpp
zostanie udostępniony kodowi wykonywalnemumain.cpp
dzięki wysiłkom konsolidatora.źródło
Generalnie oczekuje się, że moduły kodu, takie jak
.cpp
pliki, są kompilowane raz i łączone w wielu projektach, aby uniknąć niepotrzebnego powtarzania kompilacji logiki. Na przykładg++ -o class.cpp
utworzy,class.o
które możesz następnie połączyć z wielu projektów do usingg++ main.cpp class.o
.Moglibyśmy użyć
#include
jako naszego linkera, jak zdajesz się sugerować, ale byłoby to po prostu głupie, gdybyśmy wiedzieli, jak poprawnie połączyć się za pomocą naszego kompilatora z mniejszą liczbą naciśnięć klawiszy i mniej marnotrawnym powtarzaniem kompilacji, zamiast naszego kodu z większą liczbą naciśnięć klawiszy i bardziej marnotrawnym powtórzenie kompilacji ...Jednak pliki nagłówkowe nadal muszą być uwzględnione w każdym z wielu projektów, ponieważ zapewnia to interfejs dla każdego modułu. Bez tych nagłówków kompilator nie wiedziałby o żadnym z symboli wprowadzonych przez
.o
pliki.Ważne jest, aby zdać sobie sprawę, że to pliki nagłówkowe wprowadzają definicje symboli dla tych modułów; gdy zostanie to zrealizowane, ma sens, że wielokrotne wtrącenia mogą spowodować przedefiniowanie symboli (co powoduje błędy), więc używamy osłon włączających, aby zapobiec takim redefinicjom.
źródło
jego ponieważ pliki nagłówkowe definiują, co zawiera klasa (składowe, struktury danych), a pliki cpp ją implementują.
I oczywiście głównym powodem tego jest to, że jeden plik .h można dołączyć wielokrotnie do innych plików .h, ale spowodowałoby to wiele definicji klasy, co jest nieprawidłowe.
źródło