5

Почему нужны заголовочные файлы и .cpp файлы? [закрыто]

26

Описание проблемы: Почему в C++ существуют файлы заголовков и файлы .cpp?

Здравствуйте! Я столкнулся с вопросом, касающимся структуры файлов в C++. Меня интересует, зачем в этом языке программирования используются как файлы заголовков (.h или .hpp), так и файлы с исходным кодом (.cpp)? Какова их роль и как они взаимодействуют между собой?

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

Буду признателен за любые разъяснения или ссылки на полезные ресурсы!

5 ответ(ов)

6

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

Компиляция в C++ состоит из двух основных этапов:

  1. Первый этап — это компиляция текстовых файлов "исходного кода" в бинарные "объектные" файлы. Файл CPP компилируется независимо от других файлов CPP (или даже библиотек), если не предоставлены соответствующие объявления или включения заголовочных файлов. Обычно файл CPP компилируется в объектный файл с расширением .OBJ или .O.
  2. Второй этап — это связывание всех "объектных" файлов, что приводит к созданию итогового бинарного файла (либо библиотеки, либо исполняемого файла).

Где в этом процессе находится HPP?

Одинокий файл CPP...

Компиляция каждого файла CPP происходит независимо от других файлов CPP, что означает, что если A.CPP нуждается в символе, определенном в B.CPP, например:

// A.CPP
void doSomething()
{
   doSomethingElse(); // Определен в B.CPP
}

// B.CPP
void doSomethingElse()
{
   // И т.д.
}

Он не скомпилируется, потому что A.CPP не знает о существовании "doSomethingElse"... Если только не будет объявления в A.CPP, как в следующем примере:

// A.CPP
void doSomethingElse(); // Из B.CPP

void doSomething()
{
   doSomethingElse(); // Определен в B.CPP
}

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

СИГНАЛ КОПИРОВАНИЯ/ВСТАВКИ!

Да, это проблема. Копирование и вставка опасны и трудно поддерживаются. Это означает, что было бы здорово иметь способ избежать копирования и вставки и при этом объявить символ... Как это сделать? С помощью включения некоторых текстовых файлов, которые обычно имеют суффиксы .h, .hxx, .h++ или, что предпочитаю для C++ файлов — .hpp:

// B.HPP (здесь мы решили объявить все символы, определенные в B.CPP)
void doSomethingElse();

// A.CPP
#include "B.HPP"

void doSomething()
{
   doSomethingElse(); // Определен в B.CPP
}

// B.CPP
#include "B.HPP"

void doSomethingElse()
{
   // И т.д.
}

// C.CPP
#include "B.HPP"

void doSomethingAgain()
{
   doSomethingElse(); // Определен в B.CPP
}

Как работает include?

Включение файла на самом деле парсит и копирует его содержимое в файл CPP.

Например, в следующем коде с заголовком A.HPP:

// A.HPP
void someFunction();
void someOtherFunction();

... исходный файл B.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // И т.д.
}

... после включения станет:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // И т.д.
}

Один момент — зачем включать B.HPP в B.CPP?

В текущем случае это не нужно, так как B.HPP содержит объявление функции "doSomethingElse", а B.CPP содержит её определение (которое само по себе является объявлением). Однако в более общем случае, когда B.HPP используется для объявлений (и встроенного кода), может не быть соответствующего определения (например, для перечислений, простых структур и т.д.), поэтому включение может быть необходимо, если B.CPP использует эти объявления из B.HPP. В общем, это "хороший тон" для источника по умолчанию включать свой заголовок.

Заключение

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

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

#ifndef B_HPP_
#define B_HPP_

// Объявления в файле B.hpp

#endif // B_HPP_

или еще проще (хотя и не по стандарту):

#pragma once

// Объявления в файле B.hpp
1

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

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

0

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

По этой причине вам необходимо иметь способ описать интерфейс фрагмента кода, который отделён от самого кода. Это описание находится в заголовочном файле.

0

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

0

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

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь