Lesson 28 - Multiple File Programming

Note* For this section we will be screenshotting our entire editor to present sample code. This way you are able to see the file structure as well. 


Preface 

Right now, what we have done for all of the past lessons is put all of our code into one file. This practice would definitely be okay for smaller programs, but as you start approaching the benchmark of ~700 lines of code or so, it is extremely beneficial to split up the content of your code amongst different files. The reason for this is so that it is much easier to read, navigate, manage, and edit larger code when it is split up. 


In the rest of this lesson, we will discuss how to effectively program using multiple file programming. 

 

Here is an outline for this lesson. 


  1. Review: Include and working with paths

  2. What Are Header Files?

    1. An Example With Functions

    2. Example With Variables

    3. Example With Electronics

    4. Custom Classes

  3. Header Guards and Implementation

  4. Programming Practices



Review: Include and Working with Paths

Recall, the keyword #include will include the contents from a different file into your code. In order to proceed with the rest of the lesson, we highly recommend looking at lesson 13 (STL and namespaces) in order to be more familiar with include. 


In that lesson, we talked a lot about including files that are from the C++ library. To do so, we would do #include <filename>  (like #include <string> and #include <iostream> Notice how the file name has greater than / less than signs around it. 


We only briefly mentioned what #include “filename.h” meant. By doing #include and having the file name surrounded by double quotes, you are essentially including a headerfile’s content into your current program. 


Before going further into what a header file is, let’s talk more about how the computer will actually locate the file you are trying to include. 


Let’s say you have the following structure. 



Red boxes around labels are folders (for example PROS Project, include, src, and bin, are the names of the folder). 


Let’s suppose main.cpp is the current file you are working on, and you want to include SAMPLE.h. How would we go about doing this? We have to pay special attention to our current working directory. Recall from the lesson “Common PROS Commands”, you have to be detailed about where files are located by specifying the path. 


The correct line of code: #include “../include/SAMPLE.h”


The current working directory is “src.” The reason for this is because the main file of the program is located in the folder “src.” With this, to include “SAMPLE.h” we need to go back one folder level to “PROS Project.” To tell the computer you want to go up the tree to a higher folder, you would use “..” For example, typing the command “cd ..” into your terminal will take you one folder higher from the current working directory. 


Then, afterwards, since the folder “include” is within the folder “PROS Project”, we are able to simply go to the include folder using “../include” Finally, since SAMPLE.h is within include, we would add that onto the end. 


A short summary of this section: be aware of your current working directory when trying to include header files. You need to be specific about the path. 


Now What Actually Are Header Files?

Header Files are files that have the .h extension (or .hpp). Within the header files you put the declarations of functions, variables, classes, etc while within .cpp files (source files), you put the implementation/definitions (how the code is actually defined). 


Then to use the contents from a certain source file, you would include the HEADER File. Let’s go into more detail about declarations



Declarations and Implementation

Declarations of code belong in header files. The Declaration of code is the code without any assignment/implementation. Meanwhile the actual definition will belong in a corresponding cpp file. 


For example, let’s suppose I create a file named “Chassis.h” within the folder “include” which will store the code for the user control and movement functions for the chassis, then I will need to create a corresponding file named “Chassis.cpp” within the folder “src.”


Example 2: Similarly, let’s suppose you want to have the code for your lift within a separate file other than main.cpp. You could accomplish this by first creating a file named “Lift.h” within the “include” folder and then creating a source file “Lift.cpp” in the folder “src.” 


Once again, the code’s declaration will belong in the header file (Chassis.h, Lift.h) while the definition/implementation would belong in the source files (Chassis.cpp and Lift.cpp). Let’s now better define the terms declaration and definition. The rest of this lesson will be plentiful with examples. In all of them, the header file is on the left while the source file is on the left. 



FUNCTIONS IN HEADER FILES

Function Declarations: the declaration of a function within the header files will be the function without the brackets. Here is an example. The header file is on the left and the source file on the right. 


As you can see in the example, the declaration of a function is the function WITHOUT any brackets. We  have the declaration in the header file. Then, within our source file, notice how we INCLUDE the header file of the chassis. Finally, we actually define the code for the functions within “Chassis.cpp.” Also please give special attention to the File Structure of the Project and the Includes.


