C++ Classes & Objects: Complete OOP Guide
What Are Classes?
A class is a blueprint for creating objects. It bundles data (member variables) and behavior (member functions) into a single unit, with access control to protect internal state. This is the core of object-oriented programming (OOP) in C++.
While structs default to public access, classes default to private — enforcing encapsulation. The outside world interacts with an object through its public interface, while the internal implementation stays hidden. This separation lets you change how a class works internally without breaking code that uses it.
Defining a Class
#include <iostream>
#include <string>
using namespace std;
class Dog {
private:
string name;
int age;
public:
// Constructor
Dog(string n, int a) : name(n), age(a) {}
// Member functions
void bark() const {
cout << name << " says: Woof!" << endl;
}
void info() const {
cout << name << ", age " << age << endl;
}
};
int main() {
Dog rex("Rex", 3);
Dog bella("Bella", 5);
rex.bark(); // Rex says: Woof!
bella.info(); // Bella, age 5
return 0;
}
Access Specifiers
#include <iostream>
using namespace std;
class Account {
public: // Accessible from anywhere
string owner;
void display() const {
cout << owner << ": $" << balance << endl;
}
protected: // Accessible in this class and subclasses
string account_type = "Savings";
private: // Accessible only in this class
double balance = 0;
public:
// Public interface to access private data
void deposit(double amount) {
if (amount > 0) balance += amount;
}
double get_balance() const { return balance; }
};
int main() {
Account acc;
acc.owner = "Alice"; // OK — public
acc.deposit(1000); // OK — public method
// acc.balance = 999; // ERROR — private
// acc.account_type; // ERROR — protected
acc.display(); // Alice: $1000
return 0;
}
Constructors
#include <iostream>
#include <string>
using namespace std;
class Player {
string name;
int health;
int level;
public:
// Default constructor
Player() : name("Unknown"), health(100), level(1) {
cout << "Default player created" << endl;
}
// Parameterized constructor
Player(string n, int h, int l) : name(n), health(h), level(l) {
cout << name << " created (L" << level << ")" << endl;
}
// Constructor with defaults
Player(string n) : name(n), health(100), level(1) {
cout << name << " created with defaults" << endl;
}
// Copy constructor
Player(const Player& other)
: name(other.name + " (copy)"), health(other.health), level(other.level) {
cout << name << " copied" << endl;
}
void info() const {
cout << name << " | HP:" << health << " | Lv:" << level << endl;
}
};
int main() {
Player p1; // Default constructor
Player p2("Hero", 150, 10); // Parameterized
Player p3("Mage"); // Single-arg constructor
Player p4 = p2; // Copy constructor
p1.info();
p2.info();
p3.info();
p4.info();
return 0;
}
Destructors
A destructor runs when an object goes out of scope or is deleted. It cleans up resources (memory, files, connections).
#include <iostream>
#include <string>
using namespace std;
class Logger {
string name;
public:
Logger(string n) : name(n) {
cout << "[" << name << "] Logger created" << endl;
}
~Logger() {
cout << "[" << name << "] Logger destroyed" << endl;
}
void log(string msg) const {
cout << "[" << name << "] " << msg << endl;
}
};
int main() {
Logger app("App");
app.log("Starting...");
{
Logger db("DB");
db.log("Connected");
} // db destroyed here
app.log("Continuing...");
return 0;
} // app destroyed here
// Output:
// [App] Logger created
// [App] Starting...
// [DB] Logger created
// [DB] Connected
// [DB] Logger destroyed
// [App] Continuing...
// [App] Logger destroyed
Getters and Setters
#include <iostream>
#include <string>
using namespace std;
class Temperature {
double celsius;
public:
Temperature(double c = 0) : celsius(c) {}
// Getter
double get_celsius() const { return celsius; }
double get_fahrenheit() const { return celsius * 9.0 / 5.0 + 32; }
double get_kelvin() const { return celsius + 273.15; }
// Setter with validation
void set_celsius(double c) {
if (c < -273.15) {
cout << "Error: below absolute zero!" << endl;
return;
}
celsius = c;
}
void set_fahrenheit(double f) {
set_celsius((f - 32) * 5.0 / 9.0);
}
};
int main() {
Temperature t(100);
cout << t.get_celsius() << " C = "
<< t.get_fahrenheit() << " F = "
<< t.get_kelvin() << " K" << endl;
t.set_fahrenheit(72);
cout << t.get_celsius() << " C" << endl;
t.set_celsius(-300); // Error: below absolute zero!
return 0;
}
const Member Functions
#include <iostream>
#include <string>
using namespace std;
class ImmutablePerson {
string name;
int age;
public:
ImmutablePerson(string n, int a) : name(n), age(a) {}
// const methods — cannot modify member variables
string get_name() const { return name; }
int get_age() const { return age; }
// non-const method — can modify
void birthday() { age++; }
};
void print_person(const ImmutablePerson& p) {
// Can only call const methods on const references
cout << p.get_name() << ", age " << p.get_age() << endl;
// p.birthday(); // ERROR — can't call non-const on const ref
}
int main() {
ImmutablePerson alice("Alice", 25);
print_person(alice);
alice.birthday();
print_person(alice);
return 0;
}
The this Pointer
this is a pointer to the current object. It is used when parameter names shadow member names, or for method chaining.
#include <iostream>
#include <string>
using namespace std;
class Builder {
string name;
int width = 0, height = 0;
string color = "white";
public:
Builder(string name) : name(name) {}
// Method chaining using *this
Builder& set_size(int width, int height) {
this->width = width; // this-> disambiguates
this->height = height;
return *this;
}
Builder& set_color(string color) {
this->color = color;
return *this;
}
void build() const {
cout << "Built " << name << ": " << width << "x" << height
<< " " << color << endl;
}
};
int main() {
Builder("Window")
.set_size(800, 600)
.set_color("blue")
.build();
// Output: Built Window: 800x600 blue
return 0;
}
Static Members
Static members belong to the class itself, not to any individual object. They are shared across all instances.
#include <iostream>
using namespace std;
class Counter {
static int total; // Shared across all instances
int id;
public:
Counter() : id(++total) {
cout << "Counter #" << id << " created" << endl;
}
~Counter() {
cout << "Counter #" << id << " destroyed" << endl;
total--;
}
static int get_total() { return total; }
int get_id() const { return id; }
};
// Must define static members outside the class
int Counter::total = 0;
int main() {
cout << "Total: " << Counter::get_total() << endl; // 0
Counter a, b, c;
cout << "Total: " << Counter::get_total() << endl; // 3
{
Counter d;
cout << "Total: " << Counter::get_total() << endl; // 4
} // d destroyed
cout << "Total: " << Counter::get_total() << endl; // 3
return 0;
}
Operator Overloading
#include <iostream>
using namespace std;
class Vec2 {
public:
double x, y;
Vec2(double x = 0, double y = 0) : x(x), y(y) {}
// Arithmetic operators
Vec2 operator+(const Vec2& other) const {
return {x + other.x, y + other.y};
}
Vec2 operator-(const Vec2& other) const {
return {x - other.x, y - other.y};
}
Vec2 operator*(double scalar) const {
return {x * scalar, y * scalar};
}
// Comparison
bool operator==(const Vec2& other) const {
return x == other.x && y == other.y;
}
// Stream output
friend ostream& operator<<(ostream& os, const Vec2& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
};
int main() {
Vec2 a(3, 4), b(1, 2);
cout << "a + b = " << (a + b) << endl; // (4, 6)
cout << "a - b = " << (a - b) << endl; // (2, 2)
cout << "a * 3 = " << (a * 3) << endl; // (9, 12)
cout << "a == b: " << (a == b) << endl; // 0
return 0;
}
Friend Functions
#include <iostream>
using namespace std;
class Box {
double width, height;
public:
Box(double w, double h) : width(w), height(h) {}
// Friend function — has access to private members
friend double area(const Box& b);
friend bool compare(const Box& a, const Box& b);
};
double area(const Box& b) {
return b.width * b.height; // Can access private members
}
bool compare(const Box& a, const Box& b) {
return area(a) > area(b);
}
int main() {
Box small_box(3, 4);
Box big_box(10, 8);
cout << "Area: " << area(small_box) << endl; // 12
cout << "Area: " << area(big_box) << endl; // 80
cout << "Big is bigger: " << compare(big_box, small_box) << endl;
return 0;
}
Separating Header and Source
// ===== player.h =====
#ifndef PLAYER_H
#define PLAYER_H
#include <string>
class Player {
std::string name;
int health;
public:
Player(std::string n, int h);
void take_damage(int amount);
void heal(int amount);
void print() const;
bool is_alive() const;
};
#endif
// ===== player.cpp =====
#include "player.h"
#include <iostream>
using namespace std;
Player::Player(string n, int h) : name(n), health(h) {}
void Player::take_damage(int amount) {
health -= amount;
if (health < 0) health = 0;
}
void Player::heal(int amount) {
health += amount;
if (health > 100) health = 100;
}
void Player::print() const {
cout << name << " [HP: " << health << "]" << endl;
}
bool Player::is_alive() const { return health > 0; }
// ===== main.cpp =====
// #include "player.h"
// int main() {
// Player hero("Knight", 100);
// hero.take_damage(30);
// hero.print(); // Knight [HP: 70]
// return 0;
// }
Real-World Example
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class TodoList {
struct Task {
string text;
bool done;
};
string name;
vector<Task> tasks;
public:
TodoList(string name) : name(name) {}
void add(string task) {
tasks.push_back({task, false});
}
void complete(int index) {
if (index >= 0 && index < tasks.size()) {
tasks[index].done = true;
}
}
void print() const {
cout << "=== " << name << " ===" << endl;
for (int i = 0; i < tasks.size(); i++) {
cout << (tasks[i].done ? "[x] " : "[ ] ")
<< i << ". " << tasks[i].text << endl;
}
int done = 0;
for (const auto& t : tasks) if (t.done) done++;
cout << done << "/" << tasks.size() << " completed" << endl;
}
};
int main() {
TodoList todo("Today");
todo.add("Learn C++ classes");
todo.add("Write practice code");
todo.add("Review lesson");
todo.complete(0);
todo.print();
return 0;
}
Practice Exercises
Exercise 1: Create a BankAccount class with private balance, deposit/withdraw methods with validation, and a transaction history.
Exercise 2: Create a Matrix class with operator overloading for +, *, and <<.
Exercise 3: Create a String class that wraps a char* with a constructor, copy constructor, destructor, and operator+.
Exercise 4: Build a simple LinkedList class with push_front, push_back, print, and destructor.
Summary
Classes are the foundation of object-oriented C++. You learned how to define classes with access specifiers, write constructors and destructors, use getters/setters, overload operators, work with static members and friend functions, and separate declarations from definitions. The key principle is encapsulation: hide internal state behind a public interface. In the next lessons, you will build on this foundation with inheritance, polymorphism, and the Rule of Three/Five.