Хотя вы можете включать .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, я сразу предполагаю, что первый содержит объявление класса, а второй содержит определение.