Хотя вы можете включать .cpp
файлы, как вы упомянули, это плохая идея.
Как вы упомянули, объявления принадлежат заголовочным файлам. Это не вызывает проблем при включении в несколько модулей компиляции, поскольку они не включают реализации. Включение определения функции или члена класса несколько раз обычно вызывает проблему (но не всегда), потому что компоновщик запутается и выдаст ошибку.
Что должно произойти, так это то, что каждый .cpp
файл содержит определения для подмножества программы, такого как класс, логически организованная группа функций, глобальные статические переменные (используйте экономно, если вообще используется) и т. Д.
Каждый модуль ( .cpp
файл) компиляции затем включает в себя любые объявления, необходимые для компиляции содержащихся в нем определений. Он отслеживает функции и классы, на которые он ссылается, но не содержит их, поэтому компоновщик может разрешить их позже, когда объединит объектный код в исполняемый файл или библиотеку.
пример
Foo.h
-> содержит объявление (интерфейс) для класса Foo.
Foo.cpp
-> содержит определение (реализацию) для класса Foo.
Main.cpp
-> содержит основной метод, точку входа в программу. Этот код создает экземпляр Foo и использует его.
И то, Foo.cpp
и другое Main.cpp
нужно включить Foo.h
. Foo.cpp
он нужен, потому что он определяет код, который поддерживает интерфейс класса, поэтому ему нужно знать, что это за интерфейс. Main.cpp
он нужен, потому что он создает Foo и вызывает его поведение, поэтому он должен знать, каково это поведение, размер Foo в памяти, как найти его функции и т. д., но пока ему пока не нужна фактическая реализация.
Компилятор сгенерирует Foo.o
из Foo.cpp
которого содержится весь код класса Foo в скомпилированной форме. Он также генерирует, Main.o
который включает основной метод и неразрешенные ссылки на класс Foo.
Сейчас идет компоновщик, который сочетает в себе два объектных файлов Foo.o
и Main.o
в исполняемый файл. Он видит неразрешенные ссылки Foo, Main.o
но видит, что Foo.o
содержит необходимые символы, поэтому он «соединяет точки», так сказать. Вызов функции Main.o
теперь связан с фактическим местоположением скомпилированного кода, поэтому во время выполнения программа может перейти в правильное местоположение.
Если бы вы включили Foo.cpp
файл в Main.cpp
, было бы два определения класса Foo. Компоновщик увидит это и скажет: «Я не знаю, какой из них выбрать, так что это ошибка». Этап компиляции будет успешным, но соединение не будет. (Если вы просто не компилируете, Foo.cpp
но тогда почему это в отдельном .cpp
файле?)
Наконец, идея различных типов файлов не имеет отношения к компилятору C / C ++. Он компилирует «текстовые файлы», которые, как мы надеемся, содержат действительный код для желаемого языка. Иногда это может быть в состоянии сказать язык на основе расширения файла. Например, скомпилируйте .c
файл без опций компилятора, и он примет C, а расширение .cc
или .cpp
скажет, что он принимает C ++. Тем не менее, я могу легко сказать компилятору скомпилировать файл .h
или даже .docx
файл как C ++, и он будет генерировать .o
файл object ( ), если он содержит допустимый код C ++ в текстовом формате. Эти расширения больше для пользы программиста. Если я вижу Foo.h
и Foo.cpp
, я сразу предполагаю, что первый содержит объявление класса, а второй содержит определение.