Hello! Welcome to your first lesson in the "C++ for ESP32" course.
This first module focuses on the object-oriented features of C++ that are essential for writing clean, modular, and reusable code for embedded systems.
Today, we'll start with the most fundamental concept of Object-Oriented Programming (OOP): the class. You'll learn how to define your own custom data types that bundle together related data and the functions that operate on that data. This lesson directly addresses the first learning outcome of the course: Define classes with member variables and member functions in C++.
Given your background in C, you can think of a C++ class as a super-powered version of a C struct. While a struct is great for grouping data, a class allows you to group both data and the functions that manipulate that data into a single, neat package. This is a powerful idea that we will use throughout this course to model real-world components like motors, sensors, and communication interfaces for the ESP32.
Let's get started.
1. The "Why": From Data Structures to Objects
In C, you're used to creating structs to group related data. For example, to represent a DC motor, you might create a struct like this:
// C-style struct
struct DCMotor {
double voltage;
int speed_rpm;
int direction_pin1;
int direction_pin2;
int speed_pin;
};
You would then write separate functions to operate on this struct:
void start_motor(struct DCMotor* motor);
void stop_motor(struct DCMotor* motor);
void set_motor_speed(struct DCMotor* motor, int new_speed);
This works, but the data (DCMotor struct) and the operations (start_motor, stop_motor, etc.) are separate. Object-Oriented Programming brings them together. A class is a blueprint that combines these attributes (data) and methods (functions) into a single unit called an object.
This approach helps us write code that is more intuitive and organized, especially as our projects grow in complexity—a common scenario in embedded systems.
2. Conceptual Overview: Blueprints and Instances
Before we dive into the C++ syntax, let's build a strong mental model of what classes and objects are. The following video provides an excellent introduction using real-world analogies, including one about cars that should be very familiar.
C++ CLASSES & OBJECTS explained easy 🧍
This video from Bro Code introduces the core ideas of objects, attributes (characteristics), and methods (actions). Please watch the first part for a conceptual overview and the third part for a practical example using a Car class.
Watch the sections from the beginning to 01:24 and from 07:21 to 10:49. Focus on understanding the distinction between a 'class' as a blueprint and an 'object' as an instance, and how 'attributes' and 'methods' relate to this concept.
To summarize the key ideas from the video:
- Class: A blueprint or template. It defines the properties and behaviors that all objects of its type will have. For example, the engineering drawing for a Ford Mustang.
- Object: A specific instance of a class. It's a concrete thing created from the blueprint, with its own specific values for its properties. For example, a specific silver 2023 Ford Mustang with a particular VIN.
- Member Variables (or Attributes): The data stored inside an object that represents its state or characteristics (e.g.,
color,year,current_speed). - Member Functions (or Methods): The functions that belong to the class and define the actions an object can perform (e.g.,
accelerate(),brake()).
3. Defining a Class in C++
Now, let's translate these concepts into C++ code. We'll create a DCMotor class, a component you'll likely work with in embedded projects.
A class definition starts with the class keyword, followed by the class name, and a body enclosed in curly braces {}. The definition must end with a semicolon ;.
#include <iostream>
#include <string>
// Class Definition
class DCMotor {
public:
// Member Variables (Attributes)
std::string name;
int speed_rpm;
bool is_rotating;
// Member Functions (Methods)
void start() {
is_rotating = true;
std::cout << name << " motor started." << std::endl;
// On an ESP32, this might set a GPIO pin HIGH
}
void stop() {
is_rotating = false;
speed_rpm = 0;
std::cout << name << " motor stopped. Speed set to 0 RPM." << std::endl;
// On an ESP32, this might set a GPIO pin LOW
}
void set_speed(int new_speed) {
if (is_rotating) {
speed_rpm = new_speed;
std::cout << name << " motor speed set to " << speed_rpm << " RPM." << std::endl;
// On an ESP32, this would likely adjust a PWM signal
} else {
std::cout << "Cannot set speed. " << name << " motor is not rotating." << std::endl;
}
}
}; // Don't forget the semicolon!
int main() {
// We will create and use objects here in the next section
return 0;
}
Let's break this down.
Member Variables and Access Specifiers
Inside the class, we declared name, speed_rpm, and is_rotating. These are the member variables. Each DCMotor object we create will have its own copy of these variables.
You'll notice the public: keyword. This is an access specifier. It dictates which parts of your code can access the members that follow it.
public: Members can be accessed from anywhere, including from outside the class (e.g., in yourmainfunction).private: Members can only be accessed by other member functions within the same class.
By default, all members in a C++ class are private. This is a key difference from a C struct, where members are public by default. We must explicitly use public: to make our members accessible for now. We'll explore this concept of data hiding and encapsulation in a later lesson.
To understand this better, please read the following short text.
This documentation from cplusplus.com provides a formal definition of a C++ class. It clearly explains the role of access specifiers and the crucial detail that class members are private by default.
Read the section that begins just after the class syntax example. Focus on the descriptions of private, public, and protected, and pay close attention to the statement about the default access level for members of a class.
Member Functions
The functions start(), stop(), and set_speed() are member functions. Notice how they can directly access and modify the member variables (is_rotating, speed_rpm, name) without needing them to be passed as parameters. They operate on the data of the specific object they are called on.
4. Creating and Using Objects
Defining a class doesn't create any objects; it only defines the blueprint. To create an object (a process called instantiation), you declare a variable of the class type:
int main() {
// Create an object (instance) of the DCMotor class
DCMotor motor1;
// Access and assign values to its member variables using the dot (.) operator
motor1.name = "Main Drive";
motor1.speed_rpm = 0;
motor1.is_rotating = false;
// Call its member functions, also using the dot operator
motor1.start();
motor1.set_speed(1500);
motor1.stop();
std::cout << "--------------------" << std::endl;
// You can create multiple, independent objects from the same class
DCMotor fan_motor;
fan_motor.name = "Cooling Fan";
fan_motor.set_speed(5000); // This will fail because the motor hasn't been started
fan_motor.start();
fan_motor.set_speed(5000);
return 0;
}
Key points from this example:
DCMotor motor1;creates an object namedmotor1. This object has its own set ofname,speed_rpm, andis_rotatingvariables in memory.- The dot operator (
.) is used to access a specific object's members, whether they are variables or functions.motor1.start()calls thestart()function on themotor1object, so it modifiesmotor1.is_rotating. motor1andfan_motorare completely separate instances. Changing one does not affect the other. Each has its own state.
The following video provides another complete walkthrough of defining a class, creating objects, and calling their methods. It uses a different example (a bank account), which will help solidify your understanding of the general pattern.
Introduction To Classes And Objects | C++ Tutorial
This video from Portfolio Courses walks through the entire process of defining a class, creating objects, and using them. It's a great way to reinforce what we've just covered.
Watch from 02:25 to the end (11:18). Pay attention to how the BankAccount class is defined, how account1 and account2 objects are created, and how the dot operator is used to access their unique data and call their methods.
5. Defining Member Functions Outside the Class
For short functions, defining them inside the class declaration (as we did above) is fine. For longer, more complex functions, it's common practice to only declare them in the class and define them outside. This keeps the class definition clean and easy to read, acting as a clear API summary.
To do this, you use the scope resolution operator ::.
#include <iostream>
#include <string>
class DCMotor {
public:
// Member Variables
std::string name;
int speed_rpm;
bool is_rotating;
// Member Function Declarations
void start();
void stop();
void set_speed(int new_speed);
};
// Member Function Definitions
void DCMotor::start() {
is_rotating = true;
std::cout << name << " motor started." << std::endl;
}
void DCMotor::stop() {
is_rotating = false;
speed_rpm = 0;
std::cout << name << " motor stopped. Speed set to 0 RPM." << std::endl;
}
void DCMotor::set_speed(int new_speed) {
if (is_rotating) {
speed_rpm = new_speed;
std::cout << name << " motor speed set to " << speed_rpm << " RPM." << std::endl;
} else {
std::cout << "Cannot set speed. " << name << " motor is not rotating." << std::endl;
}
}
The syntax void DCMotor::start() tells the compiler that this start function is a member of the DCMotor class. Even though the definition is outside the class body, it still has access to all of the class's member variables. This is the standard way you'll see classes organized in larger C++ projects.
Conclusion
In this lesson, we've taken our first and most important step into object-oriented C++. You've learned how to move beyond simple data structures and create your own custom types that encapsulate both data and behavior.
Here are the key takeaways:
- A class is a user-defined blueprint that combines member variables (attributes) and member functions (methods).
- An object is a concrete instance of a class, with its own state (values for its member variables).
- The
classkeyword is used to define a class, and you must add a semicolon;after the closing brace. - By default, class members are
private. You use thepublic:access specifier to make them accessible from outside the class. - The dot operator (
.) is used to access the members of a specific object. - Member functions can be defined inside the class or outside using the scope resolution operator (
::).
In our DCMotor example, we created an object that not only holds data (speed_rpm) but also contains the logic to manage its own state (start(), stop()). As we move to the ESP32, you will use this exact pattern to create classes for physical hardware, making your code for complex systems much easier to manage.
In the next lesson, we will look at a special type of member function called a constructor, which allows us to initialize our objects more cleanly and reliably the moment they are created.