Добавить защиту заголовков в следующий заголовочный файл add h int add int x int y

Обновлено: 30.06.2024

По мере того, как программы становятся все больше и больше (и включают в себя больше файлов), становится все более утомительным необходимость перенаправлять каждую функцию, которую вы хотите использовать, которая находится в другом файле. Было бы неплохо, если бы вы могли разместить все свои объявления в одном месте?

Файлы кода C++ (с расширением .cpp) — не единственные файлы, которые обычно встречаются в программах C++. Другой тип файла называется заголовочным файлом, который иногда называют включаемым файлом. Файлы заголовков обычно имеют расширение .h, но иногда вы можете увидеть их с расширением .hpp или вообще без расширения. Заголовочный файл предназначен для хранения объявлений, которые могут использоваться другими файлами.

Использование заголовочных файлов стандартной библиотеки

Рассмотрите следующую программу:

Имейте в виду, что файлы заголовков обычно содержат только объявления. Они не определяют, как что-то реализовано. Итак, если cout объявлен только в заголовочном файле «iostream», где он фактически определен? Он реализован в библиотеке поддержки среды выполнения C++, которая автоматически подключается к вашей программе на этапе компоновки.

Написание собственных заголовочных файлов

Теперь вернемся к примеру, который мы обсуждали на предыдущем уроке. Когда мы остановились, у нас было два файла, add.cpp и main.cpp, которые выглядели так:


int add ( int x , int y )
<
return x + y ;
>
main.cpp:

int add (int x, int y); // предварительная декларация с использованием прототипа функции

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

Кроме того, многие библиотеки, унаследованные от C, которые все еще использовались в C++, получили префикс c (например, stdlib.h стал cstdlib). Функциональность этих библиотек также была перенесена в пространство имен std, чтобы избежать конфликтов имен.

Однако, когда вы пишете свои собственные файлы заголовков, вы должны присвоить им всем расширение .h, поскольку вы не будете помещать свой код в пространство имен std.

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

Включение файлов заголовков из других каталогов

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

Лучше сообщить вашему компилятору или IDE, что у вас есть куча файлов заголовков в каком-то другом месте, чтобы он искал их там, когда не сможет найти их в текущем каталоге. Как правило, это можно сделать, задав «включаемый путь» или «каталог поиска» в настройках проекта IDE.

В Visual Studio вы можете щелкнуть правой кнопкой мыши свой проект в обозревателе решений и выбрать «Свойства», а затем вкладку «Каталоги VC++». Отсюда вы увидите строку под названием «Включить каталоги». Добавьте туда свои включаемые каталоги.

В Code::Blocks перейдите в меню «Проект» и выберите «Параметры сборки», затем вкладку «Поиск в каталогах». Добавьте туда свои включаемые каталоги.

Используя g++, вы можете использовать параметр -I, чтобы указать альтернативный каталог включения.
g++ - o main - I /source/ включает main . cpp
Преимущество этого подхода заключается в том, что если вы когда-нибудь измените структуру каталогов, вам нужно будет изменить только один параметр компилятора или IDE, а не каждый файл кода.

Можно ли поместить определения функций или переменных в заголовочный файл?

Не следует, так как это может привести к ошибкам компоновщика. Мы объясним, почему, в следующем уроке о защите заголовка.

В уроке 2.6 -- Предварительные объявления и определения мы отметили, что идентификатор переменной или функции может иметь только одно определение (правило одного определения). Таким образом, программа, определяющая идентификатор переменной более одного раза, вызовет ошибку компиляции:

Аналогичным образом программы, определяющие функцию более одного раза, также вызовут ошибку компиляции:

Рассмотрите следующий академический пример:

Хорошая новость заключается в том, что мы можем избежать вышеупомянутой проблемы с помощью механизма, называемого защитой заголовка (также называемой защитой включения). Защита заголовков — это директивы условной компиляции, которые имеют следующую форму:

Все ваши заголовочные файлы должны иметь защиту заголовков. SOME_UNIQUE_NAME_HERE может быть любым именем, которое вы хотите, но по соглашению задается полное имя файла заголовка, набранное всеми заглавными буквами, с использованием символов подчеркивания вместо пробелов или знаков препинания. Например, square.h будет иметь защиту заголовка:

Даже заголовки стандартной библиотеки используют защиту заголовков. Если бы вы взглянули на заголовочный файл iostream из Visual Studio, вы бы увидели:

Для опытных читателей

