What are Header files?

Header files are C++ files that contain declarations - they tell the compiler about functions, classes, constants, and types without providing the actual implementation. Think of them as a "table of contents" or "menu" that lists what's available in your code library.

Let's learn about why we should use them.

The forward declaration problem

In the previous lessons, we saw how to split programs across multiple files. But there's a significant problem. We need to forward declare functions in every file that uses them.

math_operations.cpp

double add(double a, double b)
{
    return a + b;
}

double subtract(double a, double b)
{
    return a - b;
}

double multiply(double a, double b)
{
    return a * b;
}

calculator.cpp

#include <iostream>

// We need these forward declarations to use the functions
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);

int main()
{
    double result1 = add(5.0, 3.0);
    double result2 = subtract(10.0, 4.0);
    double result3 = multiply(2.0, 6.0);

    std::cout << "Results: " << result1 << ", " << result2 << ", " << result3 << std::endl;

    return 0;
}

statistics.cpp

#include <iostream>

// We need the same forward declarations again!
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);

double calculateMean(double a, double b)
{
    return add(a, b) / 2.0;
}

double calculateRange(double a, double b)
{
    return subtract(a, b);  // Assumes a > b
}

int main()
{
    double mean = calculateMean(10.0, 20.0);
    std::cout << "Mean: " << mean << std::endl;

    return 0;
}

This approach has serious problems:

  • Code duplication: Same forward declarations in multiple files
  • Maintenance nightmare: If a function signature changes, update every file
  • Error-prone: Easy to make mistakes when copying declarations
  • Hard to manage: Difficult to keep track of all dependencies

How do we solve this problem?

Header files solve this problem. They are files (typically with .h or .hpp extension) that contain:

  • Function declarations (forward declarations)
  • Type definitions
  • Constants
  • Class declarations
  • Other shared code that multiple source files need

Creating your first header file

Let's solve our math operations problem with a header file:

math_operations.h (header file)

// math_operations.h - Header file for mathematical operations
#ifndef MATH_OPERATIONS_H
#define MATH_OPERATIONS_H

// Function declarations
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);

// Constants
 double PI = 3.14159265359;
 double E = 2.71828182846;

#endif // MATH_OPERATIONS_H

math_operations.cpp (implementation file)

// math_operations.cpp - Implementation of mathematical operations
#include "math_operations.h"  // Include our own header

double add(double a, double b)
{
    return a + b;
}

double subtract(double a, double b)
{
    return a - b;
}

double multiply(double a, double b)
{
    return a * b;
}

double divide(double a, double b)
{
    return a / b;  // Caller should check for division by zero
}

main.cpp (using the header)

#include <iostream>
#include "math_operations.h"  // Include our header

int main()
{
    double x = 10.0, y = 3.0;

    std::cout << "x + y = " << add(x, y) << std::endl;
    std::cout << "x - y = " << subtract(x, y) << std::endl;
    std::cout << "x * y = " << multiply(x, y) << std::endl;
    std::cout << "x / y = " << divide(x, y) << std::endl;

    std::cout << "Pi = " << PI << std::endl;
    std::cout << "e = " << E << std::endl;

    return 0;
}

Output:

x + y = 13
x - y = 7
x * y = 30
x / y = 3.33333
Pi = 3.14159
e = 2.71828

How header files are compiled

Here's what happens during compilation when using header files:

┌───────────────────┐  #include "math_operations.h"  ┌─────────────────┐  #include <iostream>
│math_operations.cpp│ ─────────────────────────────  │  main.cpp       │ ─────────────────────┐
└───────────────────┘               │                └─────────────────┘                      │
         │                          ▼                                                         │
         │                   ┌───────────────────┐                                            ▼
         │   ─────────────── │ math_operations.h │                                   ┌─────────────────┐
         │  │                └───────────────────┘                                   │ iostream        │
         │  │                         │                                              │ (header file)   │
         │  │                         ▼                                              └─────────────────┘
         ▼  ▼                  ┌─────────┐                                                  │
   ┌─────────┐                 │ Compile │ ◄────────────────────────────────────────────────┘
   │ Compile │                 └─────────┘
   └─────────┘                       │
         │                           │
         │                           ▼
         ▼                    ┌─────────┐
