For larger programming projects, it can be very helpful to group code up into “classes,” which represent a set of functions and variables that are all related in some way. For example, when we start working on robotics programming, you’ll see that we use classes for representing robot components like motors, controllers, sensors, and the robot itself. A motor class, for instance, may contain functions for turning motors on and off. A controller class may contain functions for checking if certain buttons are pressed.
By putting code in classes, we can easily organize and separate out certain functionality so our code is easier to work with.
Here’s a program that contains a Motor
class (which, at the moment, mostly just prints messages to the screen):
#include <iostream> #include <string> class Motor { private: std::string name; public: Motor(std::string name) { std::cout << "Beginning motor constructor\n"; this->name = name; } void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << name << "\n"; } }; int main() { Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
Program output:
Beginning motor constructor Starting motor Motor0 with speed: 0.8 Stopping motor Motor0
Now let’s walk through how this works:
- A class is created by typing
class
followed by the name of the class (Motor
in this case). Class names should always start with a capital letter.
- Every variable and function in a class must be either private, public, or protected (which I’ll explain when going over “inheritance”). These three options are referred to as “access modifiers.”
- In the
Motor
class, thename
variable is private, and everything else is public. - A private variable or function cannot be accessed outside of a class.
- It’s best to make as many variables and functions inside a class private as possible. This helps to separate (“encapsulate”) functionality from other code, which aids in organization.
- A public variable or function can be accessed outside of a class.
- In the
Motor(int id) {...}
is the “constructor” of theMotor
class.- To use functions and variables in a class, we must first create an “instance” of that class (referred to as an “object”). This can be seen in line 22. There, we create a
Motor
object called “frontLeftMotor
“. - When first creating a
Motor
object, theMotor
constructor is called. In this case, the constructor has one parameter (calledname
). Therefore, when creating aMotor
object in line 22, we put a name in parentheses ("Motor0"
for instance).
- To use functions and variables in a class, we must first create an “instance” of that class (referred to as an “object”). This can be seen in line 22. There, we create a
- Inside the
Motor
constructor, we set the motor’s privatename
variable equal to thename
parameter of the constructor. This allows the otherMotor
functions to use thename
variable.- When referring to the motor’s private
name
variable, we need to precede “name
” with “this->
“. “this
” is just a pointer to the current class instance (frontLeftMotor
in this case).
- The arrow symbol (“
->
“) is how we access variables and functions from the pointer to an object. Therefore, “this->name
” represents the name offrontLeftMotor
. - The only reason “
this->
” is used here is because the constructor has a parameter called “name
“. Without “this->
“, the computer wouldn’t know which “name
” you were referring to. - By naming the constructor’s parameter to something else, like “
nameParam
“, you could just type “name = nameParam;
“.
- When referring to the motor’s private
start
andstop
are two other public functions inside theMotor
class. They behave just like normal functions.
- In the main function, we create a
Motor
instance calledfrontLeftMotor
that’s given the name “Motor0”. Then, we call thestart
andstop
functions on thefrontLeftMotor
.- The period is used to access variables and functions of an object.
- Try it: See what happens when typing “
frontLeftMotor.name
“.
- Try it: See what happens when typing “
- The
start
andstop
functions cannot be called like normal functions. They can only be used after a “.
” following aMotor
object.
- The period is used to access variables and functions of an object.
Phew! That was a lot. Now let’s go over some miscellaneous topics:
Ordering Functions and Variables in a Class
The order of functions and variables inside a class does not matter. For example, all of the programs below are equivalent to the first sample program:
#include <iostream> #include <string> class Motor { public: Motor(std::string name) { std::cout << "Beginning motor constructor\n"; this->name = name; } void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << name << "\n"; } private: std::string name; }; int main() { Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
#include <iostream> #include <string> class Motor { public: Motor(std::string name) { std::cout << "Beginning motor constructor\n"; this->name = name; } void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } private: std::string name; public: void stop() { std::cout << "Stopping motor " << name << "\n"; } }; int main() { Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
Default Constructor
If you don’t specify a constructor for a class, that class is automatically given a default constructor, in which each variable inside the class is given a default value. In the case of the Motor
class, the name
variable is set to an empty string (“”):
#include <iostream> #include <string> class Motor { private: std::string name; public: void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << name << "\n"; } }; int main() { Motor frontLeftMotor; frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
Program output:
Starting motor with speed: 0.8 Stopping motor
We can also create our own default constructor by defining a constructor with no parameters:
#include <iostream> #include <string> class Motor { private: std::string name; public: Motor() { name = "DEFAULT NAME"; } void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << name << "\n"; } }; int main() { Motor frontLeftMotor; frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
Program output:
Starting motor DEFAULT NAME with speed: 0.8 Stopping motor DEFAULT NAME
Classes can contain multiple constructors, as long as each constructor has a different set of parameters:
#include <iostream> #include <string> class Motor { private: std::string name; public: Motor() { name = "DEFAULT NAME"; } Motor(std::string name) { this->name = name; } void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << name << "\n"; } }; int main() { Motor frontLeftMotor; frontLeftMotor.start(0.8); frontLeftMotor.stop(); Motor frontRightMotor("Motor1"); frontRightMotor.start(0.6); frontRightMotor.stop(); return 0; }
Program output:
Starting motor DEFAULT NAME with speed: 0.8 Stopping motor DEFAULT NAME Starting motor Motor1 with speed: 0.6 Stopping motor Motor1
Just remember not to to use parentheses when creating a class instance with a default constructor.
Destructor
A destructor is the opposite of a constructor; it’s automatically called when an object of the destructor’s class goes out of scope or is deleted (in the case of it being on the heap). Destructors look just like constructors, except they contain a tilde character (“~
“) before the class name, and they cannot have any parameters. They also should generally be public.
#include <iostream> class SampleClass { public: ~SampleClass() { std::cout << "SampleClass destructor called!\n"; } }; void sampleFunction() { std::cout << "sampleFunction beginning.\n"; SampleClass sampleClass; std::cout << "sampleFunction end.\n"; } int main() { sampleFunction(); std::cout << "sampleFunction finished.\n"; return 0; }
Program output:
sampleFunction beginning. sampleFunction end. SampleClass destructor called! sampleFunction finished.
Because the SampleClass
object called “sampleClass
” goes out of scope at the end of sampleFunction
, its destructor is called then.
Destructors are often used to delete any heap variables before instances of their class go out of scope (thus preventing memory leaks). Doing this is referred to as RAII.
Static Functions and Variables
Functions and variables in classes can be made “static,” which makes them accessible without an instance of their class. To access static functions and variables, use two colons (“::”) following the class name. Here’s an example:
#include <iostream> #include <string> class Motor { private: std::string name; public: const static std::string MANUFACTURER; Motor(std::string name) { std::cout << "Beginning motor constructor\n"; this->name = name; } void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << name << "\n"; } static std::string className() { return "Motor"; } }; const std::string Motor::MANUFACTURER = "CTR Electronics"; int main() { std::cout << "All of the robot's motors are made by " << Motor::MANUFACTURER << "\n"; std::cout << "Motor class name: " << Motor::className() << "\n"; Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
Program output:
All of the robot's motors are made by CTR Electronics Motor class name: Motor Beginning motor constructor Starting motor Motor0 with speed: 0.8 Stopping motor Motor0
Always be aware that, to set the value of a class’s static variable, you need to set its value outside of the class, and you also need to include the variable’s data type (including the word “const
” if applicable and excluding the word “static
“). I’ve got no idea why doing this is so complex…
Static functions and classes can be very useful for any aspects of a class that won’t be different among different instances. For example, different motor objects can be given different names, so the name
variable inside the Motor
class isn’t static. However, all of our robot’s motors are made by CTR Electronics, so the MANUFACTURER
variable is static.
Initializer Lists
If a class contains const
variables that you want to set within the class’s constructor, then you need to use an initializer list. Setting a const
variable equal to some other value in the usual manner doesn’t work, which you can see here:
#include <iostream> #include <string> class Motor { private: const std::string NAME; public: Motor(std::string name) { std::cout << "Beginning motor constructor\n"; NAME = name; } void start(float speed) { std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << NAME << "\n"; } }; int main() { Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
This code should not compile because you cannot set const
variables equal to a new value after the const
variable is created (since the variable must be constant).
In order to make this program work, we can use an initializer list:
#include <iostream> #include <string> class Motor { private: const std::string NAME; public: Motor(std::string name) : NAME(name) { std::cout << "Beginning motor constructor\n"; } void start(float speed) { std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << NAME << "\n"; } }; int main() { Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
To use an initializer list, just put a colon after the constructor’s parentheses, followed by the name of one of the class’s variables with the value to set that variable equal to in parentheses. Multiple variables can go in an initializer list, separated by commas.
Structures
In classes, all variables and functions are private by default. This means that the code in the last sample program is equivalent to this:
#include <iostream> #include <string> class Motor { const std::string NAME; public: Motor(std::string name) : NAME(name) { std::cout << "Beginning motor constructor\n"; } void start(float speed) { std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << NAME << "\n"; } }; int main() { Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
In C++, a “struct
” (structure) is the same as a class, except its values are public by default. This means that a Motor
structure could look like this:
#include <iostream> #include <string> struct Motor { Motor(std::string name) : NAME(name) { std::cout << "Beginning motor constructor\n"; } void start(float speed) { std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << NAME << "\n"; } private: const std::string NAME; }; int main() { Motor frontLeftMotor("Motor0"); frontLeftMotor.start(0.8); frontLeftMotor.stop(); return 0; }
Most of the time, it’s better to use a class than a structure because classes are better at “encapsulating” (separating) your code than structures by default (This just helps with organization. Things can get messy if all of your code can access everything from every class/structure).
Pointers to Classes and Structures
Whenever passing a class or structure instance to a function, it is good practice to pass that object by pointer or by reference. This is because classes and structures can take up a large amount of memory if they contain many variables and functions. By passing by pointer or by reference, we can be more efficient and avoid copying all of that data.
Just remember that an object pointer’s variables and functions are accessed with the arrow symbol (“->
“) rather than a period. Here’s a program showing how an object pointer may be used:
#include <iostream> #include <string> class Motor { private: std::string name; public: Motor(std::string name) { this->name = name; } void start(float speed) { std::cout << "Starting motor " << name << " with speed: " << speed << "\n"; } void stop() { std::cout << "Stopping motor " << name << "\n"; } }; void slowTurnRight(Motor *leftMotor, Motor *rightMotor) { leftMotor->start(0.5); rightMotor->start(0.2); } int main() { Motor frontLeftMotor("Motor0"); Motor frontRightMotor("Motor1"); slowTurnRight(&frontLeftMotor, &frontRightMotor); frontLeftMotor.stop(); frontRightMotor.stop(); return 0; }
Program output:
Starting motor Motor0 with speed: 0.5 Starting motor Motor1 with speed: 0.2 Stopping motor Motor0 Stopping motor Motor1
The arrow symbol is actually just shorthand for combining the dereferencing operator (“*
“) and a period. In the program above, leftMotor->start(0.5);
is the same as (*leftMotor).start(0.5);
. However, I’m sure you can see that typing “->
” is a bit easier.
Object Oriented Programming
The use of classes in code is often called “object oriented programming” because instances of classes represent “objects” that can be used and manipulated. Object oriented programming is one of many programming “paradigms” (methodologies) that exist, and it is often very useful for large projects (like the code for our robot!) due to its ability to easily organize our code. We’ll be using classes a lot for our robot, so be sure you fully understand how they work before moving on!
Fun fact: C++ was created in order to turn the C programming language into a language that would support object oriented programming. C does have structures, but they’re very limited because they can only contain variables (not functions). C++ updated structures to allow for functions, added classes, and also added many more features that are really handy to have available (vectors, for instance).
You may notice now that many of the C++ features we’ve worked with so far are actually just classes: std::vector
, std::string
, and std::cout
are all classes.