Classes & Objects

Everything so far has been loose variables and standalone functions. A class bundles related data and the functions that operate on it into a single unit — a blueprint. An object is one specific instance built from that blueprint, with its own independent copy of the data.

Defining a class

A bank account has data (an owner, a balance) and behavior (depositing changes the balance). A class groups exactly that. By default, class members are private, so this example marks them public explicitly to keep things simple for now:

</> bank_account.cpp
#include <iostream>
#include <string>

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

    void deposit(double amount) {
        balance += amount;
        std::cout << "Deposited " << amount << ". New balance: " << balance << std::endl;
    }
};

int main() {
    BankAccount account;
    account.owner = "Jamie";
    account.balance = 100;
    account.deposit(50);
    return 0;
}
Terminal output
Deposited 50. New balance: 150

owner and balance are the account's data members. deposit is a member function — inside it, balance refers to that particular object's balance with no extra qualification needed.

One blueprint, many objects

Each BankAccount variable you declare is a completely independent object. Changing one never touches another, even though they share the same class:

</> two_accounts.cpp
#include <iostream>
#include <string>

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

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

int main() {
    BankAccount account1;
    account1.owner = "Jamie";
    account1.balance = 100;

    BankAccount account2;
    account2.owner = "Sasha";
    account2.balance = 500;

    account1.deposit(25);

    std::cout << account1.owner << ": " << account1.balance << std::endl;
    std::cout << account2.owner << ": " << account2.balance << std::endl;
    return 0;
}
Terminal output
Jamie: 125
Sasha: 500

Depositing into account1 left account2's balance of 500 completely undisturbed — separate objects, separate memory, even though they came from the same class definition.

private members and methods that return a result

Marking data private means it can only be touched through the class's own member functions, not poked directly from outside — the encapsulation that makes classes worth using in the first place:

</> withdraw.cpp
#include <iostream>

class BankAccount {
private:
    double balance;

public:
    void setBalance(double amount) {
        balance = amount;
    }

    bool withdraw(double amount) {
        if (amount > balance) {
            std::cout << "Insufficient funds." << std::endl;
            return false;
        }
        balance -= amount;
        return true;
    }

    double getBalance() {
        return balance;
    }
};

int main() {
    BankAccount account;
    account.setBalance(100);

    std::cout << std::boolalpha << account.withdraw(30) << std::endl;
    std::cout << account.getBalance() << std::endl;
    std::cout << std::boolalpha << account.withdraw(200) << std::endl;
    return 0;
}
Terminal output
true
70
Insufficient funds.
false

Because balance is private, code outside the class can't write account.balance = -9999; to bypass withdraw — the only way in or out is through the public member functions, which enforce the insufficient-funds rule every time.

Note: writing setBalance to initialize an object, as this example does, works but is clunky — it's easy to forget to call it, leaving an object with a garbage, uninitialized balance. The next lesson, on constructors, fixes exactly that by letting you set required data up front, at the moment an object is created.