Что должно быть в с-файле, а что должно быть в h-файле? / Хабр

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

Отчасти это дело вкуса, поэтому, кому интересно как это делаю я, добро пожаловать под кат.

Несмотря на то, что «вся правда» о h-файлах содержится в соответствующем разделе описания препроцессора gcc, позволю себе некоторые пояснения и иллюстрации.

Итак, если дословно, заголовочный файл (h-файл) — файл содержащий Си декларации и макро определения, предназначенные для использования в нескольких исходных файлах (с-файлах). Проиллюстрируем это.

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


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

  • Если нам нужно использовать функциональность, которую реализуют функции 1 и 2 где то еще, то Да
  • Если макрос 2, предназначен только для использования в файлах Unit1.c и Unit2.c, то ему не место в интерфейсном файле

Более того, действительно ли нам необходимо иметь два си-файла для реализации интерфейса, определенного в заголовочном файле? Или достаточно одного?
Ответ на этот вопрос зависит от деталей реализации интерфейсных функций и от их места реализации. Например, если сделать диаграммы более подробными, можно представить вариант, когда интерфейсные функции реализованы в разных файлах:


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

  • заголовочный файл содержит только те декларации функций, типов, макросов, которые являются частью интерфейса данного модуля.
  • Си-файл, в свою очередь, должен содержать реализацию всех функций, декларированных в h- файле, а также приватные типы, макросы и функции, которые нужны для реализации интерфейса.

Таким образом, если бы мне довелось реализовывать код, которому соответствует диаграмма приведенная выше, я бы постарался, добиться следующего (окончания _с и _h в именах файлов добавлены по причине невозможности использовать точку в инструменте, которым я пользовался для создания диаграмм):

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


Читатель, наверное, заметил, что макрос 2 из заголовочного файла снова вернулся в виде копии в оба си-файла. Конечно, это не очень удобно поддерживать. Но и делать данный макрос частью интерфейса не правильно.
В таких случаях, я предпочитаю делать отдельный заголовочный файл содержащий типы и макросы, необходимые нескольким си-файлам.

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

Спасибо за внимание к материалу.

Promise — Специальная программа на 15 автомобилей с пробегом в Major Expert

Audi

220 авто

Bentley

1 авто

BMW

325 авто

Brilliance

1 авто

Cadillac

16 авто

Chery

12 авто

Chevrolet

22 авто

Chrysler

1 авто

Citroen

12 авто

Datsun

3 авто

DongFeng

1 авто

Exeed

4 авто

FAW

1 авто

Fiat

2 авто

Ford

100 авто

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.

h

спросил

Изменено 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 .

Нет, дважды включать заголовки не нужно.

Автор записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *