Добавление в проект заголовочного файла
Помимо файлов с исходным кодом (в языке C++ они имеют расширение cpp , а в языке C — расширение c ) в проекте могут быть заголовочные файлы (в языке C++ они имеют расширение hpp или h ). В заголовочных файлах указываются прототипы функций и различные объявления.
Для создания заголовочного файла в окне Проекты щелкаем правой кнопкой мыши на названии проекта и из контекстного меню выбираем пункт Добавить новый. В открывшемся окне (рис. 2.7) из списка слева выбираем пункт C/C++, а из списка справа — пункт Заголовочный файл C/C++. Нажимаем кнопку Выбрать. На следующем шаге (рис. 2.8) вводим название HelloWorld.hpp в поле Имя файла. В поле Путь указываем значение C:\cpp\projectsQt\HelloWorld . Нажимаем кнопку Далее. На следующем шаге (рис. 2.9) нажимаем кнопку Завершить.
Рис. 2.7. Создание заголовочного файла. Шаг 1

Рис. 2.8. Создание заголовочного файла. Шаг 2

Рис. 2.9. Создание заголовочного файла. Шаг 3
Созданный файл отобразится на вкладке Проекты и будет открыт на отдельной вкладке для редактирования. Причем внутри файла будет вставлен код, приведенный в листинге 2.2. В файл HelloWorld.pro будут добавлены следующие строки:
Листинг 2.2. Содержимое файла HelloWorld.hpp
Текст после символов // является комментарием. Инструкции, начинающиеся с символа # , — это директивы препроцессора. В нашем примере их три:
- #ifndef — проверяет отсутствие константы с именем HELLOWORLD_HPP ;
- #define — создает константу с именем HELLOWORLD_HPP ;
- #endif — обозначает конец блока проверки отсутствия константы.
Зачем нужны эти директивы препроцессора? Заголовочный файл мы подключаем к файлу с исходным кодом с помощью директивы #include :
Встретив в исходном коде директиву #include компилятор вставляет все содержимое заголовочного файла на место директивы. Если мы вставим две одинаковые директивы #include , то содержимое заголовочного файла будет вставлено дважды. Так как объявить один идентификатор (например, глобальную переменную) дважды нельзя, компилятор выведет сообщение об ошибке. Чтобы этого избежать, прототипы функций и прочие объявления вкладываются в блок, ограниченный директивами #ifndef и #endif . В директиве #ifndef указывается константа, совпадающая с названием заголовочного файла. Все буквы в имени константы заглавные, а точка заменена символом подчеркивания. Если константа не существует (при первом включении заголовочного файла так и будет), то с помощью директивы #define эта константа создается и содержимое блока вставляется в исходный код. При повторном включении заголовочного файла константа уже существует, поэтому содержимое блока будет проигнорировано. Таким образом заголовочный файл дважды вставлен не будет, а значит и ошибки не будет.
Вместо этих директив можно указать в самом начале заголовочного файла директиву препроцессора #pragma со значением once , которая также препятствует повторному включению файла (в старых компиляторах директива может не поддерживаться):
Название заголовочного файла в директиве #include может быть указано внутри угловых скобок:
или внутри кавычек:
В первом случае заголовочный файл ищется в путях поиска заголовочных файлов. При этом текущий рабочий каталог не просматривается. Добавить каталог в пути поиска заголовочных файлов позволяет флаг -I в команде компиляции. Обычно с помощью угловых скобок включаются заголовочные файлы стандартной библиотеки или библиотеки стороннего разработчика.
Во втором случае мы имеем дело с заголовочным файлом, который вначале ищется в текущем рабочем каталоге (или относительно него), а затем в путях поиска заголовочных файлов, как будто название указано внутри угловых скобок. Таким способом обычно включаются заголовочные файлы проекта.
Внимательный читатель наверняка обратил внимание на то, что файл iostream не содержит расширение. Наличие расширения файла принято в стандартной библиотеке языка C. В стандартной библиотеке языка C++ расширение файла принято не указывать. Так как язык C++ наследует все библиотеки языка C, то файлы можно подключать как в стиле языка C, так и в стиле языка C++. Например, файл string.h из стандартной библиотеки языка C доступен в языке C++ под названием cstring , а файл math.h под названием cmath . Отличие между этими способами подключения заключается в импорте идентификаторов. В языке C при подключении файла (например, math.h ) все идентификаторы импортируются в глобальное пространство имен, а в языке C++ при подключении файла (например, cmath ) идентификаторы добавляются в пространство имен под названием std . Поэтому перед идентификатором необходимо указать название пространства имен (например, std::cout ). Использование пространств имен позволяет избежать конфликта имен в программе.
Можно указать просто название заголовочного файла:
абсолютный путь к нему:
или относительный путь к нему:
Если указано только название заголовочного файла и этот файл не входит с состав проекта (или название указано внутри угловых скобок), то нужно дополнительно указать место поиска заголовочных файлов. Путь к заголовочным файлам в командной строке добавляется с помощью флага -I :
В нашем случае путь содержит пробел, поэтому весь путь указывается внутри кавычек. Если пробелов нет, то кавычки можно не указывать.

