TeqStudy ยท C++ Track ยท Phase 05

OOP Fundamentals

Classes and objects, constructors and destructors, the this pointer, encapsulation, static members, and operator overloading โ€” the foundation every real C++ program builds on.

๐Ÿ—๏ธ ๐Ÿ” ๐Ÿ›ก๏ธ โž• ๐Ÿ
Course Progress
0%
MODULE 01

๐Ÿ—๏ธ Classes & Objects โ€” The Big Idea

Everything so far has kept data and the functions that work on it separate. This phase introduces the idea of bundling them together โ€” the foundation of essentially all real-world C++ code you'll encounter.

๐Ÿงฌ A Class Is a Custom Blueprint

Every type you've used so far โ€” int, std::string โ€” was built into C++ already. A class lets YOU define a brand new type, bundling together both data (variables) and behavior (functions) that belong together conceptually.

C++
class Dog {
public:
    std::string name;
    int age;

    void bark() {
        std::cout << name << " says woof!" << std::endl;
    }
};
๐Ÿช A Class Is a Cookie Cutter, an Object Is a Cookie

A cookie cutter defines a SHAPE โ€” it isn't itself a cookie you can eat. From that one cutter, you can stamp out as many actual cookies as you want, each a real, separate cookie following that shape. A class is the cutter (the blueprint); an object is an actual instance made from it โ€” a real thing in memory, following the class's shape.

๐Ÿ• Creating Objects from a Class
C++
Dog myDog;
myDog.name = "Rex";
myDog.age = 3;
myDog.bark();  // prints "Rex says woof!"

Dog anotherDog;
anotherDog.name = "Bella";
anotherDog.bark();  // prints "Bella says woof!"

myDog and anotherDog are both objects โ€” separate instances of the Dog class, each with their OWN name and age, but sharing the same bark() behavior defined once in the class itself.

๐Ÿง  Quick Check
What's the relationship between a class and an object?
MODULE 02

๐Ÿ” Member Variables & Access Control

The last module's Dog class left its data wide open โ€” anyone could set an impossible age like -50. This module covers how to actually protect a class's internal data.

๐ŸŒ public โ€” Accessible From Anywhere

The last module's Dog class used public, meaning any code outside the class can freely read or change name and age directly โ€” exactly what let something like myDog.age = -50; happen without any resistance.

๐Ÿ”’ private โ€” Accessible Only From Inside the Class
C++
class Dog {
private:
    int age;   // can ONLY be accessed by Dog's own member functions

public:
    std::string name;
};

Dog myDog;
myDog.age = 3;   // COMPILER ERROR โ€” age is private!

Marking age as private means outside code genuinely cannot touch it directly at all โ€” the compiler enforces this, refusing to compile any code that tries. This is a deliberate design choice, not a limitation to work around: it's the foundation of Module 6's encapsulation.

๐Ÿ“ The Default Convention

The common convention in real C++ code: keep member variables private by default, and only make things public when there's a genuine reason external code needs direct access โ€” usually functions, not raw data. You'll see exactly why in Module 6.

๐Ÿง  Quick Check
If a member variable is marked private, what happens when code OUTSIDE the class tries to access it directly?
MODULE 03

๐Ÿ—๏ธ Constructors

Creating a Dog object and then setting its fields one by one, line by line, is tedious and easy to forget a step on. A constructor fixes this.

๐ŸŽฌ A Special Function That Runs on Creation
C++
class Dog {
public:
    std::string name;
    int age;

    Dog(std::string dogName, int dogAge) {   // the constructor
        name = dogName;
        age = dogAge;
    }
};

Dog myDog("Rex", 3);   // name and age set immediately, in one line

A constructor is a special function โ€” sharing the class's exact name, with no return type at all, not even void โ€” that runs automatically the moment an object is created. It's the natural place to guarantee an object starts out in a valid, fully-initialized state.

