Skip to content

4. Multiple Files

Gauss Sum and its Implementation

We have already talked a little about functions in C++ and the separation of their declaration and definition. The declaration of a function describes only the interface and how the function can be used inside other code. The definition of a function describes its implementation, what the function does and how it does it.

Let us take a look at the following mathematical definition of a function \(g\). In mathematics, we have to follow the same rules. The first part in the following definition describes the interface of the function. It tells us that the function takes a natural number \(n\) as its argument and again outputs possibly another natural number \(g(n)\). We can 'call' the function \(g\) without knowing what the output will be. We call this the Black Box Principle.

\[ g:\mathbb{N}\to\mathbb{N} \ ,\qquad g(n) = \sum_{i=1}^n i = 1 + 2 + \ldots + n \]

The second part of the mathematical definition gives the details about the implementation of the function. It describes what the function is actually returning. In this case, \(g(n)\) is the sum of all natural numbers that are smaller or equal to \(n\). This procedure is called the Gauss sum.

If we now want to implement this function in C++, we would first start with the declaration. The function \(g\) gets and returns one natural number. In C++, natural numbers are mostly represented by the type int. So the direct translation to a C++ declaration would look like the following.

int g(int n);

Now, we are able to implement the function according to its definition. The Gauss sum as given as above can directly be translated into a for loop. We only have to make sure to create a variable result which is storing the result of the computation so that the function can return the respective value at the end of the function.

int g(int n) {
  int result = 0;
  for (int i = 1; i <= n; ++i) {
    result = result + i;
  }
  return result;
}

In C++, the definition of a function must include its interface because otherwise the function could not be properly implemented as the definition would not know about the types of input and output variables. As a consequence, the definition is self-consistent and C++ does not need the declaration of the function.

So why are we even dealing with declarations? As we have seen in the second lecture, a call to a function that is only given by a definition has to appear after the respective definition. For complex projects, this is not always possible. The definition of a function must appear exactly once in the project. This is called the One Definition Rule (ODR). Declarations are allowed to appear multiple times as long as they are providing the same interface. So we can put the declaration of a function everywhere to make sure the code following is able to call the according function. Furthermore, we are able to change the implementation of a function without the user of this function recognizing it. For larger projects with multiple programmers, this opens up the possibility to work in parallel on different parts of the code. We will talk about this process in more detail in the next lessons.

Looking back at the Fibonacci Project

First, let us start by creating a new project.

$ pwd
/home/lyrahgames/projects/cpp-course
$ ls
01-hello  02-input  03-fibonacci
$ mkdir 04-files
$ cd 04-files
$ pwd
/home/lyrahgames/projects/cpp-course/04-files

Make sure to open your text editor in the folder where all of your course files reside and create a new file main.cpp in the directory 04-files. If you are using Sublime Text with the given configuration, type ctrl+alt+n to open a small window at the bottom that should ask you for the path of the new file. Type 04-file/main.cpp and press enter.

Our goal is it to understand the usage of multiple source code files in a C++ project. Therefore we adapt the project 02-fibonacci and interchange the function print_fibonacci with our implementation of the function \(g\). For convenience, we will rename it to gauss_sum.

#include <iostream>

using namespace std;

int gauss_sum(int n);

int main() {
  cout << "Give me a number: \n";
  int n;
  cin >> n;

  for (int i = 1; i <= n; ++i) {
    cout << "Gauss(" << i << ") = " << gauss_sum(i) << '\n';
  }
}

int gauss_sum(int n) {
  int result = 0;
  for (int i = 1; i <= n; ++i) {
    result = result + i;
  }
  return result;
}

This code uses the same structure as the project 02-fibonacci. First, we are declaring the function gauss_sum. Then we start the program and read a number from the command line given by the user. Afterwards, we run a loop for all natural numbers that are smaller or equal to the given number and output the value for the Gauss sum of the specific iteration. At the end of the source file, we are then defining the function gauss_sum.

Make sure that the code is compiling. The output should look like the following.

$ pwd
/home/lyrahgames/projects/cpp-course/04-files
$ ls
main.cpp
$ g++ -o gauss main.cpp
$ ls
gauss  main.cpp
$ ./gauss
Give me a number: 
10
Gauss(1) = 1
Gauss(2) = 3
Gauss(3) = 6
Gauss(4) = 10
Gauss(5) = 15
Gauss(6) = 21
Gauss(7) = 28
Gauss(8) = 36
Gauss(9) = 45
Gauss(10) = 55

Using Multiple Files

Add another file to same source directory. We will call it gauss_sum.cpp and we will put the definition of the function gauss_sum into this file.

// gauss_sum.cpp
int gauss_sum(int n) {
  int result = 0;
  for (int i = 1; i <= n; ++i) {
    result = result + i;
  }
  return result;
}

Your file main.cpp should now look like this.

// main.cpp
#include <iostream>

using namespace std;

int gauss_sum(int n);

int main() {
  cout << "Give me a number: \n";
  int n;
  cin >> n;

  for (int i = 1; i <= n; ++i) {
    cout << "Gauss(" << i << ") = " << gauss_sum(i) << '\n';
  }
}

If we now run the typical commands for compilation, the following will happen.

