Skip to the content.

Файлы заголовков (.h, .hpp, .hh, .hxx)


#include не подключает никаких библиотек!

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

#include "mymodule.h"

Файл stdio.h - это вполне реальный файл, скорее всего находится он в /usr/include.

Важно понимать, что в этом файле содержится только заголовок функции printf, а самой функции там нет. Отсюда используемый суффикс “.h” - header. Всё что узнает компилятор видя заголовок - это что где-то есть функция с таким-то именем, принимающая столько-то параметров таких-то типов, что делает функция компилятору знать не нужно. Дело в том, что компилятор генерирует не готовый машинный код, а объектный файл, в котором пока что не хватает некоторых адресов. В данном случае результатом работы компилятора становится модуль, не содержащий самой функции printf. Вместо неё модуль содержит указание на то, что линкеру следует откуда-то добыть функцию с таким именем, а её адрес подставить куда следует. То есть функция print появится в программе только на этапе окончательной сборки. (более того в реальной ситуации, скорее всего, функция вообще не будет содержатся в исполняемом файле программы, после её запуска функция будет подгружена из динамической билиотеки).

!!!ОЧЕНЬ ЧАСТАЯ ОШИБКА: На вопрос что делает директива #include многие отвечают что она “подключает библиотеку”, а сам файл stdio.h - это якобы библиотека. Так вот, директива #include не имеет никакого отношения к подключению библиотек, а заголовочные файлы библиотеками не являются - они являются заголовочными файлами и ничем иным. Яп Си вообще не предусматривает возможность подключить библиотеку из текста программы, компилятор этим не занимается.

А теперь к сути дела

Допустим что наша программа разбита на 2 файла: main.cpp и func.cpp

// func.cpp

#include <iostream>
void func()
{
    std::cout << "Hello" << endl;
}
// main.cpp

int main()
{
    func();
    return 0;
}

И мы хотим использовать функцию func(), которая определена в func.cpp из main.cpp. Просто так её вызвать у нас не выйдёт, поскольку main.cpp понятия не имеет что она делает. Для того что бы её вызвать, нам необходимо объявить её перед функцией main: указать тип возврата и параметры, которые она принимает. Таким образом мы как будто обещаем компилятору, что где-то там, попозже мы определим эту функцию.

// main.cpp

void func();

int main()
{
    func();
    return 0;
}

А теперь представим такую ситуацию: функция func.cpp используеться не только в main.cpp, а и в нескольки других файлах. Всё бы ничего, до тех пор пока мы не захотим изменить “начинку” функции func(), например поменять ей тип возврата либо параметры. В таком случае нам понадобиться изменить объявление этой функции во всех файлах, которые её используют. Что бы не мучаться мы создадим ещё один файл - заголовков, в котором будет лежать как раз таки объявление функции func().

// header.h

void func();

Подключим header.h к main.cpp вместо объявления функции:

// main.cpp

#include "./header.h"

int main()
{
    func();
    return 0;
}

Теперь при компиляции строка #include “./header.h” заменится на содержимое файла заголовков header.h, теперь для изменения функции func() потребуется только два редактирования: func.cpp и header.h.

Двойное включение заголовочного файла

Может случится такая ситуация:

// main.cpp

#include "./func.h"
#include "./header.h"
...
// func.h

#include "./header.h"
...

Мы подключаем к main.cpp два файла заголовков: func.h и header.h, НО func.h подключает еще раз header.h. Зная что директива #include при компиляции будет заменена на содержимое файла, то выходит такая ситуация:

// main.cpp

#include "./header.h"
#include "./header.h"
...

Есть несколько способов избежать двойного включения (стражи включения):

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

Использование макросов

#ifndef FUNC_H
#define FUNC_H

int func();
#endif

Более простой

#pragma once

int func();

Аналогично объявлению функции (когда пишем только заголовок функции без тела) можно объявить переменную без определения:

extern int a; // объявить
int a; // объявить и определить (выделить под неё память)