C++ for C Developers | Lesson 5: Introducing Classes: Structs with Muscles (Encapsulation)
Goal: Understand the class
keyword in C++, see how it relates to C struct
s but adds behavior (member functions) and access control (public
/private
), introducing the fundamental Object-Oriented Programming (OOP) principle of encapsulation.
1. C Structs: Data Containers
Recall C struct
s: they are primarily used to group related data items together. Any code can typically access and modify the members directly. Logic that operates on the struct’s data is usually implemented as separate functions.
#include <stdio.h>
#include <math.h> // For sqrt
// C struct for a 2D Point
typedef struct {
double x;
double y;
} PointC;
// Separate functions to operate on PointC
void print_point(PointC p) {
printf("PointC(x=%f, y=%f)\n", p.x, p.y);
}
double distance_from_origin(PointC p) {
return sqrt(p.x * p.x + p.y * p.y);
}
void move_point(PointC *p, double dx, double dy) {
if (p != NULL) {
p->x += dx;
p->y += dy;
}
}
int main() {
PointC p1 = {3.0, 4.0}; // Aggregate initialization
print_point(p1);
printf("Distance from origin: %f\n", distance_from_origin(p1));
move_point(&p1, 1.0, -2.0);
printf("After move: ");
print_point(p1);
// Direct access is possible (and sometimes problematic)
p1.x = -100.0; // We can directly change internal data from anywhere
printf("After direct modification: ");
print_point(p1);
return 0;
}
flowchart TD subgraph C_Struct["C Struct (Data Only)"] C1["struct PointC { x, y };"] C2["Data is public"] C3["Functions are separate"] C4["Direct access: p.x = -100"] end
The C approach keeps data (struct PointC
) and behavior (print_point
, distance_from_origin
, move_point
) separate. All data members are exposed.
2. C++ struct
s: Data + Behavior
In C++, struct
s are enhanced. They can contain not only data members but also member functions (also called methods) that operate on the struct’s data.
#include <iostream>
#include <cmath> // For std::sqrt
// C++ struct for a 2D Point
struct PointCPP_Struct {
// Data members
double x;
double y;
// Member functions (Methods) - operate on the struct's data
void print() {
// Inside a member function, 'x' and 'y' implicitly refer
// to the members of the specific object this function is called on.
std::cout << "PointCPP_Struct(x=" << x << ", y=" << y << ")" << std::endl;
}
double distanceFromOrigin() {
return std::sqrt(x * x + y * y);
}
void move(double dx, double dy) {
x += dx;
y += dy;
}
}; // Don't forget the semicolon!
int main() {
PointCPP_Struct p1 = {3.0, 4.0}; // Aggregate initialization still works
p1.print(); // Call member function using the dot (.) operator
std::cout << "Distance from origin: " << p1.distanceFromOrigin() << std::endl;
p1.move(1.0, -2.0);
std::cout << "After move: ";
p1.print();
// Direct access is still possible by default in a C++ struct
p1.x = -200.0;
std::cout << "After direct modification: ";
p1.print();
return 0;
}
Notice how the functions are now part of the struct
definition and are called on an object (p1.print()
). This bundles data and behavior.
3. C++ class
: Introducing Access Control
The class
keyword provides everything a C++ struct
does, but with one key difference in default behavior and a stronger emphasis on encapsulation.
Encapsulation means:
- Bundling data (attributes) and methods (behavior) that operate on the data together within a single unit (the class).
- Controlling access to the internal state (data members) of that unit, hiding implementation details.
This is achieved using access specifiers:
public
: Members declared aspublic
are accessible from anywhere (inside the class, or by outside code). This defines the class’s interface.private
: Members declared asprivate
are only accessible from within the member functions of the same class. They are hidden from outside code. This protects the internal state.protected
: Related to inheritance (Lesson 8). Acts likeprivate
, but accessible by derived classes.
The class
vs struct
Difference:
- In a
class
, members areprivate
by default. - In a
struct
, members arepublic
by default.
flowchart LR subgraph Struct St1["Members public by default"] St2["Used for simple data aggregates"] St3["Can have methods"] end subgraph Class Cls1["Members private by default"] Cls2["Used for encapsulation"] Cls3["Interface via public methods"] end
That’s the only technical difference. By convention, class
is used when you want to enforce data hiding and encapsulation, while struct
is often used for simpler data aggregates where direct access is acceptable (though C++ struct
s can have private
members and methods too).
Example using class
:
#include <iostream>
#include <cmath>
class Point { // Using 'class' keyword
// By default, members here would be private
private: // Explicitly declare members as private (Good practice)
// Data members are now hidden from direct external access
double m_x; // Often prefix private members (e.g., m_, _, etc. - convention varies)
double m_y;
public: // Members from here on are public
// Provide public methods to interact with the private data
// Setter methods to modify private data (optional, controls how data changes)
void setX(double x) {
// Could add validation here, e.g., if (x >= 0)
m_x = x;
}
void setY(double y) {
m_y = y;
}
// Getter methods to access private data (controls how data is read)
double getX() const { // 'const' means this method doesn't modify the object's state
return m_x;
}
double getY() const {
return m_y;
}
// Constructor (special method for initialization - covered next lesson!)
// For now, let's use setters. A default constructor is implicitly generated.
// Other methods operating on the data
void print() const { // Also marked const
std::cout << "Point(x=" << m_x << ", y=" << m_y << ")" << std::endl;
}
double distanceFromOrigin() const { // Also marked const
return std::sqrt(m_x * m_x + m_y * m_y);
}
void move(double dx, double dy) { // Not const - it modifies state
m_x += dx;
m_y += dy;
}
}; // Don't forget the semicolon!
int main() {
Point p1; // Create an instance (object) of the Point class.
// Uses the default constructor (members likely uninitialized or zeroed)
// Set values using public methods
p1.setX(3.0);
p1.setY(4.0);
// Access values using public methods
std::cout << "Initial state: ";
p1.print();
std::cout << "X coordinate: " << p1.getX() << std::endl;
std::cout << "Distance from origin: " << p1.distanceFromOrigin() << std::endl;
p1.move(1.0, -2.0);
std::cout << "After move: ";
p1.print();
// ILLEGAL! Cannot access private members directly from outside the class
// p1.m_x = -300.0; // Compile-time Error! 'm_x' is private
// std::cout << p1.m_y; // Compile-time Error! 'm_y' is private
// We must use the public interface
p1.setX(-300.0); // OK, using the public setter method
std::cout << "After setting X via method: ";
p1.print();
// Pointer access
Point* p_ptr = new Point(); // Allocate on heap
p_ptr->setX(10.0); // Use -> operator to access members via pointer
p_ptr->setY(20.0);
p_ptr->print();
delete p_ptr; // Don't forget to delete (or use smart pointers!)
return 0;
}
Benefits of Encapsulation:
- Data Hiding: Protects the internal state of an object from unintended external modification. Prevents the object from being put into an invalid state.
- Maintainability: The internal implementation of the class (private members, method bodies) can be changed without affecting the code that uses the class, as long as the public interface (
public
methods) remains compatible. - Organization: Keeps data and the operations related to it logically grouped together.
- Control: Allows the class designer to control exactly how data is accessed and modified (e.g., through getter/setter methods that can include validation).
Summary of Lesson 5:
- C++
struct
s can contain both data members and member functions (methods). By default, members arepublic
. - C++
class
es also contain data members and member functions. By default, members areprivate
. - Access specifiers (
public
,private
,protected
) control visibility of class members. - Encapsulation is the bundling of data and methods within a class and controlling access (usually making data
private
and providingpublic
methods). - Objects are instances of classes. Use
.
to access members of an object,->
to access members via a pointer to an object. - Use
class
es to implement abstract data types with hidden internal state and a controlled public interface.
Next Lesson: We’ll focus on special member functions: constructors (how objects are initialized) and destructors (how objects are cleaned up), which are crucial for proper object lifecycle management, especially when dealing with resources like dynamic memory.
More Sources:
cppreference – Class Declaration: https://en.cppreference.com/w/cpp/language/class
Learn C++ – Classes Tutorial: https://www.learncpp.com/cpp-tutorial/classes-and-class-members/