Почему нужны заголовочные файлы и .cpp файлы? [закрыто]
Описание проблемы: Почему в C++ существуют файлы заголовков и файлы .cpp?
Здравствуйте! Я столкнулся с вопросом, касающимся структуры файлов в C++. Меня интересует, зачем в этом языке программирования используются как файлы заголовков (.h или .hpp), так и файлы с исходным кодом (.cpp)? Какова их роль и как они взаимодействуют между собой?
Я понимаю, что и те, и другие файлы имеют свои особенности, но мне бы хотелось получить более глубокое понимание их назначения и принципа работы. Почему разделяют объявления и определения? Какие преимущества это дает при разработке программ?
Буду признателен за любые разъяснения или ссылки на полезные ресурсы!
5 ответ(ов)
Компиляция C++
Компиляция в C++ состоит из двух основных этапов:
- Первый этап — это компиляция текстовых файлов "исходного кода" в бинарные "объектные" файлы. Файл CPP компилируется независимо от других файлов CPP (или даже библиотек), если не предоставлены соответствующие объявления или включения заголовочных файлов. Обычно файл CPP компилируется в объектный файл с расширением .OBJ или .O.
- Второй этап — это связывание всех "объектных" файлов, что приводит к созданию итогового бинарного файла (либо библиотеки, либо исполняемого файла).
Где в этом процессе находится 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
Причина, по которой этот подход возник, заключается в том, что язык C, с которого всё началось, существует уже 30 лет, и тогда это был единственный жизнеспособный способ связать код из нескольких файлов.
Сегодня это ужасный костыль, который полностью разрушает время компиляции в C++, вызывает множество ненужных зависимостей (поскольку определения классов в заголовочном файле раскрывают слишком много информации о реализации) и так далее.
В C++ финальный исполняемый код не содержит никакой информации о символах, поэтому он представляет собой в основном чистый машинный код.
По этой причине вам необходимо иметь способ описать интерфейс фрагмента кода, который отделён от самого кода. Это описание находится в заголовочном файле.
Потому что C++ унаследовал их от C. К сожалению.
Причина, по которой создатели формата библиотеки не захотели "тратить" место на редко используемую информацию, такую как макросы препроцессора C и определения функций, заключается в том, что эта информация необходима компилятору для указания "эта функция доступна позже, когда линкер выполняет свою работу". Поэтому им пришлось придумать второй файл, в котором можно было бы хранить эту общую информацию.
Большинство языков после C/C++ хранят такую информацию в выходных данных (например, байт-код Java), или же вообще не используют предкомпилированный формат, а всегда распространяются в исходном виде и компилируются на лету (например, Python, Perl).
В чем разница между #include <filename> и #include "filename"?
Как инициализировать приватные статические члены данных в заголовочном файле
Обработка зависимостей заголовочных файлов с помощью CMake
Фатальная ошибка C1010 - Как исправить отсутствие "stdafx.h" в Visual Studio?
Как удалить элемент из std::vector<> по индексу?