Lesson 99 min read

Classes & Objects

A class is a blueprint for a house β€” it defines the rooms (data) and the doors (methods), and you build as many houses as you want.

From Scattered Data to Organized Objects

Imagine you're building a banking app. You could track each customer with separate variables β€” balance1, name1, balance2, name2 β€” but that gets messy fast. What you really want is a BankAccount blueprint that bundles the data (balance, name) with the operations (deposit, withdraw) into one clean package.

That blueprint is a class. Each account you create from it is an object (or instance). The class defines the rules; the objects live by them.

class vs struct

In C++, class and struct are almost identical. The only difference is the default access level:

  • struct β€” members are public by default
  • class β€” members are private by default

By convention, use struct for simple data bundles (like a Point with x and y) and class for anything with behavior and invariants.

struct vs class β€” The Only Real Difference

#include <iostream>
using namespace std;
// struct: members are public by default
struct Point {
double x; // public
double y; // public
};
// class: members are private by default
class Secret {
int value; // private β€” can't access from outside!
public:
void setValue(int v) { value = v; }
int getValue() const { return value; }
};
int main() {
Point p;
p.x = 3.0; // Fine β€” public by default
p.y = 4.0;
cout << "Point: (" << p.x << ", " << p.y << ")" << endl;
Secret s;
// s.value = 42; // ERROR: value is private!
s.setValue(42);
cout << "Secret: " << s.getValue() << endl;
return 0;
}
Output
Point: (3, 4)
Secret: 42

Constructors β€” Building the Object Right

A constructor is a special function that runs when an object is created. Its job is to put the object into a valid state from the very beginning. No half-initialized objects floating around.

C++ gives you several constructor flavors:

  • Default constructor β€” no parameters, creates a sensible default
  • Parameterized constructor β€” takes arguments to initialize with specific values
  • Member initializer list β€” initializes members before the constructor body runs (faster, and required for const/reference members)

BankAccount β€” A Complete Class

#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string owner;
double balance;
int accountId;
public:
// Constructor with member initializer list
BankAccount(const string& name, double initial, int id)
: owner(name), balance(initial), accountId(id) {
cout << "Account created for " << owner << endl;
}
// Deposit money
void deposit(double amount) {
if (amount > 0) {
balance += amount;
cout << "Deposited $" << amount << endl;
}
}
// Withdraw money
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
cout << "Withdrew $" << amount << endl;
return true;
}
cout << "Insufficient funds!" << endl;
return false;
}
// const method β€” promises not to modify the object
double getBalance() const {
return balance;
}
// const method β€” read-only access
void display() const {
cout << "[" << accountId << "] " << owner
<< ": $" << balance << endl;
}
};
int main() {
BankAccount alice("Alice", 1000.0, 1001);
alice.deposit(500.0);
alice.withdraw(200.0);
alice.withdraw(5000.0);
alice.display();
return 0;
}
Output
Account created for Alice
Deposited $500
Withdrewed $200
Insufficient funds!
[1001] Alice: $1300
Note: Always use member initializer lists in constructors instead of assignment in the body. Writing : owner(name), balance(initial) directly initializes each member. Assigning in the body (owner = name;) first default-constructs the member, then assigns β€” doing double work. For const and reference members, initializer lists are required.

Access Modifiers β€” Controlling the Doors

Every member of a class has an access level:

  • public β€” anyone can access it. This is the interface you expose to the world.
  • private β€” only the class itself can access it. This is for internal data and helper functions.
  • protected β€” the class and its derived classes (children) can access it. We'll see this more in inheritance.

The golden rule: make data private and provide public methods to interact with it. This is called encapsulation β€” hiding the messy internals behind a clean interface.

The this Pointer

Inside any non-static method, this is a pointer to the current object. You rarely need it explicitly, but it's useful when a parameter name shadows a member name, or when you want to return the object itself for method chaining.

The this Pointer & Method Chaining

#include <iostream>
#include <string>
using namespace std;
class Builder {
private:
string result;
public:
Builder() : result("") {}
// Returns *this to enable chaining
Builder& add(const string& text) {
result += text;
return *this; // Return the current object
}
Builder& addLine(const string& text) {
result += text + "\n";
return *this;
}
void print() const {
cout << result;
}
};
int main() {
Builder b;
b.addLine("Hello")
.addLine("World")
.add("Done!");
b.print();
return 0;
}
Output
Hello
World
Done!

const Methods β€” The Read-Only Promise

Marking a method const tells the compiler: "this method will NOT modify any member variables." This is crucial because it allows you to call the method on const objects and const references. If you have a const BankAccount&, you can call getBalance() (const) but not deposit() (non-const).

Destructors β€” Cleaning Up

A destructor runs automatically when an object is destroyed (goes out of scope, is deleted, etc.). Its name is ~ClassName(). For simple classes, the compiler-generated destructor is fine. You write your own when the class manages resources like heap memory, file handles, or network connections.

Destructor β€” Automatic Cleanup

#include <iostream>
using namespace std;
class Logger {
private:
string name;
public:
Logger(const string& n) : name(n) {
cout << "[" << name << "] Created" << endl;
}
~Logger() {
cout << "[" << name << "] Destroyed" << endl;
}
void log(const string& msg) const {
cout << "[" << name << "] " << msg << endl;
}
};
int main() {
Logger a("Main");
a.log("Starting...");
{
Logger b("Inner");
b.log("Doing work");
} // b is destroyed here β€” goes out of scope
a.log("Done");
return 0;
} // a is destroyed here
Output
[Main] Created
[Main] Starting...
[Inner] Created
[Inner] Doing work
[Inner] Destroyed
[Main] Done
[Main] Destroyed
Challenge

Quick check

What is the ONLY difference between struct and class in C++?
← FunctionsInheritance & Polymorphism β†’