Урок 21. Заголовочные файлы

   ⁄ 

 Обновлено 24 Фев 2017

  ⁄   

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

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

Заголовочные файлы стандартной библиотеки

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

Она выводит “Hello, world!” в консоль с помощью cout. Тем не менее, в этой программе нет определения cout, тогда как компилятор знает, что такое cout? Дело в том, что cout объявлен в заголовочном файле, который называется «iostream». Когда мы пишем #include <iostream>, мы делаем запрос, чтобы всё содержимое из заголовочного файла с именем «iostream» скопировалось в наш файл. Таким образом всё содержимое «iostream» становится доступным для использования.

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

risunok1

Подумайте, что бы произошло, если бы заголовка iostream не существовало? Где бы вы ни использовали std::cout, вам пришлось бы вручную копировать все объявления, связанные с std::cout в верхнюю часть каждого вашего файла, который бы использовал эту команду! Хорошо, что можно просто #include <iostream>.

Пишем свой собственный заголовочный файл

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

add.cpp:

main.cpp:

(Если вы делаете всё по новой, то не забудьте добавить add.cpp в свой проект, чтобы он был подключен к компиляции).

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

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

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

Первая часть — header guard, об этом мы поговорим в следующем уроке (про препроцессоры). Header guards предотвращают вызовы (#includе) заголовочного файла больше одного раза с одного и того же файла.

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

add.h:

Чтобы использовать этот файл в main.cpp, мы должны его сначала подключить (include).

main.cpp, который include add.h:

add.cpp остается без изменений:

При компиляции строки #include «add.h», компилятор скопирует содержимое add.h в текущий файл. Поскольку add.h содержит прототип функции add(), то он используется в качестве предварительного объявления add()!

Примечание: когда вы подключаете (#include) файл, всё его содержимое вставляется сразу после строчки include в текущий файл.

risunok2

Если вы получите ошибку компилятора о том, что add.h не найден, убедитесь, что имя файла точно add.h. Вполне возможно, что при создании файла вы могли пропустить или указать не то, что нужно, например: просто “add” (без расширения) или «add.h.txt» или «add.hpp».

Если вы получите ошибку линкера о том, что аdd() не определен, убедитесь, что вы правильно подключили файл add.cpp в ваш проект и он подключен к компиляции вместе с другими файлами!

Угловые скобки (<>) vs Кавычки («»)

Вы, наверное, хотите узнать, почему мы используем угловые скобки для iostream и двойные кавычки для add.h. Дело в том, что используя угловые скобки, мы сообщаем компилятору, что подключаемый заголовочный файл написан не нами (он идет вместе с компилятором), так что искать этот header file нужно в системных директориях. Двойные кавычки сообщают компилятору, что мы подключаем заголовочный файл, который создали сами, поэтому искать его нужно в текущей директории, вместе с нашим исходным кодом. Если его там не окажется, то компилятор начнет проверять другие пути, в том числе и системные директории.

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

Одни заголовочные файлы могут подключать другие заголовочные файлы. Тем не менее, мы не рекомендуем это делать. Всегда подключайте заголовочные файлы отдельно в каждом файле .cpp.

Правило: Каждый файл .cpp должен #include все необходимые для компиляции заголовочные файлы.

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

Другой часто задаваемый вопрос: «Почему iostream (или любой другой из стандартных заголовочных файлов библиотеки) указывается без расширения .h?». Что ж, давайте разберемся. Есть два отдельных файла: iostream.h (заголовочный файл) и просто iostream! Для объяснения потребуется короткий экскурс в историю.

Когда C++ только создавался, все файлы стандартной библиотеке Runtime имели окончание .h. Оригинальные версии cout и cin жили в iostream.h. Когда уже язык был стандартизирован комитетом ANSI, они решили перенести все функции из библиотеки Runtime в пространствo имен std (что, между прочим, было хорошей идеей). Тем не менее, появилась проблема: если все функции переместить в std namespace, то старые программы переставали работать!

Дабы обойти эту проблему и обеспечить обратную совместимость для старых программ, был введен новый набор заголовочных файлов, которые используют одни и те же имена, но без расширения .h. Вся их функциональность находилась в std namespace. Таким образом, старые программы с #include <iostream.h> не нужно было переписывать и новые программы уже могли #include <iostream>.

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

Следует отметить, что многие заголовки в стандартной библиотеке имеют только .h версию. Для этих файлов допустимо подключать данную версию. Многие из этих библиотек имеют обратную совместимость со стандартом программирования C, а C не поддерживает namespace. Следовательно, функциональные возможности этих библиотек не будут доступны через пространство имен std. Кроме того, когда вы пишете свои собственные заголовочные файлы, вы должны им присваивать расширения .h, так как вы не будете помещать свой код в std namespace.

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

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

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

Плохой способ — записать относительный путь к файлу, который вы хотите подключить, как часть строки #include. Например:

Недостатком является отражение вашей структуры каталогов в коде. Если вы когда-нибудь обновите или измените эту структуру, ваш код перестанет работать.

Хороший способ – сообщить компилятору или IDE, что у вас есть куча заголовочных файлов в каком-то другом месте, куда нужно будет посмотреть, если компилятор не найдет их в текущей директории. Это осуществимо при настройке «include path» (подключить путь) или «search directory» (поиск каталога) в вашей IDE.

В Visual Studio нужно щелкнуть правой кнопкой мыши по вашему проекту в Solution Explorer и выбрать «Properties», затем вкладку «VC++ Directories». Вы увидите пункт «Include Directories». Добавьте туда свои другие каталоги.

В Code::Blocks, перейдите в меню Project и выберите «Build Options», затем вкладку «Search directories». Добавьте ваши каталоги.

С помощью G++, вы можете использовать опцию –I для указания альтернативного подключения директории:

g++ -o main -I /source/includes main.cpp

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

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

C++ не будет жаловаться, если вы это сделаете, но вообще такая практика не принята.

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

Для небольших проектов, это, вероятней всего, не будет большой проблемой. Но для более крупных, это может способствовать увеличению времени компиляции (так как этот же код будет компилироваться каждый раз, когда он встречается) и размеру исполняемого файла. Если внести изменения в .cpp файл, то только этот файл нужно будет перекомпилировать. Если внести изменения в определение в заголовочном файле, то каждый файл, который #include этот заголовок, нужно будет перекомпилировать. Из-за одного небольшого изменения вам придется перекомпилировать весь проект!

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

Советы

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

 Всегда подключайте header guards.

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

 Не определяйте функции в заголовочных файлах.

 Каждый заголовочный файл должен выполнять определенную работу и быть как можно более независимым. Например, вы можете поместить все ваши объявления, связанные с файлом А в файл A.h и все ваши объявления, связанные с B в файл B.h. Таким образом, если вы будете работать только с А, вам достаточно будет подключить только A.h.

 Называйте ваши заголовочные файлы именами исходных файлов, с которыми они связаны (например: grades.h работает с grades.cpp).

 Постарайтесь свести к минимуму количество других заголовочных файлов, которые вы #include. #include только самое необходимое.

 Не #include файлы .cpp.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (34 оценок, среднее: 4,82 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

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

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