Что должно быть в с-файле, а что должно быть в h-файле? / Хабр
Подобного рода вопрос мне недавно задал один коллега, начинающий программировать на языке Си. И я подумал, что это хороший повод поделится своим пониманием данного вопроса. Потому, что даже опытные программисты не всегда имеют схожие точки зрения на этот счет.
Отчасти это дело вкуса, поэтому, кому интересно как это делаю я, добро пожаловать под кат.
Несмотря на то, что «вся правда» о h-файлах содержится в соответствующем разделе описания препроцессора gcc, позволю себе некоторые пояснения и иллюстрации.
Итак, если дословно, заголовочный файл (h-файл) — файл содержащий Си декларации и макро определения, предназначенные для использования в нескольких исходных файлах (с-файлах). Проиллюстрируем это.
Легко заметить, что функции 1 и 2, а так же макрос 2, упомянуты в обоих файлах. И, поскольку, включение заголовочных файлов приводит к таким же результатам, как и копирование содержимого в каждый си-файл, мы можем сделать следующее:
Таким образом мы просто выделили общую часть из двух файлов и поместили ее в заголовочный файл.

Но является ли заголовочный файл интерфейсом в данном случае?
- Если нам нужно использовать функциональность, которую реализуют функции 1 и 2 где то еще, то Да
- Если макрос 2, предназначен только для использования в файлах Unit1.c и Unit2.c, то ему не место в интерфейсном файле
Более того, действительно ли нам необходимо иметь два си-файла для реализации интерфейса, определенного в заголовочном файле? Или достаточно одного?
Ответ на этот вопрос зависит от деталей реализации интерфейсных функций и от их места реализации. Например, если сделать диаграммы более подробными, можно представить вариант, когда интерфейсные функции реализованы в разных файлах:
Такой вариант реализации приводит к высокой связности кода, низкой тестируемости и к сложности повторного использования таких модулей.
Для того, что бы не иметь таких трудностей, я всегда рассматриваю си-файл и заголовочный файл как один модуль.

- заголовочный файл содержит только те декларации функций, типов, макросов, которые являются частью интерфейса данного модуля.
Таким образом, если бы мне довелось реализовывать код, которому соответствует диаграмма приведенная выше, я бы постарался, добиться следующего (окончания _с и _h в именах файлов добавлены по причине невозможности использовать точку в инструменте, которым я пользовался для создания диаграмм):
Из диаграммы видно, что на самом деле мы имеем дело с двумя независимыми модулями, у каждого из которых имеется свой интерфейс в виде заголовочного файла. Это дает возможность использовать только тот интерфейс, который действительно необходим в данном конкретном случае.Более того, эти модули могут быть протестированы независимо друг от друга.
Читатель, наверное, заметил, что макрос 2 из заголовочного файла снова вернулся в виде копии в оба си-файла. Конечно, это не очень удобно поддерживать. Но и делать данный макрос частью интерфейса не правильно.
В таких случаях, я предпочитаю делать отдельный заголовочный файл содержащий типы и макросы, необходимые нескольким си-файлам.
Надеюсь, мне удалось обозначить те сущности, которые нуждаются в том, что бы быть помещенными в заголовочные файлы. А так же, показать разницу между интерфейсам и файлами, содержащими декларации и макросы, необходимые нескольким си-файлам.
Спасибо за внимание к материалу.
Promise — Специальная программа на 15 автомобилей с пробегом в Major Expert
Audi
220 авто
Bentley
BMW
325 авто
Brilliance
Cadillac
16 авто
Chery
12 авто
Chevrolet
22 авто
Chrysler
1 авто
Citroen
12 авто
Datsun
3 авто
DongFeng
1 авто
Exeed
4 авто
FAW
Fiat
2 авто
Ford
Geely
12 авто
Genesis
106 авто
Great Wall
1 авто
Haval
20 авто
HINO
2 авто
Honda
15 авто
Hyundai
312 авто
Infiniti
35 авто
Jaguar
25 авто
Jeep
14 авто
Kia
369 авто
Lada
40 авто
Land Rover
88 авто
Lexus
72 авто
Mazda
63 авто
Mercedes-Benz
490 авто
Mini
20 авто
Mitsubishi
4 авто
Nissan
265 авто
Opel
24 авто
Peugeot
22 авто
Porsche
23 авто
Renault
164 авто
Seat
2 авто
Skoda
375 авто
Smart
88 авто
SsangYong
2 авто
Subaru
13 авто
Suzuki
13 авто
Toyota
156 авто
Volvo
151 авто
ГАЗ
8 авто
УАЗ
5 авто
c++ — включая заголовки и Main.

