Зачем нужны заголовочные файлы и файлы .cpp?

c++ header-files

272171 просмотра

9 ответа

Почему в C ++ есть заголовочные файлы и файлы .cpp?

Источник Размещён: 04.10.2019 11:20

Ответы (9)


194 плюса

Решение

Ну, основной причиной было бы отделение интерфейса от реализации. Заголовок объявляет, «что» будет делать класс (или что-либо еще реализуемое), в то время как файл cpp определяет, «как» он будет выполнять эти функции.

Это уменьшает зависимости, так что код, который использует заголовок, не обязательно должен знать все детали реализации и любые другие классы / заголовки, необходимые только для этого. Это сократит время компиляции, а также количество необходимой перекомпиляции, когда что-то в реализации изменится.

Это не идеально, и вы обычно прибегаете к методам, таким как Pimpl Idiom, чтобы правильно разделить интерфейс и реализацию, но это хорошее начало.

Автор: Joris Timmermans Размещён: 02.12.2008 01:23

579 плюса

Компиляция C ++

Компиляция в C ++ выполняется в 2 основных этапа:

  1. Первый - это компиляция «исходных» текстовых файлов в двоичные «объектные» файлы: файл CPP является скомпилированным файлом и компилируется без каких-либо знаний о других файлах CPP (или даже библиотеках), если только он не передан в него через необработанное объявление или заголовок включения. Файл CPP обычно компилируется в «объектный» файл .OBJ или .O.

  2. Второе - это связывание всех «объектных» файлов и, следовательно, создание окончательного двоичного файла (библиотеки или исполняемого файла).

Где ГЭС вписывается во весь этот процесс?

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

КОПИЯ / ПАСТА ПРЕДУПРЕЖДЕНИЕ!

Да, есть проблема. Копирование / вставка опасны и сложны в обслуживании. Что означает, что было бы здорово, если бы у нас был какой-то способ НЕ копировать / вставлять, и все же объявить символ ... Как мы можем это сделать? Включением некоторого текстового файла, к которому обычно добавляются суффиксы .h, .hxx, .h ++ или, как я предпочитаю, для файлов C ++, .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. В общем, для «хорошего вкуса» источник по умолчанию включает заголовок.

Заключение

Таким образом, файл заголовка необходим, потому что компилятор C ++ не может найти только объявления символов, и, следовательно, вы должны помочь ему, включив эти объявления.

И последнее слово: вы должны установить защиту заголовка вокруг содержимого ваших файлов HPP, чтобы быть уверенным, что несколько включений ничего не сломают, но в целом, я считаю, что основная причина существования файлов HPP описана выше.

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_
Автор: paercebal Размещён: 02.12.2008 01:47

89 плюса

Потому что C, где возникла эта концепция, 30 лет, и тогда это был единственный жизнеспособный способ связать код из нескольких файлов.

Сегодня это ужасный хак, который полностью уничтожает время компиляции в C ++, вызывает бесчисленные ненужные зависимости (потому что определения классов в заголовочном файле предоставляют слишком много информации о реализации) и так далее.

Автор: jalf Размещён: 02.12.2008 06:15

54 плюса

Поскольку в C ++ конечный исполняемый код не несет никакой символьной информации, это более или менее чистый машинный код.

Таким образом, вам нужен способ описания интерфейса фрагмента кода, который отделен от самого кода. Это описание находится в заголовочном файле.

Автор: unwind Размещён: 02.12.2008 01:19

14 плюса

Потому что люди, которые проектировали формат библиотеки, не хотели «тратить» пространство на редко используемую информацию, такую ​​как макросы препроцессора C и объявления функций.

Поскольку вам нужна эта информация, чтобы сообщить компилятору «эта функция доступна позже, когда компоновщик выполняет свою работу», они должны были найти второй файл, в котором эта общая информация могла бы храниться.

Большинство языков после C / C ++ хранят эту информацию в выводе (например, байт-код Java) или вообще не используют предварительно скомпилированный формат, всегда распределяются в исходной форме и компилируются на лету (Python, Perl).

Автор: Aaron Digulla Размещён: 02.12.2008 01:23

14 плюса

Потому что C ++ унаследовал их от C. К сожалению.

Автор: andref Размещён: 28.07.2010 06:54

6 плюса

Это препроцессорный способ объявления интерфейсов. Вы помещаете интерфейс (объявления методов) в заголовочный файл, а реализацию в cpp. Приложения, использующие вашу библиотеку, должны знать только интерфейс, к которому они могут получить доступ через #include.

Автор: Martin v. Löwis Размещён: 02.12.2008 01:19

5 плюса

Часто вам захочется получить определение интерфейса без необходимости доставки всего кода. Например, если у вас есть общая библиотека, вы бы отправили вместе с ней файл заголовка, который определяет все функции и символы, используемые в общей библиотеке. Без заголовочных файлов вам понадобится отправить исходный код.

Внутри одного проекта заголовочные файлы используются, IMHO, как минимум для двух целей:

  • Ясность, то есть, отделяя интерфейсы от реализации, легче читать код
  • Время компиляции. Использование только интерфейса, где это возможно, вместо полной реализации, время компиляции может быть уменьшено, потому что компилятор может просто сделать ссылку на интерфейс вместо того, чтобы анализировать фактический код (что, в идеале, должно было бы быть сделано только один раз).
Автор: Dan Размещён: 02.12.2008 01:25

-5 плюса

Отвечая на ответ MadKeithV ,

Это уменьшает зависимости, так что код, который использует заголовок, не обязательно должен знать все детали реализации и любые другие классы / заголовки, необходимые только для этого. Это сократит время компиляции, а также количество необходимой перекомпиляции, когда что-то в реализации изменится.

Другая причина в том, что заголовок дает уникальный идентификатор каждому классу.

Так что, если у нас есть что-то вроде

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

У нас будут ошибки, когда мы попытаемся построить проект, так как A является частью B, с заголовками мы бы избежали такого рода головной боли ...

Автор: Alex v Размещён: 29.02.2012 11:08
Вопросы из категории :
32x32