Учебник C++ (Qt Creator и MinGW) в формате PDF
Помощь сайту
ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов
Урок №21. Заголовочные файлы
По мере увеличения размера программ весь код уже не помещается в нескольких файлах, записывать каждый раз предварительные объявления для функций, которые мы хотим использовать, но которые находятся в других файлах, становится всё утомительнее и утомительнее. Хорошо было бы, если бы все предварительные объявления находились в одном месте, не так ли?
Файлы .cpp не являются единственными файлами в проектах. Есть еще один тип файлов — заголовочные файлы (или «заголовки»), которые имеют расширение .h . Целью заголовочных файлов является удобное хранение набора объявлений объектов для их последующего использования в других программах.
Заголовочные файлы из Стандартной библиотеки C++
Рассмотрим следующую программу:
Результат выполнения программы:
В этой программе мы используем cout, который нигде не определяем. Откуда компилятор знает, что это такое? Дело в том, что cout объявлен в заголовочном файле iostream. Когда мы пишем #include <iostream> , мы делаем запрос, чтобы всё содержимое заголовочного файла iostream было скопировано в наш файл. Таким образом, всё содержимое библиотеки iostream становится доступным для использования.
Как правило, в заголовочных файлах записываются только объявления, без определений. Следовательно, если cout только объявлен в заголовочном файле iostream, то где же он определяется? Ответ: в Стандартной библиотеке С++, которая автоматически подключается к вашему проекту на этапе линкинга.

