Что такое хедер в программировании
Перейти к содержимому

Что такое хедер в программировании

  • автор:

Что такое Заголовочный файл (header) в C++?

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

Создание заголовочного файла:

// myheader.h #ifndef MYHEADER_H #define MYHEADER_H // Объявление функции int add(int a, int b); // Объявление класса class MyClass < public: void displayMessage(); >; #endif // MYHEADER_H

Реализация функций и классов:

// myheader.cpp #include "myheader.h" #include // Реализация функции int add(int a, int b) < return a + b; >// Реализация метода класса void MyClass::displayMessage()

Преимущества использования заголовочных файлов:

  1. Разделение интерфейса и реализации: Заголовочные файлы позволяют разделить объявления и реализации, что упрощает поддержку кода.
  2. Повторное использование: Заголовочные файлы обеспечивают возможность повторного использования кода в различных частях программы.
  3. Улучшение структуры проекта: Использование заголовочных файлов способствует лучшей структурированности проекта, делая его более понятным и поддерживаемым.

Файлы заголовков (C++)

Имена элементов программы, таких как переменные, функции, классы и т. д., должны быть объявлены до их использования. Например, вы не можете просто написать x = 42 без первого объявления «x».

int x; // declaration x = 42; // use x 

Объявление сообщает компилятору, является int ли элемент , функцией double , или class другой вещью. Кроме того, каждое имя должно быть объявлено (прямо или косвенно) в каждом CPP-файле, в котором он используется. При компиляции программы каждый CPP-файл компилируется независимо в единицу компиляции. Компилятор не знает, какие имена объявляются в других единицах компиляции. Это означает, что если вы определяете класс или функцию или глобальную переменную, необходимо указать объявление этой вещи в каждом дополнительном CPP-файле, который использует его. Каждое объявление этой вещи должно быть точно идентичным во всех файлах. Небольшое несоответствие приведет к ошибкам или непреднамеренное поведение, когда компоновщик пытается объединить все единицы компиляции в одну программу.

Чтобы свести к минимуму потенциал ошибок, C++ принял соглашение об использовании файлов заголовков для хранения объявлений. Вы делаете объявления в файле заголовка, а затем используйте директиву #include в каждом CPP-файле или другом файле заголовка, который требует этого объявления. Директива #include вставляет копию файла заголовка непосредственно в CPP-файл перед компиляцией.

В Visual Studio 2019 функция модулей C++20 представлена в качестве улучшения и окончательной замены файлов заголовков . Дополнительные сведения см. в разделе «Общие сведения о модулях в C++».

Пример

В следующем примере показан общий способ объявления класса и его использования в другом исходном файле. Начнем с файла заголовка, my_class.h . Он содержит определение класса, но обратите внимание, что определение является неполным; Функция-член do_something не определена:

// my_class.h namespace N < class my_class < public: void do_something(); >; > 

Затем создайте файл реализации (обычно с расширением CPP или аналогичного расширения). Мы вызовем файл my_class.cpp и предоставим определение для объявления члена. Мы добавим директиву #include для файла «my_class.h», чтобы объявление my_class вставлялось в этот момент в CPP-файле, и мы включаем в объявление. std::cout Обратите внимание, что кавычки используются для файлов заголовков в том же каталоге, что и исходный файл, а для заголовков стандартной библиотеки используются угловые скобки. Кроме того, многие заголовки стандартной библиотеки не имеют расширения H или других файлов.

В файле реализации можно использовать using инструкцию, чтобы избежать необходимости квалифицировать каждую упоминание «my_class» или «cout» с «N::» или «std::». Не помещайте using инструкции в файлы заголовков!

// my_class.cpp #include "my_class.h" // header in local directory #include // header in standard library using namespace N; using namespace std; void my_class::do_something()

Теперь мы можем использовать my_class в другом CPP-файле. Мы #include файл заголовка, чтобы компилятор извлекал объявление. Все, что компилятор должен знать, является то, что my_class является классом, который имеет общедоступную do_something() функцию-член.

// my_program.cpp #include "my_class.h" using namespace N; int main()

После завершения компиляции каждого CPP-файла в OBJ-файлы он передает OBJ-файлы компоновщику. При слиянии компоновщика файлов объектов он находит точно одно определение для my_class; он находится в OBJ-файле, создаваемом для my_class.cpp, и сборка завершается успешно.

Включение охранников

Как правило, файлы заголовков имеют параметр include guard или директиву #pragma once , чтобы убедиться, что они не вставляются несколько раз в один CPP-файл.

// my_class.h #ifndef MY_CLASS_H // include guard #define MY_CLASS_H namespace N < class my_class < public: void do_something(); >; > #endif /* MY_CLASS_H */ 

Что нужно поместить в файл заголовка

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

  • встроенные определения типов в пространстве имен или глобальных область
  • определения функций, отличных от встроенных
  • Определения переменных, отличные от const
  • агрегатные определения
  • безымянные пространства имен
  • Директивы using

using Использование директивы не обязательно приведет к ошибке, но может вызвать проблему, так как она приносит пространство имен в область в каждом CPP-файле, который прямо или косвенно включает этот заголовок.

Пример файла заголовка

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

