Object-Oriented Programming

Reviewed & published by Brayan K

By the end of this lesson you'll be able to design your own C++ classes — bundling data with the functions that act on it, protecting that data behind a clean interface, and bringing objects to life with constructors. This is the paradigm that organises every large C++ program you'll ever read.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A class is a cookie cutter; an object is a cookie you stamp out with it. The cutter defines the shape (what data and behaviour every cookie has), but each cookie is its own thing with its own decorations (its own data). You can make a hundred cookies from one cutter, and changing the icing on one doesn't touch the others. The private part of a class is like the secret recipe sealed inside — people can eat the cookie (use its methods) without ever seeing, or messing up, the recipe.

1. Classes, Objects & Members

A class bundles together data members (the variables each object stores) and member functions, also called methods (the functions that act on that data). An object is one concrete instance of the class. You reach into an object with the . (dot) operator: myDog.bark(). Read this worked example, run it, and notice that each object keeps its own copy of the data.

#include <iostream>
#include <string>
using namespace std;

// A class is a BLUEPRINT. It bundles DATA (members) with the
// FUNCTIONS that work on that data (member functions / methods).
class Dog {
public:                      // anyone can touch what's below this label
    // --- data members (the state each object carries) ---
    string name;
    int ageYears;

    // --- member function (a method = a function that lives on the class) ---
    void bark() {
        cout << name << " says: Woof!" << endl;
    }

    void describe() {
        cout << name << " is " << ageYears << " years old." << endl;
    }
};

int main() {
    // An OBJECT is one concrete thing built from the blueprint.
    Dog d1;                  // d1 is a Dog object
    d1.name = "Rex";         // set its data members with the . (dot) operator
    d1.ageYears = 3;

    Dog d2;                  // a SECOND, independent object
    d2.name = "Bella";
    d2.ageYears = 5;

    d1.bark();               // Rex says: Woof!
    d1.describe();           // Rex is 3 years old.
    d2.describe();           // Bella is 5 years old.  (separate data!)

    return 0;
}

// ✅ Expected output:
//    Rex says: Woof!
//    Rex is 3 years old.
//    Bella is 5 years old.

Your turn. The Book class below is almost complete — fill in the two blanks marked ___ using the hints, then run it.

#include <iostream>
#include <string>
using namespace std;

class Book {
public:
    // 🎯 YOUR TURN — fill in the blanks marked with ___

    // 1) Two data members: a string "title" and an int "pages"
    string title;
    int ___;                 // 👉 name this member  pages

    // 2) A member function that prints the book's details
    void show() {
        cout << title << " (" << ___ << " pages)" << endl;  // 👉 print the pages member
    }
};

int main() {
    Book b;
    b.title = "C++ Primer";
    b.pages = 938;           // these lines already work once members exist
    b.show();

    // ✅ Expected output:
    //    C++ Primer (938 pages)
    return 0;
}

2. Access Specifiers & Encapsulation

Encapsulation means hiding an object's data and only letting the outside world touch it through safe, controlled methods. You enforce it with access specifiers: private: (only the class itself can see it), public: (anyone with an object can use it), and protected: (the class and any classes that inherit from it — you'll meet inheritance next lesson). The pattern is: keep data private, expose public methods. A method that only reads data is a getter; one that changes it (with validation) is a setter.

#include <iostream>
#include <string>
using namespace std;

class BankAccount {
private:                     // hidden from the outside world
    string owner;
    double balance;          // NOBODY can set this directly -> it stays valid

public:                      // the safe, controlled "front door"
    // Constructor: runs automatically when an object is created.
    // The : owner(name), balance(...) part is the INITIALIZER LIST.
    BankAccount(string name, double opening)
        : owner(name), balance(opening) {}

    // A controlled way to add money (a guarded "setter").
    void deposit(double amount) {
        if (amount > 0) balance += amount;          // validation lives HERE
    }

    // A "getter" — read-only access. const = "I won't change the object".
    double getBalance() const { return balance; }
    string getOwner() const { return owner; }
};

int main() {
    BankAccount acc("Alice", 100.0);   // constructor runs: balance starts at 100

    acc.deposit(50.0);                 // allowed, +50
    acc.deposit(-999.0);               // rejected by the guard -> ignored

    // acc.balance = 1000000;          // ❌ won't compile: balance is private

    cout << acc.getOwner() << " balance: $" << acc.getBalance() << endl;
    // Alice balance: $150
    return 0;
}

// ✅ Expected output:
//    Alice balance: $150

Now you try. Complete the constructor's initializer list and add the missing const to the getter:

#include <iostream>
#include <string>
using namespace std;

class Thermostat {
private:
    double tempC;            // private -> protected from bad values

public:
    // 🎯 YOUR TURN — complete the constructor and the getter.

