C++ for Beginners Part 7: Classes and Object-Oriented Programming
Object-oriented programming lets you model the real world as objects with data and behavior. This article covers C++ classes from the ground up: member variables, member functions, constructors, destructors, access specifiers, and the principle of encapsulation — with a full BankAccount example.
C++ for Beginners: A Complete Guide
An eight-part beginner-friendly journey through C++: from writing your first program and understanding variables, through control flow, functions, and arrays, to the heart of C++ — pointers, classes, inheritance, and polymorphism. Each article is self-contained and builds on the previous, giving you both a quick reference and a progressive path to mastery.
- 4 C++ for Beginners Part 4: Functions
- 5 C++ for Beginners Part 5: Arrays, Strings, and std::vector
- 6 C++ for Beginners Part 6: Pointers and Memory Management
- 7 C++ for Beginners Part 7: Classes and Object-Oriented Programming
- 8 C++ for Beginners Part 8: Inheritance and Polymorphism
Table of Contents
What is OOP?
Object-Oriented Programming (OOP) organises code around objects — bundles of data (attributes) and behavior (methods). Instead of a loose collection of functions, you model the problem as interacting objects.
The four OOP pillars:
- Encapsulation — hiding internal details, exposing only what's needed
- Inheritance — deriving new types from existing ones (Part 8)
- Polymorphism — different types responding to the same interface (Part 8)
- Abstraction — working with concepts, not implementations
Defining a class
class Rectangle {
public:
double width;
double height;
double area() {
return width * height;
}
double perimeter() {
return 2 * (width + height);
}
};
A class defines a blueprint. An object is an instance of that blueprint:
Rectangle r; // create an object
r.width = 5.0;
r.height = 3.0;
std::cout << r.area(); // 15
std::cout << r.perimeter(); // 16
Access specifiers
C++ has three access levels:
| Specifier | Who can access |
|---|---|
public |
Anyone |
private |
Only class members |
protected |
Class members and derived classes |
Members are private by default in a class (unlike struct where they're public by default).
Encapsulation in practice
class BankAccount {
private:
std::string owner;
double balance;
public:
// Constructor
BankAccount(std::string name, double initialBalance) {
owner = name;
balance = (initialBalance >= 0) ? initialBalance : 0;
}
void deposit(double amount) {
if (amount > 0) balance += amount;
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
double getBalance() const {
return balance;
}
void print() const {
std::cout << owner << ": $" << balance << "
";
}
};
balance is private — outside code cannot set it to a negative number or modify it directly. All changes go through deposit() and withdraw(), which enforce business rules.
BankAccount acc("Alice", 1000.0);
acc.deposit(250.0);
acc.withdraw(100.0);
acc.print(); // Alice: $1150
// acc.balance = -5000; // compile error: balance is private
Constructors
A constructor is a special function called automatically when an object is created. It has the same name as the class and no return type:
class Point {
public:
double x, y;
// Default constructor — no arguments
Point() {
x = 0;
y = 0;
}
// Parameterized constructor
Point(double x, double y) {
this->x = x; // 'this->' distinguishes the member from the parameter
this->y = y;
}
};
Point p1; // calls default constructor → (0, 0)
Point p2(3.0, 4.0); // calls parameterized constructor → (3, 4)
Member initializer list (preferred)
Instead of assigning in the body, use the initializer list — it's more efficient and required for const members:
Point(double x, double y) : x(x), y(y) {}
The this pointer
Inside any member function, this is a pointer to the current object. Use it when a parameter name shadows a member name:
class Circle {
double radius;
public:
void setRadius(double radius) {
this->radius = radius; // this->radius is the member
}
};
const member functions
A const member function promises not to modify the object. Call it on const objects:
class Point {
public:
double x, y;
Point(double x, double y) : x(x), y(y) {}
double distanceFromOrigin() const {
return std::sqrt(x*x + y*y);
}
};
const Point p(3.0, 4.0);
std::cout << p.distanceFromOrigin(); // 5
// p.x = 1; // error: cannot modify const object
Mark any member function that doesn't modify state as const. It's a safety guarantee and allows the function to work on const objects.
Destructors
A destructor runs automatically when an object is destroyed (goes out of scope or is deleted). Used to release resources:
class FileWriter {
std::ofstream file;
public:
FileWriter(const std::string& filename) {
file.open(filename);
}
~FileWriter() { // destructor — note the ~
if (file.is_open()) {
file.close();
std::cout << "File closed.
";
}
}
void write(const std::string& text) {
file << text;
}
};
{
FileWriter fw("log.txt");
fw.write("Hello
");
} // fw goes out of scope here → destructor called → file closed
Static members
static members belong to the class itself, not to any instance. All objects share one copy:
class Counter {
public:
static int count;
Counter() { count++; }
~Counter() { count--; }
static int getCount() { return count; }
};
int Counter::count = 0; // must define static member outside class
Counter a, b, c;
std::cout << Counter::getCount(); // 3
{
Counter d;
std::cout << Counter::count; // 4
}
std::cout << Counter::count; // 3 (d was destroyed)
Separating declaration from definition
In real projects, class declarations go in headers (.h) and definitions in .cpp:
point.h
#pragma once
#include <cmath>
class Point {
public:
double x, y;
Point(double x, double y);
double distanceTo(const Point& other) const;
void print() const;
};
point.cpp
#include "point.h"
#include <iostream>
Point::Point(double x, double y) : x(x), y(y) {}
double Point::distanceTo(const Point& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx*dx + dy*dy);
}
void Point::print() const {
std::cout << "(" << x << ", " << y << ")
";
}
main.cpp
#include "point.h"
int main() {
Point p1(0, 0), p2(3, 4);
p1.print(); // (0, 0)
p2.print(); // (3, 4)
std::cout << p1.distanceTo(p2); // 5
}
Practical example: full BankAccount
#include <iostream>
#include <string>
#include <vector>
class BankAccount {
private:
std::string owner;
double balance;
std::vector<std::string> history;
void log(const std::string& entry) {
history.push_back(entry);
}
public:
BankAccount(const std::string& name, double initial = 0)
: owner(name), balance(initial >= 0 ? initial : 0) {
log("Account opened with $" + std::to_string(balance));
}
void deposit(double amount) {
if (amount <= 0) return;
balance += amount;
log("Deposited $" + std::to_string(amount));
}
bool withdraw(double amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
log("Withdrew $" + std::to_string(amount));
return true;
}
double getBalance() const { return balance; }
void printStatement() const {
std::cout << "=== " << owner << "'s Account ===
";
for (const auto& entry : history) std::cout << " " << entry << "
";
std::cout << " Balance: $" << balance << "
";
}
};
int main() {
BankAccount acc("Bob", 500.0);
acc.deposit(250.0);
acc.withdraw(100.0);
acc.withdraw(1000.0); // fails silently (insufficient funds)
acc.printStatement();
}
Output:
=== Bob's Account ===
Account opened with $500.000000
Deposited $250.000000
Withdrew $100.000000
Balance: $650
Key takeaways
- A class is a blueprint; an object is an instance.
privatedata withpublicmethods enforces encapsulation.- Constructors initialise objects; destructors clean up resources.
constmember functions cannot modify the object — mark them always.thisis a pointer to the current object — use it to disambiguate names.- In real projects, split class declarations (
.h) from definitions (.cpp).
Next up: inheritance and polymorphism — extending classes and writing code that works with any derived type.