๐Ÿ“‹ The Member Initializer List โ€” A Cleaner Alternative
C++
Dog(std::string dogName, int dogAge) : name(dogName), age(dogAge) {
    // body can be empty โ€” the : list already did the initialization
}

The : name(dogName), age(dogAge) syntax is called a member initializer list โ€” it's the preferred, more efficient modern C++ style for simple field-setting constructors, though both approaches produce a working result.

๐ŸŽ The Default Constructor

If you write NO constructor at all, C++ automatically provides an empty one for you (a "default constructor") that does nothing beyond creating the object. But the moment you write ANY constructor yourself, that automatic one disappears โ€” if you still want a no-argument way to create the object, you need to write it explicitly.

๐Ÿง  Quick Check
When does a class's constructor actually run?
MODULE 04

๐Ÿงน Destructors

The mirror image of a constructor โ€” cleanup that happens automatically, right before an object ceases to exist. This connects directly back to Phase 4's memory management.

๐ŸŽฌ A Special Function That Runs on Destruction
C++
class Dog {
public:
    std::string name;

    Dog(std::string dogName) : name(dogName) {
        std::cout << name << " was created." << std::endl;
    }

    ~Dog() {   // the destructor โ€” note the ~
        std::cout << name << " is being destroyed." << std::endl;
    }
};

The destructor โ€” the class name prefixed with ~ โ€” runs automatically right before an object is destroyed: when a stack (local) object goes out of scope, or explicitly via delete for a heap object.

๐Ÿ”— Why This Connects Directly to Phase 4

A destructor is exactly the right place to delete any heap memory the object itself allocated with new during its lifetime โ€” guaranteeing that cleanup happens automatically, every single time an object is destroyed, without you needing to remember to call it manually everywhere the object might go out of scope. This is a genuinely powerful pattern (formally called RAII) that Phase 8's smart pointers build on directly.

๐Ÿ’ก
You don't always need to write one yourselfIf your class's members are all simple values or well-behaved types like std::string (which manage their own cleanup automatically), C++'s automatically-generated default destructor is often perfectly sufficient. You genuinely need a custom destructor mainly when your class directly manages a raw resource โ€” like memory from new, or a file handle.
๐Ÿง  Quick Check
Why is a class's destructor a natural place to call delete on memory the object allocated with new?
MODULE 05

๐Ÿ‘† Member Functions & the this Pointer

How does a member function like bark() know WHICH Dog object it's actually working on, when many Dog objects might exist at once? This module answers that directly.

๐Ÿค” The Question This Module Answers
C++
Dog rex("Rex", 3);
Dog bella("Bella", 2);

rex.bark();    // prints "Rex says woof!"
bella.bark();  // prints "Bella says woof!"

Both calls run the exact same bark() function code โ€” there's only ONE copy of that function in memory, shared by every Dog object. So how does it know to use rex's name in one call and bella's in the other?

๐Ÿ‘† this โ€” A Hidden Pointer to the Current Object

Every non-static member function secretly receives an extra, invisible parameter: this โ€” a pointer to the exact object the function was called on. When you write rex.bark(), C++ effectively passes &rex in as this behind the scenes, and name inside the function body actually means this->name โ€” "the name belonging to whichever object I was called on."

C++ โ€” what's really happening, made explicit
void bark() {
    std::cout << this->name << " says woof!" << std::endl;
    // "this->name" and just "name" mean the exact same thing here
}
๐ŸŽฏ When You Actually Need to Write this Explicitly

Most of the time, you never need to type this yourself โ€” C++ assumes it automatically. It becomes genuinely necessary mainly when a parameter name shadows (matches) a member variable's name, and you need to distinguish them: this->age = age; unambiguously means "set the member variable to the parameter's value," resolving what would otherwise be confusing.

๐Ÿง  Quick Check
What does the this pointer inside a member function actually point to?
MODULE 06

๐Ÿ›ก๏ธ Encapsulation โ€” Getters & Setters

