Заглавни файлове в C++

В C++ те се наричат още хедър файлове, което идва от английски (header files). Това са файлове с разширение .h или .hpp. Тяхното използване идва от езика C. И обратната съвместимост на C++ към него. Понякога ги наричаме просто заглавки.

В езика C++ се разделя декларацията от имплементацията. Декларацията се прави в хедър файловете. А имплементацията, описанието на кода в съответния .cpp файл със същото име както .h файла.

Какво има в хедър файла:

  • декларации на функции;
  • декларации на класове и структури;
  • декларации на константи и променливи;
  • декларации на макроси;

Какво има в .cpp файла:

В съответния .cpp файл се имплементират функциите и другите работи описани в хедър файла със същото име. Там може да има и помощни функции и данни. Но те не са видим извън съответния файл.

Разделяне на декларациите и имплементацията

Нека дадем пример. Нека направим следното: Да напишем програма, която приема две int числа от потребителя. После тя ще отпечата сбора на тези числа и техния най – малък общ делител. Това е кратък и ясен пример. Но функционалността ще я изведем от главната програма в MathCalls.h и MathCalls.cpp

MathCalls.h (декларациите)

// MathCalls.h
#ifndef MathCalls_h
#define MathCalls_h

// сума на две числа
int Sum(int x, int y);

// най - малък общ делител
int LCM(int x, int y);

#endif /* MathCalls_h */

Това са декларациите на функциите, които реализират нашата функционалност. Тези функции са видими навсякъде където добавим нашата заглавка така:

#include "MathCalls.h"

Моля обърнете внимание на кавичките. Всеки заглавен файл дефиниран от потребителя (от вас) се пише в кавички. Заглавните файлове от библиотеката на C++ се заградени с ‘<‘ и ‘>. Както досега винаги пишехме <iostream> или <string>.

Include guards

Има и друго на което трябва да обърнем внимание. От горния пример редовете: 2,3 и 11 съдържат макроси на пред процесора. Те ни предпазват от грешки. Помислете, какво би станало ако напишете два пъти: #include „MathCalls.h“ ?

  • #ifndef MathCalls_h Проверява, дали символа MathCalls_h е дефиниран. Тоест дали вече хедъра е инклуднат;
  • #define MathCalls_h Ако символа не е дефиниран добавя цялото съдържание до #endif в текущия .cpp файл; Тоест изпълнява се #include „MathCalls.h“;
  • #endif /* MathCalls_h */ Това означава край на макроса;

Така че без значение дали имате инклудване MathCalls.h няколко пъти (а това може да дойде от друг хедър, който също използва MathCalls.h) в текущия ви .cpp файл ще се добави само това:

// сума на две числа
int Sum(int x, int y);

// най - малък общ делител
int LCM(int x, int y);

MathCalls.cpp (дефиницийте)

#include "MathCalls.h"


// помощнта функция
// не се вижда в другите файлове

// най - голям общ делител
// (алгоритъм на Евклид)
int GCD(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

// НОК
// най - малък общ делител
int LCM(int a, int b) {
    return (a / GCD(a, b)) * b;
}

int Sum(int x, int y) {
    return x + y;
}

Да съберем всичко в едно

След като вече дефинирахме функционалността, която искаме трябва да приложим всичко в една изпълнима програма. Създайте нов проект в средата за разработка, която ползвате. Това ще ви генерира и файл с името main.cpp където е и главната функция на програмата. Добавите хедър и .cpp фаловете които описахме към проекта си.

main.cpp

#include <iostream>
#include <string>
#include "MathCalls.h"
using namespace std;

int ReadInt(string prompt) {
    prompt += "= ";
    cout << prompt;
    int x;
    cin >> x;
    return x;
}

int main() {
    int x = ReadInt("X");
    int y = ReadInt("Y");
    
    cout << "Sum= " << Sum(x, y) << endl;
    cout << "LCM= " << LCM(x, y) << endl;
}

Ето и вероятен изход на терминала от изпълнението на тази програма:

X= 3
Y= 2
Sum= 5
LCM= 6

Какво прави компилатора на C++ тук?

Първо нека да уточним, че това е пример за проект с множество файлове. За компилатора main.cpp и MathCalls.cpp са два отделни обекта за компилация. Те се наричат „единици за компилация„.

Хедър файловете не се компилират отделно. Те заедно с Include guards служат за правилното разпределение на декларациите в отделните .cpp файлове. Хедър файловете се „разширяват“ и стават част от всяка компилационна единица.

Компилатора компилира всеки .cpp файл отделно и генерира обектен код: .obj или .o фаил. Накрая свързващата програма наречена linker ги обединява в един изпълним файл.

Често срещани грешки

  • ❌ Multiple definition: функция дефинирана в повече от един .cpp файл;
  • ❌ Undefined reference: декларация има, но няма реализация;
  • ❌ Липсващ include: компилаторът не знае за дадена функция;