W: http://www.learncpp.com/cpp-tutorial/19-header-files/
Wymieniono następujące:
add.cpp:
int add(int x, int y)
{
return x + y;
}
main.cpp:
#include <iostream>
int add(int x, int y); // forward declaration using function prototype
int main()
{
using namespace std;
cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
return 0;
}
Użyliśmy deklaracji przekazywania, aby kompilator wiedział, co „
add
” jest podczas kompilacjimain.cpp
. Jak wspomniano wcześniej, pisanie deklaracji przekazywania dla każdej funkcji, która ma zostać użyta w innym pliku, może szybko stać się nudne.
Czy możesz wyjaśnić „ deklarację przekazania ”? Jaki jest problem, jeśli użyjemy go w main()
funkcji?
c++
declaration
forward-declaration
Prostota
źródło
źródło
Odpowiedzi:
Dlaczego deklaracja forward jest konieczna w C ++
Kompilator chce się upewnić, że nie popełniłeś błędów ortograficznych ani nie przekazałeś niewłaściwej liczby argumentów do funkcji. Nalega więc, aby przed użyciem zobaczyła deklarację „dodaj” (lub dowolnego innego typu, klasy lub funkcji).
To naprawdę pozwala kompilatorowi lepiej sprawdzać poprawność kodu i porządkować luźne końce, dzięki czemu może tworzyć ładnie wyglądający plik obiektowy. Jeśli nie musisz przekazywać dalej deklaracji, kompilator wygeneruje plik obiektowy, który będzie musiał zawierać informacje o wszystkich możliwych zgadnięciach dotyczących tego, czym może być funkcja „dodaj”. A linker musiałby zawierać bardzo sprytną logikę, aby spróbować ustalić, który „add” rzeczywiście chcesz wywołać, gdy funkcja „add” może znajdować się w innym pliku obiektowym, linker łączy się z tym, który używa add do produkcji dll lub exe. Możliwe, że linker otrzyma niewłaściwe dodanie. Powiedzmy, że chciałeś użyć int add (int a, float b), ale przypadkowo zapomniałeś go napisać, ale linker znalazł już istniejący int add (int a, int b) i pomyślałem, że to było właściwe i użyłem tego zamiast tego. Twój kod się skompiluje, ale nie będzie działał zgodnie z oczekiwaniami.
Tak więc, aby zachować jasność rzeczy i uniknąć zgadywania itp., Kompilator nalega, aby zadeklarować wszystko przed użyciem.
Różnica między deklaracją a definicją
Nawiasem mówiąc, ważne jest, aby znać różnicę między deklaracją a definicją. Deklaracja po prostu daje wystarczającą ilość kodu, aby pokazać, jak coś wygląda, więc dla funkcji jest to typ zwracany, konwencja wywoływania, nazwa metody, argumenty i ich typy. Ale kod metody nie jest wymagany. Do definicji potrzebna jest deklaracja, a także kod funkcji.
W jaki sposób deklaracje forward mogą znacznie skrócić czas kompilacji
Możesz pobrać deklarację funkcji do bieżącego pliku .cpp lub .h, # włączając nagłówek, który już zawiera deklarację funkcji. Może to jednak spowolnić kompilację, zwłaszcza jeśli #włączasz nagłówek do .h zamiast .cpp twojego programu, ponieważ wszystko co #include .h piszesz kończy się # obejmowaniem wszystkich nagłówków też napisałeś #include. Nagle kompilator ma # zawarte strony i strony kodu, które musi skompilować, nawet jeśli chcesz użyć tylko jednej lub dwóch funkcji. Aby tego uniknąć, możesz użyć deklaracji przesyłania dalej i po prostu sam wpisać deklarację funkcji u góry pliku. Jeśli używasz tylko kilku funkcji, może to naprawdę przyspieszyć twoją kompilację w porównaniu do zawsze #włączenie nagłówka. W przypadku naprawdę dużych projektów
Przerywaj odniesienia cykliczne, w których obie definicje wykorzystują się nawzajem
Ponadto deklaracje forward mogą pomóc w przerwaniu cykli. To tutaj dwie funkcje próbują się nawzajem używać. Kiedy tak się dzieje (i jest to całkowicie słuszna czynność), możesz # dołączyć jeden plik nagłówka, ale ten plik nagłówka próbuje # dołączyć plik nagłówka, który obecnie piszesz .... który następnie # zawiera drugi nagłówek , który # obejmuje ten, który piszesz. Utknąłeś w sytuacji z kurczakiem i jajkiem, a każdy plik nagłówkowy próbuje ponownie #include drugi. Aby rozwiązać ten problem, możesz zadeklarować w przód potrzebne części w jednym z plików i pozostawić #include poza tym plikiem.
Na przykład:
Plik Car.h
File Wheel.h
Hmm ... tutaj wymagana jest deklaracja Car, ponieważ Wheel ma wskaźnik do Car, ale Car.h nie może zostać tutaj uwzględniony, ponieważ spowodowałoby to błąd kompilatora. Gdyby uwzględniono Car.h, wówczas spróbowałby uwzględnić Wheel.h, który obejmowałby Car.h, który obejmowałby Wheel.h, i to trwałoby wiecznie, więc zamiast tego kompilator zgłasza błąd. Rozwiązaniem jest zamiast tego zadeklarować samochód zamiast:
Jeśli klasa Wheel ma metody, które muszą wywoływać metody car, metody te można by zdefiniować w Wheel.cpp, a Wheel.cpp może teraz obejmować Car.h bez powodowania cyklu.
źródło
// From Car.h
możesz stworzyć pewne owłosione sytuacje, próbując znaleźć definicję w dalszej części drogi, gwarantowane.Kompilator szuka każdego symbolu używanego w bieżącej jednostce tłumaczenia, który został wcześniej zadeklarowany lub nie w bieżącej jednostce. Jest to tylko kwestia stylu - wszystkie sygnatury metod należy podać na początku pliku źródłowego, a definicje podano później. Znaczącym zastosowaniem jest użycie wskaźnika do klasy jako zmiennej składowej innej klasy.
Dlatego w miarę możliwości używaj deklaracji forward w klasach. Jeśli twój program ma tylko funkcje (z plikami nagłówkowymi ho), to zapewnienie prototypów na początku jest tylko kwestią stylu. Tak by się stało, gdyby plik nagłówkowy był obecny w normalnym programie z nagłówkiem, który ma tylko funkcje.
źródło
Ponieważ C ++ jest analizowany z góry na dół, kompilator musi wiedzieć o rzeczach przed ich użyciem. Więc kiedy odwołujesz się:
w głównej funkcji kompilator musi wiedzieć, że istnieje. Aby to udowodnić, spróbuj przenieść go poniżej głównej funkcji, a pojawi się błąd kompilatora.
Zatem „ Deklaracja przekazująca ” jest dokładnie tym, co jest napisane na puszce. Deklaruje coś przed użyciem.
Zasadniczo należy zawrzeć deklaracje przekazywania w pliku nagłówkowym, a następnie dołączyć ten plik nagłówkowy w taki sam sposób, w jaki dołączony jest iostream .
źródło
Termin „ deklaracja przekazania ” w C ++ jest najczęściej używany tylko w przypadku deklaracji klas . Zobacz (na końcu) tej odpowiedzi, dlaczego „deklaracja przekazująca” klasy jest tak naprawdę prostą deklaracją klasy o fantazyjnej nazwie.
Innymi słowy, „przekazanie” tylko dodaje balast do terminu, ponieważ każda deklaracja może być postrzegana jako przekazana, o ile deklaruje jakiś identyfikator przed użyciem.
(Co to jest deklaracja w przeciwieństwie do definicji , zobacz ponownie Jaka jest różnica między definicją a deklaracją? )
źródło
Gdy kompilator zobaczy
add(3, 4)
, musi wiedzieć, co to znaczy. Deklaracja przekazująca w zasadzie mówi kompilatorowi, żeadd
jest funkcją, która pobiera dwie liczby całkowite i zwraca liczbę całkowitą. Jest to ważna informacja dla kompilatora, ponieważ musi umieścić 4 i 5 we właściwej reprezentacji na stosie i musi wiedzieć, jakiego typu jest rzecz zwracana przez add.W tym czasie kompilator nie martwi się o faktyczną implementację
add
, tj. Gdzie jest (lub jeśli jest nawet jedna) i czy się kompiluje. Pojawia się to później, po skompilowaniu plików źródłowych po wywołaniu konsolidatora.źródło
To jest tak jak
#include"add.h"
. Jeśli wiesz, preprocesor rozszerza plik, o którym wspominasz#include
, w pliku .cpp, w którym piszesz#include
dyrektywę. Oznacza to, że jeśli napiszesz#include"add.h"
, otrzymasz to samo, to tak, jakbyś robił „deklarację przekazania”.Zakładam, że
add.h
ma tę linię:źródło
jedno szybkie uzupełnienie dotyczące: zwykle umieszczasz te referencje w pliku nagłówkowym należącym do pliku .c (pp), w którym implementowana jest funkcja / zmienna itp. w twoim przykładzie wyglądałoby to tak: add.h:
słowo kluczowe extern stwierdza, że funkcja jest faktycznie zadeklarowana w zewnętrznym pliku (może to być również biblioteka itp.). Twój plik main.c wyglądałby tak:
źródło
Jednym z problemów jest to, że kompilator nie wie, jaką wartość dostarcza twoja funkcja; zakłada, że
int
w tym przypadku funkcja zwraca an , ale może być tak poprawna, jak może być błędna. Innym problemem jest to, że kompilator nie wie, jakich argumentów oczekuje twoja funkcja i nie może cię ostrzec, jeśli przekazujesz wartości niewłaściwego rodzaju. Istnieją specjalne reguły „promocji”, które obowiązują przy przekazywaniu, powiedzmy wartości zmiennoprzecinkowe do niezadeklarowanej funkcji (kompilator musi je rozszerzyć, aby wpisać podwójne), co często nie jest tym, czego funkcja faktycznie oczekuje, co prowadzi do trudnych do znalezienia błędów W czasie wykonywania.źródło