Module 2 made age private, but that alone means NO code โ€” not even legitimate code โ€” can read or change it. This module shows how to expose controlled, validated access instead.

๐Ÿ“– A Getter โ€” Controlled Reading
C++
class Dog {
private:
    int age;

public:
    int getAge() {
        return age;
    }
};

A getter is a public function whose only job is returning a private member's value โ€” outside code can now READ age through getAge(), even though it can't touch the variable directly.

โœ๏ธ A Setter โ€” Controlled, VALIDATED Writing
C++
void setAge(int newAge) {
    if (newAge >= 0 && newAge <= 30) {   // validation!
        age = newAge;
    } else {
        std::cout << "Invalid age ignored." << std::endl;
    }
}

This is the genuine payoff of encapsulation: a setter can validate a new value BEFORE accepting it โ€” rejecting myDog.setAge(-50) outright, something that was completely impossible to prevent back when age was simply public in Module 1.

๐ŸŽฏ The Real Principle, Not Just the Syntax

Encapsulation means hiding an object's internal data and only exposing it through controlled, well-defined functions. The genuine benefit isn't the getter/setter syntax itself โ€” it's that the class can enforce its own rules about what counts as a valid state, and even change HOW data is stored internally later, without breaking any code that uses the class through its public functions.

๐Ÿง  Quick Check
What genuine advantage does a setter function have over simply making a member variable public?
MODULE 07

๐ŸŽญ Constructor Overloading & Default Parameters

Phase 3's function overloading applies directly to constructors too โ€” letting an object be created in more than one way, depending on what information is available.

๐ŸŽญ Multiple Constructors, Same Class
C++
class Dog {
public:
    std::string name;
    int age;

    Dog(std::string dogName, int dogAge) : name(dogName), age(dogAge) {}

    Dog(std::string dogName) : name(dogName), age(0) {}  // age defaults to 0
};

Dog rex("Rex", 3);   // uses the first constructor
Dog puppy("Puppy");  // uses the second โ€” age becomes 0

Just like Phase 3's overloaded functions, C++ picks the matching constructor based on how many arguments you provide and their types โ€” exactly the same overload-resolution rule you already know.

โšก Default Parameters โ€” A More Compact Alternative
C++
Dog(std::string dogName, int dogAge = 0) : name(dogName), age(dogAge) {}

Dog rex("Rex", 3);   // age is 3
Dog puppy("Puppy");  // age defaults to 0 โ€” ONE constructor handles both

int dogAge = 0 in the parameter list means "if the caller doesn't provide this argument, use 0 automatically." This achieves the same flexibility as Module 6's two separate constructors, with less duplicated code โ€” a genuinely useful pattern beyond just constructors, for any function.

๐Ÿง  Quick Check
What does giving a constructor parameter a default value, like int dogAge = 0, actually achieve?
MODULE 08

๐ŸŒ Static Members

Every member variable so far belongs to one specific object. This module covers data that belongs to the CLASS itself, shared identically across every object.

๐Ÿ”ข A Shared Counter, Across All Objects
C++
class Dog {
public:
    std::string name;
    static int totalDogs;   // shared by EVERY Dog object

    Dog(std::string dogName) : name(dogName) {
        totalDogs++;   // every new Dog increments the SAME shared counter
    }
};

int Dog::totalDogs = 0;   // required: define it once, outside the class

A static member variable exists exactly ONCE, shared by the entire class โ€” not duplicated per object the way name is. Every Dog object created increments the SAME totalDogs, giving you an accurate running count across all of them.

๐Ÿ“ž Accessing a Static Member
C++
Dog rex("Rex");
Dog bella("Bella");
std::cout << Dog::totalDogs;   // prints 2 โ€” accessed via the CLASS, not an object

You access a static member through the class name itself (Dog::totalDogs), not through any particular object โ€” reflecting that it genuinely belongs to the class as a whole, not to any one Dog.