You might be wondering two things: 


  1. Can we declare the electronics elsewhere and never have to declare them again? The answer is yes. We will specify how to do this in a bit. 

  2. How would we use the functions within the chassis files within “main.cpp.” The answer: include the header file “chassis.h” within “main.cpp” and you will be able to use the functions without any problems. For example: 


Here is now one for you to try. What would the function’s declaration be?


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

bool rising_edge_L1(const Controller &joystick, bool state){
if(joystick.get_digital(DIGITAL_L1) && !state){
return true;
}
return false;
}

int main(){
return 0;
}


Once again, the function declaration would be a function without the definition (stuff inside the brackets). It would be 


bool rising_edge_L1(const Controller joystick, bool state); 



VARIABLES IN HEADER FILES

The declaration of a variable is the variable without the assignment. Meanwhile, for data types like arrays or std::string, you will need to add the keyword “extern” in front of it. This essentially tells the compiler that this data type will be used all over the code and the actual assignment (the value of the data) is assigned somewhere else. 


To clarify how we could use variables defined and assigned in different files all across the code, consider the following example. 



Now to use these variables in main.cpp, we would first include the variables HEADER file. Then, you will be able to use, assign, change, etc. with the variables that we defined within the separate files. Pay special attention to the INCLUDES, the “extern” keyword, and the differences between the header file and the source file. 



ELECTRONICS IN SEPARATE FILES

FInally, this is what you have been waiting for. 


To declare electronics, it is very similar to declaring std::string / arrays in the sense that you need the “extern” keyword within your header file. Moreover, the actual configuration of the electronics belongs in the header file’s corresponding source file. To use the electronics in different files throughout the code without having to redeclare them everytime, you would simply include the header file that contains the electronics declaration. 


Here is an example: 



Note the following: 

  • In “robot_config.h” there are all of the electronics declared with the extern keyword

  • “robot_config.cpp” includes the header file “robot_config.h” and the electronics are created like you would expect normally

  • “main.cpp” includes the “robot_config.h” header file and will be able to use all of the electronics without having to declare them in main.cpp!




CUSTOM CLASSES ACROSS MULTIPLE FILES


Once again, to review, inside the header files were the declarations of functions/variables while the .cpp files had the definition of the function / assignment of the variables. 


Classes work similarly. The header files follow almost the exact same conventions as the conventions for header files for variables / functions. This is of course because classes are essentially a collection of variables and functions. 


As for the .cpp files for classes, you would implement the class, but the syntax is a little different from what you are used to. Before the function/constructor name, you would need to have “ClassName::” to specify that the function is of ClassName. Here is an example. Let’s create a “Chassis” class that contains a tank drive function for user control and a movement function for autonomous. Here is how it would go: 



Note: Notice how the electronics do not need to be redeclared since we declared them in robot_config.h


Note: Notice how each function/constructor has “Chassis::” to tell the program that the particular function is part of the class “Chassis.”


Note: Pay attention to the includes and file structure. 


Header Guards

One problem you may across is the problem of including the same header file multiple times within the same .cpp file. To ensure that this does not happen, we will introduce something called header guards.


Essentially, at the very top of your header file, you ALWAYS need to include the following lines. 


#ifndef FILENAME_H

#define FILE_NAME_H


Then at the very bottom of your header file, you want to have the following line of code. 


#endif


By encompassing your header file within these lines, you (should) will not run into the problem of multiple, overlapping includes within one .cpp file. 


Here is a correct example of the use of header guards. 


#ifndef CHASSIS_H
#define CHASSIS_H

//includes are below the header guard
#include "main.h"
#include "robot_config.h"

class Chassis{
private:
int deadband;

public:
Chassis(int deadband);
void tank_control();
void move_to();
};

#endif



Programming Practices 

To conclude this section, we want to summarize practical, key takeaways. 

  1. Multiple file programming makes maintaining larger programs significantly easier. Keeping files short and code easy to read should be the number one priority. 

  2. A common programming practice is having classes that represent subsystems. Then typically, there will be one header file per class/subsystem, and the name will typically be the classname. 

  3. Always use header guards for all header files. 

  4. Create a config.h header file that contains the declaration for all electronics or even with functions that will be used throughout the entire program. An example of such a useful function would be one that retrieves the sign of a number.