Найти тему

Основы С++: Знакомство с заголовочными файлами, создание пользовательских заголовков и повторение пройденного материала

Оглавление

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

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

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

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

Знакомство с заголовками на примере функций из стандартной библиотеки

Каждый раз, когда мы используем средства ввода с клавиатуры или вывода данных в консоль (std::cin/std::cout), мы обращаемся к заголовочному файлу iostream. Делаем мы это так: #include <iostream>

Выполнение этой директивы заставляет препроцессор вставить на место директивы все, что содержится в заголовочном файле <iostream>, а там содержатся предварительные объявления для всех функций, обеспечивающих работу ввода/вывода — cin/cout

А теперь представьте, что вам нужно было бы каждый раз писать это все вручную. Чтобы понять масштаб, попробуйте (Если работаете в IDE Visual Studio) нажать правой кнопкой мыши на строчку <iostream> и далее выбрать пункт: Перейти к документу iostream

Нажмите на этот пункт и перед вами откроется заголовочный файл на 70+ строк кода.

Заголовочный файл iostream
Заголовочный файл iostream

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

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

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

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

Хм, что же создадим?

А давайте сделаем программу, которая будет:

1. Здороваться с пользователем и запрашивать два целых числа у него.

2. Принимать с консоли целые числа.

3. Выводить в консоль четыре строки, на каждой из которых будет складывать эти числа, отнимать друг от друга, умножать и делить друг на друга.

Программу будем делать в виде нескольких блоков. Здесь нам понадобятся знания, которые вы вместе со мной получили в прошлых статьях. Если вы не прочитали все мои статьи, не обижайтесь, что вам что-то непонятно (однако, на ваши вопросы в комментариях я с удовольствием отвечу).

Для начала создадим новый проект внутри нашей IDE и напишем в открывшейся странице редактора следующее:

-3

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

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

-4

Первый этап выполнен, отлично.

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

-5

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

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

Давайте попробуем?

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

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

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

Не забудьте добавить инструкцию возврата значения
Не забудьте добавить инструкцию возврата значения

Итак, мы добавили функции print два целочисленных параметра, x и y. Присмотритесь, я использовал параметры и в std::cout, в части выражения где выполняются арифметические действия. Теперь, если мы вызовем эту функцию, используя аргументы вызывающей функции, то параметры получат значения от аргументов. Откуда будем вызывать?

Смотрите:

И тут не забудьте добавить инструкцию возврата значения
И тут не забудьте добавить инструкцию возврата значения

Вызывать функцию print мы будем из файла, в котором хранится функция input, получающая числа от пользователя. Заметьте, что мы предварительно объявили print в строке 3 и вызываем ее внутри input с аргументами в виде переменных num и num_1

Про предварительное объявление читайте тут, если вдруг забыли тему

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

Фух!

Теперь надо все это объединить! Чтобы все заработало. А это значит мы возвращаемся в файл с главной функцией main и начинаем писать аж целых три предварительных объявления и два вызова для функций.

-9

Смотрите, я предварительно объявил все функции из разных файлов и вызвал функцию hello и input внутри main. Функцию print вызывать не надо, так как ее вызывает функция input.

Теперь компилируем и запускаем...

-10

Ура, наша программа успешно отработала и завершилась.

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

Для начала создадим заголовочный файл в папке "Файлы заголовков". Это делается так же, как вы создавали исходные файлы .cpp, только заголовки хранятся в другой папке и формат у них .h

-11

Вот так, я создал заголовочный файл fnc и поместил все предварительные объявления функций туда, теперь если я вызову его с помощью #include препроцессор автоматически подставил вместо директивы все предварительные объявления из заголовочного файла. Напишем эту директиву в файле с главной функцией, а так же в файле input, там мы предварительно объявляли функцию print

Обратите внимание, что форма записи при включении пользовательского заголовка отличается от включения системного <iostream> и выглядит так "name.h"

-12
-13

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

Это еще не все!

Заголовки, в которых есть заголовки

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

Откройте его снова, мы это уже делали в начале статьи. Там вы увидите, что заголовочный файл iostream включает заголовок объявляющий функции необходимые для корректной работы ввода данных istream

Вот он, на строке 11:

-14

Такие включения называют "транзитивными". Это связано с тем, что их включение "неявное". Некоторые пособие или школы программирования будут советовать вам использовать только явные включения. То есть вы вполне встретите тезисы, которые скажут:

Включи istream, если хочешь получить данные с консоли. А если еще и хочешь вывести, включи отдельно ostream.

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

Вот такая вот ерунда случается, да.

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

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

Рекомендации по созданию, заполнению и обслуживанию заголовочных файлов

  1. Всегда используйте защиту заголовочных файлов — это будет темой следующей статьи и вашим стимулом прийти и прочитать. А до этого можете нагуглить, что за защита заголовков и что она делает.
  2. Не надо определять переменные и функции внутри заголовков. Используйте заголовочные файлы только для предварительных объявлений. У этого правила есть исключение — глобальные константы, о них мы поговорим позже.
  3. Именуйте заголовочные файлы так же, как файлы .cpp переменные и функции из которых они объявляют (да, в моем примере, я этого не сделал, но вы не повторяйте за мной эту ошибку). Учитесь на моем плохом примере не делать так.
  4. Включайте с помощью #include только необходимые вам заголовочные файлы. Не надо включать все подряд, с фразой: "Потому что могу!"
  5. Не включайте через #include файлы с расширением .cpp

На этом сегодня все. Спасибо за внимание.

В следующей статье мы разберем крайне важную темы "Защита заголовочных файлов".

Если вам понравился материал, ставьте лайки.

Оставляйте комментарии с пожеланиями и вопросами.

Подписывайтесь, чтобы не пропустить выход новых материалов.