В больших программах могут быть два отдельных файла заголовков (включенных из разных каталогов), которые в конечном итоге будут иметь одинаковое имя файла (например,каталог A\config.h и каталог B\config.h). Если для включения защиты используется только имя файла (например, CONFIG_H), эти два файла могут в конечном итоге использовать одно и то же имя защиты. Если это произойдет, любой файл, который включает (прямо или косвенно) оба файла config.h, не получит содержимое включаемого файла, который будет включен вторым. Это, вероятно, вызовет ошибку компиляции.

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

_ _H , _ _H или _ _H

Обновление нашего предыдущего примера с помощью защиты заголовков

Вернемся к примеру square.h, используя square.h с защитой заголовка. Для удобства мы также добавим защиту заголовков в geometry.h.

После того, как препроцессор разрешает все включения, эта программа выглядит следующим образом:

Как видно из примера, второе включение содержимого square.h (из geometry.h) игнорируется, поскольку SQUARE_H был уже определен с первого включения. Поэтому функция getSquareSides включается только один раз.

Защита заголовков не препятствует однократному включению заголовка в разные файлы кода

Обратите внимание, что целью защиты заголовков является предотвращение получения файлом кода более одной копии защищенного заголовка. По своему замыслу средства защиты заголовков не предотвращают включение данного файла заголовка (один раз) в отдельные файлы кода. Это также может вызвать неожиданные проблемы. Учтите:

Обратите внимание, что square.h включен как из main.cpp, так и из square.cpp. Это означает, что содержимое square.h будет включено один раз в square.cpp и один раз в main.cpp.

Давайте рассмотрим, почему это происходит более подробно. Когда square.h включается из square.cpp, SQUARE_H определяется до конца square.cpp. Это определение предотвращает повторное включение square.h в square.cpp (что является целью защиты заголовков). Однако после завершения square.cpp SQUARE_H больше не считается определенным. Это означает, что когда препроцессор работает в main.cpp, SQUARE_H изначально не определен в main.cpp.

Конечным результатом является то, что и square.cpp, и main.cpp получают копию определения getSquareSides. Эта программа будет скомпилирована, но компоновщик будет жаловаться на то, что ваша программа имеет несколько определений для идентификатора getSquareSides!

Лучший способ обойти эту проблему — просто поместить определение функции в один из файлов .cpp, чтобы заголовок содержал только предварительное объявление:

Теперь, когда программа скомпилирована, функция getSquareSides будет иметь только одно определение (через square.cpp), поэтому компоновщик доволен. Файл main.cpp может вызывать эту функцию (даже несмотря на то, что он находится в файле square.cpp), поскольку он включает файл square.h, который предварительное объявление функции (компоновщик свяжет вызов getSquareSides из main.cpp с определением getSquareSides в square .cpp).

Можем ли мы просто избежать определений в файлах заголовков?

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

Есть довольно много случаев, которые мы покажем вам в будущем, когда необходимо поместить определения, не относящиеся к функциям, в заголовочный файл. Например, C++ позволяет создавать собственные типы. Эти определяемые пользователем типы обычно определяются в файлах заголовков, поэтому определения типов могут распространяться на файлы кода, которые должны их использовать. Без защиты заголовка файл кода может оказаться с несколькими (идентичными) копиями данного определения типа, что компилятор пометит как ошибку.

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

В целях совместимости мы рекомендуем использовать традиционные средства защиты заголовков. С ними не намного больше работы, и они гарантированно будут поддерживаться всеми совместимыми компиляторами.

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

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

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

При реализации защиты заголовка это упоминается следующим образом:

Все средства защиты заголовков позволяют включать ваши заголовки только один раз. (Если они включены несколько раз, они игнорируются.)

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

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

Я объявляю объявление в заголовочном файле и определения, или int main() входит в файл source.cpp.

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

Наконец, int main() не должен находиться в заголовочном файле. Он всегда должен находиться в файле .cpp.

Результатом предварительной обработки одного файла реализации (.cpp) является единица трансляции (TU).

Заголовки могут включать в себя другие заголовки, поэтому заголовок может косвенно включаться в одну и ту же ЕП несколько раз. (Ваш mymath.h является примером этого.)

Определения могут встречаться не более одного раза в каждой ЕП. (Некоторые определения также не должны находиться в нескольких ЕП; этот случай немного отличается и здесь не обсуждается.)

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

Защита включения используется только в заголовках. Не определяйте свою основную функцию в заголовке: поместите ее в файл реализации.

Если у вас есть заголовок, который будет определять тип и объявлять функцию, но вам также нужен сам заголовок:

«Обертывание» его включенными охранниками дает полное содержимое файла:

