Podczas może zawierać .cpp
pliki, jak wspomniano, jest to zły pomysł.
Jak wspomniałeś, deklaracje należą do plików nagłówkowych. Nie powodują one problemów, gdy są zawarte w wielu jednostkach kompilacji, ponieważ nie zawierają implementacji. Wielokrotne dołączenie definicji funkcji lub elementu klasy zwykle spowoduje problem (ale nie zawsze), ponieważ linker może się pomylić i zgłosić błąd.
Co powinno się zdarzyć, gdy każdy .cpp
plik zawiera definicje podzbioru programu, takie jak klasa, logicznie zorganizowana grupa funkcji, globalne zmienne statyczne (należy używać oszczędnie, jeśli w ogóle) itp.
Każda jednostka kompilacji ( .cpp
plik) zawiera następnie wszelkie deklaracje potrzebne do skompilowania zawartych w niej definicji. Śledzi funkcje i klasy, do których się odwołuje, ale nie zawiera, więc linker może je rozwiązać później, gdy połączy kod obiektowy w plik wykonywalny lub bibliotekę.
Przykład
Foo.h
-> zawiera deklarację (interfejs) dla klasy Foo.
Foo.cpp
-> zawiera definicję (implementację) dla klasy Foo.
Main.cpp
-> zawiera główną metodę, punkt wejścia programu. Ten kod tworzy instancję Foo i używa jej.
Oba Foo.cpp
i Main.cpp
muszą zawierać Foo.h
. Foo.cpp
potrzebuje go, ponieważ definiuje kod wspierający interfejs klasy, więc musi wiedzieć, czym jest ten interfejs. Main.cpp
potrzebuje go, ponieważ tworzy Foo i wywołuje swoje zachowanie, więc musi wiedzieć, co to za zachowanie, rozmiar Foo w pamięci i jak znaleźć jego funkcje itp., ale nie potrzebuje jeszcze faktycznej implementacji.
Kompilator wygeneruje Foo.o
z Foo.cpp
którego zawiera cały kod klasy Foo w skompilowanej formie. Generuje również, Main.o
która zawiera główną metodę i nierozwiązane odwołania do klasy Foo.
Teraz przychodzi łącznik, który łączy dwa pliki obiektów Foo.o
i Main.o
do pliku wykonywalnego. Widzi nierozwiązane odwołania do Foo, Main.o
ale Foo.o
zawiera niezbędne symbole, więc „łączy kropki”, że tak powiem. Wywołanie funkcji Main.o
jest teraz połączone z faktyczną lokalizacją skompilowanego kodu, więc w czasie wykonywania program może przejść do właściwej lokalizacji.
Gdybyś włączył Foo.cpp
plik Main.cpp
, istniałyby dwie definicje klasy Foo. Linker zobaczyłby to i powiedział „Nie wiem, który wybrać, więc to jest błąd”. Krok kompilacji się powiódł, ale połączenie nie powiodło się. (Chyba że po prostu nie kompilujesz, Foo.cpp
ale dlaczego znajduje się w osobnym .cpp
pliku?)
Wreszcie idea różnych typów plików nie ma znaczenia dla kompilatora C / C ++. Kompiluje „pliki tekstowe”, które, mam nadzieję, zawierają poprawny kod dla pożądanego języka. Czasami może być w stanie powiedzieć język na podstawie rozszerzenia pliku. Na przykład skompiluj .c
plik bez opcji kompilatora, a on przyjmie C, podczas gdy a .cc
lub .cpp
rozszerzenie poinformuje go o założeniu C ++. Jednak mogę łatwo powiedzieć kompilatorowi, aby skompilował plik, a .h
nawet .docx
plik jako C ++, i wyemituje plik object ( .o
), jeśli zawiera poprawny kod C ++ w formacie zwykłego tekstu. Te rozszerzenia są bardziej korzystne dla programisty. Jeśli widzę Foo.h
i Foo.cpp
, od razu zakładam, że pierwsza zawiera deklarację klasy, a druga zawiera definicję.