๐Ÿง  Quick Check
If Dog has a static member totalDogs, and you create 5 separate Dog objects, how many separate copies of totalDogs actually exist?
MODULE 09

โž• Operator Overloading

Phase 1's + adds numbers and Phase 3's + concatenates strings. This module lets YOUR OWN classes define what + (or any operator) means for objects of that class.

โž• Teaching + What It Means for Your Class
C++
class Point {
public:
    int x, y;

    Point(int xVal, int yVal) : x(xVal), y(yVal) {}

    Point operator+(const Point& other) {
        return Point(x + other.x, y + other.y);
    }
};

Point a(1, 2);
Point b(3, 4);
Point c = a + b;   // c.x is 4, c.y is 6

operator+ is a specially-named member function โ€” defining it teaches C++ exactly what + should DO when both sides are Point objects. Without it, writing a + b on two Point objects would simply be a compiler error, since C++ has no built-in idea what adding two points should mean.

๐ŸŽฏ Why This Is Genuinely Useful, Not Just Clever

Operator overloading lets objects of your own classes be used with the same natural, familiar syntax as built-in types โ€” a + b reads far more clearly than an equivalent a.add(b) function call, especially in math-heavy code (vectors, matrices, complex numbers) where the pattern matches how you'd write the equivalent math by hand.

๐Ÿ’ก
const & in the parameter is a deliberate, common combinationPassing other by reference (Phase 3 Module 7) avoids an unnecessary copy, and const promises the function won't modify it โ€” the standard, idiomatic way to accept "read-only access to another object" as a parameter.
๐Ÿง  Quick Check
Without defining operator+, what happens if you try to write a + b where a and b are objects of your own custom class?
MODULE 10 ๐Ÿ

๐Ÿ Capstone โ€” A BankAccount Class

Every idea from this phase, combined: private data, a validating constructor, encapsulated getters/setters, a static counter, and controlled deposit/withdraw behavior โ€” a genuinely realistic small class.

๐ŸŽฏ The Goal

Build a BankAccount class with a private balance that can never go negative, a constructor requiring a starting balance, deposit/withdraw methods with real validation, and a static counter tracking how many accounts have been created.

๐Ÿ’ป A Reference Solution
main.cpp
#include <iostream>

class BankAccount {
private:
    std::string owner;
    double balance;

public:
    static int totalAccounts;

    BankAccount(std::string ownerName, double startingBalance) : owner(ownerName) {
        balance = (startingBalance >= 0) ? startingBalance : 0;
        totalAccounts++;
    }

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    bool withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            return true;
        }
        std::cout << "Withdrawal denied โ€” insufficient funds." << std::endl;
        return false;
    }

    double getBalance() {
        return balance;
    }
};

int BankAccount::totalAccounts = 0;

int main() {
    BankAccount acc("Jude", 100.00);
    acc.deposit(50.00);
    acc.withdraw(30.00);
    std::cout << "Balance: " << acc.getBalance() << std::endl;
    std::cout << "Total accounts: " << BankAccount::totalAccounts << std::endl;
    return 0;
}
๐Ÿ” Why It's Built This Way
  • balance is private (Module 2) โ€” the only way to change it is through validated deposit/withdraw (Module 6's encapsulation).
  • The constructor itself validates the starting balance, guaranteeing no BankAccount can ever start negative โ€” Module 3's job of guaranteeing valid initial state.
  • totalAccounts is static (Module 8), correctly shared and incremented across every account created.
  • withdraw returns a bool specifically so calling code can check whether it actually succeeded โ€” a genuinely useful, realistic pattern.
๐Ÿ Extend It Yourself

Add operator+ for Transfers

Using Module 9, write a transferTo method that withdraws from one account and deposits into another โ€” reusing the validation you've already written rather than duplicating it. This mirrors exactly how real financial software has to think about combining already-validated operations safely.

Roadmap