Имя, используемое для защиты включения, должно быть уникальным, иначе конфликтующие имена приведут к запутанным результатам. Эти имена — всего лишь простые макросы, и в языке нет ничего, что навязывало бы определенный стиль. Однако соглашения по проекту обычно налагают требования. Существует несколько различных стилей именования include guard, которые вы можете найти здесь, в SO и в других местах; этот ответ дает хорошие критерии и хороший обзор.

Практически каждый заголовочный файл должен следовать идиоме include guard:

мой-header-file.h

header-1.h

header-2.h

main.c

В этом коде есть серьезная проблема: подробное содержимое MyStruct определяется дважды, что недопустимо. Это может привести к ошибке компиляции, которую будет трудно отследить, поскольку один заголовочный файл включает в себя другой. Если вместо этого вы сделали это с защитой заголовка:

header-1.h

header-2.h

main.c

Затем это расширится до:

Когда компилятор достигает второго включения header-1.h, HEADER_1_H уже был определен предыдущим включением. Следовательно, это сводится к следующему:

Таким образом, ошибки компиляции нет.

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

Если бы детали структуры не были включены в заголовок, объявленный тип был бы неполным или непрозрачным. Такие типы могут быть полезны, скрывая детали реализации от пользователей функций. Для многих целей тип FILE в стандартной библиотеке C можно рассматривать как непрозрачный тип (хотя обычно он не является непрозрачным, так что макрореализации стандартных функций ввода-вывода могут использовать внутренности структуры). В этом случае header-1.h может содержать:

Обратите внимание, что структура должна иметь имя тега (здесь MyStruct — это пространство имен тегов, отдельное от пространства имен обычных идентификаторов имени typedef MyStruct ), и что < … >опущено. Это говорит о том, что "существует структура типа struct MyStruct и для нее есть псевдоним MyStruct".

В файле реализации можно определить детали структуры, чтобы сделать тип полным:

Если вы используете C11, вы можете повторить структуру typedef MyStruct MyStruct; объявление, не вызывая ошибки компиляции, но более ранние версии C выдавали ошибку. Следовательно, по-прежнему лучше использовать идиому include guard, хотя в этом примере она была бы необязательной, если бы код компилировался только компиляторами, поддерживающими C11.

мой-header-file.h

Каждой программе на C++ требуется по крайней мере один заголовочный файл для бесперебойной работы, например, большинству программ на C++ требуется функция cin для получения данных от пользователя и много другого предварительно написанного кода, который помогает упростить программирование, поэтому для использования такие функции, вам нужен заголовочный файл.

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

Область

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

Введение

Еще одна вещь, которую вы могли заметить в большинстве программ C++, это использование пространства имен std , в основном встроенные функции C++, такие как cout , cin , string и т. д., хранятся в стандартном пространстве имен. Поскольку эти функции широко используются, поэтому мы пишем с использованием пространства имен std в начале программы на C++, чтобы вам не приходилось снова и снова писать префикс std::.

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

Содержимое файла заголовка в C++

Файл заголовка в C++ содержит:

Определения функций

Файл заголовка содержит множество предопределенных функций, которые можно использовать, просто включив файл заголовка в нашу программу. Например, функция pow(a,b) в заголовочном файле math.h принимает два аргумента a a a , b b b и возвращает a b a^b a b ;

Определение типа данных

Файлы заголовков также могут содержать определения некоторых типов данных, которые часто используются программистами на C++, поэтому мы можем просто использовать эти предопределенные типы данных в нашей программе. Например: time_t — это арифметический тип данных, который используется для представления времени.

Макросы

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

Пример:

Обратите внимание, что определение макроса не заканчивается точкой с запятой ( ; ).

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

например:. __DATE__ хранит дату компиляции текущего исходного файла в форме mmm dd yyyy .

Классы

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

Типы заголовочных файлов C++

В C++ существует два типа файлов заголовков:

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

Чтобы создать файл заголовка, выполните следующие действия:

  1. Напишите код на C++ и сохраните его с расширением .h.

Допустим, мы сохранили указанный выше файл с кодом под именемmultiple.h.

Вывод:

Как работают заголовочные файлы C++?

Есть две вещи, которые необходимо помнить при включении заголовка:

<р>2. : это имя заголовка, который вы хотите включить.

Включение нескольких файлов заголовков в C++

Мы можем включать в нашу программу несколько заголовочных файлов, но когда заголовочный файл включается в программу более одного раза, компилятор дважды обрабатывает содержимое этого заголовочного файла, что приводит к ошибке в программе. Чтобы устранить эту ошибку, мы используем условные директивы препроцессора, известные как охранники.