Подумайте о последствиях отсутствия заголовочного файла iostream. Каждый раз, при использовании cout, вам бы приходилось вручную копировать все предварительные объявления, связанные с cout в верхнюю часть вашего файла! Хорошо ведь, что можно просто указать #include <iostream> , не так ли?
Пишем свои собственные заголовочные файлы
Теперь давайте вернемся к примеру, который мы обсуждали на предыдущем уроке. У нас было два файла: add.cpp и main.cpp.
Примечание: Если вы создаете все файлы заново, то не забудьте добавить add.cpp в свой проект, чтобы он был подключен к компиляции.
Мы использовали предварительное объявление, чтобы сообщить компилятору, что такое add(). Как мы уже говорили, записывать в каждом файле предварительные объявления используемых функций — дело не слишком увлекательное.
И здесь нам на помощь приходят заголовочные файлы. Достаточно просто написать один заголовочный файл и его можно будет повторно использовать в любом количестве программ. Также и вносить изменения в такой код (например, добавление еще одного параметра) гораздо легче, нежели чем шерстить по всем файлам в поисках используемых функций.
Написать свой собственный заголовочный файл не так уж и сложно. Заголовочные файлы состоят из двух частей:
Директивы препроцессора — в частности, header guards, которые предотвращают вызов заголовочного файла больше одного раза из одного и того же файла (об этом детально на следующем уроке).
Содержимое заголовочного файла — набор объявлений.
Все ваши заголовочные файлы (которые вы написали самостоятельно) должны иметь расширение .h .
Чтобы использовать этот файл в main.cpp, вам сначала нужно будет подключить его к проекту.
main.cpp, в котором мы подключаем add.h:
add.cpp остается без изменений:
Когда компилятор встречает #include «add.h» , он копирует всё содержимое add.h в текущий файл. Таким образом, мы получаем предварительное объявление функции add().
Примечание: При подключении заголовочного файла, всё его содержимое вставляется сразу же после строки #include . .
Если вы получили ошибку от компилятора, что add.h не найден, то убедитесь, что имя вашего файла точно «add.h». Вполне возможно, что вы могли сделать опечатку, например, просто «add» (без «.h») или «add.h.txt» или «add.hpp».
Если вы получили ошибку от линкера, что функция аdd() не определена, то убедитесь, что вы корректно подключили add.cpp к вашему проекту (и к компиляции тоже)!
Угловые скобки (<>) vs. Двойные кавычки ("")
Вы, наверное, хотите узнать, почему используются угловые скобки для iostream и двойные кавычки для add.h. Дело в том, что, используя угловые скобки, мы сообщаем компилятору, что подключаемый заголовочный файл написан не нами (он является «системным», т.е. предоставляется Стандартной библиотекой С++), так что искать этот заголовочный файл следует в системных директориях. Двойные кавычки сообщают компилятору, что мы подключаем наш собственный заголовочный файл, который мы написали самостоятельно, поэтому искать его следует в текущей директории нашего проекта. Если файла там не окажется, то компилятор начнет проверять другие пути, в том числе и системные директории.
Правило: Используйте угловые скобки для подключения «системных» заголовочных файлов и двойные кавычки для ваших заголовочных файлов.
Стоит отметить, что одни заголовочные файлы могут подключать другие заголовочные файлы. Тем не менее, так делать не рекомендуется.
Почему iostream пишется без окончания .h?
Еще один часто задаваемый вопрос: «Почему iostream (или любой другой из стандартных заголовочных файлов) при подключении пишется без окончания «.h»?». Дело в том, что есть 2 отдельных файла: iostream.h (заголовочный файл) и просто iostream! Для объяснения потребуется краткий экскурс в историю.
Когда C++ только создавался, все файлы библиотеки Runtime имели окончание .h. Оригинальные версии cout и cin объявлены в iostream.h. При стандартизации языка С++ комитетом ANSI, решили перенести все функции из библиотеки Runtime в пространствo имен std, чтобы предотвратить возможность возникновения конфликтов имен с пользовательскими идентификаторами (что, между прочим, является хорошей идеей). Тем не менее, возникла проблема: если все функции переместить в пространство имен std, то старые программы переставали работать!
Для обеспечения обратной совместимости ввели новый набор заголовочных файлов с теми же именами, но без окончания «.h». Весь их функционал находится в пространстве имен std. Таким образом, старые программы с #include <iostream.h> не нужно было переписывать, а новые программы уже могли использовать #include <iostream> .
Когда вы подключаете заголовочный файл из Стандартной библиотеки C++, убедитесь, что вы используете версию без .h (если она существует). В противном случае, вы будете использовать устаревшую версию заголовочного файла, который уже больше не поддерживается.
Кроме того, многие библиотеки, унаследованные от языка Cи, которые до сих пор используются в C++, также были продублированы с добавлением префикса c (например, stdlib.h стал cstdlib). Функционал этих библиотек также перенесли в пространство имен std, чтобы избежать возможность возникновения конфликтов имен с пользовательскими идентификаторами.
Правило: При подключении заголовочных файлов из Стандартной библиотеки С++, используйте версию без «.h» (если она существует). Пользовательские заголовочные файлы должны иметь окончание «.h».
Можно ли записывать определения в заголовочных файлах?
Язык C++ не будет жаловаться, если вы это сделаете, но так делать не принято.
Как уже было сказано выше, при подключении заголовочного файла, всё его содержимое вставляется сразу же после строки с #include. Это означает, что любые определения, которые есть в заголовочном файле, скопируются в ваш файл.
Для небольших проектов, это, скорее всего, не будет проблемой. Но для более крупных это может способствовать увеличению времени компиляции (так как код будет повторно компилироваться) и размеру исполняемого файла. Если внести изменения в определения, которые находятся в файле .cpp, то перекомпилировать придется только этот файл. Если же внести изменения в определения, которые записаны в заголовочном файле, то перекомпилировать придется каждый файл, который подключает этот заголовок, используя директиву препроцессора #include. И вероятность того, что из-за одного небольшого изменения вам придется перекомпилировать весь проект, резко возрастает!
Иногда делаются исключения для простых функций, которые вряд ли изменятся (например, где определение состоит всего лишь из одной строки).
Советы
Вот несколько советов по написанию собственных заголовочных файлов:
Всегда используйте директивы препроцессора.
Не определяйте переменные в заголовочных файлах, если это не константы. Заголовочные файлы следует использовать только для объявлений.
2.10 – Заголовочные файлы
По мере того, как программы становятся больше (и используют больше файлов), становится всё более утомительным давать предварительные объявления каждой функции, которую вы хотите использовать, и которая определена в другом файле. Было бы неплохо, если бы вы могли поместить все свои предварительные объявления в одно место, а затем импортировать их, когда они вам понадобятся?
Исходные файлы кода C++ (с расширением .cpp ) – это не единственные файлы, которые обычно встречаются в программах на C++. Другой тип файлов – это заголовочный файл (иногда просто заголовок). Заголовочные файлы обычно имеют расширение .h , но иногда вы можете встретить их с расширением .hpp или вообще без расширения. Основная цель заголовочного файла – распространять объявления в исходные файлы кода.
Ключевой момент
Заголовочные файлы позволяют нам размещать объявления в одном месте, а затем импортировать их туда, где они нам нужны. Это может сэкономить много времени при наборе текста в проектах из нескольких файлов.
Использование заголовочных файлов стандартной библиотеки
Рассмотрим следующую программу:
Эта программа печатает « Hello, world! » в консоль с помощью std::cout . Однако эта программа никогда не предоставляла определение или объявление для std::cout , поэтому как компилятор узнает, что такое std::cout ?
Ответ заключается в том, что std::cout был предварительно объявлен в заголовочном файле « iostream ». Когда мы пишем #include <iostream> , мы запрашиваем, чтобы препроцессор скопировал всё содержимое (включая предварительные объявления для std::cout ) из файла с именем « iostream » в файл, выполняющий #include .
Ключевой момент
Когда вы включаете файл с помощью #include , содержимое включаемого файла вставляется в точке включения. Это удобный способ извлечения объявлений из другого файла.
Подумайте, что бы произошло, если бы заголовок iostream не существовал. Каждый раз, когда вы хотели бы использовать std::cout , вам приходилось бы вручную вводить или копировать все объявления, связанные с std::cout , в начало каждого файла, который использовал бы std::cout ! Для этого потребуется много знаний о том, как реализован std::cout , и потребуется много работы. Хуже того, если бы прототип функции изменился, нам пришлось бы вручную обновлять все предварительные объявления. Намного проще просто включить iostream с помощью #include !
Когда дело доходит до функций и переменных, стоит помнить, что заголовочные файлы обычно содержат только объявления функций и переменных, а не их определения (в противном случае может произойти нарушение правила одного определения). std::cout объявлен в заголовке iostream, но определен как часть стандартной библиотеки C++, которая автоматически подключается к вашей программе на этапе линкера.
Рисунок 1 – Диаграмма процесса сборки
Лучшая практика
Заголовочные файлы обычно не должны содержать определений функций и переменных, чтобы не нарушать правило одного определения. Исключение сделано для символьных констант (которые мы рассмотрим в уроке «4.14 – const, constexpr и символьные константы»).
Написание собственных заголовочных файлов
А теперь вернемся к примеру, который мы обсуждали в предыдущем уроке. Когда мы закончили, у нас было два файла, add.cpp и main.cpp , которые выглядели так:
(Если вы воссоздаете этот пример с нуля, не забудьте добавить add.cpp в свой проект, чтобы он компилировался).
В этом примере мы использовали предварительное объявление, чтобы при компиляции main.cpp компилятор знал, что такое идентификатор add . Как упоминалось ранее, добавление предварительных объявлений для каждой функции, которую вы хотите использовать, и которая находится в другом файле, вручную может быстро стать утомительным.
Давайте напишем заголовочный файл, чтобы избавиться от этого бремени. Написать заголовочный файл на удивление легко, поскольку файлы заголовков состоят только из двух частей:
- защита заголовка, о которой мы поговорим более подробно в следующем уроке («2.11 – Защита заголовков»);
- фактическое содержимое файла заголовка, которое должно быть предварительными объявлениями для всех идентификаторов, которые мы хотим, чтобы другие файлы могли видеть.
Добавление заголовочного файла в проект работает аналогично добавлению исходного файла (рассматривается в уроке «2.7 – Программы с несколькими файлами исходного кода»). Если вы используете IDE, выполните такие же действия и при появлении запроса выберите Файл заголовка (или C/C++ header) вместо Файла С++ (или C/C++ source). Если вы используете командную строку, просто создайте новый файл в своем любимом редакторе.
Лучшая практика
При именовании файлов заголовков используйте расширение .h .
Заголовочные файлы часто идут в паре с файлами исходного кода, при этом заголовочный файл предоставляет предварительные объявления для соответствующего исходного файла. Поскольку наш заголовочный файл будет содержать предварительное объявление для функций, определенных в add.cpp , мы назовем наш новый заголовочный файл add.h .
Лучшая практика
Если заголовочный файл идет в паре с файлом исходного кода (например, add.h с add.cpp ), они оба должны иметь одинаковое базовое имя ( add ).
Вот наш завершенный заголовочный файл:
Чтобы использовать этот заголовочный файл в main.cpp , мы должны включить его с помощью #include (используя кавычки, а не угловые скобки).
Когда препроцессор обрабатывает строку #include "add.h" , он копирует содержимое add.h в текущий файл в эту точку. Поскольку наш add.h содержит предварительное объявление для функции add , это предварительное объявление будет скопировано в main.cpp . Конечным результатом является программа, которая функционально аналогична той, в которой мы вручную добавили предварительное объявление вверху main.cpp .
Следовательно, наша программа будет правильно компилироваться и компоноваться.
Рисунок 2 – Диаграмма процесса сборки
Включение заголовочного файла в соответствующий исходный файл
Позже вы увидите, что большинство исходных файлов включают свой соответствующий заголовочный файл, даже если он им не нужен. Зачем?
Включение заголовочного файла в исходный файл увеличивает прямую совместимость. Очень вероятно, что в будущем вы добавите больше функций или измените существующие таким образом, что им нужно будет знать о существовании друг друга.
Когда мы углубимся в изучение стандартной библиотеки, вы будете включать множество заголовочных файлов библиотек. Если вам потребовалось включение в заголовочном файле, оно, вероятно, понадобилось вам для объявления функции. Это означает, что вам также потребуется такое же включение в исходный файл. Это приведет к тому, что в исходном файле у вас будет копия включений заголовочного файла. Включив заголовочный файл в исходный файл, исходный файл получит доступ ко всему, к чему имел доступ заголовочный файл.
При разработке библиотеки включение заголовочного файла в исходный файл может даже помочь в раннем обнаружении ошибок.
Лучшая практика
При написании исходного файла включите в него соответствующий заголовочный файл (если он существует), даже если он вам пока не нужен.
Поиск и устранение проблем
Если вы получаете ошибку компилятора, указывающую, что add.h не найден, убедитесь, что файл действительно называется add.h . В зависимости от того, как вы его создали и назвали, возможно, файл может иметь имя вроде add (без расширения), add.h.txt или add.hpp . Также убедитесь, что он находится в том же каталоге, что и остальные исходные файлы.
Если вы получаете сообщение об ошибке компоновщика о том, что добавление функции не определено, убедитесь, что вы добавили в проект файл add.cpp , чтобы определение для функции add можно было слинковать в программе.
Угловые скобки и двойные кавычки
Вам, наверное, интересно, почему мы используем угловые скобки для iostream и двойные кавычки для add.h . Возможно, что заголовочные файлы с таким же именем могут существовать в нескольких каталогах. Использование угловых скобок и двойных кавычек помогает компилятору понять, где ему следует искать заголовочные файлы.
Когда мы используем угловые скобки, мы сообщаем препроцессору, что это заголовочный файл, который мы не писали сами. Компилятор будет искать заголовок только в каталогах, указанных в каталогах включаемых файлов (include directories). Каталоги включаемых файлов настраиваются как часть вашего проекта / настроек IDE / настроек компилятора и обычно по умолчанию используются для каталогов, содержащих заголовочные файлы, которые поставляются с вашим компилятором и/или ОС. Компилятор не будет искать заголовочный файл в каталоге исходного кода вашего проекта.
Когда мы используем двойные кавычки, мы сообщаем препроцессору, что это заголовочный файл, который написали мы. Компилятор сначала будет искать этот заголовочный файл в текущем каталоге. Если он не сможет найти там подходящий заголовочный файл, он будет искать его в каталогах включаемых файлов.
Правило
Используйте двойные кавычки, чтобы включать заголовочные файлы, которые написали вы или которые, как ожидается, будут найдены в текущем каталоге. Угловые скобки используйте, чтобы включать заголовочные файлы, которые поставляются с вашим компилятором, ОС или сторонними библиотеками, которые вы установили в другом месте своей системы.
Почему у iostream нет расширения .h ?
Другой часто задаваемый вопрос: «Почему iostream (или любой другой заголовочный файл стандартной библиотеки) не имеет расширения .h ?». Ответ заключается в том, что iostream.h – это другой заголовочный файл, отличающийся от iostream ! Для объяснения требуется небольшой урок истории.
Когда C++ был только создан, все файлы в стандартной библиотеке оканчивались расширением .h . Жизнь была последовательной, и это было хорошо. Исходные версии cout и cin были объявлены в iostream.h . Когда комитет ANSI стандартизировал язык, они решили переместить все функции стандартной библиотеки в пространство имен std , чтобы избежать конфликтов имен с пользовательскими идентификаторами. Однако это представляло проблему: если бы они переместили всю функциональность в пространство имен std , ни одна из старых программ (включая iostream.h ) больше не работала бы!
Чтобы обойти эту проблему, был представлен новый набор заголовочных файлов, которые используют те же имена, но не имеют расширения .h . Все функции в этих новых заголовочных файлах находятся в пространстве имен std . Таким образом, старые программы, содержащие #include <iostream.h> , не нужно переписывать, а новые программы могут использовать #include <iostream> .
Кроме того, многие библиотеки, унаследованные от C, которые всё еще используются в C++, получили префикс c (например, stdlib.h стал cstdlib ). Функциональные возможности этих библиотек также были перенесены в пространство имен std , чтобы избежать конфликтов имен.
Лучшая практика
При включении заголовочного файла из стандартной библиотеки используйте версию без расширения (без .h ), если она существует. Пользовательские заголовочные файлы по-прежнему должны использовать расширение .h .
Включение заголовочных файлов из других каталогов
Другой распространенный вопрос связан с тем, как включать заголовочные файлы из других каталогов.
Один (плохой) способ сделать это – добавить относительный путь к заголовочному файлу, который вы хотите включить как часть строки #include . Например:
Хотя это будет компилироваться (при условии, что файлы существуют в этих относительных каталогах), обратная сторона этого подхода состоит в том, что он требует от вас отражения структуры каталогов в вашем коде. Если вы когда-нибудь обновите структуру каталогов, ваш код больше не будет работать.
Лучший способ – сообщить вашему компилятору или IDE, что у вас есть куча заголовочных файлов в каком-то другом месте, чтобы он смотрел туда, когда не может найти их в текущем каталоге. Обычно это можно сделать, установив путь включения (include path) или каталог поиска (search directory) в настройках проекта в IDE.
Для пользователей Visual Studio
Кликните правой кнопкой мыши на своем проекте в обозревателе решений и выберите Свойства (Properties), затем вкладку Каталоги VC++.(VC++ Directories). Здесь вы увидите строку с названием «Включаемые каталоги» (Include Directories). Добавьте каталоги, в которых компилятор должен искать дополнительные заголовочные файлы.
Для пользователей Code::Blocks
В Code:: Blocks перейдите в меню Project (Проект) и выберите Build Options (Параметры сборки), затем вкладку Search directories (Каталоги поиска). Добавьте каталоги, в которых компилятор должен искать дополнительные заголовочные файлы.
Для пользователей GCC/G++
Используя g++, вы можете использовать параметр -I , чтобы указать альтернативный каталог для включения.
Хороший момент в этом подходе заключается в том, что если вы когда-нибудь измените структуру каталогов, вам нужно будет изменить только одну настройку компилятора или IDE, а не каждый файл кода.
Заголовочные файлы могут включать другие заголовочные файлы
Обычно для заголовочных файлов требуется объявление или определение, которое находится в другом заголовочном файле. Из-за этого заголовочные файлы часто включают с помощью #include другие заголовочные файлы.
Когда ваш исходный файл включает с помощью #include первый заголовочный файл, вы также получите любые другие заголовочные файлы, которые были включены в первый заголовочный файл (и любые заголовочные файлы, которые были включены в предыдущие, и т.д.). Эти дополнительные заголовочные файлы иногда называют «транзитивными включениями», поскольку они включаются неявно.
Содержимое этих транзитивных включений доступно для использования в вашем файле исходного кода. Однако не следует полагаться на содержимое заголовков, которые включены транзитивно. Реализация заголовочных файлов может со временем меняться или отличаться в разных системах. Если это произойдет, ваш код может компилироваться только на определенных системах или может компилироваться сейчас, но перестать в будущем. Этого легко избежать, явно включив все заголовочные файлы, необходимые для содержимого вашего файла исходного кода.
Лучшая практика
Каждый файл должен явно включать с #include все заголовочные файлы, необходимые для компиляции. Не полагайтесь на заголовочные файлы, включенные транзитивно из других заголовков.
К сожалению, нет простого способа определить, полагается ли ваш файл кода случайно на содержимое заголовочного файла, который был включен другим заголовочным файлом.
Вопрос: Я не включил <someheader.h> , и моя программа всё равно работала! Почему?
Это один из наиболее часто задаваемых вопросов. Ответ: скорее всего, он работает, потому что вы включили какой-то другой заголовок (например, <iostream> ), который сам включает <someheader.h> . Несмотря на то, что ваша программа будет компилироваться, в соответствии с приведенными выше рекомендациями вам не следует полагаться на это. То, что компилируется у вас, может не компилироваться на машине друга.
Порядок #include заголовочных файлов
Если ваши заголовочные файлы написаны правильно и включают с #include всё, что им нужно, порядок включения не имеет значения. Однако включение заголовочных файлов в определенном порядке может помочь выявить ошибки, когда заголовочные файлы могут не включать в себя всё, что им нужно.
Лучшая практика
Упорядочьте свои включения с #include следующим образом: сначала ваши собственные пользовательские заголовки, затем заголовки сторонних библиотек, затем заголовки стандартных библиотек; заголовки в каждом разделе должны быть отсортированы в алфавитном порядке.
Таким образом, если в одном из ваших пользовательских заголовков отсутствует #include для заголовка сторонней библиотеки или стандартной библиотеки, это с большей вероятностью вызовет ошибку компиляции, чтобы вы могли ее исправить.
Рекомендации по использованию заголовочных файлов
Вот еще несколько рекомендаций по созданию и использованию заголовочных файлов.
C Language
Создание и включение файлов заголовков
В современных C заголовочные файлы являются важными инструментами, которые необходимо разрабатывать и использовать правильно. Они позволяют компилятору перекрестно проверять самостоятельно скомпилированные части программы.
Заголовки объявляют типы, функции, макросы и т. Д., Которые нужны потребителям набора средств. Весь код, который использует любой из этих объектов, включает заголовок. Весь код, определяющий эти объекты, включает заголовок. Это позволяет компилятору проверить соответствие совпадений и определений.
Вступление
При создании и использовании файлов заголовков в проекте C существует ряд рекомендаций:
Если заголовочный файл несколько раз включается в блок трансляции (TU), он не должен нарушать сборки.
Если вам нужны средства, объявленные в файле заголовка, вам не нужно включать какие-либо другие заголовки явно.
Вы не должны удалять информацию из заголовка, не вызывая сбоев сборки.
Включить то, что вы используете (IWYU)
Больше заботы о C ++, чем C, но тем не менее важны и для C. Если код в TU (вызовите его code.c ) напрямую использует функции, объявленные заголовком (назовите его "headerA.h" ), тогда code.c должен #include "headerA.h" напрямую, даже если TU включает другой заголовок (назовите его "headerB.h" ), который в настоящий момент происходит с включением "headerA.h" .
Иногда бывает достаточно оснований для нарушения одного или нескольких из этих рекомендаций, но вы должны знать, что нарушаете правило и осознаете последствия этого, прежде чем нарушить его.
идемпотентность
Если конкретный заголовочный файл включен более одного раза в блок трансляции (TU), не должно быть проблем с компиляцией. Это называется «идемпотенция»; ваши заголовки должны быть идемпотентными. Подумайте, насколько сложной была бы жизнь, если бы вы должны были убедиться, что #include <stdio.h> был включен только один раз.
Существует два способа достижения идемпотенциала: защита заголовков и директива #pragma once .
Защитные кожухи
Защитные решетки просты и надежны и соответствуют стандарту C. Первые строки без комментариев в файле заголовка должны иметь следующий вид:
Последняя строка без комментария должна быть #endif , необязательно с комментарием после нее:
Весь операционный код, включая другие директивы #include , должен находиться между этими строками.
Каждое имя должно быть уникальным. Часто используется схема имен, такая как HEADER_H_INCLUDED . В некотором более старом коде используется символ, обозначенный как защита заголовка (например, #ifndef BUFSIZ в <stdio.h> ), но он не так надежен, как уникальное имя.
Один из вариантов заключается в использовании сгенерированного хеша MD5 (или другого) для имени защитника заголовка. Вам следует избегать эмуляции схем, используемых заголовками системы, которые часто используют имена, зарезервированные для имен реализации, начиная с символа подчеркивания, за которым следует либо другое подчеркивание, либо буква верхнего регистра.
Директива #pragma once
В качестве альтернативы, некоторые компиляторы поддерживают директиву #pragma once которая имеет тот же эффект, что и три строки, показанные для защиты заголовков.
Компиляторы, которые поддерживают #pragma once включают MS Visual Studio и GCC и Clang. Однако, если переносимость вызывает беспокойство, лучше использовать защиту заголовков или использовать оба варианта. Современные компиляторы (поддерживающие C89 или более поздние) должны игнорировать, без комментариев, прагмы, которые они не распознают («Любая такая прагма, которая не распознается реализацией, игнорируется»), но старые версии GCC не были настолько снисходительными.
Автономность
Современные заголовки должны быть автономными, а это значит, что программа, которая должна использовать средства, определенные header.h может включать этот заголовок ( #include "header.h" ) и не беспокоиться о том, нужно ли сначала включать другие заголовки.
Рекомендация: Файлы заголовков должны быть автономными.
Исторические правила
Исторически сложилось так, что это был спорный спорный вопрос.
Файлы заголовков не должны быть вложенными. Поэтому пролог для файла заголовка должен описывать, какие другие заголовки должны быть #include d для того, чтобы заголовок был функциональным. В крайних случаях, когда большое количество файлов заголовков должно быть включено в несколько разных исходных файлов, допустимо поместить все обычные #include s в один файл include.
Это противоположность самоограничения.
Современные правила
Однако с тех пор мнение имеет тенденцию в противоположном направлении. Если исходный файл должен использовать средства, объявленные заголовком header.h , программист должен иметь возможность писать:
и (при условии наличия правильных путей поиска, установленных в командной строке), любые необходимые предварительные заголовки будут включены header.h без необходимости добавления дополнительных заголовков в исходный файл.
Это обеспечивает лучшую модульность исходного кода. Он также защищает источник от «догадки, почему этот заголовок был добавлен», головоломка, которая возникает после того, как код был изменен и взломан на десятилетие или два.
Стандарты кодирования космического полета NASA Goddard (GSFC) для C — один из самых современных стандартов, но сейчас его трудно отследить. В нем говорится, что заголовки должны быть автономными. Он также обеспечивает простой способ гарантировать, что заголовки являются автономными: файл реализации для заголовка должен включать заголовок в качестве первого заголовка. Если он не является самодостаточным, этот код не будет компилироваться.
Обоснование, данное GSFC, включает:
§2.1.1 Заголовок включает в себя обоснование
Этот стандарт требует, чтобы заголовок блока содержал инструкции #include для всех других заголовков, требуемых заголовком блока. Размещение #include для заголовка блока сначала в блоке устройства позволяет компилятору проверить, содержит ли заголовок все необходимые инструкции #include .
Альтернативный дизайн, не разрешенный этим стандартом, не допускает операторов #include в заголовках; все #includes производятся в файлах body. Заголовочные файлы блока должны содержать инструкции #ifdef, которые проверяют, что требуемые заголовки включены в правильный порядок.
Одно из преимуществ альтернативного дизайна заключается в том, что список #include в основном файле — это список зависимостей, необходимый в make-файле, и этот список проверяется компилятором. При стандартном дизайне инструмент должен использоваться для создания списка зависимостей. Тем не менее, все рекомендованные отраслью среды разработки предоставляют такой инструмент.
Основным недостатком альтернативного дизайна является то, что если список требуемых заголовков блока изменяется, каждый файл, который использует этот модуль, должен быть отредактирован, чтобы обновить список операторов #include . Кроме того, требуемый список заголовков для библиотеки библиотеки компилятора может отличаться для разных целей.
Другим недостатком альтернативного дизайна является то, что файлы заголовков библиотеки компилятора и другие файлы сторонних разработчиков должны быть изменены для добавления необходимых операторов #ifdef .
Таким образом, самоограничение означает, что:
- Если заголовку header.h нужен новый вложенный заголовок extra.h , вам не нужно проверять каждый исходный файл, который использует header.h чтобы узнать, нужно ли вам добавлять extra.h .
- Если заголовку header.h больше не нужно включать определенный заголовок notneeded.h , вам не нужно проверять каждый исходный файл, который использует header.h чтобы увидеть, можете ли вы безопасно удалить notneeded.h (но см. notneeded.h Включить то, что вы используете» .
- Вам не нужно устанавливать правильную последовательность для включения необходимых заголовков (что требует топологической сортировки для правильной работы).
Проверка самоограничения
См. Ссылку на статическую библиотеку для скрипта chkhdr который может использоваться для проверки идемпотенции и самоограничения файла заголовка.
Минимальность
Заголовки — это решающий механизм проверки согласованности, но они должны быть как можно меньше. В частности, это означает, что заголовок не должен включать другие заголовки только потому, что для файла реализации понадобятся другие заголовки. Заголовок должен содержать только те заголовки, которые необходимы для потребителя описанных услуг.
Например, заголовок проекта не должен включать <stdio.h> если только один из функциональных интерфейсов не использует тип FILE * (или один из других типов, определенных исключительно в <stdio.h> ). Если интерфейс использует size_t , наименьший заголовок, который достаточно, <stddef.h> . Очевидно, что если включен другой заголовок, который определяет size_t , нет необходимости включать <stddef.h> .
Если заголовки минимальны, то это также приводит к минимуму времени компиляции.
Можно создать заголовки, единственной целью которых является включение множества других заголовков. Они редко оказываются хорошей идеей в долгосрочной перспективе, потому что для нескольких исходных файлов понадобятся все средства, описанные всеми заголовками. Например, можно было бы разработать <standard-ch> , который включает все стандартные заголовки C — с осторожностью, поскольку некоторые заголовки не всегда присутствуют. Однако очень немногие программы фактически используют средства <locale.h> или <tgmath.h> .
- См. Также Как связать несколько файлов реализации в C?
Включить то, что вы используете (IWYU)
Проект Google Include What You Use , или IWYU, гарантирует, что исходные файлы включают все заголовки, используемые в коде.
Предположим, что исходный файл source.c содержит заголовок arbitrary.h который, в свою очередь, случайно включает freeloader.h , но исходный файл также явно и независимо использует средства из freeloader.h . Все хорошо начать. Затем в один прекрасный день arbitrary.h изменяется, поэтому его клиентам больше не нужны средства freeloader.h . Внезапно source.c прекращает компиляцию — поскольку он не соответствует критериям IWYU. Поскольку код в source.c явно использовал возможности freeloader.h , он должен был включить то, что он использует — в источнике также должен был быть явно #include "freeloader.h" . ( Идемпотентность гарантировала бы, что проблем не было.)
Философия IWYU максимизирует вероятность того, что код продолжает компилироваться даже при разумных изменениях, внесенных в интерфейсы. Очевидно, что если ваш код вызывает функцию, которая впоследствии удаляется из опубликованного интерфейса, никакая подготовка не может препятствовать изменениям. Вот почему, когда это возможно, избегаются изменения API-интерфейсов и почему существуют циклы отказов по нескольким выпускам и т. Д.
Это особая проблема в C ++, потому что стандартным заголовкам разрешено включать друг друга. Исходный файл file.cpp может включать один заголовок header1.h который на одной платформе включает другой заголовок header2.h . file.cpp может использовать средства header2.h . Первоначально это не было проблемой — код будет компилироваться, поскольку header1.h включает header2.h . На другой платформе или обновлении текущей платформы header1.h можно было бы пересмотреть, чтобы она больше не включала header2.h , а затем file.cpp прекратил компиляцию в результате.
IWYU обнаружит проблему и порекомендует, что header2.h будет включен непосредственно в file.cpp . Это обеспечило бы ее компиляцию. Аналогичные соображения также относятся к коду C.
Обозначение и сборник
В стандарте C говорится, что между обозначениями #include <header.h> и #include "header.h" очень мало различий.
[ #include <header.h> ] ищет последовательность определённых реализацией мест для заголовка, идентифицированного однозначно указанной последовательностью между разделителями < и > и вызывает замену этой директивы на все содержимое заголовка. Как указано места, или идентифицированный заголовок определяется реализацией.
[ #include "header.h" ] вызывает замену этой директивы всем содержимым исходного файла, идентифицированного указанной последовательностью между разделителями "…" . Именованный исходный файл выполняется поисковым способом. Если этот поиск не поддерживается или если поиск не выполняется, директива перерабатывается, как если бы она читала [ #include <header.h> ] .
Таким образом, двойная кавычка может выглядеть в большем количестве мест, чем форма с угловыми скобками. Стандарт указывает на пример, что стандартные заголовки должны быть включены в угловые скобки, даже если компиляция работает, если вместо этого использовать двойные кавычки. Аналогично, такие стандарты, как POSIX, используют формат с угловым скобкой — и вам тоже нужно. Зарезервировать заголовки с двойными кавычками для заголовков, определенных проектом. Для внешних заголовков (включая заголовки других проектов, на которые опирается ваш проект), наиболее подходящим является обозначение углового кронштейна.
Обратите внимание, что между #include и заголовком должно быть пробел, хотя компиляторы не будут там места. Пробелы дешевы.
В ряде проектов используются такие обозначения, как:
Вы должны подумать, следует ли использовать этот контроль пространства имен в вашем проекте (это, скорее всего, хорошая идея). Вы должны избегать имен, используемых существующими проектами (в частности, как sys и linux будут плохими выборами).
Если вы используете это, ваш код должен быть осторожным и последовательным в использовании обозначений.
Не используйте обозначения #include "../include/header.h" .
Заголовочные файлы должны редко задавать переменные. Хотя вы сохраните глобальные переменные до минимума, если вам нужна глобальная переменная, вы объявите ее в заголовке и определите ее в одном подходящем исходном файле, и этот исходный файл будет включать заголовок для перекрестной проверки декларации и определения , и все исходные файлы, которые используют переменную, будут использовать заголовок, чтобы объявить его.
Следствие: вы не будете объявлять глобальные переменные в исходном файле — исходный файл будет содержать только определения.
Заголовочные файлы должны редко объявлять static функции с заметным исключением static inline функций, которые будут определены в заголовках, если эта функция необходима в нескольких исходных файлах.