┌───────────────────┐         │ main.o  │
│ math_operations.o │         └─────────┘
└───────────────────┘               │
         │                          │
         └─────────────┐   ┌────────┘
                       │   │
                       ▼   ▼
                  ┌─────────┐    ┌─────────────────┐
                  │  Link   │◄───│ C++ Standard    │
                  └─────────┘    │  Library        │
                        │        └─────────────────┘
                        ▼
                  ┌─────────┐
                  │main.exe │
                  └─────────┘

Key points:

  1. Preprocessing: Each source file that includes a header gets a copy of the header's contents inserted at the #include location
  2. Separate compilation: Each .cpp file (with its included headers) is compiled independently into an object file (.o)
  3. Linking: All object files are linked together to create the final executable
  4. Header files are never compiled directly - they're only compiled as part of the source files that include them

This is why header files should only contain declarations, not definitions - otherwise you'd get multiple definitions during linking!

Header guards

Notice the #ifndef, #define, and #endif in the header? These are header guards. They prevent the header from being included multiple times in the same file.

A simple example

Note: This example contains a header guard. While omitting the guard wouldn't cause an error in this simple case, it's essential to always include guards to prevent issues in larger projects.

Let's create a basic calculator with header files:

calculator.h

#ifndef CALCULATOR_H
#define CALCULATOR_H

// Function declarations
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);

#endif // CALCULATOR_H

calculator.cpp

#include "calculator.h"

double add(double a, double b)
{
    return a + b;
}

double subtract(double a, double b)
{
    return a - b;
}

double multiply(double a, double b)
{
    return a * b;
}

main.cpp

#include <iostream>
#include "calculator.h"

int main()
{
    double x = 10.0;
    double y = 3.0;

    std::cout << x << " + " << y << " = " << add(x, y) << std::endl;
    std::cout << x << " - " << y << " = " << subtract(x, y) << std::endl;
    std::cout << x << " * " << y << " = " << multiply(x, y) << std::endl;

    return 0;
}

Output:

10 + 3 = 13
10 - 3 = 7
10 * 3 = 30

System headers vs user headers

System headers (angle brackets)

#include <iostream>     // Standard library header
#include <string>       // Standard library header
#include <vector>       // Standard library header
Note: C++ standard headers don't use `.h` extensions (like `` vs the old ``) to distinguish them from C headers. The modern C++ versions put everything in the `std` namespace instead of the global namespace and provide better type safety.

User headers (quotes)

#include "myheader.h"      // User-defined header in same directory
#include "utils/math.h"    // User-defined header in subdirectory
#include "../common.h"     // User-defined header in parent directory
Do not #include .cpp files

Never include .cpp files with #include. Including source files may cause naming collisions, longer compile times and it would be an unusual practice.

Real-world header organization

Note: Each header file (`.h`) is typically paired with a corresponding source file (`.cpp`) that contains the implementations.

In larger projects, headers are often organized like this:

project/
├── src/
│   ├── main.cpp
│   ├── math/
│   │   ├── arithmetic.h
│   │   ├── arithmetic.cpp
│   │   ├── geometry.h
│   │   └── geometry.cpp
│   ├── ui/
│   │   ├── interface.h
│   │   ├── interface.cpp
│   │   ├── display.h
│   │   └── display.cpp
│   └── utils/
│       ├── string_utils.h
│       ├── string_utils.cpp
│       ├── file_utils.h
│       └── file_utils.cpp

Each directory contains related headers and implementation files.

Benefits of header files

  • Organization: Group related declarations together with clear separation of interface and implementation
  • Maintainability: Change function signatures once in the header, all files update automatically
  • Reusability: Include headers in multiple projects with standard interfaces
  • Documentation: Headers serve as clear documentation of available functionality

Summary

Header files are essential for organizing larger C++ programs:

Key concepts:

  • Header files: Contain declarations, constants, and shared code
  • Implementation files: Contain function definitions
  • Include directive: #include brings header contents into source files
  • Header guards: Prevent multiple inclusion problems

Benefits:

  • Eliminate code duplication: Declare functions once, use everywhere
  • Improve maintainability: Change signatures in one place
  • Better organization: Clear separation of interface and implementation
  • Enable reusability: Headers can be shared between projects

Best practices:

  • Always use header guards
  • Include only what you need
  • Never use using namespace in headers
  • Keep implementations in .cpp files
  • Use meaningful file names and organization

File extensions:

  • .h or .hpp for headers
  • .cpp for implementation files

Header files are the foundation of modular C++ programming and essential for any project beyond simple single-file programs.