// sample.h #pragma once #include // #include directive #include namespace N // namespace declaration < inline namespace P < //. >enum class colors : short < red, blue, purple, azure >; const double PI = 3.14; // const and constexpr definitions constexpr int MeaningOfLife< 42 >; constexpr int get_meaning() < static_assert(MeaningOfLife == 42, "unexpected!"); // static_assert return MeaningOfLife; >using vstr = std::vector; // type alias extern double d; // extern variable #define LOG // macro definition #ifdef LOG // conditional compilation directive void print_to_log(); #endif class my_class // regular class definition, < // but no non-inline function definitions friend class other_class; public: void do_something(); // definition in my_class.cpp inline void put_value(int i) < vals.push_back(i); >// inline OK private: vstr vals; int i; >; struct RGB < short r< 0 >; // member initialization short g< 0 >; short b< 0 >; >; template // template definition class value_store < public: value_store() = default; void write_value(T val) < //. function definition OK in template >private: std::vector vals; >; template // template declaration class value_widget; > 

Обратная связь

Были ли сведения на этой странице полезными?

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

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

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

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

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

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

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

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

Таким образом, если бы мне довелось реализовывать код, которому соответствует диаграмма приведенная выше, я бы постарался, добиться следующего (окончания _с и _h в именах файлов добавлены по причине невозможности использовать точку в инструменте, которым я пользовался для создания диаграмм):
image
Из диаграммы видно, что на самом деле мы имеем дело с двумя независимыми модулями, у каждого из которых имеется свой интерфейс в виде заголовочного файла. Это дает возможность использовать только тот интерфейс, который действительно необходим в данном конкретном случае.Более того, эти модули могут быть протестированы независимо друг от друга.
Читатель, наверное, заметил, что макрос 2 из заголовочного файла снова вернулся в виде копии в оба си-файла. Конечно, это не очень удобно поддерживать. Но и делать данный макрос частью интерфейса не правильно.
В таких случаях, я предпочитаю делать отдельный заголовочный файл содержащий типы и макросы, необходимые нескольким си-файлам.
image

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

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

include и заголовочный файлы

Заголовочный файл (иногда головной файл, англ. header file), или подключаемый файл — в языках программирования Си и C++ файл, содержащий определения типов данных, структуры, прототипы функций, перечисления, макросы препроцессора. Имеет по умолчанию расширение .h; иногда для заголовочных файлов языка C++ используют расширение .hpp. Заголовочный файл используется путём включения его текста в данный файл директивой препроцессора #include. Заголовочный файл в общем случае может содержать любые конструкции языка программирования, но на практике исполняемый код (за исключением inline-функций в C++) в заголовочные файлы не помещают. Например, идентификаторы, которые должны быть объявлены более чем в одном файле, удобно описать в заголовочном файле, а затем его подключать по мере надобности.

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

По традиции заголовочные файлы имеют расширение .h, а файлы, содержащие определения функций или данных, расширение .c. Иногда их называют «h-файлы» или «с-файлы» соответственно. Используют и другие расширения для этих файлов: .C, cxx, .cpp и .cc. Принятое расширение вы найдете в своем справочном руководстве.

Для включения файлов из стандартных каталогов (обычно каталоги с именем INCLUDE) надо вместо кавычек использовать угловые скобки < и >. Если имя_файла — в угловых скобках, то препроцессор разыскивает файл в стандартных системных каталогах. Если имя_файла заключено в кавычки, то вначале препроцессор просматривает текущий каталог пользователя и только затем обращается к просмотру стандартных системных каталогов. Например:

#include // включение из стандартного каталога. Имя в угловых скобках. #include "myheader.h" // включение из текущего каталога. Имя в кавычках. #include "..\CommonFiles\CmnHdr.h"

Включение из стандартных каталогов имеет то преимущество, что имена этих каталогов никак не связаны с конкретной программой (обычно вначале включаемые файлы ищутся в каталоге /usr/include/CC, а затем в /usr/include). К сожалению, в этой команде пробелы существенны:

#include < stream.h>// не будет найден

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

Укажем, что может содержать заголовочный файл:

Определения типов struct point < int x, y; >; Шаблоны типов template class V < /* . */ >Описания функций extern int strlen(const char*); Определения inline char get() < return *p++; >функций-подстановок Описания данных extern int a; Определения констант const float pi = 3.141593; Перечисления enum bool < false, true >; Описания имен class Matrix; Команды включения файлов #include Макроопределения #define Case break;case Комментарии /* проверка на конец файла */

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

Определений обычных функций char get() < return *p++; >Определений данных int a; Определений составных const tb[i] = < /* . */ >; констант

Один Заголовочный Файл (.h)

Проще всего решить проблему разбиения программы на несколько файлов поместив функции и определения данных в подходящее число исходных файлов и описав типы, необходимые для их взаимодействия, в одном заголовочном файле, который включается во все остальные файлы. Для программы калькулятора можно использовать четыре .c файла: lex.c, syn.c, table.c и main.c, и заголовочный файл dc.h, содержащий описания всех имен, которые используются более чем в одном .c файле.

Множественные Заголовочные Файлы (.h)

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

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

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