Хоча ви можете включати .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, тоді як a .ccабо .cppрозширення підкаже йому прийняття C ++. Однак я можу легко сказати компілятору скласти файл .hабо навіть навіть .docxяк C ++, і він видасть .oфайл object ( ), якщо він містить дійсний код C ++ у простому текстовому форматі. Ці розширення більше на користь програміста. Якщо я бачу Foo.hі Foo.cpp, я одразу припускаю, що перша містить декларацію класу, а друга містить визначення.