$ pwd
/home/lyrahgames/projects/cpp-course/04-files
$ ls
gauss_sum.cpp  main.cpp
$ g++ -o gauss main.cpp
/usr/bin/ld: /tmp/cceWitU4.o: in function `main':
main.cpp:(.text+0x79): undefined reference to `gauss_sum(int)'
collect2: error: ld returned 1 exit status

We do get a cryptic error message. The error is caused by the linker and is therefore called a linker error. It basically tells us that the function gauss_sum is declared but no definition exists. The C++ compiler needs to get the other source file gauss_sum.cpp where the definition of the function lies as second input argument so it will be able to find the definition. This behavior is a consequence of the procedure the C++ compiler uses to generate a binary executable file. Again, we will talk about this detail in the next lessons.

$ g++ -o gauss main.cpp gauss_sum.cpp
$ ./gauss
Give me a number: 
10
Gauss(1) = 1
Gauss(2) = 3
Gauss(3) = 6
Gauss(4) = 10
Gauss(5) = 15
Gauss(6) = 21
Gauss(7) = 28
Gauss(8) = 36
Gauss(9) = 45
Gauss(10) = 55

Adding a Second Function

The Gauss sum cannot only be calculated by iterating over natural numbers and computing their sum. Gauss has proven the following alternative formula for evaluating a Gauss sum.

\[ g(n) = \sum_{i=1}^n i = \frac{n(n+1)}{2} \]

We will not show the correctness of the formula. But we will now add a second function implementing this alternative formula and we will test if the equation gives us really the same output for a few numbers. The implementation of this new function gauss_sum_eq is straightforward and uses the same principles as the implementation of the function gauss_sum.

// gauss_sum.cpp
int gauss_sum(int n) {
  int result = 0;
  for (int i = 1; i <= n; ++i) {
    result = result + i;
  }
  return result;
}

int gauss_sum_eq(int n) {
  return n * (n + 1) / 2;
}

In the file main.cpp, we have to add the declaration to make sure the function is available for calling inside the function main. There we will add the call to gauss_sum_eq to the output procedure.

// main.cpp
#include <iostream>

using namespace std;

int gauss_sum(int n);
int gauss_sum_eq(int n);

int main() {
  cout << "Give me a number: \n";
  int n;
  cin >> n;

  for (int i = 1; i <= n; ++i) {
    cout << "Gauss(" << i << ") = " << gauss_sum(i)  //
         << " = " << gauss_sum_eq(i) << '\n';
  }
}

The following listing shows an example output of the program where we see that the output is indeed the same for our tested numbers.

$ g++ -o gauss main.cpp gauss_sum.cpp
$ ./gauss
Give me a number: 
10
Gauss(1) = 1 = 1
Gauss(2) = 3 = 3
Gauss(3) = 6 = 6
Gauss(4) = 10 = 10
Gauss(5) = 15 = 15
Gauss(6) = 21 = 21
Gauss(7) = 28 = 28
Gauss(8) = 36 = 36
Gauss(9) = 45 = 45
Gauss(10) = 55 = 55

Removing the Need of Manual Declaration

Now it becomes clear that every time we want to call one of the functions in gauss_sum.cpp in a different source file, we have to state the according declarations of these functions at the beginning of this source file. If we are using only one or two functions, this may not be a big problem. But it becomes a cumbersome process for many functions and more complicated projects. Hence, we would like to have a file containing all those declarations which can easily be included into the source file where we want to call the functions. This file serves not as a typical C++ source file. Instead, its content should be read and inserted into the file it is called from. We are calling such files header files and typically set their extension to hpp. So create another file gauss_sum.hpp and put the declarations there.

// gauss_sum.hpp
int gauss_sum(int n);
int gauss_sum_eq(int n);

Now, your main.cpp file has to include the header file by the typical #include directive which we have already used uncountable times to get access to standard input and output routines.

// main.cpp
#include <iostream>
#include "gauss_sum.hpp"

using namespace std;

int main() {
  cout << "Give me a number: \n";
  int n;
  cin >> n;

  for (int i = 1; i <= n; ++i) {
    cout << "Gauss(" << i << ") = " << gauss_sum(i)  //
         << " = " << gauss_sum_eq(i) << '\n';
  }
}

Please note, header files are in general not allowed to be compiled. Hence, the header file must not be given as an extra argument to the compiler. It is only used for text replacement. After its content was inserted into the source file by processing the #include directive, the manipulated source file will be compiled with a copy of the content of the header file.

$ g++ -o gauss main.cpp gauss_sum.cpp
$ ./gauss
Give me a number: 
10
Gauss(1) = 1 = 1
Gauss(2) = 3 = 3
Gauss(3) = 6 = 6
Gauss(4) = 10 = 10
Gauss(5) = 15 = 15
Gauss(6) = 21 = 21
Gauss(7) = 28 = 28
Gauss(8) = 36 = 36
Gauss(9) = 45 = 45
Gauss(10) = 55 = 55

Exercises

  1. In the #include directive, what is the difference between <...> and "..."? Why are we not typing #include <iostream.hpp>?
  2. Add a third function which outputs the factorial \(n!\) of a given natural number \(n\) to the files gauss_sum.hpp and gauss_sum.cpp. Call this function in main.cpp.

Last update: October 22, 2020