    // 1) Constructor: store the starting temperature in tempC
    //    using an initializer list ( : member(value) )
    Thermostat(double start) : ___ {}   // 👉 write  tempC(start)

    // 2) A read-only getter that returns tempC
    double getTemp() ___ { return tempC; }   // 👉 add the  const  keyword

    void warmer() { tempC += 1.0; }   // already written for you
};

int main() {
    Thermostat t(20.0);
    t.warmer();
    t.warmer();
    cout << "Temp: " << t.getTemp() << "C" << endl;

    // ✅ Expected output:
    //    Temp: 22C
    return 0;
}

3. Constructors, this & Destructors

A constructor is a special method that runs automatically when an object is created — its job is to put the object in a valid starting state. A class can have several: a default constructor (no arguments) and one or more parameterized constructors. The cleanest way to set members is the initializer list — the : member(value) part written before the { body. Inside a method, this is a pointer to the current object, handy when a parameter shares a member's name (this->name = name;). The destructor (named ~ClassName) runs automatically when the object is destroyed — the place to release any resources it held.

#include <iostream>
#include <string>
using namespace std;

class Player {
private:
    string name;
    int score;

public:
    // Default constructor — no arguments. Used when you write  Player p;
    Player() : name("Guest"), score(0) {
        cout << "Default Player created: " << name << endl;
    }

    // Parameterized constructor — takes arguments.
    // 'this' is a pointer to THIS object; this->name means "my own name".
    Player(string name, int score) {
        this->name = name;     // disambiguates the member from the parameter
        this->score = score;
        cout << "Player created: " << name << endl;
    }

    void addPoints(int p) { score += p; }
    void show() const { cout << name << ": " << score << " pts" << endl; }

    // Destructor — runs automatically when the object is destroyed
    // (here, when main ends). Use it to clean up. Note the ~ prefix.
    ~Player() {
        cout << "Player " << name << " left the game." << endl;
    }
};

int main() {
    Player guest;                  // calls the DEFAULT constructor
    Player hero("Aria", 100);      // calls the PARAMETERIZED constructor

    hero.addPoints(50);
    guest.show();                  // Guest: 0 pts
    hero.show();                   // Aria: 150 pts

    return 0;                      // destructors fire here, in REVERSE order
}

// ✅ Expected output:
//    Default Player created: Guest
//    Player created: Aria
//    Guest: 0 pts
//    Aria: 150 pts
//    Player Aria left the game.
//    Player Guest left the game.

🔎 Deep Dive: struct vs class

In C++ a struct and a class are almost identical — both can have data, methods, and constructors. The only real difference is the default access level:

By convention, reach for struct when you just need a plain bundle of data with no hidden rules (like a 2D Point), and class when you want encapsulation. Picking the right one signals your intent to the next reader.

struct Point { double x, y; };   // x and y are public
class  Wallet { double cash; };  // cash is private (hidden)

Putting It Together: an Inventory

This program uses everything from the lesson at once — a plain struct Point, an encapsulated Inventory class with a constructor, a private vector, and public methods that control how it changes. Read it line by line; you understand every part now.

#include <iostream>
#include <string>
#include <vector>
using namespace std;

// A struct: like a class but members are PUBLIC by default.
// Great for plain bundles of data with no hidden rules.
struct Point {
    double x;
    double y;
};

// A class: members are PRIVATE by default -> use it when you want
// to protect data behind methods (encapsulation).
class Inventory {
private:
    string shopName;
    vector<string> items;

public:
    Inventory(string name) : shopName(name) {}

    void addItem(const string& item) {
        items.push_back(item);                 // controlled mutation
        cout << "Added '" << item << "' to " << shopName << endl;
    }

    int count() const { return items.size(); }

    void list() const {
        cout << shopName << " stock (" << count() << "):" << endl;
        for (const string& it : items) cout << "  - " << it << endl;
    }
};

int main() {
    Point origin{0.0, 0.0};                    // struct: set fields directly
    cout << "Origin at (" << origin.x << ", " << origin.y << ")" << endl;

    Inventory shop("Corner Store");
    shop.addItem("Milk");
    shop.addItem("Bread");
    shop.addItem("Eggs");
    shop.list();
    cout << "Total distinct items: " << shop.count() << endl;
    return 0;
}

// ✅ Expected output:
//    Origin at (0, 0)
//    Added 'Milk' to Corner Store
//    Added 'Bread' to Corner Store
//    Added 'Eggs' to Corner Store
//    Corner Store stock (3):
//      - Milk
//      - Bread
//      - Eggs
//    Total distinct items: 3

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

ConceptCodeMeaning
Define a classclass Dog { ... };Blueprint (note the ;)
Create an objectDog d;One instance
Access a memberd.name = "Rex";Dot operator
ConstructorDog(string n) : name(n) {}Runs on creation
Destructor~Dog() { ... }Runs on destruction
Read-only getterint get() constWon't modify object
The object itselfthis->namePointer to current object

Mini-Challenge: Rectangle Class

No blanks this time — just a brief and an outline to keep you on track. Build the class yourself, run it, and check your output against the example in the comments. This is exactly the kind of small, self-contained class real programs are built from.

#include <iostream>
#include <string>
using namespace std;

class Rectangle {
    // 🎯 MINI-CHALLENGE: a Rectangle class
    // 1. Private data members: double width and double height.
    // 2. A constructor that takes width and height (use an initializer list).
    // 3. A method  double area() const  that returns width * height.
    // 4. A method  double perimeter() const  that returns 2 * (width + height).
    //
    // ✅ Expected output (for 4 x 3):
    //    Area: 12
    //    Perimeter: 14

    // your code here
};

int main() {
    // Rectangle r(4.0, 3.0);
    // cout << "Area: " << r.area() << endl;
    // cout << "Perimeter: " << r.perimeter() << endl;
    return 0;
}

🎉 Lesson Complete

Practice quiz

What is the difference between a class and an object?

  • They are the same thing
  • An object is a blueprint; a class is an instance
  • A class is a blueprint; an object is one instance built from it
  • A class can only make one object

Answer: A class is a blueprint; an object is one instance built from it. A class is the blueprint; an object is one concrete instance of it.

What is the ONLY real difference between struct and class in C++?

  • Default access: struct is public, class is private
  • struct cannot have methods
  • class cannot have constructors
  • struct is faster

Answer: Default access: struct is public, class is private. struct members are public by default; class members are private by default.

Which operator reaches into an object to use a member or method?

  • -> arrow
  • :: scope
  • @
  • . (dot)

Answer: . (dot). You access an object's members with the . (dot) operator, e.g. myDog.bark().

When does a constructor run?

  • When the program ends
  • Automatically when an object is created
  • Only when you call it by name
  • Never automatically

Answer: Automatically when an object is created. A constructor runs automatically when an object is created, putting it in a valid starting state.

What does the ': owner(name), balance(opening)' part of a constructor represent?

  • A member initializer list
  • A function call
  • A comment
  • A return statement

Answer: A member initializer list. That is the member initializer list, which initializes members directly as the object is built.

Why make data members private?

  • It makes them faster
  • It is required for all members
  • Encapsulation — only controlled methods can change them, keeping state valid
  • So they print automatically

Answer: Encapsulation — only controlled methods can change them, keeping state valid. Private data is the core of encapsulation: the object can never reach an invalid state from outside.

What is 'this' inside a member function?

  • A copy of the object
  • A pointer to the current object
  • The class name
  • A reserved error value

Answer: A pointer to the current object. this is a pointer to the current object; this->name disambiguates a member from a same-named parameter.

When does a destructor (~ClassName) run for a local object?

  • When the program starts
  • Only if you call it manually
  • Halfway through main
  • When the object goes out of scope

Answer: When the object goes out of scope. A local object's destructor runs automatically when it goes out of scope, in reverse order of creation.

What does marking a getter 'const', e.g. 'double getBalance() const', promise?

  • It runs faster
  • The method won't modify the object
  • It returns a constant
  • It can only be called once

Answer: The method won't modify the object. A const member function promises not to change the object, so it can be called on const objects too.

What does adding 'public:' before members mean?

  • Only the class can use them
  • They become constants
  • Anyone with an object can use them
  • They are hidden

Answer: Anyone with an object can use them. public members can be accessed by anyone holding an object; private members cannot.

Continue this course

Frequently asked questions

What is the difference between a class and an object in C++?

A class is the blueprint — it defines what data members and member functions a thing has. An object is one concrete instance built from that blueprint. One Dog class can produce many Dog objects (Rex, Bella), each with its own independent data.

What is the difference between struct and class in C++?

Technically only the default access level: members of a struct are public by default, while members of a class are private by default. By convention, use struct for plain data bundles with no hidden rules, and class when you want encapsulation — protecting data behind methods.

Why should I make data members private?

Private data is the core of encapsulation. By hiding the data and only exposing controlled getters and setters, you guarantee an object can never hold an invalid state — a BankAccount balance can't be set to a nonsense value from outside, because the only way in is a deposit() method that validates first.

What does the colon in BankAccount(string n) : owner(n) mean?

That is a member initializer list. It initialises each data member directly as the object is built, before the constructor body runs. It is more efficient than assigning inside the body and is required for const members and references.

What is 'this' in C++?

this is a pointer to the current object — the specific instance a method was called on. You use this->name when a parameter has the same name as a data member, to say 'set MY name to the parameter'. You can write the member name on its own when there is no clash.

When does the destructor run?

Automatically, when an object is destroyed — for a local object that is when it goes out of scope (the end of the function). Destructors run in the reverse order objects were created and are where you release resources like memory or file handles.

Related lessons