An enumeration, or enum, is a special type of variable in C++ that lets you name a set of related values. Instead of using random numbers, you can use meaningful names. This makes your code easier to read and understand. Enums are especially useful in robotics, where you might have different states or modes for your individual motors, subsystems, or sensors.
When you use numbers to represent different values, it can get confusing quickly if you use just numbers to keep track of states. Notice how hard it is to understand what each number means just by looking at the code:
Again, it's hard to remember what 1 and 2 mean for both the arm and the claw. Imagine this now with 5 to 10 different states!
We can use enums to make it clear what each state represents. This makes the code more organized and human-friendly.
By using UP, DOWN, OPEN, and CLOSED, anyone can understand what state the arm and claw should be in. This reduces mistakes and makes the code much easier to work with.
To keep your code even more organized, you can use . Namespaces act like drawers to separate different sets of enums. This is particularly useful when you have enums for different parts of your robot.
By placing enums inside namespaces, you can easily see which part of the robot each enum belongs to. This adds another layer of organization to your code, making it much easier to maintain and expand.
Using enums and namespaces together helps keep your code neat and straightforward. It's like turning a messy room into an organized one, where everything is easy to find and use.
(Purdue SIGBots)
namespace::feature()
Namespaces are an easy way to avoid name conflicts in large projects by grouping related code under a name. They help keep your code organized and prevent different parts of the program from interfering with each other. They can look like namespace::feature and might be slightly confusing to newer users.
Here's a simple example of how to use namespaces in C++:
In this example, both First and Second namespaces have their own display function. By specifying the namespace (using First:: or Second::), you avoid any confusion between the two functions. This is especially helpful in larger projects where different parts of the code might use the same function names.
PROS uses namespaces extensively with their
prosvexOk let's use a non-code example:
Your friend asks for you to get a burger. However, you don't know what sort of burger they want.
They could be asking for a burger from Burger King, McDonalds, or some other fast food restuarant!
If we were to treat the menus of these fast food restuarants as namespaces, and the burgers as functions or classes, they'd look something like this in C++:
Now, we can have foods (objects or functions) such as a "burger" from different resturaunts (namespaces).
BLRS (Purdue SIGBots)
McDonalds::burger
Burger_King::burgerint armPosition = 1;
int clawPosition = 2;
if (armPosition == 1 && clawPosition == 2) {
// Raise arm and open claw
armMotor.move(100);
clawMotor.move(100);
} else if (armPosition == 2 && clawPosition == 1) {
// Lower arm and close claw
armMotor.move(-100);
clawMotor.move(-100);
}enum ArmPosition {
UP = 1,
DOWN = 2
};
enum ClawPosition {
OPEN = 1,
CLOSED = 2
};
ArmPosition armPosition = UP;
ClawPosition clawPosition = OPEN;
if (armPosition == UP && clawPosition == OPEN) {
// Raise arm and open claw
armMotor.move(100);
clawMotor.move(100);
} else if (armPosition == DOWN && clawPosition == CLOSED) {
// Lower arm and close claw
armMotor.move(-100);
clawMotor.move(-100);
}namespace MySingleMotor {
enum Direction {
FORWARD = 1,
BACKWARD = 2,
STOPPED = 3
};
}
namespace Arm {
enum Position {
UP = 1,
DOWN = 2
};
}
namespace Claw {
enum State {
OPEN = 1,
CLOSED = 2
};
}
Motor::Direction motorDirection = Motor::FORWARD;
Arm::Position armPosition = Arm::UP;
Claw::State clawState = Claw::OPEN;
if (motorDirection == MySingleMotor::FORWARD) {
// Move motor forward
motor.move(100);
} else if (motorDirection == MySingleMotor::BACKWARD) {
// Move motor backward
motor.move(-100);
} else if (motorDirection == MySingleMotor::STOPPED) {
// Stop motor
motor.move(0);
}
if (armPosition == Arm::UP && clawState == Claw::OPEN) {
// Raise arm and open claw
armMotor.move(100);
clawMotor.move(100);
} else if (armPosition == Arm::DOWN && clawState == Claw::CLOSED) {
// Lower arm and close claw
armMotor.move(-100);
clawMotor.move(-100);
}#include <iostream>
// Define a namespace called 'First'
namespace First {
// Inside this namespace, we define a function 'display'
void display() {
std::cout << "This is the First namespace." << std::endl;
}
}
// Define another namespace called 'Second'
namespace Second {
// Inside this namespace, we define another function 'display'
void display() {
std::cout << "This is the Second namespace." << std::endl;
}
}
int main() {
// Call the 'display' function from the 'First' namespace
First::display();
// Call the 'display' function from the 'Second' namespace
Second::display();
return 0;
}if (starting_to_code) read_this_article();
Control flow in C++ determines the order in which the statements of your program are executed. It ensures that your program can make decisions, repeat tasks, and handle multiple pathways. Let's explore the fundamental control flow structures in C++ that you need to know as a beginner.
The simplest form of control flow is sequential execution, where the program executes statements one after the other.
In this example, the program prints "First statement" followed by "Second statement."
Now to experienced programmers this might seem obvious, why is this important to keep in mind as a beginner?
Simply put, as a beginner you need to remember the code executes line to line, left to right. Many programmers make the mistake of believing that code executes in blocks or chunks rather than one line at a time.
The if-else structure allows your program to execute certain code based on a condition.
In this example, the program checks if x is greater than 5. If true, it runs the first block of code; otherwise, it runs the second block.
When you have multiple conditions, you can use else if to check them sequentially.
The switch statement is another way to execute code based on a specific value. It is useful when you have many conditions to check against a single variable.
In this example, the program prints the day of the week based on the value of day.
The for loop is used to repeat a block of code a specific number of times. It is especially useful when you know beforehand how many times you need to iterate.
In this example, the loop runs five times, printing the iteration number each time.
The while loop repeatedly executes a block of code as long as a condition is true.
The do-while loop is similar to the while loop, but it guarantees that the block of code runs at least once, even if the condition is false initially.
The break statement immediately exits the loop or switch statement, terminating further iterations.
In this example, the loop stops when i equals 5.
The continue statement skips the current iteration and continues with the next iteration of the loop.
In this example, the loop skips printing when i equals 5 but continues with the next iteration.
Basic control flow allows you to direct the execution of your program's statements in a meaningful way. Understanding and mastering if, else if, switch, for, while, and do-while loops, along with break and continue statements, will enable you to build complex and efficient C++ programs. As you gain more experience, these fundamental structures will become the building blocks for more advanced programming concepts.
(Purdue SIGBots)
When the code to your robot becomes large enough, splitting it into multiple files can help structure your code and make it easier to find things.
In C and C++, you'll commonly find source (implementation) files and header (API) files. Source files contain the actual functionality of your program, and header files define the functions, variables, etc. that are viewable by other files in the code.
Source files typically end .c for C code, or
.cxxHeader files typically end with .h for C and C++ code, or .hpp or .hxx for C++ code.
Typically, but not always, you'll find one header file per source file.
When using objects in the context of a header and source file, be sure to:
Declare your classes and functions in header files: Define the class structure, its data members, and function prototypes in a header file with a .h or .hpp extension. The same applies for functions not associated with a class.
In the header, be sure to use the extern keyword to declare that the motor, sensor, or other object is initialized somewhere else (see example below).
Implement your class methods in source files: Write the implementation of your class methods in a source file with a .cpp (for C++) or corresponding extension. This keeps your code organized and improves compile time. This is not necessary with built in objects such as motors and other sensors.
Other good practices:
Use #include guards in header files: To prevent multiple inclusions of the same header file, use #include guards or #pragma once at the beginning of your header files (more on this below).
Include the necessary header files in source files: In your source file, include the header file of the class and any other headers necessary for the implementation with the #include directive.
Separate interface from implementation: Keep the class interface in the header file clear and concise, focusing on what the class does. Leave the how to the source file where you implement the methods.
Its important to ensure that headers are only included once in each source file. This is done with header guards:
If header guards aren't present, it may lead to compilation issues when a header file is included multiple times either directly or indirectly through other headers. This can lead to multiple definitions of the same class, function, or variable, causing compilation errors due to redefinition. Additionally, omitting header guards can significantly increase compile times as the same header is processed multiple times.
Under the hood, what #include does is blanket copying and pasting the header file into where the include is. That's why the redefinition may occur.
To summarize, source files can include a header file to make functions, variables, and other objects that were defined in another source file(such as a motor or a sensor) visible. It helps to keep code organized and readable.
In most project layouts (such as a pros project), the src directory is used for storing the .cpp files, while the inc directory is used for storing header files.
IncorrectMain.cpp
bad_example.hpp
bad_example.cpp
This code will result in a linker error because undefinedFunction() is implemented in bad_example.cpp but is not declared in bad_example.hpp, so IncorrectMain.cpp doesn't know about its existence.
A common application for this is branching each of the subsystems into their own files rather than cramming all the subsystems into one.
This helps making organizing and navigating code a lot easier, as each subsystem is fit nicely into its own file.
BLRS (Purdue SIGBots)
#include <iostream>
int main() {
std::cout << "First statement\n";
std::cout << "Second statement\n";
return 0;
}int x = 10;
if (x > 5) {
std::cout << "x is greater than 5\n";
} else {
std::cout << "x is not greater than 5\n";
}int score = 70;
if (score >= 90) {
std::cout << "Grade: A\n";
} else if (score >= 80) {
std::cout << "Grade: B\n";
} else if (score >= 70) {
std::cout << "Grade: C\n";
} else {
std::cout << "Grade: F\n";
}int day = 3;
switch (day) {
case 1:
std::cout << "Monday\n";
break;
case 2:
std::cout << "Tuesday\n";
break;
case 3:
std::cout << "Wednesday\n";
break;
default:
std::cout << "Other day\n";
break;
}for (int i = 0; i < 5; i++) {
std::cout << "Iteration " << i << "\n";
}int i = 0;
while (i < 5) {
std::cout << "Iteration " << i << "\n";
i++;
}int i = 0;
do {
std::cout << "Iteration " << i << "\n";
i++;
} while (i < 5);for (int i = 0; i < 10; i++) {
if (i == 5) {
break;
}
std::cout << "Iteration " << i << "\n";
}for (int i = 0; i < 10; i++) {
if (i == 5) {
continue;
}
std::cout << "Iteration " << i << "\n";
}#ifndef MY_HEADER_HPP
#define MY_HEADER_HPP
// Your public definitions go here
#endif//MY_HEADER_HPP#include "my_functions.hpp"
int main() {
myFunction();
m1.stop();
return 0;
}// my_functions.hpp
#ifndef MY_FUNCTIONS_HPP
#define MY_FUNCTIONS_HPP
#include "pros/motors.hpp"
void myFunction();
extern pros::Motor m1;
#endif // MY_FUNCTIONS_HPP// my_functions.cpp
#include "my_functions.hpp"
#include <iostream>
std::int8_t mtr_port = 1;
pros::Motor m1(mtr_port);
void myFunction() {
m1.move(127);
std::cout << "Hello from myFunction!" << std::endl;
}#include "bad_example.hpp"
int main() {
undefinedFunction();
return 0;
}#ifndef BAD_EXAMPLE_HPP
#define BAD_EXAMPLE_HPP
// Forget extern keyword, so every time this is included in a src file we are
// "redefining" the motor object.
pros::Motor m1;
// Since we included this header file in both IncorrectMain.cpp and bad_example.cpp,
// we will now get a "multiple declaration error of pros::Motor m1" error when we
// compile.
// Forgot to declare undefinedFunction here, so it's not visible to other files
// such as Incorrectmain.cpp
// (missing here)
#endif // BAD_EXAMPLE_HPP#include "bad_example.hpp"
#include <iostream>
void undefinedFunction() {
std::cout << "This will cause a linker error since it's not declared in the header." << std::endl;
}PROS Project
β
βββ src
β βββ chassis.cpp
β βββ lift.cpp
β βββ flywheel.cpp
β βββ main.cpp
β
βββ inc
βββ chassis.hpp
βββ lift.hpp
βββ flywheel.hpp