спросил
Изменено 9 лет, 5 месяцев назад
Просмотрено 29 тысяч раз
Хорошо, не уверен, что это правильный путь или даже правильный, но я видел это и начал его использовать. Скажем, у вас есть 6 файлов
main.cpp main.h car.cpp машина.ч скорость.cpp скорость.ч
- 1-й — нужен ли вам main.h?
- 2nd — если в main.h есть #include car.h и #include speed.h, то в car/speed.cpp вам просто нужно добавить #main.h (таким образом, это будет включить автомобиль/скорость.ч)
- 3-й — стоит ли вам когда-нибудь идти по этому пути?
- c++
- включить
- заголовочные файлы
1
#include
минималистично. Причина включения должна заключаться в том, что при удалении код не компилируется.
Не #include
, когда вы можете объявить вперед. Если «Класс А;» будет достаточно, не добавляйте #include a.h
.
В частности, предпочтительнее использовать предварительное объявление в файлах заголовков, избегая вложенных включений, которые генерируют сильно связанные мегафайлы включения.
См. также Самодостаточные заголовочные файлы в соответствующем вопросе.
5
1) Только если вам нужно выставить что-то в main.cpp
другим файлам cpp
, так что зависит от того, что у него есть.
2) Возможно, но не рекомендуется.
3) По ряду причин (дизайн кода, время компиляции и т. д.) вы хотите включать как можно меньше. Кроме того, принято, чтобы ваш класс имел .h
и .cpp
, и чтобы один из них напрямую включал другой. Вы также должны попытаться включить заголовки в свои файлы .
и постараться избегать заголовков, включающих заголовки, где это возможно. cpp
Нет обычно нет main.h
. Я считаю хорошей практикой включать все необходимые вам заголовки в исходный файл, а не только в заголовок. Если вы полагаетесь на то, что заголовки включают все, что вам нужно, может случиться так, что изменение заголовка нарушит ваш исходный файл.
1-й — нужен ли вам main.h?
Очень редко. main.cpp
означает, что он компилирует модуль перевода, который содержит main()
, который обычно является клиентским кодом для других библиотек более низкого уровня, которым не нужно знать о символах в главная()
. Если каким-то образом в вашем дизайне что-то стало цикличным, и была веская причина (массивная нехватка времени?) не разбивать что-то, что вы выложили, на отдельный .cpp
, тогда вы могли бы получить main.h
. Это должно действительно только объявлять символы в main. cpp, к которым другим единицам перевода может потребоваться доступ. В частности, он не должен включать car.h и/или speed.h, если только не раскрываются функции main.h, которым нужны объявления car.h или speed.h — например, объявление функции в main.cpp, которая принимает аргументы типов от авто.ч или скорость.ч.
2-й — если в main.h есть #include car.h и #include speed.h, тогда в car/speed.cpp вам просто нужно добавить #main.h (таким образом, он будет включать car/speed.h)
Как и выше, это почти наверняка очень неправильный дизайн: ожидается, что main.cpp будет напрямую включать car.h и speed.h, и если он не хочет этого делать, а для car.h и speed.h, он должен быть назван в соответствии с их общей темой (например, transport.h), а не в честь конкретного клиента, который хочет получить доступ к обоим. Помните, что main.h должен существовать только в том случае, если необходимо выставить что-то из main.cpp.
3-й — стоит ли идти по этому пути?
Вероятно, нет, учитывая то, что я объяснил выше.
Нетипично иметь «main.h», но, конечно же, нет правила, запрещающего это.
Что касается того, что должно быть включено, и как вы этого добьетесь, на самом деле зависит от того, что делают соответствующие классы, какие знания друг о друге им нужны.
Обычно считается плохой идеей иметь «один включаемый файл, который включает все остальное» в описанном вами стиле. По нескольким причинам: 1. Трудно понять, какой исходный файл зависит от того, какой включает. 2. Вы получаете больше времени компиляции, так как компилятору приходится читать кучу определений классов, которые не используются. 3. Нельзя просто так взять, скажем, «car.h» и «car.cpp» и всунуть их в другой проект без «speed.h».
Вы должны создать заголовочный файл для исходных файлов, которые вы хотели бы использовать в своем коде. Поэтому маловероятно — да и не невозможно — вам нужно создать заголовок main.h, поскольку и в car.cpp, и в speed.cpp вы, вероятно, не включаете функции, объявленные в main. cpp. Напротив, вы можете включить функциональность car.cpp и speed.cpp в main.cpp, и поэтому вы хотели бы включить их заголовки в свой основной файл.
c++ — Нужно ли включать библиотеки в мой основной cpp, даже если он включен в файл заголовка?
спросил
Изменено 1 год, 4 месяца назад
Просмотрено 1к раз
Скажем, у меня есть файл player.h
, и в player.h я включил следующее:
#include#include <строка> #include <вектор> #include
Нужно ли мне снова включать их в player.cpp
, где я конкретизирую функции, объявленные в заголовочном файле? Если я этого не сделаю, нужно ли мне включать их, когда я запускаю main.cpp
, который вызывает функции из моих различных . cpp и .h
Школа никогда не говорила мне, делать это или нет, поэтому я Я всегда включал все по всем направлениям. Если в этом нет необходимости, есть ли заметная разница между включением всего несколько раз и отсутствием этого?
- c++
- включить
- заголовочные файлы
4
Директива препроцессора include указывает препроцессору заменить ее содержимым файла. Следовательно, когда у вас есть
//some_header.h #include
и
// some_source.cpp #include
тогда, если вы скомпилируете source.cpp
, компилятор увидит после этапа предварительной обработки следующее:
// некоторый_источник.cpp ... содержимое foo ...
То есть: #include
заменяется содержимым этого заголовка, а #include
заменяется содержимым foo
.
Нет, дважды включать заголовки не нужно.