DEV Community

Jess
Jess

Posted on

C++ Header Files

In C++, functions and variables need to be declared before they can be used. For example, if you want to call a function in main, the function needs to be before main in your source code so the compiler knows about it, because functions are compiled in the order in which they appear.

#include <iostream>
#include <ctime>

int getRandomTotal()
{
    return rand() % 10 + 1;
}

void printHelloWorld()
{
    int total = getRandomTotal();

    std::cout << "Print " << total << " times:\n\n";

    for (int i = 0; i < total; i++)
    {
        std::cout << "Hello World!\n";
    }
}

int main()
{
    srand(time(NULL));

    printHelloWorld();

    return 0;
}

Enter fullscreen mode Exit fullscreen mode

Here printHelloWorld is called in main, so it needs to be before it. getRandomTotal is called in printHelloWorld and therefore needs to be before that function.

This isn't exactly convenient and can get pretty messy fast. Fortunately, you can use a function prototype instead, and then the function can exist below the function call, or in an entirely different file.

The function prototype is the heading of a function, which consists of the return type, function name, and parameters, followed by a semi-colon:

functionType functionName(parameter list);

You don't have to specify the variable names for the parameter list in the function prototype but you have to specify the data type.

For example:

#include <iostream>

int addNumbers(int, int); // function prototype

int main()
{
    std::cout << addNumbers(2, 3) << std::endl;

    return 0;
}

int addNumbers(int x, int y)
{
    return x + y;
}
Enter fullscreen mode Exit fullscreen mode

Here is the same program re-written with function prototypes:

// Function prototypes
void printHelloWorld();
int getRandomTotal();

int main()
{
    srand(time(NULL));

    printHelloWorld();

    return 0;
}

void printHelloWorld()
{
    int total = getRandomTotal();

    std::cout << "Print " << total << " times:\n\n";

    for (int i = 0; i < total; i++)
    {
        std::cout << "Hello World!\n";
    }
}

int getRandomTotal()
{
    return rand() % 10 + 1;
}

Enter fullscreen mode Exit fullscreen mode

Now, the function prototypes are at the top, and the function definitions are below main.

You can see that the order of the functions no longer matters. I rearranged the functions (putting getRandomTotal below printHelloWorld) to illustrate that it still works. This is because the compiler knows about both functions before printHelloWorld is called in main.

However, because each file is compiled independently, if I split the function definitions into another file the order that the definitions are in matters again:

// file main.cpp

#include <iostream>
#include <ctime>

// Function prototypes
void printHelloWorld();
int getRandomTotal();

int main()
{
    srand(time(NULL));

    printHelloWorld();

    return 0;
}

// file funcs.cpp

#include <iostream>
#include <ctime>

void printHelloWorld()
{
    int total = getRandomTotal();

    std::cout << "Print " << total << " times:\n\n";

    for (int i = 0; i < total; i++)
    {
        std::cout << "Hello World!\n";
    }
}

int getRandomTotal()
{
    return rand() % 10 + 1;
}

Enter fullscreen mode Exit fullscreen mode

This results in the error 'getRandomTotal': identifier not found

If, in funcs.cpp, you put getRandomTotal before printHelloWorld it will work again.

Header Files

In C++, header files are used to contain function declarations. If you wanted to use printHelloWorld and getRandomTotal in multiple files you would have to include the function prototypes every time, the same way you need to include files like iostream. Instead, you can place the declarations in a header file and include that. That is what you're doing when you use #include <iostream>.

Header files end in .h or .hpp. When I use Visual Studio it creates header files with the .h extension, and when I use Xcode it creates them with .hpp. I believe .h is a C thing, but is used frequently in C++ programs, and .hpp is a C++ thing but can't be used in C programs. Whichever you use in your C++ program, be consistent.

// file main.cpp

#include <iostream>
#include <ctime>
#include "Funcs.h" // <- our header file

int main()
{
    srand(time(NULL));

    printHelloWorld();
    std::cout << addNumbers(2, 3) << std::endl;
    return 0;
}


// file Funcs.h
// Function declarations

#pragma once

int getRandomTotal();
void printHelloWorld();


// file Funcs.cpp
// Function definitions
#include <iostream>
#include <ctime>

int getRandomTotal()
{
    return rand() % 10 + 1;
}

void printHelloWorld()
{
    int total = getRandomTotal();

    std::cout << "Print " << total << " times:\n\n";

    for (int i = 0; i < total; i++)
    {
        std::cout << "Hello World!\n";
    }
}

Enter fullscreen mode Exit fullscreen mode

Brackets vs Quotes

The compiler looks for header files in several locations. If you include your header file in quotes, the compiler will first look in the same directory as your source file before looking in other directories. If you use brackets, the compiler does not look in the same directory as your source file.

Quotes can be used for everything and the compiler will find it. Files in quotes are relative to the current file. If I created a folder called Headers and moved Funcs.h inside it, I could write:

#include "./Headers/Funcs.h"

but #include "Funcs.h" will still work as well.

Include Guards

To prevent files from being inserted multiple times and causing errors, include guards are used.

You might see #pragma once, as I've used above.

Another one is:

// file Funcs2.h

#ifndef Funcs2_h
#define Funcs2_h

int multiplyNums(int, int);

#endif

// file Funcs2.cpp
#include "Funcs2.h"

int multiplyNums(int x, int y)
{
    return x * y;
}


// file main.cpp
#include "Funcs2.h"

int main()
{
    std::cout << multiplyNums(12, 3) << std::endl;

    return 0;
}


Enter fullscreen mode Exit fullscreen mode

Again, just be consistent in what you use.

What Not to Put in a Header File

Including these in a header file are either not allowed, or considered bad practice:

  • built-in type definitions at namespace or global scope
  • non-inline function definitions
  • non-const variable definitions
  • aggregate definitions
  • unnamed namespaces
  • using directives

Further Reading / References

Top comments (0)