Почему у С++ есть файлы заголовков и .cpp файлы?
Зачем нужны файлы заголовков и .cpp файлы?
Ответ 1
Ну, основная причина заключалась в том, чтобы отделить интерфейс от реализации. Заголовок объявляет "что" класс (или что-то, что выполняется) будет делать, а файл cpp определяет "как" он будет выполнять эти функции.
Это уменьшает зависимости, поэтому код, который использует заголовок, не обязательно должен знать все детали реализации и любые другие классы/заголовки, необходимые только для этого. Это сократит время компиляции, а также объем перекомпиляции, необходимый для изменения ситуации.
Это не идеально, и вы обычно прибегаете к таким технологиям, как Pimpl Idiom, чтобы правильно разделить интерфейс и реализацию, но это хороший старт.
Ответ 2
Компиляция С++
Компиляция в С++ выполняется в 2 основных этапах:
-
Во-первых, это компиляция "исходных" текстовых файлов в двоичные "объекты": файл CPP является скомпилированным файлом и скомпилирован без каких-либо знаний о других файлах CPP (или даже в библиотеках) к нему через сырое объявление или включение заголовка. Файл CPP обычно скомпилируется в файл .OBJ или .O "object".
-
Вторая - это объединение всех "объектных" файлов и, следовательно, создание финального двоичного файла (либо библиотеки, либо исполняемого файла).
Где находится HPP во всем этом процессе?
Плохой одиночный файл CPP...
Компиляция каждого файла CPP не зависит от всех других файлов CPP, а это означает, что если A.CPP нуждается в символе, определенном в B.CPP, например:
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
Он не будет компилироваться, потому что A.CPP не имеет никакого способа узнать, что "doSomethingElse" существует... Если в A.CPP нет объявления, например:
// A.CPP
void doSomethingElse() ; // From B.CPP
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
Затем, если у вас есть C.CPP, который использует тот же символ, вы затем копируете/вставляете декларацию...
COPY/PASTE ALERT!
Да, есть проблема. Копирование/пасты опасны и трудно поддерживать. Это означает, что было бы здорово, если бы у нас был способ НЕ копировать/вставлять и все еще объявлять символ... Как мы можем это сделать? К включению некоторого текстового файла, который обычно помещается как .h,.hxx,.h ++ или, я предпочитаю файлы С++,.hpp:
// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;
// A.CPP
#include "B.HPP"
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
// B.CPP
#include "B.HPP"
void doSomethingElse()
{
// Etc.
}
// C.CPP
#include "B.HPP"
void doSomethingAgain()
{
doSomethingElse() ; // Defined in B.CPP
}
Как работает include
?
Включение файла будет, по сути, анализировать, а затем копировать его содержимое в файл CPP.
Например, в следующем коде с заголовком A.HPP:
// A.HPP
void someFunction();
void someOtherFunction();
... источник B.CPP:
// B.CPP
#include "A.HPP"
void doSomething()
{
// Etc.
}
... станет после включения:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
Одна маленькая вещь - зачем включать B.HPP в B.CPP?
В текущем случае это не требуется, а B.HPP имеет объявление функции doSomethingElse
, а B.CPP имеет определение функции doSomethingElse
(которое само по себе является объявлением). Но в более общем случае, когда B.HPP используется для объявлений (и встроенного кода), не может быть никакого соответствующего определения (например, перечислений, простых структур и т.д.), Поэтому включение может потребоваться, если B.CPP использует это выражение от B.HPP. В целом, это хороший вкус для источника, который по умолчанию включает заголовок.
Заключение
Таким образом, заголовочный файл необходим, поскольку компилятор С++ не может самостоятельно искать объявления символов, и поэтому вы должны помочь ему, включив эти объявления.
Последнее слово: вы должны ставить защитники заголовков вокруг содержимого ваших файлов HPP, чтобы убедиться, что несколько включений ничего не сломают, но в целом я считаю, что основная причина существования файлов HPP объясняется выше.
#ifndef B_HPP_
#define B_HPP_
// The declarations in the B.hpp file
#endif // B_HPP_
Ответ 3
Поскольку C, откуда возникла концепция, исполнилось 30 лет, и тогда это был единственный жизнеспособный способ связать код из нескольких файлов.
Сегодня это ужасный хак, который полностью разрушает время компиляции на С++, вызывает бесчисленные ненужные зависимости (поскольку определения классов в файле заголовка содержат слишком много информации об этой реализации) и т.д.
Ответ 4
Поскольку в С++ конечный исполняемый код не несет никакой информации о символе, это более или менее чистый машинный код.
Таким образом, вам нужен способ описания интерфейса фрагмента кода, который отделен от самого кода. Это описание находится в файле заголовка.
Ответ 5
Поскольку люди, которые разработали формат библиотеки, не захотели "тратить" пространство на редко используемую информацию, такую как макросы препроцессора C и декларации функций.
Поскольку вам нужна эта информация, чтобы сообщить вашему компилятору, "эта функция доступна позже, когда компоновщик выполняет свою работу", им пришлось придумать второй файл, в котором можно было бы сохранить эту общую информацию.
Большинство языков после C/С++ хранят эту информацию в выводе (например, байт-код Java), или они вообще не используют предварительно скомпилированный формат, всегда распространяются в исходной форме и компилируются "на лету" (Python, Perl).
Ответ 6
Потому что С++ унаследовал их от C. К сожалению.
Ответ 7
Это препроцессорный способ декларирования интерфейсов. Вы помещаете интерфейс (объявления метода) в заголовочный файл и реализацию в cpp. Приложения, использующие вашу библиотеку, должны знать интерфейс, к которому они могут получить доступ через #include.
Ответ 8
Часто вам нужно иметь определение интерфейса без отправки всего кода. Например, если у вас есть разделяемая библиотека, вы отправляете с ней заголовочный файл, который определяет все функции и символы, используемые в общей библиотеке. Без файлов заголовков вам необходимо отправить исходный код.
В рамках одного проекта используются файлы заголовков, IMHO, по крайней мере для двух целей:
- Ясность, то есть, сохраняя интерфейсы отдельно от реализации, легче прочитать код
- Время компиляции. Используя только интерфейс, где это возможно, вместо полной реализации время компиляции может быть уменьшено, потому что компилятор может просто сделать ссылку на интерфейс вместо того, чтобы анализировать фактический код (что, идеально, было бы нужно сделать только один раз).
Ответ 9
Отвечаем ответ MadKeithV,
Это уменьшает зависимости, поэтому код, который использует заголовок, не обязательно необходимо знать все детали реализации и любые другие классы/заголовки нужны только для этого. Это уменьшит время компиляции, а также количество перекомпиляции, необходимое, когда что-то в реализации меняется.
Другая причина заключается в том, что заголовок дает уникальный идентификатор каждому классу.
Итак, если у нас есть что-то вроде
class A {..};
class B : public A {...};
class C {
include A.cpp;
include B.cpp;
.....
};
У нас будут ошибки, когда мы попытаемся построить проект, так как A является частью B, с заголовками мы бы избегали этой головной боли...