Эти средства защиты предотвращают двойное включение файла заголовка.

Синтаксис:

Чтобы включить несколько файлов заголовков в зависимости от требований программы, используются несколько условий.

Синтаксис:

Стандартные заголовочные файлы и их использование

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

Ниже приведены некоторые библиотеки и их заголовочные файлы:

Библиотека ввода/вывода

Он используется для ввода и отображения вывода из консоли с помощью cin и cout соответственно.

Он используется для создания файлов, записи информации в файлы и чтения информации из файлов.

Библиотека чисел

Он используется для выполнения обычных математических операций, таких как sqrt() , pow() и т. д.

Операции со сложными числами и манипуляции с ними.

Библиотека алгоритмов

  • Он содержит алгоритмы, работающие с контейнерами C++, такими как векторы, карты и т. д.

Библиотека контейнеров

Векторы — это динамические массивы в C++.

Двусторонние очереди — это контейнеры последовательностей с функцией вставки и удаления элементов с обоих концов.

Наборы — это тип ассоциативного контейнера, в котором хранятся уникальные элементы. Значения хранятся в определенном порядке.

Карты — это ассоциативные контейнеры, в которых хранится пара ключ-значение, хранятся уникальные ключи.

Этот контейнер аналогичен набору, но он не хранит значения в упорядоченном виде и использует хеш-таблицы для хранения значений.

Этот контейнер аналогичен карте, но он не хранит значения в упорядоченном виде и использует хеш-таблицы для хранения значений.

Стеки – это тип контейнеров, в которых значения хранятся по принципу LIFO (последний пришел – первый ушел), элементы вставляются и удаляются только с одного конца.


Очередь — это тип контейнера, в котором значения хранятся в порядке FIFO (первым пришел — первым обслужен), элементы вставляются с одного конца и удаляются с другого конца.

Библиотека строк

Он используется для выполнения операций над строками. Некоторые из операций включают strcmp() , size() и т. д.

Содержит встроенные функции для обработки символов.

Угловые скобки ( <> ) и двойные кавычки ("" )

Есть два способа включить заголовочный файл в нашу программу:

Использование угловых скобок

Угловые скобки сообщают препроцессору, что мы не писали этот заголовочный файл, и сообщают компилятору, что нужно искать заголовочный файл только в каталогах, указанных в директиве include. Директива include настраивается как часть вашего проекта/настроек IDE/настроек компилятора. Компилятор не будет искать заголовочный файл в вашем текущем каталоге, в котором находится ваш исходный код.

Использование двойных кавычек

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

Почему у iostream нет расширения .h?

В старых версиях C++ все заголовочные файлы заканчивались расширением .h. Подлинная версия cout и cin была найдена в iostream.h. Когда язык стал стандартизирован комитетом ANSI, они переместили все функции внутри библиотеки времени выполнения в пространство имен std. Однако это создало проблемы, поскольку старые программы больше не работали.

Включение заголовочных файлов C++ из других каталогов

Есть два способа включения файлов заголовков из других каталогов:

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

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

Для этого используйте параметр -I, чтобы указать альтернативный включаемый каталог:

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

Рекомендации по работе с заголовочными файлами C++

Вот некоторые из рекомендаций, которым следует следовать при создании и использовании заголовочных файлов:

  • Всегда используйте защиту заголовков, потому что мы знаем, что в соответствии с одним правилом определения переменная или функция может иметь только одно определение, несколько определений приведут к ошибке компиляции. Средства защиты заголовков гарантируют, что конкретная переменная или функция будет включена в файл заголовка только один раз.
  • Не используйте функцию с одним и тем же именем в разных файлах заголовков, находящихся в одном проекте/каталоге, так как это также может привести к конфликту.
  • Имена файла заголовка и связанного с ним исходного файла должны совпадать.
  • Каждый заголовочный файл должен иметь определенное задание.
  • Включайте только те заголовочные файлы, функции которых вы хотите использовать, и не включайте без необходимости заголовочные файлы, которые вам не нужны, поскольку это только увеличит время выполнения вашей программы.
  • Если вы создаете файл заголовка, всегда сохраняйте его с расширением .h.
  • Определяемые пользователем файлы заголовков следует заключать в двойные кавычки, а для предварительно написанных файлов заголовков использовать угловые скобки.

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

мой_заголовок_файл.h

В приведенном выше примере показаны различные виды объявлений и определений, разрешенных в заголовочном файле.

Читайте также: