Заголовки и единицы трансляции
Чему вы научитесь:
- разносить объявления и определения по заголовкам и
.cpp-файлам; - объяснять, какой текст
#includeвставляет в единицу трансляции; - защищать заголовок от повторного включения;
- отличать ошибки компоновщика
multiple definitionиundefined reference.

Представьте контору, где у каждого отдела своя папка с бумагами, а в чужие папки никто не заглядывает. Отделу продаж и бухгалтерии нужна одна и та же памятка: «функция triple принимает целое число и возвращает целое». Оригинал памятки хранят в одном месте, а в каждую папку кладут его копию. Тогда правка оригинала доходит до всех отделов.
В программе на C++ отделы — это .cpp-файлы. Функцию triple определяют в triple.cpp, а вызывают из main.cpp, report.cpp и других файлов программы. Компилятор примет каждый вызов, только если выше него стоит объявление int triple(int x);. Копировать это объявление в каждый файл вручную ненадёжно. Если тип параметра поменяют в одной копии и забудут про остальные, файлы начнут объявлять разные функции.
Заголовочный файл
Объявление пишут один раз, в заголовочном файле — файле с объявлениями, который подключают туда, где они нужны. Это и есть оригинал памятки. Файл main.cpp подключает его строкой #include. Строки, которые начинаются с #, называют директивами.
#include <iostream>
#include "triple.h"
int main() {
std::cout << triple(14) << '\n'; // 42
}Директива #include кладёт в файл копию памятки: вставляет на своё место текст заголовка triple.h. В нём одна строка — объявление:
int triple(int x);Имя своего заголовка пишут в кавычках, а заголовка стандартной библиотеки — в угловых скобках. Заголовок обычно называют так же, как .cpp-файл с определениями. Определение остаётся в triple.cpp, и этот файл тоже подключает triple.h:
#include "triple.h"
int triple(int x) {
return x * 3;
}Теперь #include вставляет объявление в triple.cpp, и компилятор видит его рядом с определением. Если они разойдутся по типу результата, компилятор сообщит об ошибке ещё при компиляции triple.cpp.
Единица трансляции
Компилятор переводит не отдельный файл, а единицу трансляции: .cpp-файл вместе со всем текстом, который директивы #include вставили в него. Это папка отдела вместе с вложенными копиями памяток. Каждую единицу компилятор переводит отдельно, в свой объектный файл, и в другие единицы не заглядывает.
В одном аналогия неточна: копии не хранятся в файлах. #include вставляет текст заново при каждой компиляции, а сами .cpp-файлы на диске не меняются.
Заголовок сам по себе не компилируется: его текст входит в каждую единицу, которая его подключила. Поэтому в команде сборки перечисляют только .cpp-файлы: clang++ main.cpp triple.cpp. Это команда для терминала: она компилирует перечисленные файлы и компонует их в одну программу. В Visual Studio ту же роль играет список файлов проекта.
Так программу делят на части: заголовок сообщает, какие функции есть, а .cpp-файл — как они работают. Подключить заголовок могут сколько угодно файлов программы. Объявление теперь записано в одном месте, и компилятор сверяет с ним каждый вызов.
Частая ошибка
Свой заголовок подключили в угловых скобках:
#include <iostream>
#include <triple.h> // ✗
int main() {
std::cout << triple(14) << '\n';
}error: 'triple.h' file not found with <angled> include; use "quotes" insteadЭто ошибка компиляции. В угловых скобках компилятор ищет заголовок среди каталогов библиотек, а папку, где лежит main.cpp, не просматривает. С кавычками поиск начинается с папки самого файла.
Главное
- Объявления пишут в заголовке, определения — в
.cpp-файле. #includeвставляет текст заголовка на место директивы.- Единица трансляции —
.cpp-файл со всем вставленным текстом, и каждую компилируют отдельно. - Свой заголовок подключают в кавычках, заголовок стандартной библиотеки — в угловых скобках.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.