Back in the early days of coding, most programs were written using something called procedural programming. This simply meant writing step-by-step instructions, like following a recipe. You define functions, give them data, and run them in order.
While that worked fine for small programs, it got tricky as things got bigger. Since the code and the data it used were handled separately, managing complex programs became messy, especially when multiple parts needed to use or change the same data.
To fix this, programmers came up with a better approach called Object-Oriented Programming (OOP). It groups both data and related functions together into something called an object. This makes the code easier to organize, reuse, and understand.
A class is like a blueprint or template. It defines how something should behave and what it should contain — like the attributes (e.g., name, color) and the behaviors (e.g., move, stop) of a car.
There are mainly two types of classes you’ll hear about:
We’ll explore abstract classes more in upcoming sections. 😊
An object is like a real-world thing created from a class blueprint. It holds actual values and can do things using the functions defined in the class.
There are two main ways to create objects in languages like C++:
// C++ example
class Car {
public:
void drive() { /* ... */ }
};
int main() {
Car myCar; // Stack object
myCar.drive();
// myCar is destroyed automatically
}
new and must manually delete it to
avoid memory leaks.
// C++ example
Car* myCarPtr = new Car(); // Dynamic object
myCarPtr->drive();
delete myCarPtr; // You must delete it manually
🔁 In short: stack objects are easy to manage, while dynamic objects give you more control but require extra care.
A class contains multiple components:
// Attribute example
class Car {
public:
string color;
int model;
};
// Method example
class Car {
public:
void honk() {
cout << "Beep!";
}
};
// Constructor example
class Car {
public:
string color;
Car(string c) {
color = c;
}
};
// Destructor example
class Car {
public:
~Car() {
// cleanup code
}
};
// Access Modifiers example
class Car {
private:
int speed;
public:
void setSpeed(int s) { speed = s; }
};
// Static Member/Function example
class Car {
public:
static int count;
Car() { count++; }
static void showCount() {
cout << count;
}
};
int Car::count = 0;
// Friend Function example
class Box {
private:
int width;
public:
friend void setWidth(Box &b, int w);
};
void setWidth(Box &b, int w) {
b.width = w;
}
Inheritance is like passing down traits from parents to children, but in code! It lets you create a new class (called a derived or child class) based on an existing class (the base or parent class). The child class automatically gets all the attributes and methods of the parent, and you can add more or change them as needed.
This helps you avoid repeating code and makes it easier to organize related classes. For example, you can have Mother and Father classes, and then create a Child class that inherits from them.
// Single Inheritance
class Mother {
public:
void hair_color() { /* ... */ }
};
class Child : public Mother {
// Child gets hair_color() from Mother
};
// Multiple Inheritance
class Mother {
public:
void cook() { /* ... */ }
};
class Father {
public:
void drive() { /* ... */ }
};
class Child : public Mother, public Father {
// Child gets cook() from Mother and drive() from Father
};
// Multilevel Inheritance
class Grandmother {
public:
void cook() { /* ... */ }
};
class Mother : public Grandmother {
// inherits cook()
};
class Child : public Mother {
// inherits cook() from Grandmother through Mother
};
// Hierarchical Inheritance
class Mother {
public:
void hair_type() { /* ... */ }
};
class Son : public Mother { /* ... */ };
class Daughter : public Mother { /* ... */ };
// Both Son and Daughter get thick hair()
// Hybrid Inheritance Example
class Shape { /* ... */ };
class Rectangle : public Shape { /* ... */ };
class Color { /* ... */ };
class ColoredRectangle : public Rectangle, public Color { /* ... */ };
// ColoredRectangle inherits from both Rectangle (which inherits Shape) and Color
Sometimes, multiple inheritance can cause confusion. Imagine both Mother and Father inherit from Grandmother, and then Child inherits from both Mother and Father. Now, Child has two copies of Grandmother! This is called the diamond problem.
// Diamond Problem Example
class Grandmother { public: void cook() { /* ... */ } };
class Mother : public Grandmother { };
class Father : public Grandmother { };
class Child : public Mother, public Father { };
// Child has two copies of Grandmother::cook()
To fix this, C++ lets you use virtual base classes. By declaring the inheritance as virtual, you make sure only one copy of the base class is shared, no matter how many times it appears in the hierarchy.
// Solution with Virtual Base Class
class Grandmother { public: void cook() { /* ... */ } };
class Mother : virtual public Grandmother { };
class Father : virtual public Grandmother { };
class Child : public Mother, public Father { };
// Only one copy of Grandmother in Child
If you ever need to specify which parent's method to use (in case of ambiguity), you can use the scope resolution operator (::):
// Using scope resolution
child.Mother::cook(); // Calls cook() from Mother's Grandmother
child.Father::cook(); // Calls cook() from Father's Grandmother
In summary: inheritance helps you reuse code, but be careful with multiple inheritance! Virtual base classes and the scope resolution operator help you avoid confusion and keep things tidy—even in a big family tree.
✨ You can zoom in or out using your mouse scroll wheel, and click and drag the diagram to explore different parts. It’s like moving around a real whiteboard — helpful for revising all the key concepts at your own pace!