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